资讯动态

MySQL调优实战:从参数配置到SQL索引优化完整指南

发布时间:2026/10/15 20:45:45 来源:尧图企业网站定制
做后端开发这些年MySQL调优是我见过最容易被神化、也最容易走弯路的领域之一。一说调优很多人第一反应就是翻出一堆参数照着网上的“推荐配置”一顿改结果不但没变快反而把线上系统搞崩的案例我见过不少。实际上MySQL调优的核心从来不是背参数而是建立一套“定位瓶颈”的思维方法。这篇文章我想结合自己的实操经历把MySQL调优从整体思路、关键参数、SQL索引到案例走查、常见避坑完整梳理一遍。没有基础的朋友可以从头看有一定经验的可以直接跳到第三部分和第四部分这些都是实际项目中收益最大、踩坑最多的地方。1. 调优前先想清楚MySQL性能优化的整体思路1.1 先回答三个问题你的目标、基线和瓶颈是什么任何调优动作开始之前我都会先问三个问题目标是什么基线在哪里瓶颈判断的依据是什么目标这件事很多人其实没想清楚。你是想让数据库响应时间从200ms降低到50ms还是想支撑翻倍的用户量又或者是想解决高峰期CPU打满的问题不同的目标会导致完全不同的调优方向。响应时间慢可能是SQL问题CPU打满可能是并发模型问题而用户量翻倍可能要动架构。先把目标写下来后面所有工作才不会跑偏。基线和目标同样重要。没有基线的调优是耍流氓。你改了十个参数怎么知道哪个起了作用哪个有副作用我在实操中习惯在调优前至少记录一周的核心指标每天的QPS/TPS曲线、平均查询响应时间、慢查询数量、磁盘IO使用率、连接数峰值。这些数据既是你判断问题的依据也是你改完之后对比效果的标尺。最后是瓶颈判断。MySQL出现性能问题表象往往是在一端根因在另一端。用户看到的是接口转圈你去看服务器发现是磁盘IO满了再往下挖发现是一条全表扫描SQL把缓冲池读穿了。所以拿到性能问题先别急着改把当时的监控数据拉出来判断瓶颈到底出在哪一层是硬件层、系统层、MySQL层还是SQL层。判断错层后面全白干。1.2 瓶颈分层从硬件到SQL每一层能拿到的收益不一样我把MySQL性能瓶颈分成四层硬件资源层、MySQL配置层、SQL与索引层、数据模型与架构层。这个分层基本决定了我处理问题的优先级。硬件资源层是CPU、内存、磁盘、网络这些基础资源。这一层的问题通常表现为资源使用率打满比如CPU 100%、磁盘IO等待时间长。硬件层优化成本最高——加内存、换SSD、扩容机器——但带来的收益却不一定最高。你换了一台高配机器但业务SQL本身是全表扫描那只是把问题从几分钟延迟到几十分钟而已。MySQL配置层是参数层面的调整包括缓冲池大小、连接数、日志刷盘策略等。这一层能解决的是“MySQL没有合理利用资源”的问题。比如缓冲池配得太小明明有128G内存只给了2G给InnoDB那大量热数据只能反复走磁盘这就是配置层的经典问题。SQL与索引层才是调优收益最大的地方。同样的数据一条走了索引的SQL和一条全表扫描的SQL性能差距是几个数量级。这一层的优化几乎不花钱靠的是对SQL和执行计划的深入理解但产出最明显。数据模型与架构层是最底层的思考比如表结构设计是否合理、是否需要拆分大表、要不要引入缓存或读写分离。这个层级通常不会单次优化立刻做但遇到量表数据过亿的场景前面三层的优化空间都挖完之后架构调整是绕不开的。我的习惯是先花最少的成本确认SQL与索引层有没有问题。因为这一层问题最常见、优化成本最低、效果最立竿见影。确认这一层没问题之后再去看配置层最后才考虑硬件和架构。1.3 调优工具准备用手上的数据说话调优不能靠感觉得靠数据。我常用的“调优三件套”其实很简单慢查询日志、状态变量、EXPLAIN。慢查询日志用来发现哪些SQL是“毒瘤”状态变量用来看MySQL整体的运行状态EXPLAIN用来分析单条SQL的执行计划。还有一套很管用的命令。SHOW GLOBAL STATUS里面有大量计数器可以看当前MySQL的各种累计指标比如Innodb_buffer_pool_read_requests和Innodb_buffer_pool_reads两个值一做对比就能算出缓冲池的命中率从而判断缓冲池大小是否合理。SHOW PROCESSLIST看当前正在跑的线程定位突发的高频查询。performance_schema和sys库更是MySQL自带的高级监控体系可以按SQL语句维度统计资源消耗。我第一次做调优的时候就是纯靠感觉一顿乱改线上出了事故才回头补监控。后来养成了习惯调优动作之前先干活把监控和日志全部打开确保每一条优化决策都有数据支撑。这个习惯帮我把很多线上问题从“玄学”变成了“科学”。2. 上手就改的几个关键参数原理与配置经验2.1 innodb_buffer_pool_size数据库的“工作台”应该有多大InnoDB缓冲池是MySQL用来缓存表数据和索引的内存区域。我习惯把它比作厨师的“操作台面”——台面越大需要用的食材和工具都能摆在手边不用反复去储物间磁盘拿。所有数据读取和修改都先在缓冲池里进行缓冲池不够用的时候才被迫把数据从磁盘读进来或者把脏数据刷出去。这台面的大小直接决定了数据库能不能干得快。配置经验上专用MySQL服务器通常把innodb_buffer_pool_size设为物理内存的50%~70%。比如一台64G内存的机器设置32G到48G是比较常见的区间。如果机器上还跑了应用服务就相应调低避免内存互相挤兑导致操作系统使用交换分区。但配置不是拍脑袋配完要看命中率验证。查询方法很简单SHOW GLOBAL STATUS LIKE Innodb_buffer_pool_read_requests; SHOW GLOBAL STATUS LIKE Innodb_buffer_pool_reads;第一个值代表从缓冲池读的次数第二个值代表从磁盘读的次数。命中率计算公式是read_requests / (read_requests reads)。一个健康的系统这个命中率应该在99%以上。如果低于95%优先考虑扩充缓冲池同时检查是否存在大量的全表扫描。还有一个细节容易被忽略MySQL 5.7及以上版本支持动态调整缓冲池大小不用重启就能生效但要注意的是调整过程会逐步搬移数据页期间会有额外的CPU和IO开销。所以我一般建议在低峰期调整改完用SHOW ENGINE INNODB STATUS观察缓冲池的resize状态是否结束。2.2 max_connections并发连接数的上下限博弈max_connections是很多人喜欢乱改的参数之一它代表MySQL允许同时建立的最大客户端连接数。改小了业务高峰期用户一多就直接报“Too many connections”页面全挂改大了也不见得是好事因为每个连接都要占用线程和内存连接数过多反而会让系统资源耗尽拖垮正常请求。一个实用的判断方法是去查两个关键的运行时指标Threads_connected当前已建立的连接数和Threads_running当前正在执行语句的线程数。如果Threads_connected接近max_connections上限但Threads_running很低说明连接数配置太紧可以调大。如果Threads_running本身就很高说明是并发执行的真高光调大连接数只会让情况更糟这时候要去看SQL本身的效率和锁等待情况。我实际项目里最常见的配置区间是200到500。大多数中小业务系统350左右已经能撑住日常流量。真正需要担心的不是连接数而是大量空闲连接占着不释放。这个问题要靠wait_timeout和interactive_timeout来约束。我曾经排查过一个线上系统连接数长期突破上限排查发现是应用层没有正确的连接池回收策略加上wait_timeout默认8小时太长空闲连接堆积成山。把wait_timeout调成300秒之后连接数压力立刻缓解。2.3 innodb_flush_log_at_trx_commit性能与数据安全之间的取舍这个参数控制的是事务提交时日志从内存缓冲区刷到磁盘的策略有三个取值。取1表示每次事务提交都把redo log刷到磁盘最安全性能也最差但能保证即使数据库宕机也不丢数据。取2表示每次提交只写到操作系统的缓存里由操作系统定期刷盘性能好很多但机器断电时可能丢最近的数据。取0则是每秒钟刷一次盘性能最好但MySQL自身崩溃都可能丢数据。绝大多数业务场景我建议保持默认值1。尤其涉及订单、支付、资金类的系统数据安全永远优先于性能丢一条事务记录就是重大的事故。但如果你的系统属于日志类、统计类或者可以接受少量数据丢失的辅助系统那么2是一个性价比很高的折中方案实测能比1带来两三倍以上的写入吞吐提升。有人会问这个参数和sync_binlog是不是一回事。其实两者独立控制innodb_flush_log_at_trx_commit管的是redo logsync_binlog管的是binlog。最安全的状态是两者都为1但这会让每次提交经历两次刷盘。如果对写入性能要求很高又不想丢数据可以在保证底层存储IO能力的前提下把sync_binlog调成1、innodb_flush_log_at_trx_commit保持1然后升级SSD这是我在项目里验证过的相对稳妥的方案。2.4 容易被忽视的几个参数除了上面三个大头还有几个参数在特定场景下效果非常明显。innodb_log_file_size定义redo log文件的大小。默认配置偏小在高并发写入场景下日志轮换频繁会导致不必要的刷盘和性能抖动。调到512M到1G配合较大的缓冲池写入性能会更稳定。但文件太大也会让崩溃恢复时间变长需要权衡。tmp_table_size和max_heap_table_size控制内存临时表的最大值。排序和去重操作如果数据量超过这个值临时表会落到磁盘上性能骤降。出现大量Using temporary类型的执行计划时可以考虑调大这两个参数。还有一个要说的是query_cache。这个参数在5.7及以下版本默认关闭到8.0直接移除了。很多旧教程还推荐开查询缓存但实际体验下来在写多读少的场景下查询缓存反而会成为全局锁一样的瓶颈。所有针对它的调优知识点都可以丢掉了不用担心这个参数8.0之后没有它反而省心。参数调整有个重要原则一次只改一个。我曾经遇到过一个线上故障同事一次性改了缓冲池、日志刷盘、排序缓冲等七八个参数结果出问题根本定位不到是哪个参数引起的。后来我给自己定了个规矩任何环境改动一次只改一个参数改完观察确认稳定再改下一个。3. 拉大差距的重头戏SQL与索引层面的调优实战3.1 慢查询日志定位问题SQL的第一入口配置调得再合理也挡不住一条烂SQL拖垮整个系统。所以我的调优流程里第一步永远是打开慢查询日志把那些“害群之马”找出来。慢查询日志本身很简单就是记录执行时间超过阈值的SQL语句。在MySQL中可以通过以下方式临时开启SET GLOBAL slow_query_log ON; SET GLOBAL long_query_time 1; -- 超过1秒的SQL都会被记录 SET GLOBAL slow_query_log_file /var/log/mysql/mysql-slow.log;线上生产环境我建议long_query_time先设置为1秒甚至0.5秒。等业务稳定再把阈值放到2秒减少日志量。还有两个容易忽略的开关log_queries_not_using_indexes可以记录所有没有走索引的SQL哪怕它执行很快这个开关对发现隐性问题很有用log_slow_admin_statements控制是否记录ALTER TABLE这类管理语句。拿到慢查询日志之后怎么分析我常用系统自带的mysqldumpslow工具也可以直接打开日志文件看慢日志文件里记录了每一条SQL的执行时间、锁等待时间、返回行数和扫描行数。重点看两类一类是执行频率高、单次执行也不算快的SQL这类SQL平均耗时虽然不大但累积起来会吃掉大量资源另一类是单次执行极慢的SQL这类往往是全表扫描或者复杂关联查询。3.2 读懂EXPLAIN执行计划里的每一个关键信号没有EXPLAIN的慢SQL分析等于盲人摸象。EXPLAIN是MySQL提供的一条命令作用就是展示MySQL执行一条SQL时的具体计划。用法很简单在SQL前面加EXPLAIN关键字MySQL会返回一张执行计划表。这里面的每个字段都是有价值的信号。我最先看的永远是type列。这张表把访问类型从好到坏分成好几个层级依次是system表中只有一行、const用主键或唯一索引查到一行、eq_ref关联查询时驱动表每行只匹配一行、ref用普通索引等值查询、range用索引做范围查询、index遍历索引树、ALL全表扫描。看到ALL基本可以断定这条SQL有问题但也别慌全表扫描不是罪不可赦如果表数据量本来就很小那走全表扫描反而比走索引更高效。其次是key列看实际用了哪个索引。有时候你给表建了索引但MySQL没有选它这就是典型的“索引白建”。再看rows列它估算的是MySQL认为它需要扫描的行数。这个值和实际表行数差距越大说明统计信息越不准或者SQL写法给了优化器错误的引导。最后别忘看Extra列出现Using filesort表示排序没有走索引产生额外开销出现Using temporary说明用到了临时表通常是GROUP BY或者DISTINCT导致要特别警惕出现Using index则是好信号表示覆盖索引生效不用回表。EXPLAIN不是万能的它显示的rows是估算值而非实际值而且EXPLAIN只展示单条SQL的计划。真正确认问题还要配合PROFILING或者状态变量来看实际耗时。但绝大部分情况下慢SQL通过EXPLAIN就能定位到七八成问题。3.3 索引设计最左前缀法则与常见的索引失效索引是MySQL调优里的第一生产力但建了索引不等于万事大吉。联合索引遵循最左前缀法则一个(a, b, c)联合索引可以匹配(a)、(a, b)、(a, b, c)三种查询组合但如果查询条件跳过了a直接以b开头这个索引就无法使用。这是我在实际业务场景里反复验证过的很多人建了联合索引但查询条件用不到最左列索引就直接失效了。常见的索引失效场景还有这么几个。第一对索引列使用函数比如WHERE DATE(create_time) 2024-01-01这种写法会让索引失效正确的做法是改成范围查询WHERE create_time 2024-01-01 00:00:00 AND create_time 2024-01-02 00:00:00这样不仅索引能用查询也更准确。第二LIKE模糊查询时前置通配符LIKE %关键字这种写法索引也会失效。第三隐式类型转换比如索引列是字符串查询条件却传了数字MySQL做了类型转换后索引照样用不上。第四索引列参与了运算比如WHERE price 10 100这种写法也需要改写。还有覆盖索引的概念我觉得价值很高。普通索引查到行号之后还需要回表去查整行数据但如果查询的字段都在索引树上MySQL就不需要回表了直接就能返回。举个例子SELECT user_id, status FROM orders WHERE user_id 123如果user_id是索引列而status不是那就要回表如果我在(user_id, status)上建了联合索引那么这个查询可以直接从索引树拿到全部需要的字段速度会快很多。覆盖索引还能减少随机IO在高并发场景下效果明显。3.4 深分页和Join两个高频性能陷阱深分页问题在日常业务里太常见了。LIMIT 200000, 10这个写法MySQL会先扫描出前面200010条记录然后丢掉前200000条只返回最后10条。这个过程中所有扫描和排序都是真实发生的所以页码越深查询越慢而且是线性上升的。优化深分页有个经典做法叫延迟关联。思路是先通过子查询快速定位到目标行的主键ID再拿这批ID去关联原表取完整数据。比如-- 优化前 SELECT * FROM orders ORDER BY create_time DESC LIMIT 200000, 10; -- 优化后 SELECT o.* FROM orders o INNER JOIN ( SELECT id FROM orders ORDER BY create_time DESC LIMIT 200000, 10 ) t ON o.id t.id;子查询里只查主键和排序字段用的是覆盖索引扫描成本低很多外层再按主键精确取行。还有一种思路是基于索引做偏移量循环即每次记下上一页最后一条记录的位置用WHERE create_time 上一页最后时间来取下一页而不是用OFFSET。这两种方式我都实测过延迟关联实现简单、改动小推荐优先考虑。Join层面的坑最典型的是没有索引驱动的关联。一张千万级的大表和一个只有几十行的小表做JOIN如果关联字段没有索引MySQL的嵌套循环可能要做几百万次数据比对。这里有一条铁律关联查询中驱动表小表的关联字段需要有索引被驱动表大表的关联字段也必须有索引。另外要记住一个原则——小表驱动大表。在编写查询时尽量把筛选后数据量小的结果集作为驱动表优化器在很多情况下会自动调整但复杂场景下手工调整会更可靠。有一次我帮一个团队排查一条报表SQL三张表做JOIN带了一堆WHERE条件执行时间稳定在8秒。用EXPLAIN一看type列全是ALL于是给关联字段和WHERE条件里的筛选字段补了索引。修改之后执行时间降到0.1秒整个报表页面像换了台机器一样。这种案例在调优工作中几乎每天都在发生SQL慢往往就是缺一个索引或者有一个索引失效。4. 从一次线上事故说起完整调优案例走查4.1 故障现象一个订单查询接口的响应时间暴涨有一段时间我负责的一个业务系统在下午高峰期频繁出现接口超时告警。最开始只是零星几条后来越来越密集。这个接口是订单查询业务方反馈用户端页面加载要转圈好几秒。查监控数据MySQL所在机器的CPU使用率不算太高内存也还有余量但磁盘IO等待时间明显升高慢查询日志里的记录数量比平时翻了十几倍。第一反应是去看慢查询日志还真让我一眼就看到了问题一条查询订单列表的SQL单次执行时间竟然到了2.6秒平时这个接口全链路也就80毫秒。从慢日志的扫描行数和返回行数来看扫描了38万行最终只返回20行数据——典型的“大海捞针”式查询。这条SQL的业务逻辑是按用户ID、订单状态、下单时间三个条件筛选订单再按时间倒序取第一页。4.2 定位过程从慢日志到执行计划的逐层排查拿到慢SQL我立刻在测试库里跑了一遍EXPLAIN结果很直观type列显示ALL全表扫描rows列估算38万行Extra列还出现了Using filesort。看到这三个信号问题其实已经比较清楚了——这张订单表的查询条件字段没有合适的索引MySQL只能把整张表读一遍再在内存里做排序。但这里有个值得留意的细节表里其实已经有一个订单号的唯一索引也有一个按创建时间的索引。那为什么WHERE条件用了user_id、status和create_time优化器还是走全表呢因为这三个字段没有组成联合索引单靠其中一个根本无法高效过滤。create_time索引虽然存在但订单表的create_time分布极其不均匀——这张表上线快三年了90%以上的订单都集中在最近几个月而查询场景里用户通常会选择半年前甚至更早的订单。优化器遇到这种情况评估下来走create_time索引还不如全表扫描加排序来得“划算”。这个例子很典型不是索引不够多而是没有根据查询模式设计正确的联合索引。4.3 实施优化建立联合索引和改写SQL定位到问题后我做了两步操作。第一步建立联合索引。根据查询条件的组合我把联合索引设计为(user_id, status, create_time)。设计顺序的逻辑是user_id是等值筛选选择性最高放在最左边status也是等值条件次之create_time是范围条件放在最后。这样查询走索引时能够通过user_id和status快速定位到目标集合然后在create_time索引段内做范围有序扫描同时解决排序问题——因为索引本身按create_time有序查询到结果时已经天然排好序了Using filesort也顺带消失。需要注意的一点是如果条件里status有多个值比如使用IN (1,2)索引在status之后的部分可能不会完全使用但在这个案例里status是等值条件下的INMySQL 5.6之后的索引条件下推优化也能妥善处理整体效果依然很好。第二步改写SQL。原SQL里用SELECT *获取所有列我改为按接口实际需要的字段进行SELECT。这几个字段正好全部在联合索引的叶子节点上覆盖到了变成了覆盖索引查询连回表都省了。同时分页查询改造改用上一页订单ID作为游标来取下一页而不是用LIMIT OFFSET来做深分页。优化完成后我在测试库先验证了一遍同样的查询条件下执行时间从2.6秒降到45毫秒扫描行数从38万降到了不足1000行EXPLAIN里type从ALL升级为refExtra也没有Using filesort了。上线之后连续观察一周这个接口的线上平均响应时间稳定在60毫秒左右高峰期再也没出现超时告警。4.4 复盘调优前后对比和踩坑记录复盘这个案例有几个地方值得拿出来单独说。第一个是一开始差点走弯路。最初我准备直接扩充硬件资源来解决理由是磁盘IO等待时间长。但仔细分析后发现硬件层面的IO压力是结果而不是原因——SQL读了几十万行数据全不在内存里导致反复读磁盘。就算换了再快的磁盘这种写法依然会被拖慢。正确的顺序是先优化SQL再看硬件需要不需要升级。第二个是索引上线时的风险控制。给线上大表建索引时如果直接执行ALTER TABLEMySQL会重建整张表几千万行的订单表可能锁写几个小时。我当时的做法是用pt-osc这类在线变更工具或者利用8.0版本的ALGORITHMINPLACE选项在不阻塞业务读写的情况下完成索引创建。这一步如果没注意优化的过程本身就会变成一次事故。另外还发现一个附加问题这个接口在高峰期偶尔报“Too many connections”。查了一下Threads_connected确实到了峰值上限。原因是应用层的连接池配置过大加上很多长时间空闲的连接没有及时释放。我把wait_timeout从默认的8小时调到300秒同时调整了应用连接池的最小空闲连接数这个问题也没有再出现过。5. 避坑指南常见问题、排查套路与实操笔记5.1 改完参数不生效是怎么回事参数改完不生效是我在交流群里看到最多的问题。通常有三种原因。第一种是改错了级别。MySQL参数分全局级和会话级你用SET SESSION改的只对当前会话生效别的连接感知不到。用SET GLOBAL改的是全局级别的但是注意已经存在的旧连接不会立刻拿到新值要重新连接才会应用。所以改参数后要么等连接重建要么主动重启应用。第二种是参数被静态限制。像innodb_buffer_pool_size这类参数其实支持动态修改但innodb_log_file_size、max_connections在部分版本里就不能在线生效。遇到这类参数需要修改配置文件并重启MySQL。改配置文件之前务必做好备份重启之前再看一遍配置项有没有语法错误。第三种是重启之后丢配置。很多人只用了SET GLOBAL临时改没有更新配置文件MySQL一重启就回到原样。正确的姿势是两条路同时走一边在线SET GLOBAL一边把配置写进/etc/my.cnf或/etc/mysql/mysql.conf.d/对应文件双保险。5.2 索引建了但优化器就是不选这是另一个高频困惑。索引明明建了EXPLAIN出来还是ALL。我遇到过几个具体情况。一个是统计信息过期。MySQL的优化器靠表的统计信息来决定执行计划如果表刚经历过大量数据变更统计信息没跟上优化器就会做出错误判断。解决办法是执行ANALYZE TABLE 表名刷新统计信息很多情况下刷完之后执行计划就正常了。另一个是数据分布问题。优化器评估后发现就算走索引也仍然要访问大量行那还不如全表扫描顺序读。比如一张小表总共几百行索引查询需要访问其中一半数据优化器就会倾向全表扫描。这种情况不是病不需要强行纠正。还有一个是查询写法限制。前面提到的隐式类型转换、函数包裹索引列、LIKE前置通配符都会让优化器想用索引也用不上。这种问题只能靠改写SQL解决没有捷径。如果实在判断不了优化器为什么选了次优计划可以用FORCE INDEX强制走某个索引来对比效果。但这个方法我只建议在排查阶段用不建议上线长期使用——表数据量变化后强制索引可能反而不如优化器自己的选择。5.3 磁盘IO持续高的排查套路磁盘IO高是MySQL最容易被误诊的问题之一。很多新手看到IO高第一反应是加内存或者换高性能SSD。但在动手之前我建议先区分两类情况逻辑IO高和物理IO高。逻辑IO高意味着数据库频繁地进行大范围的数据读取表现在SHOW GLOBAL STATUS里面的Innodb_pages_read持续增加。这种情况的根因通常是SQL扫描行数过多要么是缺索引要么是查询条件不带过滤且大量读取。物理IO高则是数据不在内存中必须从磁盘读取这跟缓冲池大小和冷热数据比例有关。排查顺序我一般是这样的先用iostat看磁盘的利用率、IO队列长度和等待时间确认IO压力真实存在再打开performance_schema或者sys库按SQL语句分组统计磁盘逻辑读次数。sys.statement_analysis这个视图能直接列出每条SQL的累计执行次数、总耗时、扫描行数非常好用我线上定位问题SQL基本都靠它。物理IO持续高还有一个容易被忽略的原因——脏页刷盘。如果业务是密集写入型innodb_io_capacity设置过低会导致InnoDB在批量刷脏页的时候产生IO尖峰。这种情况下调整innodb_io_capacity和innodb_io_capacity_max配合合理的innodb_max_dirty_pages_pct能有效平滑刷盘节奏。5.4 连接数暴涨的应对方案连接数暴涨在业务大促或者突发流量场景下非常常见。处理这类问题不能只盯着max_connections这一个参数我总结了一套组合拳。第一是快速止血。如果当前Threads_connected已经接近上限先临时调大max_connections给业务喘息空间但这个操作会加大MySQL本身的压力适合短期顶一下不适合长期维持。同时立刻把连接池参数优化缩短wait_timeout让空闲连接快速释放。第二是找源头。连接数暴涨往往不是数据库的问题而是应用层的连接池配置不合理。应用连接池的maximum-pool-size如果配得过大在流量尖峰时所有线程同时抢连接瞬间就会把数据库连接数打满。我在项目里曾遇到过一次事故应用侧连接池上限配到了200三个服务实例同时扩容结果数据库瞬间收到600个连接直接把MySQL打挂了。后来把应用连接池上限压到50每实例数据库侧再配合合理的max_connections就稳下来了。第三是处理慢查询。如果连接数高且Threads_running高说明大量连接进来后都在跑很慢的SQL连接被长时间占用。这时候核心任务是找慢SQL并优化否则连接数只会持续堆积光调参数没意义。如果短时间内无法优化SQL可以考虑在数据库前面加一层查询缓存或把读流量切到只读副本这些都是过渡方案但能争取到优化时间。5.5 软件层优化到顶之后的路把SQL和参数都优化到位之后单机MySQL的性能上限早晚会到来。这时候要考虑的就不仅仅是“调优”二字了而是整个数据架构的演进。最基础的一步是读写分离。业务中的读流量通常远大于写流量把读流量分到只读副本上应用层通过中间件做路由能够显著减轻主库压力。这里要注意的是读写分离会有主从延迟问题如果业务对一致性要求很高读自己的写这种场景需要做特殊处理比如强制读主库。再往前走是分库分表。当单表数据量超过千万甚至上亿即使索引设计很合理写入性能也会遇到瓶颈。分库分表不是小事它会引入分布式事务、全局ID、跨库查询等一系列复杂问题所以一定要在架构层面充分论证后再动手。还有一种更温和的过渡方案是数据归档把历史订单数据迁移到归档表或冷存储业务表只保留近几个月的数据这种方式执行成本低在业务允许“查旧数据走专门通道”的前提下非常实用。还有就是在数据库前面叠加缓存层。热点数据进Redis之类的内存缓存数据库只承担最终一致性写入和兜底查询。这一层加得好数据库的压力可以下降一个数量级。我见过很多性能问题其实根本不该找数据库解决——有些数据读多写少、实时性要求不高却每次实时查库这种场景不优化缓存策略数据库再调也没什么用。我个人在实际操作中的体会是MySQL调优的终点不是某个参数完美而是你对自己的数据访问模式有了清晰认知知道哪部分工作数据库自己能干好哪部分应该交给缓存、交给拆分、交给架构去解决。尊重每一层的职责边界性能问题才能真正长治久安。

读完文章,也想定制专属网站?

尧图设计师 24 小时内与您沟通定制方案

免费获取报价 →
↑