MySQL neden bu sipariş için yürürlükte olsa bile endeksi görmezden geliyor?


14

Ben bir EXPLAIN:

mysql> explain select last_name from employees order by last_name;
+----+-------------+-----------+------+---------------+------+---------+------+-------+----------------+  
| id | select_type | table     | type | possible_keys | key  | key_len | ref  | rows  | Extra          |
+----+-------------+-----------+------+---------------+------+---------+------+-------+----------------+  
|  1 | SIMPLE      | employees | ALL  | NULL          | NULL | NULL    | NULL | 10031 | Using filesort |
+----+-------------+-----------+------+---------------+------+---------+------+-------+----------------+  
1 row in set (0.00 sec)  

Masamdaki dizinler:

mysql> show index from employees;  
+-----------+------------+---------------+--------------+---------------+-----------+-------------+----------+--------+------+------------+---------+---------------+  
| Table     | Non_unique | Key_name      | Seq_in_index | Column_name   | Collation | Cardinality | Sub_part | Packed | Null | Index_type | Comment | Index_comment |  
+-----------+------------+---------------+--------------+---------------+-----------+-------------+----------+--------+------+------------+---------+---------------+  
| employees |          0 | PRIMARY       |            1 | subsidiary_id | A         |           6 |     NULL | NULL   |      | BTREE      |         |               |  
| employees |          0 | PRIMARY       |            2 | employee_id   | A         |       10031 |     NULL | NULL   |      | BTREE      |         |               |  
| employees |          1 | idx_last_name |            1 | last_name     | A         |       10031 |      700 | NULL   |      | BTREE      |         |               |  
| employees |          1 | date_of_birth |            1 | date_of_birth | A         |       10031 |     NULL | NULL   | YES  | BTREE      |         |               |  
| employees |          1 | date_of_birth |            2 | subsidiary_id | A         |       10031 |     NULL | NULL   |      | BTREE      |         |               |  
+-----------+------------+---------------+--------------+---------------+-----------+-------------+----------+--------+------+------------+---------+---------------+  
5 rows in set (0.02 sec)  

Last_name üzerinde bir dizin var ancak optimizatör bunu kullanmıyor.
Ben de:

mysql> explain select last_name from employees force index(idx_last_name) order by last_name;  
+----+-------------+-----------+------+---------------+------+---------+------+-------+----------------+  
| id | select_type | table     | type | possible_keys | key  | key_len | ref  | rows  | Extra          |  
+----+-------------+-----------+------+---------------+------+---------+------+-------+----------------+  
|  1 | SIMPLE      | employees | ALL  | NULL          | NULL | NULL    | NULL | 10031 | Using filesort |  
+----+-------------+-----------+------+---------------+------+---------+------+-------+----------------+  
1 row in set (0.00 sec)  

Ama yine de dizin oluşturduğunu değil kullandı! Burada neyi yanlış yapıyorum?
Endeksin olmasıyla NON_UNIQUEmı ilgili? BTW son_adıVARCHAR(1000)

@RolandoMySQLDBA tarafından istenen güncelleme

mysql> SELECT COUNT(DISTINCT last_name) DistinctCount FROM employees;  
+---------------+  
| DistinctCount |  
+---------------+  
|         10000 |  
+---------------+  
1 row in set (0.05 sec)  


mysql> SELECT COUNT(1) FROM (SELECT COUNT(1) Count500,last_name FROM employees GROUP BY last_name HAVING COUNT(1) > 500) A;  
+----------+  
| COUNT(1) |  
+----------+  
|        0 |  
+----------+  
1 row in set (0.15 sec)  

Lütfen şu iki sorguyu çalıştırın: 1) SELECT COUNT(DISTINCT last_name) DistinctCount FROM employees;2) SELECT COUNT(1) FROM (SELECT COUNT(1) Count500,last_name FROM employees GROUP BY last_name HAVING COUNT(1) > 500) A;. Her sayımın sonucu nedir?
RolandoMySQLDBA

@RolandoMySQLDBA: OP'yi istediğiniz bilgi ile güncelledim.
Cratylus

İki sorgu daha, lütfen: 1) SELECT COUNT(1) FullTableCount FROM employees;ve 2) SELECT * FROM (SELECT COUNT(1) Count500,last_name FROM employees GROUP BY last_name HAVING COUNT(1) > 500) A LIMIT 10;.
RolandoMySQLDBA

Boş ver, ihtiyacım olan açıklamayı görüyorum.
RolandoMySQLDBA

2
@Cratylus yanlış bir yanıtı kabul ettiniz, Michael-sqlbot'un
miracle173

Yanıtlar:


6

SORUN # 1

Sorguya bak

select last_name from employees order by last_name;

Anlamlı bir WHERE deyimi görmüyorum ve MySQL Sorgu Optimize Edici de yok. Endeks kullanma teşviki yoktur.

SORUN # 2

Sorguya bak

select last_name from employees force index(idx_last_name) order by last_name; 

Ona bir indeks verdiniz, fakat Query Opitmizer devreye girdi. Daha önce bu davranışı gördüm ( nasıl bir JOIN MySQL belirli bir dizin kullanmaya zorlar? )

Bu neden olmalı?

Bir WHEREfıkra olmadan , Query Optimizer kendi kendine şunları söyler:

  • Bu bir InnoDB Tablosu
  • Dizine alınmış bir sütun
  • Dizin, gen_clust_index (Kümelenmiş Dizin olarak da bilinir)
  • Neden dizine bakmalıyım?
    • WHEREmadde yok mu?
    • Daima masaya geri dönmek zorunda kalır mıydım?
  • InnoDB tablosundaki tüm satırlar gen_clust_index ile aynı 16K bloklarda bulunduğundan, bunun yerine tam tablo taraması yapacağım.

Sorgu Optimize Edici, en az dirençli yolu seçti.

Biraz şok olacaksınız, ama işte gidiyor: Sorgu Optimize Edici'nin MyISAM'ı oldukça farklı şekilde ele alacağını biliyor muydunuz?

Muhtemelen HUH diyorsun ???? NASIL ????

MyISAM, verileri bir .MYDdosyada ve tüm dizinleri dosyada saklar .MYI.

Aynı sorgu farklı bir EXPLAIN planı üretecektir çünkü dizin verilerden farklı bir dosyada yaşıyor. Neden ? İşte nedeni:

  • Gereken veriler ( last_namesütun) zaten.MYI
  • En kötü durumda, tam bir dizin taraması yapacaksınız
  • Sütuna yalnızca last_namedizinden erişeceksiniz
  • İstenmeyen şeyleri gözden geçirmenize gerek yok
  • Sıralama için geçici dosya oluşturmayı tetiklemezsiniz

Bundan nasıl bu kadar emin olabilirsiniz? Farklı EXPLAIN planı (bazen daha iyi bir) üretecektir Farklı bir depolama kullanarak nasıl bu çalışma teorisini test ettik: o SİPARİŞ BY için kullanılacak için bir indeks kapağı tüm sütunları seçili olmalı?


1
-1 @Rolando bu cevap Michael-sqlbot'un doğru cevabından daha az kesin değildir, ancak yanlıştır, örneğin el kitabında şöyle der: "MySQL bu işlemler için dizinler kullanır: (...) Sıralama veya gruplama kullanılabilir bir dizinin en sol önekinde yapılır (...) ". Ayrıca yayınınızın diğer ifadelerine itiraz edilebilir. Bu yanıtı silmenizi veya yeniden çalışmanızı öneririm.
miracle173

Bu cevap doğru değil. Bir dizin, sıralamayı önlüyorsa WHERE yan tümcesi olmasa bile kullanılabilir.
istiridye

19

Aslında, buradaki sorun bunun bir önek dizinine benzemesi. Sorudaki tablo tanımını görmüyorum ama sub_part= 700? Tüm sütunu dizine eklemediniz, bu nedenle dizin sıralama için kullanılamaz ve kaplama dizini olarak da kullanışlı değildir. Yalnızca "eşleşebilecek" satırları bulmak için kullanılabilir WHEREve sunucu katmanı (depolama motorunun üstünde) eşleşen satırları daha fazla filtrelemek zorunda kalır. Soyadı için gerçekten 1000 karaktere ihtiyacınız var mı?


göstermek için güncelleştirme : Ben bir sütun içinde bir web sitesinin etki alanı adı domain_name VARCHAR(254) NOT NULLve hiçbir dizin ile 500 satırdan fazla bir litle ile bir tablo test tablo var .

mysql> alter table keydemo add key(domain_name);
Query OK, 0 rows affected (0.17 sec)
Records: 0  Duplicates: 0  Warnings: 0

Tam sütun dizine eklenmişse, sorgu dizini kullanır:

mysql> explain select domain_name from keydemo order by domain_name;
+----+-------------+---------+-------+---------------+-------------+---------+------+------+-------------+
| id | select_type | table   | type  | possible_keys | key         | key_len | ref  | rows | Extra       |
+----+-------------+---------+-------+---------------+-------------+---------+------+------+-------------+
|  1 | SIMPLE      | keydemo | index | NULL          | domain_name | 764     | NULL |  541 | Using index |
+----+-------------+---------+-------+---------------+-------------+---------+------+------+-------------+
1 row in set (0.01 sec)

Şimdi, bu dizini bırakacağım ve sadece domain_name'in ilk 200 karakterini dizine ekleyeceğim.

mysql> alter table keydemo drop key domain_name;
Query OK, 0 rows affected (0.11 sec)
Records: 0  Duplicates: 0  Warnings: 0

mysql> alter table keydemo add key(domain_name(200));
Query OK, 0 rows affected (0.08 sec)
Records: 0  Duplicates: 0  Warnings: 0

mysql> explain select domain_name from keydemo order by domain_name;
+----+-------------+---------+------+---------------+------+---------+------+------+----------------+
| id | select_type | table   | type | possible_keys | key  | key_len | ref  | rows | Extra          |
+----+-------------+---------+------+---------------+------+---------+------+------+----------------+
|  1 | SIMPLE      | keydemo | ALL  | NULL          | NULL | NULL    | NULL |  541 | Using filesort |
+----+-------------+---------+------+---------------+------+---------+------+------+----------------+
1 row in set (0.00 sec)

mysql>

Voila.

Ayrıca, 200 karakterden oluşan dizinin sütundaki en uzun değerden daha uzun olduğunu unutmayın ...

mysql> select max(length(domain_name)) from keydemo;
+--------------------------+
| max(length(domain_name)) |
+--------------------------+
|                       43 |
+--------------------------+
1 row in set (0.04 sec)

... ama bu bir fark yaratmıyor. Önek uzunluğu ile bildirilen bir dizin, tanım gereği tam sütun değerini içermediğinden, yalnızca aramalar için kullanılabilir, sıralama için değil, kaplama dizini olarak kullanılamaz.

Ayrıca, yukarıdaki sorgular bir InnoDB tablosunda çalıştırıldı, ancak bunları bir MyISAM tablosunda çalıştırmak neredeyse aynı sonuçları verir. Sadece bu durumda fark için InnoDB'nin saymak olduğunu rowsiki depolama motorları çok farklı indeks dalış ele yana normal davranıştır satırların tam sayısı (563) MyISAM gösterileri sırasında (541) biraz kapalıdır.

Yine de last_name sütununun gerekenden daha büyük olduğunu iddia ediyorum, ancak InnoDB kullanıyorsanız ve MySQL 5.5 veya 5.6 çalıştırıyorsanız hala tüm sütunu indekslemek mümkündür :

Varsayılan olarak, tek sütunlu bir dizin için bir dizin anahtarı 767 bayta kadar olabilir. Aynı uzunluk sınırı herhangi bir dizin anahtarı öneki için geçerlidir. Bkz. Bölüm 13.1.13, “ CREATE INDEXSözdizimi”. Örneğin, bir karakter kümesi ve her karakter için maksimum 3 bayt olduğu varsayılarak , bir TEXTveya VARCHARsütun üzerinde 255 karakterden fazla bir sütun önek diziniyle bu sınıra UTF-8ulaşabilirsiniz. Ne zaman innodb_large_prefixyapılandırma seçeneği etkinleştirildiğinde, bu uzunluk sınırı 3.072 bayt için yükseltilir InnoDBkullanmak tablolar DYNAMICve COMPRESSEDsatır biçimler.

- http://dev.mysql.com/doc/refman/5.5/en/innodb-restrictions.html


İlginç bir bakış açısı. Sütun, varchar(1000)ancak bu, ~ 750
Cratylus

8
Bu cevap kabul edilen cevap olmalıdır.
ypercubeᵀᴹ

1
@ypercube Bu cevap benimkinden daha kesindir. Yorumunuz için +1 ve bu yanıt için +1. Bu benimkinde kabul edilmeli.
RolandoMySQLDBA

1
@Timo, bu ilginç bir soru ... burada bağlam için yeni bir soru olarak yayınlamayı önerebilirim. Sürümün tamamının EXPLAIN SELECT ...yanı sıra SHOW CREATE TABLE ...ve SELECT @@VERSION;optimize edicideki sürümler arasında yapılan değişiklikler alakalı olabilir.
Michael - sqlbot

1
Şimdiye kadar yukarıdaki yorumumda istediğim gibi (en az 5.7 için) bir önek endeksinin boş indekslemeye yardımcı olmadığını bildirebilirim .
Timo

2

Bir yorum oluşturmayı desteklemeyeceği ve RolandoMySQL DBA gen_clust_index ve innodb hakkında konuştuğu için bir cevap verdim. Ve bu, innodb tabanlı bir tabloda çok önemlidir. Bu normal DBA bilgisinden daha ileri gider çünkü C kodunu analiz edebilmeniz gerekir.

Innodb kullanıyorsanız DAİMA HER ZAMAN BİR İLK ANAHTAR veya BENZERSİZ BİR ANAHTAR yapmalısınız. Eğer innodb kullanmazsanız, kendi yaratılan ROW_ID değerini kullanırsınız ki bu size faydadan çok zarar verebilir.

Kolay açıklamaya çalışacağım çünkü kanıt C koduna dayanıyor.

/**********************************************************************//**
Returns a new row id.
@return the new id */
UNIV_INLINE
row_id_t
dict_sys_get_new_row_id(void)
/*=========================*/
{
    row_id_t    id;

    mutex_enter(&(dict_sys->mutex));

    id = dict_sys->row_id;

    if (0 == (id % DICT_HDR_ROW_ID_WRITE_MARGIN)) {
          dict_hdr_flush_row_id();
    }

    dict_sys->row_id++;
    mutex_exit(&(dict_sys->mutex));
    return(id);
}

İlk sorun

mutex_enter (& (dict_sys-> muteksin));

Bu satır, aynı anda yalnızca bir iş parçacığının dict_sys-> mutex'e erişebilmesini sağlar. Zaten değer mutexed olsaydı ... evet bir iplik beklemek zorunda böylece iplik kilitleme gibi güzel bir rastgele özellik gibi bir şey olsun veya kendi PRIMARY KEY veya UNIQUE KEY olmadan daha fazla tablo varsa o zaman ile güzel bir özellik olurdu innodb ' masa kilitleme ', MyISAM'ın InnoDB ile değiştirilmesinin nedeni bu değil çünkü kayıt / sıra tabanlı kilitleme adı verilen güzel özellikten dolayı ..

İkinci sorun

(0 == (kimlik% DICT_HDR_ROW_ID_WRITE_MARGIN))

Her seferinde yeniden hesaplanması gerektiğinden toplu ekleme yapıyorsanız modulo (%) hesaplamaları yavaş değildir.

(0 == (kimlik & (DICT_HDR_ROW_ID_WRITE_MARGIN - 1)))

C derleyicisi optimize edilecek şekilde yapılandırılmışsa ve iyi bir optimize ediciyse, C optimize edici "ağır" kodu daha hafif sürüme sabitleyecektir

hikayenin sloganı her zaman kendi PRIMARY KEY'inizi oluşturun veya baştan bir tablo oluşturduğunuzda BENZERSİZ bir dizininizin olduğundan emin olun


Satır tabanlı çoğaltma ve satır kimliklerinin sunucular arasında tutarlı olmaması ve Raymond'un her zaman birincil anahtar oluşturma konusundaki anlamı daha da önemlidir.

Lütfen UNIQUEbunun yeterli olduğunu önermeyin; benzersiz dizinin PK'ya yükseltilmesi için yalnızca NULL olmayan sütunlar da içermesi gerekir.
Rick James

"modulo (%) hesaplamaları yavaş" - Daha da önemlisi INSERTbu fonksiyonda anın yüzde kaçıdır. Sanırım önemsiz. Sütunları kürekle harcamak için çaba harcayın, ara sıra bir blok bölünmesi, buffer_pool'daki çeşitli muteksler, değişiklik tamponu şeyleri vb.Dahil olmak üzere BTree işlemleri yapın
Rick James

Gerçek @RickJames tepegöz çok küçük bir sayı olabilir ama birçok küçük sayılar da toplanır (yine de bir mikro optimizasyon olurdu) .. İlk sorunun yanı sıra en sorun bazı
Raymond Nijland
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.