Bagaimana cara mendesain indeks untuk kolom dengan nilai NULL di MySQL?

11

Saya memiliki database dengan 40 juta entri dan ingin menjalankan kueri dengan WHEREklausa berikut

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

POP1adalah kolom float yang juga bisa NULL. POP1 IS NOT NULLharus mengecualikan sekitar 50% dari entri, itu sebabnya saya meletakkannya di awal. Semua istilah lain hanya mengurangi sedikit.

Di antara yang lain, saya merancang indeks pop1_vt_source, yang tampaknya tidak digunakan, sedangkan indeks dengan vtsebagai kolom pertama digunakan. MENJELASKAN-output:

| 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 |

Mengapa indeks dengan pop1kolom pertama tidak digunakan? Karena NOTatau karena NULLpada umumnya. Bagaimana saya bisa meningkatkan desain indeks dan klausa WHERE saya? Bahkan ketika membatasi hingga 10 entri, kueri membutuhkan lebih dari 30 detik, meskipun 100 entri pertama dalam tabel harus berisi 10 kecocokan.

Sven
sumber

Jawaban:

10

Itu adalah 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`;

memberi:

+--------+------+
| 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 |
+--------+------+

Buat indeks:

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

SHOW INDEX FROM `myTab`;

memberi:

+-------+------------+------------+--------------+-------------+-----------+-------------+----------+--------+------+------------+---------+---------------+
| 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      |         |               |
+-------+------------+------------+--------------+-------------+-----------+-------------+----------+--------+------+------------+---------+---------------+

sekarang jelaskan pilihannya. Tampaknya MySQL menggunakan indeks, bahkan jika Anda menggunakan NOT NULL:

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 |
+----+-------------+-------+-------+---------------+------------+---------+------+------+--------------------------+

Tapi, ketika membandingkan NOT NULLdan NULL, sepertinya MySQL lebih suka indeks lain saat menggunakan NOT NULL. Meskipun ini jelas tidak menambah informasi apa pun. Ini karena MySQL menerjemahkan NOT NULLsebagai rentang seperti yang Anda lihat di kolom-jenis. Saya tidak yakin Jika ada solusi:

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 |
+----+-------------+-------+-------+-----------------------+------------+---------+------+------+--------------------------+

Saya pikir mungkin ada implementasi yang lebih baik di MySQL, karena NULLmerupakan nilai khusus. Mungkin sebagian besar orang tertarik pada NOT NULLnilai.

John Garreth
sumber
3

Masalahnya bukan nilai NULL. Ini adalah selektivitas indeks. Dalam contoh Anda, selektivitas source, pop1lebih baik daripada selektivitas adil pop1. Ini mencakup lebih banyak kondisi dalam whereklausa, sehingga lebih mungkin untuk mengurangi klik halaman.

Anda mungkin berpikir bahwa mengurangi jumlah baris hingga 50% sudah cukup, tetapi sebenarnya tidak. Manfaat indeks dalam whereklausa adalah mengurangi jumlah halaman yang sedang dibaca. Jika sebuah halaman memiliki, rata-rata, setidaknya satu catatan dengan nilai non-NULL, maka tidak ada keuntungan menggunakan indeks. Dan, jika ada 10 catatan per halaman, maka hampir setiap halaman akan memiliki salah satu catatan itu.

Anda dapat mencoba indeks pada (pop1, vt, source) . Pengoptimal harus memilih yang itu.

Pada akhirnya, meskipun, jika whereklausa itu hilang catatan - tidak ada aturan tapi katakanlah 20% - maka indeks mungkin tidak akan membantu. Satu pengecualian adalah ketika indeks berisi semua kolom yang dibutuhkan oleh kueri. Maka itu dapat memenuhi permintaan tanpa membawa halaman data untuk setiap catatan.

Dan, jika indeks digunakan dan selektivitasnya tinggi, maka kinerja dengan indeks bisa lebih buruk daripada kinerja tanpanya.

Gordon Linoff
sumber
Saya pikir itu benar-benar rentang yang menyebabkan perbedaan (lihat jawaban saya). Walaupun saya pikir itu bisa lebih baik diimplementasikan di MySQL, karena kebanyakan orang tertarik pada NOT NULLkolom.