PostgreSQL DBA最常用SQL:连接、慢查询、锁与vacuum巡检实战
发布时间:2026/10/2 10:37:25来源:尧图网络
做了这么多年 PostgreSQL DBA我手里攒下的 SQL 片段非常多但真正能让我每次巡检、救火、接手新环境时打开就用的永远就那么几十条。后来我把它们整理成一个文件起了个名字叫dba_daily.sql新同事接手时照着跑一圈基本上就对当前环境的连接、空间、锁、慢查询心里有数了。今天我就把这一套 PostgreSQL DBA 最常用 SQL 拿出来聊聊不追求冷门只求高频、能落地、能解释清楚为什么这么查。这套东西适合正在维护 PostgreSQL 11 到 17 的同行也适合准备从开发转向 DBA 的同学直接对照着抄。1. 连接与会话管理巡检之前先看命根子1.1 连接数还剩多少任何一次巡检我都建议从连接数开始尤其是半夜被叫起来处理“数据库连不上”的工单时第一步永远不是翻应用日志而是先进库看一眼连接有没有打满。最常用的一条SELECT count(*) AS current_conn, (SELECT setting::int FROM pg_settings WHERE name max_connections) AS max_conn, (SELECT setting::int FROM pg_settings WHERE name max_connections) - count(*) AS remaining_conn FROM pg_stat_activity;pg_stat_activity是 PostgreSQL 的会话视图里面每一行代表一个后端进程也就是一个连接。pg_settings里存的max_connections是实例级最大连接数默认一般 100实际生产环境很多会调到 500 甚至更高。两条合在一起能直接看到“还剩多少连接名额”。但只看总量不够我会再按用户、数据库、状态分组看一眼SELECT datname, usename, state, count(*) FROM pg_stat_activity GROUP BY 1, 2, 3 ORDER BY 4 DESC;这里的state是会话状态主要关注几种active表示正在执行查询idle表示连接空闲idle in transaction表示事务打开了但还没提交或回滚这是最需要警惕的状态它可能不占 CPU但会占着锁、挡住 autovacuum甚至导致事务号膨胀。如果看到大量idle in transaction基本可以断定应用层有事务忘记提交了。1.2 当前正在跑什么跑了多久连接数够不够只是第一层还得知道当前哪些语句在跑、跑多久了。SELECT pid, usename, datname, state, now() - query_start AS query_duration, wait_event_type, wait_event, left(query, 120) AS query FROM pg_stat_activity WHERE state active AND query NOT ILIKE %pg_stat_activity% AND pid pg_backend_pid() ORDER BY query_duration DESC LIMIT 50;这条 SQL 里有几个细节值得说一下。now() - query_start算出来是一个 interval 类型比如00:02:15.431表示这条查询已经跑了 2 分 15 秒。wait_event_type和wait_event是“当前在等什么”的线索如果一条查询跑了很久说明不是它在忙而是在等待锁、等待 IO、等待客户端读取结果。left(query, 120)只截取前 120 个字符避免一条几千字的 SQL 刷爆终端。pid pg_backend_pid()这个条件是我后来专门加上的因为如果你自己就在这个会话里跑查询pg_stat_activity也会把你自己算进去导致列表里永远有一条看起来好像一直很慢的 SQL。1.3 长事务和空闲事务快速定位比慢查询更隐蔽的是长事务。一条查询跑了很久至少是有活的 CPU 消耗一个事务跑了很久但一直没提交才是最让 DBA 头疼的。它会占用事务号、保留旧版本数据、阻止 vacuum 清理甚至让一张表膨胀到磁盘爆掉。查长事务用这条SELECT pid, usename, datname, state, now() - xact_start AS xact_duration, now() - query_start AS query_duration, left(query, 100) AS query FROM pg_stat_activity WHERE xact_start IS NOT NULL ORDER BY xact_duration DESC LIMIT 50;xact_start是事务开始时间query_start是当前这条 SQL 开始时间。区别在于一个事务里可以连续执行多条 SQL事务开始时间可能很早但每条 SQL 的 query_start 都是各自的。如果xact_duration很长而query_duration很短这十有八九是应用开了事务又没提交原地挂机。还需要看一眼两阶段提交事务SELECT gid, prepared, owner, database FROM pg_prepared_xacts ORDER BY prepared;pg_prepared_xacts里出现的 prepared 事务如果长期存在同样会拖住 vacuum而且比普通长事务更难清理因为它要人工ROLLBACK PREPARED或COMMIT PREPARED。平时巡检最好把它也纳入例行检查。2. 空间到底去哪了表大小、索引大小和膨胀排查2.1 数据库和表的大小排名磁盘报警是另一类高频工单。我最常用的是先看数据库总大小排名SELECT datname, pg_size_pretty(pg_database_size(datname)) AS size FROM pg_database ORDER BY pg_database_size(datname) DESC;pg_database_size是求整个数据库占用的磁盘空间pg_size_pretty把字节转成易读的 GB、MB。接着再看单表排名SELECT n.nspname AS schema_name, c.relname AS table_name, pg_size_pretty(pg_total_relation_size(c.oid)) AS total_size, pg_size_pretty(pg_relation_size(c.oid)) AS table_size, pg_size_pretty(pg_indexes_size(c.oid)) AS index_size FROM pg_class c JOIN pg_namespace n ON n.oid c.relnamespace WHERE c.relkind IN (r, p, m) AND n.nspname NOT IN (pg_catalog, information_schema) ORDER BY pg_total_relation_size(c.oid) DESC LIMIT 20;pg_total_relation_size(oid)是整个表占用的总空间包含表主体、索引、TOAST 表。pg_relation_size(oid)只看表主体数据。pg_indexes_size(oid)单独看这个表的所有索引占多大。很多“表不大但是磁盘爆了”的场景最终都是索引太大或者 TOAST 太大这几个数字一拆就立刻清楚了。2.2 TOAST 占了多少表里如果有大字段比如 JSON、XML、TEXT 存了大量内容数据会被挪到 TOAST 表里。想看 TOAST 占用的实操 SQL 是这样SELECT schemaname, relname, pg_size_pretty(pg_relation_size(relid)) AS heap_size, pg_size_pretty(pg_indexes_size(relid)) AS index_size, pg_size_pretty(pg_total_relation_size(relid) - pg_relation_size(relid) - pg_indexes_size(relid)) AS toast_size, pg_size_pretty(pg_total_relation_size(relid)) AS total_size FROM pg_stat_user_tables ORDER BY pg_total_relation_size(relid) DESC LIMIT 20;pg_stat_user_tables里其实已经有relid字段直接拿它当表 OID 用就行。因为pg_total_relation_size 表主体 索引 TOAST所以把前两项减掉剩下的就是 TOAST 大小。如果一张表主体只有几百 MBTOAST 却占了几个 GB就要考虑这些大字段是不是真的需要全量存在关系表里或者是不是有垃圾数据没清理。2.3 表膨胀和 dead tuple 怎么看PostgreSQL 的 MVCC 机制决定了 deleted/updated 的行不会立刻物理消失而是先在表里留下 dead tuple等 vacuum 来清理。如果 vacuum 跟不上写入频率表就会膨胀。所以巡检必须看n_dead_tupSELECT schemaname, relname, n_live_tup, n_dead_tup, CASE WHEN n_live_tup 0 THEN round(100 * n_dead_tup::numeric / n_live_tup, 2) ELSE 0 END AS dead_ratio, last_vacuum, last_autovacuum FROM pg_stat_user_tables WHERE n_dead_tup 100 ORDER BY n_dead_tup DESC LIMIT 20;dead_ratio是 dead tuple 占 live tuple 的比例超过 20% 甚至 50% 就该警惕了说明写入或删除很频繁而 autovacuum 没跟上。last_vacuum是手动 vacuum 时间last_autovacuum是自动 vacuum 时间。如果last_autovacuum是 NULL 或非常久远多半是 autovacuum 没触发或者被vacuum_cost_limit拖累了。不过要提醒一点n_dead_tup是统计信息不是磁盘上物理膨胀的精确值。真想知道物理页面上有多少空槽可以装扩展pgstattupleCREATE EXTENSION pgstattuple; SELECT * FROM pgstattuple(public.my_table);它返回dead_tuple_percent等字段更接近真实膨胀率。但它在扫描表时会占用 IO大表建议放在低峰期做。3. 慢SQL与索引效率性能调优的核心3.1 pg_stat_statements 是慢SQL分析第一利器PostgreSQL 不像某些商业数据库那样自带很漂亮的慢 SQL 报表最头部的性能分析入口是pg_stat_statements扩展。启用它需要两步CREATE EXTENSION IF NOT EXISTS pg_stat_statements;同时在postgresql.conf里加上shared_preload_libraries pg_stat_statements加完这个参数必须重启实例才能生效很多人只执行了CREATE EXTENSION就以为有了结果查不到数据就是这个原因。启用之后看累计执行时间最长的 SQLSELECT queryid, calls, round(total_exec_time::numeric, 2) AS total_ms, round(total_exec_time::numeric / NULLIF(calls, 0), 2) AS avg_ms, round(max_exec_time::numeric, 2) AS max_ms, left(query, 120) AS query FROM pg_stat_statements ORDER BY total_exec_time DESC LIMIT 20;这里有个版本差异PostgreSQL 14 之前的字段名是total_time、mean_time14 之后改成了total_exec_time、mean_exec_time、min_exec_time、max_exec_time等。如果你维护的是 PG 12/13照抄上面的 SQL 会报“列不存在”。除了按累计时间排序还可以按平均时间排序找出“单次跑得很久”的查询SELECT queryid, calls, round(total_exec_time::numeric / NULLIF(calls, 0), 2) AS avg_ms, round(max_exec_time::numeric, 2) AS max_ms, left(query, 120) AS query FROM pg_stat_statements ORDER BY total_exec_time::numeric / NULLIF(calls, 0) DESC LIMIT 20;pg_stat_statements把所有文本相同的 SQL 聚合成了参数化形式所以同一类查询会汇总成一行。它默认只统计数据库里最耗时的调用但对生产排查来说已经非常够用了。3.2 没开扩展也能抓现场有些库权限受限或者刚接手一时半会儿不能重启就用pg_stat_activity抓当前慢 SQLSELECT pid, usename, datname, state, now() - query_start AS query_duration, left(query, 100) AS query FROM pg_stat_activity WHERE state active AND now() - query_start interval 5 seconds AND query NOT ILIKE %pg_stat_activity% ORDER BY query_duration DESC;这相当于“现场抓人”专门把已经跑了 5 秒以上的查询列出来。不过它只能看到正在执行的跑完就没了。如果想保留历史一定要配置log_min_duration_statementlog_min_duration_statement 200作用是把超过 200 毫秒的语句写入日志。配合后期分析日志比只依赖实时视图要全面得多。3.3 索引有没有被使用索引不是越多越好很多表上一堆索引写性能被拖慢查询却一个都没用上。看索引使用情况用pg_stat_user_indexesSELECT schemaname, relname, indexrelname, idx_scan, idx_tup_read, idx_tup_fetch, pg_size_pretty(pg_relation_size(indexrelid)) AS idx_size FROM pg_stat_user_indexes WHERE schemaname NOT IN (pg_catalog, information_schema) ORDER BY idx_scan ASC, pg_relation_size(indexrelid) DESC LIMIT 30;idx_scan是索引被扫描的次数如果它是 0 或者很小同时索引文件又很大那大概率是“僵尸索引”。不过别急着删唯一的例外是主键和唯一约束索引它们即使不参与查询扫描也承担着保证数据唯一性的职责删了会破坏约束。更简单的做法是先按表把所有索引定义拉出来人工快速比对SELECT schemaname, tablename, indexname, indexdef FROM pg_indexes WHERE schemaname public ORDER BY tablename, indexname;看到同一张表上有多个索引字段前缀和顺序都一样就值得进一步确认是否重复。3.4 缓存命中率和顺序扫描比例数据库的内存配置合不合理参考值很直观。巡检pg_stat_databaseSELECT datname, blks_hit, blks_read, round(100 * blks_hit::numeric / NULLIF(blks_hit blks_read, 0), 2) AS cache_hit_pct, seq_scan, idx_scan, round(100 * seq_scan::numeric / NULLIF(seq_scan idx_scan, 0), 2) AS seq_scan_pct FROM pg_stat_database WHERE datname NOT IN (template0, template1) ORDER BY seq_scan_pct DESC;blks_hit是命中 shared buffer 的块数blks_read是从磁盘读的块数。正常运行的系统缓存命中率应该在 99% 以上如果某个库低于 98%要么 shared_buffers 太小要么查询在扫大量冷数据。再看表级顺序扫描SELECT schemaname, relname, seq_scan, idx_scan, n_live_tup FROM pg_stat_user_tables WHERE seq_scan 10000 AND idx_scan 0 ORDER BY seq_scan DESC;如果你看到一张几百万行的大表 seq_scan 很高而 idx_scan 为 0大概率是缺索引或者现有索引没有走对。当然小表走全表扫描反而更快所以这个指标不能只看数字还要结合表行数判断。4. 锁与阻塞救火现场最实用的SQL4.1 谁在等锁被谁阻塞锁问题最直观的入口是pg_blocking_pids。它接收一个正在阻塞别人的 pid返回阻塞源 pid 数组。先找所有“正在等锁”的会话SELECT blocked.pid AS blocked_pid, blocked.state, to_char(blocked.query_start, YYYY-MM-DD HH24:MI:SS) AS query_start, pg_blocking_pids(blocked.pid) AS blocking_pids, left(blocked.query, 100) AS blocked_query FROM pg_stat_activity blocked WHERE cardinality(pg_blocking_pids(blocked.pid)) 0 ORDER BY blocked.query_start;跑完这条你会看到类似{12345, 67890}这样的数组这就是正在阻塞当前会话的 pid。接着去看这几个阻塞源本身在干嘛SELECT pid, state, wait_event_type, wait_event, now() - query_start AS query_duration, now() - xact_start AS xact_duration, left(query, 100) AS query FROM pg_stat_activity WHERE pid ANY(pg_blocking_pids(阻塞方pid));这里非常关键阻塞别人的进程状态往往是idle in transaction也就是事务开了没提交。它自己可能已经没有任何查询在跑看起来好像“没事做”但它持有事务快照别人更新同一行就会被卡住。4.2 pg_locks 视图怎么读pg_locks是全实例锁信息的底表。我更习惯用它看“等待锁未获得”的直接证据SELECT l.pid, l.locktype, l.mode, l.granted, c.relname AS relation_name, s.state, now() - s.query_start AS query_duration FROM pg_locks l LEFT JOIN pg_class c ON c.oid l.relation LEFT JOIN pg_stat_activity s ON s.pid l.pid WHERE NOT l.granted ORDER BY query_duration DESC NULLS LAST;granted false表示这个会话在等待锁granted true表示它已经拿到锁。locktype常见取值有relation、tuple、transactionid、virtualxid等。实际遇到行锁冲突时经常看到tuple类和transactionid类锁这时候要顺着pg_locks里的pid回到pg_stat_activity去查事务状态。4.3 等待事件数据库告诉你卡在哪使用最新版本 PostgreSQL 的好处是等待事件足够细分。我经常用一条聚合 SQL 看全局等待分布SELECT wait_event_type, wait_event, count(*) FROM pg_stat_activity WHERE wait_event IS NOT NULL GROUP BY 1, 2 ORDER BY 3 DESC;常见 wait_event_type 有Lock表示等锁IO表示等磁盘读写LWLock表示等内部轻量级锁ClientRead表示等客户端读取结果。如果大量会话停在ClientRead不要慌这通常不是数据库问题而是应用层没有把结果取完。真正要重点排查的是Lock和IO两个类型。4.4 取消还是终止找到嫌疑 pid 后有两种处理方式SELECT pg_cancel_backend(pid); SELECT pg_terminate_backend(pid);pg_cancel_backend是“建议取消”它发给后端进程一个取消信号让正在执行的查询自己停下来。pg_terminate_backend是“强制终止”直接断开整个会话有点像直接把进程 kill 掉。我的习惯是能 cancel 绝不 terminate。因为 terminate 会导致这个连接里的事务全部回滚如果它已经跑了很久回滚本身也会消耗大量 IO 和时间。只有面对idle in transaction这种挂死又不理人的会话或者 cancel 之后仍然不退的情况下才用 terminate。5. 防暴毙vacuum 与事务ID回绕检查5.1 数据库年龄和回绕风险PostgreSQL 的事务 ID 是 32 位整数用完后需要回卷而回卷会造成数据丢失。因此系统用frozenxid把老事务永久冻结规避回卷。DBA 最重要的指标之一就是“年龄”SELECT datname, age(datfrozenxid) AS xid_age, mxid_age(datminmxid) AS mxid_age, pg_size_pretty(pg_database_size(datname)) AS db_size FROM pg_database ORDER BY age(datfrozenxid) DESC;age(datfrozenxid)越大说明这个库距离强制冻结越近。如果超过 15 亿必须马上处理。更严格一点我建议过了 10 亿就开始人工介入。别等 autovacuum因为接管大库的 aggressive vacuum 会把 IO 打满严重时影响业务。定位是否危险SELECT datname, age(datfrozenxid), mxid_age(datminmxid) FROM pg_database WHERE age(datfrozenxid) 1500000000 OR mxid_age(datminmxid) 1500000000;如果真到了这一步最直接的挽救手段是在维护窗口执行VACUUM (VERBOSE) my_database;注意这个VACUUM不带表名表示处理整个数据库。它会消耗大量 IO时间也可能很长务必提前评估。5.2 autovacuum 到底有没有跟上事务年龄是宏观指标具体到每张表要看最后清理时间和 dead tuple 数量SELECT schemaname, relname, n_live_tup, n_dead_tup, COALESCE(last_autovacuum, last_vacuum) AS last_cleanup, autovacuum_count, vacuum_count, round(extract(epoch FROM now() - COALESCE(last_autovacuum, last_vacuum)) / 3600, 1) AS hours_since_cleanup FROM pg_stat_user_tables WHERE (n_live_tup 1000 OR n_dead_tup 1000) ORDER BY hours_since_cleanup DESC NULLS LAST LIMIT 30;hours_since_cleanup表示距离上次清理已经过去几小时。如果一张高写入表这个数字非常大说明 autovacuum 没有按预期触发需要检查基础参数SELECT name, setting, unit FROM pg_settings WHERE name IN ( autovacuum, autovacuum_vacuum_threshold, autovacuum_vacuum_scale_factor, autovacuum_vacuum_cost_limit, autovacuum_freeze_max_age );需要注意PostgreSQL 16 之前默认的autovacuum_vacuum_scale_factor是 0.2也就是表里 20% 的行变成死行才触发。对于几亿行的大表来说这个触发条件非常滞后。PG 16 之后开始转向用autovacuum_vacuum_insert_threshold等参数改进方向是对的但存量老库仍要结合业务调整。5.3 VACUUM、VACUUM FULL 和 REINDEX 怎么选日常维护最常见的三个操作VACUUM (VERBOSE, ANALYZE) public.mytable; VACUUM FULL VERBOSE public.mytable; REINDEX INDEX CONCURRENTLY public.myindex;普通VACUUM可以频繁执行它清理 dead tuple、更新统计信息但不回收磁盘空间给操作系统空间只是“可以被复用”。VACUUM FULL会重建表物理缩小文件但它持有ACCESS EXCLUSIVE锁整个过程表不能读写必须安排在维护窗口。REINDEX CONCURRENTLY可以不停机重建索引但会比普通 REINDEX 慢也会消耗更多资源。我的经验是对在线业务尽量多用普通VACUUM少用VACUUM FULL。如果真的是空间长期只增不减优先考虑业务清理策略再配合pg_repack这类在线重建工具而不是每次都锁表。6. 复制与高可用主备巡检的常用SQL6.1 主从延迟怎么看只要用了流复制pg_stat_replication就是必看的视图。在主库上执行SELECT client_addr, client_port, usename, application_name, state, sent_lsn, write_lsn, flush_lsn, replay_lsn, write_lag, flush_lag, replay_lag FROM pg_stat_replication;replay_lag是备库回放落后主库的时间是业务最关心的延迟。write_lag是 WAL 写到备库的时间flush_lag是备库把 WAL 刷盘的时间。如果replay_lag持续增长说明备库回放能力不足或者正被只读查询抢资源。在备库上执行SELECT pg_is_in_recovery(), pg_last_wal_receive_lsn(), pg_last_wal_replay_lsn();pg_is_in_recovery()返回 true 说明当前是一个备库这是每次角色切换后我都要确认的第一件事。如果返回 false那这台机器已经是以主库身份运行了。6.2 复制槽与 WAL 堆积复制槽最大的坑是“备库下线了主库还傻傻地保留着所有 WAL”。检查复制槽SELECT slot_name, slot_type, active, restart_lsn, pg_size_pretty(pg_wal_lsn_diff(pg_current_wal_insert_lsn(), restart_lsn)) AS retained_wal FROM pg_replication_slots WHERE slot_type physical;restart_lsn是这个槽要求保留的最老 WAL 位置。pg_current_wal_insert_lsn()是主库当前写入位置。两者差值越大说明主库正在为这个槽保留越多的旧 WAL。如果某个槽active false且 retained_wal 快速增长说明备库已经断开但槽没有删除这就是磁盘被写满的经典前兆。遇到这种情况不要急着SELECT pg_drop_replication_slot(slot_name);来释放空间。先确认对应备库是不是已经不需要同步了如果备库只是临时断开删掉槽会让它回来之后再也追不上 WAL最终只能重新做备份。插槽是保护机制删的时候要想清楚。6.3 手动切换和升级前的确认虽然生产环境一般用 Patroni、repmgr 这类工具做高可用但手动环境或应急场景下SQL 层面几个检查点还是应该记住。切换前先看备库延迟是否为 0-- 主库执行确认所有备库都追平 SELECT application_name, replay_lag FROM pg_stat_replication;然后主库主动切换一个 WAL 文件让备库更及时收到SELECT pg_switch_wal();备库追平后执行提升SELECT pg_promote();在 PostgreSQL 大版本升级之后最容易被漏掉的是统计信息迁移。升级完应该尽早执行ANALYZE或者直接跑vacuumdb --all --analyze-only不重新收集统计信息优化器会拿着旧版本或空的统计信息去选执行计划那种“升级之后反而变慢”的问题多半就出在这里。7. 我踩过的坑和避坑建议7.1 查连接时记得过滤自己用pg_stat_activity列慢查询时如果不加pid pg_backend_pid()你自己的这条查询也会出现在结果里。刚开始我以为是数据库真有慢查询反复排查半天最后发现那是我自己。这个问题很傻但确实容易踩建议所有基于pg_stat_activity的 SQL 都把这个条件写进去。7.2 留意 PostgreSQL 版本的字段变化PostgreSQL 系统视图和扩展的字段名并不一成不变。最典型的就是pg_stat_statementsPG 13 和 PG 14 的字段名就不一样。网上很多教程抄来抄去可能来自老版本直接粘到新环境就报错。接手一个新库第一件事先确认版本SELECT version(), current_setting(server_version_num);这里附一个常见差异速查表PostgreSQL 版本pg_stat_statements 时间字段12 / 13total_time, mean_time14 及以上total_exec_time, mean_exec_time, min_exec_time, max_exec_time遇到“字段不存在”报错先看版本再决定要不要换列名。7.3 大表批量更新前先摸清膨胀水平很多人习惯直接 DELETE UPDATE 上亿行的表跑完发现表比之前还大这才开始找原因。其实在操作之前先跑一下n_dead_tup和表大小心里就有底了SELECT pg_size_pretty(pg_total_relation_size(public.big_table)) AS table_size, n_dead_tup, last_autovacuum FROM pg_stat_user_tables WHERE schemaname public AND relname big_table;如果表已经有大量 dead tuple 和较长的last_autovacuum间隔说明 vacuum 本来就滞后批量更新会雪上加霜。这时候要么降频要么分批提交要么准备维护窗口。7.4 别把 VACUUM FULL 当日常操作VACUUM FULL是我见过最容易被误用的命令。它确实能把空间还给操作系统但代价是全表锁写。生产系统哪怕半夜也可能有定时任务、备份程序在跑一次VACUUM FULL把业务卡住几十分钟的事并不少见。如果一张表确实膨胀严重优先考虑先看是否有长事务阻塞 autovacuum再调整 autovacuum 参数最后才考虑VACUUM FULL。如果真的要做提前和业务方确认维护窗口用VACUUM (VERBOSE, FULL)并在日志里观察进度。7.5 连接池环境下别只看 pg_stat_activity如果应用前端挂了 PgBouncerpg_stat_activity里看到的client_addr往往不是应用真实 IP而是连接池所在主机的 IP。之前排查一个“某个应用持续占用连接”的问题照着 client_addr 找半天发现全是 PgBouncer 的地址。这种情况下要结合application_name或者连接池自身的统计视图去定位真实来源否则容易被表象带偏。我自己现在维持的习惯是每天定时跑一遍连接数、数据库年龄、复制延迟、dead tuple 和慢查询 Top N 这几组 SQL并把这些结果落到一个巡检脚本里。真出了问题手边能立刻拿出来的是可复用的命令而不是重新翻文档。这套 PostgreSQL DBA 最常用 SQL对我来说就是晚上睡得着觉的底气。
网站建设高端定制企业官网