数据库存:产品技术团队实施建议:围绕性能优化稳步提升提高并发能力
数据库并发能力上不去时,最容易出现的错误不是“没有做优化”,而是团队优化得太快:接口一慢就加连接池,CPU 一高就加机器,报表一重就上缓存,最后甚至还没有确认瓶颈位置,就开始讨论分库分表。我的判断是,数据库性能治理首先是一项定位和取舍工作,其次才是 SQL、索引、缓存和架构技术。只有把请求延迟、连接池等待、锁竞争、磁盘 I/O、数据访问模式放在同一条证据链上,产品技术团队才能在不牺牲稳定性的前提下,稳步提升并发能力。
这篇文章不把“高并发”理解成一个孤立的 QPS 数字,而是从产品、研发、测试、运维共同实施的角度,拆解数据库并发优化的判断顺序、具体步骤、典型误区、验证方法和不同阶段的技术取舍。文中的案例数据会明确区分公开资料、工程经验和情景模拟,避免把未经验证的提升比例包装成真实项目结果。
在实际系统中,我不会只用“数据库每秒能处理多少请求”来描述并发能力。更有意义的定义是:在目标业务流量下,系统能否持续满足延迟、错误率、一致性和资源安全边界。
因此,数据库并发能力至少同时受到四类约束影响。第一类是单次请求的资源消耗,包括扫描行数、排序数据量、锁持有时间和网络传输量。第二类是请求之间的竞争,包括连接、CPU、磁盘、锁和缓存空间的竞争。第三类是访问模式,例如读多写少、热点写入、批处理和在线事务混在一起。第四类是业务容忍度,例如是否允许读写短暂不一致、是否可以异步处理、是否允许降级。
如果只看连接数,可能会把“更多请求排队”误认为“更强并发”;如果只看平均响应时间,可能会忽略高峰时 P99 已经严重恶化;如果只看数据库 CPU,则可能漏掉应用线程卡在连接池获取、锁等待或网络调用上的情况。
| 观察维度 | 需要回答的问题 | 常见误判 |
|---|---|---|
| 吞吐量 | 目标流量下每秒完成多少有效请求? | 把数据库连接数直接当成 QPS |
| 延迟 | P95、P99 是否在高峰期持续恶化? | 只看平均值,忽略长尾请求 |
| 资源 | CPU、内存、磁盘 I/O 是否超过安全区间? | CPU 不高就认定数据库没有瓶颈 |
| 竞争 | 连接池、锁、热点行是否造成等待? | 看到慢查询就只改索引 |
| 业务结果 | 是否出现超时、重复提交、读写不一致? | 技术指标改善,但业务体验变差 |
对产品技术团队来说,最重要的结论是:优化目标不应写成“把并发提升十倍”,而应写成“在某个峰值流量下,将核心接口 P99 控制在目标范围,错误率不超过阈值,同时数据库资源保持可持续”。

我通常把数据库并发治理分为五个层次。第一层是确认瓶颈,第二层是 SQL、索引和数据访问治理,第三层是连接、事务和锁控制,第四层是缓存、读写分离和异步化,第五层才是分库分表、数据拆分和整体架构调整。
这个顺序并不是因为架构改造不先进,而是因为前面的基础问题往往成本更低、收益更容易验证。一个存在全表扫描、重复查询或长事务的系统,即使增加只读节点,也可能只是把低效访问复制到更多机器上。
每一个阶段都应有退出条件。例如,SQL 优化阶段不能只写“完成索引调整”,而应要求高频慢查询数量下降、扫描行数减少、核心接口 P99 改善,并且写入性能没有明显恶化。
以数据分析、经营看板和业务查询系统为例,用户打开看板时通常会触发多个指标查询:销售额、订单数、客户数、区域排名、同比环比和明细列表。单个查询看起来并不复杂,但当多个图表同时加载、多个用户同时刷新,再叠加定时同步和批量计算时,数据库面对的不是一条 SQL,而是一组相互竞争的访问任务。
这类场景在九数云这类数据分析平台的业务使用方式中尤其值得关注。这里需要说明:下面是基于数据分析平台常见访问模式设计的情景案例,用于说明排查方法,不代表九数云官方披露的具体客户性能数据,也不构成对某个部署环境的性能承诺。
假设一家零售企业有 300 名内部用户,工作日上午九点集中打开经营看板。每次看板加载触发 12 个查询,其中 8 个查询读取聚合数据,4 个查询读取明细数据;同时,系统每 15 分钟执行一次销售数据同步。平峰时每秒约 25 次查询,高峰时短时间内升至 160 次。数据库并不一定马上 CPU 打满,但连接池等待、临时表、排序和磁盘读取可能同时上升。
如果团队只看“数据库 CPU 只有 55%”,很容易得出数据库还有余量的结论。但从用户角度看,页面可能已经从 2 秒打开变成 12 秒。原因可能是磁盘 I/O 等待、连接获取超时、多个查询同时争抢临时空间,或者某几个低频但极重的明细查询拖慢了整体任务。
| 时间段 | 查询请求量 | 连接池等待 | 平均响应时间 | P99 响应时间 | 主要现象 |
|---|---|---|---|---|---|
| 平峰 | 25 次/秒 | 接近 0 | 620 毫秒 | 1.8 秒 | 用户访问基本稳定 |
| 看板集中打开 | 160 次/秒 | 明显增加 | 2.4 秒 | 11.6 秒 | 长尾延迟快速上升 |
| 同步任务重叠 | 175 次/秒 | 持续等待 | 3.1 秒 | 18.9 秒 | 在线查询和批处理争抢资源 |
这个案例最值得注意的地方是:高峰期问题不一定表现为数据库整体不可用,而可能先表现为少数关键请求的长尾延迟恶化。因此,产品团队需要关注“用户在高峰时最慢多久”,而不仅仅是“系统平均处理了多少请求”。

我在排查数据库慢问题时,会先把等待时间拆成四段:应用等待连接的时间、数据库排队或执行的时间、锁等待时间、结果返回和序列化时间。只要这四段没有拆开,团队就容易把所有问题都归到 SQL 上。
这四类问题的处理方式完全不同。连接池等待需要看应用实例数和连接占用时间;锁等待需要找长事务和热点行;执行计划问题需要看索引和数据分布;存储问题则要评估内存、磁盘和数据冷热分布。没有瓶颈分类,就没有可靠的优化方案。
连接池调大是最常见、也最容易被滥用的操作。应用实例数为 20,每个实例连接池上限为 50,理论上就可能向数据库发起 1000 个连接请求。可是数据库的 CPU、内存、锁和磁盘吞吐并不会因为连接数增加而同步增加。
当活跃连接超过数据库有效处理能力时,更多连接通常意味着更多排队、上下文切换和锁竞争。对于事务型业务而言,连接池过大还可能让大量请求同时进入关键区,导致响应时间从稳定状态突然恶化。
正确做法不是盲目调大连接池,而是建立一个整体关系:
数据库可承载连接数
≥ 应用实例数 × 单实例连接池上限
+ 管理连接
+ 运维和迁移连接
+ 故障切换预留
这个关系只是容量规划的上限约束,不代表连接池应该直接设置成数据库最大连接数。实际配置还要结合单次 SQL 耗时、事务占用时间、实例数量、请求峰值和数据库并行处理能力。连接池大小应该通过压测逐步寻找拐点,而不是凭经验填写一个更大的数字。

索引不是免费的加速器。它会占用存储空间、增加写入成本、增加更新和删除成本,还可能让优化器选择并不适合当前数据分布的执行计划。
我判断一个索引是否值得增加,至少会看四件事:查询的执行频率、过滤条件的选择性、扫描行数与返回行数的比例、对写入链路的影响。一个每天只执行几次、但偶尔耗时较长的后台报表查询,不一定值得牺牲在线交易写入性能去建立复杂索引。
相反,一个每秒执行数百次、每次扫描几万行只返回几十行的查询,即使平均耗时只有几十毫秒,也可能是更值得优先处理的对象。索引优先级应该由“累计资源消耗”决定,而不是只看单次耗时。
缓存最适合解决的是重复读取和热点读取,不适合直接掩盖所有数据库问题。如果接口每次查询条件都不同,缓存命中率天然很低;如果数据更新频繁,缓存失效和一致性处理的成本可能超过查询本身;如果热点 Key 在同一时间集中失效,还可能产生缓存击穿,瞬间把压力回源到数据库。
在数据分析平台或经营看板中,缓存通常适合放在“已确认的热点聚合结果”上,例如当天销售汇总、固定维度的区域指标和相对稳定的字典数据。对于实时库存、订单状态或强一致的资金数据,是否缓存必须先由产品明确一致性边界,而不是由研发单方面决定。
读写分离会带来复制延迟、路由、故障切换和读写一致性问题;分库分表会带来分片键、跨分片查询、数据迁移和运维复杂度。它们解决的是特定规模和特定访问模式下的扩展问题,不是数据库性能优化的起点。
如果一个系统的主要问题是深分页、重复查询、批处理和在线事务争抢,那么分库分表很可能只是把问题分散到更多节点,并没有减少无效计算。只有当单库已经完成基础治理,且资源、数据量或访问隔离出现明确瓶颈时,才值得进入架构扩展阶段。
平均值非常容易掩盖长尾。假设 99% 的请求耗时 100 毫秒,1% 的请求耗时 20 秒,平均值可能仍然不高,但这 1% 可能恰好是核心客户、关键报表或高峰期请求。
我更关注 P95 和 P99 的变化,同时观察错误率、超时率、锁等待和连接池排队。一次优化如果平均响应时间下降 20%,但 P99 从 1 秒升到 8 秒,通常不能被视为成功。
一次接口请求通常会经历网关排队、应用线程处理、获取数据库连接、执行 SQL、读取结果、序列化和网络返回。产品技术团队需要通过链路追踪或埋点,判断总耗时究竟集中在哪一段。
如果数据库执行耗时只有 100 毫秒,但连接池等待达到 800 毫秒,优化 SQL 可能不会改善用户体验;如果数据库执行 2 秒,其中 1.8 秒用于锁等待,那么单纯增加索引同样不一定有效。
| 耗时位置 | 应重点检查 | 优先动作 |
|---|---|---|
| 获取连接 | 连接池上限、连接泄漏、事务占用时间 | 修复释放问题,调整池大小并压测 |
| SQL 执行 | 执行计划、扫描量、排序、关联、返回数据量 | 重写 SQL、优化索引、拆分查询 |
| 锁等待 | 长事务、热点行、批量更新、死锁 | 缩短事务、拆批、调整并发控制 |
| 结果返回 | 结果集大小、序列化、网络和前端渲染 | 限制字段和行数,采用分页或预聚合 |
我不建议团队只从慢查询日志中挑最长的一条 SQL。更有效的方式是把 SQL 按“调用次数、平均耗时、总耗时、扫描行数、返回行数、错误次数”进行排序。
例如,SQL A 单次耗时 1.5 秒,每分钟执行 2 次;SQL B 单次耗时 80 毫秒,每秒执行 200 次。SQL A 的单次体验更差,但 SQL B 每分钟消耗的数据库执行时间约为 960 秒,可能才是整体资源消耗的主要来源。
累计执行时间
= 单次平均耗时 × 执行次数
优先级参考
= 累计执行时间 × 业务重要性 × 高峰影响系数
这个公式不是数据库产品的标准算法,而是我在团队排期时使用的简化排序方法。它的价值不在于计算出绝对正确的分数,而在于让研发、产品和运维围绕同一组事实讨论优先级。

“查询条件里有索引字段”并不等于索引一定生效。数据选择性、联合索引顺序、隐式类型转换、函数计算、模糊匹配方式和统计信息,都可能改变优化器的选择。
检查执行计划时,我会重点关注访问类型、估算行数、实际扫描行数、过滤比例、排序方式和关联顺序。尤其要比较估算行数和实际行数,如果两者差距很大,可能需要更新统计信息、检查数据倾斜或重新评估索引设计。
对于分页查询,前几页很快而后几页越来越慢,是非常典型的深分页问题。数据库可能需要先扫描并丢弃大量前置记录,再返回目标页。此时可以考虑基于有序唯一键的游标式分页,但前提是产品能够接受“上一页/下一页”而不是任意跳页。
-- 偏移分页:数据量大时可能扫描并丢弃大量记录 SELECT id, title, created_at FROM orders WHERE customer_id = 1001 ORDER BY id DESC LIMIT 100000, 50; -- 游标式分页:以上一页最后一条记录作为边界 SELECT id, title, created_at FROM orders WHERE customer_id = 1001 AND id < 987654 ORDER BY id DESC LIMIT 50;
示例只用于解释访问模式,具体语法和执行效果需要根据数据库类型、索引结构和业务排序规则验证。游标式分页并非所有页面都适用,后台管理系统如果要求跳转到任意页,就需要在产品交互和查询成本之间做取舍。
数据库优化不能只由数据库指标决定。一个每秒访问量不高但关系到支付、库存或核心经营决策的接口,优先级可能高于一个访问量更大的非核心查询。
我建议产品团队为接口增加三个标签:业务重要性、可接受延迟、可接受一致性。研发据此决定是否可以缓存、异步化、降级或延迟计算;测试据此设计压力模型;运维据此配置告警和发布观察窗口。
| 业务类型 | 延迟要求 | 一致性要求 | 更适合的优化方式 |
|---|---|---|---|
| 交易写入 | 较高 | 强一致或明确事务边界 | SQL、事务、锁、队列削峰 |
| 商品或字典读取 | 高 | 通常可接受短暂延迟 | 缓存、只读副本、覆盖索引 |
| 经营看板 | 中等 | 取决于刷新频率 | 预聚合、结果缓存、查询隔离 |
| 历史报表 | 较低 | 通常允许延迟更新 | 异步计算、归档、分析库隔离 |
很多系统的第一批收益并不来自复杂的数据库调优,而是来自减少不必要的工作。常见问题包括重复查询同一条数据、查询后在应用层过滤大量记录、使用不必要的全字段查询、一次接口加载过多明细,以及在循环中逐条访问数据库。
如果一个看板页面有 12 个图表,每个图表都独立查询同一张事实表,数据库可能重复扫描相同数据。此时,团队应先判断哪些指标可以合并计算,哪些数据可以按时间、组织或商品维度预聚合,哪些图表可以延迟加载。
在数据分析场景中,“页面打开速度”不一定要依赖一次性完成所有查询。产品可以将核心指标优先展示,明细表和非关键图表异步加载。这样既降低瞬时数据库并发,也让用户更早获得有价值的信息。
索引设计至少要回答三个问题:它服务于哪一类查询?能过滤掉多少数据?新增索引会给写入和存储带来什么代价?如果这三个问题答不上来,索引变更就不应直接在线执行。
联合索引的顺序也不能简单套用“等值条件在前、范围条件在后”的口诀。实际还要结合查询组合、字段选择性、排序要求和覆盖需求。一个适合列表查询的联合索引,可能并不适合聚合查询;一个对读取有利的覆盖索引,也可能显著增加写入成本。
我建议建立索引变更记录,至少保留以下信息:
数据表持续膨胀后,索引维护、备份恢复、范围扫描和数据清理都会变得更昂贵。特别是订单、日志、操作记录和同步明细表,在线业务只需要最近一段时间的数据,却让历史数据长期留在同一张热表中。
大表治理可以从数据保留策略开始。产品需要明确哪些数据必须在线查询,哪些数据可以归档,哪些数据只需要按月或按季度查询。研发再根据访问模式选择分区、归档表、冷热分离或独立分析存储。
这里的关键不是“表越小越好”,而是让在线请求不要为历史数据承担不必要的扫描和索引维护成本。归档前必须完成查询改造、数据校验和恢复演练,否则看似降低了在线压力,却可能让历史查询和故障恢复变得不可控。

事务不是越大越安全。事务范围过大,会让连接被长时间占用、锁持有时间变长、回滚成本增加,并且更容易与其他请求形成环路等待。
一个典型错误是:事务开启后先查询数据库,再调用外部服务,等待外部服务返回后继续更新数据库。外部服务一旦延迟,数据库连接和锁就会被同步占用。更稳妥的方式是尽可能把远程调用移出事务,或者使用明确的状态机、消息队列和补偿机制表达业务过程。
对于批量更新,也不要默认“一次事务处理全部数据”。可以根据锁竞争、日志量、回滚成本和业务一致性要求拆成合理批次,并在批处理期间监控在线请求的 P99 和锁等待时间。
很多团队只在单个应用实例上看连接池配置,却忽略了实例扩容会把连接数成倍放大。假设当前有 10 个实例,每个实例最大连接数为 30,总上限是 300;发布后扩容到 30 个实例,如果配置不变,总上限就变成 900。
因此,连接池配置应与发布策略、自动扩缩容策略和数据库容量共同管理。每次应用实例数变化,都应重新检查数据库最大连接数、活跃连接数、空闲连接数和管理连接预留。
连接池监控至少应包括:
锁等待的排查不能只看等待线程。真正需要找到的是持锁事务、事务开始时间、最后一次 SQL、锁定对象和业务请求来源。
如果一个凌晨批处理事务持有锁数分钟,白天在线请求可能会随机出现超时。此时增加数据库 CPU 或扩大连接池都无法解决问题,甚至会让更多请求排队。解决方案可能是缩小批处理范围、拆分事务、调整执行窗口或将批处理迁移到隔离节点。
死锁也不应简单理解为“数据库不稳定”。在并发写入场景下,死锁可能是多个事务以不同顺序更新资源造成的。研发需要统一资源访问顺序,并在业务允许的情况下对特定错误进行有限次数重试。重试必须有退避、幂等和上限,不能无条件重复提交。
库存扣减、余额更新、计数器递增和热门内容统计,都可能形成热点行。多个请求同时更新同一行时,数据库即使拥有足够 CPU,也会因为串行化锁竞争而无法线性扩展。
热点写入的处理方式取决于业务语义。库存扣减通常需要严格校验和幂等控制;计数器可以考虑分片计数后异步汇总;日志和行为记录可以先进入消息队列;高峰活动则可以通过限流、排队和预扣减减少瞬时写入压力。
| 热点类型 | 主要矛盾 | 可选方案 | 主要代价 |
|---|---|---|---|
| 库存扣减 | 同一商品或库存行被频繁更新 | 分段库存、队列串行化、乐观锁 | 一致性和补偿逻辑更复杂 |
| 访问计数 | 单行递增造成锁竞争 | 分片计数、异步聚合 | 实时数字存在延迟 |
| 批量状态更新 | 大事务长期占用锁 | 拆批、限速、错峰执行 | 处理周期变长 |
| 同步写入 | 在线请求与同步任务争抢资源 | 队列削峰、任务隔离、独立库 | 数据到达存在延迟 |

缓存的第一原则是确认数据是否具有稳定的访问热点。如果同一份数据在短时间内被大量重复读取,缓存可以显著减少数据库重复执行;如果请求参数高度离散,缓存命中率低,维护缓存反而增加系统复杂度。
以经营看板为例,可以将“当前月份按区域汇总的销售额”作为缓存对象,但不一定适合缓存每个用户、每个筛选条件下的全部明细结果。后者的 Key 数量可能迅速膨胀,缓存空间、失效策略和回源压力都需要重新评估。
缓存方案应在设计阶段明确以下边界:
读写分离的技术动作通常不难,困难在于业务能否接受复制延迟。用户刚刚提交订单,随后刷新页面却暂时看不到订单;用户刚修改权限,下一次查询仍然读取旧数据。这些问题不是数据库连接配置能够自动解决的。
可采用的策略包括:写入后短时间强制读主、关键请求携带读主标记、根据复制位点判断副本是否追上、对非关键页面接受最终一致性。具体选择取决于业务重要性,而不是简单地把所有读请求都发送到副本。
如果只读副本数量增加,却没有按照查询类型、租户、业务模块或负载情况进行路由,最终可能出现主库压力下降但某一个副本过载的情况。读写分离还必须配套复制延迟监控、节点健康检查和故障切换演练。
数据同步、报表计算、通知发送、日志落库和统计汇总等任务,如果不要求在用户请求内立即完成,可以通过消息队列或任务调度异步化。这样可以把突发流量转换为可控制的处理队列,避免在线请求与批量任务直接竞争数据库。
异步化也会引入新的工程问题:消息积压怎么办,重复消费如何处理,任务失败如何重试,数据最终一致性如何向用户解释,消费速度是否会把数据库重新压垮。比较稳妥的做法是为消费者设置并发上限、批量大小和退避机制,并把队列深度纳入容量监控。

在线交易查询、经营看板、历史报表和同步任务使用同一数据库时,任何一个重型任务都可能影响其他业务。此时可以先做逻辑隔离:限制报表查询范围、设置独立连接池、控制并发、错峰执行,必要时再采用只读副本或独立分析存储。
对于九数云这类数据分析平台的使用场景,产品团队可以重点评估“实时查询”和“分析计算”的边界。并不是所有指标都必须实时从明细数据计算,很多经营指标可以按小时、天或业务事件进行预聚合。只要产品在页面上明确数据更新时间,系统就能用更低的数据库成本换取更稳定的访问体验。
分库分表适合解决单库数据规模过大、单表持续膨胀、写入压力集中或单节点资源无法继续扩展的问题。它不适合用来解决一个还没有完成 SQL、事务和任务隔离治理的系统。
进入评估前,我会要求团队提供至少四类证据:第一,核心 SQL 已经完成执行计划和索引治理;第二,连接池和事务问题已经排除;第三,缓存、读写分离或异步化无法满足目标容量,或者存在明确的强一致限制;第四,单库资源在峰值和容量预测下确实接近上限。
如果团队无法说明“当前瓶颈是什么、拆分后哪个资源会下降、跨库查询怎么处理、如何迁移和回滚”,就不应因为行业流行而启动分库分表项目。
分片键通常应尽量稳定、分布均匀,并且出现在核心查询条件中。按照租户、组织、用户或业务区域拆分,可能降低单分片数据量;但如果核心查询经常跨租户、跨区域或按时间汇总,就会产生大量跨分片请求。
分片键选择不能只看当前查询,还要看未来产品功能。今天所有查询都带租户条件,明天产品增加全平台排行,原有分片方案就可能需要跨所有分片聚合。架构评审时,产品经理必须参与,因为跨分片查询的代价最终会表现为功能限制、报表延迟或额外的数据汇总链路。
单库时代,研发通常可以直接执行 SQL、查看事务和恢复数据。拆分之后,同一个业务请求可能访问多个节点,问题定位需要关联路由、分片、节点状态和跨库调用。数据迁移、扩容再平衡、备份恢复和故障切换也会变得更复杂。
| 方案 | 主要收益 | 新增复杂度 | 适合的前置条件 |
|---|---|---|---|
| SQL 与索引治理 | 降低单次查询成本 | 需要持续评审和回归 | 几乎所有系统都应先做 |
| 缓存 | 减少重复读取 | 一致性、失效和热点风险 | 读多写少、热点明显 |
| 读写分离 | 分散读取压力 | 复制延迟和故障切换 | 读请求比例较高 |
| 异步化 | 削峰、解耦、降低在线压力 | 消息积压和最终一致性 | 任务允许延迟完成 |
| 分库分表 | 水平扩展数据和访问压力 | 跨分片查询、迁移和运维 | 单库存在明确容量或规模瓶颈 |

如果确实需要分库分表,建议从非核心、可重建或可回放的数据开始演练。先验证路由规则、数据校验、双写一致性、历史数据补偿、读流量切换和回滚流程,再逐步迁移核心链路。
迁移项目必须预先定义停止条件。例如,校验差异超过阈值、复制延迟持续超过业务容忍度、核心接口 P99 恶化、错误率升高或回滚时间超过窗口,都应触发暂停,而不是继续推进。
技术负责人需要特别警惕“迁移完成”这个模糊词。数据写入新库、查询切换、旧库下线、备份确认和故障演练是不同阶段,不能因为数据已经复制完成,就认为项目已经具备生产安全性。
产品需求文档中如果只有“支持高并发”,研发几乎无法据此设计数据库容量。更有用的需求描述应包括日常请求量、峰值请求量、峰值持续时间、突发倍率、核心接口、可接受延迟和错误处理方式。
对于看板和报表,还要说明数据更新时间。是要求秒级实时、分钟级刷新、小时级更新,还是允许每天汇总一次?这个选择会直接决定系统能否使用预聚合、异步计算和独立分析存储。
对于交易类功能,产品还要明确重复提交、超时重试和最终一致性的处理方式。如果这些边界没有定义,研发往往只能把所有逻辑放进同步事务,最终造成数据库连接和锁竞争。
研发团队应将 SQL、索引、事务和数据量纳入接口设计,而不是等到上线前再进行性能补救。每个核心接口都应该能回答:一次请求访问几次数据库、每次返回多少行、是否包含事务、是否可能触发批量操作、是否允许缓存。
建议为核心接口建立访问预算,例如单次请求最多访问几次数据库、最大返回数据量、目标 P95 和 P99、允许的连接持有时间。预算不是绝对限制,但能让代码评审从“功能能不能跑”升级到“访问成本是否可控”。
只用单一接口压测,通常无法代表真实系统。线上流量往往同时包含读、写、列表、详情、批处理、登录、权限校验和后台任务。压测脚本如果只重复一个轻量查询,得到的吞吐量会明显高估。
压测至少应覆盖四种负载:
压测前还需要准备可重复的数据集。数据量太小会让索引和缓存效果失真,数据分布过于平均也无法暴露热点问题。测试数据应尽量模拟真实的时间分布、组织分布、热门对象和长尾对象。
数据库优化通常会改变执行计划、缓存命中、锁竞争或数据路由,开发环境的成功不等于生产环境的成功。灰度期间应同时观察业务指标和数据库指标,并预先设定回滚阈值。
例如,某索引上线后核心接口 P95 降低,但写入延迟增加 40%,这可能对交易系统不可接受;某缓存上线后数据库 CPU 降低,但数据更新时间异常,这可能对经营决策产生误导。优化必须同时满足技术和业务验收。

只有“数据库 CPU 高”“接口超时”这类告警,无法直接指导处理。更有用的监控应该把业务请求、应用连接池和数据库执行关联起来。
告警还要绑定责任人和处理手册。否则告警数量增加,只会让团队产生告警疲劳。对于核心指标,应记录最近一次变更、当前版本、相关 SQL 和回滚方法,减少故障发生后的临时排查时间。
先确认 CPU 消耗来自 SQL 执行、排序聚合、锁竞争还是连接调度。找出累计执行时间最高的 SQL,查看扫描量和执行计划,再判断是重写、索引、预聚合、缓存还是任务隔离。
如果 CPU 高但 P99 和错误率仍稳定,可以先做容量趋势预测;如果 CPU 高并伴随长尾恶化,应立即限制非核心任务、降低重型查询并发,避免在资源耗尽后被动故障。
优先检查连接池等待、锁等待、磁盘 I/O、网络延迟和应用线程池。CPU 不高并不代表系统有充足处理能力,数据库可能正在等待磁盘、锁或外部资源。
如果锁等待占比高,应定位持锁事务;如果连接池等待高,应检查连接泄漏、事务范围和实例总连接数;如果磁盘 I/O 高,应减少扫描和临时数据,评估内存、存储性能和数据归档。
可以优先评估缓存、只读副本和预聚合。缓存适合明确热点的固定结果,读写分离适合读请求比例高且能够接受复制延迟的系统,预聚合适合计算规则稳定、重复查询明显的分析场景。
不要三种方案同时上线。一次只改变一个主要变量,才能判断性能收益来自哪里,也便于出现一致性或故障问题时回滚。
重点放在事务范围、批次大小、热点行、幂等、队列削峰和限流上。读写分离对主库写入压力帮助有限,缓存也不能直接解决高并发写冲突。
对于计数、日志和统计类写入,可以采用异步汇总;对于库存、余额和状态变更,必须优先保证业务正确性,再讨论吞吐量。任何“先写缓存、后异步落库”的方案,都需要明确丢失、重复和补偿处理。
优先做查询隔离和数据链路隔离。可以限制报表查询时间范围,使用独立连接池和只读副本,将重型计算迁移到分析存储,或者将指标改为定时预计算。
如果产品要求所有报表都实时、所有明细都可跨维度自由查询,那么数据库承受的压力会显著增加。此时需要产品参与取舍:是牺牲实时性换稳定性,还是投入独立分析架构换查询自由度。
不要因为未来可能增长就立即分库分表。先建立容量趋势,包括数据增长速度、索引增长、备份窗口、恢复时间和单表访问变化。提前做好归档、分区和访问规范,通常比过早引入复杂分片更稳妥。
但如果迁移窗口、备份恢复和单表维护已经开始超过业务容忍度,就应提前进行分片设计和迁移演练。架构改造的最佳时机通常不是系统已经故障之后,而是容量趋势已经清楚、但仍有足够回滚空间的时候。

第一类是业务流量,包括 QPS、TPS、读写比例和峰值持续时间。第二类是延迟,包括平均值、P50、P95 和 P99。第三类是稳定性,包括错误率、超时率、重试次数和熔断次数。
第四类是数据库资源,包括 CPU、内存、磁盘 I/O、活跃连接和临时空间。第五类是数据库内部行为,包括慢查询、锁等待、死锁、长事务、缓存命中和复制延迟。
只记录“优化前 QPS”和“优化后 QPS”是不够的。因为优化后可能通过增加机器、降低一致性或减少返回数据量获得吞吐量增长,团队必须同时知道为此付出了什么成本。
优化前后的对比必须保持数据量、请求比例、并发模型、缓存状态和压测时长一致。一次使用热缓存、一次使用冷缓存,或者一次只测轻量接口、一次混合重型接口,都会导致结论失真。
建议至少进行三轮测试:冷启动测试、稳定负载测试和突发流量测试。冷启动用于观察缓存和连接建立成本,稳定负载用于确认持续容量,突发测试用于观察队列、限流和系统恢复能力。
每项优化都应同时设定收益指标和代价指标。例如,增加索引的收益指标是查询 P99 和扫描行数下降,代价指标是写入延迟、存储空间和索引维护时间;引入缓存的收益指标是数据库读取量下降,代价指标是一致性延迟、缓存空间和回源峰值。
| 优化动作 | 收益指标 | 代价指标 | 验收关注点 |
|---|---|---|---|
| SQL 重写 | 扫描行数、P95、P99 | 代码复杂度、兼容性 | 不同数据分布下执行计划是否稳定 |
| 增加索引 | 查询耗时、累计执行时间 | 写入延迟、存储空间 | 是否影响高频写入和备份窗口 |
| 缓存 | 命中率、数据库读取量 | 一致性延迟、回源峰值 | 失效、击穿和缓存不可用时是否可控 |
| 异步化 | 在线接口延迟、数据库峰值压力 | 消息积压、处理延迟 | 幂等、重试、补偿和顺序是否完善 |
| 分库分表 | 单节点资源、单表规模 | 迁移成本、跨库复杂度 | 路由、扩容、回滚和故障恢复 |

“系统要快”无法执行,“核心接口在 500 请求/秒下 P99 不超过 800 毫秒,错误率低于 0.1%,数据库 CPU 不连续超过 75%”就可以被测试和运维验证。
性能预算还可以分解到接口、SQL 和数据访问层。例如一个接口总预算为 800 毫秒,网关和应用处理占 150 毫秒,数据库可用预算就只有 650 毫秒。如果接口内部串行执行 8 次查询,那么每次查询的平均预算并不是简单的 81 毫秒,还要考虑并行、连接获取和结果处理的额外成本。
SQL、索引、分页、事务治理和数据归档,通常属于低成本、低风险方案,适合大多数团队优先实施。缓存、读写分离和异步化,属于中等复杂度方案,能够扩大容量,但会引入一致性、运维和故障处理问题。
分库分表和独立分析架构,属于高成本、高扩展方案。它们适合数据规模和业务复杂度已经达到一定阶段的团队,但需要持续投入监控、迁移、容量规划和故障演练。
所有数据都实时计算,能够提供最新结果,但数据库成本和高峰风险更高;采用预聚合或异步计算,可以显著降低在线压力,但页面需要显示数据更新时间,并且产品要接受短暂延迟。
对于经营看板,用户往往更关心趋势、排名和异常,而不是每一秒的数据变化。产品可以根据指标类型划分刷新频率:核心经营指标按分钟更新,历史趋势按小时更新,明细数据按需查询。这样的设计通常比强行追求全量实时更符合成本和体验的平衡。
强一致事务可以降低业务歧义,但会增加锁、连接和等待成本;最终一致性可以提升吞吐和可用性,但需要状态展示、补偿和对账机制。
这不是数据库团队单独能够决定的技术问题。产品需要明确用户是否能接受“处理中”“数据将在几分钟后更新”等状态,财务、库存和权限等关键场景则需要更加严格的一致性约束。
缓存、消息队列、只读副本和分片节点越多,系统的可扩展性可能越强,但故障链路也越长。团队必须评估是否拥有相应的监控、排障、备份、恢复和演练能力。
如果一个团队只有少量研发和运维人员,且业务规模尚未达到明显瓶颈,那么先把 SQL、索引、事务、归档、慢查询和容量监控做好,通常比引入多个中间件更实际。技术方案的先进程度,不能脱离团队长期维护能力。

第一周的目标不是让系统马上变快,而是建立可重复的性能基线。团队应选出 5 至 10 个核心接口,记录高峰流量、P95、P99、错误率、数据库资源、连接池等待和主要 SQL。
同时梳理任务清单,标记哪些是在线请求、哪些是批处理、哪些是报表、哪些是同步任务。很多数据库问题不是某条 SQL 单独造成的,而是多个任务在同一时间段叠加。
这一阶段优先处理累计执行时间最高的 SQL、重复查询、深分页、无效字段、长事务和连接释放问题。每一次修改都要保留执行计划和指标对比,不要同时修改太多变量。
如果一个接口同时存在 SQL 慢和连接池等待,先确认两者的因果关系。单条 SQL 变快后,连接持有时间下降,连接池等待可能自然改善;如果连接泄漏存在,则必须先修复连接生命周期,否则继续优化 SQL 只是在降低症状。
基础问题处理后,再根据观察结果决定是否引入缓存、预聚合、只读副本或异步队列。每项方案都应明确适用接口、数据更新策略、故障降级方式和回滚路径。
对于分析场景,可以先从一个高频看板或一个固定指标集开始试点,而不是一次性缓存所有查询。对于批处理,可以先限制并发和拆分事务,观察在线请求是否恢复,再决定是否迁移到独立分析链路。
当单库 CPU、存储、连接、备份恢复或数据规模出现持续性瓶颈,并且基础治理无法满足业务目标时,再启动分库分表、独立分析存储或更大规模的数据架构改造。
架构项目必须包含容量模型、迁移方案、数据校验、灰度计划、回滚策略、监控改造和人员排班。没有这些配套内容的分库分表,只是一次高风险的数据迁移。

数据库性能不是一次性项目。新增字段、索引、筛选条件、报表维度和批处理任务,都可能改变执行计划和资源消耗。核心接口应在版本发布前进行最小化性能回归,至少验证高频 SQL、关键写入和高峰并发。
回归测试不必每次都做大型全链路压测,但要保证关键指标有历史对照。如果某个版本让扫描行数、连接持有时间或 P99 明显上升,团队应在上线前找到原因,而不是等用户反馈页面变慢。
第一,选出最重要的五个接口,补齐 QPS、P95、P99、错误率和数据库耗时。没有这组数据,团队无法判断问题是否真实改善。
第二,按“执行次数 × 平均耗时”排序 SQL,而不是只挑最慢的一条。累计资源消耗通常比单次耗时更能决定数据库整体压力。
第三,检查应用实例总数、连接池上限和数据库最大连接数。很多所谓的数据库并发问题,根源是扩容后连接数被成倍放大,或者事务长期占用连接。
我的最终判断是:数据库并发优化的核心,不是寻找一个万能组件,也不是把所有参数调到最大,而是持续减少每个请求的无效工作、缩短资源占用时间、隔离不同类型的访问,并用可复现的指标验证每一次变化。
对于产品技术团队,最稳妥的路径通常是:先测量,再治理 SQL 和索引;再处理连接、事务、锁和热点;随后根据访问模式引入缓存、预聚合、读写分离或异步化;最后在单库出现明确容量瓶颈时,评估分库分表和独立分析架构。
下一步不妨把今天的数据库监控、接口链路和慢查询记录放在同一张表里,挑出一个高频核心接口做小范围优化。只改一个变量,保留优化前后数据,经过压测、灰度和高峰观察后再扩大范围。稳步提升并发能力的关键,不是一次完成大改造,而是让每次改动都能被解释、被验证、被回滚。
我们团队最近发现,流量上升后接口 P99 延迟从 180ms 增加到 1.6s,大家第一反应是分库分表。但我担心改造周期长、跨库查询复杂,想知道到底应该先排查哪些问题,什么情况下才值得做分库分表?
不建议一开始就分库分表,是因为很多所谓的“并发瓶颈”其实来自低效 SQL、连接池排队、长事务或热点数据,而不是单库容量已经无法扩展。过早做分库分表,往往只是把一个可定位的问题,变成跨库查询、数据迁移、分布式事务和扩容再平衡等一组更难排查的问题。
在一次典型的订单查询压测复盘中,团队将一个接口从 800 QPS 提升到 1500 QPS 后,数据库 CPU 长时间超过 85%,于是准备拆分订单表。进一步查看执行计划后发现,查询虽然有索引,但因为过滤条件发生隐式类型转换,实际扫描行数从约 2000 行扩大到 120 万行。
修正字段类型并重写查询后,单条 SQL 耗时从 420ms 降到 18ms,数据库 CPU 降至约 52%,原本的分库分表计划也被取消。
排查阶段优先确认的问题典型处理方式 第一阶段SQL 是否高频、慢、扫描量大执行计划、慢查询、索引和分页优化 第二阶段连接池或事务是否造成排队调整连接池、缩短事务、处理锁等待 第三阶段读写压力是否明显不均缓存、读写分离、异步削峰 第四阶段单库资源或数据规模是否已达上限评估分库分表和水平扩展 只有当 SQL 和索引治理完成后,单库仍在目标峰值下持续出现 CPU、I/O、连接数或存储容量瓶颈,并且业务能够确定稳定的分片键,才适合进入分库分表评估。
我的判断标准不是“数据量大不大”,而是“基础优化完成后,单库是否仍无法在可接受成本内满足目标指标”。
我们的应用实例数量增加后,经常出现获取数据库连接超时。开发同学建议把每个实例的最大连接数从 50 调到 200,但我担心数据库会被大量连接拖垮,应该怎样计算连接池大小,而不是凭经验调参数?
连接池调大不等于并发能力提高,很多时候只是把“应用侧排队”转移成“数据库侧排队”。连接数过多会增加线程调度、内存占用和锁竞争;如果瓶颈是慢 SQL,更多连接只会让更多请求同时执行慢 SQL,最终表现为数据库 CPU 飙升、上下文切换增加和整体延迟恶化。
实际排查连接池问题时,我会先把应用实例数、单实例最大连接数、数据库最大连接数和请求耗时放在同一张表里,而不是只看某个连接池参数。例如 12 个应用实例,每个实例配置 200 个连接,理论最大连接数就是 2400 个,还没有扣除管理连接、后台任务和其他服务的占用。
如果数据库实际稳定承载能力只有 600 个活跃连接,这种配置很容易造成连接争抢。
配置方式理论连接数可能结果 12 个实例 × 50600需要确认是否覆盖峰值,并观察连接池等待 12 个实例 × 1001200可能缓解应用排队,但数据库压力明显上升 12 个实例 × 2002400高概率出现数据库资源争抢和锁竞争 更稳妥的做法是先记录连接池活跃连接数、空闲连接数、获取连接等待时间、数据库活跃会话数和 SQL 平均耗时。
若连接池等待时间高,但数据库 CPU、I/O 和活跃会话都不高,才可能是连接池偏小;如果数据库已经高负载,则应优先优化慢 SQL、缩短事务或限制并发,而不是继续加连接。建议采用小步调整,例如每次增加 10% 至 20%,在固定压测流量下观察 P95、P99、错误率和数据库资源。
连接池的验收目标应该是“峰值流量下等待时间可接受且数据库资源稳定”,而不是单纯追求更大的最大连接数。
我们准备给商品详情和活动库存接口做性能优化,团队内部有三种意见:有人建议上缓存,有人建议增加只读节点,还有人建议用消息队列异步处理。我不想为了追求高并发一次性引入太多组件,应该根据什么条件做选择?
这三种方案解决的不是同一个问题,不能按照“哪个更先进”来选择。缓存主要减少重复读,读写分离主要分散读请求,异步化主要处理可延迟任务或突发流量;如果没有先判断请求类型和一致性要求,方案很容易选错。以商品详情接口为例,如果商品名称、图片和规格信息读多写少,且允许几秒级缓存延迟,缓存通常是第一选择。
若接口每秒有 2000 次读取,其中 80% 集中在少量热门商品上,缓存命中率达到 90%,理论上数据库只需承接约 200 QPS 的对应查询。此时直接增加只读节点,可能只是把原本可以避免的查询复制到更多数据库节点。
业务现象优先方案必须提前解决的问题 热点数据重复读取,更新不频繁缓存失效、击穿、穿透和更新一致性 读请求占比高,允许短暂延迟读写分离主从延迟、写后读一致性和故障切换 通知、统计、日志等可延迟处理异步化消息积压、重复消费和失败补偿 核心写请求集中争抢同一数据限流或队列化排队策略、超时和业务降级 活动库存场景则不能简单套用缓存。
库存扣减属于高并发写入,缓存只能承担展示或预热作用,不能在没有一致性和扣减约束设计的情况下直接替代数据库。此类场景更应该先确认扣减原子性、热点行锁竞争、超卖控制和失败重试,再评估限流、队列化或分段库存等方案。我的实施顺序通常是:先识别读写比例和一致性边界,再对热点读请求做缓存;
确认只读节点能够承受复制延迟后再做读写分离;只有当任务确实允许延迟处理时,才引入异步化。组件越多,故障模式越多,性能优化的收益必须能够覆盖新增的运维和排障成本。
我们已经增加了索引、改了几条 SQL,开发环境里的响应时间确实下降了,但上线后高峰期仍然偶发超时。我想知道性能优化到底应该怎样压测和验收,为什么平均响应时间不能作为唯一依据?
平均响应时间不能作为唯一依据,因为它会掩盖尾部请求。假设 99 个请求耗时 50ms,只有 1 个请求耗时 10 秒,平均值约为 149.5ms,看起来不算特别糟,但真实用户和上游服务仍可能频繁遇到超时。高并发场景下,P95、P99、超时率和错误率通常比平均值更能反映系统是否稳定。
我会把压测分成基线、峰值、突发和长稳四类,而不是只跑一次逐步加压。基线用于记录优化前后差异,峰值用于验证正常高峰,突发用于模拟活动开始或批量任务触发,长稳则用于观察连接泄漏、缓存失效、慢查询堆积和磁盘增长等问题。
指标优化前示例优化后示例验收意义 核心接口 P991.6s320ms观察最慢 1% 请求是否改善 错误率2.8%0.2%确认性能提升没有以失败请求为代价 数据库 CPU88%61%判断是否仍缺少资源余量 连接池等待最高 740ms低于 30ms确认应用是否仍在排队 慢查询数量每分钟 126 条每分钟 18 条验证 SQL 治理是否有效 压测流量还必须尽量接近真实业务比例。
例如线上读写比例是 9∶1,测试却使用 1∶1 的读写流量,数据库锁竞争和日志写入压力都会失真;如果线上存在批量任务,压测时也应模拟它与在线请求同时运行。
最终验收建议同时设置业务指标和资源指标:核心接口 P99 达到目标值,错误率和超时率在阈值内,数据库 CPU 与 I/O 保留安全余量,连接池没有持续排队,锁等待和复制延迟不影响业务。只有当这些指标在峰值和长稳测试中都达标,才能判断优化是有效的,而不是仅仅在开发环境里“看起来变快了”。


读者评论
文章把数据库并发问题拆成吞吐量、延迟、资源和竞争几类指标,比单看CPU或连接数更实用。尤其强调P99和错误率,符合线上系统排查长尾问题的实际情况。
优化顺序比较稳妥,先定位瓶颈,再处理SQL、索引、事务和连接池,最后考虑缓存或分库分表,能避免一遇到性能问题就过度架构升级。
连接池过大不等于并发能力更强这一点很有参考价值。实际配置还需要结合实例数量、事务耗时和压测结果,不能简单照搬数据库最大连接数。
看板查询与同步任务重叠的案例贴近常见业务场景,但文中数据明确标注为情景模拟,这种证据边界说明比较客观,也提醒了容量评估需要结合真实监控数据。