MySQL은 왜이 명령에 대해 강제로 색인을 무시합니까?


14

나는 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)  

내 테이블의 인덱스 :

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에 색인이 있지만 옵티마이 저가이를 사용하지 않습니다.
그래서 나는 :

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)  

그러나 여전히 인덱스는 사용 되지 않습니다 ! 내가 여기서 뭘 잘못하고 있니?
인덱스가 사실이라는 것과 관련이 NON_UNIQUE있습니까? BTW 성은VARCHAR(1000)

@RolandoMySQLDBA가 요청한 업데이트

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)  

이 두 쿼리를 실행하십시오 : 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;. 각 카운트의 결과는 무엇입니까?
RolandoMySQLDBA

@RolandoMySQLDBA : 요청한 정보로 OP를 업데이트했습니다.
Cratylus

두 가지 질문이 더 있습니다 : 1) SELECT COUNT(1) FullTableCount FROM employees;및 2) SELECT * FROM (SELECT COUNT(1) Count500,last_name FROM employees GROUP BY last_name HAVING COUNT(1) > 500) A LIMIT 10;.
RolandoMySQLDBA

신경 쓰지 마라, 내가 필요한 것을 설명해 준다.
RolandoMySQLDBA

2
@Cratylus 당신은 잘못된 답변을 수락, 당신은 Michael-sqlbot의 정답을
miracle173

답변:


6

문제 # 1

검색어를보십시오

select last_name from employees order by last_name;

의미있는 WHERE 절이 표시되지 않으며 MySQL Query Optimizer도 마찬가지입니다. 인덱스를 사용하는 동기는 없습니다.

문제 # 2

검색어를보십시오

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

색인을 주었지만 Query Opitmizer가 대신했습니다. 내가 전에이 문제를 보았다 ( 나는이 MySQL의 특정 인덱스를 사용하는 가입 강요하는 걸까 어떻게? )

왜 이런 일이 발생합니까?

WHERE절이 없으면 Query Optimizer는 다음과 같이 말합니다.

  • 이것은 InnoDB 테이블입니다
  • 인덱스 열입니다.
  • 인덱스에는 gen_clust_index (일명 Clustered Index) 의 row_id가 있습니다.
  • 왜 색인을 봐야합니까?
    • 어떤이없는 WHERE절은?
    • 나는 항상 테이블로 튕겨 야합니까?
  • InnoDB 테이블의 모든 행은 gen_clust_index와 동일한 16K 블록에 있으므로 대신 전체 테이블 스캔을 수행합니다.

Query Optimizer는 최소 저항 경로를 선택했습니다.

당신은 약간의 충격을받을 것입니다. 그러나 여기에 있습니다 : Query Optimizer가 MyISAM을 상당히 다르게 처리 할 것이라는 것을 알고 있습니까?

당신은 아마 HUH ???? 어떻게 ????

MyISAM은 데이터를 .MYD파일에 저장하고 모든 인덱스를 .MYI파일에 저장합니다.

인덱스가 데이터와 다른 파일에 있기 때문에 동일한 쿼리는 다른 EXPLAIN 계획을 생성합니다. 왜 ? 이유는 다음과 같습니다.

  • 필요한 데이터 ( last_name열)는.MYI
  • 최악의 경우 전체 인덱스 스캔이 가능합니다
  • last_name인덱스 에서만 열에 액세스합니다
  • 원치 않는 걸음질 할 필요가 없습니다
  • 정렬을 위해 임시 파일 작성을 트리거하지 않습니다.

이것을 어떻게 확신 할 수 있습니까? 나는 다른 EXPLAIN 계획 (때로는 더 나은 일)을 생성하는 다른 저장소를 사용하는 방법에 대한이 작업 이론을 테스트 한 : 이 ORDER BY에 사용하는 인덱스 커버 모든 열을 선택해야합니까?


1
-1 @Rolando이 대답은 올바른보다 정확하지 마이클-sqlbot의 대답 하지만 그것이 잘못이다, egthe 매뉴얼은 말한다 : "MySQL은 이러한 작업을 위해 인덱스를 사용하는 경우 정렬 또는 테이블을 정렬 또는 그룹에 (...) 그룹화는 사용 가능한 인덱스 (...) "의 가장 왼쪽 접두사에서 수행됩니다. 또한 귀하의 게시물의 다른 진술 중 일부는 논쟁의 여지가 있습니다. 이 답변을 삭제하거나 재 작업하는 것이 좋습니다.
miracle173

이 답변은 정확하지 않습니다. 정렬을 피하는 경우 WHERE 절이없는 경우에도 색인을 계속 사용할 수 있습니다.
oysteing

19

실제로 여기서 문제는 이것이 접두사 색인처럼 보입니다. 질문에 테이블 정의가 표시되지 않지만 sub_part= 700입니까? 전체 열을 색인화하지 않았으므로 색인을 정렬에 사용할 수 없으며 포함 색인으로도 유용하지 않습니다. "일치 할 수있는"행 WHERE과 서버 계층 (스토리지 엔진 위)이 일치하는 행을 추가로 필터링해야하는 행을 찾는 데만 사용할 수 있습니다 . 성에 실제로 1000자가 필요합니까?


설명을 위해 업데이트 : 500 행 이상의 작은 테이블이있는 테이블 테스트 테이블이 있습니다. 각 테이블에는 열에 웹 사이트의 도메인 이름이 있고 domain_name VARCHAR(254) NOT NULL인덱스는 없습니다.

mysql> alter table keydemo add key(domain_name);
Query OK, 0 rows affected (0.17 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 | index | NULL          | domain_name | 764     | NULL |  541 | Using index |
+----+-------------+---------+-------+---------------+-------------+---------+------+------+-------------+
1 row in set (0.01 sec)

이제이 인덱스를 삭제하고 domain_name의 처음 200자를 인덱싱합니다.

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>

짜잔

또한 200 문자의 색인이 열에서 가장 긴 값보다 깁니다.

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

...하지만 아무런 차이가 없습니다. 접두사 길이로 선언 된 인덱스는 정의에 따라 전체 열 값을 포함하지 않기 때문에 정렬이 아닌 조회에만 사용할 수 있으며 커버링 인덱스로는 사용할 수 없습니다.

또한 위의 쿼리는 InnoDB 테이블에서 실행되었지만 MyISAM 테이블에서 실행하면 거의 동일한 결과를 얻을 수 있습니다. 이 경우 의 유일한 차이점은 InnoDB 수 rows는 약간 떨어져 있고 (541) MyISAM은 정확한 행 수 (563)를 보여주는데, 이는 두 스토리지 엔진이 인덱스 다이빙을 매우 다르게 처리하기 때문에 정상적인 동작입니다.

내가하고자 여전히 LAST_NAME 열이 필요한 것보다 가능성이 크지 만, 그것은 여전히 주장 할 수 는 이노를 사용하여 MySQL을 5.5 또는 5.6을 실행하는 경우, 인덱스로 전체 열 :

기본적으로 단일 열 인덱스의 인덱스 키는 최대 767 바이트입니다. 인덱스 키 접두사에 동일한 길이 제한이 적용됩니다. 13.1.13 절 CREATE INDEX. “ 구문”을 참조하십시오 . 예를 들어, 당신은에 255 개 이상의 문자의 열 접두사 색인이 제한 히트 수 TEXT또는 VARCHAR가정, 열을 UTF-8문자 세트와 각 문자에 대한 3 바이트의 최대. 때 innodb_large_prefix구성 옵션이 활성화되어,이 길이 제한은 3072 바이트를 위해 발생 InnoDB을 사용하는 테이블 DYNAMICCOMPRESSED행 형식을.

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


흥미로운 관점. 열은 varchar(1000)그러나 이것은 ~ 750 인 색인에 허용되는 최대 값을 초과합니다
Cratylus

8
이 답변은 받아 들여 져야합니다.
ypercubeᵀᴹ

1
@ypercube이 답변은 내 것보다 더 정확합니다. 댓글 +1,이 답변 +1 대신에 이것을 받아 들여야합니다.
RolandoMySQLDBA

1
@ 티모 (Timo), 흥미로운 질문입니다 ...이 질문에 대한 링크가있는 새로운 질문으로 게시하는 것이 좋습니다. 에서 전체 출력을 게시 EXPLAIN SELECT ...뿐만 아니라 SHOW CREATE TABLE ...SELECT @@VERSION;버전에서 최적화 변경 이후 관련이있을 수 있습니다.
Michael-sqlbot

1
이제는 위의 의견에서 요청한 것처럼 접두어 색인 null 인덱싱에 도움이되지 않는다는 사실을 (최소 5.7의 경우)보고 할 수 있습니다 .
Timo

2

주석이 형식화를 지원하지 않으므로 RolandoMySQL DBA가 gen_clust_index 및 innodb에 대해 이야기했기 때문에 대답했습니다. 그리고 이것은 innodb 기반 테이블에서 매우 중요합니다. C 코드를 분석 할 수 있어야하기 때문에 이는 일반적인 DBA 지식보다 더 뛰어납니다.

Innodb를 사용하는 경우 항상 PRIMARY KEY 또는 UNIQUE KEY를 만들어야합니다. innodb를 사용하지 않으면 자체 생성 된 ROW_ID를 사용하여 이익보다 더 해를 끼칠 수 있습니다.

증명은 C 코드를 기반으로하기 때문에 쉽게 설명하려고합니다.

/**********************************************************************//**
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);
}

첫 번째 문제

mutex_enter (& (dict_sys-> mutex));

이 줄은 한 스레드 만 동시에 dict_sys-> mutex에 액세스 할 수 있도록합니다. 이미 값이 뮤텍스 된 경우 어떻게해야합니까 ... 예 스레드는 스레드 잠금 과 같은 멋진 임의의 기능과 같은 것을 얻 거나 자신의 기본 키 또는 고유 키가없는 테이블이 더 있으면 멋진 기능을 갖게됩니다. innodb ' 테이블 잠금 '은 MyISAM이 레코드 / 행 기반 잠금이라는 훌륭한 기능 때문에 InnoDB로 대체 된 이유가 아닙니다.

두 번째 문제

(0 == (ID % DICT_HDR_ROW_ID_WRITE_MARGIN))

모듈로 (%) 계산은 매번 다시 계산해야하기 때문에 배치 삽입을 수행하는 경우 속도가 느리지 않습니다. DICT_HDR_ROW_ID_WRITE_MARGIN (값 256)은 2의 거듭 제곱이므로 훨씬 빠릅니다.

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

C 컴파일러가 최적화하도록 구성되어 있고 최적화 프로그램 인 경우 C 최적화 프로그램은 "무거운"코드를 더 가벼운 버전으로 수정합니다.

스토리의 모토는 항상 자신의 기본 키를 만들거나 처음부터 테이블을 만들 때 고유 인덱스가 있는지 확인하십시오.


행 기반 복제를 추가하고 행 ID가 서버에서 일관성이 없다는 사실을 추가하고 항상 기본 키를 작성하는 것에 대한 Raymond의 요지가 더 중요합니다.

UNIQUE충분하다고 제안하지 마십시오 . 고유 인덱스를 PK로 승격하려면 NULL이 아닌 열만 포함해야합니다.
Rick James

"모듈로 (%) 계산이 느리다"- INSERT이 함수에서 시간의 백분율을 소비 하는 것이 더 중요 합니다. 나는 무의미하다고 생각합니다. 열을 삽질하려는 노력과 대조적으로, 때때로 블록 분할, buffer_pool의 다양한 뮤텍스, 변경 버퍼 등을 포함한 BTree 작업을 수행하십시오.
Rick James

True @RickJames 오버 헤드는 매우 적을 수 있지만 많은 수의 작은 숫자도 합쳐집니다 (아직 미세 최적화 일 것입니다.). 첫 번째 문제 외에 가장 큰 문제는
Raymond Nijland
당사 사이트를 사용함과 동시에 당사의 쿠키 정책개인정보 보호정책을 읽고 이해하였음을 인정하는 것으로 간주합니다.
Licensed under cc by-sa 3.0 with attribution required.