MySQL'in dizini kullanmayı seçmesi için seçilen tüm sütunların dizine eklenmesi gerçekten gerekli mi?
Bu, yüklü bir sorudur çünkü bir dizinin kullanılmaya değer olup olmadığını belirleyen faktörler vardır.
FAKTÖR # 1
Herhangi bir endeks için kilit popülasyon nedir? Başka bir deyişle, indekse kaydedilen tüm tuple'lerin kardinalitesi (ayrı sayım) nedir?
FAKTÖR # 2
Hangi depolama motorunu kullanıyorsunuz? Gerekli tüm sütunlara bir dizinden erişilebilir mi?
SIRADAKİ NE ???
Basit bir örnek verelim: iki değer içeren bir tablo (Erkek ve Kadın)
Dizin kullanımı testi ile böyle bir tablo oluşturalım
USE test
DROP TABLE IF EXISTS mf;
CREATE TABLE mf
(
id int not null auto_increment,
gender char(1),
primary key (id),
key (gender)
) ENGINE=InnODB;
INSERT INTO mf (gender) VALUES
('M'),('M'),('M'),('M'),('M'),('M'),('M'),('M'),
('M'),('M'),('M'),('M'),('F'),('F'),('M'),('M'),
('M'),('M'),('M'),('M'),('M'),('M'),('M'),('M'),
('M'),('M'),('M'),('M'),('M'),('M'),('M'),('M'),
('F'),('M'),('M'),('M'),('M'),('M'),('M'),('M');
ANALYZE TABLE mf;
EXPLAIN SELECT gender FROM mf WHERE gender='F';
EXPLAIN SELECT gender FROM mf WHERE gender='M';
EXPLAIN SELECT id FROM mf WHERE gender='F';
EXPLAIN SELECT id FROM mf WHERE gender='M';
TEST BİLGİLERİ
mysql> USE test
Database changed
mysql> DROP TABLE IF EXISTS mf;
Query OK, 0 rows affected (0.00 sec)
mysql> CREATE TABLE mf
-> (
-> id int not null auto_increment,
-> gender char(1),
-> primary key (id),
-> key (gender)
-> ) ENGINE=InnoDB;
Query OK, 0 rows affected (0.07 sec)
mysql> INSERT INTO mf (gender) VALUES
-> ('M'),('M'),('M'),('M'),('M'),('M'),('M'),('M'),
-> ('M'),('M'),('M'),('M'),('F'),('F'),('M'),('M'),
-> ('M'),('M'),('M'),('M'),('M'),('M'),('M'),('M'),
-> ('M'),('M'),('M'),('M'),('M'),('M'),('M'),('M'),
-> ('F'),('M'),('M'),('M'),('M'),('M'),('M'),('M');
Query OK, 40 rows affected (0.06 sec)
Records: 40 Duplicates: 0 Warnings: 0
mysql> ANALYZE TABLE mf;
+---------+---------+----------+----------+
| Table | Op | Msg_type | Msg_text |
+---------+---------+----------+----------+
| test.mf | analyze | status | OK |
+---------+---------+----------+----------+
1 row in set (0.00 sec)
mysql> EXPLAIN SELECT gender FROM mf WHERE gender='F';
+----+-------------+-------+------+---------------+--------+---------+-------+------+--------------------------+
| id | select_type | table | type | possible_keys | key | key_len | ref | rows | Extra |
+----+-------------+-------+------+---------------+--------+---------+-------+------+--------------------------+
| 1 | SIMPLE | mf | ref | gender | gender | 2 | const | 3 | Using where; Using index |
+----+-------------+-------+------+---------------+--------+---------+-------+------+--------------------------+
1 row in set (0.00 sec)
mysql> EXPLAIN SELECT gender FROM mf WHERE gender='M';
+----+-------------+-------+------+---------------+--------+---------+-------+------+--------------------------+
| id | select_type | table | type | possible_keys | key | key_len | ref | rows | Extra |
+----+-------------+-------+------+---------------+--------+---------+-------+------+--------------------------+
| 1 | SIMPLE | mf | ref | gender | gender | 2 | const | 37 | Using where; Using index |
+----+-------------+-------+------+---------------+--------+---------+-------+------+--------------------------+
1 row in set (0.00 sec)
mysql> EXPLAIN SELECT id FROM mf WHERE gender='F';
+----+-------------+-------+------+---------------+--------+---------+-------+------+--------------------------+
| id | select_type | table | type | possible_keys | key | key_len | ref | rows | Extra |
+----+-------------+-------+------+---------------+--------+---------+-------+------+--------------------------+
| 1 | SIMPLE | mf | ref | gender | gender | 2 | const | 3 | Using where; Using index |
+----+-------------+-------+------+---------------+--------+---------+-------+------+--------------------------+
1 row in set (0.00 sec)
mysql> EXPLAIN SELECT id FROM mf WHERE gender='M';
+----+-------------+-------+------+---------------+--------+---------+-------+------+--------------------------+
| id | select_type | table | type | possible_keys | key | key_len | ref | rows | Extra |
+----+-------------+-------+------+---------------+--------+---------+-------+------+--------------------------+
| 1 | SIMPLE | mf | ref | gender | gender | 2 | const | 37 | Using where; Using index |
+----+-------------+-------+------+---------------+--------+---------+-------+------+--------------------------+
1 row in set (0.00 sec)
mysql>
MyISAM TESTİ
mysql> USE test
Database changed
mysql> DROP TABLE IF EXISTS mf;
Query OK, 0 rows affected (0.00 sec)
mysql> CREATE TABLE mf
-> (
-> id int not null auto_increment,
-> gender char(1),
-> primary key (id),
-> key (gender)
-> ) ENGINE=MyISAM;
Query OK, 0 rows affected (0.05 sec)
mysql> INSERT INTO mf (gender) VALUES
-> ('M'),('M'),('M'),('M'),('M'),('M'),('M'),('M'),
-> ('M'),('M'),('M'),('M'),('F'),('F'),('M'),('M'),
-> ('M'),('M'),('M'),('M'),('M'),('M'),('M'),('M'),
-> ('M'),('M'),('M'),('M'),('M'),('M'),('M'),('M'),
-> ('F'),('M'),('M'),('M'),('M'),('M'),('M'),('M');
Query OK, 40 rows affected (0.00 sec)
Records: 40 Duplicates: 0 Warnings: 0
mysql> ANALYZE TABLE mf;
+---------+---------+----------+----------+
| Table | Op | Msg_type | Msg_text |
+---------+---------+----------+----------+
| test.mf | analyze | status | OK |
+---------+---------+----------+----------+
1 row in set (0.00 sec)
mysql> EXPLAIN SELECT gender FROM mf WHERE gender='F';
+----+-------------+-------+------+---------------+--------+---------+-------+------+--------------------------+
| id | select_type | table | type | possible_keys | key | key_len | ref | rows | Extra |
+----+-------------+-------+------+---------------+--------+---------+-------+------+--------------------------+
| 1 | SIMPLE | mf | ref | gender | gender | 2 | const | 3 | Using where; Using index |
+----+-------------+-------+------+---------------+--------+---------+-------+------+--------------------------+
1 row in set (0.00 sec)
mysql> EXPLAIN SELECT gender FROM mf WHERE gender='M';
+----+-------------+-------+------+---------------+--------+---------+-------+------+--------------------------+
| id | select_type | table | type | possible_keys | key | key_len | ref | rows | Extra |
+----+-------------+-------+------+---------------+--------+---------+-------+------+--------------------------+
| 1 | SIMPLE | mf | ref | gender | gender | 2 | const | 36 | Using where; Using index |
+----+-------------+-------+------+---------------+--------+---------+-------+------+--------------------------+
1 row in set (0.00 sec)
mysql> EXPLAIN SELECT id FROM mf WHERE gender='F';
+----+-------------+-------+------+---------------+--------+---------+-------+------+-------------+
| id | select_type | table | type | possible_keys | key | key_len | ref | rows | Extra |
+----+-------------+-------+------+---------------+--------+---------+-------+------+-------------+
| 1 | SIMPLE | mf | ref | gender | gender | 2 | const | 3 | Using where |
+----+-------------+-------+------+---------------+--------+---------+-------+------+-------------+
1 row in set (0.00 sec)
mysql> EXPLAIN SELECT id FROM mf WHERE gender='M';
+----+-------------+-------+------+---------------+------+---------+------+------+-------------+
| id | select_type | table | type | possible_keys | key | key_len | ref | rows | Extra |
+----+-------------+-------+------+---------------+------+---------+------+------+-------------+
| 1 | SIMPLE | mf | ALL | gender | NULL | NULL | NULL | 40 | Using where |
+----+-------------+-------+------+---------------+------+---------+------+------+-------------+
1 row in set (0.00 sec)
mysql>
InnoDB Analizi
Veriler InnoDB olarak yüklendiğinde, dört EXPLAIN
planın hepsinin gender
dizini kullandığını lütfen unutmayın . Üçüncü ve dördüncü EXPLAIN
planlar gender
, istenen veriler olmasına rağmen endeksi kullanmıştır id
. Neden? Çünkü id
içinde PRIMARY KEY
ve tüm ikincil dizinler PRIMARY KEY
( gen_clust_index üzerinden) ) .
MyISAM için analiz
Veriler MyISAM olarak yüklendiğinde, lütfen ilk üç EXPLAIN
planın gender
dizini kullandığını unutmayın . Dördüncü EXPLAIN
planda, Sorgu Optimize Edici hiç bir dizin kullanmamaya karar verdi. Bunun yerine tam bir masa taraması yapmayı seçti. Neden?
DBMS ne olursa olsun, Sorgu İyileştiricileri çok basit bir genel kural üzerinde çalışır: Bir dizin, arama yapmak için kullanılacak bir aday olarak taranıyorsa ve Sorgu Optimize Edici, toplam sayı tablodaki satırlar:
- alma için gerekli tüm sütunlar seçilen dizinde ise tam dizin taraması yapılır
- aksi takdirde tam tablo taraması
SONUÇ
Uygun örtme indeksleriniz yoksa veya herhangi bir demet için anahtar popülasyonu tablonun% 5'inden fazlaysa, altı şey olmalıdır:
- Sorguların profilini oluşturmanız gerektiğinin farkına varın
- Hepsini bul
WHERE
,GROUP BY
bu Sorgular gelen ve SİPARİŞ BY` maddeleri
- Dizinleri bu sırayla formüle et
WHERE
statik değerlere sahip yan tümce sütunları
GROUP BY
sütunlar
ORDER BY
sütunlar
- Tam Tablo Taramalardan Kaçının (Mantıklı olmayan sorgular
WHERE
madde )
- Kötü Anahtar Popülasyonlarından Kaçının (veya en azından bu Kötü Anahtar Popülasyonlarını önbelleğe alın)
- Tablolar için en iyi MySQL Depolama Motoruna ( InnoDB veya MyISAM ) karar verin
Geçmişte bu% 5 başparmak kuralı hakkında yazmıştım:
GÜNCELLEME 2012-11-14 13:05 EDT
Sorunuza ve orijinal SO yayınına bir göz attım . Sonra, Analysis for InnoDB
daha önce bahsettiğim şeyleri düşündüm . İle çakışıyorperson
. Neden?
Her iki tabloları için mf
veperson
- Depolama Motoru InnoDB
- Birincil Anahtar (şimdiki değeri)
id
- Tablo erişimi ikincil dizine göre yapılır
- Tablo MyISAM olsaydı, tamamen farklı bir
EXPLAIN
plan görürdük
Şimdi, SO sorudan sorguya bakın: select * from person order by age\G
. Hiçbir WHERE
madde olmadığından, açık bir şekilde tam tablo taraması talep ettiniz . Tablonun varsayılan sıralama düzeni id
, auto_increment nedeniyle (PRIMARY KEY) olur ve gen_clust_index (Kümelenmiş Dizin olarak da bilinir) dahili rowid tarafından sıralanır . Dizin tarafından sipariş ettiğinizde, InnoDB ikincil dizinlerinin her bir dizin girişine satır kimliğine sahip olduğunu unutmayın. Bu, her seferinde tam sıra erişim için dahili ihtiyacı üretir.
ORDER BY
InnoDB dizinlerinin nasıl düzenlendiğiyle ilgili bu gerçekleri görmezden gelirseniz, bir InnoDB tablosunda kurulum yapmak oldukça zor bir iş olabilir.
Bu SO sorgusuna geri dönersek, açıkça tam bir tablo taraması talep ettiğiniz için , MySQL Sorgu Doktoru IMHO doğru şeyi yaptı (veya en azından en az direnç yolunu seçti). InnoDB ve SO sorgusu söz konusu olduğunda, filesort
her ikincil dizin girişi için gen_clust_index aracılığıyla tam dizin taraması ve satır araması yapmak yerine tam tablo taraması ve ardından bazılarını gerçekleştirmek çok daha kolaydır .
EXPLAIN planını göz ardı ettiği için Index Hints kullanmanın savunucusu değilim. Buna rağmen, verilerinizi gerçekten InnoDB'den daha iyi biliyorsanız, özellikle WHERE
hükmü olmayan sorgularda Dizin İpuçlarına başvurmanız gerekecektir .
GÜNCELLEME 2012-11-14 14:21 EDT
MySQL Internals'ı Anlama kitabına göre
Paragraf 7, aşağıdakileri açıklar:
Veriler, birincil anahtarın anahtar değeri olarak hareket ettiği bir B-ağacı ve veri bölümünde gerçek kayıt (bir işaretçi yerine) olan kümelenmiş dizin adı verilen özel bir yapıda depolanır . Bu nedenle, her InnoDB tablosunun birincil anahtarı olmalıdır. Biri sağlanmazsa, birincil anahtar olarak işlev görmesi için normalde kullanıcı tarafından görülemeyen özel bir satır kimliği sütunu eklenir. İkincil bir anahtar, kaydı tanımlayan birincil anahtarın değerini depolar. B-ağacı kodu innobase / btr / btr0btr.c dosyasında bulunabilir .
Bu yüzden daha önce de belirtmiştim: her ikincil dizin girişi için gen_clust_index aracılığıyla tam dizin taraması ve satır araması yapmak yerine tam tablo taraması ve ardından bazı dosya sıralarını gerçekleştirmek çok daha kolay . InnoDB her seferinde çift endeks araması yapacak . Kulağa acımasız geliyor, ama bu sadece gerçekler. Yine, WHERE
madde eksikliğini göz önünde bulundurun . Bu, kendi başına, tam bir tablo taraması yapmak için MySQL Sorgu Optimize Edici'nin ipucudur.
FOR ORDER BY
(bu sorudaki özel durum budur). Soru, bu durumda depolama motorunun olduğunu belirttiInnoDB
(ve orijinal SO sorusu, 10k satırın 8 öğeye oldukça eşit bir şekilde dağıldığını gösteriyor, kardinalite de burada bir sorun olmamalı). Ne yazık ki, bunun soruyu cevapladığını sanmıyorum.