5. 정렬 최적화
5.1 정렬 최적화
질문: WHERE 조건 필드에 인덱스를 추가하는데, ORDER BY 필드에 인덱스를 추가해야 하는 이유는 무엇입니까?
최적화 제안:
-
SQL에서는 WHERE 절과 ORDER BY 절에 인덱스를 사용하여 WHERE 절에서 전체 테이블 스캔을 방지하고 ORDER BY 절에서 FileSort 정렬을 피할 수 있습니다. 물론 어떤 경우에는 전체 테이블 스캔이나 FileSort 정렬이 반드시 인덱싱보다 느린 것은 아닙니다. 그러나 일반적으로 쿼리 효율성을 향상하려면 여전히 이를 피해야 합니다.
-
ORDER BY 정렬을 완료하려면 Index를 사용해 보세요. WHERE와 ORDER BY 뒤에 동일한 컬럼이 오면 단일 인덱스 컬럼을 사용하고, 서로 다른 경우에는 결합 인덱스를 사용합니다.
-
Index를 사용할 수 없는 경우 FileSort 메서드를 조정해야 합니다.
INDEX a_b_c(a,b,c)
order by 能使用索引最左前缀
- ORDER BY a
- ORDER BY a,b
- ORDER BY a,b,c
- ORDER BY a DESC,b DESC,c DESC
如果WHERE使用索引的最左前缀定义为常量,则order by 能使用索引
- WHERE a = const ORDER BY b,c
- WHERE a = const AND b = const ORDER BY c
- WHERE a = const ORDER BY b,c
- WHERE a = const AND b > const ORDER BY b,c
不能使用索引进行排序
- ORDER BY a ASC,b DESC,c DESC /* 排序不一致 */
- WHERE g = const ORDER BY b,c /*丢失a索引*/
- WHERE a = const ORDER BY c /*丢失b索引*/
- WHERE a = const ORDER BY a,d /*d不是索引的一部分*/
- WHERE a in (...) ORDER BY b,c /*对于排序来说,多个相等条件也是范围查询*/
5.2 사례 실습
ORDER BY 절에서는 Index 정렬을 사용하고 FileSort 정렬은 사용하지 마십시오.
케이스를 실행하기 전에 학생의 인덱스를 지우고 기본 키만 남겨 둡니다.
DROP INDEX idx_age ON student;
DROP INDEX idx_age_classid_stuno ON student;
DROP INDEX idx_age_classid_name ON student;
#或者
call proc_drop_index('atguigudb2','student');
시나리오: 30세이고 학생 번호가 101000 미만인 학생을 사용자 이름별로 정렬하여 쿼리합니다.
mysql> EXPLAIN SELECT SQL_NO_CACHE * FROM student WHERE age = 30 AND stuno <101000 ORDER BY
-> NAME ;
+----+-------------+---------+------------+------+---------------+------+---------+------+--------+----------+-----------------------------+
| id | select_type | table | partitions | type | possible_keys | key | key_len | ref | rows | filtered | Extra |
+----+-------------+---------+------------+------+---------------+------+---------+------+--------+----------+-----------------------------+
| 1 | SIMPLE | student | NULL | ALL | NULL | NULL | NULL | NULL | 498917 | 3.33 | Using where; Using filesort |
+----+-------------+---------+------------+------+---------------+------+---------+------+--------+----------+-----------------------------+
1 row in set, 2 warnings (0.00 sec)
쿼리 결과는 다음과 같습니다.
mysql> SELECT SQL_NO_CACHE * FROM student WHERE age = 30 AND stuno <101000 ORDER BY
-> NAME ;
+-----+--------+--------+------+---------+
| id | stuno | name | age | classId |
+-----+--------+--------+------+---------+
| 695 | 100695 | bXLNEI | 30 | 979 |
| 322 | 100322 | CeOJNY | 30 | 40 |
| 993 | 100993 | DVVPnT | 30 | 340 |
| 983 | 100983 | fmUNei | 30 | 433 |
| 946 | 100946 | iSPxRQ | 30 | 511 |
| 469 | 100469 | LTktoo | 30 | 69 |
| 45 | 100045 | mBZrKC | 30 | 280 |
| 635 | 100635 | nQnUJL | 30 | 732 |
| 16 | 100016 | NzjxKh | 30 | 539 |
| 363 | 100363 | OMuKtM | 30 | 695 |
| 293 | 100293 | qOYywO | 30 | 586 |
| 169 | 100169 | qUElsg | 30 | 526 |
| 798 | 100798 | rhHPdX | 30 | 71 |
| 749 | 100749 | TCgaJe | 30 | 697 |
| 157 | 100157 | TUQtvY | 30 | 22 |
| 580 | 100580 | UHDUOj | 30 | 423 |
| 532 | 100532 | XvmZkc | 30 | 861 |
| 939 | 100939 | yBlCbB | 30 | 320 |
| 710 | 100710 | yhmRvD | 30 | 219 |
| 266 | 100266 | YueogP | 30 | 524 |
+-----+--------+--------+------+---------+
20 rows in set, 1 warning (0.16 sec)
결론: 유형은 ALL이며 이는 최악의 경우입니다. filesort를 사용하는 것은 Extra에도 나타나며 이는 최악의 시나리오이기도 합니다. 최적화는 필수입니다.
최적화 아이디어:
옵션 1: 파일 정렬을 제거하기 위해 인덱스를 구축할 수 있습니다.
#创建新索引
CREATE INDEX idx_age_name ON student(age,NAME);
옵션 2: 조건 필터링 및 정렬에 상위 인덱스를 사용해보세요.
세 필드의 결합된 인덱스를 만듭니다.
DROP INDEX idx_age_name ON student;
CREATE INDEX idx_age_stuno_name ON student (age,stuno,NAME);
EXPLAIN SELECT SQL_NO_CACHE * FROM student WHERE age = 30 AND stuno <101000 ORDER BY NAME;
mysql> EXPLAIN SELECT SQL_NO_CACHE * FROM student WHERE age = 30 AND stuno <101000 ORDER BY NAME;
+----+-------------+---------+------------+-------+--------------------+--------------------+---------+------+------+----------+---------------------------------------+
| id | select_type | table | partitions | type | possible_keys | key | key_len | ref | rows | filtered | Extra |
+----+-------------+---------+------------+-------+--------------------+--------------------+---------+------+------+----------+---------------------------------------+
| 1 | SIMPLE | student | NULL | range | idx_age_stuno_name | idx_age_stuno_name | 9 | NULL | 20 | 100.00 | Using index condition; Using filesort |
+----+-------------+---------+------------+-------+--------------------+--------------------+---------+------+------+----------+---------------------------------------+
1 row in set, 2 warnings (0.00 sec)
mysql> SELECT SQL_NO_CACHE * FROM student
-> WHERE age = 30 AND stuno <101000 ORDER BY NAME ;
+-----+--------+--------+------+---------+
| id | stuno | name | age | classId |
+-----+--------+--------+------+---------+
| 695 | 100695 | bXLNEI | 30 | 979 |
| 322 | 100322 | CeOJNY | 30 | 40 |
| 993 | 100993 | DVVPnT | 30 | 340 |
| 983 | 100983 | fmUNei | 30 | 433 |
| 946 | 100946 | iSPxRQ | 30 | 511 |
| 469 | 100469 | LTktoo | 30 | 69 |
| 45 | 100045 | mBZrKC | 30 | 280 |
| 635 | 100635 | nQnUJL | 30 | 732 |
| 16 | 100016 | NzjxKh | 30 | 539 |
| 363 | 100363 | OMuKtM | 30 | 695 |
| 293 | 100293 | qOYywO | 30 | 586 |
| 169 | 100169 | qUElsg | 30 | 526 |
| 798 | 100798 | rhHPdX | 30 | 71 |
| 749 | 100749 | TCgaJe | 30 | 697 |
| 157 | 100157 | TUQtvY | 30 | 22 |
| 580 | 100580 | UHDUOj | 30 | 423 |
| 532 | 100532 | XvmZkc | 30 | 861 |
| 939 | 100939 | yBlCbB | 30 | 320 |
| 710 | 100710 | yhmRvD | 30 | 219 |
| 266 | 100266 | YueogP | 30 | 524 |
+-----+--------+--------+------+---------+
20 rows in set, 1 warning (0.00 sec)
그 결과, filesort의 sql 실행 속도는 최적화된 filesort의 sql보다 훨씬 빨랐고, 결과도 거의 즉각적으로 나타났다.
결론적으로:
- 두 개의 인덱스가 동시에 존재하며 MySQL은 자동으로 최적의 솔루션을 선택합니다. (이 예에서는 mysql이 idx_age_stuno_name을 선택합니다.) 그러나 데이터의 양이 변경되면 선택한 인덱스도 변경됩니다.
- [범위 조건]과 [그룹화 또는 정렬 기준] 필드 중 선택이 있는 경우 조건 필드의 필터링 수량을 관찰하는 것이 우선이며, 필터링된 데이터가 충분하고 정렬할 데이터가 많지 않은 경우 , 필드의 범위에 인덱스를 배치하는 것이 우선순위입니다. 그 반대.
5.3 파일 정렬 알고리즘: 양방향 정렬과 단방향 정렬
양방향 정렬(느림)
-
MySQL 4.1 이전에는 양방향 정렬이 사용되었습니다. 이는 말 그대로 디스크를 두 번 스캔하여 최종적으로 데이터를 얻은 다음 행 포인터를 읽고 열별로 정렬하고 정렬한 다음 정렬된 목록을 스캔하고 목록에서 다시 시작하는 것을 의미합니다. 목록의 값에 목록에서 해당 데이터 출력을 읽으십시오.
-
디스크에서 정렬 필드를 가져와서 버퍼에서 정렬한 다음 디스크에서 다른 필드를 가져옵니다.
일괄 데이터를 얻으려면 디스크를 두 번 스캔해야 하는데, 우리 모두 알고 있듯이 IO는 시간이 많이 걸리기 때문에 mysql4.1 이후 두 번째로 향상된 알고리즘인 단방향 정렬이 등장했습니다.
단방향 정렬(빠름)
쿼리에 필요한 모든 열을 디스크에서 읽고, 버퍼에서 열 순서에 따라 정렬한 다음 정렬된 목록을 스캔하여 출력합니다. 이는 더 효율적이며 데이터를 두 번 읽는 것을 방지합니다. 그리고 무작위 IO를 순차 IO로 바꾸지만 각 행을 메모리에 저장하기 때문에 더 많은 공간을 사용하게 됩니다.
결론 및 제기된 질문
- 단일 경로는 뒤에서 나오므로 일반적으로 이중 경로보다 좋습니다.
- 하지만 단일 채널을 사용하는 데에는 문제가 있습니다.
6. GROUP BY 최적화
- Index by by를 사용하는 원리는 order by와 거의 동일하며, index를 사용하는 필터 조건이 없더라도 Group by를 사용하면 바로 index를 사용할 수 있다.
- 인덱스 구성을 위한 가장 좋은 왼쪽 접두사 규칙에 따라 먼저 정렬별로 그룹화한 다음 그룹화합니다.
- 인덱스 컬럼을 사용할 수 없는 경우 max_length_for_sort_data 및 sort_buffer_size 매개변수의 설정을 늘리십시오.
- where의 효율성이 갖는 것보다 높기 때문에 조건을 where에 쓸 수 있으면 갖는 것에 쓰지 마세요.
- 순서대로 사용을 줄이고, 정렬 없이 업체와 소통하거나, 정렬 기능을 프로그램에 넣어보세요. Order by, groupby, discover와 같은 문은 더 많은 CPU를 소비하며 데이터베이스의 CPU 리소스는 매우 소중합니다.
- order by, group by, distinct 등의 쿼리문이 포함되어 있으며 where 조건으로 필터링된 결과 집합은 1,000행 이내로 유지되어야 하며, 그렇지 않으면 SQL이 매우 느려집니다.
7. 페이징 쿼리 최적화
최적화 아이디어 1
인덱스에 대한 정렬 및 페이징 작업을 완료하고 마지막으로 기본 키를 기반으로 원래 테이블 쿼리에 필요한 다른 열 내용과 다시 연결합니다.
mysql> EXPLAIN SELECT * FROM student t,(SELECT id FROM student ORDER BY id LIMIT 2000000,10)
-> a
-> WHERE t.id = a.id;
+----+-------------+------------+------------+--------+---------------+---------+---------+------+--------+----------+-------------+
| id | select_type | table | partitions | type | possible_keys | key | key_len | ref | rows | filtered | Extra |
+----+-------------+------------+------------+--------+---------------+---------+---------+------+--------+----------+-------------+
| 1 | PRIMARY | <derived2> | NULL | ALL | NULL | NULL | NULL | NULL | 498917 | 100.00 | NULL |
| 1 | PRIMARY | t | NULL | eq_ref | PRIMARY | PRIMARY | 4 | a.id | 1 | 100.00 | NULL |
| 2 | DERIVED | student | NULL | index | NULL | PRIMARY | 4 | NULL | 498917 | 100.00 | Using index |
+----+-------------+------------+------------+--------+---------------+---------+---------+------+--------+----------+-------------+
3 rows in set, 1 warning (0.00 sec)
최적화 아이디어 2
이 솔루션은 자동 증가 기본 키가 있는 테이블에 적합하며 특정 위치에서 Limit 쿼리를 쿼리로 변환할 수 있습니다.
mysql> EXPLAIN SELECT * FROM student WHERE id > 2000000 LIMIT 10;
+----+-------------+---------+------------+-------+---------------+---------+---------+------+------+----------+-------------+
| id | select_type | table | partitions | type | possible_keys | key | key_len | ref | rows | filtered | Extra |
+----+-------------+---------+------------+-------+---------------+---------+---------+------+------+----------+-------------+
| 1 | SIMPLE | student | NULL | range | PRIMARY | PRIMARY | 4 | NULL | 1 | 100.00 | Using where |
+----+-------------+---------+------------+-------+---------------+---------+---------+------+------+----------+-------------+
1 row in set, 1 warning (0.00 sec)
8. 커버링 인덱스 우선순위 지정
8.1 커버링 인덱스란 무엇입니까?
방법 1의 이해: 인덱스는 행을 효율적으로 찾는 방법이지만 일반 데이터베이스도 인덱스를 사용하여 열의 데이터를 찾을 수 있으므로 행 전체를 읽을 필요가 없습니다. 결국 인덱스 리프 노드는 자신이 인덱스한 데이터를 저장하므로 인덱스를 읽어 원하는 데이터를 얻을 수 있으면 행을 읽을 필요가 없습니다. 쿼리 결과를 만족하는 데이터가 포함된 인덱스를 커버링 인덱스라고 합니다.
이해 방법 2: 쿼리의 SELECT, JOIN 및 WHERE 절에 사용된 모든 열을 포함하는 비클러스터형 복합 인덱스 형태(즉, 인덱스를 작성하는 데 사용된 필드가 정확히 쿼리 조건에 포함된 필드임
) . 간단히 말하면 인덱스 컬럼 + 기본 키에는 SELECT에서 FROM까지 쿼리되는 컬럼이 포함됩니다.
8.2 인덱스 커버링의 장점과 단점
혜택:
-
Innodb 테이블 인덱스의 2차 쿼리 방지(테이블 반환)
-
무작위 IO를 순차 IO로 전환하여 쿼리 효율성을 높일 수 있습니다.
단점:
인덱스 필드의 유지 관리에는 항상 비용이 듭니다. 따라서 포함 인덱스를 지원하기 위해 중복 인덱스를 구축할 때 고려해야 할 장단점이 있습니다. 이것이 비즈니스 DBA, 즉 비즈니스 데이터 설계자의 일입니다.