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
절이 없으므로 명시 적으로 전체 테이블 스캔을 요구했습니다 . 테이블의 기본 정렬 순서는 id
auto_increment 및 gen_clust_index (일명 Clustered Index)로 인해 (PRIMARY KEY)이며 내부 rowid로 정렬됩니다 . 인덱스로 주문할 때 InnoDB 보조 인덱스에는 각 인덱스 항목에 행 ID가 첨부되어 있습니다. 이렇게하면 매번 전체 행 액세스가 필요합니다.
ORDER BY
InnoDB 인덱스 구성 방법에 대한 이러한 사실을 무시하면 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 개의 항목에 상당히 균일하게 분포되어 있으며 카디널리티도 문제가되지 않아야 함을 나타냅니다). 슬프게도, 이것이 이것이 질문에 대답한다고 생각하지 않습니다.