诏安网站建设,开一个网上商城要多少钱,seo关键词词库,网站文字特效线上告警#xff1a;接口响应超时。一查#xff0c;又是慢SQL。
这种场景太常见了。数据库是后端最常见的性能瓶颈#xff0c;而慢SQL是罪魁祸首。这篇把我排查慢SQL的套路整理出来。 第一步#xff1a;找到慢SQL
1. 开启慢查询日志
-- 查看是否开启
SHOW VARIABLES LIK…线上告警接口响应超时。一查又是慢SQL。这种场景太常见了。数据库是后端最常见的性能瓶颈而慢SQL是罪魁祸首。这篇把我排查慢SQL的套路整理出来。第一步找到慢SQL1. 开启慢查询日志-- 查看是否开启SHOWVARIABLESLIKEslow_query%;SHOWVARIABLESLIKElong_query_time;-- 动态开启重启失效SETGLOBALslow_query_logON;SETGLOBALlong_query_time1;-- 超过1秒算慢SETGLOBALslow_query_log_file/var/log/mysql/slow.log;永久生效写my.cnf[mysqld] slow_query_log 1 slow_query_log_file /var/log/mysql/slow.log long_query_time 1 log_queries_not_using_indexes 1 # 没用索引的也记录2. 分析慢查询日志# 用mysqldumpslow分析mysqldumpslow-st-t10/var/log/mysql/slow.log# -s t: 按查询时间排序# -t 10: 显示前10条# 输出示例# Count: 156 Time3.21s (500s) Lock0.00s (0s) Rows1000.0 (156000)# SELECT * FROM orders WHERE user_id N AND status N3. 实时查看正在执行的SQL-- 查看当前进程SHOWPROCESSLIST;SHOWFULLPROCESSLIST;-- 显示完整SQL-- 看哪个SQL执行时间长SELECT*FROMinformation_schema.processlistWHEREcommand!SleepORDERBYtimeDESC;-- 杀掉卡住的查询KILL进程ID;4. performance_schema分析-- 开启MySQL 5.6默认开启-- 查询最耗时的SQLSELECTDIGEST_TEXT,COUNT_STARASexec_count,SUM_TIMER_WAIT/1000000000000AStotal_time_sec,AVG_TIMER_WAIT/1000000000ASavg_time_ms,SUM_ROWS_EXAMINEDASrows_examinedFROMperformance_schema.events_statements_summary_by_digestORDERBYSUM_TIMER_WAITDESCLIMIT10;第二步分析执行计划找到慢SQL后用EXPLAIN分析。EXPLAIN基本用法EXPLAINSELECT*FROMordersWHEREuser_id123ANDstatus1;输出关键字段字段含义关注点type访问类型ALL全表扫描要优化key实际用的索引NULL说明没用索引rows预估扫描行数越小越好Extra额外信息Using filesort、Using temporary要注意type类型从好到差system const eq_ref ref range index ALL - const: 主键或唯一索引等值查询最多一条 - eq_ref: 多表join时被驱动表用主键/唯一索引 - ref: 普通索引等值查询 - range: 索引范围扫描 - index: 全索引扫描比ALL好但也不理想 - ALL: 全表扫描必须优化真实案例分析-- 问题SQLEXPLAINSELECT*FROMordersWHEREcreate_time2024-01-01ANDstatus1ORDERBYidDESCLIMIT100;-- 输出--------------------------------------------------------------------------------------------------------|id|select_type|table|type|possible_keys|key|key_len|ref|rows|Extra|--------------------------------------------------------------------------------------------------------|1|SIMPLE|orders|ALL|idx_create|NULL|NULL|NULL|500000|Usingwhere;Usingfilesort|--------------------------------------------------------------------------------------------------------问题typeALL全表扫描keyNULL没用索引Using filesort额外排序第三步索引优化1. 联合索引的最左前缀原则-- 有索引 (a, b, c)-- 能用上索引WHEREa1WHEREa1ANDb2WHEREa1ANDb2ANDc3-- 用不上或部分用上WHEREb2-- 不能用跳过了aWHEREa1ANDc3-- 只用到a跳过了bWHEREb2ANDc3-- 不能用2. 覆盖索引-- 原SQL需要回表SELECT*FROMusersWHEREage20;-- 优化只查索引包含的列SELECTid,name,ageFROMusersWHEREage20;-- 如果有索引 (age, name)就是覆盖索引不用回表-- Extra显示 Using index3. 索引失效的常见原因-- 1. 对索引列做运算或函数SELECT*FROMordersWHEREYEAR(create_time)2024;-- ❌SELECT*FROMordersWHEREcreate_time2024-01-01ANDcreate_time2025-01-01;-- ✅-- 2. 隐式类型转换-- phone是varchar类型SELECT*FROMusersWHEREphone13800138000;-- ❌ 数字会转换SELECT*FROMusersWHEREphone13800138000;-- ✅ 字符串-- 3. LIKE以%开头SELECT*FROMusersWHEREnameLIKE%张;-- ❌SELECT*FROMusersWHEREnameLIKE张%;-- ✅-- 4. OR条件中有非索引列SELECT*FROMusersWHEREid1ORname张三;-- name没索引则全表-- 5. 不等于 ! 或 SELECT*FROMusersWHEREstatus!0;-- 可能全表扫描-- 6. IS NULL 或 IS NOT NULL视情况-- 如果NULL值很多优化器可能选择全表扫描4. 索引设计原则-- 查询条件优先-- 经常出现在WHERE、JOIN、ORDER BY的列建索引-- 区分度高的列优先-- 区分度 COUNT(DISTINCT col) / COUNT(*)-- 性别区分度低0.5手机号区分度高接近1-- 联合索引把区分度高的放前面CREATEINDEXidx_userONorders(user_id,status);-- user_id区分度更高-- 适当冗余避免回表-- 如果经常查 SELECT id, name FROM users WHERE age 20-- 可以建 INDEX idx_age_name (age, name)第四步SQL改写优化1. 避免SELECT *-- 差SELECT*FROMordersWHEREuser_id123;-- 好只查需要的列SELECTid,order_no,amount,statusFROMordersWHEREuser_id123;2. 小表驱动大表-- user表1000条order表100万条-- 差大表驱动小表SELECT*FROMorders oLEFTJOINusers uONo.user_idu.id;-- 好小表驱动大表SELECT*FROMusers uLEFTJOINorders oONu.ido.user_id;-- 或者用EXISTSSELECT*FROMorders oWHEREEXISTS(SELECT1FROMusers uWHEREu.ido.user_idANDu.status1);3. 分页优化-- 深分页问题OFFSET很大时很慢SELECT*FROMordersORDERBYidLIMIT100000,20;-- 要扫描100020行丢弃前100000行-- 优化1用上一页的IDSELECT*FROMordersWHEREid上一页最后一条的IDORDERBYidLIMIT20;-- 优化2延迟关联SELECTo.*FROMorders oINNERJOIN(SELECTidFROMordersORDERBYidLIMIT100000,20)tONo.idt.id;-- 子查询只查主键速度快4. 避免在循环中查询// 差N1查询users:getUsers()for_,user:rangeusers{orders:getOrdersByUserID(user.ID)// 循环里查询}// 好批量查询users:getUsers()userIDs:extractUserIDs(users)orders:getOrdersByUserIDs(userIDs)// 一次查出来orderMap:groupByUserID(orders)5. UNION ALL 替代 UNION-- UNION会去重需要排序SELECTidFROMordersWHEREstatus1UNIONSELECTidFROMordersWHEREstatus2;-- UNION ALL不去重更快SELECTidFROMordersWHEREstatus1UNIONALLSELECTidFROMordersWHEREstatus2;第五步表结构优化1. 选择合适的数据类型-- 整数类型TINYINT-- 1字节-128~127SMALLINT-- 2字节INT-- 4字节BIGINT-- 8字节-- 状态字段用TINYINT足够别用INTstatusTINYINTNOTNULLDEFAULT0-- 时间戳用INT存储4字节比DATETIME8字节省空间-- 但DATETIME可读性好看业务需求-- 金额用DECIMAL别用FLOAT/DOUBLE精度问题amountDECIMAL(10,2)NOTNULLDEFAULT0.002. 大表拆分-- 垂直拆分把大字段拆出去-- orders表id,user_id,order_no,amount,status,create_time-- order_details表大字段id,order_id,goods_info,remark-- 水平拆分按时间或ID分表orders_2024,orders_2025 orders_0,orders_1,orders_2...orders_15-- 按user_id取模3. 归档历史数据-- 定期把老数据移到归档表INSERTINTOorders_archiveSELECT*FROMordersWHEREcreate_time2023-01-01;DELETEFROMordersWHEREcreate_time2023-01-01LIMIT10000;-- 小批量删除避免锁表太久真实案例一次慢查询排查现象订单列表接口偶尔超时排查过程查慢查询日志找到问题SQLSELECT*FROMordersWHEREuser_id123ANDstatusIN(1,2,3)ANDcreate_time2024-01-01ORDERBYcreate_timeDESCLIMIT20;EXPLAIN分析type: ref key: idx_user_id rows: 50000 Extra: Using where; Using filesort虽然用了索引但rows很大还有filesort。分析这个用户订单很多大客户idx_user_id单列索引筛选后还要回表过滤status和时间ORDER BY create_time和索引顺序不一致要filesort优化方案-- 新建联合索引CREATEINDEXidx_user_status_timeONorders(user_id,status,create_time);-- 改SQL把IN改成多个条件SELECT*FROMordersWHEREuser_id123ANDstatus1ANDcreate_time2024-01-01ORDERBYcreate_timeDESCLIMIT20UNIONALLSELECT*FROMordersWHEREuser_id123ANDstatus2ANDcreate_time2024-01-01ORDERBYcreate_timeDESCLIMIT20...或者接受现状在应用层做缓存。结果查询时间从2s降到50ms。工具推荐pt-query-digest分析慢查询日志比mysqldumpslow强大Percona Toolkit一套MySQL工具集MySQL Workbench可视化执行计划Explain AnalyzeMySQL 8.0真实执行统计-- MySQL 8.0 用EXPLAIN ANALYZEEXPLAINANALYZESELECT*FROMordersWHEREuser_id123;-- 显示真实执行时间而不是预估总结慢SQL排查的套路定位慢查询日志、SHOW PROCESSLIST、performance_schema分析EXPLAIN看执行计划重点关注type、key、rows、Extra优化索引优化联合索引、覆盖索引、避免索引失效SQL改写避免SELECT *、分页优化、小表驱动大表表结构优化合适的数据类型、大表拆分记住一点看数据量看场景。100条数据的表怎么写都快上亿数据的表细节决定成败。有问题评论区聊。