web-dev-qa-db-ja.com

MySQLでNULL値を持つ列のインデックスを設計する方法は?

4,000万のエントリを持つデータベースがあり、次のWHERE句を使用してクエリを実行したい

...
WHERE
  `POP1` IS NOT NULL 
  && `VT`='ABC'
  && (`SOURCE`='HOME')
  && (`alt` RLIKE '^[AaCcGgTt]$')
  && (`ref` RLIKE '^[AaCcGgTt]$')
  && (`AA` RLIKE '^[AaCcGgTt]$')
  && (`ref` = `AA` || `alt` = `AA`)
LIMIT 10 ;

POP1は、NULLの場合もある浮動列です。 POP1 IS NOT NULLは、エントリの約50%を除外する必要があります。そのため、最初にそれを配置しました。他のすべての用語は、数をごくわずかに減らします。

とりわけ、私はインデックスを設計しましたpop1_vt_source、これは使用されていないようですが、最初の列がvtのインデックスが使用されています。 EXPLAIN-出力:

| id | select_type | table | type | possible_keys                          | key                 | key_len | ref         | rows     | Extra       |
|  1 | SIMPLE      | myTab | ref  | vt_source_pop1_pop2,pop1_vt_source,... | vt_source_pop1_pop2 | 206     | const,const | 20040021 | Using where |

なぜインデックスはpop1最初の列は使用されていませんか?一般にNOTまたはNULLが原因です。インデックスとWHERE句のデザインを改善するにはどうすればよいですか?テーブルの最初の100エントリには10個の一致が含まれているはずですが、10エントリに制限する場合でも、クエリには30秒以上かかります。

11
Sven

それはNOT NULLです:

CREATE TEMPORARY TABLE `myTab` (`notnul` FLOAT, `nul` FLOAT);
INSERT INTO `myTab` VALUES (1, NULL), (1, 2), (1, NULL), (1, 2), (1, NULL), (1, 2), (1, NULL), (1, 2), (1, NULL), (1, 2), (1, NULL), (1, 2);
SELECT * FROM `myTab`;

与える:

+--------+------+
| notnul | nul  |
+--------+------+
|      1 | NULL |
|      1 |    2 |
|      1 | NULL |
|      1 |    2 |
|      1 | NULL |
|      1 |    2 |
|      1 | NULL |
|      1 |    2 |
|      1 | NULL |
|      1 |    2 |
|      1 | NULL |
|      1 |    2 |
+--------+------+

インデックスを作成します。

CREATE INDEX `notnul_nul` ON `myTab` (`notnul`, `nul`);
CREATE INDEX `nul_notnul` ON `myTab` (`nul`, `notnul`);

SHOW INDEX FROM `myTab`;

与える:

+-------+------------+------------+--------------+-------------+-----------+-------------+----------+--------+------+------------+---------+---------------+
| Table | Non_unique | Key_name   | Seq_in_index | Column_name | Collation | Cardinality | Sub_part | Packed | Null | Index_type | Comment | Index_comment |
+-------+------------+------------+--------------+-------------+-----------+-------------+----------+--------+------+------------+---------+---------------+
| myTab |          1 | notnul_nul |            1 | notnul      | A         |          12 |     NULL | NULL   | YES  | BTREE      |         |               |
| myTab |          1 | notnul_nul |            2 | nul         | A         |          12 |     NULL | NULL   | YES  | BTREE      |         |               |
| myTab |          1 | nul_notnul |            1 | nul         | A         |          12 |     NULL | NULL   | YES  | BTREE      |         |               |
| myTab |          1 | nul_notnul |            2 | notnul      | A         |          12 |     NULL | NULL   | YES  | BTREE      |         |               |
+-------+------------+------------+--------------+-------------+-----------+-------------+----------+--------+------+------------+---------+---------------+

選択について説明します。あなたがNOT NULLを使用していても、MySQLはインデックスを使用しているようです:

EXPLAIN SELECT * FROM `myTab` WHERE `notnul` IS NOT NULL;
+----+-------------+-------+-------+---------------+------------+---------+------+------+--------------------------+ 
| id | select_type | table | type  | possible_keys | key        | key_len | ref  | rows | Extra                    |
+----+-------------+-------+-------+---------------+------------+---------+------+------+--------------------------+ 
|  1 | SIMPLE      | myTab | index | notnul_nul    | notnul_nul | 10      | NULL |   12 | Using where; Using index |
+----+-------------+-------+-------+---------------+------------+---------+------+------+--------------------------+


EXPLAIN SELECT * FROM `myTab` WHERE `nul` IS NOT NULL;
+----+-------------+-------+-------+---------------+------------+---------+------+------+--------------------------+
| id | select_type | table | type  | possible_keys | key        | key_len | ref  | rows | Extra                    |
+----+-------------+-------+-------+---------------+------------+---------+------+------+--------------------------+
|  1 | SIMPLE      | myTab | range | nul_notnul    | nul_notnul | 5       | NULL |    6 | Using where; Using index |
+----+-------------+-------+-------+---------------+------------+---------+------+------+--------------------------+

ただし、NOT NULLNULLを比較すると、MySQLはNOT NULLを使用するときに他のインデックスを優先するようです。これは明らかに情報を追加しませんが。これは、MySQLがtype-columnで確認できるようにNOT NULLを範囲として解釈するためです。回避策があるかどうかはわかりません:

EXPLAIN SELECT * FROM `myTab` WHERE `nul` IS NULL && notnul=2;
+----+-------------+-------+------+-----------------------+------------+---------+-------------+------+--------------------------+
| id | select_type | table | type | possible_keys         | key        | key_len | ref         | rows | Extra                    |
+----+-------------+-------+------+-----------------------+------------+---------+-------------+------+--------------------------+
|  1 | SIMPLE      | myTab | ref  | notnul_nul,nul_notnul | notnul_nul | 10      | const,const |    1 | Using where; Using index |
+----+-------------+-------+------+-----------------------+------------+---------+-------------+------+--------------------------+


EXPLAIN SELECT * FROM `myTab` WHERE `nul` IS NOT NULL && notnul=2;
+----+-------------+-------+-------+-----------------------+------------+---------+------+------+--------------------------+
| id | select_type | table | type  | possible_keys         | key        | key_len | ref  | rows | Extra                    |
+----+-------------+-------+-------+-----------------------+------------+---------+------+------+--------------------------+
|  1 | SIMPLE      | myTab | range | notnul_nul,nul_notnul | notnul_nul | 10      | NULL |    1 | Using where; Using index |
+----+-------------+-------+-------+-----------------------+------------+---------+------+------+--------------------------+

NULLは特別な値なので、MySQLでの実装が改善されると思います。おそらくほとんどの人はNOT NULLの値に興味を持っています。

10
John Garreth

問題はNULL値ではありません。インデックスの選択性です。あなたの例では、source, pop1の選択性はpop1だけの選択性よりも優れています。 where句の条件の多くをカバーしているため、ページヒットを減らす可能性が高くなります。

行数を50%減らすだけで十分だと思うかもしれませんが、実際はそうではありません。 where句のインデックスの利点は、読み込まれるページ数を減らすことです。ページに平均して、NULL以外の値を持つレコードが少なくとも1つある場合、インデックスを使用するメリットはありません。また、ページごとに10個のレコードがある場合、ほとんどすべてのページにそれらのレコードの1つが含まれます。

(pop1, vt, source)でインデックスを試してみてください。オプティマイザはそれを選択する必要があります。

しかし結局のところ、where句がレコードを失っている場合-規則はありませんが、20%としましょう-おそらくインデックスは役に立ちません。 1つの例外は、インデックスにallクエリに必要な列が含まれる場合です。次に、各レコードのデータページを読み込まなくてもクエリを実行できます。

また、インデックスが使用され、選択性が高い場合、インデックスを使用した場合のパフォーマンスは、インデックスを使用しない場合よりも低下する可能性があります。

3
Gordon Linoff