목차
서문
오류 설명
7월 24일 11시, 특정 데이터베이스에 온라인에서 갑자기 대량의 알람이 수신되었습니다. 느린 쿼리 수가 기준을 초과하여 연결 수가 갑자기 증가했습니다. 데이터베이스의 응답 속도가 느려 비즈니스에 영향을 미쳤습니다. 차트를 보면, 느린 쿼리가 최고조에 달할 때 분당 14,000번에 달했습니다. 일반적인 상황에서는 느린 쿼리의 횟수가 아래와 같이 두 자릿수 미만에 불과합니다.
먼저 해당 문장이 index가 안되어 있는지 의심해 보아야 합니다. 테이블 생성 DML에서 index를 확인해보세요:
MySQL索引选择原理
优化器索引选择的准则
rows是怎么预估出来的
索引要考虑 order by 的字段
更改limit大小能解决问题?
为何突然出现异常慢查询
解决方案
强制选择索引:force index
干涉优化器选择:增大limit
干涉优化器选择:增加包含order by id字段的联合索引
干涉优化器选择:写成子查询
还有很多解决办法...
总结
데이터 베이스 MySQL 튜토리얼 MySQL에서 잘못된 인덱스 선택으로 인한 온라인 느린 쿼리 사고

MySQL에서 잘못된 인덱스 선택으로 인한 온라인 느린 쿼리 사고

Oct 19, 2020 pm 05:24 PM
mysql 느린 쿼리 색인

mysql 비디오 튜토리얼 칼럼에서는 MySQL

MySQL에서 잘못된 인덱스 선택으로 인한 온라인 느린 쿼리 사고

서문

또 만나요! 2주가 더 지났고 클라우드 노트에 미완성 기사 초안이 몇 개 더 남아 있습니다. 일부는 품질이 기대에 미치지 못하기 때문에 더 많은 콘텐츠를 추가할 준비가 되어 있는 반면, 다른 일부는 단지 영감만 주고 콘텐츠가 전혀 없는 경우도 있습니다. 일주일에 5~6개의 기사를 낼 수 있는 큰 놈들이 많다는 게 부럽다. 간 두 개를 줘도 부족하다. 알았어, 더 이상 헛소리는 하지마...

최근 온라인 환경에서 느린 SQL 쿼리로 인해 데이터베이스 장애가 발생하여 온라인 비즈니스에 영향을 미쳤습니다. 조사 결과 SQL 실행 시 MySQL 옵티마이저가 잘못된 인덱스를 선택했기 때문인 것으로 확인됐다. 조사 과정에서 많은 정보를 참고하고 MySQL 옵티마이저에 의한 인덱스 선택의 기본 원리를 배웠습니다. 이 기사에서는 문제 해결을 위한 아이디어를 공유합니다. MySQL에 대한 나의 이해는 제한되어 있습니다. 실수가 있을 경우 합리적인 토론과 수정을 환영합니다.

이번 사고에서 우리는 MySQL의 작동 원리에 대한 심층적인 이해의 중요성도 충분히 알 수 있습니다. 이는 문제가 발생했을 때 독립적으로 문제를 해결할 수 있는 열쇠입니다.

어둡고 폭풍우가 치는 밤에 회사의 온라인 회선이 갑자기 다운되고 동료가 온라인 상태가 되지 않는다고 상상해 보세요. 이때 문제를 해결할 수 있는 조건이 있는 사람은 바로 당신입니다. 엔지니어의 기술, 부끄럽습니까...

이 기사의 주요 내용:

Fault 설명
  • 문제 원인 문제 해결
  • MySQL 인덱스 선택 원칙
  • 해결 방법
  • 생각 및 요약
  • 텍스트

오류 설명

7월 24일 11시, 특정 데이터베이스에 온라인에서 갑자기 대량의 알람이 수신되었습니다. 느린 쿼리 수가 기준을 초과하여 연결 수가 갑자기 증가했습니다. 데이터베이스의 응답 속도가 느려 비즈니스에 영향을 미쳤습니다. 차트를 보면, 느린 쿼리가 최고조에 달할 때 분당 14,000번에 달했습니다. 일반적인 상황에서는 느린 쿼리의 횟수가 아래와 같이 두 자릿수 미만에 불과합니다.

MySQL에서 잘못된 인덱스 선택으로 인한 온라인 느린 쿼리 사고 느린 SQL 기록을 빠르게 확인하여 찾아보세요. 모두 같은 유형의 문으로 인해 발생합니다. 느린 쿼리(테이블 이름과 같은 개인 데이터를 숨겼습니다.):

select
  *
from
  sample_table
where
    1 = 1
    and (city_id = 565)
    and (type = 13)
order by
  id desc
limit
  0, 1复制代码
로그인 후 복사

문이 매우 간단하고 특별한 것이 없는 것 같습니다. 그러나 각 실행에 대한 쿼리 시간은 놀랍게도 44초에 달했습니다.

MySQL에서 잘못된 인덱스 선택으로 인한 온라인 느린 쿼리 사고단순히 충격적입니다. 더 이상 "느리다"라고 할 수는 없습니다...

다음으로 테이블 데이터 정보를 확인하면 아래와 같이 됩니다.

MySQL에서 잘못된 인덱스 선택으로 인한 온라인 느린 쿼리 사고테이블 데이터 볼륨이 크고, 예상 행 수는 83683240으로 약 8천만 개,

수천만 개의 데이터가 담긴 테이블

입니다. 일반적인 상황은 이렇습니다. 문제 해결로 넘어가겠습니다.

문제 원인 해결

먼저 해당 문장이 index가 안되어 있는지 의심해 보아야 합니다. 테이블 생성 DML에서 index를 확인해보세요:

KEY `idx_1` (`city_id`,`type`,`rank`),
KEY `idx_log_dt_city_id_rank` (`log_dt`,`city_id`,`rank`),
KEY `idx_city_id_type` (`city_id`,`type`)复制代码
로그인 후 복사

두 개의 index idx_1, idx_city_id_type이 중복되는 것은 무시해주세요 , 이것들은 모두 역사에 남겨진 문제입니다.

idx_city_id_type 및 idx_1 인덱스

가 있음을 알 수 있습니다. 쿼리 조건은 city_id 및 type이며 두 인덱스 모두 도달할 수 있습니다. 하지만 쿼리 조건이 실제로 city_id와 유형만 고려하면 되나요? (예리한 친구들은 문제를 알아차렸을 것입니다. 모두가 생각하도록 남겨두겠습니다.)

이제 인덱스가 있으므로 SQL을 분석하기 위해 SQL문이 실제로 인덱스에 도달하는지 확인할 차례입니다. 성명. explain은 SELECT 쿼리 문을 분석하는 데 사용됩니다.

Explain의 더 중요한 필드는 다음과 같습니다.

select_type: 단순 쿼리, 결합 쿼리, 하위 쿼리 등을 포함한 쿼리 유형
  • key: 사용된 인덱스
  • rows: 스캔할 예상 행 수
  • 자세한 내용 설명에 대한 소개는 다음을 참조하세요: MySQL 성능 최적화 아티팩트 설명 사용 분석

설명을 사용하여 명령문을 분석합니다:

select * from sample_table where city_id = 565 and type = 13 order by id desc limit 0,1复制代码
로그인 후 복사

결과는 다음과 같습니다.

MySQL에서 잘못된 인덱스 선택으로 인한 온라인 느린 쿼리 사고possiblekey에 인덱스가 있지만 결국 기본 키 인덱스가 사용되었습니다. 테이블의 크기는 수천만 개이고 쿼리 조건은 결국 빈 데이터를 반환합니다. 즉, MySQL은 실제로 기본 키 인덱스를 검색하는 데 오랜 시간이 걸리므로 쿼리가 느려집니다.

강제 인덱스(idx_city_id_type)를 사용하여 명령문이 우리가 설정한 공동 인덱스를 선택하도록 할 수 있습니다:

select * from sample_table force index(idx_city_id_type)  where ( ( (1 = 1) and (city_id = 565) ) and (type = 13) ) order by id desc limit 0, 1复制代码
로그인 후 복사
로그인 후 복사
이번에는 확실히 매우 빠르게 실행됩니다. 명령문을 분석하세요:

MySQL에서 잘못된 인덱스 선택으로 인한 온라인 느린 쿼리 사고

实际执行时间0.00175714s,走了联合索引后,不再是慢查询了。

问题找到了,总结下来就是:MySQL优化器认为在limit 1的情况下,走主键索引能够更快的找到那一条数据,并且如果走联合索引需要扫描索引后进行排序,而主键索引天生有序,所以优化器综合考虑,走了主键索引。实际上,MySQL遍历了8000w条数据也没找到那个天选之人(符合条件的数据),所以浪费了很多时间。

MySQL索引选择原理

优化器索引选择的准则

MySQL一条语句的执行流程大致如下图,而查询优化器则是选择索引的地方:

MySQL에서 잘못된 인덱스 선택으로 인한 온라인 느린 쿼리 사고

引用参考文献一段解释:

首先要知道,选择索引是MySQL优化器的工作。

而优化器选择索引的目的,是找到一个最优的执行方案,并用最小的代价去执行语句。在数据库里面,扫描行数是影响执行代价的因素之一。扫描的行数越少,意味着访问磁盘数据的次数越少,消耗的CPU资源越少。

当然,扫描行数并不是唯一的判断标准,优化器还会结合是否使用临时表、是否排序等因素进行综合判断。

总结下来,优化器选择有许多考虑的因素:扫描行数、是否使用临时表、是否排序等等

我们回头看刚才的两个explain截图:

MySQL에서 잘못된 인덱스 선택으로 인한 온라인 느린 쿼리 사고

MySQL에서 잘못된 인덱스 선택으로 인한 온라인 느린 쿼리 사고

走了主键索引的查询语句,rows预估行数1833,而强制走联合索引行数是45640,并且Extra信息中,显示需要Using filesort进行额外的排序。所以在不加强制索引的情况下,优化器选择了主键索引,因为它觉得主键索引扫描行数少,而且不需要额外的排序操作,主键索引天生有序。

rows是怎么预估出来的

同学们就要问了,为什么rows只有1833,明明实际扫描了整个主键索引啊,行数远远不止几千行。实际上explain的rows是MySQL预估的行数,是根据查询条件、索引和limit综合考虑出来的预估行数。

MySQL是怎样得到索引的基数的呢?这里,我给你简单介绍一下MySQL采样统计的方法。

为什么要采样统计呢?因为把整张表取出来一行行统计,虽然可以得到精确的结果,但是代价太高了,所以只能选择“采样统计”。

采样统计的时候,InnoDB默认会选择N个数据页,统计这些页面上的不同值,得到一个平均值,然后乘以这个索引的页面数,就得到了这个索引的基数。

而数据表是会持续更新的,索引统计信息也不会固定不变。所以,当变更的数据行数超过1/M的时候,会自动触发重新做一次索引统计。

在MySQL中,有两种存储索引统计的方式,可以通过设置参数innodb_stats_persistent的值来选择:

设置为on的时候,表示统计信息会持久化存储。这时,默认的N是20,M是10。
设置为off的时候,表示统计信息只存储在内存中。这时,默认的N是8,M是16。
由于是采样统计,所以不管N是20还是8,这个基数都是很容易不准的。复制代码
로그인 후 복사

我们可以使用analyze table t 命令,可以用来重新统计索引信息。但是这条命令生产环境需要联系DBA,所以我就不做实验了,大家可以自行实验。

索引要考虑 order by 的字段

为什么这么说?因为如果我这个表中的索引是city_id,typeid的联合索引,那优化器就会走这个联合索引,因为索引已经做好了排序。

更改limit大小能解决问题?

把limit数量调大会影响预估行数rows,进而影响优化器索引的选择吗?

答案是会。

我们执行limit 10

select * from sample_table where city_id = 565 and type = 13 order by id desc limit 0,10复制代码
로그인 후 복사

MySQL에서 잘못된 인덱스 선택으로 인한 온라인 느린 쿼리 사고

图中rows变为了18211,增长了10倍。如果使用limit 100,会发生什么?

MySQL에서 잘못된 인덱스 선택으로 인한 온라인 느린 쿼리 사고

优化器选择了联合索引。初步估计是rows还会翻倍,所以优化器放弃了主键索引。宁愿用联合索引后排序,也不愿意用主键索引了。

为何突然出现异常慢查询

问:这个查询语句已经在线上稳定运行了非常长的时间,为何这次突然出现了慢查询?

答:以前的语句查询条件返回结果都不为空,limit1很快就能找到那条数据,返回结果。而这次代码中查询条件实际结果为空,导致了扫描了全部的主键索引。

解决方案

知道了MySQL为何选择这个索引的原因后,我们就可以根据上面的思路来列举出解决办法了。

主要有两个大方向:

  1. 强制指定索引
  2. 干涉优化器选择

强制选择索引:force index

就像上面我最开始的操作那样,我们直接使用force index,让语句走我们想要走的索引。

select * from sample_table force index(idx_city_id_type)  where ( ( (1 = 1) and (city_id = 565) ) and (type = 13) ) order by id desc limit 0, 1复制代码
로그인 후 복사
로그인 후 복사

这样做的优点是见效快,问题马上就能解决。

缺点也很明显:

  • 高耦合,这种语句写在代码里,会变得难以维护,如果索引名变化了,或者没有这个索引了,代码就要反复修改。属于硬编码。
  • 很多代码用框架封装了SQL,force index()并不容易加进去。

我们换一种办法,我们去引导优化器选择联合索引。

干涉优化器选择:增大limit

通过增大limit,我们可以让预估扫描行数快速增加,比如改成下面的limit 0, 1000

SELECT * FROM sample_table where city_id = 565 and type = 13 order by id desc LIMIT 0,1000复制代码
로그인 후 복사

这样就会走上联合索引,然后排序,但是这样强行增长limit,其实总有种面向黑盒调参的感觉。我们还有更优美的解决方案吗?

干涉优化器选择:增加包含order by id字段的联合索引

我们这句慢查询使用的是order by id,但是我们却没有在联合索引中加入id字段,导致了优化器认为联合索引后还要排序,干脆就不太想走这个联合索引了。

我们可以新建city_id,typeid的联合索引,来解决这个问题。

这样也有一定的弊端,比如我这个表到了8000w数据,建立索引非常耗时,而且通常索引就有3.4个g,如果无限制的用索引解决问题,可能会带来新的问题。表中的索引不宜过多。

干涉优化器选择:写成子查询

还有什么办法?我们可以用子查询,在子查询里先走city_id和type的联合索引,得到结果集后在limit1选出第一条。

但是子查询使用有风险,一版DBA也不建议使用子查询,会建议大家在代码逻辑中完成复杂的查询。当然我们这句并不复杂啦~

Select * From sample_table Where id in (Select id From `newhome_db`.`af_hot_price_region` where (city_id = 565 and type = 13)) limit 0, 1复制代码
로그인 후 복사

还有很多解决办法...

SQL优化是个很大的工程,我们还有非常多的办法能够解决这句慢查询问题,这里就不一一展开了。留给大家做为思考题了。

总结

本文带大家回顾了一次MySQL优化器选错索引导致的线上慢查询事故,可以看出MySQL优化器对于索引的选择并不单单依靠某一个标准,而是一个综合选择的结果。我自己也对这方面了解不深入,还需要多多学习,争取能够好好的做一个索引选择的总结(挖坑)。不说了,拿起巨厚的《高性能MySQL》,开始...

压住我的泡面...

最后做个文章总结:

  • 该慢查询语句中使用order by id导致优化器在主键索引和city_id和type的联合索引中有所取舍,最终导致选择了更慢的索引。
  • 可以通过强制指定索引,建立包含id的联合索引,增大limit等方式解决问题。
  • 平时开发时,尤其是对于特大数据量的表,要注意SQL语句的规范和索引的建立,避免事故的发生。

相关免费学习推荐:mysql视频教程

위 내용은 MySQL에서 잘못된 인덱스 선택으로 인한 온라인 느린 쿼리 사고의 상세 내용입니다. 자세한 내용은 PHP 중국어 웹사이트의 기타 관련 기사를 참조하세요!

본 웹사이트의 성명
본 글의 내용은 네티즌들의 자발적인 기여로 작성되었으며, 저작권은 원저작자에게 있습니다. 본 사이트는 이에 상응하는 법적 책임을 지지 않습니다. 표절이나 침해가 의심되는 콘텐츠를 발견한 경우 admin@php.cn으로 문의하세요.

핫 AI 도구

Undresser.AI Undress

Undresser.AI Undress

사실적인 누드 사진을 만들기 위한 AI 기반 앱

AI Clothes Remover

AI Clothes Remover

사진에서 옷을 제거하는 온라인 AI 도구입니다.

Undress AI Tool

Undress AI Tool

무료로 이미지를 벗다

Clothoff.io

Clothoff.io

AI 옷 제거제

AI Hentai Generator

AI Hentai Generator

AI Hentai를 무료로 생성하십시오.

인기 기사

R.E.P.O. 에너지 결정과 그들이하는 일 (노란색 크리스탈)
4 몇 주 전 By 尊渡假赌尊渡假赌尊渡假赌
R.E.P.O. 최고의 그래픽 설정
4 몇 주 전 By 尊渡假赌尊渡假赌尊渡假赌
R.E.P.O. 아무도들을 수없는 경우 오디오를 수정하는 방법
4 몇 주 전 By 尊渡假赌尊渡假赌尊渡假赌
WWE 2K25 : Myrise에서 모든 것을 잠금 해제하는 방법
1 몇 달 전 By 尊渡假赌尊渡假赌尊渡假赌

뜨거운 도구

메모장++7.3.1

메모장++7.3.1

사용하기 쉬운 무료 코드 편집기

SublimeText3 중국어 버전

SublimeText3 중국어 버전

중국어 버전, 사용하기 매우 쉽습니다.

스튜디오 13.0.1 보내기

스튜디오 13.0.1 보내기

강력한 PHP 통합 개발 환경

드림위버 CS6

드림위버 CS6

시각적 웹 개발 도구

SublimeText3 Mac 버전

SublimeText3 Mac 버전

신 수준의 코드 편집 소프트웨어(SublimeText3)

MySQL : 초보자를위한 데이터 관리의 용이성 MySQL : 초보자를위한 데이터 관리의 용이성 Apr 09, 2025 am 12:07 AM

MySQL은 설치가 간단하고 강력하며 데이터를 쉽게 관리하기 쉽기 때문에 초보자에게 적합합니다. 1. 다양한 운영 체제에 적합한 간단한 설치 및 구성. 2. 데이터베이스 및 테이블 작성, 삽입, 쿼리, 업데이트 및 삭제와 같은 기본 작업을 지원합니다. 3. 조인 작업 및 하위 쿼리와 같은 고급 기능을 제공합니다. 4. 인덱싱, 쿼리 최적화 및 테이블 파티셔닝을 통해 성능을 향상시킬 수 있습니다. 5. 데이터 보안 및 일관성을 보장하기위한 지원 백업, 복구 및 보안 조치.

MySQL : 쉽게 학습하기위한 간단한 개념 MySQL : 쉽게 학습하기위한 간단한 개념 Apr 10, 2025 am 09:29 AM

MySQL은 오픈 소스 관계형 데이터베이스 관리 시스템입니다. 1) 데이터베이스 및 테이블 작성 : CreateAbase 및 CreateTable 명령을 사용하십시오. 2) 기본 작업 : 삽입, 업데이트, 삭제 및 선택. 3) 고급 운영 : 가입, 하위 쿼리 및 거래 처리. 4) 디버깅 기술 : 확인, 데이터 유형 및 권한을 확인하십시오. 5) 최적화 제안 : 인덱스 사용, 선택을 피하고 거래를 사용하십시오.

phpmyadmin을 여는 방법 phpmyadmin을 여는 방법 Apr 10, 2025 pm 10:51 PM

다음 단계를 통해 phpmyadmin을 열 수 있습니다. 1. 웹 사이트 제어판에 로그인; 2. phpmyadmin 아이콘을 찾고 클릭하십시오. 3. MySQL 자격 증명을 입력하십시오. 4. "로그인"을 클릭하십시오.

Navicat Premium을 만드는 방법 Navicat Premium을 만드는 방법 Apr 09, 2025 am 07:09 AM

Navicat Premium을 사용하여 데이터베이스 생성 : 데이터베이스 서버에 연결하고 연결 매개 변수를 입력하십시오. 서버를 마우스 오른쪽 버튼으로 클릭하고 데이터베이스 생성을 선택하십시오. 새 데이터베이스의 이름과 지정된 문자 세트 및 Collation의 이름을 입력하십시오. 새 데이터베이스에 연결하고 객체 브라우저에서 테이블을 만듭니다. 테이블을 마우스 오른쪽 버튼으로 클릭하고 데이터 삽입을 선택하여 데이터를 삽입하십시오.

MySQL 및 SQL : 개발자를위한 필수 기술 MySQL 및 SQL : 개발자를위한 필수 기술 Apr 10, 2025 am 09:30 AM

MySQL 및 SQL은 개발자에게 필수적인 기술입니다. 1.MySQL은 오픈 소스 관계형 데이터베이스 관리 시스템이며 SQL은 데이터베이스를 관리하고 작동하는 데 사용되는 표준 언어입니다. 2.MYSQL은 효율적인 데이터 저장 및 검색 기능을 통해 여러 스토리지 엔진을 지원하며 SQL은 간단한 문을 통해 복잡한 데이터 작업을 완료합니다. 3. 사용의 예에는 기본 쿼리 및 조건 별 필터링 및 정렬과 같은 고급 쿼리가 포함됩니다. 4. 일반적인 오류에는 구문 오류 및 성능 문제가 포함되며 SQL 문을 확인하고 설명 명령을 사용하여 최적화 할 수 있습니다. 5. 성능 최적화 기술에는 인덱스 사용, 전체 테이블 스캔 피하기, 조인 작업 최적화 및 코드 가독성 향상이 포함됩니다.

Navicat에서 MySQL에 새로운 연결을 만드는 방법 Navicat에서 MySQL에 새로운 연결을 만드는 방법 Apr 09, 2025 am 07:21 AM

응용 프로그램을 열고 새로운 연결 (Ctrl n)을 선택하여 Navicat에서 새로운 MySQL 연결을 만들 수 있습니다. "MySQL"을 연결 유형으로 선택하십시오. 호스트 이름/IP 주소, 포트, 사용자 이름 및 비밀번호를 입력하십시오. (선택 사항) 고급 옵션을 구성합니다. 연결을 저장하고 연결 이름을 입력하십시오.

SQL이 행을 삭제 한 후 데이터를 복구하는 방법 SQL이 행을 삭제 한 후 데이터를 복구하는 방법 Apr 09, 2025 pm 12:21 PM

백업 또는 트랜잭션 롤백 메커니즘이없는 한 데이터베이스에서 직접 삭제 된 행 복구는 일반적으로 불가능합니다. 키 포인트 : 거래 롤백 : 트랜잭션이 데이터를 복구하기 전에 롤백을 실행합니다. 백업 : 데이터베이스의 일반 백업을 사용하여 데이터를 신속하게 복원 할 수 있습니다. 데이터베이스 스냅 샷 : 데이터베이스의 읽기 전용 사본을 작성하고 데이터를 실수로 삭제 한 후 데이터를 복원 할 수 있습니다. 주의해서 삭제 명령문을 사용하십시오. 실수로 데이터를 삭제하지 않도록 조건을주의 깊게 점검하십시오. WHERE 절을 사용하십시오 : 삭제할 데이터를 명시 적으로 지정하십시오. 테스트 환경 사용 : 삭제 작업을 수행하기 전에 테스트하십시오.

Navicat에서 SQL을 실행하는 방법 Navicat에서 SQL을 실행하는 방법 Apr 08, 2025 pm 11:42 PM

Navicat에서 SQL을 수행하는 단계 : 데이터베이스에 연결하십시오. SQL 편집기 창을 만듭니다. SQL 쿼리 또는 스크립트를 작성하십시오. 실행 버튼을 클릭하여 쿼리 또는 스크립트를 실행하십시오. 결과를 봅니다 (쿼리가 실행 된 경우).

See all articles