MySQL8.0でSELECT COUNT(*)が低速になる動作は8.0.37で解消されていた!

投稿日
2024/6/13
タグ
MySQL
Aurora
執筆者
鬼海雄太鬼海雄太
メディア統括本部 サービスリライアビリティグループ(SRG)の鬼海雄太(@fat47)です。
#SRG(Service Reliability Group)は、主に弊社メディアサービスのインフラ周りを横断的にサポートしており、既存サービスの改善や新規立ち上げ、OSS貢献などを行っているグループです。
本記事は、MySQL8.0で有名なSELECT COUNT(*)が遅くなるという事象が、MySQL8.0.37で解消された話を書いています。
なにかの役に立てば幸いです。
 

MySQL8.0ではSELECT COUNT(*)が低速になる


MySQL8.0ではSELECT COUNT(*)が低速になるというのは有名な話です。
 
実際にAurora MySQL Version3(MySQL8.0互換)へのアップグレードを進めるなかで、この問題に遭遇したことが何度かあります。
今まではクエリを変更してSELECT COUNTの利用をやめるなり、アプリケーション側で対応を進める必要がありました。
しかし、MySQL8.0のリリースから数年、最新のMySQL8.0ではこの問題が解消されたようです。

MySQL8.0.37で低速なのが解消された!?


2024年4月にMySQL8.0.37はリリースされています。
ですが、リリースノートをいくら眺めてもSELECT COUNT(*)の挙動が解消された記述は一切ありません。
 
しかし、bugs.mysqlに2019年に投稿されたページを見てみますと、
Bug #97709 MySQL 8 Select Count(*) is very slow
 
2024年4月に以下の投稿がされています。
[30 Apr 16:31] MySQL Verification Team
Thank you for the feedback and test case.
Degradation observed by you could be because the select count(*) must be using the clustered index instead of secondary index.
We have fixed this problem in 8.0.37 throughBug #112767 SELECT COUNT(*) degraded performance on 8.0 compared to 5.7 .  Please give it a try with 8.0.37 and report a new bug if you still face the problem.
Plain Text
📕
[30 Apr 16:31] MySQL検証チーム
フィードバックとテストケースをありがとうございます。 あなたが観測した劣化は、SELECT COUNT()がセカンダリインデックスの代わりにクラスタ化インデックスを使用していることが原因である可能性があります。 この問題は8.0.37のバグ#112767 SELECT COUNT()で修正しました。 8.0.37で試してみて、まだ問題に直面するようであれば、新しいバグを報告してください。
 
改めてリリースノートを見て、#112767の部分を見てみます。
InnoDB: MySQL no longer ignores the optimizer hint to use a secondary index scan, which instead forced a clustered (parallel) index scan. In addition, added the ability to provide an index hint that forces use of a clustered index. (Bug #100597, Bug #112767, Bug #31791868, Bug #35952353) References: This issue is a regression of: Bug #12978.
📕
InnoDB: MySQL はセカンダリ・インデックス・スキャンを使用するオプティマイザ・ヒントを無視しなくなった。さらに、クラスタ化インデックスの使用を強制するインデックスヒントを提供する機能が追加されました。(バグ#100597、バグ#112767、バグ#31791868、バグ#35952353) 参考文献 この問題はリグレッションです: Bug #12978のリグレッションです。
 
どうやらこの部分の修正でSELECT COUNT(*)の低速な部分が解消されているようです。

動作検証


再現検証は下記のbugs.mysqlの中に再現手順がありましたので、そちらの方法でおこないます。
 
テーブルの作成とデータの生成
DROP SCHEMA IF EXISTS example;
CREATE SCHEMA example;
USE example;

DROP TABLE IF EXISTS table1;
CREATE TABLE table1
(
    id     INT AUTO_INCREMENT,
    col_idx VARCHAR(28),
    col_data  LONGTEXT,
    PRIMARY KEY (id),
    KEY `idx` (`col_idx`)
) CHARSET = utf8mb4
  COLLATE = utf8mb4_general_ci;

DROP PROCEDURE IF EXISTS fill_data;
DELIMITER //

CREATE PROCEDURE fill_data()
BEGIN
    DECLARE i INT DEFAULT 0;
    WHILE i < 100000
        DO
            INSERT INTO table1 (col_idx, col_data)
            VALUES (REPEAT('a', 28),
                    REPEAT('a', 5000));
            SET i = i + 1;
        END WHILE;
END
//
DELIMITER ;
SET AUTOCOMMIT = true;
call fill_data();
SQL
 
このテーブルに対してSELECT COUNT(*)を実行すると、MySQL5.7では0.04秒程度で実行できていました。
$ mysql -uroot -e "SELECT VERSION();"
+------------+
| VERSION()  |
+------------+
| 5.7.41-log |
+------------+

mysql> select count(*) from table1;
+----------+
| count(*) |
+----------+
|   100000 |
+----------+
1 row in set (0.04 sec)
SQL
 
MySQL8.0.28の環境では以下のように0.8秒(約20倍)かかるようになってしまいました。
$ mysql -uroot -e "SELECT VERSION();"
+-----------+
| VERSION() |
+-----------+
| 8.0.28    |
+-----------+

mysql> select count(*) from table1;
+----------+
| count(*) |
+----------+
|   100000 |
+----------+
1 row in set (0.82 sec)
SQL
 
これをMySQL8.0.36まで上げてみます。
これは2024年6月現在、最新のAurora MySQL3.07.0との互換バージョンとなります。
$ mysql -uroot -e "SELECT VERSION();"
+-----------+
| VERSION() |
+-----------+
| 8.0.36    |
+-----------+

mysql> select count(*) from table1;
+----------+
| count(*) |
+----------+
|   100000 |
+----------+
1 row in set (0.90 sec)
SQL
このバージョンでもSELECT COUNTは低速なままです。
 
MySQL8.0.37まで上げてみます。
2024年6月現在、このバージョン互換のAurora MySQLはリリースされていません。
$ mysql -uroot -e "SELECT VERSION();"
+-----------+
| VERSION() |
+-----------+
| 8.0.37    |
+-----------+

mysql> select count(*) from table1;
+----------+
| count(*) |
+----------+
|   100000 |
+----------+
1 row in set (0.04 sec)
SQL
レスポンスが高速になったことが確認できました。
 
なお、私が担当している実際のサービスでもSELECT COUNTの低速化が確認されており、
そちらではMySQL5.7では0.6秒程度だったが、MySQL8.0では38秒もかかるようになっており、
サービスの継続が困難な状態となっていました。
mysql> select count(*) from entry;
+----------+
| count(*) |
+----------+
|  1473385 |
+----------+
1 row in set (38.94 sec)
SQL
同様にこの環境もMySQL8.0.37にアップグレードしたところ、5.7相当にレスポンスが改善されたことが確認できました。
$ mysql -uroot -e "SELECT VERSION();"
+-----------+
| VERSION() |
+-----------+
| 8.0.37    |
+-----------+

mysql> select count(*) from entry;
+----------+
| count(*) |
+----------+
|  1473385 |
+----------+
1 row in set (0.63 sec)
SQL
 

LTS MySQL8.4ではどうなの?


LTSのMySQL8.4のバージョンではこの改善は取り入れられているのでしょうか。
 
[16 Feb 22:07] Philip Olson
Posted by developer:

Fixed as of the upcoming MySQL Server 8.0.37 and 8.4.0 releases, and here's the proposed changelog entry from the documentation team:

A SELECT COUNT(*) query that used a secondary index for scanning would
perform much slower than the same query did in MySQL 5.7.

Thank you for the bug report.
Plain Text
どうやら8.4にも改善は取り入れられていそうです。
 
リリースノートを確認してみると、ちゃんと同様の記述がありました。
InnoDB: MySQL no longer ignores the optimizer hint to use a secondary index scan, which instead forced a clustered (parallel) index scan. In addition, added the ability to provide an index hint that forces use of a clustered index. (Bug #100597, Bug #112767, Bug #31791868, Bug #35952353)
 
一応動作確認をしてみましたが、ちゃんと高速化されていました。
mysql -uroot -e "SELECT VERSION();"
+-----------+
| VERSION() |
+-----------+
| 8.4.0     |
+-----------+

mysql> SELECT COUNT(*) FROM table1;
+----------+
| COUNT(*) |
+----------+
|   100000 |
+----------+
1 row in set (0.03 sec)
SQL

終わりに


MySQL8.0リリースから、SELECT COUNT(*)が低速になるのは有名な話でしたが、ようやくここに改善の手が入りました!
Aurora MySQLでは現時点で対応していないので、早く対応したバージョンがリリースされると嬉しいですね!
SRG では一緒に働く仲間を募集しています。 ご興味ありましたらぜひこちらからご連絡ください。
 
このエントリーをはてなブックマークに追加