MySQL이 인덱스를 사용하도록 선택하려면 선택한 모든 열을 인덱스해야합니까?
인덱스를 사용할 가치가 있는지 여부를 결정하는 요소가 있기 때문에 이것은로드 된 질문입니다.
요인 # 1
주어진 인덱스의 주요 인구는 무엇입니까? 다시 말해, 인덱스에 기록 된 모든 튜플의 카디널리티 (고유 한 수)는 무엇입니까?
요인 # 2
어떤 스토리지 엔진을 사용하고 있습니까? 인덱스에서 필요한 모든 열에 액세스 할 수 있습니까?
무엇 향후 계획 ???
간단한 예를 들어 보겠습니다. 두 값을 보유하는 테이블 (남성과 여성)
색인 사용 테스트를 통해 이러한 테이블을 작성하십시오.
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';
테스트 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>
테스트 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>
InnoDB 분석
데이터가 InnoDB로로드 될 때 네 가지 EXPLAIN계획 모두 gender인덱스를 사용했습니다 . 세 번째 및 네 번째 EXPLAIN계획 gender은 요청 된 데이터가이지만 색인을 사용했습니다 id. 왜? id은 PRIMARY KEY및에있는 모든 보조 인덱스에 PRIMARY KEY( gen_clust_index 를 통해) 참조 포인터 가 있기 때문 입니다 .
MyISAM 분석
데이터가 MyISAM으로로드 될 때 처음 세 EXPLAIN계획은 gender인덱스를 사용했습니다 . 네 번째 EXPLAIN계획에서 Query Optimizer는 인덱스를 전혀 사용하지 않기로 결정했습니다. 대신 전체 테이블 스캔을 선택했습니다. 왜?
DBMS와 상관없이 Query Optimizer는 매우 간단한 룰에서 작동합니다. 색인을 조회 수행에 사용할 후보로 선별하고 Query Optimizer가 총 조회 수의 5 % 이상을 조회해야한다고 계산합니다. 테이블의 행 :
- 검색에 필요한 모든 열이 선택된 색인에 있으면 전체 색인 스캔이 수행됩니다.
- 그렇지 않으면 전체 테이블 스캔
결론
적절한 커버링 인덱스가 없거나 주어진 튜플의 주요 모집단이 테이블의 5 % 이상인 경우 6 가지 일이 발생해야합니다.
- 쿼리를 프로파일 링해야한다는 것을 깨달으십시오
- 모든 찾기
WHERE, GROUP BY그 쿼리에서, 그리고 ORDER BY` 절을
- 이 순서대로 인덱스를 공식화
WHERE 정적 값을 가진 절 열
GROUP BY 열
ORDER BY 열
- 전체 테이블 스캔 피하기 (현명한
WHERE절이 없는 쿼리 )
- 잘못된 키 채우기를 피하십시오 (또는 적어도 해당 잘못된 키 채우기를 캐시).
- 테이블에 가장 적합한 MySQL 스토리지 엔진 ( InnoDB 또는 MyISAM ) 결정
나는 과거 에이 5 %의 경험 법칙에 대해 썼습니다.
업데이트 2012-11-14 13:05 EDT
나는 당신의 질문과 원래의 SO 포스트를 되돌아 보았습니다 . 그런 다음 Analysis for InnoDB이전에 언급 한 내용에 대해 생각했습니다 . person테이블 과 일치 합니다. 왜?
테이블 mf과person
- 스토리지 엔진은 InnoDB
- 기본 키
id
- 테이블 액세스는 보조 인덱스에 의한 것입니다
- 테이블이 MyISAM이라면, 완전히 다른
EXPLAIN계획을 보게 될 것입니다
이제 SO 질문의 쿼리를 살펴보십시오 select * from person order by age\G. WHERE절이 없으므로 명시 적으로 전체 테이블 스캔을 요구했습니다 . 테이블의 기본 정렬 순서는 idauto_increment 및 gen_clust_index (일명 Clustered Index)로 인해 (PRIMARY KEY)이며 내부 rowid로 정렬됩니다 . 인덱스로 주문할 때 InnoDB 보조 인덱스에는 각 인덱스 항목에 행 ID가 첨부되어 있습니다. 이렇게하면 매번 전체 행 액세스가 필요합니다.
ORDER BYInnoDB 인덱스 구성 방법에 대한 이러한 사실을 무시하면 InnoDB 테이블에서 설정하는 것은 다소 어려운 작업이 될 수 있습니다.
SO 쿼리로 돌아가서 명시 적으로 전체 테이블 스캔을 요구 했기 때문에 MySQL Query Optimizer는 IMHO가 올바른 일을했습니다 (적어도 저항이 가장 적은 경로를 선택했습니다). InnoDB 및 SO 쿼리의 경우 filesort각 보조 인덱스 항목에 대해 gen_clust_index를 통해 전체 인덱스 스캔 및 행 조회를 수행하는 것보다 전체 테이블 스캔을 수행 한 다음 일부를 수행하는 것이 훨씬 쉽습니다 .
EXPLAIN 계획을 무시하기 때문에 Index Hints 사용을 옹호하지 않습니다. 그럼에도 불구하고 InnoDB보다 데이터를 더 잘 알고 있다면, 특히 WHERE절이 없는 쿼리를 사용하여 인덱스 힌트를 사용해야 합니다.
업데이트 2012-11-14 14:21 EDT
책에 따르면 MySQL 내부 이해하기

문단 7은 다음과 같이 말합니다.
데이터는 clustered index 라는 특수 구조에 저장 되는데, 이는 키 값으로 작동하는 기본 키와 데이터 부분의 실제 레코드 (포인터가 아닌)가있는 B- 트리입니다. 따라서 각 InnoDB 테이블에는 기본 키가 있어야합니다. 하나를 제공하지 않으면 일반적으로 사용자에게 표시되지 않는 특수 행 ID 열이 기본 키 역할을하도록 추가됩니다. 보조 키는 레코드를 식별하는 기본 키의 값을 저장합니다. B- 트리 코드는 innobase / btr / btr0btr.c에 있습니다.
이것이 내가 이전에 언급 한 이유 입니다. 각 보조 인덱스 항목에 대해 gen_clust_index를 통해 전체 인덱스 스캔 및 행 조회를 수행하는 것보다 전체 테이블 스캔을 수행 한 다음 일부 파일 정렬을 수행하는 것이 훨씬 쉽습니다 . InnoDB는 매번 이중 인덱스 조회를 수행 할 것 입니다. 그것은 잔인한 것처럼 들리지만 사실입니다. WHERE절의 부족을 다시 고려하십시오 . 이것 자체는 전체 테이블 스캔을 수행하기위한 MySQL Query Optimizer의 힌트입니다.
FOR ORDER BY(이 질문의 특정 경우). 이 경우 스토리지 엔진이 문제였습니다InnoDB(원래 SO 질문은 10k 행이 8 개의 항목에 상당히 균일하게 분포되어 있으며 카디널리티도 문제가되지 않아야 함을 나타냅니다). 슬프게도, 이것이 이것이 질문에 대답한다고 생각하지 않습니다.