Wie entwerfe ich Indizes für Spalten mit NULL-Werten in MySQL?

11

Ich habe eine Datenbank mit 40 Millionen Einträgen und möchte Abfragen mit der folgenden WHEREKlausel ausführen

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

POP1ist eine Float-Spalte, die auch NULL sein kann. POP1 IS NOT NULLsollte etwa 50% der Einträge ausschließen, deshalb habe ich es am Anfang gesetzt. Alle anderen Begriffe reduzieren die Anzahl nur geringfügig.

Unter anderem habe ich einen Index entworfen pop1_vt_source, der anscheinend nicht verwendet wird, während ein Index mit vtder ersten Spalte verwendet wird. EXPLAIN-Ausgabe:

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

Warum wird der Index mit pop1als erste Spalte nicht verwendet? Wegen der NOToder wegen NULLim Allgemeinen. Wie kann ich das Design meiner Indizes und WHERE-Klauseln verbessern? Selbst wenn die Anzahl der Einträge auf 10 begrenzt ist, dauert die Abfrage länger als 30 Sekunden, obwohl die ersten 100 Einträge in der Tabelle die 10 Übereinstimmungen enthalten sollten.

Sven
quelle

Antworten:

10

Es ist das 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`;

gibt:

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

Erstellen Sie den Index:

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

SHOW INDEX FROM `myTab`;

gibt:

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

Erklären Sie nun die Auswahl. Es scheint, dass MySQL den Index verwendet, auch wenn Sie Folgendes verwenden 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 |
+----+-------------+-------+-------+---------------+------------+---------+------+------+--------------------------+

Beim Vergleich von NOT NULLund NULLscheint MySQL jedoch andere Indizes bei der Verwendung zu bevorzugen NOT NULL. Obwohl dies offensichtlich keine Informationen hinzufügt. Dies liegt daran, dass MySQL NOT NULLals Bereich interpretiert , wie Sie in der Typspalte sehen können. Ich bin mir nicht sicher, ob es eine Problemumgehung gibt:

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

Ich denke, es könnte eine bessere Implementierung in MySQL geben, da dies NULLein besonderer Wert ist. Wahrscheinlich interessieren sich die meisten Menschen für NOT NULLWerte.

John Garreth
quelle
3

Das Problem sind nicht die NULL-Werte. Dies ist die Selektivität des Index. In Ihrem Beispiel ist die Selektivität von source, pop1besser als die Selektivität von just pop1. Es deckt mehr Bedingungen in der whereKlausel ab, sodass es wahrscheinlicher ist, dass Seitentreffer reduziert werden.

Sie denken vielleicht, dass es ausreicht, die Anzahl der Zeilen um 50% zu reduzieren, aber das ist es wirklich nicht. Der Vorteil von Indizes in einer whereKlausel besteht darin, dass weniger Seiten gelesen werden. Wenn eine Seite im Durchschnitt mindestens einen Datensatz mit einem Wert ungleich NULL enthält, hat die Verwendung des Index keinen Vorteil. Und wenn es 10 Datensätze pro Seite gibt, hat fast jede Seite einen dieser Datensätze.

Sie könnten einen Index anprobieren (pop1, vt, source). Der Optimierer sollte diesen aufheben.

Wenn die whereKlausel jedoch keine Datensätze enthält - es gibt keine Regel, aber sagen wir 20% -, hilft der Index wahrscheinlich nicht weiter. Eine Ausnahme wäre, wenn der Index alle von der Abfrage benötigten Spalten enthält . Dann kann es die Abfrage erfüllen, ohne die Datenseite für jeden Datensatz aufzurufen.

Und wenn ein Index verwendet wird und die Selektivität hoch ist, kann die Leistung mit dem Index schlechter sein als die Leistung ohne ihn.

Gordon Linoff
quelle
Ich denke, es sind wirklich die Bereiche, die den Unterschied verursachen (siehe meine Antwort). Obwohl ich denke, dass es besser in MySQL implementiert werden könnte, da die meisten Leute an NOT NULLSpalten interessiert sind.