概要
WHERE a > xx OR b > xxのようなクエリはUNION使った方が早いよ!って記事があったので「本当に!?」と思って調べてみました。
準備
環境
MySQL 5.7.22
テストデータの作成
CREATE TABLE `test` (
`id` int(10) unsigned NOT NULL AUTO_INCREMENT,
`a` int(11) NOT NULL DEFAULT 0,
`b` int(11) NOT NULL DEFAULT 0,
PRIMARY KEY (`id`),
KEY `a` (`a`),
KEY `b` (`b`)
) ENGINE=InnoDB;
insert into test () values ();
insert into test (id) selet 0 from test;
insert into test (id) selet 0 from test;
insert into test (id) selet 0 from test;
insert into test (id) selet 0 from test;
insert into test (id) selet 0 from test;
insert into test (id) selet 0 from test;
insert into test (id) selet 0 from test;
insert into test (id) selet 0 from test;
insert into test (id) selet 0 from test;
insert into test (id) selet 0 from test;
insert into test (id) selet 0 from test;
insert into test (id) selet 0 from test;
insert into test (id) selet 0 from test;
insert into test (id) selet 0 from test;
insert into test (id) selet 0 from test;
insert into test (id) selet 0 from test;
insert into test (id) selet 0 from test;
insert into test (id) selet 0 from test;
insert into test (id) selet 0 from test;
insert into test (id) selet 0 from test;
UPDATE test SET
a = CEIL(RAND() * 10000),
b = CEIL(RAND() * 10000);
※約10万レコードを追加し、各テストカラムを1 ~ 10000の値で初期化しました。
結果
mysql> SELECT * FROM test WHERE a > 5000 OR b > 5000;
...
785925 rows in set (0.52 sec)
mysql> SELECT * FROM test WHERE a > 5000 UNION SELECT * FROM test WHERE b > 5000;
...
785925 rows in set (4.46 sec)
mysql> SELECT * FROM test USE INDEX (a) WHERE a > 5000 UNION ALL SELECT * FROM test USE INDEX (b) WHERE b > 5000;
...
1047773 rows in set (1.89 sec)
明確にUNION使ったほうが遅い。
UNION ALLでも遅いので絶望的。
mysql> SELECT * FROM test USE INDEX (a) WHERE a > 5000 UNION SELECT * FROM test USE INDEX (b) WHERE b > 5000;
...
785925 rows in set (4.45 sec)
Indexを使用してもさほど変わらず。
mysql> SELECT * FROM test WHERE a = 5000 OR b = 5000;
...
192 rows in set (0.00 sec)
mysql> SELECT * FROM test WHERE a = 5000 UNION SELECT * FROM test WHERE b = 5000;
192 rows in set (0.00 sec)
'='の場合は、この件数だとどちらが早いか判断がつかない。
考察
- 結合する件数が増えるとUNIONを使った場合、明確に遅くなる。
- 2つのOR文においてIndexがパフォーマンスに有効に機能し、かつ結果の件数が少ない場合はUNIONの方が早いのかもしれない。(未調査)
とはいえローカルでただクエリを実行した結果でしかないため、あくまで正確な調査ではない。
クエリは「キャッシュ」や「メモリに何が乗るか」、「データの分布」の影響も強いため、本番でどう影響出るかはフラットに判断するべきだろう。
ただ少なくとも脳死的にUNIONサイコーってことではなさそう。
おまけ
Indexについて
本題のUNIONではなく、Index周りで予想と違う挙動があったため深堀調査。
x > 9100だとIndexが使われない。
mysql> EXPLAIN SELECT * FROM test WHERE a > 9100 UNION SELECT * FROM test WHERE b > 9100;
+----+--------------+------------+------------+------+---------------+------+---------+------+---------+----------+-----------------+
| id | select_type | table | partitions | type | possible_keys | key | key_len | ref | rows | filtered | Extra |
+----+--------------+------------+------------+------+---------------+------+---------+------+---------+----------+-----------------+
| 1 | PRIMARY | test | NULL | ALL | a | NULL | NULL | NULL | 1043718 | 17.68 | Using where |
| 2 | UNION | test | NULL | ALL | b | NULL | NULL | NULL | 1043718 | 18.25 | Using where |
| NULL | UNION RESULT | <union1,2> | NULL | ALL | NULL | NULL | NULL | NULL | NULL | NULL | Using temporary |
+----+--------------+------------+------------+------+---------------+------+---------+------+---------+----------+-----------------+
3 rows in set, 1 warning (0.00 sec)
9割(9万件以上)絞り込めててもIndexが使用されない?
昔はもっと緩い条件でもIndexが使われていたような。
この結果を見るに、範囲検索メインのカラムに対してIndex貼るのはあまり有効ではなさそうですね。
Index有無での速度調査
mysql> SELECT count(b) FROM test IGNORE INDEX (a) WHERE a > 5000;
+----------+
| count(b) |
+----------+
| 523664 |
+----------+
1 row in set (0.23 sec)
mysql> SELECT count(b) FROM test USE INDEX (a) WHERE a > 5000;
+----------+
| count(b) |
+----------+
| 523664 |
+----------+
1 row in set (0.23 sec)
変わらず。
この程度の件数じゃフルスキャンと変わらんよってことか。
mysql> SELECT * FROM test IGNORE INDEX (a) WHERE a = 5000;
...
101 rows in set (0.23 sec)
mysql> SELECT * FROM test USE INDEX (a) WHERE a = 5000;
...
101 rows in set (0.00 sec)
このくらいだとさすがに違いが出る。