1 数据库内核要建立什么图景
当数据库查询变慢时,运维人员常先看 CPU、内存和磁盘利用率。这些指标能指出主机正在忙什么,却不能单独解释数据库为什么忙。同样是磁盘写入增加,可能是正常刷新数据页,也可能是日志写入、临时文件溢出或维护任务在竞争设备。本课要建立的图景是:把 SQL 的执行、数据库内部状态和操作系统资源连起来,再用可观察的证据区分原因。
这里的“内核”指数据库管理系统负责存储、查询、事务与恢复的核心部分,并不是操作系统内核。数据库通常作为用户态进程运行;有的实现为每个连接安排独立进程,有的使用线程。它通过文件接口读写数据和日志,通过网络接口接收请求,依靠操作系统安排 CPU 时间、管理内存映射并驱动设备。理解这些边界,才能避免把数据库锁等待误认为操作系统调度问题。
先跟随 1 条查询:客户端经连接发送 SQL,数据库解析语句、生成执行计划,执行器按计划访问记录。需要的数据可能已经在数据库缓冲池中,也可能必须读文件。读取结束后,结果还要编码并通过套接字返回。客户端看到的耗时还可能包含连接池排队、网络往返和读取结果的时间,因此服务端执行耗时与调用方总耗时不必相等。
写入又多出事务责任。数据库需要判断并发访问是否冲突,把修改记入恢复日志,并在约定的持久化条件满足后确认提交。提交成功不表示所有数据页都已经写回数据文件;这种分工正是后面缓冲池与 WAL 协作的起点。数据库还可能在后台刷新页面、生成检查点、清理旧版本,这些工作会消耗资源,却不直接对应当前屏幕上的 SQL。

观察时可以分成 3 个问题:请求在哪个阶段停留,数据库正在等待什么,操作系统是否提供了所需资源。比如查询等待另 1 个事务释放锁时,增加磁盘带宽通常无济于事;若结果已经产生而客户端迟迟不读取,网络发送等待也不等于执行器计算缓慢。关联连接、会话和进程时,要先确认产品采用的进程模型,不能把所有实现都理解为“1 个连接对应 1 个 PID”。
学习的边界也要明确。本课使用 PostgreSQL 的公开 SQL 与视图做可复现实验,因为它们能把计划、事务和等待直接呈现出来。缓冲、日志、并发和恢复的基本问题可以迁移到其他数据库,但视图名称、日志格式和锁策略不能直接照搬。我们只建立解释运行现象的基础模型,不展开存储页布局、优化器算法和操作系统协议栈实现。
2 缓冲池、WAL 与检查点
数据库把文件中的数据组织成页面,并在内存中保留经常访问的页面,这部分空间通常称为缓冲池。查询命中缓冲池时,数据库可以减少对数据文件的读取。未命中时,需要发起文件读取;这次读取又可能被操作系统文件缓存满足,所以数据库报告的“读入块”不一定等于物理设备完成了相同数量的读取。数据库缓存、操作系统缓存和设备缓存属于不同层,统计口径也不同。
页面在内存里被修改后称为脏页。若每次修改都要求立刻随机写回数据文件,提交路径会受大量零散写入影响;若只修改内存,进程退出或主机掉电又会丢失修改。预写日志 WAL 的核心规则是:与数据页修改有关的日志必须先达到相应的持久化位置,随后才能把该脏页作为已持久化数据写出。恢复时,数据库据此重做必要修改。
对采用常见本地同步提交配置的数据库,可以把提交理解为“等待相关提交日志持久化”。具体实现可能把多个事务的日志合并刷新,因此并非每个事务都产生 1 次独立的设备刷新。日志顺序、提交确认和数据页刷新需要配合,但并不要求所有脏页先落盘。日志的持久化还依赖文件系统、设备及其缓存正确兑现刷新语义,进程中的写调用返回不能自动证明断电后仍然存在。
检查点用于记录恢复可以依据的进度,并推进必要的数据页刷新。它帮助控制恢复需要处理的日志范围,也影响后台写入节奏。它不是额外的提交按钮,更不是备份。检查点更密集,可能增加或集中写入压力;检查点间隔更大,则可能留下更多恢复工作。实际恢复范围还受脏页、日志保留和实现策略影响,不能只用墙上经过的时间来推断。

考虑 1 次配置变更后的故障:事务已收到成功,数据文件仍有旧页,进程随后异常退出。只要相应日志已按承诺持久化,启动恢复可以把已提交修改重新应用到数据页。相反,若启用异步提交,成功响应可能早于日志持久化;故障时就可能丢失最近已确认的事务。这不意味着数据库文件必然损坏,而是应用获得的成功承诺发生了变化。
实践中不要用 kill -9 或断电作为生产验证手段。恢复演练应在隔离副本进行,分别记录进程退出、主机故障与存储故障的边界,并验证重新启动后的数据和业务约束。即使日志机制能处理崩溃,它也无法替代误删除恢复、历史版本恢复和独立备份。可恢复性必须说明“从什么故障恢复到什么状态”,否则“有 WAL 所以安全”只是没有边界的口号。
3 锁、MVCC 与执行器
当多个会话同时访问记录时,数据库要保证事务约定。锁用于协调冲突访问,例如 2 个事务都要更新相同记录时,后来的修改可能等待先到的事务结束。锁并不是坏现象;短暂等待常常是正确串行化某段工作的代价。问题在于持锁时间过长、锁范围过大,或者事务之间形成循环等待。死锁检测通常会中止某个事务,应用必须理解错误并按明确规则处理重试。
MVCC,即多版本并发控制,为读取提供另 1 条思路:保存记录的版本信息,让读取根据快照判断哪些版本可见。这样读操作在许多常见场景下可以与写操作并行。它不代表完全没有锁,也不代表读取永远不会等待。表结构变更、显式锁和写写冲突仍然可能造成等待;不同隔离级别又会改变快照的范围和事务可能遇到的结果。
以 PostgreSQL 为例,默认的读已提交隔离级别通常为每条语句取得快照,因此同 1 个事务中的后续语句可能看到别人刚提交的数据。可重复读事务则会维持适用于该事务读取的快照。这里先掌握“可见性由快照规则决定”,不展开隔离异常的完整分类。排查长事务时,必须同时查看隔离级别、语句状态和快照相关信息,不能从事务开始时间单独断言它保留了哪些版本。
执行器负责把计划变成实际访问。例如顺序扫描逐步检查表内记录,索引扫描先定位候选记录,再按需要读取表页;排序、聚合和连接则把输入组合成结果。计划中的每个节点都可能消耗 CPU、缓冲页或临时存储。某个节点返回行数与估计相差很大时,后续节点的代价也可能跟着放大,例如原以为只有少量行,却实际执行了大量重复查找。

事务时间与语句执行时间也不同。应用可以执行很快的更新后,等待外部接口返回,再提交;这段网络等待期间,数据库事务可能仍持有锁。解决方法往往是调整事务边界,避免在持锁期间调用缓慢的外部依赖。盲目调高连接数只会让更多请求排在同 1 把锁后面,还可能增加内存和上下文切换压力。
执行器的工作内存同样不能按“每个服务只有 1 份”来理解。以 PostgreSQL 的 work_mem 为例,它影响排序、哈希等操作的内存预算,1 条复杂查询可能有多个相关节点,并行执行与大量会话还会放大总需求。把该参数乘以连接数只能做粗略风险提示,不能当作精确上限。看到临时文件时,应结合计划节点与并发量讨论内存调整,而不是直接把预算扩大到主机剩余内存。
4 动手:用一次慢查询对照执行计划
在可丢弃的 PostgreSQL 实验数据库中准备 1 张主机观测表。它模拟采集器记录 CPU 样本的场景,字段包括主机标识、采集时间和测量值。下面会创建独立实验表并写入 200000 行;若名称已存在,先换表名,不要直接删除已有对象。不要在生产库运行造数和索引实验。连接示例沿用已经配置好的认证方式,避免把口令写进命令行或 shell 历史。
psql -X -d kernel_lab
CREATE TABLE s57_cpu_sample (
host_id integer NOT NULL,
collected_at timestamptz NOT NULL,
cpu_pct integer NOT NULL
);
INSERT INTO s57_cpu_sample
SELECT g % 1000,
timestamptz '2026-01-01 00:00:00+00'
+ g * interval '1 second',
g % 101
FROM generate_series(1, 200000) AS t(g);
ANALYZE s57_cpu_sample;
EXPLAIN (ANALYZE, BUFFERS)
SELECT collected_at, cpu_pct
FROM s57_cpu_sample
WHERE host_id = 42
ORDER BY collected_at DESC
LIMIT 20;
EXPLAIN 展示计划,附加 ANALYZE 后会真实执行语句,不能把它当成无副作用的计划预览。本例使用读取查询,仍然会消耗资源。先辨认扫描、过滤、排序和返回上限分别位于哪里。cost 是优化器用于比较方案的代价单位,不是毫秒;actual time 才给出本次节点执行的时间信息。嵌套节点时间有包含关系,不能直接把每个节点耗时相加当成总耗时。
接着看估计行数与实际行数,再看 loops。实际行数与节点时间通常按每次循环报告,估计总工作量时要结合循环次数,但仍需避免重复计算父子节点。Rows Removed by Filter 可以提示读进来又丢掉了多少候选记录;它与最终返回 20 行不是相同概念。没有索引时,为找到目标主机,计划可能检查大量不相关样本。
CREATE INDEX s57_cpu_sample_host_time_idx
ON s57_cpu_sample (host_id, collected_at DESC);
ANALYZE s57_cpu_sample;
EXPLAIN (ANALYZE, BUFFERS)
SELECT collected_at, cpu_pct
FROM s57_cpu_sample
WHERE host_id = 42
ORDER BY collected_at DESC
LIMIT 20;
这个索引把主机筛选与时间顺序放在相邻的访问路径中,可能让执行器更早找到需要的记录并停止。是否真的采用索引、是否省去显式排序,都应以实际计划为准。表很小、缓存状态不同或成本配置不同,结果可能变化。实验的重点是解释读取与过滤工作如何改变,而不是保证某个固定的加速倍数。

BUFFERS 中的 shared hit 表示命中数据库共享缓冲区,shared read 表示需要把块读入该缓冲区;后者可能仍命中操作系统缓存。父节点缓冲统计通常包含子节点,不要逐层累加。连续运行会改变缓存热度,所以至少标明首次与后续运行,并记录相同条件下的多次结果。不要为了得到“冷缓存”在共享主机上清空缓存。
同时可在另 1 个终端查看 iostat -xz 1 5、vmstat 1 5 和 ss -tn,这些工具可能需要额外安装或相应权限。它们分别提供设备、主机运行状态和 TCP 连接线索;设备 %util 不能单独代表所有存储都已达到吞吐上限。把采样时间与查询时间对齐,才能讨论因果。若实验机器很快,这条查询未必肉眼可见地“慢”,依然可以对照它完成了多少扫描、过滤和排序工作。
5 吞吐与可恢复取舍
吞吐描述单位时间完成多少工作,延迟描述某次请求需要多久。提高并发可能先增加吞吐,随后因为 CPU、存储或锁竞争让等待队列变长。此时平均值可能变化不大,尾部延迟却明显恶化。数据库调优不能只报告“每秒事务数提高”,还应给出请求类型、并发连接、事务大小、测试时长和高分位延迟,否则不同负载的数字没有可比性。
提交路径尤其容易把吞吐与持久化混在一起。默认同步提交等待必要的本地日志刷新;异步提交可能更快确认成功,但允许故障时丢失最近已确认事务。调整这类设置,必须先问数据是否可重建、上游能否重放、重复写入怎样识别,再确定可接受的丢失窗口。恢复日志仍被正常生成,并不意味着每次成功响应都已经得到相同的持久化保证。
交互使用假设的刷新间隔说明成功响应与日志持久化之间的时间差。它不是实际参数推荐器,也不能把滑块数值当成 PostgreSQL 的精确丢失上界。实际时间取决于后台刷新时机、调度、存储和负载。若要给业务承诺,需要测量指定配置在指定故障模型下的行为,并保留数据核对手段;不能用教学模型直接签署恢复指标。
批量提交是另 1 个取舍。把多个采样点放进 1 个短事务,可能摊薄连接、解析和提交等待的开销;但批次扩大也会延长事务、增加回滚范围,让单批失败重试更昂贵。运维采集器还要考虑网络中断:客户端未收到提交结果时,事务可能已经提交,也可能没有提交。稳定的采集批次标识和幂等写入约束,比简单“超时就重发”更可靠。
缓冲池、工作内存和检查点预算也需要一起考虑。给数据库更多内存,可能减少读取,但主机还要容纳操作系统、连接进程和监控代理。若总内存承诺过高,引发交换或进程被终止,单个查询的局部优化就会伤害整体稳定性。检查点相关写入可能与日志刷新争用设备;看到提交延迟升高,应检查同时间的写入节奏,而不是只归因于网络。

可恢复目标应拆开描述。可接受丢失多少最近数据,与服务需要多久恢复,是不同问题;本地崩溃恢复、从备份恢复和切换到副本,也不是同 1 条恢复路径。把检查点调得更密集可能减少某些恢复工作,却不能保证恢复总时长,因为日志回放速度、文件检查、连接恢复和应用核对都可能成为瓶颈。副本的存在也不能自动替代独立备份。
调整时每次优先改变 1 类因素,并保留回退条件。比如先限定连接池并发,再观察锁等待和尾部延迟是否下降;或者在确认内存余量后,小幅调整相关预算,并检查临时写入与主机内存压力。把参数值、版本、重载或重启要求记在实验记录里。配置文件中的数值不等于当前已生效的数值,应通过数据库提供的查询接口确认。
6 常见坑:只加索引不看选择性
索引的作用是提供额外访问路径,但这条路径也有成本。选择性通常描述谓词能把候选记录缩小到什么程度;交流时最好直接说“预计匹配占比”,避免“选择性高”在不同语境里含义不清。主机标识只匹配少量记录时,索引可能很有价值;若条件覆盖了大部分样本,沿索引访问许多表页可能还不如顺序扫描。
假设观测表新增 uploaded 字段,绝大部分行都已经上传。为查找 uploaded = true 而加普通索引,未必能减少多少读取。相反,少量待上传记录也许更适合针对该访问模式设计路径。这里不展开特殊索引类型;先核对常用谓词、匹配占比、排序和返回列,再决定是否需要索引。字段取值少并不自动说明索引永远无用,关键仍是查询访问了哪部分数据。
组合索引也不能只看“包含了这几个字段”。前面实验按主机筛选,再按采集时间取最近样本,索引列的顺序与这条访问路径相配。若查询改成所有主机最近的样本,原索引未必同样有效。对时间字段套函数、发生隐式类型转换或使用不同排序方式,也可能改变可用路径;具体是否受影响,要查看谓词怎样出现在计划里,不能仅凭 SQL 外观下结论。
统计信息是优化器判断行数的重要依据。持续写入、集中删除或数据分布改变后,旧统计可能无法反映当前情况,执行器便可能收到不合适的计划。ANALYZE 更新统计可以帮助估计,但它不会修复锁等待,也不能保证任意复杂条件都估计准确。若多个字段高度相关,单独看每个字段的分布也可能产生偏差;本课只要求识别估计偏差,不展开高级统计机制。
索引还需要被维护。插入、删除及涉及索引列的更新,会增加额外工作,并占用文件空间、缓存和日志。索引数量增加可能让读取更快,却让采集写入和维护更慢。生产建索引还涉及锁、额外 I/O 与可用磁盘容量;应按所用版本选择合适操作方式,先验证资源影响,不能把实验中的普通 CREATE INDEX 直接当作线上无干扰操作。

遇到慢查询,先确认它是在执行还是等待。如果等待锁,就追查阻塞者和事务边界;如果正在扫描,就比较估计行数、实际行数和丢弃行数;如果排序或哈希写临时文件,再分析输入规模、计划与内存预算。若服务端执行很快但客户端总耗时长,应回到连接池、结果大小和网络链路。这个顺序能避免把每种性能问题都归结成“缺 1 个索引”。
最后要用相同查询与相近负载比较变更前后,既看查询耗时,也看读取块数、写入代价和存储增长。若新索引没有稳定收益,就没有必要因为“已经建好了”而长期保留。删除已有索引同样要核对其他查询、约束依赖和完整观察窗口。索引是围绕工作负载维护的资源,不是字段旁边越多越好的装饰。
7 案例:长事务拖垮清理与空间
某台采集数据库持续接收主机状态,并定期删除过期样本。运维人员发现删除任务正常完成,但表文件没有明显缩小,磁盘空间还在增长,随后查询读取和维护开销也升高。最初的猜测是“删除失效”或“磁盘写不动”。查看会话后,发现报表连接开启了可重复读事务,查询结束后没有提交,长时间保持着旧快照。
在 MVCC 下,删除和更新通常不会立刻把旧版本从文件中物理移走。只要仍可能有事务需要看到这些版本,清理就不能把它们当作无人使用的空间回收。这个案例中,旧快照拖住了清理进度;持续更新又不断产生新版本,于是文件与访问负担增长。并非所有长事务都以同样方式保留旧版本,判断要落到实际快照和清理边界上。
可以先用下面的只读查询观察当前数据库。查看其他会话的完整信息可能需要监控权限;不要为了排查给日常应用授予广泛管理权限。query 字段还可能含业务内容,采集诊断材料时应注意访问范围。
SELECT pid, application_name, state,
now() - xact_start AS xact_age,
backend_xmin, wait_event_type, wait_event,
pg_blocking_pids(pid) AS blockers
FROM pg_stat_activity
WHERE datname = current_database()
ORDER BY xact_start NULLS LAST;
SELECT relname, n_live_tup, n_dead_tup,
last_autovacuum, last_autoanalyze
FROM pg_stat_user_tables
WHERE relname = 's57_cpu_sample';
事务年龄能筛出候选者,backend_xmin 提供快照相关线索,阻塞列表用于识别锁等待关系。这些字段不能互相替代:没有锁阻塞者,不代表没有快照阻碍清理;n_dead_tup 是估计值,也不是操作系统立即可回收的字节数。还应对照删除速度、表大小增长和自动清理日志,避免只凭某个计数就确定原因。
处置先从连接所属的应用入手,确认事务是否仍有必要。优先让应用按业务含义提交或回滚;取消当前语句未必会结束整个事务。若需要终止会话,应由有权限的人员确认回滚代价和调用方重试行为,避免直接杀数据库主进程。事务结束后,还要确认清理确实推进、增长速度恢复,不能把“连接不见了”作为恢复完成的证据。
普通 VACUUM 通常让空间能被表内后续写入复用,并不保证文件立即向文件系统归还同等容量。少数情况下末尾空闲页面可以被截断,但不能依赖这一点估算可释放空间。更强的空间整理可能需要额外空间、锁或较长维护窗口。磁盘接近耗尽时,先控制增长并保留操作余量,不能在没有容量评估时直接启动重写。
预防措施包括明确事务边界、避免报表长时间持有快照、配置适合业务的空闲事务超时,以及持续观察旧事务和清理进度。超时可以减少遗忘连接,但可能中断合法任务,应用仍须处理事务失败。案例的关键不是“清理参数太小”,而是先移除阻止清理推进的条件,再讨论清理资源是否足够。日志保留异常还可能来自其他机制,需要另行核查,不能全部归咎于这次长事务。
8 把内核参数写进调优方案
参数方案应从可观察的问题开始,例如“在既定采样速率下,提交延迟的高分位超过目标,并与日志设备等待同步升高”。它比“把缓存调大”更有用,因为前者能说明要解决什么、需要哪些证据。至少保留数据库版本、主机资源、存储类型、连接池上限、代表性 SQL 与事务大小。缺少负载边界,参数值就无法被别人复现或评审。
接下来写出机制假设。若怀疑读取受缓存容量限制,应有数据库读入块、主机存储读取与查询计划的联合证据;若怀疑日志刷新受阻,应看提交等待、日志活动与设备延迟的时间关系;若主要是锁等待,就应调整事务与并发,而不是增加缓冲池。每个参数要说明它影响哪条路径,并写出可能把压力转移到哪里。
在 PostgreSQL 中,可以通过配置视图确认部分常见参数的当前值和生效方式。不同版本支持的参数及上下文可能变化,应以目标版本为准。查询展示的是状态,不会替你判断新值是否合理。
SELECT name, setting, unit, context, source, pending_restart
FROM pg_settings
WHERE name IN (
'shared_buffers', 'work_mem', 'max_connections',
'synchronous_commit', 'checkpoint_timeout',
'max_wal_size', 'idle_in_transaction_session_timeout'
)
ORDER BY name;
context 帮助判断变更需要在哪个层级执行,pending_restart 提示某些已改配置仍待重启。会话级设置还要检查目标应用的实际连接,不能因为管理终端显示某值就认为整个连接池已经采用。max_wal_size 也不能当成日志目录的绝对空间上限;保留条件、归档问题和其他因素都可能让实际占用超过预期,容量监控必须独立存在。
验证计划要同时覆盖收益与代价。缓存调整后既看读取与尾部延迟,也看主机可用内存和交换活动;工作内存调整后既看临时写入,也看并发峰值下的总内存;检查点调整后既看写入节奏,也看恢复演练。对于提交持久化策略,必须把成功响应的含义写成业务可理解的句子,不能埋在参数表的脚注里。

实施顺序也需要进入方案。先在代表性环境建立基线,再按小范围变更观察稳定窗口,最后决定是否扩大。窗口要覆盖正常维护与采样高峰,否则容易把没有出现的压力误认为已经消除。回退条件应可测量,例如高分位延迟持续越线、内存压力增加或日志空间消耗异常;回退动作则需说明恢复旧值、重连或重启的要求。
交付物不是参数清单,而是一段可核对的因果说明:哪类请求受影响,证据指向哪个机制,改动改变了什么,如何确认目标达成,以及出现什么信号就撤回。涉及重启的配置还要记录服务不可用窗口;涉及业务持久化承诺的配置则需要明确责任人。把这些写清,后续值班人员才能在故障中使用这份方案,而不必猜测上次调优者的意图。
9 从数据库内核走向大规模系统设计(S58)
数据库内核解释了单个实例如何协调计算、缓存、存储与事务,但真实服务还包含采集器、连接池、网络、备份和多个运行节点。单机指标正常,不保证端到端请求顺畅。某次网络抖动可能让应用超时重试,重试又提高连接并发,进而扩大锁等待。若只盯着最终变慢的数据库,就会漏掉最初的流量变化和反馈过程。
在扩展到多节点前,先说清单实例的容量边界:在哪种事务比例、数据规模、结果大小和并发下,延迟开始恶化;瓶颈在 CPU、存储、内存还是冲突。增加实例并不会自动消除所有瓶颈,同 1 组热点记录上的写入冲突也可能继续存在。先用本课的方法测量工作与等待,再讨论将哪些工作移到其他位置。
恢复边界同样会变复杂。本地 WAL 主要服务于该实例的恢复,而跨节点的数据复制还涉及传输进度、接收进度和应用进度。客户端收到成功时,究竟只有本地日志持久化,还是另有节点已经确认,需要由具体配置和协议说明。这里只保留问题,不提前展开复制协议与一致性算法;S58 将把这些承诺放到系统结构中讨论。
网络失败尤其需要区分“确认失败”和“没有得到确认”。请求发出后连接断开,客户端往往无法仅靠超时判断数据库是否已经提交。采集业务可以使用稳定样本标识、唯一约束和可查询的处理状态来减少重复影响,但这些都需要进入接口约定。把超时设得更长或无限重试,无法消除这种不确定性,反而可能延长故障期间的排队。
观测也应跨越边界。给采集批次、应用连接和数据库会话建立可追踪的关联,统一时间口径,记录客户端总耗时与服务端执行耗时。数据库侧保留计划、等待与资源证据,主机侧保留进程和设备状态,网络侧保留连接错误与重连情况。无需采集全部内部细节;能围绕同 1 次事件把证据对齐,比堆积大量无关联指标更有价值。
进入大规模设计时,还会遇到分区、复制、故障切换和容量增长等选择。每种选择都需要重新回答:数据放在哪里,谁能确认成功,失败后怎样核对,维护操作会占用什么资源。缓冲池提醒我们区分命中层级,WAL 提醒我们区分响应与持久化,锁和 MVCC 提醒我们区分并发数量与可并行工作。它们会成为下一课讨论系统边界的基础。
带着这套图景阅读架构图时,不必急着数节点。先沿 1 次采集写入与 1 次报表查询,标出网络等待、事务边界、提交承诺和恢复入口,再问某个节点失效会使哪部分承诺改变。这样从数据库内核走向系统设计,就有了可以测量和验证的连续路径,而不是把单机调参经验直接扩大成未经检验的架构结论。
放进方案里
在主机观测平台方案中,把数据库定位为采样数据的持久化与查询节点,明确采集写入、最近样本查询和历史报表各自的事务边界。基线应记录数据保留期、峰值采样速率、并发连接、典型返回行数与延迟目标,并注明数据库版本和存储条件。客户端总耗时与服务端执行耗时分别采集,保证出现超时时能区分连接排队、网络等待和数据库执行。
存储部分说明缓冲池、日志与数据文件的作用及容量余量。提交策略写明成功响应代表什么,是否允许最近已确认数据在故障后丢失,以及上游如何重放和去重。检查点策略与恢复目标一起评估,不能将缩短间隔直接等同于满足恢复时间。备份恢复和隔离环境中的崩溃恢复应分别演练,并记录数据核对结果。
查询优化以代表性执行计划为依据。对主机与时间条件建立的索引,应说明匹配占比、排序收益及写入代价,保留变更前后的行数估计、实际行数和缓冲访问证据。连接池上限与工作内存联合评估,覆盖采集高峰和维护任务重叠的情形,避免单条查询获益却使整机内存承压。
运行部分安排旧事务、锁等待、清理进度、日志占用、磁盘余量与尾部延迟的监控,并写清告警后的检查顺序。长事务先定位应用和快照影响,再决定结束方式;普通清理后的空间复用与文件缩小分别验收。参数变更记录当前值、目标值、生效范围和重启要求,明确观察窗口、回退阈值与执行责任人,使方案能被值班人员直接使用。
10 小结:下一步大规模系统设计(S58)
数据库性能问题需要同时看执行工作、事务等待和系统资源。缓冲池减少重复读取,WAL 支撑约定范围内的恢复,检查点推进恢复进度;锁协调冲突,MVCC 管理版本可见性,执行器把计划转成实际访问。它们共同解释查询、写入和后台维护为什么会互相影响。
离开本课前,试着口头说明:为什么提交成功时数据页仍可没有写回;为什么顺序扫描有时比索引扫描合理;为什么删除后文件没有马上缩小;为什么客户端超时不能证明事务未提交。回答时至少给出 1 个可查看的接口或指标,并说明它不能证明什么。
下一课 S58 将把这些实例内部的承诺放进更大的系统,讨论容量、复制与故障边界。先说清单实例如何工作,再判断增加节点后哪些保证得到扩展、哪些问题需要新的设计。