LoginSignup
1
0

More than 3 years have passed since last update.

UNIONを使うとWHERE a OR bより早いよ!って内容に対する調査

Last updated at Posted at 2018-09-27

概要

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)

このくらいだとさすがに違いが出る。

1
0
0

Register as a new user and use Qiita more conveniently

  1. You get articles that match your needs
  2. You can efficiently read back useful information
  3. You can use dark theme
What you can do with signing up
1
0