ARTICLE DETAIL

资讯详情

深耕网站视觉设计与运营推广的一线实战洞察。

MySQL优化实战:慢SQL定位、索引设计与主从延迟排查

MySQL优化实战:慢SQL定位、索引设计与主从延迟排查 上周五晚上十一点半线上告警群里突然弹出一条消息某个核心报表接口的P99延迟从200ms涨到了8秒。登录数据库一看一条五表JOIN的统计SQL的rows估算值是三千多万实际执行直接把主库拖到CPU满负荷。这种场景太熟悉了。MySQL优化从来不是改一条SQL那么简单而是一套从SQL改写、索引设计、配置参数到架构选型的组合拳。这篇文章把我实际项目中沉淀下来的MySQL优化策略汇总一下包含可以直接上手的命令、参数和排查思路也把同学们问得最多的安装、锁表、存储过程、主从复制这些点串起来讲。适合被慢查询折磨过的后端开发、运维工程师以及所有想系统建立MySQL调优知识体系的人。1. 先定位问题慢在SQL、慢在配置还是慢在架构1.1 三步诊断法从慢查询日志开始我早先吃过一个亏线上一直偶发卡顿我上来就调了一堆参数结果第二天更糟。后来才学会优化第一步永远是“把问题量化”而不是凭感觉动手。第一步先开启慢查询日志把那些超过阈值的SQL抓出来SET GLOBAL slow_query_log ON; SET GLOBAL long_query_time 2; SET GLOBAL log_queries_not_using_indexes ON;需要提醒的是这些GLOBAL变量在MySQL 8.0里重启后会丢失真正要持久化得写进my.cnf[mysqld] slow_query_log 1 slow_query_log_file /var/lib/mysql/slow.log long_query_time 2 log_queries_not_using_indexes 1long_query_time设多少是个取舍。设成0会记录全部SQL日志膨胀很快设成10秒又会让很多“温水煮青蛙”的慢语句从眼皮底下漏过去。我一般在测试环境设1秒线上从2秒起步跑一两个业务周期后再收紧。日志抓出来后先用自带的mysqldumpslow做初步归总mysqldumpslow -s t -t 10 /var/lib/mysql/slow.log按执行时间排序列出Top 10能直接看到SQL文本、平均执行时间和扫描行数。如果装了Percona Toolkitpt-query-digest的统计维度更丰富可以按指纹聚合出总分、平均耗时和查询样本是我在集群环境下排查慢语句的首选工具。1.2 看懂服务器状态与关键指标慢日志抓到了SQL还要确认它是不是当前数据库真正的压力来源。所以第二步是看状态指标我常用的几条命令如下SHOW GLOBAL STATUS LIKE Threads_connected; SHOW GLOBAL STATUS LIKE Threads_running; SHOW GLOBAL STATUS LIKE Innodb_row_lock_current_waits; SHOW GLOBAL STATUS LIKE Questions; SHOW ENGINE INNODB STATUS\G简单解读一下Threads_connected是当前连接数如果长期顶到max_connections上限客户端就会报“Too many connections”Threads_running是真正在干活的线程数如果远超服务器CPU核数说明SQL质量差或者并发控制有问题。Innodb_row_lock_current_waits不为0说明存在锁等待后面一定要查事务。QPS可以自己算间隔60秒跑两次SHOW GLOBAL STATUS LIKE Questions差值除以60就是每秒请求数。配合监控图看趋势比单看一个值靠谱。1.3 明确优化目标先分清瓶颈类型第三步是把问题分层。我自己习惯把MySQL变慢归为四类CPU瓶颈、IO瓶颈、锁等待、网络或连接瓶颈。CPU跑满重点查SQL和索引IO延迟高重点看缓冲池命中率和redo刷盘频率锁等待多重点查大事务和并发更新连接打满再去看连接数与连接池配置。这就好比你去看病大夫不会上来就开药而是先量血压、听心率、再开检查单。SQL优化也是同一个道理先确认病灶在哪一层再去动刀。很多人一上来把max_connections调到几千结果Threads_running长期二十几条问题根本没解决反而把数据库拖得更垮。2. 索引优化收益最快坑也最多2.1 建索引前先想清楚三个问题索引是MySQL优化里成本最低、收益最明显的部分也是最容易乱用的部分。每次接到新建索引的需求我都会先问三个问题这条SQL是不是高频查询查询条件里的字段区分度够不够高这张表的写入频率能不能承受索引的额外开销区分度这个事经常被忽略。简单算一下SELECT COUNT(DISTINCT col) / COUNT(*) FROM t。如果结果接近1说明这个列几乎每条记录都不同索引价值很高如果只有0.1甚至更低比如性别、状态这类列单独建索引基本没意义优化器也不会用。拿热词里那个学生课程成绩表来举例一张典型的成绩单表可以这样设计CREATE TABLE student_score ( id BIGINT UNSIGNED NOT NULL AUTO_INCREMENT, student_id BIGINT UNSIGNED NOT NULL COMMENT 学生ID, course_id INT UNSIGNED NOT NULL COMMENT 课程ID, score DECIMAL(5,2) NOT NULL COMMENT 成绩, exam_date DATETIME NOT NULL COMMENT 考试时间, exam_type TINYINT NOT NULL DEFAULT 1 COMMENT 1平时 2期中 3期末, PRIMARY KEY (id), KEY idx_stu_course (student_id, course_id), KEY idx_course_date (course_id, exam_date) ) ENGINEInnoDB DEFAULT CHARSETutf8mb4 COMMENT学生课程成绩表;这里我建的是联合索引而不是给student_id和course_id各自建单列索引。原因后面讲。2.2 联合索引的最左前缀与排序场景联合索引的核心规则叫最左前缀很多人栽在这里。索引(a,b,c)实际能匹配这些查询a、ab、abc但是直接用b或者c作为第一个条件的查询用不上这个索引。为什么B树的索引顺序是先按a排a相同再按b排再相同按c排。你直接给一个b的值树根本没法定位。联合索引还有一个隐藏价值它可以帮助ORDER BY避免文件排序。比如上面的idx_course_date(course_id, exam_date)下面这条SQL就能直接用索引排序SELECT student_id, score FROM student_score WHERE course_id 10 ORDER BY exam_date DESC;因为course_id已经等值定位剩下的行天然按exam_date排好序Extra里不会出现Using filesort。设计联合索引时把等值查询的字段放前面把排序或范围字段放后面是最常见的一种套路。2.3 索引失效的七个常见场景索引建好了不等于每条SQL都会老老实实走。我整理了一份高频踩坑清单失效场景示例解决思路索引列使用函数WHERE YEAR(create_time)2024改成范围条件 WHERE create_time 2024-01-01 AND create_time 2025-01-01隐式类型转换phone是varcharWHERE phone13800138000加引号 WHERE phone13800138000LIKE以%开头WHERE name LIKE %张%改成前缀匹配张%或用全文索引OR连接非索引列WHERE a1 OR b2两个条件都建索引或改写为UNION ALL使用、NOT IN、NOT LIKEWHERE status 1改成等值或范围查询或换思路联合索引不满足最左前缀索引(a,b)WHERE b1调整索引顺序或补建索引优化器估算回表成本高小表全表扫描比走索引更快用覆盖索引或FORCE INDEX重点说隐式类型转换这个太隐蔽了。表里phone字段是varchar(11)你写WHERE phone13800138000MySQL会把phone列转成数字再去比较相当于对索引列做了函数操作索引直接废掉。类似的问题还有int列和字符串比较稍不留神就是全表扫描。这种问题排查的时候特别阴间执行计划一出来type是ALL你还得回头想半天到底哪里触发了转换。2.4 覆盖索引与回表少一次随机IO就快一截InnoDB的表是聚簇索引结构主键索引的叶子节点存整行数据二级索引的叶子节点只存索引列加主键值。走二级索引查到主键后还要再到主键索引里“回表”取完整行这就是一次随机IO。覆盖索引的思路是让SELECT需要的列全部包含在二级索引里这样连回表都省了Extra显示Using index。比如前面那张成绩表如果高频查询是“按课程统计平均分”SELECT course_id, AVG(score) FROM student_score GROUP BY course_id;给course_id和score建个联合索引(course_id, score)查询时只需扫描这棵二级索引树不碰主键索引大表上的性能提升非常明显。这也是我强烈建议少用SELECT *的原因之一取一堆用不到的列不仅浪费网络传输还让覆盖索引的设计难度变大。3. 慢SQL识别与改写实战3.1 用EXPLAIN读懂执行计划发现慢SQL后最常用的工具就是EXPLAIN。在SELECT前面加EXPLAINMySQL会输出这条SQL的预估执行计划。我主要看四列type、key、rows、Extra。type列代表访问类型从好到差大致是system const eq_ref ref range index ALL。看到ALL就说明全表扫描了这是优化的重点对象看到index说明扫了整棵索引树比全表好但也不算优range说明走了范围扫描属于正常。rows列是优化器估算的扫描行数数值越大越危险。Extra里最该警惕几个词Using filesort是文件排序Using temporary是临时表这俩出现往往意味着SQL要重写如果看到Using index恭喜覆盖索引生效了。举个例子EXPLAIN SELECT student_id, score FROM student_score WHERE course_id 10 AND score 90 ORDER BY exam_date DESC\G如果执行计划里type是ref或者range、key用了idx_course_date、Extra里却出现Using filesort说明排序没利用上索引需要调整索引顺序或者另外加一个覆盖排序条件的索引。3.2 慢查询日志分析配合performance_schema除了EXPLAIN的预估MySQL还提供了实际执行统计。慢日志里能看到单条SQL的执行时间、锁等待时间和扫描行数这些是真实数据。如果想看更细的耗时分布MySQL 8.0可以用performance_schema的events_statements_summary_by_digest表做聚合SELECT DIGEST_TEXT, COUNT_STAR, SUM_ROWS_EXAMINED, SUM_TIMER_WAIT/1000000000000 AS sec FROM performance_schema.events_statements_summary_by_digest ORDER BY SUM_TIMER_WAIT DESC LIMIT 10;这一手在排查“哪一类SQL占了数据库大半时间”时特别管用比一条条翻慢日志省事得多。3.3 改写SQL的六个方向执行计划看明白后SQL改写是重头戏。我常用的改写方向有六个每个都经历过线上实战验证。第一子查询尽量改成JOIN。老版本MySQL对IN子查询优化很弱现在的版本有了物化和半连接优化但改写后仍然要EXPLAIN确认不能想当然。比如查“选了数据库原理课程的学生”子查询版本改成JOIN后执行计划往往更可控。第二巨大分页改成主键关联。LIMIT 100000, 20这种写法MySQL要扫掉前10万行再扔掉纯粹浪费。改成先查主键SELECT t.* FROM student_score t JOIN (SELECT id FROM student_score WHERE course_id10 ORDER BY exam_date DESC LIMIT 100000, 20) tmp ON t.id tmp.id;因为子查询里只扫二级索引不碰大行数据速度差距经常是几十倍。第三OR改写为UNION ALL。如果OR两边的列各自有索引优化器可能会用Index Merge但很多时候不如UNION ALL直观可控。第四HAVING能省则省。HAVING是对分组后的结果过滤如果条件可以在WHERE阶段做就别拖到分组后否则分组临时表白白变大。第五避免在循环里执行SELECT。这个属于应用层问题但效果最明显1000次循环查询改成一条IN查询或JOIN数据库压力直接降一个量级。第六排序字段别跟索引反着来。ORDER BY的字段顺序、升降序要尽量和索引匹配否则就是Using filesort。3.4 分页、排序、聚合的优化细节再展开三个高频场景。排序优化前面说了用索引避免filesort如果实在避免不了确认sort_buffer_size别设太大会话级变量设个2MB到4MB就够全局调大会让每个连接都吃内存。聚合场景GROUP BY常常伴随临时表。MySQL 8.0为GROUP BY提供了一些优化比如松散索引扫描但前提是满足特定索引结构。日常更实用的办法是统计类SQL放到从库跑或者干脆用汇总表业务高峰期查汇总表而不是实时聚合。分页还有一个细节不要用OFFSET做深分页而是用游标方式。前端传last_idSQL写成WHERE id last_id ORDER BY id LIMIT 20这种写法在千万级表上非常稳定扫描量恒定。4. 配置参数调优一次只动一个变量4.1 InnoDB缓冲池最该改的参数如果MySQL优化只能动一个参数我会选innodb_buffer_pool_size。InnoDB的缓冲池缓存数据和索引页相当于给数据库加了一层内存缓存。太小了查询频繁去磁盘读页IO延迟高太大了操作系统内存不够触发swap反而更卡。经验值是一台专用数据库服务器分配给这个参数物理内存的60%到75%。比如32G内存的机器设20G左右比较合理。MySQL 8.0可以动态调整SET GLOBAL innodb_buffer_pool_size 20 * 1024 * 1024 * 1024;调整后看命中率SHOW GLOBAL STATUS LIKE Innodb_buffer_pool_read_requests; SHOW GLOBAL STATUS LIKE Innodb_buffer_pool_reads;前者是缓冲池内读请求次数后者是磁盘读次数。命中率 requests / (requests reads)长期低于95%就要警惕了。注意如果机器上还跑着别的服务别把内存全吃光。4.2 连接数、线程与超时连接数参数是另一个高频调整项。先看现状再调不要拍脑袋设个10000SHOW VARIABLES LIKE max_connections; SHOW GLOBAL STATUS LIKE Max_used_connections;Max_used_connections是历史最高连接数max_connections设置成它的1.3倍左右通常够用还要预留业务突增空间。wait_timeout如果设得太长比如默认8小时一堆sleep连接会占着连接数不放设太短又会把长连接杀掉报错。一般生产环境我设30到60秒后端配合连接池可以随时重建连接。thread_cache_size建议设成16到64之间能减少线程频繁创建销毁的CPU开销。调整后要观察Threads_created指标如果仍在快速增加说明缓存大小不够或者应用频繁建立新连接。4.3 日志刷盘策略性能与数据安全的权衡innodb_flush_log_at_trx_commit这个参数决定了事务提交时redo log的刷盘策略直接在性能和崩溃恢复之间做取舍参数值行为性能安全1每次事务提交都刷盘最慢最安全正常宕机不丢已提交数据2每次提交写入操作系统缓存每秒刷盘较快操作系统崩溃可能丢1秒内数据0每秒才写入并刷盘最快数据库进程崩溃可能丢1秒内数据金融、订单类业务老老实实用1配上sync_binlog1。如果是日志、统计、缓存类数据能容忍宕机丢最近几秒的数据用2能换来明显吞吐提升。每次改这个参数之前一定要让业务方确认可容忍的数据丢失范围这种决策责任和参数调优同样重要。4.4 容器与特殊环境的部署要点现在MySQL经常跑在Docker、K8s里。Docker跑MySQL做开发和测试很省心但生产环境直接docker run却忘掉数据卷容器一重建数据全没了这种事故我见过不止一次。K8s环境部署MySQL比如用KubeSphere这类平台核心是把数据目录挂到PVC上、给资源设置limits防止OOM、把my.cnf通过ConfigMap注入。心态上要把MySQL当成有状态服务对待存储持久化永远第一位。部分国产Linux发行版上启动MySQL最常见的坑是数据目录权限不对以及系统安全模块拦住了进程。启动失败先看错误日志别反复restart盲试。MySQL的错误日志通常在datadir下的hostname.err看懂日志比乱猜命令快得多。5. 锁、事务与并发控制5.1 InnoDB锁的类型与加锁规则锁问题是并发场景下最隐蔽的性能杀手。InnoDB的锁类型主要是行锁、间隙锁和next-key锁。行锁用于锁定具体记录间隙锁锁的是记录之间的空隙next-key锁是二者的结合同时锁住记录和前面的间隙。为什么有间隙锁MySQL默认隔离级别是REPEATABLE READ为了防止幻读InnoDB在RR下对范围查询会加间隙锁或next-key锁。代价是并发插入被锁挡住这时候你会看到大量锁等待。很多人遇到莫名其妙的全表更新阻塞就是间隙锁把范围锁死了。这里用生活类比帮助理解行锁是“这个车位被占”间隙锁相当于“这段马路禁止停车”不但当前车位不能停整段路的空位也不许再插车。范围更新时InnoDB为了保证一致性宁可把整段路都锁起来。5.2 死锁排查与事务超时死锁的典型特征是两个事务互相持有对方需要的锁谁也不让谁最后InnoDB死锁检测会选其中一个回滚。先看死锁现场SHOW ENGINE INNODB STATUS\G输出里的LATEST DETECTED DEADLOCK段落记录了涉及事务各自执行的SQL、持有的锁和等待的锁这是第一手证据务必保存。死锁绝大多数可以避免。我在开发规范里写死一条同一个事务里对多张表的更新顺序保持全局一致。比如先更新订单表再更新库存表所有代码都按这个顺序死锁概率能降一半以上。另外就是让事务尽可能短减少锁的持有时间需要锁多行时条件尽量用索引缩小锁范围避免扩大到间隙甚至全表。还可以把innodb_lock_wait_timeout从默认的50秒改小比如5秒让极端情况下的锁等待快速暴露而不是让请求全部堆积在那里。5.3 事务隔离级别的取舍事务隔离级别不要随便动但也不要谈之色变。MySQL默认RR很多团队线上改成RC因为RC下没有间隙锁并发插入能力和死锁减少都很明显。RC配合MVCC普通SELECT走快照读不加锁日常查询性能跟RR几乎没有差别。什么场景必须用RR对确实需要杜绝幻读的读一致业务比如某些账务、库存对账。用RC时如果业务依赖“同一查询两次结果完全一致”要做额外处理。比隔离级别更值得关注的是大事务。一个大事务长时间不提交会一直持有锁导致别的更新全被卡住还会让undo log膨胀主从复制延迟飙升。我处理过最夸张的一次一个大事务跑了16个小时没提交整库redo疯狂增长从库延迟直接奔着天去了。所以代码里坚持短事务、勤提交批量操作拆成每几百条一提交这类事故基本能绝迹。6. 架构层面主从复制、连接池与拆分6.1 主从复制的核心参数与延迟排查对中大型业务单机MySQL扛不住读压力第一选择就是主从复制加读写分离。主从的原理不复杂主库把变更写入binlog从库的IO线程拉取binlog写入relay logSQL线程再串行执行relay log里的变更。主从延迟是永恒的话题。先看延迟量SHOW REPLICA STATUS\G关注Seconds_Behind_Source字段。如果长期大于0常见原因有几个主库大事务、从库硬件比主库差、从库有额外查询负载、表没有主键导致回放效率低。解决办法依次是拆大事务、给无主键表补主键、升级从库性能、开启并行复制[mysqld] replica_parallel_workers 4 replica_parallel_type LOGICAL_CLOCK还有一个隐蔽问题binlog_format建议用ROW虽然日志量比STATEMENT大但准确性和可恢复性都好很多对误操作后的数据恢复尤其关键。主从环境中的表必须有主键这是我从多次延迟事故里总结出来的头号结论。6.2 连接池别拍脑袋配应用连数据库直接用JDBC裸连是新手常犯的错误生产环境必须用连接池。连接池解决的问题是复用TCP连接避免每次请求都握手、鉴权。以JavaWeb项目最常用的HikariCP为例参数没有那么玄学maximumPoolSize: 20 minimumIdle: 10 connectionTimeout: 30000 maxLifetime: 1800000有个流传的公式叫“CPU核心数*21”这是HikariCP作者提的参考值但实际要根据QPS和SQL耗时调整。如果单条SQL平均20毫秒20个连接理论上每秒能处理1000个请求通常够用。关键是别把maximumPoolSize设得过大连接多了光线程切换和锁竞争就让数据库变慢这是反直觉但真实存在的现象。Druid在国内用得也很多配置自带监控页面和慢SQL收集对排查问题很方便。不管用哪个池都可以通过show processlist观察是否有大量Sleep且长时间不释放的连接这是连接泄漏的典型信号。写代码时用try-with-resources确保Connection一定归还。6.3 读写分离与分库分表要谨慎到了这个阶段常见的演进路线是加缓存、读写分离、垂直拆分、水平拆分。很多团队卡在第一步不做缓存就分库分表结果分布式事务、跨库JOIN、全局ID这些问题接踵而至治理成本远高于收益。我的原则是能用SQL优化解决的不上架构能用缓存缓解的不上分库。真到了分片这一步拆分键的选择最关键。按用户维度拆同一用户的聚合查询都落在同一个分片按时间维度拆适合日志和流水型数据但要注意热点时间。不管怎么拆都要先在方案层面评估最差情况别等上线后才发现某类查询必须扫全部分片。6.4 时序数据场景从MySQL转向TDengine最后补一个不同的优化思路。有些业务数据天然是时序型的比如监控指标、设备上报、埋点日志量大且按时间高频写入。MySQL在这种场景下再怎么调参也不划算更务实的做法是把这类数据迁到TDengine这类时序数据库。而迁移的第一步就是做表结构转换把MySQL表结构自动转成TDengine的超级表加子表。转换的关键是识别两类字段时间戳字段和标签字段。TDengine要求主键必须是timestamp这是最容易翻车的地方MySQL里随便一个自增ID当主键迁移过去直接报错。实际做的时候可以用information_schema.columns把MySQL表结构读出来写脚本生成TDengine的CREATE STABLE和CREATE TABLE语句。数据量上来后时序库的写入和聚合能力确实比死磕MySQL参数强出一个量级。7. 常见问题与故障排查实录7.1 安装与启动问题速查很多同学遇到MySQL优化之前第一步卡在安装和启动上。这里汇总几个问得最多的问题报错或场景原因解决办法ERROR 2002 (HY000): Cant connect to local MySQL server through socket /tmp/mysql.sockmysqld没启动或socket路径配置不一致先看进程ps -ef | grep mysqld确认my.cnf里socket路径再检查客户端工具用的socket路径MySQL SSL连接错误客户端和服务端SSL配置不一致连接参数里按需设置ssl-modeDISABLED或REQUIREDJDBC可配useSSLfalse或配置信任证书Windows下服务启动失败事件ID里出现e0434352通常是系统.NET运行时组件异常或MySQL服务配置损坏打开事件查看器看具体异常模块确认my.ini里的basedir和datadir路径正确尝试重新注册服务银河麒麟这类国产Linux发行版启动MySQL失败数据目录权限不足或安全模块拦截chown -R mysql:mysql 数据目录查看error log定位具体拦截项rpm安装和旧版mariadb-libs冲突rpm包冲突先移除mariadb-libs再按依赖顺序安装rpm包离线环境务必准备好依赖找不到初始密码MySQL初始化后生成临时密码在日志里grep temporary password /var/log/mysqld.log注意像socket连接错误这类问题很多人第一反应是改socket路径但真正原因往往只是服务没启动。排查顺序应该是确认mysqld进程是否在、确认登录方式是socket还是TCP、再检查权限别一上来就改配置。7.2 存储过程与SQL细节存储过程平时用得不频繁但面试和批处理场景还是有人问。写存储过程时最容易被坑的是分隔符。因为过程体里有分号直接提交会被MySQL截断所以要用DELIMITER临时把分隔符换成别的DELIMITER $$ CREATE PROCEDURE sp_update_score(IN p_student_id BIGINT) BEGIN DECLARE v_cnt INT DEFAULT 0; SELECT COUNT(*) INTO v_cnt FROM student_score WHERE student_id p_student_id; IF v_cnt 0 THEN UPDATE student_score SET score score * 1.1 WHERE student_id p_student_id; END IF; END$$ DELIMITER ;触发器也是同样的逻辑建触发器之前先加DELIMITER $$。另外两个高频小问题。字符串转日期用STR_TO_DATESELECT STR_TO_DATE(2025-06-01, %Y-%m-%d)。注意别在索引列上直接包STR_TO_DATE再作为WHERE条件否则索引失效正确的做法是把要查的区间算成日期范围再比较。还有一个热词是“mysql int5”。这个简单但值得提醒INT类型最大能到2147483647如果一条记录里的值已经接近上限加5就会溢出报错。设计字段时算好业务量的上限TINYINT、SMALLINT、INT还是BIGINT宁可大一号也别赌。7.3 表结构设计的几个建议表结构设计是优化的源头这里把通用建议列一下。字符集无脑选utf8mb4别再用utf8emoji和冷门字符会直接存不进去。主键用自增BIGINT尽量别用UUID做主键随机插入会导致页频繁分裂写入性能肉眼可见地下降。状态、性别这类有限枚举值用TINYINT别用VARCHAR存一堆字符串浪费空间还容易出错。时间字段用DATETIME还是TIMESTAMP是个老问题TIMESTAMP有2038年问题还有时区转换逻辑DATETIME更直观应用层做好时区约定即可。大字段如TEXT和BLOB不要和主表高频查询字段混在一起必要时拆到附属表。7.4 我处理一次优化需求的完整套路最后分享一个我的例行流程希望能给你参考。收到一条慢SQL或者一次性能报警我先做三件事开启或查看慢查询日志确认问题SQL和它的执行计划看一下服务器现状区分瓶颈类型然后对照表结构和索引判断是SQL问题还是设计问题。确定方案后每次只改一个变量不管是加索引、改SQL还是调参数改完跑一轮回归比较执行时间和资源消耗通过后再小流量上生产。索引变更我习惯在低峰期做表特别大的用pt-online-schema-change或gh-ost这类在线工具不会锁表卡住线上业务。这套流程看着笨但比别人告诉你“调这个参数就行”靠谱得多。不同业务的数据分布、并发模型、硬件条件完全不同别人的经验只能是线索必须在你自己的库上验证过才算数。我个人这几年攒下最深的体会是MySQL优化百分之七十的收益来自SQL和索引层面的清理配置参数能压榨性能但救不了烂SQL遇到问题先把表结构和SQL摆到台面上看别急着拧参数。还有一条铁律任何线上改动都要先备份、可回滚先小流量验证再铺开。这套基本功练扎实了遇到任何数据库性能问题都不会慌。
返回列表