Mysql 고급 - 인덱스 최적화 및 쿼리 최적화 (2)

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보다 훨씬 빨랐고, 결과도 거의 즉각적으로 나타났다.

결론적으로:

  1. 두 개의 인덱스가 동시에 존재하며 MySQL은 자동으로 최적의 솔루션을 선택합니다. (이 예에서는 mysql이 idx_age_stuno_name을 선택합니다.) 그러나 데이터의 양이 변경되면 선택한 인덱스도 변경됩니다.
  2. [범위 조건]과 [그룹화 또는 정렬 기준] 필드 중 선택이 있는 경우 조건 필드의 필터링 수량을 관찰하는 것이 우선이며, 필터링된 데이터가 충분하고 정렬할 데이터가 많지 않은 경우 , 필드의 범위에 인덱스를 배치하는 것이 우선순위입니다. 그 반대.

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 인덱스 커버링의 장점과 단점

혜택:

  1. Innodb 테이블 인덱스의 2차 쿼리 방지(테이블 반환)

  2. 무작위 IO를 순차 IO로 전환하여 쿼리 효율성을 높일 수 있습니다.

단점:
인덱스 필드의 유지 관리에는 항상 비용이 듭니다. 따라서 포함 인덱스를 지원하기 위해 중복 인덱스를 구축할 때 고려해야 할 장단점이 있습니다. 이것이 비즈니스 DBA, 즉 비즈니스 데이터 설계자의 일입니다.

추천

출처blog.csdn.net/qq_51495235/article/details/133102814