Dizin, ORDER BY için kullanılacak tüm seçili sütunları kapsamalı mı?


15

SO üzerinde, yakın zamanda birisi neden ORDER BY dizini kullanmıyor diye sordu.

Durum, MySQL'de üç sütun ve 10k satır içeren basit bir InnoDB tablosu içeriyordu. Bir tamsayı olan sütunlardan biri dizine eklenmişti ve OP bu sütunda sıralanan tüm tablosunu almaya çalıştı:

SELECT * FROM person ORDER BY age

O ekli EXPLAINbu sorgu ile çözüldü gösteren çıktıyı filesort(ziyade endeksi yerine) ve bu olurdu neden sordu.

Rağmen ipucu FORCE INDEX FOR ORDER BY (age) kullanılacak endeksi neden , birileri cevap ki (başkalarından yorum / upvotes destekleyen) bir dizin sadece seçilen sütunları dizinden tüm okuma olduğunda sıralamak için kullanılır (yani normalde göstereceği gibi Using indexiçinde Extrakolona nın-ninEXPLAIN çıkış). Daha sonra, dizinin üzerinden geçip tablodan sütun getirmenin MySQL'in a'dan daha pahalı görüntülediği rasgele G / Ç ile sonuçlandığına dair bir açıklama yapıldı filesort.

Bu görünür manuel bölümün karşısında uçmak için ORDER BYOptimizasyon sadece tatmin dair güçlü bir izlenim taşır, ORDER BYbir dizinden aslında (ek şekilde sıralamak tercih edilir, filesortquicksort ve MergeSort bir kombinasyonudur ve bu nedenle gereken bir alt sınırı vardır dizinde sıraya girerken ve masaya bakarken - bu mükemmel bir anlam ifade eder), ama aynı zamanda bu iddia edilen "optimizasyon" dan bahsetmeyi de ihmal ederken:Ω(nlog n)O(n)

Aşağıdaki sorgular ORDER BYparçayı çözmek için dizini kullanır :

SELECT * FROM t1
  ORDER BY key_part1,key_part2,... ;

Benim okumaya göre, bu durumda tam olarak budur (yine de dizin açık bir ipucu olmadan kullanılmıyordu).

Sorularım:

  • MySQL'in dizini kullanmayı seçmesi için seçilen tüm sütunların dizine eklenmesi gerçekten gerekli mi?

    • Öyleyse, bu nerede belgelenir (eğer varsa)?

    • Değilse, burada neler oluyordu?

Yanıtlar:


14

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 EXPLAINplanın hepsinin genderdizini kullandığını lütfen unutmayın . Üçüncü ve dördüncü EXPLAINplanlar gender, istenen veriler olmasına rağmen endeksi kullanmıştır id. Neden? Çünkü idiçinde PRIMARY KEYve tüm ikincil dizinler PRIMARY KEY( gen_clust_index üzerinden) ) .

MyISAM için analiz

Veriler MyISAM olarak yüklendiğinde, lütfen ilk üç EXPLAINplanın genderdizini kullandığını unutmayın . Dördüncü EXPLAINplanda, 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:

  1. Sorguların profilini oluşturmanız gerektiğinin farkına varın
  2. Hepsini bul WHERE,GROUP BY bu Sorgular gelen ve SİPARİŞ BY` maddeleri
  3. 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
  4. Tam Tablo Taramalardan Kaçının (Mantıklı olmayan sorgular WHERE madde )
  5. Kötü Anahtar Popülasyonlarından Kaçının (veya en azından bu Kötü Anahtar Popülasyonlarını önbelleğe alın)
  6. 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 InnoDBdaha önce bahsettiğim şeyleri düşündüm . İle çakışıyorperson . Neden?

Her iki tabloları için mfveperson

  • 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 EXPLAINplan görürdük

Şimdi, SO sorudan sorguya bakın: select * from person order by age\G. Hiçbir WHEREmadde 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 BYInnoDB 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, filesorther 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 WHEREhü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

resim açıklamasını buraya girin

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, WHEREmadde eksikliğini göz önünde bulundurun . Bu, kendi başına, tam bir tablo taraması yapmak için MySQL Sorgu Optimize Edici'nin ipucudur.


Rolando, bu kadar ayrıntılı ve ayrıntılı bir cevap için teşekkür ederim. Ancak, dizinlerin seçilmesiyle ilgili görünmemektedir FOR ORDER BY(bu sorudaki özel durum budur). Soru, bu durumda depolama motorunun olduğunu belirtti InnoDB(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.
ay içinde eggyal

İlk bölüm benim ilk içgüdüm olduğu için bu ilginçti (iyi bir kardinalite yoktu, bu yüzden mysql tam taramayı kullanmayı seçti). Ancak okuduğumda, bu kural optimizasyon yoluyla sipariş için geçerli görünmüyordu. Innodb kümelenmiş dizinleri için birincil anahtarla sipariş verdiğinden emin misiniz? Bu gönderi birincil anahtarın sonuna eklendiğini belirtir, bu nedenle sıralama hala dizinin açık sütunlarında yer almaz mı? Kısacası, hala boğuldum!
Derek Downey

1
filesortSeçim basit bir nedenden dolayı Sorgu Doktoru tarafından üzerine karar verildi: Size sahip oldukları verilerin Önbilgin yoksundur. Dizin ipuçlarını kullanma seçiminiz (2. sayıya göre) size tatmin edici bir çalışma süresi getiriyorsa, elbette bunun için gidin. Verdiğim cevap, MySQL Sorgu Optimize Edici'nin ne kadar mizaçlı olabileceğini göstermek ve aynı zamanda eylem kursları önermek için yapılan akademik bir egzersizdi.
RolandoMySQLDBA

1
Ben bu ve diğer mesajları okudum ve yeniden okudum, ve ben sadece (çünkü bir kapsayan dizin değil) birincil anahtar üzerinde innodb sipariş ile ilgili olduğunu kabul edebilir. ORDER BY optimizasyon belgesi sayfasında bu InnoDB'ye özgü tuhaflıktan bahsedilmediğine şaşırdım. Her neyse, +1 Rolando
Derek Downey

1
@eggyal Bu hafta yazılmıştır. Aynı EXPLAIN planına dikkat edin ve veri seti belleğe sığmazsa tam tarama daha uzun sürer.
Derek Downey

0

Denis'in SO ile ilgili başka bir soruya vereceği yanıttan (izinle) uyarlanmıştır :

Tüm kayıtlar (veya neredeyse tümü) sorgu tarafından alınacağından, hiçbir dizin olmadan genellikle daha iyi durumda olursunuz. Bunun nedeni, aslında bir indeksi okumak için bir maliyete neden olmasıdır.

Tüm tabloya giderken, tabloyu sırayla okumak ve satırlarını bellekte sıralamak en ucuz planınız olabilir. Yalnızca birkaç satıra ihtiyacınız varsa ve çoğu nerede yan tümcesiyle eşleşirse, en küçük dizine gitmek hile yapar.

Nedenini anlamak için ilgili disk G / Ç'sini hayal edin.

Dizin olmadan tabloyu istediğinizi varsayalım. Bunu yapmak için, tablonun sonuna ulaşıncaya kadar, sırada yer alan çeşitli disk sayfalarını ziyaret ederek data_page1, data_page2, data_page3 vb. Sonra sıralayıp geri dönersiniz.

Dizin olmadan en üstteki 5 satırı istiyorsanız, üstteki 5 satırı yığınla sıralarken tablonun tamamını sırayla okursunuz. Kuşkusuz, bu bir avuç satır için çok fazla okuma ve sıralama.

Şimdi, tüm tabloyu bir dizinle istediğinizi varsayalım. Bunu yapmak için, sırasıyla index_page1, index_page2, vb. Bu, daha sonra, örneğin, veri_sayfa3'ü, ardından veri_sayfa1'i, sonra tekrar veri_sayfa3'ü, ardından veri_sayfa2 vb. İlgili IO, tüm karışıklığı sırayla okumayı ve hafızadaki tutma çantasını sıralamayı daha ucuz hale getirir.

Dizinlenmiş bir tablonun yalnızca ilk 5 satırını istiyorsanız, bunun yerine, dizini kullanmak doğru strateji haline gelir. En kötü senaryoda, belleğe 5 veri sayfası yükler ve devam edersiniz.

İyi bir SQL sorgu planlayıcısı olan btw, verilerinizin ne kadar parçalanmış olduğuna bağlı olarak bir dizin kullanılıp kullanılmayacağına karar verecektir. Satırları sırayla getirmek, tabloyu ileri geri yakınlaştırmak anlamına geliyorsa, iyi bir planlayıcı endeksi kullanmaya değmeyeceğine karar verebilir. Buna karşılık, tablo aynı dizin kullanılarak kümelenirse, satırların sıralı olacağı garanti edilir ve bu da kullanılma olasılığını artırır.

Başka bir tabloyla aynı sorguyu katılmak ve eğer Ama sonra, bundan başka tablo küçük bir dizin kullanabilirsiniz fıkra, o karar verebilir planlayıcısı olarak etiketlenir tüm satırları kimliklerini getirme aslında daha iyi, örneğin için nerede son derece seçici vardır foo, karma tablolara katılın ve bunları bellekte sıralayın.

Sitemizi kullandığınızda şunları okuyup anladığınızı kabul etmiş olursunuz: Çerez Politikası ve Gizlilik Politikası.
Licensed under cc by-sa 3.0 with attribution required.