news 2026/10/1 9:43:26

数据库性能优化全路径:从索引设计到分库分表实战指南

作者头像

张小明

前端开发工程师

1.2k 24
文章封面图
数据库性能优化全路径:从索引设计到分库分表实战指南

做数据库性能优化这些年,我最常听到的一句话就是“系统越来越慢了,数据库顶不住了”。业务方催、老板催,开发和DBA互相甩锅,最后查下来十有八九不是数据库真的扛不住,而是索引没建对、SQL写得糙、或者架构本身就停在单机阶段。这篇我就从索引、SQL、连接池、架构四个层面,把数据库性能优化的完整路径捋一遍,每一条都是我在线上环境实测过的方案,也会把踩过的坑摊开来说,希望能帮你少走几段弯路。

先说个定心丸:大部分所谓的“数据库性能瓶颈”,根本轮不到上分布式、分库分表这种重型武器。优化是有层级的,从成本最低、收益最明显的索引和SQL入手,一步步往上走,多数问题在第一层第二层就能解决。真正走到架构层面的,其实占比不高。所以我会先讲定位问题的思路,再逐个层面拆解实操方案。

1. 数据库性能优化的底层逻辑:先定位瓶颈,再谈优化

1.1 性能瓶颈到底卡在哪一层

很多人一接到“数据库慢”的反馈,第一反应就是加索引或者重启。这是典型的本末倒置。数据库变慢的根因通常可以分成四类:CPU密集型、IO密集型、锁竞争和网络延迟。你要先搞清楚瓶颈卡在哪一层,优化才有针对性。

CPU密集型,表现为数据库服务器的CPU使用率持续飙高,业务高峰期几乎打满。这种场景通常是复杂的聚合计算、大量排序、函数运算把CPU吃满了,比如在一个百万行的表上做GROUP BY配合SUM、COUNT,或者对索引列做了函数运算导致全表扫描。

IO密集型,表现为iowait高、磁盘读写频繁、数据页缓存命中率低。系统慢,但CPU其实闲着。这种最常见的原因就是内存缓冲池配置太小、查询没走索引导致大量随机读、或者数据量太大内存装不下。判断起来很简单,观察vmstat里的wa列,或者看MySQL里Innodb_buffer_pool_reads和Innodb_buffer_pool_read_requests的比值。

锁竞争,表现为TPS上不去、Threads_running持续偏高、行锁等待时间增长。最典型的场景是热点行并发更新、事务长时间不提交导致锁持有时间过长。MySQL里可以通过SHOW ENGINE INNODB STATUS查看锁等待,或者监控innodb_row_lock_wait_time_ms这个指标。注意,锁问题单独靠加索引是解决不了的,要从事务设计和隔离级别入手。

网络延迟,这个在微服务架构下特别多。应用服务和数据库跨机房部署,一次查询来来往往几十次往返,延迟自然高。表现为接口响应慢,但数据库的QPS并不高,慢查询日志里也找不到明显的慢语句。这种基本是交互太多、连接复用不足或者缓存缺失导致的,属于应用层的性能问题,别甩锅给数据库。

我的建议是先看监控面板,再看慢查询日志,最后用EXPLAIN验证SQL执行计划。三步下来,瓶颈大概率已经清楚了。这个过程走完再动手,才不至于白忙活。

1.2 优化分层模型:索引、SQL、参数、架构

我给团队定的优化顺序是这样的,直接当工作原则用:

  1. 索引层:检查SQL是否走了合理的索引,有没有复合索引可以优化,有没有索引失效的情况。这一层改动最小、收益最大。
  2. SQL层:改写低效SQL,消除不必要的回表、避免深度分页、减少大事务。
  3. 参数层:调整连接池大小、缓冲池大小、刷盘策略等。注意参数不是越多越好,改错反而出问题。
  4. 架构层:加缓存、做读写分离、分库分表、走向分布式。这是最重的改动,要放在最后。

举一个我印象很深的案例。有个业务表三百万行,一个列表查询接口耗了三秒多。开发同学上来就想上读写分离,说数据库扛不住了。我拿慢查询日志一看,SQL长这样:SELECT * FROM orders WHERE status=0 ORDER BY create_time DESC LIMIT 10,整条语句没走索引,EXPLAIN出来的type是ALL,全表扫描三百万行再排序。一个复合索引(status, create_time)就解决了,加完之后接口耗时从三秒掉到20毫秒。读写分离根本不需要做。

这种案例太多了,所以我反复强调:从下往上做,先把地基打牢。地基不牢,架构上再花哨都是空中楼阁。

2. 索引设计的实战要点:从单列到复合索引

2.1 单列索引还是复合索引,不能拍脑袋

索引是数据库优化里最核心的手段,没有之一。MySQL的InnoDB引擎用的是B+树,索引本质上是一棵排好序的树,目的就是减少扫描的数据量。单列索引解决的是单条件过滤,比如WHERE user_id=5这种;复合索引解决的是多条件过滤,比如WHERE status=0 AND create_time>='2024-01-01',还能顺便做覆盖索引减少回表。

很多新人会犯一个错:每个字段都建一个单列索引,觉得这样不管怎么查都能用上。这在MySQL里往往事与愿违,因为一次查询一般只能选一个索引来用。如果选了idx_status,那create_time的过滤就变成回表之后在内存里筛了。你写了两个单列索引,实际执行计划大概率只用一个,另一个白白占空间还拖慢写入。

那什么时候用复合索引?原则很简单:当查询条件里同时有多个字段,而它们之间是AND关系时,优先考虑建立一个复合索引,让所有过滤条件都能在索引树上完成。但复合索引也有讲究,最关键的就是字段顺序,这就是“最左前缀原则”:MySQL的复合索引只能从左向右匹配,你建了(a, b, c)索引,能用到a、a+b、a+b+c这三种组合,但b单独查、c单独查、b+c查都走不了这个索引。

2.2 mysql where条件a and b,应该怎么建索引

这是被问得最多的问题:一张订单表,查询条件是WHERE a=XXX AND b=YYY,索引到底怎么建?这个热搜词我太熟悉了,几乎每天都有开发来问。

先说结论:复合索引建一个就够,顺序取决于字段的区分度,区分度高的放左边。所谓区分度,就是某个字段的不同值数量占总行数的比例。比如a字段一千万行里有一百万个不同值,区分度就是0.1;如果b字段只有10个不同值,区分度就是0.000001。区分度越高的字段,过滤掉的行数越多,放左边能更快缩小扫描范围。

实操做法是先查一下两个字段的区分度:

SELECT COUNT(DISTINCT a) / COUNT(*) AS card_a, COUNT(DISTINCT b) / COUNT(*) AS card_b FROM your_table;

假设a的区分度是0.35,b是0.02,那就应该建(a, b)这个复合索引。执行计划里type会从ALL变成ref,扫描行数大幅下降。如果反过来建(b, a),索引也能用,但会先按b过滤出大量数据,效率就差一个数量级。

还有一个细节容易被忽略:如果项目里除了WHERE a AND b之外,还经常单独WHERE a查询,那复合索引(a, b)可以同时复用,因为最左前缀原则保证了a能走这个索引。但如果还有频繁的WHERE b单独查询,那对不起,光靠(a, b)解决不了,你得额外评估要不要给b建单列索引。注意权衡写入成本,索引越多,INSERT、UPDATE越慢,不是越多越好。

2.3 主键索引、覆盖索引与索引失效的坑

主键索引在InnoDB里是聚簇索引,表数据本身就挂在主键的B+树上。这意味着主键查询是最快的访问路径。但主键怎么设计,直接影响写入性能和存储空间。

我最推荐自增整数主键,或者雪花算法生成的分布式ID。不要用UUID这种随机字符串做主键,因为B+树是按主键顺序排列的,随机值会让索引页频繁分裂、碎片增多,写入性能明显下降。还有一个隐藏问题:InnoDB的二级索引叶子节点存的是主键值,主键越长,每个二级索引占用的空间越大,查询时IO开销也越大。UUID是32位十六进制字符串,比8字节的长整数大好几倍,全表索引体积都会膨胀。

覆盖索引是个很实用的技巧。简单说,就是查询需要的所有列都在索引里,查询过程不需要回表去查聚簇索引。比如有个查询:

SELECT user_id, create_time FROM orders WHERE status=1;

如果你建了(status, user_id, create_time)复合索引,那这个查询直接扫索引就返回了,Extra列会显示Using index。如果只建了(status),那索引查到主键后再回表拿user_id和create_time,多一次随机IO。在高频查询场景,回表的性能差异是数量级的。

索引失效的坑,我列几个最常见的:

  • 对索引列使用函数: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'。
  • 隐式类型转换:WHERE user_id=123,如果user_id是字符串类型,MySQL会自动把两边转成数值再比较,索引失效。反过来WHERE mobile=123,mobile是字符串,也可能触发转换。查一下字段类型,匹配上就好。
  • LIKE以通配符开头:WHERE name LIKE '%张%'走不了索引,但WHERE name LIKE '张%'可以走,因为前缀是确定的。
  • OR连接的条件不全有索引:WHERE a=1 OR b=2,如果只有a有索引,那这个查询还是会全表扫描。要么给b也建索引,要么改成两个查询后用UNION ALL。

这些坑我基本都亲自踩过,印象最深的是有一次排查线上慢查询,发现开发同学给时间字段建了索引但查询用了DATE()函数,导致全表扫描。这种问题用EXPLAIN一看就知道,type是ALL,key是NULL。

3. 慢SQL排查与SQL改写实战

3.1 EXPLAIN要怎么看,才能快速定位问题

EXPLAIN是排查慢SQL的第一工具,但很多人看了等于没看。我告诉你重点看哪几列。

type字段,从上到下按性能排序:system>const>eq_ref>ref>range>index>ALL。const和ref是理想状态,range是范围扫描还能接受,index是全索引扫描,ALL是全表扫描,是性能最差的,看到ALL就要警惕了。

key字段,显示实际用到的索引。如果是NULL,说明没走任何索引,这就是第一嫌疑犯。

rows字段,是MySQL估算的扫描行数,这个值直接决定查询的耗时量级。优化目标非常简单:让rows尽可能小。

Extra字段,重点看几个危险信号:Using filesort代表排序没走索引,需要额外排序;Using temporary代表用了临时表,通常出现在GROUP BY、DISTINCT这类操作上;Using index是加分项,代表覆盖索引生效了。

举个实例。有个慢查询:

EXPLAIN SELECT * FROM payment WHERE user_id=123 ORDER BY pay_time DESC LIMIT 10;

假设结果是:typeref、keyidx_user_id、rows5000、ExtraUsing filesort。这说明索引定位到了5000行,但排序是文件排序。优化方法很简单,把索引改成(user_id, pay_time),这样同一个索引里已经按pay_time排好序了,Extra变成空的,文件排序消除。

3.2 典型慢SQL改写案例:分页、子查询和OR

我收集了几个高频慢SQL的改写方案,每一个都在线验证过,性能提升至少一个数量级。

第一个,深度分页。LIMIT 100000, 20这种写法,MySQL要扫描前100020行再丢掉前100000行,越翻页越慢。正解是延迟关联:

-- 优化前 SELECT * FROM orders ORDER BY id LIMIT 100000, 20; -- 优化后 SELECT o.* FROM orders o JOIN (SELECT id FROM orders ORDER BY id LIMIT 100000, 20) t ON o.id = t.id;

子查询只在索引里找20个id,再回原表拿完整数据,扫描量从十万行锐减到几十行。实测下来,这个优化在数据量大时能把耗时从几百毫秒降到个位数毫秒。

第二个,NOT IN改LEFT JOIN。查出所有没有订单的用户:

-- 优化前 SELECT * FROM user WHERE id NOT IN (SELECT user_id FROM orders); -- 优化后 SELECT u.* FROM user u LEFT JOIN orders o ON u.id = o.user_id WHERE o.user_id IS NULL;

NOT IN子查询在某些版本里优化不够好,容易导致全表扫描。改成LEFT JOIN加空值判断,执行计划往往更优。这里注意,如果子查询的结果集很小,NOT IN可能也不差,要实际看执行计划再决定。

第三个,OR改UNION ALL。WHERE a=1 OR b=2,如果a和b都有独立索引,MySQL优化器不一定能正确使用索引合并。更稳的是拆开:

SELECT * FROM t WHERE a=1 UNION ALL SELECT * FROM t WHERE b=2;

前提是两个分支不会产生重复行,否则用UNION。这个改写特别适合热点查询,索引利用率明显提升。

第四个,不要SELECT *。很多人图省事写SELECT *,查出来的列全要回表拿一遍,覆盖索引直接失效。只查业务真正需要的字段,可能让查询变成Using index,性能差距非常大。

4. 连接池与数据库参数调优

4.1 连接池不是越大越好:以HikariCP为例

连接池是把双刃剑。很多团队的直觉是“并发高就把连接池调大”,结果越调越慢。原因很简单:每个连接背后都是一个线程,连接过多会导致CPU上下文切换开销剧增;数据库端的连接数也是有限资源,MySQL默认max_connections是151,连接过多反而互相排队。

连接池大小的经验公式,我一般推荐:核心CPU数 * 2 + 1。比如服务器是8核,连接池给17左右合适。这个公式不是拍脑袋,它遵循一个原则:数据库IO密集型场景下,一个CPU核同时能支撑的有效并发操作大概就是两个左右,多了只会增加排队等待。

HikariCP是目前Spring Boot默认的连接池,配置很干净。我常用的最小配置如下:

spring: datasource: hikari: minimum-idle: 5 maximum-pool-size: 17 connection-timeout: 30000 idle-timeout: 600000 max-lifetime: 1800000

几个参数关键的要解释一下:

  • connection-timeout:客户端获取连接的超时时间,太短高峰期会拿不到连接,太长会让请求堆积。我一般设30秒。
  • max-lifetime:连接最大存活时间,建议小于数据库的wait_timeout。为什么要设置?因为网络中间件、防火墙可能会断开空闲连接,让连接池里的连接变成“死连接”,设一个最大值定期换新的更稳。
  • minimum-idle:最小空闲连接数,取决于业务低峰期的并发量。流量波动大的业务建议设小一点,避免低峰期还占着一堆连接。

连接池优化的核心思路是:够用就好,留出合理余量,但别让连接成为新的瓶颈。线上如果出现Connection is not available,先看连接池是不是设太小了,再看是不是有连接泄露——很多团队是因为没关闭PreparedStatement导致连接被占死。排查连接泄露的办法,最简单的就是看监控里连接池活跃数是否持续居高不下,以及数据库端的连接数是否缓慢增长不回落。

4.2 MySQL核心参数怎么调才安全

参数调优是最容易“翻车”的环节,改错了可能连服务都起不来。我只说几个线上验证过的高频参数,以及合理的取值逻辑。

innodb_buffer_pool_size,这个最重要。它是InnoDB的缓冲池,相当于数据库的“内存缓存”,缓存索引和数据页。建议设置为物理内存的60%到70%。比如服务器32G内存,缓冲池设20G左右。设置太小会导致频繁磁盘IO,太大又会和操作系统争内存,触发swap。改这个参数要重启MySQL,所以一般放在停机窗口做。

innodb_flush_log_at_trx_commit,控制redo log的刷盘策略。有三个值:

  • 1:每次事务提交都刷盘,最安全,不会丢数据,但性能最慢。
  • 2:每次提交只写到系统缓存,每秒刷一次盘,性能好,但操作系统崩溃可能丢一秒数据。
  • 0:每秒刷一次,性能最好,但MySQL崩溃都可能丢数据。

线上我一般建议保持1,除非你明确知道业务可以接受丢失少量事务,比如日志、统计类数据,可以用2换取吞吐量。

max_connections,连接数上限。不是越大越好,每个连接都要占用线程栈和内存。我见过有人直接设成2000,结果数据库还没满,系统内存先爆了。合理的做法是根据连接池总量加上运维连接、后台任务的余量来算。比如应用连接池峰值50,3个应用实例加起来150,加上监控、备份等后台连接,设到200左右就够。

query_cache已经过时了,MySQL 8.0直接移除了这个功能。如果你还在用5.7以下版本,也别指望查询缓存解决性能问题,它的全局锁机制在写入频繁的场景下反而拖后腿。真正有效的缓存要放在Redis那一层,后面讲架构再展开。

参数调优有个原则,一次只改一个参数,观测一段时间,别同时动好几个,不然性能变化了你都不知道是哪个起的作用。我见过把缓冲池改大后又调刷盘策略,结果性能波动,回滚都不知道回滚哪个。

5. 从单机到分布式:架构层面根治性能瓶颈

5.1 缓存层先上:Redis缓解读压力

当索引和SQL没问题,但数据库的读QPS依然高企,比如单机扛不住上万QPS的读流量,第一步想的是加缓存,而不是分库分表。

经典方案是Cache Aside模式,也叫旁路缓存。逻辑简单:

  1. 读请求先查Redis,命中直接返回。
  2. 未命中,查数据库,回填Redis,设置过期时间。
  3. 写请求先更新数据库,再删除Redis中的缓存。

这里有个很多人踩的坑:更新缓存应该用“删除”而不是“更新”。你想想,如果先更新缓存再更新数据库,两个操作不是原子的,数据库更新失败缓存就变成脏数据。删掉缓存的话,下次读请求会重新从数据库加载,天然自愈。所以“更新数据库后删除缓存”是更安全的做法。

缓存还有三个经典问题,面试常考,线上也常遇到:

缓存穿透:查询一个根本不存在的数据,缓存没命中,每次都打到数据库。解决方案是缓存空值并设置短过期时间,或者用布隆过滤器前置过滤。

缓存击穿:某个热点key过期瞬间,大量请求同时打到数据库。解决方案是互斥锁,同一时间只让一个请求去加载数据库,其他请求等待或返回旧值。

缓存雪崩:大量key同一时间过期,数据库瞬间被打垮。解决方案是给过期时间加随机值,避免集体失效。

缓存层的收益非常明显,我曾经处理过一个报表接口,数据库QPS常年5000,加了Redis缓存后数据库QPS直接降到300,慢查询从一天几十条变成零条。但注意,缓存只适合读多写少的数据,更新频繁的数据放缓存反而是负担。遇到写多读少的场景,别上缓存,先考虑把写入路径优化好。

5.2 读写分离:让读和写各走各的路

加了缓存之后,如果依然有大量读请求需要实时查库,比如用户订单、账户余额这类不能承受缓存延迟的数据,就要考虑读写分离。

读写分离的思路是:主库负责写,从库通过主从复制同步数据,负责读。MySQL的主从复制基于binlog,从库用relay log回放,延迟一般在毫秒级,但在大事务、大DDL场景下延迟可能飙到秒级。

部署上很简单,应用层配置两个数据源,@Transactional(readOnly=true)的查询走从库,写操作强制走主库。很多数据库中间件也支持自动读写分离,比如ShardingSphere、MyCat。

但读写分离有一个核心矛盾:主从延迟。业务刚写入一条数据,立刻从从库查,可能查不到。这在订单支付类场景是致命的。我常用的应对方案有三招:

第一,刚写入的数据强制读主库。把“写后立即读”的请求路由到主库,其余查询走从库。实现上可以设置一个标记,比如@RouterHint(master=true)。

第二,使用半同步复制,确保从库收到日志后再返回写入成功,减少延迟窗口。牺牲一点主库性能换取一致性。

第三,监控从库延迟,SHOW SLAVE STATUS里的Seconds_Behind_Master大于阈值时暂时只读主库。

读写分离适合的是“读量远大于写量”的业务,比例一般是几十比一。如果你的业务读写比例接近,那读写分离的收益很有限,问题可能出在SQL本身。判定条件很简单:看binlog大小和每秒读请求量的比值,读请求是写请求五倍以上再考虑这个方案。

5.3 分库分表:最后的重型武器

分库分表是数据库扩展的终极手段,也是成本最高、改动最大的方案,能不上就不上。很多人把分库分表当成灵丹妙药,实际上它引入了路由、分布式事务、跨节点Join、全局主键等一系列新问题。所以我前面反复强调把前面几层做扎实,是因为大部分业务根本走不到这一步。

什么时候真的需要分库分表?我的判断标准是三条同时满足:

  • 单表数据量超过2000万行,且查询性能通过索引已经无法优化。
  • 写入QPS持续超过单库的承受能力,比如超过5000TPS。
  • 数据增长趋势明确,未来一年会翻一倍以上。

分库分表有两个维度:垂直拆分和水平拆分。垂直拆分是把表按业务域拆到不同库,比如订单库、用户库、商品库,这本质上和微服务拆分是同方向的。水平拆分才是真正的挑战,把一张大表按某个规则分散到多个库多张表。

分表键的选择是核心设计决策,没有之一。我常用的是哈希路由和范围路由两种:

哈希路由:用user_id % 16之类的算法把数据均匀分散到16个分片。优点分布均匀,缺点范围查询要遍历所有分片。

范围路由:按时间分片,比如按月份分表。优点范围查询高效、扩容方便,缺点热点集中,比如当月数据都在最新的表上。

业务中订单表我一般用user_id或者order_id做分表键,因为订单查询绝大多数是按用户来。但按订单号单独查询的场景怎么办?这就需要一个映射机制,或者用全局ID里的分片信息,比如订单号生成时把分片号编码进去,这样从订单号就能反推分片位置。

引入分库分表之后,原来在单库很容易做的事情全变得棘手:

  • 跨分片的JOIN,基本禁止,要拆到应用层做多次查询再合并。
  • 跨分片的COUNT、SUM,要聚合各个分片的结果。
  • 分布式事务,需要用TCC、Saga或本地消息表这类方案保证最终一致性。
  • 全局主键,不能用自增,要用雪花算法或者号段模式。

中间件的选型,我用得比较多的是ShardingSphere,它的读写分离、分库分表、数据加密在同一个生态里,配置驱动,对业务侵入小。MyCat我也用过,偏向传统Proxy模式,对存量系统改造相对简单,但性能和灵活性上ShardingSphere更优。

数据迁移和同步这块,如果你是从单库切到分库分表,现有存量数据怎么搬?一般用ETL工具或者中间件自带的数据迁移能力,在低峰期搬迁并校验增量。这一步很容易出问题,我建议先做全量迁移、再开增量同步、最后切流,三个步骤分开验证,不要图快一把梭。这里也回应一下热词里的“数据库同步软件”,在实际项目中,主从同步用MySQL原生的binlog复制就够,分片数据同步则需要借助中间件的数据迁移插件,关键是要有校验环节,不能搬完就当完事。

5.4 微服务架构下的数据库设计

微服务架构现在是标配了,但很多人把它理解成“把接口拆碎”,数据库还是共享一个大库。这不叫微服务,这叫分布式单体。微服务架构下的数据库设计有几个原则,直接关系到性能:

每个服务独享自己的数据库。订单服务和用户服务不要直接互相查询对方的表。这是为了避免强耦合,也避免一张大表被多个服务争抢。服务间要数据,通过API调用,或者通过消息订阅。

避免跨服务Join。原来在单库里一条SQL解决的问题,拆了服务之后怎么办?答案是应用层组装或者数据冗余。比如订单列表要显示用户名,可以在订单服务里冗余一份user_name字段,从用户服务同步过来。听起来不符合“范式”,但在微服务场景下是最常见的性能优化手段。

数据一致性用最终一致性。拆分服务后,创建订单和扣库存变成了两个服务的操作,不可能再用本地事务。方案是本地消息表加MQ:先写本地事务,再发送消息,下游消费并处理。牺牲了强一致性,换来了系统性能和可用性,这是分布式系统的必然取舍。

微服务架构还有一个数据库性能陷阱:服务间调用放大。一个页面汇聚了订单、用户、商品、物流四个服务的数据,前端一次请求,后端可能产生几十次数据库查询。如果不加缓存、不合并接口,数据库压力会被成倍放大。我处理过这类问题,方案是加一层聚合服务,或者用GraphQL统一的BFF层把多次查询合并,数据库查询次数从几十次降到两三次,性能提升立竿见影。

6. 常见问题与排查技巧实录

最后这部分,把我在实际运维中遇到的高频问题和解决思路整理成一个速查表,方便你遇到同样问题的时候直接对号入座。

症状可能原因排查与解决办法
某个接口偶发超时慢查询没被发现开启慢查询日志,slow_query_log=ON, long_query_time=1,分析典型慢SQL
数据库CPU高,SQL都不慢但整体慢并发连接过多、连接池过大调小连接池,检查是否有线程冲突
同样一条SQL有时快有时慢缓存命中率波动检查缓冲池大小innodb_buffer_pool_size是否足够
写入慢,更新一条数据要几百毫秒锁竞争查看SHOW ENGINE INNODB STATUS里的锁等待,检查是否有长事务
查询走了索引,但rows依然很大区分度低,索引效果差重新设计复合索引,把区分度高的字段放左边
分页翻到后面特别慢LIMIT过大延迟关联改写
上线后数据库连接不够用应用连接泄露检查连接释放逻辑,打开连接池监控看活跃连接数
数据量不大,但表体积很大行溢出、碎片多OPTIMIZE TABLE,评估是否字段类型过大

排查工具这块,我平时依赖的就三个:Percona Toolkit里的pt-query-digest分析慢查询日志统计、EXPLAIN人工检查执行计划、以及Prometheus+Grafana做数据库指标监控。新项目我非常建议提前把performance_schema开起来,这是MySQL自带的性能采集工具,能拿到等待事件、锁等待、IO统计等关键数据,排查问题会舒服很多。

再分享一个我个人的实操习惯:每次优化一个SQL之前,先把EXPLAIN结果和执行时间记录下来,优化之后做对比,形成一个小清单。不要只凭感觉说“好像快了一点”,要用数字说话。这个习惯让我少走很多弯路,也希望你能用起来。

数据库性能优化没有什么银弹,它更像一门“望闻问切”的手艺。多测、多看执行计划、多对比数据,你会发现自己对系统的敏感度越来越高。按照索引、SQL、参数、架构这条路径一层层走,多数性能瓶颈都能在你的掌控之内被解决。

版权声明: 本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若内容造成侵权/违法违规/事实不符,请联系邮箱:809451989@qq.com进行投诉反馈,一经查实,立即删除!
网站建设 2026/10/1 9:42:24

基于SpringBoot的农业乡村振兴助农管理系统(源码+讲解视频+LW)

联系博主 温馨提示&#xff1a;本人主页置顶文章(点我)开头有 CSDN 平台官方提供的学长联系方式的名片&#xff01; 温馨提示&#xff1a;本人主页置顶文章(点我)开头有 CSDN 平台官方提供的学长联系方式的名片&#xff01; 温馨提示&#xff1a;本人主页置顶文章(点我)开头有 …

作者头像 李华
网站建设 2026/10/1 9:41:45

DeepSeek Harness 本地 AI 工作台:从需求到成果的完整搭建与避坑指南

1. 为什么我要折腾一个本地 AI 工作台第一次看到“从一句需求&#xff0c;到看得见的成果”这个说法&#xff0c;我脑子里冒出来的不是兴奋&#xff0c;而是怀疑。过去两年我用过太多号称“一句话生成应用”的工具&#xff0c;绝大多数最后都停在“生成一段看起来像那么回事的代…

作者头像 李华
网站建设 2026/10/1 9:41:24

FACT模型:细粒度跨变量卷积实现多元时间序列动态交互预测

多元时间序列预测一直是数学建模竞赛里的重头戏。不管是华为杯还是研究生数学建模&#xff0c;碰到交通流量预测、空气质量预报、电力负荷预估这类题目时&#xff0c;最大的难点往往不是模型不够复杂&#xff0c;而是变量之间的关系压根不是静止的。你用一个固定矩阵描述变量关…

作者头像 李华
网站建设 2026/10/1 9:39:21

A星算法在无人机三维路径规划中的MATLAB实现与实战

做无人机项目、搞路径规划研究的同学&#xff0c;对A星这个名字应该再熟悉不过了。不过大多数人接触到的都是二维寻路&#xff0c;真正把它扩展到三维空间&#xff0c;和无人机飞行结合&#xff0c;这里面的门道就多了。这篇文章把“基于A星算法的无人机三维路径规划”这件事从…

作者头像 李华
网站建设 2026/10/1 9:36:36

从零开始AI工程:模型部署、数据漂移与全链路最佳实践

1. 为什么是“从零开始”&#xff1a;AI工程到底在造什么如果你点进这篇文章&#xff0c;大概率和我一样&#xff0c;在某个深夜对着报错的训练脚本发过呆&#xff1a;模型明明在排行榜上跑得很好&#xff0c;为什么一落到自己的业务数据里就各种翻车&#xff1f;这就是我把项目…

作者头像 李华