Apakah memang perlu untuk semua kolom yang dipilih diindeks agar MySQL dapat memilih untuk menggunakan indeks?
Ini adalah pertanyaan yang dimuat karena ada faktor yang menentukan apakah indeks layak digunakan.
FAKTOR # 1
Untuk indeks apa pun, berapa populasi kunci? Dengan kata lain, apa kardinalitas (jumlah berbeda) dari semua tupel yang dicatat dalam indeks?
FAKTOR # 2
Mesin penyimpanan apa yang Anda gunakan? Apakah semua kolom yang diperlukan dapat diakses dari indeks?
APA BERIKUTNYA ???
Mari kita ambil contoh sederhana: tabel yang memuat dua nilai (Pria dan Wanita)
Mari buat tabel seperti itu dengan tes untuk penggunaan indeks
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 InnoDB
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>
UJI MyISAM
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>
Analisis untuk InnoDB
Ketika data dimuat sebagai InnoDB, harap dicatat bahwa keempat EXPLAIN
paket menggunakan gender
indeks. Rencana ketiga dan keempat EXPLAIN
menggunakan gender
indeks meskipun data yang diminta id
. Mengapa? Karena id
ada dalam PRIMARY KEY
dan semua indeks sekunder memiliki pointer referensi kembali ke PRIMARY KEY
(melalui gen_clust_index ).
Analisis untuk MyISAM
Ketika data dimuat sebagai MyISAM, harap dicatat bahwa tiga EXPLAIN
paket pertama menggunakan gender
indeks. Dalam paket keempat EXPLAIN
, Pengoptimal Kueri memutuskan untuk tidak menggunakan indeks sama sekali. Itu memilih untuk pemindaian tabel penuh sebagai gantinya. Mengapa?
Terlepas dari DBMS, Pengoptimal Kueri beroperasi pada aturan praktis yang sangat sederhana: Jika indeks sedang disaring sebagai kandidat yang akan digunakan untuk melakukan pencarian dan Pengoptimal Kueri menghitung bahwa ia harus mencari lebih dari 5% dari total jumlah baris dalam tabel:
- pemindaian indeks lengkap dilakukan jika semua kolom yang diperlukan untuk pengambilan berada dalam indeks yang dipilih
- pemindaian tabel lengkap sebaliknya
KESIMPULAN
Jika Anda tidak memiliki indeks cakupan yang tepat, atau jika populasi kunci untuk setiap tuple yang diberikan lebih dari 5% dari tabel, enam hal harus terjadi:
- Datanglah ke kesadaran bahwa Anda harus membuat profil kueri
- Temukan semua
WHERE
,, GROUP BY
dan klausa ORDER BY` dari Query tersebut
- Formulasikan indeks dalam urutan ini
WHERE
klausa kolom dengan nilai statis
GROUP BY
kolom
ORDER BY
kolom
- Hindari Pemindaian Tabel Penuh (Kueri yang tidak memiliki
WHERE
klausa yang masuk akal )
- Hindari Populasi Key Buruk (atau setidaknya cache populasi Key Buruk itu)
- Tentukan Mesin Penyimpanan MySQL terbaik ( InnoDB atau MyISAM ) untuk Tabel
Saya telah menulis tentang aturan praktis 5% ini di masa lalu:
UPDATE 2012-11-14 13:05 EDT
Saya melihat kembali pertanyaan Anda dan pada posting SO asli . Kemudian, saya memikirkan tentang yang saya Analysis for InnoDB
sebutkan sebelumnya. Itu bertepatan dengan person
meja. Mengapa?
Untuk tabel mf
danperson
- Mesin Penyimpanan adalah InnoDB
- Kunci Utama adalah
id
- Akses tabel adalah dengan indeks sekunder
- Jika tabel adalah MyISAM, kita akan melihat
EXPLAIN
rencana yang sama sekali berbeda
Sekarang, melihat query dari pertanyaan SO: select * from person order by age\G
. Karena tidak ada WHERE
klausa, Anda secara eksplisit menuntut pemindaian tabel penuh . Urutan sortir default tabel adalah oleh id
(PRIMARY KEY) karena auto_increment dan gen_clust_index (alias Clustered Index) dipesan oleh rowid internal . Ketika Anda memesan oleh indeks, perlu diingat bahwa indeks sekunder InnoDB memiliki rowid yang melekat pada setiap entri indeks. Ini menghasilkan kebutuhan internal untuk akses baris penuh setiap kali.
Menyiapkan ORDER BY
tabel InnoDB bisa menjadi tugas yang agak menakutkan jika Anda mengabaikan fakta-fakta ini tentang bagaimana indeks InnoDB diatur.
Kembali ke permintaan SO, karena Anda secara eksplisit menuntut pemindaian tabel penuh , IMHO MySQL Query Optimizer melakukan hal yang benar (atau setidaknya, memilih jalur yang paling tidak resistan). Ketika datang ke InnoDB dan permintaan SO, jauh lebih mudah untuk melakukan pemindaian tabel penuh dan kemudian beberapa filesort
daripada melakukan pemindaian indeks penuh dan pencarian baris melalui gen_clust_index untuk setiap entri indeks sekunder.
Saya bukan penganjur menggunakan Petunjuk Indeks karena mengabaikan rencana MENJELASKAN. Meskipun demikian, jika Anda benar-benar mengetahui data Anda lebih baik daripada InnoDB, Anda harus beralih ke Petunjuk Indeks, terutama dengan kueri yang tidak memiliki WHERE
klausa.
UPDATE 2012-11-14 14:21 EDT
Menurut buku Memahami MySQL Internal
Paragraf 7 mengatakan:
Data disimpan dalam struktur khusus yang disebut indeks berkerumun , yang merupakan pohon-B dengan kunci utama yang bertindak sebagai nilai kunci, dan catatan aktual (bukan penunjuk) di bagian data. Dengan demikian, setiap tabel InnoDB harus memiliki kunci utama. Jika tidak disediakan, kolom ID baris khusus yang biasanya tidak terlihat oleh pengguna ditambahkan untuk bertindak sebagai kunci utama. Kunci sekunder akan menyimpan nilai kunci utama yang mengidentifikasi catatan. Kode B-tree dapat ditemukan di innobase / btr / btr0btr.c .
Inilah sebabnya saya nyatakan sebelumnya: jauh lebih mudah untuk melakukan pemindaian tabel penuh dan kemudian beberapa filesort daripada melakukan pemindaian indeks penuh dan pencarian baris melalui gen_clust_index untuk setiap entri indeks sekunder . InnoDB akan melakukan pencarian indeks ganda setiap kali . Kedengarannya brutal, tapi itu faktanya. Sekali lagi, pertimbangkan kurangnya WHERE
klausa. Ini, dengan sendirinya, adalah petunjuk untuk Pengoptimal Permintaan MySQL untuk melakukan pemindaian tabel penuh.
FOR ORDER BY
(yang merupakan kasus khusus dalam pertanyaan ini). Pertanyaannya memang menyatakan bahwa dalam hal ini mesin penyimpananInnoDB
(dan pertanyaan SO asli menunjukkan bahwa baris 10k didistribusikan secara merata di 8 item, kardinalitas juga tidak boleh menjadi masalah di sini). Sayangnya, saya tidak berpikir bahwa ini menjawab pertanyaan.