{"index.json":{"schemaVersion":2,"app":"DBAtlas","updated":"2026-07-04","engines":"engines.json","rosetta":["rosetta/sessions.json","rosetta/storage.json","rosetta/performance.json","rosetta/backup.json","rosetta/security.json","rosetta/maintenance.json","rosetta/schema.json","rosetta/replication.json","rosetta/connect.json","rosetta/objects.json","rosetta/datamove.json","rosetta/monitoring.json","rosetta/automation.json","rosetta/lifecycle.json","rosetta/migration.json","rosetta/tuning.json","rosetta/tuning-adv.json","rosetta/advanced-features.json","rosetta/sql-syntax.json"],"cloud":["cloud/cloud-ops-1.json","cloud/cloud-ops-2.json"],"guide":{"bestPractices":["guide/best-practices.json","guide/best-practices-2.json","guide/best-practices-3.json","guide/best-practices-4.json"],"glossary":["guide/glossary.json","guide/glossary-2.json","guide/glossary-3.json"]},"rosettaOverlays":["rosetta/overlay-elasticsearch.json"],"types":"types/types-functions.json","errors":["errors/oracle.json","errors/postgres.json","errors/mysql.json","errors/mssql.json","errors/mongodb.json","errors/redis.json","errors/elasticsearch.json","errors/oracle-more.json","errors/postgres-more.json","errors/mysql-more.json","errors/mssql-more.json","errors/mongodb-more.json","errors/redis-more.json","errors/elasticsearch-more.json","errors/oracle-more2.json","errors/postgres-more2.json","errors/mysql-more2.json","errors/mssql-more2.json","errors/mongodb-more2.json","errors/redis-more2.json","errors/elasticsearch-more2.json","errors/oracle-more3.json","errors/postgres-more3.json","errors/mysql-more3.json","errors/mssql-more3.json","errors/mongodb-more3.json","errors/redis-more3.json","errors/elasticsearch-more3.json","errors/oracle-more4.json","errors/postgres-more4.json","errors/mysql-more4.json","errors/mssql-more4.json","errors/mongodb-more4.json","errors/redis-more4.json","errors/elasticsearch-more4.json"],"health":["health/oracle.json","health/postgres.json","health/mysql.json","health/mssql.json","health/mongodb.json","health/redis.json","health/elasticsearch.json"],"recipes":["recipes/oracle.json","recipes/oracle-ha.json","recipes/oracle-ops.json","recipes/postgres.json","recipes/postgres-ha.json","recipes/postgres-ops.json","recipes/mysql.json","recipes/mysql-ext.json","recipes/mssql.json","recipes/mssql-ext.json","recipes/mongodb.json","recipes/mongodb-ext.json","recipes/redis.json","recipes/redis-ext.json","recipes/elasticsearch.json","recipes/elasticsearch-ext.json"],"playbooks":["playbooks/playbooks.json","playbooks/hadr.json","playbooks/security.json","playbooks/cloud.json","playbooks/automation.json"],"solver":["solver/slow-query.json","solver/deadlock.json","solver/disk-full.json","solver/connection-storm.json","solver/replication-lag.json","solver/ora-01555.json","solver/backup-restore.json","solver/upgrade-wizard.json","solver/migration-wizard.json","solver/high-cpu.json","solver/memory-pressure.json","solver/install-setup.json","solver/corruption-recovery.json","solver/failover-drill.json","solver/security-incident.json","solver/audit-setup.json","solver/decide-ha.json","solver/decide-isolation.json","solver/decide-sqlnosql.json","solver/decide-shard-replica.json","solver/decide-backup.json","solver/decide-index.json","solver/decide-partition.json","solver/decide-pooling.json","solver/decide-cache.json","solver/decide-datatype.json","solver/decide-capacity.json","solver/slow-write.json","solver/io-bottleneck.json","solver/undo-temp-pressure.json","solver/long-transaction.json","solver/plan-regression.json","solver/replica-resync.json","solver/cache-hit-low.json","solver/arch-oltp.json","solver/arch-dwh.json","solver/arch-microservice.json","solver/arch-eventdriven.json","solver/arch-multiregion.json","solver/arch-highscale.json"],"reference":{"params":["reference/params-oracle.json","reference/params-postgres.json","reference/params-mysql.json","reference/params-mssql.json","reference/params-mongodb.json","reference/params-redis.json","reference/params-elasticsearch.json"],"waits":["reference/waits-oracle.json","reference/waits-postgres.json","reference/waits-mysql.json","reference/waits-mssql.json","reference/waits-mongodb.json","reference/waits-redis.json","reference/waits-elasticsearch.json"],"alerts":["reference/alerts-oracle.json","reference/alerts-postgres.json","reference/alerts-mysql.json","reference/alerts-mssql.json","reference/alerts-mongodb.json","reference/alerts-redis.json","reference/alerts-elasticsearch.json"]},"install":"install/index.json"},"engines.json":{"schemaVersion":1,"updated":"2026-07-03","engines":[{"id":"oracle","name":"Oracle","full":"Oracle Database","color":"#C74634","family":"sql","cli":"sqlplus","docs":"https://docs.oracle.com/en/database/"},{"id":"postgres","name":"PostgreSQL","full":"PostgreSQL","color":"#3461B5","family":"sql","cli":"psql","docs":"https://www.postgresql.org/docs/"},{"id":"mysql","name":"MySQL","full":"MySQL / MariaDB","color":"#00758F","family":"sql","cli":"mysql","docs":"https://dev.mysql.com/doc/"},{"id":"mssql","name":"SQL Server","full":"Microsoft SQL Server","color":"#6E3FC4","family":"sql","cli":"sqlcmd","docs":"https://learn.microsoft.com/en-us/sql/"},{"id":"mongodb","name":"MongoDB","full":"MongoDB","color":"#2E7D32","family":"document","cli":"mongosh","docs":"https://www.mongodb.com/docs/"},{"id":"redis","name":"Redis","full":"Redis","color":"#B45309","family":"keyvalue","cli":"redis-cli","docs":"https://redis.io/docs/"},{"id":"elasticsearch","name":"Elasticsearch","full":"Elasticsearch","color":"#BE2A6E","family":"search","cli":"curl / Kibana","docs":"https://www.elastic.co/guide/"}]},"rosetta/sessions.json":{"category":"会话与活动","categoryId":"sessions","order":1,"concepts":[{"id":"active-sessions","title":"显示活动会话 / 正在运行的查询","aliases":["active sessions","running queries","who is connected","current activity","active session","running query","who is connected now","processlist","current op"],"engines":{"oracle":{"command":"SELECT sid, serial#, username, status, sql_id, machine, program\nFROM v$session\nWHERE type = 'USER' AND status = 'ACTIVE';","note":"v$session 提供当前活动会话。如需完整 SQL 文本，按 sql_id 关联 v$sql。","danger":"safe"},"postgres":{"command":"SELECT pid, usename, state, wait_event_type, query\nFROM pg_stat_activity\nWHERE state <> 'idle';","note":"state = 'active' 显示当时正在执行查询的会话。可根据 backend_start / query_start 计算持续时间。","danger":"safe"},"mysql":{"command":"SELECT id, user, host, db, command, time, state, info\nFROM information_schema.processlist\nWHERE command <> 'Sleep';\n-- Shortcut: SHOW FULL PROCESSLIST;","note":"在 MySQL 8 / MariaDB 上，performance_schema.processlist 持有的锁更少。","danger":"safe"},"mssql":{"command":"SELECT r.session_id, s.login_name, r.status, r.command,\n       r.cpu_time, r.total_elapsed_time, t.text AS sql_text\nFROM sys.dm_exec_requests r\nJOIN sys.dm_exec_sessions s ON r.session_id = s.session_id\nCROSS APPLY sys.dm_exec_sql_text(r.sql_handle) t\nWHERE s.is_user_process = 1;","note":"快速查看：EXEC sp_who2 'active';","danger":"safe"},"mongodb":{"command":"db.currentOp({ \"active\": true })","note":"筛选长时间运行的操作：db.currentOp({ active: true, secs_running: { $gt: 5 } })","danger":"safe"},"redis":{"command":"CLIENT LIST\n# For a summary: INFO clients","note":"由于 Redis 是单线程的，这里列出的是已连接的客户端，而不是“正在运行的查询”。慢命令请使用 SLOWLOG GET。","danger":"safe"}}},{"id":"blocking-sessions","title":"查找阻塞 / 等待锁的会话","aliases":["blocking","locks","lock wait","deadlock source","blocking session","lock waiting","who is blocking","who is blocking me","blocked","lock holder","what am I waiting on"],"engines":{"oracle":{"command":"SELECT blocking_session, sid, serial#, username,\n       sql_id, seconds_in_wait, event\nFROM v$session\nWHERE blocking_session IS NOT NULL;","note":"blocking_session 有值的会话被 blocking_session 中的 SID 阻塞。","danger":"safe"},"postgres":{"command":"SELECT pid, usename, pg_blocking_pids(pid) AS blocked_by,\n       wait_event, query\nFROM pg_stat_activity\nWHERE cardinality(pg_blocking_pids(pid)) > 0;","note":"blocked_by 数组给出阻塞该 pid 的 PID。","danger":"safe"},"mysql":{"command":"SELECT * FROM sys.innodb_lock_waits\\G\n-- MySQL 8: SELECT * FROM performance_schema.data_lock_waits;","note":"sys schema 以易读形式展示等待和阻塞的 trx_id / 线程。","danger":"safe"},"mssql":{"command":"SELECT r.session_id, r.blocking_session_id,\n       r.wait_type, r.wait_time, r.wait_resource\nFROM sys.dm_exec_requests r\nWHERE r.blocking_session_id <> 0;","note":"blocking_session_id <> 0 的请求处于被阻塞状态。可以编写递归查询来追踪阻塞链。","danger":"safe"},"mongodb":{"command":"db.currentOp({ \"waitingForLock\": true })","note":"WiredTiger 在文档/集合级别加锁。可使用 killOp 终止长时间运行的操作。","danger":"safe"},"redis":{"command":"# Not applicable: Redis commands run atomically one at a time; there are no row locks.\n# Closest diagnostic: SLOWLOG GET 10","note":"阻塞通常源于单个长时间运行的命令（如 KEYS *、大型 SORT）；SLOWLOG 可显示这些命令。","danger":"safe"}}},{"id":"kill-session","title":"终止（kill）会话 / 查询","aliases":["kill session","terminate","cancel query","terminate session","cancel the query","kill query","killop"],"engines":{"oracle":{"command":"ALTER SYSTEM KILL SESSION 'sid,serial#' IMMEDIATE;\n-- To cancel only the running statement (18c+):\n-- ALTER SYSTEM CANCEL SQL 'sid,serial#';","note":"从 v$session 获取 sid 和 serial# 值。IMMEDIATE 会立即开始回滚。","danger":"danger"},"postgres":{"command":"-- Cancel only the running query (the session stays):\nSELECT pg_cancel_backend(pid);\n-- Close the session entirely:\nSELECT pg_terminate_backend(pid);","note":"先尝试 pg_cancel_backend；若无效，再使用 pg_terminate_backend。","danger":"danger"},"mysql":{"command":"-- Kill only the query:\nKILL QUERY <id>;\n-- Close the connection entirely:\nKILL CONNECTION <id>;","note":"<id> 值即 processlist 中的 Id 列。","danger":"danger"},"mssql":{"command":"KILL <session_id>;\n-- To see rollback progress:\n-- KILL <session_id> WITH STATUSONLY;","note":"KILL 会回滚未提交的事务；对于大事务，回滚可能耗时很长。","danger":"danger"},"mongodb":{"command":"db.killOp(<opid>)","note":"从 db.currentOp() 输出的 opid 字段获取 opid 值。","danger":"danger"},"redis":{"command":"CLIENT KILL ID <client-id>\n# or by address: CLIENT KILL ADDR 10.0.0.5:6379","note":"使用 CLIENT LIST 获取 client-id。已在运行的单个命令无法中断（Lua 脚本除外：SCRIPT KILL），只能等待其完成。若要在不断开连接的情况下释放正在等待 BLPOP/XREAD BLOCK 等阻塞命令的客户端，使用 CLIENT UNBLOCK <client-id> [TIMEOUT|ERROR]。CLIENT UNPAUSE 仅恢复被 CLIENT PAUSE 暂停的客户端。","danger":"caution"}}},{"id":"connection-count","title":"查看连接数和连接上限","aliases":["connections","max connections","connection limit","connection count","how many connections","is the connection pool full"],"engines":{"oracle":{"command":"SELECT COUNT(*) AS current_sessions FROM v$session WHERE type = 'USER';\nSELECT name, value FROM v$parameter WHERE name IN ('processes','sessions');","note":"上限由 processes/sessions 参数决定。使用率也可通过 resource_limit 视图查看。","danger":"safe"},"postgres":{"command":"SELECT count(*) FROM pg_stat_activity WHERE backend_type = 'client backend';\nSHOW max_connections;","note":"由于 pg_stat_activity 还包含后台进程，这里只统计客户端连接（backend_type = 'client backend'）。superuser_reserved_connections 槽位为管理员保留。","danger":"safe"},"mysql":{"command":"SHOW STATUS LIKE 'Threads_connected';\nSHOW VARIABLES LIKE 'max_connections';","note":"Max_used_connections 状态值显示峰值。","danger":"safe"},"mssql":{"command":"SELECT COUNT(*) FROM sys.dm_exec_sessions WHERE is_user_process = 1;\nSELECT @@MAX_CONNECTIONS AS configured_max;","note":"@@MAX_CONNECTIONS 是理论上限；实际上限取决于内存和工作线程。","danger":"safe"},"mongodb":{"command":"db.serverStatus().connections","note":"返回 current、available 和 totalCreated 字段。上限由 net.maxIncomingConnections 设置。","danger":"safe"},"redis":{"command":"INFO clients\nCONFIG GET maxclients","note":"connected_clients 是当前连接数；maxclients 是配置的上限（默认 10000）。","danger":"safe"}}}]},"rosetta/storage.json":{"category":"空间与存储","categoryId":"storage","order":2,"concepts":[{"id":"database-size","title":"显示数据库总大小","aliases":["database size","db size","database size","how much space does it use","disk usage","total size"],"engines":{"oracle":{"command":"SELECT ROUND(SUM(bytes)/1024/1024/1024, 2) AS total_gb\nFROM dba_data_files;","note":"仅统计已用段时，汇总 dba_segments。TEMP 需加上 dba_temp_files。","danger":"safe"},"postgres":{"command":"SELECT pg_size_pretty(pg_database_size(current_database()));","note":"所有数据库：SELECT datname, pg_size_pretty(pg_database_size(datname)) FROM pg_database ORDER BY 2 DESC;","danger":"safe"},"mysql":{"command":"SELECT table_schema AS db,\n       ROUND(SUM(data_length + index_length)/1024/1024, 1) AS mb\nFROM information_schema.tables\nGROUP BY table_schema\nORDER BY mb DESC;","note":"对于 InnoDB，该值为估算值；精确的文件大小请在操作系统层面检查 ibdata/数据文件。","danger":"safe"},"mssql":{"command":"EXEC sp_spaceused;\n-- All databases:\nSELECT DB_NAME(database_id) AS db,\n       CAST(SUM(size) * 8.0 / 1024 AS DECIMAL(10,1)) AS mb\nFROM sys.master_files\nGROUP BY database_id;","note":"size 列以 8 KB 页为单位；* 8 / 1024 换算为 MB。","danger":"safe"},"mongodb":{"command":"db.stats(1024*1024)\n// returns the dataSize, storageSize, indexSize fields in MB","note":"storageSize 是压缩后的磁盘占用大小；dataSize 是未压缩的逻辑大小。","danger":"safe"},"redis":{"command":"INFO memory\n# the used_memory_human line gives the total size in memory\nDBSIZE  # number of keys","note":"Redis 基于内存；“大小”指 RAM 使用量。RDB/AOF 文件大小是持久化到磁盘的状态。","danger":"safe"}}},{"id":"largest-tables","title":"列出最大的表 / 集合","aliases":["largest tables","biggest tables","top tables by size","largest table","tables using the most space","bigkeys"],"engines":{"oracle":{"command":"SELECT segment_name, segment_type,\n       ROUND(bytes/1024/1024, 1) AS mb\nFROM dba_segments\nWHERE segment_type IN ('TABLE','TABLE PARTITION')\nORDER BY bytes DESC\nFETCH FIRST 10 ROWS ONLY;","note":"去掉 segment_type 过滤条件即可同时查看索引。","danger":"safe"},"postgres":{"command":"SELECT relname AS table,\n       pg_size_pretty(pg_total_relation_size(relid)) AS total\nFROM pg_catalog.pg_statio_user_tables\nORDER BY pg_total_relation_size(relid) DESC\nLIMIT 10;","note":"pg_total_relation_size 是表 + 索引 + TOAST 的总和；仅看表本身使用 pg_relation_size。","danger":"safe"},"mysql":{"command":"SELECT table_name,\n       ROUND((data_length + index_length)/1024/1024, 1) AS mb\nFROM information_schema.tables\nWHERE table_schema = DATABASE()\nORDER BY (data_length + index_length) DESC\nLIMIT 10;","note":"针对特定 schema，使用 WHERE table_schema = 'name'。","danger":"safe"},"mssql":{"command":"SELECT TOP 10 OBJECT_NAME(object_id) AS table_name,\n       SUM(reserved_page_count) * 8 / 1024 AS mb\nFROM sys.dm_db_partition_stats\nGROUP BY object_id\nORDER BY mb DESC;","note":"reserved_page_count 涵盖所有已保留页（数据 + 索引）。","danger":"safe"},"mongodb":{"command":"db.getCollectionNames()\n  .map(c => ({ name: c, mb: +(db[c].stats().storageSize/1048576).toFixed(1) }))\n  .sort((a, b) => b.mb - a.mb)\n  .slice(0, 10)","note":"storageSize 是压缩后的磁盘占用大小。即使在大集合上，stats() 的开销也很低。","danger":"safe"},"redis":{"command":"redis-cli --bigkeys\n# To rank by memory (bytes): redis-cli --memkeys","note":"两者都通过 SCAN 扫描整个键空间（不会阻塞服务器，但会增加负载）；区别在于指标：--bigkeys 按每种类型的元素数/长度报告最大的键，--memkeys 通过 MEMORY USAGE 按内存消耗报告。单个键使用 MEMORY USAGE <key>。","danger":"caution"}}},{"id":"free-space","title":"检查剩余空间 / 使用率","aliases":["free space","disk full","free space","usage","space left","tablespace usage","disk is full"],"engines":{"oracle":{"command":"SELECT df.tablespace_name,\n       ROUND(SUM(df.bytes)/1024/1024) AS total_mb,\n       ROUND(NVL(fs.free, 0)/1024/1024) AS free_mb,\n       ROUND((1 - NVL(fs.free,0)/SUM(df.bytes)) * 100, 1) AS pct_used\nFROM dba_data_files df\nLEFT JOIN (SELECT tablespace_name, SUM(bytes) free\n           FROM dba_free_space GROUP BY tablespace_name) fs\n  ON df.tablespace_name = fs.tablespace_name\nGROUP BY df.tablespace_name, fs.free;","note":"对于开启 AUTOEXTEND 的文件，真正的上限是 maxbytes；MAXSIZE 请查看 dba_data_files.maxbytes。","danger":"safe"},"postgres":{"command":"-- Datafile usage is at the OS level (df -h). Tablespace sizes from inside the DB:\nSELECT spcname, pg_size_pretty(pg_tablespace_size(spcname))\nFROM pg_tablespace;","note":"PostgreSQL 依赖操作系统提供剩余磁盘空间；对于关键阈值，操作系统监控（df）必不可少。","danger":"safe"},"mysql":{"command":"SELECT table_schema,\n       ROUND(SUM(data_free)/1024/1024, 1) AS reclaimable_mb\nFROM information_schema.tables\nGROUP BY table_schema;","note":"data_free 是表内可回收（碎片化）的空间；磁盘剩余空间在操作系统层面查看。","danger":"safe"},"mssql":{"command":"SELECT name AS file_name,\n       size/128.0 AS size_mb,\n       size/128.0 - CAST(FILEPROPERTY(name,'SpaceUsed') AS int)/128.0 AS free_mb\nFROM sys.database_files;","note":"SpaceUsed 以页为单位；除以 128 得到 MB（8 KB 页）。","danger":"safe"},"mongodb":{"command":"var s = db.stats();\n({ fsTotalGB: +(s.fsTotalSize/1073741824).toFixed(1),\n   fsUsedGB:  +(s.fsUsedSize/1073741824).toFixed(1) })","note":"使用 WiredTiger 时，fsTotalSize / fsUsedSize 给出操作系统层面磁盘的总大小和已用大小。","danger":"safe"},"redis":{"command":"INFO memory\nCONFIG GET maxmemory\n# Usage = used_memory / maxmemory","note":"maxmemory 为 0 时不设上限，RAM 耗尽时由操作系统 OOM killer 介入。maxmemory-policy 决定淘汰行为。","danger":"safe"}}},{"id":"row-count","title":"获取行数 / 文档数（快速）","aliases":["row count","count rows","row count","how many records","document count","how many rows"],"engines":{"oracle":{"command":"-- Statistics-based (fast, estimated):\nSELECT num_rows FROM dba_tables WHERE table_name = 'CUSTOMER';\n-- Exact: SELECT COUNT(*) FROM customer;","note":"num_rows 反映最近一次 ANALYZE/DBMS_STATS 的结果；需要精确值请使用 COUNT(*)。","danger":"safe"},"postgres":{"command":"-- Estimated (instant):\nSELECT reltuples::bigint FROM pg_class WHERE relname = 'customer';\n-- Exact: SELECT count(*) FROM customer;","note":"reltuples 是截至最近一次 ANALYZE/VACUUM 的估算值；在大表上执行完整 COUNT 代价很高。","danger":"safe"},"mysql":{"command":"-- Estimated (InnoDB):\nSELECT table_rows FROM information_schema.tables\nWHERE table_name = 'customer';\n-- Exact: SELECT COUNT(*) FROM customer;","note":"在 InnoDB 中，table_rows 是估算值，误差可达 50%。","danger":"safe"},"mssql":{"command":"SELECT SUM(row_count) AS rows\nFROM sys.dm_db_partition_stats\nWHERE object_id = OBJECT_ID('dbo.customer')\n  AND index_id IN (0, 1);","note":"index_id 0（堆）或 1（聚集）给出行数；比 COUNT(*) 扫描快得多。","danger":"safe"},"mongodb":{"command":"// Fast (from metadata):\ndb.customer.estimatedDocumentCount()\n// Exact (can count with a filter): db.customer.countDocuments({})","note":"estimatedDocumentCount 读取元数据（即时）；countDocuments 执行实际扫描。","danger":"safe"},"redis":{"command":"DBSIZE\n# Specific pattern: count roughly with SCAN (do not use KEYS * in production)","note":"DBSIZE 以 O(1) 返回所选数据库中的键总数。","danger":"safe"}}}]},"rosetta/performance.json":{"category":"性能","categoryId":"performance","order":3,"concepts":[{"id":"top-slow-queries","title":"查找最慢 / 开销最大的查询","aliases":["slow queries","top queries","slowest queries","top resource consumers","expensive queries","slowlog"],"engines":{"oracle":{"command":"SELECT sql_id,\n       ROUND(elapsed_time/1e6, 1) AS elapsed_sec,\n       executions,\n       ROUND(elapsed_time/1e6/NULLIF(executions,0), 3) AS sec_per_exec,\n       sql_text\nFROM v$sql\nORDER BY elapsed_time DESC\nFETCH FIRST 10 ROWS ONLY;","note":"历史分析使用 AWR：dba_hist_sqlstat。有了 sql_id，DBMS_XPLAN.DISPLAY_CURSOR 可显示执行计划。","danger":"safe"},"postgres":{"command":"SELECT calls,\n       ROUND(total_exec_time::numeric, 1) AS total_ms,\n       ROUND(mean_exec_time::numeric, 2) AS mean_ms,\n       query\nFROM pg_stat_statements\nORDER BY total_exec_time DESC\nLIMIT 10;","note":"需要 pg_stat_statements 扩展（shared_preload_libraries）。使用 pg_stat_statements_reset() 重置。","danger":"safe"},"mysql":{"command":"SELECT digest_text,\n       count_star AS calls,\n       ROUND(sum_timer_wait/1e12, 2) AS total_sec,\n       ROUND(avg_timer_wait/1e9, 2) AS avg_ms\nFROM performance_schema.events_statements_summary_by_digest\nORDER BY sum_timer_wait DESC\nLIMIT 10;","note":"替代方案：开启 slow_query_log = ON 并设置 long_query_time 的慢查询日志。","danger":"safe"},"mssql":{"command":"SELECT TOP 10\n       qs.execution_count,\n       qs.total_elapsed_time/1000 AS total_ms,\n       qs.total_elapsed_time/qs.execution_count/1000 AS avg_ms,\n       SUBSTRING(t.text, 1, 200) AS sql_text\nFROM sys.dm_exec_query_stats qs\nCROSS APPLY sys.dm_exec_sql_text(qs.sql_handle) t\nORDER BY qs.total_elapsed_time DESC;","note":"如果启用了 Query Store，可使用 sys.query_store_runtime_stats 进行更丰富的历史分析。","danger":"safe"},"mongodb":{"command":"// Enable the profiler for operations over 100ms:\ndb.setProfilingLevel(1, { slowms: 100 })\n// Then read the slowest ones:\ndb.system.profile.find().sort({ millis: -1 }).limit(10)","note":"profiler 会增加写入开销；诊断结束后用 db.setProfilingLevel(0) 关闭。","danger":"caution"},"redis":{"command":"SLOWLOG GET 10\n# Set the threshold (microseconds): CONFIG SET slowlog-log-slower-than 10000","note":"slowlog-log-slower-than 以微秒为单位（10000 = 10 ms）。使用 SLOWLOG RESET 清空。","danger":"safe"}}},{"id":"explain-plan","title":"获取查询的执行计划（EXPLAIN）","aliases":["explain","execution plan","query plan","explain plan","show plan","explain analyze","why slow"],"engines":{"oracle":{"command":"EXPLAIN PLAN FOR\nSELECT * FROM customer WHERE city = 'Ankara';\nSELECT * FROM TABLE(DBMS_XPLAN.DISPLAY);\n-- For the actual executed plan:\n-- SELECT * FROM TABLE(DBMS_XPLAN.DISPLAY_CURSOR('&sql_id', NULL, 'ALLSTATS LAST'));","note":"EXPLAIN PLAN 给出估算计划；如需实际行数，使用 DISPLAY_CURSOR + STATISTICS_LEVEL=ALL。","danger":"safe"},"postgres":{"command":"EXPLAIN (ANALYZE, BUFFERS, FORMAT TEXT)\nSELECT * FROM customer WHERE city = 'Ankara';","note":"ANALYZE 会真正执行查询（对 INSERT/UPDATE/DELETE 要小心；在事务内尝试并 ROLLBACK）。","danger":"caution"},"mysql":{"command":"EXPLAIN ANALYZE\nSELECT * FROM customer WHERE city = 'Ankara';\n-- Estimate only (does not execute): EXPLAIN FORMAT=JSON SELECT ...;","note":"EXPLAIN ANALYZE（MySQL 8.0.18+）会执行查询；FORMAT=JSON 不执行即可给出详细信息。","danger":"caution"},"mssql":{"command":"SET STATISTICS PROFILE ON;\nSELECT * FROM dbo.customer WHERE city = 'Ankara';\nSET STATISTICS PROFILE OFF;\n-- Estimated plan only (does not execute):\n-- SET SHOWPLAN_XML ON;","note":"在 SSMS 中，“包括实际的执行计划”（Ctrl+M）显示图形化计划。","danger":"caution"},"mongodb":{"command":"db.customer.find({ city: \"Ankara\" }).explain(\"executionStats\")","note":"executionStats 显示实际返回的文档数和索引使用情况（IXSCAN vs COLLSCAN）。","danger":"safe"},"redis":{"command":"# Not applicable: Redis has no query planner.\n# The complexity of each command is documented (e.g. GET O(1), SORT O(N+M*log(M))).","note":"性能取决于命令复杂度以及键/数据结构的大小；使用 SLOWLOG 进行测量。","danger":"safe"}}},{"id":"unused-indexes","title":"检测未使用的索引","aliases":["unused indexes","unused index","redundant index","index usage","drop index candidate"],"engines":{"oracle":{"command":"-- Start monitoring:\nALTER INDEX customer_city_idx MONITORING USAGE;\n-- Check after a while (12c+, all schemas):\nSELECT owner, index_name, used FROM dba_object_usage;","note":"USED = NO 表示该索引在监控期间未被使用。使用 NOMONITORING USAGE 关闭监控。旧的 v$object_usage 只显示当前会话用户的索引，且自 12c 起已弃用。在 12.2+ 中，索引使用情况会自动记录在 DBA_INDEX_USAGE 中。","danger":"safe"},"postgres":{"command":"SELECT relname AS table, indexrelname AS index, idx_scan\nFROM pg_stat_user_indexes\nWHERE idx_scan = 0\nORDER BY relname;","note":"idx_scan = 0 表示自上次重置统计信息以来该索引未被使用；不要删除 UNIQUE/PK 索引。","danger":"safe"},"mysql":{"command":"SELECT * FROM sys.schema_unused_indexes;","note":"必须启用 performance_schema，且服务器需已运行一段时间；UNIQUE 约束不在统计范围内。","danger":"safe"},"mssql":{"command":"SELECT OBJECT_NAME(i.object_id) AS table_name, i.name AS index_name\nFROM sys.indexes i\nLEFT JOIN sys.dm_db_index_usage_stats s\n  ON i.object_id = s.object_id AND i.index_id = s.index_id\n  AND s.database_id = DB_ID()\nWHERE i.type_desc = 'NONCLUSTERED'\n  AND COALESCE(s.user_seeks + s.user_scans + s.user_lookups, 0) = 0;","note":"usage_stats 在服务器重启时会被重置；需留出足够的观察窗口。","danger":"safe"},"mongodb":{"command":"db.customer.aggregate([{ $indexStats: {} }])\n// Indexes with low/0 accesses.ops are candidates","note":"accesses.ops 统计自 accesses.since 时间戳以来的访问次数；_id 索引无法删除。","danger":"safe"},"redis":{"command":"# Not applicable: core Redis has no secondary indexes.\n# If the RediSearch module is used, check statistics with FT.INFO <index>.","note":"由于数据模型是键值型，不存在传统意义上的索引概念。","danger":"safe"}}},{"id":"cache-hit-ratio","title":"测量缓存命中率","aliases":["cache hit ratio","buffer cache","cache hit","hit ratio","buffer pool hit"],"engines":{"oracle":{"command":"SELECT metric_name, ROUND(value, 2) AS pct\nFROM v$sysmetric\nWHERE metric_name = 'Buffer Cache Hit Ratio'\n  AND intsize_csec = (SELECT MAX(intsize_csec) FROM v$sysmetric);","note":"命中率低并不总是坏事；应结合工作负载解读，而不是孤立看待。","danger":"safe"},"postgres":{"command":"SELECT ROUND(sum(blks_hit) * 100.0 / NULLIF(sum(blks_hit) + sum(blks_read), 0), 2) AS cache_hit_pct\nFROM pg_stat_database;","note":"目标通常为 99%+。若偏低，检查 shared_buffers 和操作系统缓存。","danger":"safe"},"mysql":{"command":"SELECT ROUND((1 - (\n    (SELECT variable_value FROM performance_schema.global_status WHERE variable_name='Innodb_buffer_pool_reads') /\n    (SELECT variable_value FROM performance_schema.global_status WHERE variable_name='Innodb_buffer_pool_read_requests')\n  )) * 100, 2) AS buffer_pool_hit_pct;","note":"reads 是从磁盘的读取，read_requests 是总读取次数。若命中率低，增大 innodb_buffer_pool_size。","danger":"safe"},"mssql":{"command":"SELECT ROUND(\n  100.0 * (SELECT cntr_value FROM sys.dm_os_performance_counters WHERE counter_name='Buffer cache hit ratio')\n        / NULLIF((SELECT cntr_value FROM sys.dm_os_performance_counters WHERE counter_name='Buffer cache hit ratio base'), 0), 2) AS pct;","note":"在现代 SQL Server 上，Page Life Expectancy（PLE）是更有意义的内存压力指标。","danger":"safe"},"mongodb":{"command":"var c = db.serverStatus().wiredTiger.cache;\n({ configuredMB: +(c['maximum bytes configured']/1048576).toFixed(0),\n   inCacheMB:    +(c['bytes currently in the cache']/1048576).toFixed(0),\n   readIntoCache: c['pages read into cache'],\n   evictedPages:  c['unmodified pages evicted'] })","note":"“pages read into cache”和淘汰数量偏高，说明 WiredTiger 缓存过小。","danger":"safe"},"redis":{"command":"INFO stats\n# keyspace_hits / (keyspace_hits + keyspace_misses) = hit ratio","note":"命中率持续偏低说明需要检查 TTL/淘汰设置或访问模式。","danger":"safe"}}}]},"rosetta/backup.json":{"category":"备份与恢复","categoryId":"backup","order":4,"concepts":[{"id":"logical-dump","title":"执行逻辑备份（dump）","aliases":["backup","dump","export","take backup","logical dump","logical backup","pg_dump","mysqldump","mongodump"],"engines":{"oracle":{"command":"expdp system/password DIRECTORY=dpump_dir \\\n  SCHEMAS=hr DUMPFILE=hr_%U.dmp LOGFILE=hr_exp.log PARALLEL=4","note":"使用 Data Pump。必须事先通过 CREATE DIRECTORY 定义 DIRECTORY 对象。物理备份请使用 RMAN。","danger":"safe"},"postgres":{"command":"pg_dump -U postgres -h localhost -Fc -f db.dump mydb\n# -Fc = compressed custom format (restored with pg_restore)","note":"-Fc 支持灵活恢复（选择性表/并行）。整个集群（包括角色）使用 pg_dumpall。","danger":"safe"},"mysql":{"command":"mysqldump -u root -p --single-transaction \\\n  --routines --triggers --events mydb > db.sql","note":"--single-transaction 可在 InnoDB 上获得无锁的一致性备份。大数据量的并行方案：mydumper/myloader 或 MySQL Shell util.dumpInstance()/dumpSchemas()（mysqlpump 已在 8.0.34 中弃用，并在 8.4 中移除）。","danger":"safe"},"mssql":{"command":"BACKUP DATABASE [MyDB]\nTO DISK = N'C:\\backup\\MyDB_full.bak'\nWITH COMPRESSION, CHECKSUM, INIT;","note":"INIT 会覆盖现有文件；追加使用 NOINIT。日志备份使用 BACKUP LOG（FULL 恢复模式下）。","danger":"safe"},"mongodb":{"command":"mongodump --uri=\"mongodb://localhost:27017\" \\\n  --archive=db.archive.gz --gzip --db=mydb","note":"在分片集群上，为保证一致性，应在停止 balancer 后通过 mongos 执行。","danger":"safe"},"redis":{"command":"BGSAVE\n# Takes an RDB snapshot in the background (dump.rdb).\n# Non-blocking copy: redis-cli --rdb /backup/dump.rdb","note":"BGSAVE 通过 fork 在后台运行；SAVE 会阻塞服务器（生产环境勿用）。启用 AOF 时丢失的数据更少。","danger":"safe"}}},{"id":"restore-dump","title":"从备份恢复","aliases":["restore","import","restore backup","restore from backup","recover","pg_restore","mongorestore"],"engines":{"oracle":{"command":"impdp system/password DIRECTORY=dpump_dir \\\n  SCHEMAS=hr DUMPFILE=hr_%U.dmp LOGFILE=hr_imp.log","note":"加载到其他 schema 使用 REMAP_SCHEMA，加载到其他表空间使用 REMAP_TABLESPACE。","danger":"danger"},"postgres":{"command":"# custom format:\npg_restore -U postgres -d target_db --clean --if-exists db.dump\n# plain SQL: psql -U postgres -d target_db -f db.sql","note":"--clean 会删除现有对象。并行恢复使用 -j <n>。","danger":"danger"},"mysql":{"command":"mysql -u root -p target_db < db.sql","note":"目标数据库必须已存在（CREATE DATABASE）。对于大文件，推荐使用 source 命令。","danger":"danger"},"mssql":{"command":"RESTORE DATABASE [MyDB]\nFROM DISK = N'C:\\backup\\MyDB_full.bak'\nWITH REPLACE, RECOVERY;","note":"时间点恢复时，先 WITH NORECOVERY 恢复完整备份，再用 STOPAT 应用日志备份。","danger":"danger"},"mongodb":{"command":"mongorestore --uri=\"mongodb://localhost:27017\" \\\n  --archive=db.archive.gz --gzip --drop","note":"--drop 会在恢复前删除目标集合。选择性恢复使用 --nsInclude。","danger":"danger"},"redis":{"command":"# 1) Stop Redis   2) Copy dump.rdb to the 'dir' location\n# 3) Start Redis (the RDB is loaded automatically at startup)\nredis-cli CONFIG GET dir   # find the target directory","note":"启用 AOF 时，加载的是 AOF 而不是 RDB；要从 RDB 恢复，需以 appendonly no 启动或重建 AOF。","danger":"danger"}}},{"id":"last-backup-status","title":"检查最近一次备份的状态 / 时间","aliases":["last backup","backup status","when was the last backup","backup state","backup history","rpo"],"engines":{"oracle":{"command":"SELECT session_key, input_type, status,\n       start_time, end_time,\n       output_bytes_display\nFROM v$rman_backup_job_details\nORDER BY start_time DESC\nFETCH FIRST 10 ROWS ONLY;","note":"RMAN 历史也可通过 RMAN> LIST BACKUP SUMMARY 查看。","danger":"safe"},"postgres":{"command":"-- WAL archiving health (for physical backup/PITR):\nSELECT last_archived_wal, last_archived_time,\n       failed_count, last_failed_time\nFROM pg_stat_archiver;","note":"逻辑（pg_dump）备份不会记录在数据库内；请检查备份工具/作业的日志。","danger":"safe"},"mysql":{"command":"-- There is no built-in backup record; check the backup tool's log.\n-- For the binlog-based PITR window:\nSHOW BINARY LOGS;","note":"SHOW BINARY LOGS 显示可用的 binlog 文件（PITR 窗口）；备份历史在工具（Xtrabackup 等）的日志中。","danger":"safe"},"mssql":{"command":"SELECT database_name,\n       MAX(backup_finish_date) AS last_backup,\n       CASE type WHEN 'D' THEN 'Full' WHEN 'I' THEN 'Diff'\n                 WHEN 'L' THEN 'Log' END AS backup_type\nFROM msdb.dbo.backupset\nGROUP BY database_name, type\nORDER BY database_name;","note":"msdb.dbo.backupset 记录 SQL Server 执行的每一次备份。","danger":"safe"},"mongodb":{"command":"// There is no built-in backup record. PITR window = oplog duration:\nrs.printReplicationInfo()  // oplog first/last event times","note":"oplog 窗口决定了从副本集最多能往前做多久的 PITR；备份历史在工具的日志中。","danger":"safe"},"redis":{"command":"INFO persistence\n# check the rdb_last_save_time (epoch), rdb_last_bgsave_status,\n# aof_last_bgrewrite_status lines","note":"rdb_last_save_time 给出最近一次成功 RDB 保存的时间（Unix epoch）；rdb_changes_since_last_save 显示尚未备份的写入次数。","danger":"safe"}}}]},"rosetta/security.json":{"category":"用户与权限","categoryId":"security","order":5,"concepts":[{"id":"list-users","title":"列出用户 / 角色","aliases":["list users","show users","list users","roles","who has access","accounts"],"engines":{"oracle":{"command":"SELECT username, account_status, default_tablespace, created\nFROM dba_users\nORDER BY username;","note":"account_status 显示 LOCKED/EXPIRED 状态。角色请使用 dba_roles。","danger":"safe"},"postgres":{"command":"SELECT rolname, rolsuper, rolcanlogin, rolvaliduntil\nFROM pg_roles\nORDER BY rolname;\n-- Shortcut (psql): \\du","note":"在 PostgreSQL 中，用户是带有 LOGIN 属性的角色；通过 rolcanlogin 区分。","danger":"safe"},"mysql":{"command":"SELECT user, host, account_locked, password_expired\nFROM mysql.user\nORDER BY user;","note":"在 MySQL 中，身份由用户 + 主机组合确定（'app'@'%' 与 'app'@'localhost' 是不同的账户）。","danger":"safe"},"mssql":{"command":"-- Server logins:\nSELECT name, type_desc, is_disabled FROM sys.server_principals\nWHERE type IN ('S','U','G');\n-- Database users:\nSELECT name, type_desc FROM sys.database_principals\nWHERE type IN ('S','U','G');","note":"登录名（login）是服务器级的，用户是数据库级的；两者通过 SID 映射。","danger":"safe"},"mongodb":{"command":"// Users in the current database:\ndb.getUsers()\n// All users in the system: use admin; db.system.users.find({}, {user:1, db:1, roles:1})","note":"用户定义在其创建所在的数据库中（authenticationDatabase）。","danger":"safe"},"redis":{"command":"ACL LIST\n# Names only: ACL USERS\n# Your own identity: ACL WHOAMI","note":"Redis 6+ 的 ACL 体系。在此之前只有一个 'default' 用户和 requirepass。","danger":"safe"}}},{"id":"create-user-grant","title":"创建用户并授予权限","aliases":["create user","grant","add user","create user","grant privilege","grant permission","new user"],"engines":{"oracle":{"command":"CREATE USER app IDENTIFIED BY \"Strong#Pass1\";\nGRANT CREATE SESSION TO app;\nGRANT SELECT, INSERT, UPDATE ON hr.employee TO app;","note":"没有 CREATE SESSION 权限，用户无法连接。设置配额：ALTER USER app QUOTA 100M ON users;","danger":"caution"},"postgres":{"command":"CREATE ROLE app LOGIN PASSWORD 'Strong#Pass1';\nGRANT CONNECT ON DATABASE mydb TO app;\nGRANT USAGE ON SCHEMA public TO app;\nGRANT SELECT, INSERT, UPDATE ON ALL TABLES IN SCHEMA public TO app;","note":"针对将来创建的表：ALTER DEFAULT PRIVILEGES IN SCHEMA public GRANT SELECT ON TABLES TO app;","danger":"caution"},"mysql":{"command":"CREATE USER 'app'@'%' IDENTIFIED BY 'Strong#Pass1';\nGRANT SELECT, INSERT, UPDATE ON mydb.* TO 'app'@'%';","note":"'%' 允许来自任意主机的连接；出于安全考虑，尽可能指定具体的 IP/主机。CREATE USER/GRANT 会立即生效；只有直接对授权表执行了 DML 时才需要 FLUSH PRIVILEGES。","danger":"caution"},"mssql":{"command":"CREATE LOGIN app WITH PASSWORD = 'Strong#Pass1';\nUSE mydb;\nCREATE USER app FOR LOGIN app;\nGRANT SELECT, INSERT, UPDATE ON SCHEMA::dbo TO app;","note":"登录名是服务器范围的；用户需在每个数据库中分别创建。添加角色：ALTER ROLE db_datareader ADD MEMBER app;","danger":"caution"},"mongodb":{"command":"db.createUser({\n  user: \"app\",\n  pwd: \"Strong#Pass1\",\n  roles: [ { role: \"readWrite\", db: \"mydb\" } ]\n})","note":"创建用户时所在的 db 即成为其认证数据库。内置角色：read、readWrite、dbAdmin、clusterAdmin……","danger":"caution"},"redis":{"command":"ACL SETUSER app on >Strong#Pass1 ~app:* +@read +@write\n# To persist: ACL SAVE if an aclfile is defined, otherwise CONFIG REWRITE (users are written to redis.conf)","note":"on = 启用，>password 添加密码，~app:* 是键模式，+@read/+@write 是命令类别。若未持久化，重启后会丢失：ACL SAVE 仅在配置了 aclfile 时可用，否则返回错误；这种情况下使用 CONFIG REWRITE。","danger":"caution"}}},{"id":"show-privileges","title":"显示用户的权限","aliases":["show grants","privileges","show privileges","who can access what","permissions","roles of user"],"engines":{"oracle":{"command":"SELECT privilege FROM dba_sys_privs WHERE grantee = 'APP'\nUNION ALL SELECT granted_role FROM dba_role_privs WHERE grantee = 'APP'\nUNION ALL SELECT privilege||' ON '||owner||'.'||table_name\n          FROM dba_tab_privs WHERE grantee = 'APP';","note":"grantee 以大写形式存储。对于通过角色间接授予的权限，还需遍历 role_role_privs。","danger":"safe"},"postgres":{"command":"SELECT grantee, table_schema, table_name, privilege_type\nFROM information_schema.role_table_grants\nWHERE grantee = 'app';\n-- Role memberships (direct): \\drg app (PG16+; earlier \\du app)","note":"角色成员关系（继承）存储在 pg_auth_members 中；\\du/\\drg 只显示直接成员关系，间接成员关系使用 pg_has_role('app', '<role>', 'MEMBER')。","danger":"safe"},"mysql":{"command":"SHOW GRANTS FOR 'app'@'%';","note":"对于角色（MySQL 8），先执行 SET ROLE ALL，或使用 SHOW GRANTS FOR 'app'@'%' USING 'role_name';","danger":"safe"},"mssql":{"command":"SELECT dp.name AS principal, p.permission_name,\n       p.state_desc, o.name AS object\nFROM sys.database_permissions p\nJOIN sys.database_principals dp ON p.grantee_principal_id = dp.principal_id\nLEFT JOIN sys.objects o ON p.major_id = o.object_id\nWHERE dp.name = 'app';","note":"对于通过角色授予的权限，还需检查 sys.database_role_members 中的成员关系。","danger":"safe"},"mongodb":{"command":"db.getUser(\"app\", { showPrivileges: true, showCredentials: false })","note":"showPrivileges 还会列出角色展开后的（有效）权限。","danger":"safe"},"redis":{"command":"ACL GETUSER app","note":"返回允许的命令、键模式（~）、pub/sub 频道（&）和密码哈希。","danger":"safe"}}},{"id":"change-password","title":"修改用户密码","aliases":["change password","reset password","change password","reset password","alter password"],"engines":{"oracle":{"command":"ALTER USER app IDENTIFIED BY \"New#Pass2\";","note":"解锁被锁定的账户：ALTER USER app ACCOUNT UNLOCK; 如果密码已过期，同一命令会将其重置。","danger":"caution"},"postgres":{"command":"ALTER ROLE app WITH PASSWORD 'New#Pass2';","note":"为避免密码出现在日志中，\\password app（psql）元命令会在客户端对密码进行哈希。","danger":"caution"},"mysql":{"command":"ALTER USER 'app'@'%' IDENTIFIED BY 'New#Pass2';","note":"确保选对主机；'app'@'localhost' 是另一个账户。ALTER USER 立即生效；无需 FLUSH PRIVILEGES。","danger":"caution"},"mssql":{"command":"ALTER LOGIN app WITH PASSWORD = 'New#Pass2';\n-- For a user changing their own password: add OLD_PASSWORD","note":"MUST_CHANGE 可强制首次登录时修改密码：WITH PASSWORD='...' MUST_CHANGE, CHECK_POLICY=ON。","danger":"caution"},"mongodb":{"command":"db.changeUserPassword(\"app\", \"New#Pass2\")","note":"确保已切换到该用户的认证数据库（use mydb）。","danger":"caution"},"redis":{"command":"# Clear old passwords and set the new one:\nACL SETUSER app resetpass >New#Pass2\n# Persistence: ACL SAVE if an aclfile is defined, otherwise CONFIG REWRITE","note":"resetpass 会移除所有现有密码；> 只是添加一个密码。持久化方面：若使用 aclfile 则执行 ACL SAVE；若用户定义在 redis.conf 中则执行 CONFIG REWRITE（没有 aclfile 时 ACL SAVE 会返回错误）。","danger":"caution"}}}]},"rosetta/maintenance.json":{"category":"维护与管理","categoryId":"maintenance","order":6,"concepts":[{"id":"update-statistics","title":"更新统计信息（供优化器使用）","aliases":["update statistics","analyze","update stats","gather stats","optimizer stats","analyze for planner"],"engines":{"oracle":{"command":"EXEC DBMS_STATS.GATHER_TABLE_STATS('HR', 'EMPLOYEE');\n-- Whole schema: EXEC DBMS_STATS.GATHER_SCHEMA_STATS('HR');","note":"AUTO 任务通常在夜间收集统计信息；出现问题时可手动收集。推荐使用 ESTIMATE_PERCENT=>AUTO_SAMPLE_SIZE。","danger":"safe"},"postgres":{"command":"ANALYZE customer;\n-- Both cleanup and analyze: VACUUM ANALYZE customer;","note":"Autovacuum 会自动完成这项工作；在突发的大批量数据加载后手动执行 ANALYZE 可得到准确的执行计划。","danger":"safe"},"mysql":{"command":"ANALYZE TABLE customer;","note":"在 InnoDB 上会短暂持有读锁；大表应在低负载时段执行。","danger":"safe"},"mssql":{"command":"UPDATE STATISTICS dbo.customer WITH FULLSCAN;\n-- Whole database: EXEC sp_updatestats;","note":"FULLSCAN 最准确但开销最大；大表上可优先使用抽样（SAMPLE）。","danger":"safe"},"mongodb":{"command":"// There are no manual statistics; the planner measures at runtime.\n// To clear a stale plan cache:\ndb.customer.getPlanCache().clear()","note":"MongoDB 不做成本估算，而是进行实际试运行（plan racing）；无需手动维护统计信息。","danger":"safe"},"redis":{"command":"# Not applicable: Redis has no query optimizer / statistics.","note":"命令复杂度是固定的；性能取决于数据结构的选择。","danger":"safe"}}},{"id":"rebuild-index","title":"重建 / 重组索引","aliases":["rebuild index","reindex","refresh index","reorganize","index maintenance","defrag index"],"engines":{"oracle":{"command":"ALTER INDEX customer_city_idx REBUILD ONLINE;","note":"ONLINE 可在不锁表的情况下重建（Enterprise Edition）。移动到其他表空间：... REBUILD TABLESPACE new_ts。","danger":"caution"},"postgres":{"command":"REINDEX INDEX CONCURRENTLY customer_city_idx;\n-- Whole table: REINDEX TABLE CONCURRENTLY customer;","note":"CONCURRENTLY（PG 12+）在不阻塞表写入的情况下重建；速度较慢但安全。","danger":"caution"},"mysql":{"command":"-- Rebuild InnoDB indexes + table:\nALTER TABLE customer ENGINE=InnoDB, ALGORITHM=INPLACE, LOCK=NONE;\n-- Alternative: OPTIMIZE TABLE customer;","note":"LOCK=NONE 允许并发写入；并非所有 ALTER 都能使用 INPLACE。","danger":"caution"},"mssql":{"command":"-- Rebuild for high fragmentation:\nALTER INDEX ALL ON dbo.customer REBUILD WITH (ONLINE = ON);\n-- Lighter option for low fragmentation:\n-- ALTER INDEX ALL ON dbo.customer REORGANIZE;","note":"常用阈值：碎片率 5-30% 执行 REORGANIZE，>30% 执行 REBUILD。ONLINE 是 Enterprise 功能。","danger":"caution"},"mongodb":{"command":"db.customer.reIndex()","note":"在前台重建所有索引并锁定集合。在 6.0+ 中仅能在独立实例上运行（副本集成员上不允许）；在副本集上，应逐节点（滚动）通过 dropIndex + createIndex 重建单个索引。","danger":"danger"},"redis":{"command":"# Not applicable (core). If RediSearch is used:\n# FT.ALTER / rebuilding is done per the module documentation.","note":"键值模型没有传统意义上的索引维护。","danger":"safe"}}},{"id":"reclaim-space","title":"回收空闲空间（shrink / vacuum / compact）","aliases":["reclaim space","vacuum","shrink","compact","free up space","clean up bloat","bloat"],"engines":{"oracle":{"command":"ALTER TABLE customer ENABLE ROW MOVEMENT;\nALTER TABLE customer SHRINK SPACE;","note":"SHRINK 会降低高水位线（HWM）。若要同时收缩索引，使用 SHRINK SPACE CASCADE。","danger":"caution"},"postgres":{"command":"-- Online, light locking (does not return space to the OS):\nVACUUM (VERBOSE, ANALYZE) customer;\n-- Return space to the OS (locks the table!):\n-- VACUUM FULL customer;","note":"VACUUM FULL 会获取 AccessExclusiveLock 并重写整张表；不要在高峰时段执行。替代方案：pg_repack。","danger":"caution"},"mysql":{"command":"OPTIMIZE TABLE customer;","note":"在 InnoDB 上会重建表并释放 data_free 空间；大表上耗时很长。","danger":"caution"},"mssql":{"command":"-- Generally NOT RECOMMENDED (causes fragmentation). If really needed:\nDBCC SHRINKFILE (N'MyDB_data', 1024);  -- target 1024 MB","note":"收缩数据文件会导致索引碎片；之后需对索引执行 REBUILD。对于日志，先执行备份。","danger":"danger"},"mongodb":{"command":"db.runCommand({ compact: \"customer\" })","note":"在 WiredTiger 上会尝试将空闲空间归还给操作系统（并非总能成功）。在 4.4+ 中不会阻塞读写，只阻塞同一集合上的某些 DDL 操作；由于会产生大量 IO，在从节点上并在维护窗口内执行更安全。","danger":"danger"},"redis":{"command":"MEMORY PURGE\n# Tries to return jemalloc fragmentation to the OS","note":"activedefrag yes 设置可在线自动整理碎片；MEMORY DOCTOR 会给出建议。","danger":"caution"}}},{"id":"version-uptime","title":"显示版本和运行时间","aliases":["version","uptime","server version","how long has it been running","server version","days since startup"],"engines":{"oracle":{"command":"SELECT banner_full FROM v$version;\nSELECT startup_time, ROUND(SYSDATE - startup_time, 1) AS up_days\nFROM v$instance;","note":"v$instance 还提供 status、host_name 等实例详情。","danger":"safe"},"postgres":{"command":"SELECT version();\nSELECT pg_postmaster_start_time() AS started,\n       now() - pg_postmaster_start_time() AS uptime;","note":"server_version_num 便于进行数值比较（例如 160002 = 16.2）。","danger":"safe"},"mysql":{"command":"SELECT VERSION();\nSHOW GLOBAL STATUS LIKE 'Uptime';  -- seconds","note":"Uptime 以秒为单位；除以 86400 得到天数。","danger":"safe"},"mssql":{"command":"SELECT @@VERSION;\nSELECT sqlserver_start_time,\n       DATEDIFF(DAY, sqlserver_start_time, GETDATE()) AS up_days\nFROM sys.dm_os_sys_info;","note":"SERVERPROPERTY('ProductVersion') 和 ('Edition') 给出版本/版次详情。","danger":"safe"},"mongodb":{"command":"db.version()\ndb.serverStatus().uptime   // seconds","note":"serverStatus() 还包含 host、process 和 connections 信息。","danger":"safe"},"redis":{"command":"INFO server\n# redis_version and uptime_in_seconds / uptime_in_days lines","note":"INFO server 还提供 os、run_id 和 tcp_port 信息。","danger":"safe"}}},{"id":"show-config-param","title":"查看配置参数","aliases":["show config","parameter","show setting","config parameter","setting","memory setting","variable"],"engines":{"oracle":{"command":"SHOW PARAMETER sga_target;\n-- or: SELECT name, value, isdefault FROM v$parameter WHERE name = 'sga_target';","note":"修改方法：ALTER SYSTEM SET ... SCOPE=BOTH;（部分参数只能 SCOPE=SPFILE + 重启）。","danger":"safe"},"postgres":{"command":"SHOW shared_buffers;\n-- Details: SELECT name, setting, unit, context FROM pg_settings WHERE name = 'shared_buffers';","note":"context 列说明修改是否需要重启或重新加载（SELECT pg_reload_conf()）。","danger":"safe"},"mysql":{"command":"SHOW VARIABLES LIKE 'innodb_buffer_pool_size';","note":"注意 GLOBAL 与 SESSION 的区别：SHOW GLOBAL VARIABLES 显示服务器范围的值。","danger":"safe"},"mssql":{"command":"SELECT name, value, value_in_use, is_dynamic\nFROM sys.configurations\nWHERE name LIKE '%max server memory%';","note":"修改方法：sp_configure + RECONFIGURE；is_dynamic=0 的选项需要重启。","danger":"safe"},"mongodb":{"command":"db.adminCommand({ getParameter: 1, cursorTimeoutMillis: 1 })\n// All startup options: db.adminCommand({ getCmdLineOpts: 1 })","note":"可在运行时修改的参数使用 setParameter；持久化设置位于 mongod.conf 文件中。","danger":"safe"},"redis":{"command":"CONFIG GET maxmemory\n# Wildcard: CONFIG GET max*","note":"运行时修改使用 CONFIG SET；持久化使用 CONFIG REWRITE（写入 redis.conf）。","danger":"safe"}}}]},"rosetta/schema.json":{"category":"Schema 与对象","categoryId":"schema","order":7,"concepts":[{"id":"list-tables","title":"列出表 / 集合","aliases":["list tables","show tables","list all tables","table list","list collections","collections","which tables exist"],"engines":{"oracle":{"command":"SELECT table_name FROM user_tables ORDER BY table_name;\n-- All schemas: SELECT owner, table_name FROM dba_tables ORDER BY owner, table_name;","note":"user_tables 返回当前 schema 中的表，all_tables 返回可访问的表。","danger":"safe"},"postgres":{"command":"SELECT table_schema, table_name\nFROM information_schema.tables\nWHERE table_type = 'BASE TABLE' AND table_schema NOT IN ('pg_catalog','information_schema')\nORDER BY 1, 2;\n-- psql shortcut: \\dt *.*","note":"仅 public schema：WHERE table_schema = 'public'。","danger":"safe"},"mysql":{"command":"SHOW TABLES;\n-- Another schema: SHOW TABLES FROM mydb;","note":"查看详情：SELECT table_name, table_rows FROM information_schema.tables WHERE table_schema = DATABASE();","danger":"safe"},"mssql":{"command":"SELECT s.name AS schema_name, t.name AS table_name\nFROM sys.tables t\nJOIN sys.schemas s ON t.schema_id = s.schema_id\nORDER BY s.name, t.name;","note":"快捷方式：SELECT * FROM INFORMATION_SCHEMA.TABLES WHERE TABLE_TYPE = 'BASE TABLE';","danger":"safe"},"mongodb":{"command":"show collections\n// or: db.getCollectionNames()","note":"如需同时查看系统集合：db.getCollectionInfos()。","danger":"safe"},"redis":{"command":"SCAN 0 MATCH * COUNT 100\n# Continue with SCAN <cursor> ... using the returned cursor","note":"Redis 没有“表”；列出的是键。生产环境中切勿使用 KEYS *（会阻塞）；SCAN 基于游标。","danger":"caution"}}},{"id":"describe-table","title":"显示表的列 / 结构（describe）","aliases":["describe","desc table","columns","table columns","table structure","show schema","field types","fields"],"engines":{"oracle":{"command":"SELECT column_name, data_type, data_length, nullable\nFROM user_tab_columns\nWHERE table_name = 'EMPLOYEE'\nORDER BY column_id;\n-- Shortcut (SQL*Plus): DESC employee","note":"表名以大写形式存储（如果创建时未使用引号）。","danger":"safe"},"postgres":{"command":"SELECT column_name, data_type, is_nullable, column_default\nFROM information_schema.columns\nWHERE table_name = 'customer'\nORDER BY ordinal_position;\n-- psql shortcut: \\d customer","note":"\\d 还会显示索引、约束和外键。","danger":"safe"},"mysql":{"command":"DESCRIBE customer;\n-- or: SHOW COLUMNS FROM customer;","note":"完整定义使用 SHOW CREATE TABLE customer;（参见下文“生成 DDL”）。","danger":"safe"},"mssql":{"command":"SELECT c.name, t.name AS type, c.max_length, c.is_nullable\nFROM sys.columns c\nJOIN sys.types t ON c.user_type_id = t.user_type_id\nWHERE c.object_id = OBJECT_ID('dbo.customer')\nORDER BY c.column_id;\n-- Shortcut: EXEC sp_help 'dbo.customer';","note":"sp_help 除列之外还会列出索引和约束。","danger":"safe"},"mongodb":{"command":"// The schema is flexible; infer it from a sample document:\ndb.customer.findOne()\n// For field coverage: db.customer.aggregate([{ $sample: { size: 100 } }])","note":"MongoDB 没有固定 schema；字段可因文档而异。如果定义了 $jsonSchema 验证器，可通过 db.getCollectionInfos 查看。","danger":"safe"},"redis":{"command":"TYPE mykey        # string | list | set | hash | zset | stream\nOBJECT ENCODING mykey   # internal encoding","note":"没有固定 schema；每个键的类型和编码需分别查询。hash 字段使用 HGETALL。","danger":"safe"}}},{"id":"list-indexes","title":"列出表的索引","aliases":["list indexes","show indexes","show table indexes","which indexes","table indexes","getindexes"],"engines":{"oracle":{"command":"SELECT index_name, column_name, column_position\nFROM user_ind_columns\nWHERE table_name = 'EMPLOYEE'\nORDER BY index_name, column_position;","note":"关联 user_indexes 可获取索引类型/唯一性。","danger":"safe"},"postgres":{"command":"SELECT indexname, indexdef\nFROM pg_indexes\nWHERE tablename = 'customer';","note":"indexdef 给出完整的 CREATE INDEX 语句；在 psql 中 \\d customer 也会列出索引。","danger":"safe"},"mysql":{"command":"SHOW INDEX FROM customer;","note":"Non_unique、Seq_in_index 和 Cardinality 列显示索引结构和选择性。","danger":"safe"},"mssql":{"command":"EXEC sp_helpindex 'dbo.customer';\n-- Details: join sys.indexes + sys.index_columns","note":"sp_helpindex 返回索引名称、类型和列；筛选列/包含列请使用 sys 视图。","danger":"safe"},"mongodb":{"command":"db.customer.getIndexes()","note":"返回每个索引的键以及唯一性、TTL/partial 等选项。","danger":"safe"},"redis":{"command":"# Not applicable (core). If the RediSearch module is present:\n# FT._LIST        (all indexes)\n# FT.INFO idx     (details of one index)","note":"键值模型没有二级索引；只有通过 RediSearch/RedisJSON 等模块才能获得。","danger":"safe"}}},{"id":"create-index","title":"创建索引","aliases":["create index","add index","build index","new index","createindex"],"engines":{"oracle":{"command":"CREATE INDEX customer_city_idx ON customer(city) ONLINE;","note":"ONLINE 可在不锁表的情况下创建。唯一索引使用 CREATE UNIQUE INDEX；复合索引使用 (a, b)。","danger":"caution"},"postgres":{"command":"CREATE INDEX CONCURRENTLY customer_city_idx ON customer(city);","note":"CONCURRENTLY 不会阻塞表写入，但不能在事务块内运行，且速度较慢。","danger":"caution"},"mysql":{"command":"CREATE INDEX customer_city_idx ON customer(city);\n-- Lock-free: ... ALGORITHM=INPLACE, LOCK=NONE;","note":"InnoDB 可在线添加大多数索引；大表上使用 LOCK=NONE 允许写入。","danger":"caution"},"mssql":{"command":"CREATE NONCLUSTERED INDEX IX_customer_city\nON dbo.customer (city)\nWITH (ONLINE = ON);","note":"ONLINE 是 Enterprise 功能。覆盖列使用 INCLUDE (col1, col2)。","danger":"caution"},"mongodb":{"command":"db.customer.createIndex({ city: 1 })\n// Unique: { city: 1 }, { unique: true }\n// Built in the background without blocking (default behavior in 4.2+)","note":"1 为升序，-1 为降序。在大集合上，先在从节点上创建可减轻负载。","danger":"caution"},"redis":{"command":"# Not applicable (core). If RediSearch is present:\n# FT.CREATE idx ON HASH PREFIX 1 customer: SCHEMA city TAG","note":"二级索引只能通过 RediSearch 模块获得；核心 Redis 按键访问。","danger":"safe"}}},{"id":"show-ddl","title":"生成对象的 DDL（CREATE 语句）","aliases":["show ddl","get ddl","create statement","generate ddl","table definition","show create table","generate script"],"engines":{"oracle":{"command":"SELECT DBMS_METADATA.GET_DDL('TABLE', 'EMPLOYEE', 'HR') FROM dual;","note":"索引使用 'INDEX'，包使用 'PACKAGE'，依此类推。使用 SET LONG 100000 防止长输出被截断。","danger":"safe"},"postgres":{"command":"-- There is no single function in SQL; from the shell:\npg_dump -t public.customer --schema-only mydb","note":"在 psql 中，\\d customer 以易读形式显示结构；完整 DDL 使用 pg_dump --schema-only。","danger":"safe"},"mysql":{"command":"SHOW CREATE TABLE customer\\G","note":"一条命令即可返回完整的 CREATE TABLE（列、索引、引擎、字符集）。","danger":"safe"},"mssql":{"command":"-- T-SQL has no single built-in command. Options:\n-- 1) SSMS: right-click > Script Table as > CREATE To\n-- 2) EXEC sp_help 'dbo.customer';  (structure summary)\n-- 3) mssql-scripter tool (command line)","note":"查看存储过程/视图主体：SELECT OBJECT_DEFINITION(OBJECT_ID('dbo.usp_name'));","danger":"safe"},"mongodb":{"command":"// There is no DDL (schemaless). View collection options/validator:\ndb.getCollectionInfos({ name: \"customer\" })","note":"如果定义了 options.validator（$jsonSchema），它就是唯一的“schema 定义”；索引需通过 getIndexes() 单独获取。","danger":"safe"},"redis":{"command":"# Not applicable: there is no schema/DDL concept.","note":"数据结构在应用代码中定义；持久化存储在 RDB/AOF 文件中。","danger":"safe"}}}]},"rosetta/replication.json":{"category":"复制与高可用","categoryId":"replication","order":8,"concepts":[{"id":"replication-status","title":"显示复制状态和延迟","aliases":["replication status","replica lag","replication status","lag","slave lag","standby lag","how far behind"],"engines":{"oracle":{"command":"-- Role and mode:\nSELECT database_role, open_mode FROM v$database;\n-- Data Guard lag:\nSELECT name, value, unit FROM v$dataguard_stats\nWHERE name LIKE '%lag%';","note":"Apply lag 和 transport lag 是 Data Guard 的主要延迟指标；在备库上执行。","danger":"safe"},"postgres":{"command":"-- On the PRIMARY:\nSELECT client_addr, state, sync_state,\n       write_lag, flush_lag, replay_lag\nFROM pg_stat_replication;\n-- On the STANDBY:\nSELECT now() - pg_last_xact_replay_timestamp() AS replay_lag;","note":"replay_lag 是回放延迟；sync_state = 'sync' 表示同步副本。","danger":"safe"},"mysql":{"command":"SHOW REPLICA STATUS\\G\n-- Before 8.0.22: SHOW SLAVE STATUS\\G","note":"Seconds_Behind_Source 给出延迟；Replica_IO_Running 和 Replica_SQL_Running 必须为 'Yes'。SHOW SLAVE STATUS 在 8.0.22–8.3 中是别名，并已在 8.4 中移除。","danger":"safe"},"mssql":{"command":"SELECT ar.replica_server_name, drs.synchronization_state_desc,\n       drs.log_send_queue_size, drs.redo_queue_size\nFROM sys.dm_hadr_database_replica_states drs\nJOIN sys.availability_replicas ar ON drs.replica_id = ar.replica_id;","note":"适用于 Always On。redo_queue_size 较大表示辅助副本正在落后。","danger":"safe"},"mongodb":{"command":"rs.printSecondaryReplicationInfo()\n// Detailed status: rs.status()","note":"显示每个从节点落后主节点多少秒；比较的是 optimeDate 值。","danger":"safe"},"redis":{"command":"INFO replication\n# Check the role, connected_slaves, master_repl_offset,\n# and slaveN:...,offset=... lines","note":"master_repl_offset 与 slave offset 之差即为延迟；master_link_status 必须为 'up'。","danger":"safe"}}},{"id":"list-replicas","title":"列出副本 / 集群节点","aliases":["list replicas","show replicas","list replicas","nodes","cluster members","slaves","standby list"],"engines":{"oracle":{"command":"-- Standby destinations:\nSELECT dest_id, destination, status, target\nFROM v$archive_dest\nWHERE status <> 'INACTIVE';\n-- RAC instances: SELECT inst_id, instance_name, status FROM gv$instance;","note":"Data Guard 配置也可通过 DGMGRL> SHOW CONFIGURATION 查看。","danger":"safe"},"postgres":{"command":"SELECT application_name, client_addr, state, sync_state\nFROM pg_stat_replication;","note":"仅在 PRIMARY 上列出已连接的副本；每行代表一个备库。","danger":"safe"},"mysql":{"command":"SHOW REPLICAS;\n-- Before 8.0.22: SHOW SLAVE HOSTS;","note":"在源服务器上执行；显示设置了 report-host 的副本。SHOW SLAVE HOSTS 在 8.0.22–8.3 中是别名，并已在 8.4 中移除。","danger":"safe"},"mssql":{"command":"SELECT ar.replica_server_name, ars.role_desc,\n       ars.connected_state_desc, ars.synchronization_health_desc\nFROM sys.dm_hadr_availability_replica_states ars\nJOIN sys.availability_replicas ar ON ars.replica_id = ar.replica_id;","note":"role_desc 区分 PRIMARY/SECONDARY；synchronization_health_desc 给出整体健康状况。","danger":"safe"},"mongodb":{"command":"rs.status().members.map(m => ({ name: m.name, state: m.stateStr, health: m.health }))","note":"stateStr 返回 PRIMARY/SECONDARY/ARBITER；health 为 1 表示健康。","danger":"safe"},"redis":{"command":"INFO replication\n# If Sentinel is used:\n# SENTINEL replicas <master-name>","note":"slave0、slave1... 行给出每个副本的 ip/port/offset。","danger":"safe"}}},{"id":"promote-failover","title":"提升副本 / 故障转移（promote）","aliases":["promote","failover","switchover","promote","role change","stepdown","make master","make primary"],"engines":{"oracle":{"command":"-- Data Guard broker (DGMGRL):\nSWITCHOVER TO 'standby_db';     -- planned, no data loss\n-- FAILOVER TO 'standby_db';     -- on loss of the primary, emergency","note":"SWITCHOVER 是计划内的角色切换（主备切换）；FAILOVER（故障转移）在主库不可达时执行，存在数据丢失风险。","danger":"danger"},"postgres":{"command":"-- Promote the standby to primary:\nSELECT pg_promote();\n-- or from the shell: pg_ctl promote -D /var/lib/pgsql/data","note":"提升之前，务必确认旧主库确实已停止（脑裂风险）。提升之后，要将旧主库作为新主库的副本重新加入需要 pg_rewind；为此必须启用 wal_log_hints = on 或数据校验和。","danger":"danger"},"mysql":{"command":"-- On the replica (manual):\n-- 1) Stop receiving new events, wait until the received relay log is fully applied:\nSTOP REPLICA IO_THREAD;\nSELECT RECEIVED_TRANSACTION_SET FROM performance_schema.replication_connection_status;\nSELECT @@GLOBAL.gtid_executed;  -- the received set must be fully contained here\n-- 2) Remove replication and enable writes:\nSTOP REPLICA;\nRESET REPLICA ALL;\nSET GLOBAL read_only = OFF;  -- also turns off super_read_only\n-- 3) Fence the old source against writes, then redirect the application.","note":"如果在 relay log 完全应用之前执行 RESET REPLICA ALL，已接收但未应用的事务将丢失。副本通常启用 read_only/super_read_only 运行；关闭之前不接受写入。如果旧源库仍可写，存在脑裂风险。该语法适用于 8.0.22+（之前为 STOP SLAVE / RESET SLAVE ALL；旧形式已在 8.4 中移除）。生产环境中要实现自动且安全的故障转移，优先选择 InnoDB Cluster/Group Replication、Orchestrator 或 MHA。","danger":"danger"},"mssql":{"command":"-- On the target secondary replica (synchronous):\nALTER AVAILABILITY GROUP [AG_Name] FAILOVER;\n-- Force it, with risk of data loss:\n-- ALTER AVAILABILITY GROUP [AG_Name] FORCE_FAILOVER_ALLOW_DATA_LOSS;","note":"向同步副本进行计划内故障转移不会丢失数据；FORCE 仅用于灾难场景。","danger":"danger"},"mongodb":{"command":"// Trigger an election on the current primary:\nrs.stepDown(60)   // step down from primary for 60 s","note":"stepDown 会发起新的选举；若要偏向特定节点，调整成员的 priority 值。","danger":"danger"},"redis":{"command":"# Make the replica an independent primary:\nREPLICAOF NO ONE\n# With Sentinel (recommended): SENTINEL FAILOVER <master-name>","note":"如果使用 Sentinel/Cluster，请使用 SENTINEL FAILOVER 而不是手动 REPLICAOF；否则存在脑裂风险。","danger":"danger"}}},{"id":"open-transactions","title":"查找未提交 / 长时间运行的事务","aliases":["open transactions","long running transaction","open transaction","long transaction","idle in transaction","oldest transaction","undo"],"engines":{"oracle":{"command":"SELECT s.sid, s.serial#, s.username,\n       t.start_time, t.used_ublk AS undo_blocks\nFROM v$transaction t\nJOIN v$session s ON t.ses_addr = s.saddr\nORDER BY t.start_time;","note":"used_ublk 较高表示大事务正在消耗大量 undo；它可能正在等待 commit/rollback。","danger":"safe"},"postgres":{"command":"SELECT pid, usename, state,\n       now() - xact_start AS xact_age, query\nFROM pg_stat_activity\nWHERE xact_start IS NOT NULL\nORDER BY xact_start;","note":"处于 'idle in transaction' 状态的长事务会阻碍 VACUUM 和元组清理；需加以监控。","danger":"safe"},"mysql":{"command":"SELECT trx_id, trx_state, trx_started,\n       TIMESTAMPDIFF(SECOND, trx_started, NOW()) AS age_sec,\n       trx_mysql_thread_id\nFROM information_schema.innodb_trx\nORDER BY trx_started;","note":"使用 trx_mysql_thread_id 与 PROCESSLIST 关联，必要时将其 KILL。","danger":"safe"},"mssql":{"command":"DBCC OPENTRAN;\n-- Detailed:\nSELECT st.session_id, dt.database_transaction_begin_time\nFROM sys.dm_tran_database_transactions dt\nJOIN sys.dm_tran_session_transactions st ON st.transaction_id = dt.transaction_id\nWHERE dt.database_id = DB_ID()\nORDER BY dt.database_transaction_begin_time;","note":"长时间未提交的事务会阻止日志截断并使日志文件膨胀。","danger":"safe"},"mongodb":{"command":"db.adminCommand({\n  currentOp: 1,\n  \"transaction\": { $exists: true }\n})","note":"长时间运行的多文档事务在超过 transactionLifetimeLimitSeconds（默认 60 s）后会被自动中止。","danger":"safe"},"redis":{"command":"# Not applicable: MULTI/EXEC transactions are short and atomic; they do not stay open.","note":"Redis 事务不持有锁；长时间运行的单个命令请检查 SLOWLOG。","danger":"safe"}}}]},"rosetta/connect.json":{"category":"连接与 CLI","categoryId":"connect","order":9,"concepts":[{"id":"connect-cli","title":"从命令行连接（CLI / 连接字符串）","aliases":["connect","connection string","connect","how do i connect","cli connect","login","dsn","uri"],"engines":{"oracle":{"command":"sqlplus app_user/password@//host:1521/service_name\n# As SYSDBA: sqlplus sys/password@//host:1521/orcl AS SYSDBA","note":"//host:port/service 是 'Easy Connect' 格式；也可使用 tnsnames.ora 中的 TNS 别名。","danger":"safe"},"postgres":{"command":"psql -h host -p 5432 -U postgres -d mydb\n# With a URI: psql \"postgresql://app_user:password@host:5432/mydb?sslmode=require\"","note":"为避免在命令行输入密码，使用 ~/.pgpass 文件或 PGPASSWORD 环境变量。","danger":"safe"},"mysql":{"command":"mysql -h host -P 3306 -u root -p mydb\n# Require TLS: --ssl-mode=REQUIRED","note":"单独使用 -p（会提示输入密码）；'mysql -pPassword' 形式会在进程列表中暴露密码。","danger":"safe"},"mssql":{"command":"sqlcmd -S host,1433 -U sa -P password -d mydb\n# Windows authentication: sqlcmd -S host -E -d mydb","note":"在较新的 go-sqlcmd 中，TLS 可能需要 -C（信任服务器证书）和 -N（加密）参数。","danger":"safe"},"mongodb":{"command":"mongosh \"mongodb://app_user:password@host:27017/mydb?authSource=admin\"\n# Replica set: ...mongodb://h1,h2,h3/db?replicaSet=rs0","note":"authSource 通常是定义该用户的数据库（大多数情况下为 admin）。","danger":"safe"},"redis":{"command":"redis-cli -h host -p 6379 -a password\n# With TLS: redis-cli -h host -p 6379 --tls -a password\n# Redis 6+ ACL: redis-cli -h host --user app --pass password","note":"-a 在命令行中传递密码（会打印警告）；安全登录请使用 REDISCLI_AUTH 环境变量。","danger":"safe"}}},{"id":"list-databases","title":"列出数据库 / schema","aliases":["list databases","show databases","list databases","which databases","list schemas","pdbs","show dbs"],"engines":{"oracle":{"command":"-- Schemas (users):\nSELECT username FROM dba_users ORDER BY username;\n-- Multitenant (CDB) PDBs:\nSELECT name, open_mode FROM v$pdbs;","note":"在 Oracle 中，“数据库”对应实例；隔离在 schema（用户）级别或 CDB 内的 PDB 级别实现。","danger":"safe"},"postgres":{"command":"SELECT datname FROM pg_database WHERE datistemplate = false;\n-- psql shortcut: \\l","note":"查看 schema：SELECT schema_name FROM information_schema.schemata;","danger":"safe"},"mysql":{"command":"SHOW DATABASES;","note":"在 MySQL 中，“database”和“schema”是同义词。","danger":"safe"},"mssql":{"command":"SELECT name, state_desc FROM sys.databases ORDER BY name;","note":"state_desc 显示 ONLINE/OFFLINE/RESTORING 等数据库状态。","danger":"safe"},"mongodb":{"command":"show dbs\n// or: db.adminCommand({ listDatabases: 1 })","note":"空数据库（尚未写入数据）不会出现在列表中。","danger":"safe"},"redis":{"command":"INFO keyspace\n# db0:keys=..., db1:keys=... lines show the logical DBs in use","note":"Redis 有编号的逻辑 DB（默认 0-15）；Cluster 模式下只使用 DB 0。","danger":"safe"}}},{"id":"switch-database","title":"切换当前数据库 / schema","aliases":["switch database","use database","change database","switch","change schema","change schema","use db"],"engines":{"oracle":{"command":"-- Change the default schema:\nALTER SESSION SET CURRENT_SCHEMA = hr;\n-- Switch to a PDB (inside a CDB):\nALTER SESSION SET CONTAINER = pdb1;","note":"CURRENT_SCHEMA 只改变对象名解析，不改变权限。","danger":"safe"},"postgres":{"command":"-- Switch to another DB inside psql (reconnects):\n\\c mydb\n-- Schema search path (same connection):\nSET search_path TO hr, public;","note":"在 PostgreSQL 中，一个连接绑定到单个数据库；切换 DB 意味着重新连接。","danger":"safe"},"mysql":{"command":"USE mydb;","note":"在不断开连接的情况下切换当前 schema。","danger":"safe"},"mssql":{"command":"USE mydb;","note":"切换批处理上下文；后续命令在该数据库中执行。","danger":"safe"},"mongodb":{"command":"use mydb","note":"即使数据库不存在也会切换；写入第一个文档时才会真正创建。","danger":"safe"},"redis":{"command":"SELECT 1\n# Switches to a logical DB index between 0 and 15","note":"Cluster 模式下 SELECT 只支持 0；请用键前缀代替逻辑 DB 隔离。","danger":"safe"}}}]},"rosetta/objects.json":{"category":"对象管理","categoryId":"objects","order":10,"concepts":[{"id":"create-partition","title":"创建 / 拆分 / 删除分区","aliases":["partition","partition","split","split partition","drop partition","add partition","remove partition"],"engines":{"oracle":{"command":"-- Add a RANGE partition\nALTER TABLE sales ADD PARTITION p2026 VALUES LESS THAN (TO_DATE('2027-01-01','YYYY-MM-DD'));\n\n-- Split an existing partition\nALTER TABLE sales SPLIT PARTITION pmax AT (TO_DATE('2027-01-01','YYYY-MM-DD')) INTO (PARTITION p2026, PARTITION pmax);\n\n-- Drop a partition\nALTER TABLE sales DROP PARTITION p2020 UPDATE INDEXES;","note":"支持 RANGE/LIST/HASH 分区。DROP PARTITION 会删除该分区中的所有行；使用 UPDATE INDEXES 时全局索引保持有效。间隔分区可自动添加分区。","danger":"caution","priv":"表上的 ALTER 权限","minver":"Partitioning 选件"},"postgres":{"command":"-- Declarative partitioning: parent table\nCREATE TABLE sales (id bigint, created_at date, amount numeric) PARTITION BY RANGE (created_at);\n\n-- Add a new partition\nCREATE TABLE sales_2026 PARTITION OF sales FOR VALUES FROM ('2026-01-01') TO ('2027-01-01');\n\n-- Attach a standalone (existing) table as a partition\nCREATE TABLE sales_2027 (LIKE sales INCLUDING ALL);\nALTER TABLE sales ATTACH PARTITION sales_2027 FOR VALUES FROM ('2027-01-01') TO ('2028-01-01');\n\n-- Detach a partition (the table is not dropped; it remains a standalone table)\nALTER TABLE sales DETACH PARTITION sales_2026;","note":"核心 PostgreSQL 没有 SPLIT/MERGE PARTITION（截至 PG17）；要拆分，需 DETACH 该分区，按新边界创建两个分区，迁移数据后再 ATTACH。DETACH ... CONCURRENTLY（PG14+）可缩短加锁时间。","danger":"caution","priv":"表所有者","minver":"PG10"},"mysql":{"command":"-- Partitioned table\nCREATE TABLE sales (id BIGINT, created_at DATE) PARTITION BY RANGE (YEAR(created_at)) (\n  PARTITION p2024 VALUES LESS THAN (2025),\n  PARTITION p2025 VALUES LESS THAN (2026),\n  PARTITION pmax VALUES LESS THAN MAXVALUE\n);\n\n-- Add / split / drop a partition\nALTER TABLE sales REORGANIZE PARTITION pmax INTO (\n  PARTITION p2026 VALUES LESS THAN (2027),\n  PARTITION pmax VALUES LESS THAN MAXVALUE\n);\nALTER TABLE sales DROP PARTITION p2024;","note":"分区列必须包含在每个 PRIMARY KEY / UNIQUE 键中。对于 RANGE，拆分/合并通过 REORGANIZE PARTITION 完成。DROP PARTITION 同时会删除行（RANGE/LIST）。","danger":"caution","priv":"表上的 ALTER、DROP 权限","minver":"5.7"},"mssql":{"command":"-- 1) Partition function and scheme\nCREATE PARTITION FUNCTION pf_date (date) AS RANGE RIGHT FOR VALUES ('2025-01-01','2026-01-01');\nCREATE PARTITION SCHEME ps_date AS PARTITION pf_date ALL TO ([PRIMARY]);\n\n-- 2) Add a new boundary (split)\nALTER PARTITION SCHEME ps_date NEXT USED [PRIMARY];\nALTER PARTITION FUNCTION pf_date() SPLIT RANGE ('2027-01-01');\n\n-- 3) Remove a boundary (merge)\nALTER PARTITION FUNCTION pf_date() MERGE RANGE ('2025-01-01');","note":"先定义分区函数 + 分区方案，再通过 CREATE TABLE ... ON ps_date(created_at) 在该方案上建表。SPLIT 之前必须设置 NEXT USED 文件组。要移除数据，使用 SWITCH PARTITION 将其切换到临时表。","danger":"caution","priv":"表上的 ALTER 权限、ALTER ANY DATASPACE","minver":"2016 SP1（所有版本）"},"mongodb":{"command":"// MongoDB uses sharding instead of table partitioning\nsh.shardCollection(\"db.sales\", { created_at: 1 });\n\n// Native time-series collection for time series\ndb.createCollection(\"measurements\", { timeseries: { timeField: \"ts\", granularity: \"hours\" } });","note":"没有传统意义上的分区。水平拆分通过分片（sharding）以 chunk 方式实现；对于时间相关数据，时间序列集合提供自动分桶。","danger":"safe"},"redis":{"command":"# Not applicable: Redis has no table/partition concept; scaling is done with Redis Cluster hash slots (16384).","note":"Redis Cluster 将键分布到 16384 个哈希槽；这是应用层面的拆分，而非表分区。没有手动分区命令。","danger":"safe"},"elasticsearch":{"command":"# Time-based automatic splitting (rollover) with an ILM policy + data stream\nPUT _ilm/policy/logs-policy\n{ \"policy\": { \"phases\": { \"hot\": { \"actions\": { \"rollover\": { \"max_size\": \"50gb\", \"max_age\": \"30d\" } } } } } }\n\nPUT _index_template/logs-tmpl\n{ \"index_patterns\": [\"logs*\"], \"data_stream\": {}, \"template\": { \"settings\": { \"index.lifecycle.name\": \"logs-policy\" } } }","note":"在 Elasticsearch 中，拆分通过将索引划分为分片实现；对于时间序列，则通过 ILM + data streams 创建新的 backing index（rollover）。旧索引可通过 ILM 删除。","danger":"safe"}}},{"id":"sequence","title":"创建序列 / 自增值","aliases":["sequence","sequence","auto increment","identity","auto-increment","nextval","serial"],"engines":{"oracle":{"command":"-- Sequence\nCREATE SEQUENCE customer_seq START WITH 1 INCREMENT BY 1 NOCACHE;\nSELECT customer_seq.NEXTVAL FROM dual;\n\n-- or an identity column (12c+)\nCREATE TABLE customer (id NUMBER GENERATED ALWAYS AS IDENTITY, name VARCHAR2(100));","note":"经典方法是 CREATE SEQUENCE + NEXTVAL/CURRVAL。自 12c 起也支持 GENERATED AS IDENTITY 列。CACHE 值可提升性能，但可能出现间隙。","danger":"safe","priv":"CREATE SEQUENCE","minver":"IDENTITY 需 12c"},"postgres":{"command":"-- Sequence\nCREATE SEQUENCE customer_seq START 1 INCREMENT 1;\nSELECT nextval('customer_seq');\n\n-- Modern method: identity column\nCREATE TABLE customer (id int GENERATED ALWAYS AS IDENTITY PRIMARY KEY, name text);\n-- Classic: serial (creates a sequence behind the scenes)\nCREATE TABLE logs (id serial PRIMARY KEY, message text);","note":"GENERATED ... AS IDENTITY 符合 SQL 标准，推荐使用；serial 是旧式简写。手动访问使用 nextval/currval。可使用 setval 重置值。","danger":"safe","priv":"schema 上的 CREATE 权限","minver":"IDENTITY 需 PG10"},"mysql":{"command":"-- AUTO_INCREMENT column\nCREATE TABLE customer (id BIGINT AUTO_INCREMENT PRIMARY KEY, name VARCHAR(100));\n\n-- Set the starting value\nALTER TABLE customer AUTO_INCREMENT = 1000;\n\n-- Get the last inserted value\nSELECT LAST_INSERT_ID();","note":"MySQL 没有独立的（标准）序列对象；使用 AUTO_INCREMENT 列（每表一个，必须建索引）。MariaDB 10.3+ 支持 CREATE SEQUENCE。","danger":"safe","priv":"表上的 CREATE、ALTER 权限"},"mssql":{"command":"-- IDENTITY column\nCREATE TABLE customer (id INT IDENTITY(1,1) PRIMARY KEY, name NVARCHAR(100));\n\n-- Standalone sequence object\nCREATE SEQUENCE customer_seq AS INT START WITH 1 INCREMENT BY 1;\nSELECT NEXT VALUE FOR customer_seq;","note":"IDENTITY(seed,increment) 绑定到表的列。CREATE SEQUENCE（2012+）提供独立于表、可在多表间共享的计数器；使用 NEXT VALUE FOR 获取值。","danger":"safe","priv":"CREATE SEQUENCE / 表上的 ALTER 权限","minver":"SEQUENCE 需 2012"},"mongodb":{"command":"// MongoDB has no auto counter; counter pattern with findOneAndUpdate\ndb.counters.findOneAndUpdate(\n  { _id: \"customer\" },\n  { $inc: { seq: 1 } },\n  { upsert: true, returnDocument: \"after\" }\n);","note":"默认 _id 是 ObjectId（本身唯一且大致递增）。如需连续整数，可在单独的 counters 集合中通过 $inc 原子生成。","danger":"safe"},"redis":{"command":"# Atomic counter\nINCR customer:id\n# or with a specific step\nINCRBY customer:id 10","note":"INCR/INCRBY 是原子操作，非常适合在分布式环境中生成唯一递增 ID。键不存在时从 0 开始。","danger":"safe"},"elasticsearch":{"command":"# Not applicable: Elasticsearch does not offer a sequence object. Document IDs are assigned automatically;\n# the internal _seq_no and _primary_term fields are used for concurrency control.\nPOST logs/_doc\n{ \"message\": \"sample\" }","note":"如果不带 ID 执行 POST，ES 会分配一个随机唯一的 _id（非递增）。_seq_no 是随每次写入递增的内部序列号，但它用于乐观并发控制，而不是应用计数器。","danger":"safe"}}},{"id":"add-constraint","title":"添加或禁用约束（FK/CHECK/UNIQUE）","aliases":["constraint","constraint","foreign key","check","unique","foreign key","disable constraint"],"engines":{"oracle":{"command":"-- Add a foreign key\nALTER TABLE orders ADD CONSTRAINT fk_customer FOREIGN KEY (customer_id) REFERENCES customer(id);\n-- CHECK\nALTER TABLE product ADD CONSTRAINT chk_price CHECK (price >= 0);\n-- Disable / enable\nALTER TABLE orders DISABLE CONSTRAINT fk_customer;\nALTER TABLE orders ENABLE CONSTRAINT fk_customer;","note":"Oracle 约束可通过 DISABLE/ENABLE 临时关闭（适用于批量加载）。ENABLE VALIDATE 会检查现有行；NOVALIDATE 不检查。","danger":"caution","priv":"表上的 ALTER 权限、目标上的 REFERENCES 权限"},"postgres":{"command":"-- Foreign key and CHECK\nALTER TABLE orders ADD CONSTRAINT fk_customer FOREIGN KEY (customer_id) REFERENCES customer(id);\nALTER TABLE product ADD CONSTRAINT chk_price CHECK (price >= 0);\n\n-- Instead of disabling the FK: NOT VALID + validate later\nALTER TABLE orders ADD CONSTRAINT fk2 FOREIGN KEY (c_id) REFERENCES customer(id) NOT VALID;\nALTER TABLE orders VALIDATE CONSTRAINT fk2;\nALTER TABLE orders DROP CONSTRAINT fk2;","note":"PostgreSQL 没有约束 DISABLE。为减少大表上的加锁，可先以 NOT VALID 添加约束，稍后再 VALIDATE。ALTER TABLE ... DISABLE TRIGGER ALL 也会禁用 FK 的内部触发器，且需要 superuser；关闭期间插入的数据永远不会被验证，重新启用也不会追溯检查（存在静默破坏完整性的风险）。应改为 DROP 约束，以 NOT VALID 重新添加，然后执行 VALIDATE CONSTRAINT。","danger":"caution","priv":"表所有者、目标上的 REFERENCES 权限"},"mysql":{"command":"-- Foreign key and CHECK\nALTER TABLE orders ADD CONSTRAINT fk_customer FOREIGN KEY (customer_id) REFERENCES customer(id);\nALTER TABLE product ADD CONSTRAINT chk_price CHECK (price >= 0);\n\n-- Temporarily disable FK checks for the session\nSET FOREIGN_KEY_CHECKS = 0;\n-- ... bulk operation ...\nSET FOREIGN_KEY_CHECKS = 1;","note":"需要 InnoDB。不是禁用单个约束，而是使用会话级的 SET FOREIGN_KEY_CHECKS=0。CHECK 约束从 MySQL 8.0.16 起才真正生效（之前会被忽略）。","danger":"caution","priv":"表上的 ALTER、REFERENCES 权限","minver":"CHECK 需 8.0.16"},"mssql":{"command":"-- Foreign key and CHECK\nALTER TABLE orders ADD CONSTRAINT fk_customer FOREIGN KEY (customer_id) REFERENCES customer(id);\nALTER TABLE product ADD CONSTRAINT chk_price CHECK (price >= 0);\n\n-- Disable / re-enable (validate with WITH CHECK)\nALTER TABLE orders NOCHECK CONSTRAINT fk_customer;\nALTER TABLE orders WITH CHECK CHECK CONSTRAINT fk_customer;","note":"在 SQL Server 中，FK/CHECK 约束通过 NOCHECK CONSTRAINT 禁用。重新启用时若不使用 WITH CHECK CHECK CONSTRAINT，约束将保持“not trusted”状态，查询优化器无法利用它。","danger":"caution","priv":"表上的 ALTER 权限、目标上的 REFERENCES 权限"},"mongodb":{"command":"// Constraint-like checks with schema validation rules\ndb.runCommand({ collMod: \"product\",\n  validator: { $jsonSchema: { required: [\"price\"], properties: { price: { bsonType: \"number\", minimum: 0 } } } },\n  validationLevel: \"strict\"\n});\n// Unique index for uniqueness\ndb.customer.createIndex({ email: 1 }, { unique: true });","note":"没有外键（在应用层强制）。UNIQUE 的等价物是唯一索引。用 $jsonSchema 验证代替 CHECK；validationAction: 'warn' 可达到禁用的效果。","danger":"caution"},"redis":{"command":"# Not applicable: Redis has no FK/CHECK/UNIQUE constraints. Uniqueness is enforced at the application level with SET or\n# SETNX/conditional writes.\nSET customer:email:sample@x.com 1 NX","note":"没有关系完整性或约束的概念。唯一性在应用端通过 SET ... NX（仅在不存在时写入）或检查单独的 SET/HASH 来管理。","danger":"safe"},"elasticsearch":{"command":"# Not applicable: Elasticsearch has no FK/CHECK. Field types are enforced with the mapping,\n# and uniqueness with a fixed _id.\nPUT product/_doc/ABC-123\n{ \"price\": 100 }","note":"引擎中不存在关系和约束。类型检查通过 mapping 完成。要实现 UNIQUE 效果，可将自然键用作 _id（写入相同 _id 会覆盖），或用 op_type=create 拒绝冲突。","danger":"safe"}}},{"id":"create-view","title":"创建 / 修改视图","aliases":["view","view","create view","alter view","or replace"],"engines":{"oracle":{"command":"CREATE OR REPLACE VIEW active_customer AS\nSELECT id, name, email FROM customer WHERE status = 'ACTIVE';\n\n-- Drop\nDROP VIEW active_customer;","note":"CREATE OR REPLACE VIEW 既可创建也可修改。对于在特定条件下可更新的视图，可添加 WITH CHECK OPTION。","danger":"safe","readonly":true,"priv":"CREATE VIEW"},"postgres":{"command":"CREATE OR REPLACE VIEW active_customer AS\nSELECT id, name, email FROM customer WHERE status = 'ACTIVE';\n\nDROP VIEW active_customer;","note":"使用 CREATE OR REPLACE 时，新列只能添加在末尾；修改列的类型/顺序需要先 DROP。简单视图可自动更新。","danger":"safe","readonly":true,"priv":"schema 上的 CREATE 权限"},"mysql":{"command":"CREATE OR REPLACE VIEW active_customer AS\nSELECT id, name, email FROM customer WHERE status = 'ACTIVE';\n\n-- Modify\nALTER VIEW active_customer AS SELECT id, name FROM customer;\nDROP VIEW active_customer;","note":"定义通过 CREATE OR REPLACE VIEW 或 ALTER VIEW 更新。执行方式可通过 ALGORITHM = MERGE/TEMPTABLE 设置；WITH CHECK OPTION 可保护可更新视图。","danger":"safe","readonly":true,"priv":"表上的 CREATE VIEW 权限"},"mssql":{"command":"CREATE OR ALTER VIEW active_customer AS\nSELECT id, name, email FROM customer WHERE status = 'ACTIVE';\n\nDROP VIEW active_customer;","note":"CREATE OR ALTER VIEW（2016 SP1+）用一条语句完成创建/修改。旧版本中先 CREATE，再 ALTER VIEW。视图语句必须是批处理中的第一条语句。","danger":"safe","readonly":true,"priv":"CREATE VIEW","minver":"OR ALTER 需 2016 SP1"},"mongodb":{"command":"// Read-only view (on top of an aggregation pipeline)\ndb.createView(\"active_customer\", \"customer\", [ { $match: { status: \"ACTIVE\" } }, { $project: { name: 1, email: 1 } } ]);\n\ndb.active_customer.drop();","note":"db.createView 基于源集合和聚合管道创建只读视图。视图不可写入；要修改定义，需删除后重建（或使用 collMod）。","danger":"safe","readonly":true},"redis":{"command":"# Not applicable: Redis has no view concept; derived views are provided on the application\n# side or with RedisJSON/RediSearch indexes.","note":"不支持基于查询定义的视图。类似需求可保存预计算的键，或用 RediSearch 构建可搜索索引。","danger":"safe"},"elasticsearch":{"command":"# Filtered logical view with an alias\nPOST _aliases\n{ \"actions\": [ { \"add\": { \"index\": \"customer\", \"alias\": \"active_customer\",\n  \"filter\": { \"term\": { \"status\": \"ACTIVE\" } } } } ] }","note":"没有传统的 SQL 视图；带过滤条件的别名（filtered alias）是最接近的等价物，会自动对查询应用过滤。列选择请在查询中使用 _source 过滤。","danger":"safe","readonly":true}}},{"id":"materialized-view","title":"创建并刷新物化视图","aliases":["materialized view","mview","refresh","refresh","cached view","materialize"],"engines":{"oracle":{"command":"-- FAST refresh first requires an MV log on the source table:\nCREATE MATERIALIZED VIEW LOG ON sales\n  WITH ROWID, SEQUENCE (region, amount) INCLUDING NEW VALUES;\n\nCREATE MATERIALIZED VIEW mv_revenue\nBUILD IMMEDIATE REFRESH FAST ON COMMIT\nAS SELECT region, SUM(amount) total, COUNT(*) cnt, COUNT(amount) amount_cnt\n   FROM sales GROUP BY region;\n\n-- Manual refresh\nEXEC DBMS_MVIEW.REFRESH('MV_REVENUE', 'C');","note":"REFRESH FAST 需要物化视图日志；COMPLETE（'C'）完全重算，FAST（'F'）为增量刷新。ON COMMIT/ON DEMAND 决定刷新时机。","danger":"caution","priv":"CREATE MATERIALIZED VIEW"},"postgres":{"command":"CREATE MATERIALIZED VIEW mv_revenue AS\nSELECT region, SUM(amount) AS total FROM sales GROUP BY region;\n\n-- Refresh (CONCURRENTLY requires a unique index)\nCREATE UNIQUE INDEX ON mv_revenue (region);\nREFRESH MATERIALIZED VIEW CONCURRENTLY mv_revenue;","note":"REFRESH 会重新计算全部数据（非增量）。CONCURRENTLY 不阻塞读取，但需要唯一索引且速度较慢。","danger":"caution","priv":"表所有者","minver":"PG9.3（CONCURRENTLY 9.4）"},"mysql":{"command":"-- MySQL/MariaDB has no built-in materialized views.\n-- Approach: summary table + refresh with a trigger or a scheduled event\nCREATE TABLE mv_revenue (region VARCHAR(50) PRIMARY KEY, total DECIMAL(18,2));\n\nCREATE EVENT ev_mv_revenue ON SCHEDULE EVERY 1 HOUR DO\n  REPLACE INTO mv_revenue SELECT region, SUM(amount) FROM sales GROUP BY region;","note":"没有原生的物化视图支持。需维护一张汇总表，通过触发器实时或通过 EVENT SCHEDULER 定期重新填充。","danger":"caution","priv":"schema 上的 CREATE、EVENT 权限"},"mssql":{"command":"-- Indexed view (materialized view equivalent)\nCREATE VIEW dbo.mv_revenue WITH SCHEMABINDING AS\nSELECT region, SUM(ISNULL(amount, 0)) AS total, COUNT_BIG(*) AS cnt\nFROM dbo.sales GROUP BY region;\nGO\nCREATE UNIQUE CLUSTERED INDEX ix_mv_revenue ON dbo.mv_revenue (region);","note":"SQL Server 使用索引视图：WITH SCHEMABINDING 是必需的；创建唯一聚集索引后，数据会被物理存储并自动维护（无需单独 REFRESH）。使用 GROUP BY 时必须包含 COUNT_BIG(*)；如果 SUM 中的列可为空，应写成 SUM(ISNULL(amount, 0))（否则报错 8662）。视图必须在 QUOTED_IDENTIFIER 和 ANSI_NULLS 为 ON 时创建。查询优化器的自动视图匹配仅在 Enterprise（及 Developer）版中可用；在 Standard 版中，需使用 WITH (NOEXPAND) 提示引用视图。","danger":"caution","priv":"CREATE VIEW 权限、schema 上的 ALTER 权限"},"mongodb":{"command":"// On-demand materialized view: write the result to a collection with $merge\ndb.sales.aggregate([\n  { $group: { _id: \"$region\", total: { $sum: \"$amount\" } } },\n  { $merge: { into: \"mv_revenue\", whenMatched: \"replace\", whenNotMatched: \"insert\" } }\n]);","note":"通过 $out 或 $merge 将聚合结果写入持久集合，即可获得按需刷新的物化视图。刷新时重新运行管道（手动或借助调度器）。","danger":"caution"},"redis":{"command":"# Not applicable: Redis has no materialized views. The computed result is written to a key\n# and cached with a TTL.\nSET mv:revenue:region1 12345 EX 3600","note":"没有物化视图的概念；最接近的做法是将预计算值保存在键中（String/Hash/Sorted Set），并用 EX/TTL 限时缓存。","danger":"safe"},"elasticsearch":{"command":"# Continuous/materialized summary index with a transform\nPUT _transform/mv_revenue\n{ \"source\": { \"index\": \"sales\" },\n  \"dest\": { \"index\": \"mv_revenue\" },\n  \"pivot\": { \"group_by\": { \"region\": { \"terms\": { \"field\": \"region\" } } },\n            \"aggregations\": { \"total\": { \"sum\": { \"field\": \"amount\" } } } } }\nPOST _transform/mv_revenue/_start","note":"transform 对源索引进行透视，并将其物化到汇总目标索引中。本例是批处理 transform：通过 _start 运行一次后停止。若需要随新数据到达自动更新的连续 transform，在配置中添加带时间字段的 \"sync\": { \"time\": { \"field\": \"<time_field>\", \"delay\": \"60s\" } }（不存在 continuous: true 设置）。","danger":"caution"}}},{"id":"rename-object","title":"重命名表 / 对象","aliases":["rename","rename","change name","rename table","sp_rename"],"engines":{"oracle":{"command":"-- Table\nALTER TABLE old_name RENAME TO new_name;\n-- or\nRENAME old_name TO new_name;\n-- Column\nALTER TABLE customer RENAME COLUMN name TO full_name;","note":"表/视图/序列使用 RENAME 或 ALTER ... RENAME TO。依赖对象（视图、同义词）可能失效，需要重新编译。","danger":"caution","priv":"对象上的 ALTER 权限"},"postgres":{"command":"ALTER TABLE old_name RENAME TO new_name;\nALTER TABLE customer RENAME COLUMN name TO full_name;\nALTER VIEW old_view RENAME TO new_view;","note":"ALTER ... RENAME TO 适用于大多数对象类型。即使名称改变，外键和索引引用也会自动保留（基于 OID）。","danger":"caution","priv":"表所有者"},"mysql":{"command":"RENAME TABLE old_name TO new_name;\n-- or\nALTER TABLE old_name RENAME TO new_name;\n-- Column (8.0+)\nALTER TABLE customer RENAME COLUMN name TO full_name;","note":"RENAME TABLE 是原子操作，可在一条语句中移动多张表。重命名列在 8.0+ 中使用 RENAME COLUMN；之前必须使用 CHANGE 并同时指定类型。","danger":"caution","priv":"旧对象上的 ALTER、DROP 权限；新对象上的 CREATE、INSERT 权限"},"mssql":{"command":"-- Table\nEXEC sp_rename 'dbo.old_name', 'new_name';\n-- Column\nEXEC sp_rename 'dbo.customer.name', 'full_name', 'COLUMN';","note":"SQL Server 没有 RENAME TABLE 语句；使用系统过程 sp_rename。表名中带 schema 前缀，新名称中不带。依赖的视图/存储过程需手动更新。","danger":"caution","priv":"对象上的 ALTER 权限、CONTROL"},"mongodb":{"command":"// Rename a collection\ndb.old_name.renameCollection(\"new_name\");\n// or the admin command\ndb.adminCommand({ renameCollection: \"db.old_name\", to: \"db.new_name\" });","note":"renameCollection 修改集合名称。重命名字段需在 updateMany 中使用 $rename 运算符。分片集合可在 5.0+ 中重命名；旧版本不支持。","danger":"caution","priv":"renameCollectionSameDB / clusterManager"},"redis":{"command":"# Rename a key\nRENAME old_key new_key\n# Without overwriting if the target exists\nRENAMENX old_key new_key","note":"RENAME 重命名键；若目标已存在则被覆盖。RENAMENX 仅在目标不存在时生效。源键不存在时返回错误。","danger":"caution"},"elasticsearch":{"command":"# An index cannot be renamed directly; reindex + alias approach\nPOST _reindex\n{ \"source\": { \"index\": \"old_name\" }, \"dest\": { \"index\": \"new_name\" } }\nDELETE old_name\n# or use an alias for a seamless switch\nPOST _aliases\n{ \"actions\": [ { \"add\": { \"index\": \"new_name\", \"alias\": \"customer\" } } ] }","note":"没有索引重命名命令。用 _reindex 重建到新名称后删除旧索引。建议通过稳定的别名访问，使应用不依赖索引名称。","danger":"caution"}}}]},"rosetta/datamove.json":{"category":"数据迁移","categoryId":"datamove","order":11,"concepts":[{"id":"bulk-import","title":"批量加载数据（CSV / 从文件）","aliases":["import","bulk load","load csv","bulk insert","load data","copy from","import data"],"engines":{"oracle":{"command":"-- SQL*Loader (from the shell)\nsqlldr app_user/password control=customer.ctl log=load.log\n\n-- Contents of customer.ctl:\n-- LOAD DATA INFILE 'customer.csv'\n-- INTO TABLE customer FIELDS TERMINATED BY ','\n-- (id, name, email)\n\n-- Alternative: with an external table\nCREATE TABLE ext_customer (id NUMBER, name VARCHAR2(100))\nORGANIZATION EXTERNAL (TYPE ORACLE_LOADER DEFAULT DIRECTORY dir_csv\n  ACCESS PARAMETERS (FIELDS TERMINATED BY ',') LOCATION ('customer.csv'));","note":"最常用的工具是 SQL*Loader（配合控制文件）。若要像查询一样读取文件，使用外部表。大数据量时，直接路径加载（DIRECT=TRUE）速度很快。","danger":"caution","priv":"表上的 INSERT 权限、DIRECTORY 对象"},"postgres":{"command":"-- Server side (file on the server; requires superuser/pg_read_server_files)\nCOPY customer (id, name, email) FROM '/data/customer.csv' WITH (FORMAT csv, HEADER true);\n\n-- Client side (psql, file on the client)\n\\copy customer (id, name, email) FROM 'customer.csv' WITH (FORMAT csv, HEADER true)","note":"COPY 读取服务器上的文件，需要相应权限；psql \\copy 读取客户端文件，无需额外权限。这是加载 CSV 最快的方式。","danger":"caution","priv":"表上的 INSERT 权限（COPY 需 pg_read_server_files）"},"mysql":{"command":"-- Server side\nLOAD DATA INFILE '/data/customer.csv' INTO TABLE customer\nFIELDS TERMINATED BY ',' ENCLOSED BY '\"' LINES TERMINATED BY '\\n' IGNORE 1 LINES;\n\n-- From a client file\nLOAD DATA LOCAL INFILE 'customer.csv' INTO TABLE customer\nFIELDS TERMINATED BY ',' IGNORE 1 LINES;","note":"LOAD DATA INFILE 是最快的批量加载方式。LOCAL 读取客户端文件，必须在服务器（local_infile=ON）和客户端两侧都启用。secure_file_priv 限制服务器路径。","danger":"caution","priv":"FILE（LOCAL 需 INSERT）"},"mssql":{"command":"-- T-SQL BULK INSERT\nBULK INSERT customer FROM 'C:\\data\\customer.csv'\nWITH (FORMAT='CSV', FIRSTROW=2, FIELDTERMINATOR=',', ROWTERMINATOR='0x0a');\n\n-- bcp from the shell\nbcp mydb.dbo.customer in customer.csv -c -t, -S server -T","note":"BULK INSERT 从 SQL Server 服务账户可访问的路径读取文件。bcp 是命令行工具；-c 为字符模式，-t 为字段分隔符。为获得最高速度，使用 TABLOCK 争取最小日志记录。","danger":"caution","priv":"INSERT、ADMINISTER BULK OPERATIONS"},"mongodb":{"command":"# Import CSV / JSON (from the shell)\nmongoimport --db=shop --collection=customer --type=csv --headerline --file=customer.csv\n\n# For a JSON array\nmongoimport --db=shop --collection=customer --file=customer.json --jsonArray","note":"mongoimport 加载 CSV/TSV/JSON；--headerline 将第一行视为字段名。更新现有文档使用 --mode=upsert --upsertFields。大数据量时 --numInsertionWorkers 可加快速度。","danger":"caution","priv":"insert（readWrite）"},"redis":{"command":"# Fastest: feed commands in protocol format and pipe them\ncat commands.txt | redis-cli --pipe\n\n# Example lines in commands.txt:\n# SET customer:1 \"Alice\"\n# SET customer:2 \"Bob\"","note":"批量加载使用 redis-cli --pipe（mass insert）；它通过单个连接以 RESP 协议格式流式发送命令，因此速度非常快。也可以通过 pipelining 对命令分组。","danger":"caution"},"elasticsearch":{"command":"# _bulk API (NDJSON: an action line + a source line per document)\ncurl -s -H 'Content-Type: application/x-ndjson' -XPOST 'localhost:9200/customer/_bulk' --data-binary @data.ndjson\n\n# Contents of data.ndjson:\n# { \"index\": {} }\n# { \"id\": 1, \"name\": \"Alice\" }\n# { \"index\": {} }\n# { \"id\": 2, \"name\": \"Bob\" }","note":"_bulk API 接收 NDJSON（每个文档一行操作 + 一行数据；文件必须以 \\n 结尾）。CSV 使用 Logstash、Filebeat 或 elasticdump。加载期间禁用 refresh_interval 可加快速度。","danger":"caution","priv":"index / create index 权限"}}},{"id":"bulk-export","title":"导出数据（CSV / 到文件）","aliases":["export","export data","export csv","copy to","outfile","dump","extract data"],"engines":{"oracle":{"command":"-- CSV with SQL*Plus (spool)\nSET MARKUP CSV ON\nSPOOL customer.csv\nSELECT id, name, email FROM customer;\nSPOOL OFF\n\n-- Logical export with Data Pump (from the shell)\nexpdp app_user/password tables=customer directory=dir_dmp dumpfile=customer.dmp","note":"CSV 使用 SQL*Plus SET MARKUP CSV ON + SPOOL（12.2+）。迁移 schema/表时，Data Pump（expdp）生成二进制 .dmp，再用 impdp 导入另一个 Oracle 数据库。","danger":"safe","readonly":true,"priv":"表上的 SELECT 权限","minver":"MARKUP CSV 需 12.2"},"postgres":{"command":"-- Server side\nCOPY (SELECT id, name, email FROM customer) TO '/data/customer.csv' WITH (FORMAT csv, HEADER true);\n\n-- Client side (psql)\n\\copy (SELECT id, name, email FROM customer) TO 'customer.csv' WITH (FORMAT csv, HEADER true)","note":"COPY ... TO 将查询结果写入文件（服务器端，需要权限）；psql \\copy 写入客户端文件。HEADER true 会添加表头行。","danger":"safe","readonly":true,"priv":"表上的 SELECT 权限"},"mysql":{"command":"-- To a file on the server\nSELECT id, name, email FROM customer\nINTO OUTFILE '/data/customer.csv'\nFIELDS TERMINATED BY ',' ENCLOSED BY '\"' LINES TERMINATED BY '\\n';\n\n-- To the client: mysql -e ... > file (tab-separated)\n-- mysql -e \"SELECT * FROM customer\" shop > customer.tsv","note":"SELECT ... INTO OUTFILE 在服务器上创建文件（需要 secure_file_priv 路径和 FILE 权限；不能覆盖已有文件）。客户端输出请用 mysql -e 重定向。","danger":"safe","readonly":true,"priv":"SELECT、FILE"},"mssql":{"command":"-- Export with bcp (from the shell)\nbcp \"SELECT id, name, email FROM shop.dbo.customer\" queryout customer.csv -c -t, -S server -T\n\n-- or the whole table\nbcp shop.dbo.customer out customer.dat -n -S server -T","note":"bcp out/queryout 将数据写入文件：-c 字符模式，-t, 字段分隔符，-n 原生二进制格式。查询结果使用 queryout。SSMS 中的“结果保存到文件”也是一种选择。","danger":"safe","readonly":true,"priv":"表上的 SELECT 权限"},"mongodb":{"command":"# Export CSV (fields must be specified)\nmongoexport --db=shop --collection=customer --type=csv --fields=id,name,email --out=customer.csv\n\n# As JSON\nmongoexport --db=shop --collection=customer --out=customer.json","note":"mongoexport 生成 CSV/JSON；CSV 必须通过 --fields 显式指定列。使用 --query 过滤。完整的二进制备份推荐使用 mongodump。","danger":"safe","readonly":true,"priv":"find（read）"},"redis":{"command":"# Scan keys (SCAN instead of KEYS in production)\nredis-cli --scan --pattern 'customer:*' > keys.txt\n\n# For a full data backup, download an RDB snapshot to the client side (non-blocking)\nredis-cli --rdb ./dump.rdb","note":"没有内置的结构化 CSV 导出。键列表使用 --scan（非阻塞），完整数据使用 RDB 文件：redis-cli --rdb 会把服务器在后台生成的快照下载到本地文件；如果希望文件留在服务器上，使用 BGSAVE。SAVE 会阻塞主线程，生产环境勿用。若还需要值，循环读取 scan 结果。","danger":"safe","readonly":true},"elasticsearch":{"command":"# Export with elasticdump\nelasticdump --input=http://localhost:9200/customer --output=customer.json --type=data\n\n# For large data, paged reads with scroll / point-in-time\nGET customer/_search?scroll=1m\n{ \"size\": 1000, \"query\": { \"match_all\": {} } }","note":"没有直接的 CSV 导出。获取全部数据可使用 elasticdump，或通过 scroll/PIT（point-in-time）分页读取 _search。转换为 CSV 需要 Logstash csv output 或外部工具。","danger":"safe","readonly":true,"priv":"索引上的 read 权限"}}},{"id":"copy-between","title":"在表 / 环境之间复制数据","aliases":["copy","copy","insert select","ctas","reindex","move data","between tables"],"engines":{"oracle":{"command":"-- Into an existing table\nINSERT INTO target_tbl (id, name) SELECT id, name FROM source_tbl WHERE status = 'ACTIVE';\n\n-- By creating a new table (CTAS)\nCREATE TABLE target_tbl AS SELECT * FROM source_tbl;\n\n-- From a remote database via a database link\nINSERT INTO target_tbl SELECT * FROM source_tbl@remote_db;","note":"同一数据库内使用 INSERT ... SELECT 或 CREATE TABLE AS SELECT（CTAS）。跨环境复制推荐使用数据库链接（@remote_db）或 Data Pump。APPEND 提示通过直接路径提升速度。","danger":"caution","priv":"源上的 SELECT 权限，目标上的 INSERT/CREATE 权限"},"postgres":{"command":"-- Same database\nINSERT INTO target_tbl (id, name) SELECT id, name FROM source_tbl;\nCREATE TABLE target_tbl AS SELECT * FROM source_tbl;\n\n-- Between environments (psql pipe)\n-- pg_dump -t source_tbl sourcedb | psql targetdb\n-- or read the remote table with postgres_fdw and INSERT","note":"同一 DB 内使用 INSERT ... SELECT / CTAS。跨服务器使用 pg_dump | psql 管道，或通过 postgres_fdw 外部表复制。","danger":"caution","priv":"源上的 SELECT 权限，目标上的 INSERT/CREATE 权限"},"mysql":{"command":"-- Same database\nINSERT INTO target_tbl (id, name) SELECT id, name FROM source_tbl;\nCREATE TABLE target_tbl AS SELECT * FROM source_tbl;\n\n-- Between servers (mysqldump pipe)\n-- mysqldump sourcedb source_tbl | mysql -h targethost targetdb","note":"同一 DB 内使用 INSERT ... SELECT 或 CREATE TABLE ... AS SELECT。不同服务器之间，常见做法是将 mysqldump 输出通过管道传给 mysql 客户端。CTAS 不会复制索引和约束。","danger":"caution","priv":"源上的 SELECT 权限，目标上的 INSERT/CREATE 权限"},"mssql":{"command":"-- Into an existing table\nINSERT INTO target_tbl (id, name) SELECT id, name FROM source_tbl;\n\n-- By creating a new table\nSELECT * INTO target_tbl FROM source_tbl;\n\n-- Between servers: linked server\nINSERT INTO target_tbl SELECT * FROM [REMOTE_SRV].shop.dbo.source_tbl;","note":"在 SQL Server 中，复制到新表使用 SELECT ... INTO（等价于 CTAS）。跨服务器使用链接服务器（四段式名称）、bcp out/in 或 SSIS。","danger":"caution","priv":"源上的 SELECT 权限，目标上的 INSERT / CREATE TABLE 权限"},"mongodb":{"command":"// Write an aggregation result to another collection\ndb.source_tbl.aggregate([\n  { $match: { status: \"ACTIVE\" } },\n  { $out: \"target_tbl\" }\n]);\n\n// Merge into an existing collection (upsert)\ndb.source_tbl.aggregate([ { $merge: { into: \"target_tbl\", whenMatched: \"merge\", whenNotMatched: \"insert\" } } ]);","note":"$out 完全替换目标集合；$merge 与现有文档合并/插入（同一或其他 DB）。跨服务器迁移使用 mongodump | mongorestore 或 mongoexport/mongoimport。","danger":"caution","priv":"源上的 find 权限，目标上的 insert 权限"},"redis":{"command":"# Move a single key to another server (deleted from the source)\nMIGRATE target_host 6379 customer:1 0 5000\n\n# To copy (without deleting): DUMP + RESTORE\nDUMP customer:1          # returns the serialized value\nRESTORE customer:1new 0 \"<dump-data>\"","note":"MIGRATE 以原子方式将键迁移到目标实例并从源中删除（使用 COPY 选项时不删除）。在同一实例内复制使用 COPY 命令（6.0+）或 DUMP+RESTORE。","danger":"caution"},"elasticsearch":{"command":"# Within the same cluster\nPOST _reindex\n{ \"source\": { \"index\": \"source_tbl\" }, \"dest\": { \"index\": \"target_tbl\" } }\n\n# From a remote cluster (remote reindex; requires the whitelist setting)\nPOST _reindex\n{ \"source\": { \"remote\": { \"host\": \"http://remote:9200\" }, \"index\": \"source_tbl\" },\n  \"dest\": { \"index\": \"target_tbl\" } }","note":"_reindex 在索引之间复制数据；可通过查询过滤并用脚本转换。跨集群使用 remote reindex（目标上必须定义 reindex.remote.whitelist）。大型任务通过 slices 并行处理。","danger":"caution","priv":"源上的 read 权限，目标上的 write 权限"}}},{"id":"generate-insert","title":"将数据生成为 INSERT 脚本","aliases":["generate insert","insert script","generate script","data dump","inserts","generate script"],"engines":{"oracle":{"command":"-- DBMS_METADATA for object DDL\nSELECT DBMS_METADATA.GET_DDL('TABLE','CUSTOMER') FROM dual;\n\n-- Generating row data as INSERTs is not built in;\n-- use SQL Developer 'Export > Insert' or SQLcl 'SET SQLFORMAT INSERT':\n-- SET SQLFORMAT INSERT\n-- SELECT * FROM customer;","note":"Oracle 没有可将数据直接转换为现成 INSERT 脚本的 SQL 命令。DDL 使用 DBMS_METADATA.GET_DDL；行的 INSERT 使用 SQLcl（SET SQLFORMAT INSERT）或 SQL Developer 导出向导。","danger":"safe","readonly":true,"priv":"表上的 SELECT 权限"},"postgres":{"command":"-- Data only, as INSERT statements\npg_dump --data-only --inserts --table=customer shop > insert.sql\n\n-- With column names (more robust to schema differences)\npg_dump --data-only --column-inserts --table=customer shop > insert.sql","note":"pg_dump --inserts 将每行写成单独的 INSERT；--column-inserts 还会加上列名（对 schema 差异更稳健）。默认（不带参数）生成 COPY 格式，加载速度快得多。","danger":"safe","readonly":true,"priv":"表上的 SELECT 权限"},"mysql":{"command":"-- Data only (INSERTs), without the table definition\nmysqldump --no-create-info shop customer > insert.sql\n\n-- One INSERT per row (more readable)\nmysqldump --no-create-info --skip-extended-insert shop customer > insert.sql","note":"mysqldump 默认生成 INSERT 语句；--no-create-info 跳过 CREATE TABLE，只输出数据。--skip-extended-insert 使每行成为单独的 INSERT（默认会把多行合并为一条语句）。","danger":"safe","readonly":true,"priv":"表上的 SELECT 权限"},"mssql":{"command":"-- SSMS: right-click > Tasks > Generate Scripts >\n--   Advanced > 'Types of data to script' = Data only (or Schema and data)\n\n-- Command-line alternative (sqlpackage / mssql-scripter tool)\n-- mssql-scripter -S server -d shop -t customer --data-only > insert.sql","note":"SQL Server 没有用于此目的的内置 T-SQL 命令。最常用的方法是在 SSMS “生成脚本”向导中选择“仅限数据”。在命令行中，Microsoft 的 mssql-scripter 工具可通过 --data-only 生成 INSERT。","danger":"safe","readonly":true,"priv":"表上的 SELECT 权限"},"mongodb":{"command":"# Produces a binary/BSON backup instead of INSERT SQL\nmongodump --db=shop --collection=customer --out=./backup\n\n# As JSON text (can be adapted to insertMany)\nmongoexport --db=shop --collection=customer --jsonArray --out=customer.json","note":"没有 SQL INSERT 的概念。可移植的备份使用 mongodump（BSON，用 mongorestore 恢复）。如需嵌入代码的 insertMany，可使用 mongoexport --jsonArray 的输出。","danger":"safe","readonly":true,"priv":"find（read）"},"redis":{"command":"# Portable serialization of a single key\nDUMP customer:1\n# The output is restored with RESTORE:\n# RESTORE customer:1 0 \"<dump-data>\"\n\n# RDB snapshot for all data\nredis-cli --rdb dump.rdb","note":"不会生成 SQL INSERT 脚本。按键使用 DUMP + RESTORE 序列化，全部数据使用 RDB（--rdb）文件。如需命令列表，用 SCAN 遍历并为每种类型构建相应的 SET/HSET 命令。","danger":"safe","readonly":true},"elasticsearch":{"command":"# The INSERT equivalent is _bulk NDJSON; produce it with elasticdump\nelasticdump --input=http://localhost:9200/customer --output=customer_bulk.json --type=data\n\n# Manual: converting a _search result to _bulk NDJSON requires an external script","note":"不会生成 SQL INSERT；在 Elasticsearch 中，加载单位是 _bulk NDJSON。要以可重新加载的形式提取现有数据，使用 elasticdump（type=data）；_search + PIT 的输出可通过外部脚本转换为 NDJSON。","danger":"safe","readonly":true,"priv":"索引上的 read 权限"}}}]},"rosetta/monitoring.json":{"category":"监控与诊断","categoryId":"monitoring","order":12,"concepts":[{"id":"current-locks","title":"列出当前锁","aliases":["locks","lock list","lock list","current locks","active locks"],"engines":{"oracle":{"command":"SELECT s.sid, s.serial#, l.type, DECODE(l.lmode,0,'None',1,'Null',2,'Row-S',3,'Row-X',4,'Share',5,'S/Row-X',6,'Exclusive') AS lmode, o.object_name, s.username, s.blocking_session\nFROM v$lock l\nJOIN v$session s ON s.sid = l.sid\nLEFT JOIN v$locked_object lo ON lo.session_id = l.sid\nLEFT JOIN dba_objects o ON o.object_id = lo.object_id\nORDER BY s.sid;","note":"通过 v$lock 和 v$locked_object 显示活动锁及阻塞会话（blocking_session）。","danger":"safe","readonly":true,"priv":"v$lock、v$session 上的 SELECT 权限"},"postgres":{"command":"SELECT l.locktype, l.mode, l.granted, a.pid, a.usename, a.state, a.wait_event_type, a.query\nFROM pg_locks l\nJOIN pg_stat_activity a ON a.pid = l.pid\nWHERE l.pid <> pg_backend_pid()\nORDER BY l.granted, a.pid;","note":"将 pg_locks 与 pg_stat_activity 关联；granted=false 的行表示正在等待（被阻塞）的锁。","danger":"safe","readonly":true,"priv":"pg_monitor 角色（用于查看所有查询）"},"mysql":{"command":"SELECT ENGINE, OBJECT_SCHEMA, OBJECT_NAME, LOCK_TYPE, LOCK_MODE, LOCK_STATUS, THREAD_ID\nFROM performance_schema.data_locks;\n-- For waiters: SELECT * FROM performance_schema.data_lock_waits;","note":"performance_schema.data_locks 列出当前 InnoDB 锁，data_lock_waits 列出等待关系。","danger":"safe","readonly":true,"priv":"performance_schema 上的 SELECT 权限","minver":"8.0"},"mssql":{"command":"SELECT tl.resource_type, tl.resource_database_id, tl.request_mode, tl.request_status, tl.request_session_id, es.login_name, er.blocking_session_id\nFROM sys.dm_tran_locks tl\nLEFT JOIN sys.dm_exec_sessions es ON es.session_id = tl.request_session_id\nLEFT JOIN sys.dm_exec_requests er ON er.session_id = tl.request_session_id\nORDER BY tl.request_session_id;","note":"sys.dm_tran_locks 显示当前锁请求；request_status = WAIT 的是被阻塞的请求。","danger":"safe","readonly":true,"priv":"VIEW SERVER STATE"},"mongodb":{"command":"db.currentOp({ \"$or\": [ { \"waitingForLock\": true }, { \"locks\": { \"$exists\": true } } ] })","note":"db.currentOp 显示等待锁的操作（waitingForLock）和持有锁的操作；MongoDB 在文档/集合级别加锁，而非行级。","danger":"safe","priv":"inprog / clusterMonitor 角色"},"redis":{"command":"# Not applicable: Redis has no row/table lock concept; commands run sequentially on a single thread. For blocking analysis, use the SLOWLOG and LATENCY commands.","note":"由于 Redis 是单线程的，没有传统的锁列表。","danger":"safe"},"elasticsearch":{"command":"GET _cluster/pending_tasks","note":"Elasticsearch 没有行锁；此处列出在 master 上排队的集群变更任务（pending tasks）。","danger":"safe","readonly":true}}},{"id":"long-operations","title":"查看长时间运行操作的进度","aliases":["progress","operation progress","long operations","long-running operations","longops"],"engines":{"oracle":{"command":"SELECT sid, serial#, opname, target, sofar, totalwork,\n       ROUND(sofar / NULLIF(totalwork,0) * 100, 2) AS pct_done,\n       time_remaining, elapsed_seconds\nFROM v$session_longops\nWHERE totalwork > 0 AND sofar < totalwork\nORDER BY start_time;","note":"v$session_longops 显示运行超过 6 秒的操作的完成百分比和剩余时间（time_remaining）。","danger":"safe","readonly":true,"priv":"v$session_longops 上的 SELECT 权限"},"postgres":{"command":"SELECT pid, phase, heap_blks_total, heap_blks_scanned,\n       ROUND(heap_blks_scanned::numeric / NULLIF(heap_blks_total,0) * 100, 2) AS pct\nFROM pg_stat_progress_vacuum;\n-- Others: pg_stat_progress_create_index, _cluster, _analyze, _copy, _basebackup","note":"pg_stat_progress_* 视图报告 VACUUM、CREATE INDEX、CLUSTER、ANALYZE 和 COPY 操作的进度。","danger":"safe","readonly":true,"minver":"9.6"},"mysql":{"command":"SELECT t.processlist_id, st.event_name, st.work_completed, st.work_estimated,\n       ROUND(st.work_completed / NULLIF(st.work_estimated,0) * 100, 2) AS pct\nFROM performance_schema.events_stages_current st\nJOIN performance_schema.threads t ON t.thread_id = st.thread_id\nWHERE st.work_estimated > 0;","note":"启用 stage instrumentation 后，events_stages_current 通过 work_completed/work_estimated 显示进度（如 ALTER TABLE）。","danger":"safe","readonly":true,"priv":"performance_schema 上的 SELECT 权限"},"mssql":{"command":"SELECT session_id, command, percent_complete,\n       DATEADD(ms, estimated_completion_time, GETDATE()) AS est_finish,\n       total_elapsed_time / 1000 AS elapsed_sec\nFROM sys.dm_exec_requests\nWHERE percent_complete > 0\nORDER BY percent_complete;","note":"sys.dm_exec_requests.percent_complete 给出 BACKUP、RESTORE、DBCC 和索引重建等操作的完成百分比。","danger":"safe","readonly":true,"priv":"VIEW SERVER STATE"},"mongodb":{"command":"db.currentOp({ \"active\": true, \"secs_running\": { \"$gte\": 3 } })","note":"在 db.currentOp 输出中，长时间运行的操作以及索引构建的 'progress' 字段（done/total）显示进度。","danger":"safe","priv":"inprog / clusterMonitor 角色"},"redis":{"command":"INFO persistence\n# Keys: rdb_bgsave_in_progress, aof_rewrite_in_progress, rdb_last_bgsave_status, loading, loading_loaded_perc","note":"INFO persistence 部分报告 RDB/AOF 保存或重写操作是否正在进行以及加载百分比。","danger":"safe","readonly":true},"elasticsearch":{"command":"GET _tasks?detailed=true&actions=*reindex,*update/byquery,*delete/byquery\n# For recovery/shard relocation: GET _cat/recovery?v&active_only=true","note":"_tasks?detailed 显示长时间运行任务（reindex、update_by_query）的状态；_cat/recovery 显示活动分片恢复的进度。","danger":"safe","readonly":true}}},{"id":"session-trace","title":"跟踪会话 / 查询（启用 trace / profiling）","aliases":["trace","enable tracing","profiling","session trace","query tracing"],"engines":{"oracle":{"command":"-- For a specific session (10046 event tracing):\nBEGIN\n  DBMS_MONITOR.SESSION_TRACE_ENABLE(session_id => 123, serial_num => 4567, waits => TRUE, binds => TRUE);\nEND;\n/\n-- To disable: DBMS_MONITOR.SESSION_TRACE_DISABLE(123, 4567);","note":"DBMS_MONITOR.SESSION_TRACE_ENABLE 为会话生成 10046 跟踪文件；完成后务必关闭（跟踪会增加开销和磁盘占用）。","danger":"caution","priv":"DBMS_MONITOR 上的 EXECUTE 权限"},"postgres":{"command":"LOAD 'auto_explain';\nSET auto_explain.log_min_duration = 0;\nSET auto_explain.log_analyze = true;\n-- Simple alternative: SET log_min_duration_statement = 0;","note":"auto_explain 将查询计划写入日志文件，log_min_duration_statement 将慢查询/所有查询写入日志文件；值为 0 时记录每条查询，需注意日志量。","danger":"caution","priv":"SUPERUSER（用于 LOAD）或会话级 SET"},"mysql":{"command":"-- performance_schema approach (recommended):\nUPDATE performance_schema.setup_consumers SET ENABLED='YES' WHERE NAME LIKE 'events_statements%';\n-- Legacy/simple method (being deprecated):\nSET profiling = 1;\n-- run the query, then:\nSHOW PROFILES;","note":"查询跟踪通过 performance_schema events_statements 实现；SET profiling 方法较旧（已弃用），但可快速进行会话级测量。","danger":"caution","priv":"performance_schema 上的 SELECT/UPDATE 权限"},"mssql":{"command":"CREATE EVENT SESSION [trace_slow] ON SERVER\nADD EVENT sqlserver.sql_statement_completed(\n  WHERE duration > 1000000)\nADD TARGET package0.event_file(SET filename=N'trace_slow.xel');\nGO\nALTER EVENT SESSION [trace_slow] ON SERVER STATE = START;","note":"Extended Events（XEvents）是现代的跟踪方法；创建会话后用 START 启动，完成后用 STOP/DROP 停止。","danger":"caution","priv":"ALTER ANY EVENT SESSION"},"mongodb":{"command":"db.setProfilingLevel(2)\n// 1 = slow operations only (slowms threshold), 0 = off\n// Results: db.system.profile.find().sort({ ts: -1 }).limit(10)","note":"setProfilingLevel(2) 会将所有操作记录到 system.profile 集合；生产环境优先使用 1（仅慢操作），因为级别 2 会增加开销。","danger":"caution","priv":"dbAdmin / enableProfiler"},"redis":{"command":"MONITOR\n# Streams all commands arriving at the server in real time. Exit: Ctrl-C","note":"MONITOR 实时显示所有命令，但会导致严重的性能下降；在生产环境中应谨慎短时使用。替代方案：SLOWLOG GET。","danger":"caution"},"elasticsearch":{"command":"GET my-index/_search\n{\n  \"profile\": true,\n  \"query\": { \"match\": { \"title\": \"elastic\" } }\n}\n// Persistent slow query tracing: the index.search.slowlog.threshold.query.warn setting","note":"\"profile\": true 给出单次搜索的查询/聚合耗时明细；持续监控请配置 slowlog 阈值。","danger":"safe","readonly":true}}},{"id":"top-wait-events","title":"查看主要等待事件","aliases":["wait events","wait event list","top waits","waits"],"engines":{"oracle":{"command":"SELECT event, total_waits, time_waited, ROUND(time_waited/100,2) AS sec_waited\nFROM v$system_event\nWHERE wait_class <> 'Idle'\nORDER BY time_waited DESC\nFETCH FIRST 20 ROWS ONLY;\n-- For real-time analysis: v$active_session_history (ASH)","note":"v$system_event 给出累计等待事件；v$active_session_history 给出最近几分钟的活动会话采样（需要 Diagnostics Pack 许可）。","danger":"safe","readonly":true,"priv":"v$system_event 上的 SELECT 权限"},"postgres":{"command":"SELECT wait_event_type, wait_event, count(*)\nFROM pg_stat_activity\nWHERE wait_event IS NOT NULL AND state = 'active'\nGROUP BY 1, 2\nORDER BY 3 DESC;","note":"pg_stat_activity 显示当前等待状态；反复采样（轮询）可揭示最频繁的等待事件。","danger":"safe","readonly":true,"priv":"pg_monitor 角色"},"mysql":{"command":"SELECT event_name, count_star, sum_timer_wait,\n       ROUND(sum_timer_wait/1000000000000, 2) AS sec_waited\nFROM performance_schema.events_waits_summary_global_by_event_name\nWHERE count_star > 0\nORDER BY sum_timer_wait DESC\nLIMIT 20;","note":"events_waits_summary_global_by_event_name 给出每个事件的总等待时间（以皮秒为单位）；必须启用等待 instrument。","danger":"safe","readonly":true,"priv":"performance_schema 上的 SELECT 权限"},"mssql":{"command":"SELECT wait_type, waiting_tasks_count, wait_time_ms,\n       signal_wait_time_ms, wait_time_ms - signal_wait_time_ms AS resource_wait_ms\nFROM sys.dm_os_wait_stats\nWHERE wait_time_ms > 0\nORDER BY wait_time_ms DESC;","note":"sys.dm_os_wait_stats 显示自实例启动以来的累计等待类型；可用 DBCC SQLPERF 重置。","danger":"safe","readonly":true,"priv":"VIEW SERVER STATE"},"mongodb":{"command":"db.serverStatus().wiredTiger.concurrentTransactions\n// For general waits/congestion:\n// db.serverStatus().globalLock  and  db.serverStatus().wiredTiger.cache","note":"WiredTiger 没有传统的等待事件；concurrentTransactions（tickets）耗尽、globalLock.currentQueue 和缓存压力表明存在拥塞。","danger":"safe","priv":"clusterMonitor 角色"},"redis":{"command":"CONFIG SET latency-monitor-threshold 100\nLATENCY LATEST\nLATENCY HISTORY command\n# For the slowest commands: SLOWLOG GET 10","note":"设置 latency monitor 阈值后，LATENCY LATEST/HISTORY 会报告造成最大延迟的事件。","danger":"safe"},"elasticsearch":{"command":"GET _nodes/hot_threads","note":"_nodes/hot_threads 给出 CPU 消耗最高的线程的堆栈跟踪，从而揭示瓶颈。","danger":"safe","readonly":true}}},{"id":"os-resource","title":"在数据库内查看服务器资源使用（CPU/IO）","aliases":["os resource","cpu io","resource usage","server resources","system metrics"],"engines":{"oracle":{"command":"SELECT stat_name, value FROM v$osstat\nWHERE stat_name IN ('NUM_CPUS','BUSY_TIME','IDLE_TIME','IOWAIT_TIME','PHYSICAL_MEMORY_BYTES');\n-- Real-time metrics: SELECT metric_name, value FROM v$sysmetric WHERE group_id = 2;","note":"v$osstat 给出操作系统 CPU/内存计数器；v$sysmetric 给出最近一个时间间隔的实时数据库指标（CPU、IOPS）。","danger":"safe","readonly":true,"priv":"v$osstat、v$sysmetric 上的 SELECT 权限"},"postgres":{"command":"SELECT backend_type, object, context, reads, writes, hits, evictions\nFROM pg_stat_io\nORDER BY reads DESC;\n-- Note: CPU usage is not visible from inside the DB; an OS tool (top, sar) is required.","note":"pg_stat_io 给出读/写/缓存命中计数器；PostgreSQL 不提供 CPU 使用率，因此需要操作系统工具。","danger":"safe","readonly":true,"minver":"16"},"mysql":{"command":"SELECT * FROM sys.host_summary;\n-- For InnoDB I/O and thread details:\nSHOW ENGINE INNODB STATUS\\G","note":"sys schema 汇总视图（host_summary）给出 IO/延迟明细；SHOW ENGINE INNODB STATUS 显示缓冲池和 IO 活动。","danger":"safe","readonly":true,"priv":"sys 上的 SELECT 权限、PROCESS"},"mssql":{"command":"SELECT object_name, counter_name, cntr_value\nFROM sys.dm_os_performance_counters\nWHERE counter_name LIKE '%CPU%' OR counter_name LIKE '%Page life expectancy%';\n-- CPU history: sys.dm_os_ring_buffers (RING_BUFFER_SCHEDULER_MONITOR)","note":"sys.dm_os_performance_counters 给出数据库性能计数器；dm_os_ring_buffers 给出最近约 256 分钟内 SQL/其他进程的 CPU 占比。","danger":"safe","readonly":true,"priv":"VIEW SERVER STATE"},"mongodb":{"command":"db.serverStatus().metrics\n// Relevant fields: .opcounters, .network, .wiredTiger.cache, .mem, .extra_info.page_faults","note":"serverStatus() 给出内存、网络、opcounters 和 WiredTiger 缓存指标；详细的操作系统 CPU/IO 请使用 mongostat 或操作系统工具。","danger":"safe","priv":"clusterMonitor 角色"},"redis":{"command":"INFO cpu\nINFO stats\n# used_cpu_sys, used_cpu_user, instantaneous_ops_per_sec, total_net_input_bytes","note":"INFO cpu 报告 Redis 进程的 CPU 消耗，INFO stats 报告每秒操作数和网络流量。","danger":"safe","readonly":true},"elasticsearch":{"command":"GET _nodes/stats/os,process\n// Look at the os.cpu.percent, os.mem, process.cpu.percent, fs (disk) fields","note":"_nodes/stats/os,process 给出每个节点的 CPU 百分比、内存和进程指标；磁盘使用 fs 部分。","danger":"safe","readonly":true}}}]},"rosetta/automation.json":{"category":"自动化与审计","categoryId":"automation","order":13,"concepts":[{"id":"schedule-job","title":"创建计划作业","aliases":["schedule job","scheduled job","cron","create job","scheduled task"],"engines":{"oracle":{"command":"BEGIN\n  DBMS_SCHEDULER.CREATE_JOB(\n    job_name        => 'NIGHTLY_MAINT',\n    job_type        => 'PLSQL_BLOCK',\n    job_action      => 'BEGIN my_maintenance_proc; END;',\n    start_date      => SYSTIMESTAMP,\n    repeat_interval => 'FREQ=DAILY; BYHOUR=3',\n    enabled         => TRUE);\nEND;\n/","note":"DBMS_SCHEDULER.CREATE_JOB 定义计划作业；repeat_interval 通过日历表达式设置重复周期。","danger":"caution","priv":"CREATE JOB"},"postgres":{"command":"SELECT cron.schedule('nightly-vacuum', '0 3 * * *', 'VACUUM ANALYZE');\n-- Requires the pg_cron extension. Alternative: pgAgent.","note":"pg_cron 扩展通过 cron.schedule 以标准 cron 语法定义作业；如未安装该扩展，使用 pgAgent 或操作系统的 cron。","danger":"caution","priv":"已安装 pg_cron；通常需 superuser/扩展所有者"},"mysql":{"command":"SET GLOBAL event_scheduler = ON;\nCREATE EVENT nightly_cleanup\nON SCHEDULE EVERY 1 DAY STARTS '2026-01-01 03:00:00'\nDO\n  DELETE FROM logs WHERE created_at < NOW() - INTERVAL 30 DAY;","note":"CREATE EVENT 通过 event scheduler 运行；全局变量 event_scheduler 必须为 ON，否则事件不会触发。","danger":"caution","priv":"EVENT"},"mssql":{"command":"USE msdb;\nEXEC dbo.sp_add_job @job_name = N'NightlyMaint';\nEXEC dbo.sp_add_jobstep @job_name = N'NightlyMaint', @step_name = N'Step1',\n     @subsystem = N'TSQL', @command = N'EXEC dbo.usp_Maintenance;';\nEXEC dbo.sp_add_schedule @schedule_name = N'Daily3AM',\n     @freq_type = 4, @freq_interval = 1, @active_start_time = 030000;\nEXEC dbo.sp_attach_schedule @job_name = N'NightlyMaint', @schedule_name = N'Daily3AM';\nEXEC dbo.sp_add_jobserver @job_name = N'NightlyMaint';","note":"SQL Server Agent 作业通过 msdb 存储过程创建：定义作业、步骤和计划并附加到服务器。SQL Server Agent 服务必须处于运行状态。","danger":"caution","priv":"SQLAgentUserRole 或 sysadmin"},"mongodb":{"command":"# Not applicable: MongoDB has no built-in scheduler. Use external cron/systemd timers, an application scheduler or a MongoDB Atlas Scheduled Trigger.","note":"Community 版没有计划作业；需要外部调度器或 Atlas Trigger。","danger":"safe"},"redis":{"command":"# Not applicable: Redis has no built-in job scheduler. Use EXPIRE/keyspace notifications for key expiry and external cron for periodic tasks.","note":"Redis 不运行计划作业；这类需求通过 TTL/keyspace 通知和外部 cron 实现。","danger":"safe"},"elasticsearch":{"command":"PUT _slm/policy/daily-snapshot\n{\n  \"schedule\": \"0 30 1 * * ?\",\n  \"name\": \"<snap-{now/d}>\",\n  \"repository\": \"backup_repo\",\n  \"config\": { \"indices\": [\"*\"] },\n  \"retention\": { \"expire_after\": \"30d\" }\n}","note":"SLM（Snapshot Lifecycle Management）执行计划快照；ILM 用于数据生命周期管理，Watcher 用于高级自动化。","danger":"caution","priv":"manage_slm 集群权限"}}},{"id":"list-jobs","title":"列出计划作业 / 查看其状态","aliases":["list jobs","show jobs","job status","scheduled jobs","job history"],"engines":{"oracle":{"command":"SELECT job_name, enabled, state, last_start_date, next_run_date\nFROM dba_scheduler_jobs\nORDER BY next_run_date;\n-- Run history: SELECT job_name, status, actual_start_date, run_duration\n--                 FROM dba_scheduler_job_run_details ORDER BY log_date DESC;","note":"dba_scheduler_jobs 显示已定义的作业及其状态；dba_scheduler_job_run_details 显示历史运行的成功/失败结果。","danger":"safe","readonly":true,"priv":"dba_scheduler_jobs 上的 SELECT 权限"},"postgres":{"command":"SELECT jobid, schedule, command, nodename, active FROM cron.job;\n-- Run history:\nSELECT jobid, status, return_message, start_time, end_time\nFROM cron.job_run_details ORDER BY start_time DESC LIMIT 20;","note":"使用 pg_cron 时，cron.job 列出已定义的作业，cron.job_run_details 给出每次运行的状态和返回消息。","danger":"safe","readonly":true,"priv":"已安装 pg_cron；可访问 cron schema"},"mysql":{"command":"SELECT event_schema, event_name, status, interval_value, interval_field, last_executed\nFROM information_schema.events\nORDER BY last_executed DESC;","note":"information_schema.events 列出已定义的事件、其状态（ENABLED/DISABLED）及最近执行时间。","danger":"safe","readonly":true,"priv":"EVENT / SELECT"},"mssql":{"command":"SELECT j.name, j.enabled, h.run_status, h.run_date, h.run_time\nFROM msdb.dbo.sysjobs j\nLEFT JOIN msdb.dbo.sysjobhistory h ON h.job_id = j.job_id AND h.step_id = 0\nORDER BY h.run_date DESC, h.run_time DESC;","note":"msdb.dbo.sysjobs 给出已定义的 Agent 作业，sysjobhistory 给出运行历史和结果（run_status）。","danger":"safe","readonly":true,"priv":"SQLAgentReaderRole / msdb 访问权限"},"mongodb":{"command":"# Not applicable: there is no built-in scheduler, so there are no jobs to list. If Atlas Triggers are used, view them in the Atlas UI/API.","note":"Community MongoDB 没有作业表；Atlas Triggers 在 Atlas 界面中管理。","danger":"safe"},"redis":{"command":"# Not applicable: Redis has no concept of scheduled jobs, so there are no jobs to list.","note":"Redis 不含作业调度器；没有可列出的作业。","danger":"safe"},"elasticsearch":{"command":"GET _slm/policy\nGET _ilm/policy\n// For Watcher states: GET _watcher/stats  and  GET _watcher/_query/watches","note":"_slm/policy 列出快照策略，_ilm/policy 列出生命周期策略，_watcher/stats 显示 Watcher 的执行状态。","danger":"safe","readonly":true,"priv":"read_slm / read_ilm 权限"}}},{"id":"enable-audit","title":"启用审计 / 日志记录","aliases":["audit","auditing","logging","enable audit","audit log"],"engines":{"oracle":{"command":"-- Unified Auditing (12c+):\nCREATE AUDIT POLICY table_audit\n  ACTIONS SELECT, INSERT, UPDATE, DELETE ON hr.employees;\nAUDIT POLICY table_audit;\n-- Monitoring: SELECT * FROM unified_audit_trail ORDER BY event_timestamp DESC;","note":"使用 Unified Auditing 时，创建策略并用 AUDIT 启用；记录保存在 unified_audit_trail 视图中。","danger":"caution","priv":"AUDIT SYSTEM / AUDIT_ADMIN 角色","minver":"12.1"},"postgres":{"command":"-- pgaudit extension (recommended; first shared_preload_libraries = 'pgaudit' + restart + CREATE EXTENSION pgaudit):\nALTER SYSTEM SET pgaudit.log = 'write, ddl';\nSELECT pg_reload_conf();\n-- Simple built-in method:\nALTER SYSTEM SET log_statement = 'ddl';\nSELECT pg_reload_conf();","note":"pgaudit 提供详细的审计日志；使用 ALTER SYSTEM 设置以在整个服务器范围生效（会话级 SET 只审计该会话）。没有该扩展时，可通过 log_statement（'ddl','mod','all'）启用基本日志记录。注意日志量。","danger":"caution","priv":"superuser（ALTER SYSTEM）"},"mysql":{"command":"-- Enterprise/MariaDB audit plugin:\nINSTALL PLUGIN audit_log SONAME 'audit_log.so';\nSET GLOBAL audit_log_policy = 'ALL';\n-- Simple alternative (all queries):\nSET GLOBAL general_log = 'ON';","note":"audit_log 插件（Enterprise，MariaDB 中为 server_audit）保留详细的审计记录；general_log 记录所有查询，但会降低性能。","danger":"caution","priv":"SUPER / SYSTEM_VARIABLES_ADMIN"},"mssql":{"command":"USE master;\nCREATE SERVER AUDIT Audit_A TO FILE (FILEPATH = 'C:\\\\audit\\\\');\nALTER SERVER AUDIT Audit_A WITH (STATE = ON);\nUSE mydb;\nCREATE DATABASE AUDIT SPECIFICATION db_spec\n  FOR SERVER AUDIT Audit_A\n  ADD (SELECT, INSERT, UPDATE, DELETE ON SCHEMA::dbo BY public)\n  WITH (STATE = ON);","note":"先创建并启用 SERVER AUDIT 目标（文件/事件日志），然后通过服务器级或数据库级 AUDIT SPECIFICATION 定义要跟踪的事件。","danger":"caution","priv":"ALTER ANY SERVER AUDIT / CONTROL SERVER"},"mongodb":{"command":"# MongoDB Enterprise / Atlas only. Configuration (mongod.conf):\n#   auditLog:\n#     destination: file\n#     format: JSON\n#     path: /var/log/mongodb/audit.json\n#     filter: '{ atype: { $in: [\"authCheck\",\"createCollection\",\"dropCollection\"] } }'\n# mongod --auditDestination file --auditPath /var/log/mongodb/audit.json","note":"审计仅在 Enterprise 和 Atlas 中可用；通过 mongod 配置的 auditLog 部分启用。Community 版中不提供。","danger":"caution"},"redis":{"command":"ACL LOG\n# Shows rejected authentication/command/key access attempts.\n# To reset: ACL LOG RESET\n# For general command auditing: an external proxy or MONITOR (with care).","note":"ACL LOG 会自动记录安全事件（认证失败、未授权的命令/键）；没有内置的完整命令审计。","danger":"safe","priv":"ACL 权限（默认为 admin）"},"elasticsearch":{"command":"# elasticsearch.yml (on all nodes, restart required):\nxpack.security.audit.enabled: true\n# Optional event filter:\nxpack.security.audit.logfile.events.include: [ \"authentication_failed\", \"access_denied\" ]","note":"审计日志通过 xpack.security.audit.enabled 启用；在 elasticsearch.yml 中设置，需要重启节点（非动态）。需要商业许可。","danger":"caution"}}},{"id":"current-config-diff","title":"列出非默认设置","aliases":["config diff","changed settings","non-default settings","modified settings","modified config"],"engines":{"oracle":{"command":"SELECT name, value, isdefault\nFROM v$parameter\nWHERE isdefault = 'FALSE'\nORDER BY name;","note":"v$parameter 中 isdefault = 'FALSE' 的行显示与默认值不同（显式设置）的参数。","danger":"safe","readonly":true,"priv":"v$parameter 上的 SELECT 权限"},"postgres":{"command":"SELECT name, setting, unit, source\nFROM pg_settings\nWHERE source NOT IN ('default', 'override')\nORDER BY name;","note":"如果 pg_settings.source 不是 'default' 和 'override'，说明该设置已被配置文件、命令行或会话修改。","danger":"safe","readonly":true},"mysql":{"command":"SHOW GLOBAL VARIABLES;\n-- MySQL does not automatically compare variables with defaults; with performance_schema\n-- you can see the source of each setting:\nSELECT VARIABLE_NAME, VARIABLE_SOURCE, VARIABLE_VALUE\nFROM performance_schema.variables_info\nWHERE VARIABLE_SOURCE <> 'COMPILED';","note":"SHOW GLOBAL VARIABLES 返回所有设置；它不会直接显示与默认值的差异。performance_schema.variables_info 的 VARIABLE_SOURCE 列显示除编译默认值以外的变更来源（文件、命令行、动态）。","danger":"safe","readonly":true,"minver":"8.0"},"mssql":{"command":"SELECT name, value, value_in_use, is_dynamic, is_advanced\nFROM sys.configurations\nWHERE value <> value_in_use\nORDER BY name;\n-- value <> value_in_use: changes not yet RECONFIGUREd or awaiting a restart (is_dynamic = 0).","note":"sys.configurations 返回服务器设置；如果 value 与 value_in_use 不同，说明存在尚未通过 RECONFIGURE 应用（或等待重启）的变更。sys.configurations 不存储默认值（minimum/maximum 只是允许范围）；要查看与默认值的差异，需将值与文档中的默认值进行比较。","danger":"safe","readonly":true,"priv":"VIEW SERVER STATE"},"mongodb":{"command":"db.adminCommand({ getCmdLineOpts: 1 })\n// The 'parsed' field shows settings explicitly provided (configuration file/command line) beyond the defaults.","note":"getCmdLineOpts 命令的 'parsed' 部分只列出显式指定（非默认）的设置。","danger":"safe","priv":"clusterMonitor / hostInfo 权限"},"redis":{"command":"CONFIG GET *\n# Redis lists all settings but does not compare them with defaults.\n# To see differences, compare the output with the default redis.conf values for your version.","note":"CONFIG GET * 返回所有运行中的设置；它没有内置与默认值的对比，因此需将输出与相应版本的默认 redis.conf 值进行比较。","danger":"safe","readonly":true},"elasticsearch":{"command":"GET _cluster/settings\n// Returns only explicitly set persistent and transient settings.\n// For everything including defaults: GET _cluster/settings?include_defaults=true&flat_settings=true","note":"_cluster/settings 返回非默认（persistent/transient）的集群设置；使用 include_defaults=true 时也会显示默认值以便比较。","danger":"safe","readonly":true}}}]},"rosetta/lifecycle.json":{"category":"安装与升级","categoryId":"lifecycle","order":14,"concepts":[{"id":"check-version","title":"显示已安装的版本和补丁级别","aliases":["version","patch level","server version","patch level check","release","build"],"engines":{"oracle":{"command":"SELECT * FROM v$version;\n-- Registered RU/patch history (SQL layer):\nSELECT patch_id, action, status, action_time\nFROM dba_registry_sqlpatch ORDER BY action_time;\n-- From the OS shell (patches installed in ORACLE_HOME):\n-- $ORACLE_HOME/OPatch/opatch lspatches","note":"v$version banner 给出版本。dba_registry_sqlpatch 显示已应用的 Release Update（RU）和 one-off 补丁是否已在 SQL 层注册，而 'opatch lspatches' 显示 home 中的二进制补丁。","danger":"safe","readonly":true,"priv":"v$version、dba_registry_sqlpatch 上的 SELECT 权限"},"postgres":{"command":"SELECT version();\n-- Numeric version only:\nSHOW server_version;\nSELECT current_setting('server_version_num');","note":"version() 返回包含构建信息的完整文本。PostgreSQL 没有单独的补丁号；小版本号（如 16.3）即代表修复级别。","danger":"safe","readonly":true},"mysql":{"command":"SELECT VERSION();\n-- Detailed build information:\nSHOW VARIABLES LIKE 'version%';","note":"VERSION() 返回如 '8.0.36'；最后一部分为补丁（release）级别。version_comment 变量表明构建版本是 Community/Enterprise 还是 MariaDB。","danger":"safe","readonly":true},"mssql":{"command":"SELECT @@VERSION;\n-- Structured version fields:\nSELECT SERVERPROPERTY('ProductVersion')       AS version,\n       SERVERPROPERTY('ProductLevel')         AS service_pack,\n       SERVERPROPERTY('ProductUpdateLevel')   AS cu_level,\n       SERVERPROPERTY('ProductUpdateReference') AS kb_number,\n       SERVERPROPERTY('Edition')              AS edition;","note":"ProductUpdateLevel 给出已应用的 Cumulative Update（如 CU12），ProductUpdateReference 给出相关 KB 编号。ProductLevel 显示 'RTM'，旧版本上显示 'SP'。","danger":"safe","readonly":true},"mongodb":{"command":"db.version()\n// Detailed build information:\ndb.serverBuildInfo()\n// or: db.adminCommand({ buildInfo: 1 })","note":"db.version() 返回版本字符串（如 '7.0.11'）。在 MongoDB 中，修复以新的补丁版本发布；没有单独的“补丁级别”概念。","danger":"safe"},"redis":{"command":"INFO server\n# redis-cli one-liner: redis-cli INFO server | grep redis_version","note":"INFO server 输出中的 redis_version 字段给出已安装的版本。在 Redis 中，修复以新的补丁版本发布（如 7.2.5）；redis_git_sha1 字段还显示构建标识。","danger":"safe"},"elasticsearch":{"command":"GET /\n# curl: curl -s http://localhost:9200/","note":"根端点返回 version.number（如 '8.14.1'）、lucene_version 和 build_flavor。查看所有节点的版本：GET _cat/nodes?v&h=name,version","danger":"safe","readonly":true}}},{"id":"check-upgrade-readiness","title":"升级前兼容性/就绪检查","aliases":["upgrade readiness","preupgrade","compatibility check","readiness","compatibility check","deprecation"],"engines":{"oracle":{"command":"-- Recommended method for 19c and later (AutoUpgrade analyze mode):\n-- $ java -jar autoupgrade.jar -config db.cfg -mode analyze\n-- Legacy/alternative: preupgrade script (produces a report only):\n-- $ $NEW_ORACLE_HOME/jdk/bin/java -jar $NEW_ORACLE_HOME/rdbms/admin/preupgrade.jar FILE TEXT DIR /tmp","note":"AutoUpgrade 'analyze' 模式在不修改数据的情况下生成升级前报告；列出必须修复的警告以及将自动解决的项目。务必在正式升级前运行。","danger":"safe"},"postgres":{"command":"# With the new version's binaries, compatibility check only (moves no data):\n$ pg_upgrade \\\n    --old-datadir=/var/lib/pgsql/16/data \\\n    --new-datadir=/var/lib/pgsql/17/data \\\n    --old-bindir=/usr/pgsql-16/bin \\\n    --new-bindir=/usr/pgsql-17/bin \\\n    --check","note":"--check 在不修改任何数据的情况下验证升级是否可行（报告缺失的扩展、不兼容的数据类型等）。--check 也可在旧集群运行时执行；正式升级需在两个集群都停止的情况下进行。","danger":"safe"},"mysql":{"command":"-- 8.0 and later: Upgrade Checker utility (MySQL Shell):\n-- $ mysqlsh -- util check-for-server-upgrade { --user=root --host=localhost }\n-- Table consistency for legacy 5.7->8.0:\n$ mysqlcheck --all-databases --check-upgrade -u root -p","note":"MySQL Shell 的 util.checkForServerUpgrade() 会报告已移除的功能、保留字冲突和不兼容的定义。从 8.0 起，服务器在启动时自行完成数据字典升级；不再需要单独运行 mysql_upgrade。","danger":"safe"},"mssql":{"command":"-- Run from the desktop with Data Migration Assistant (DMA):\n-- DmaCmd.exe /AssessmentName=... /AssessmentDatabases=...\n-- (Produces compatibility/deprecated feature and blocker reports.)","note":"Data Migration Assistant（DMA）取代了旧的 Upgrade Advisor；它按数据库报告已弃用的功能、重大变更和阻碍因素。在升级前运行并解决发现的问题。","danger":"safe"},"mongodb":{"command":"// Verify the current Feature Compatibility Version (FCV):\ndb.adminCommand({ getParameter: 1, featureCompatibilityVersion: 1 })\n// Compatibility items (deprecations) in the release notes are reviewed manually.","note":"升级二进制版本之前，FCV 必须等于当前版本；否则升级会被阻止。MongoDB 没有自动的“弃用扫描器”；需手动查阅版本兼容性说明。","danger":"safe"},"redis":{"command":"# Not applicable: Redis has no separate pre-upgrade readiness tool.\n# Checklist: read the target version's release notes for RDB/AOF persistence format,\n# changed configuration directives, and removed commands/arguments.","note":"Redis 升级通常向前兼容；由于没有官方的就绪扫描工具，需根据发行说明手动检查 RDB/AOF 文件格式、已移除的配置指令和命令变更。","danger":"safe"},"elasticsearch":{"command":"GET _migration/deprecations\n# For a specific component: GET <index>/_migration/deprecations","note":"Deprecation Info API 以 'critical' / 'warning' 列出升级到下一个大版本时会引发问题的集群、节点、索引和 ML 设置。Kibana Upgrade Assistant 以可视化方式展示相同数据。","danger":"safe","readonly":true}}},{"id":"apply-patch","title":"应用补丁","aliases":["apply patch","patching","patch","minor upgrade","hotfix","ru apply"],"engines":{"oracle":{"command":"# 1) Shut down the database, apply the binary patch with OPatch:\n$ $ORACLE_HOME/OPatch/opatch apply\n# 2) Open the database and load the SQL layer changes:\n$ $ORACLE_HOME/OPatch/datapatch -verbose","note":"opatch apply 更新 Oracle Home 中的二进制文件；随后 datapatch 在打开的数据库上应用数据字典/SQL 变更，并记录到 dba_registry_sqlpatch 中。在 RAC 上，支持滚动安装的补丁可逐节点应用。","danger":"caution","priv":"操作系统：ORACLE_HOME 所有者；SQL：SYSDBA"},"postgres":{"command":"# Minor version patch = replace binaries + restart (no dump needed):\n$ sudo systemctl stop postgresql-16\n$ sudo dnf update postgresql16-server   # or apt upgrade\n$ sudo systemctl start postgresql-16","note":"同一大版本系列内的小版本补丁（16.3 -> 16.4）不会改变数据格式：更新软件包并重启服务即可；无需 pg_upgrade 或 dump/restore。","danger":"caution"},"mysql":{"command":"# Minor/patch update: update the package and restart the service\n$ sudo systemctl stop mysqld\n$ sudo dnf update mysql-community-server   # or apt upgrade\n$ sudo systemctl start mysqld","note":"MySQL 8.0+ 在启动时自动升级数据字典；无需单独的 mysql_upgrade 步骤。在复制环境中，先给副本打补丁，再给源库打补丁。","danger":"caution"},"mssql":{"command":"-- Run the Cumulative Update (CU) or GDR security update installer:\n-- SQLServer2022-KB<no>-x64.exe /quiet /IAcceptSQLServerLicenseTerms\n--   /Action=Patch /InstanceName=MSSQLSERVER\n-- The instance then restarts; verify the version with SERVERPROPERTY.","note":"CU（Cumulative Update）和 GDR（General Distribution Release）更新通过 setup 安装程序应用，需要重启实例。在 Always On 组中，先给辅助副本打补丁，再进行故障转移，以尽量缩短停机时间。","danger":"caution"},"mongodb":{"command":"// 'Rolling' patch on a replica set: one node at a time\n// 1) Stop mongod, replace the binary with the new patch version, restart\n// 2) Secondaries first, the primary last\n// Step down the primary manually before patching it:\nrs.stepDown()","note":"同一大版本内的补丁更新（7.0.x）以滚动方式进行，先从节点。主节点在通过 rs.stepDown() 移交后再打补丁，从而尽量缩短写入停机时间。","danger":"caution"},"redis":{"command":"# Patch via rolling restart in a replicated deployment:\n# 1) Stop one replica node, install the new version, start it, wait for sync\n# 2) Repeat for the remaining replicas\n# 3) Finally, fail over the master and patch it:\nredis-cli -h <replica> REPLICAOF NO ONE   # promote the upgraded replica","note":"补丁版本（7.2.x）通过滚动重启应用：先升级副本并重新同步，然后将一个副本提升为 master，再给旧 master 打补丁。Sentinel/Cluster 可自动完成故障转移。","danger":"caution"},"elasticsearch":{"command":"# Rolling upgrade (patch within the same major version), per node:\nPUT _cluster/settings\n{ \"persistent\": { \"cluster.routing.allocation.enable\": \"primaries\" } }\n# -> stop the node, update the package, start the node, then:\nPUT _cluster/settings\n{ \"persistent\": { \"cluster.routing.allocation.enable\": null } }","note":"补丁/小版本升级通过滚动升级完成：限制分片分配，逐个更新并重启节点，然后重新启用分配，等待集群恢复为 green。","danger":"caution"}}},{"id":"upgrade-major","title":"大版本升级","aliases":["major upgrade","major version","version upgrade","release upgrade","version upgrade"],"engines":{"oracle":{"command":"# Recommended: AutoUpgrade (analyze + fixups + deploy in a single flow):\n$ java -jar $NEW_ORACLE_HOME/rdbms/admin/autoupgrade.jar -config db.cfg -mode deploy\n# Graphical alternative: DBUA (Database Upgrade Assistant)\n$ $NEW_ORACLE_HOME/bin/dbua","note":"AutoUpgrade 通过一条命令完成分析、预修复、升级和后续步骤；可依次处理多个数据库以缩短停机时间。DBUA 以向导界面完成同样的工作。事先进行完整备份是必须的。","danger":"danger","priv":"SYSDBA + 操作系统：新 ORACLE_HOME 所有者"},"postgres":{"command":"# pg_upgrade to the new major version (with both clusters stopped):\n$ pg_upgrade \\\n    --old-datadir=/var/lib/pgsql/16/data \\\n    --new-datadir=/var/lib/pgsql/17/data \\\n    --old-bindir=/usr/pgsql-16/bin \\\n    --new-bindir=/usr/pgsql-17/bin \\\n    --link   # fast migration with hard links\n# Then gather statistics (analyze_new_cluster.sh was removed in PG14):\n$ vacuumdb --all --analyze-in-stages","note":"pg_upgrade 原地执行大版本迁移（16 -> 17）；--link 模式通过创建硬链接而非复制文件来缩短时间，但会使旧集群失效。迁移后必须重新收集统计信息。","danger":"danger"},"mysql":{"command":"# In-place upgrade (same datadir, new binaries):\n$ sudo systemctl stop mysqld\n$ sudo dnf install mysql-community-server-8.4   # replacing the old package\n$ sudo systemctl start mysqld   # the server upgrades the dictionary automatically","note":"从 MySQL 8.x 起支持原地大版本升级：使用新二进制文件启动的服务器会自动转换数据字典和系统表（旧的单独 mysql_upgrade 步骤已被移除）。每次只升级一个大版本。","danger":"danger"},"mssql":{"command":"-- In-place major version upgrade (with setup):\n-- setup.exe /q /ACTION=upgrade /INSTANCENAME=MSSQLSERVER\n--   /IAcceptSQLServerLicenseTerms\n-- Alternative: install a new instance side by side and move the databases (restore/attach).","note":"setup /ACTION=upgrade 将实例原地升级到新的大版本。更安全的方法是并行安装（side-by-side），再通过 backup/restore 迁移数据库，并在单独的步骤中提升兼容级别。","danger":"danger"},"mongodb":{"command":"// Stepwise major upgrade (e.g. 6.0 -> 7.0), one major version at a time:\n// 1) Roll the binary to 7.0 on all nodes\n// 2) Once the cluster is stable, raise the FCV:\ndb.adminCommand({ setFeatureCompatibilityVersion: \"7.0\", confirm: true })","note":"MongoDB 的大版本升级分两个阶段：先将所有节点的二进制文件滚动升级到新版本，待集群稳定后再将 FCV 提升到新版本。每次只能升级一个大版本；FCV 单独管理，以保留降级能力。","danger":"danger"},"redis":{"command":"# Major upgrade via a replica (no downtime):\n# 1) Create a replica on the new version, wait for full sync from the master\n# 2) Prepare the application; fail over the master:\nredis-cli -h <new-replica> REPLICAOF NO ONE   # make the new version the master\n# 3) Upgrade the old master, attach it as a replica of the new master","note":"Redis 大版本迁移（6.x -> 7.x）的做法是：在新版本上搭建副本，等其同步后提升为 master；写入停机时间因此几乎降为零。RDB 文件与后续版本兼容。","danger":"danger"},"elasticsearch":{"command":"# Major versions cannot be skipped; one major at a time, via the last minor first (e.g. 6.8 -> 7.17 -> 8.x):\n# 1) Upgrade to the last minor (7.17), resolve the GET _migration/deprecations warnings\n# 2) Rolling upgrade, per node: set allocation to \"primaries\" -> update the node -> start -> wait for green\n# Alternative: full-cluster restart (stop all nodes -> upgrade -> start together); this also moves only one major","note":"Elasticsearch 不支持以任何方式跳过大版本：滚动升级和全集群重启都只能升级到下一个大版本（n -> n+1），且要求源集群处于最后一个小版本（如 7.17）。两个大版本之前创建的索引（如 6.x 中创建的）无法在新版本中打开；必须在迁移前用 _reindex 重建（8.x 只能以只读 archive 索引的方式有限读取）。迁移前先做快照并解决弃用警告。","danger":"danger"}}},{"id":"set-compatibility","title":"设置兼容级别 / 功能版本","aliases":["compatibility level","database compatibility level","feature version","fcv","compatible","compat level"],"engines":{"oracle":{"command":"-- Raise the COMPATIBLE initialization parameter (irreversible!):\nALTER SYSTEM SET COMPATIBLE = '19.0.0' SCOPE=SPFILE;\n-- Then restart the instance. To see the current value:\nSHOW PARAMETER compatible;","note":"COMPATIBLE 参数决定启用哪个版本的功能和文件格式。一旦提升并重启实例，就无法回退（降级被阻止）；因此在确认升级成功之前应逐步提升。","danger":"danger","priv":"ALTER SYSTEM（SYSDBA）"},"postgres":{"command":"# Not applicable: PostgreSQL has no database compatibility level.\n# If legacy behavior is needed, set the relevant GUCs (e.g. SET standard_conforming_strings=off)\n# or manage the major migration with dump/restore.","note":"PostgreSQL 没有类似 SQL Server/Oracle 的“兼容级别”概念；版本行为直接与二进制版本绑定。版本间迁移使用 pg_dump/pg_restore 或 pg_upgrade。","danger":"safe"},"mysql":{"command":"# Not applicable: MySQL has no configurable database compatibility level.\n# Behavioral differences are managed with sql_mode:\nSET GLOBAL sql_mode = 'STRICT_TRANS_TABLES,NO_ENGINE_SUBSTITUTION';","note":"MySQL 没有基于版本的兼容级别；SQL 行为主要取决于二进制版本和 sql_mode 设置。可微调 sql_mode 以保留旧应用的行为。","danger":"caution"},"mssql":{"command":"-- Set the database compatibility level (e.g. SQL Server 2022 = 160):\nALTER DATABASE Sales SET COMPATIBILITY_LEVEL = 160;\n-- To see the current value:\nSELECT name, compatibility_level FROM sys.databases;","note":"COMPATIBILITY_LEVEL 决定查询优化器和语言功能按哪个版本的行为运行（150=2019，160=2022）。实例升级后，数据库可保持在旧级别；建议逐步提升并监控执行计划回归。可以再次降低。","danger":"caution"},"mongodb":{"command":"// Set the Feature Compatibility Version (FCV):\ndb.adminCommand({ setFeatureCompatibilityVersion: \"7.0\", confirm: true })\n// Read the current value:\ndb.adminCommand({ getParameter: 1, featureCompatibilityVersion: 1 })","note":"FCV 控制随版本而来的持久化数据格式变更和新功能何时启用。在二进制升级完成、集群稳定后再提升；只有当同一二进制版本仍支持较低的 FCV 时才能降低。","danger":"caution","priv":"clusterManager / root 角色"},"redis":{"command":"# Not applicable: Redis has no compatibility level / feature version concept.\n# Behavior depends directly on the running binary version.","note":"Redis 没有可配置的兼容级别；功能集完全取决于运行的版本。兼容性仅在 RDB/AOF 文件格式层面向前保持。","danger":"safe"},"elasticsearch":{"command":"# Not applicable: Elasticsearch has no configurable compatibility level.\n# An index is tied to the format of the version it was created in; very old indices\n# cannot be read in the new version and require a reindex.","note":"Elasticsearch 没有兼容级别设置；每个索引都带有其创建时所在大版本的格式。上一大版本中创建的索引可以读取，更早的索引则必须用 _reindex 迁移到新版本。","danger":"safe"}}},{"id":"post-upgrade-tasks","title":"升级后任务（重新编译/分析）","aliases":["post upgrade","post-upgrade","recompile","analyze","refresh statistics","invalid objects"],"engines":{"oracle":{"command":"-- 1) Compile invalid objects in parallel:\n@?/rdbms/admin/utlrp.sql\n-- 2) Refresh dictionary/fixed object statistics:\nEXEC DBMS_STATS.GATHER_DICTIONARY_STATS;\nEXEC DBMS_STATS.GATHER_FIXED_OBJECTS_STATS;\n-- Check remaining invalid objects:\nSELECT owner, object_name, object_type FROM dba_objects WHERE status='INVALID';","note":"utlrp.sql 会重新编译升级后变为 INVALID 的包/视图/触发器。之后收集数据字典和固定对象统计信息，可让优化器在新版本中生成合理的执行计划。","danger":"caution","priv":"SYSDBA"},"postgres":{"command":"# 1) Regather statistics after pg_upgrade (mandatory):\n$ vacuumdb --all --analyze-in-stages\n-- 2) Bring extensions to the new version:\nALTER EXTENSION postgis UPDATE;\n-- 3) If the collation changed, rebuild text indexes (can take long):\nREINDEX DATABASE database_name;","note":"pg_upgrade（PG17 及更早版本）不会迁移规划器统计信息；analyze-in-stages 先收集粗略统计信息、再收集完整统计信息，使系统尽快可用。扩展必须通过 ALTER EXTENSION ... UPDATE 刷新；若排序规则（collation）发生变化，索引需执行 REINDEX。","danger":"caution"},"mysql":{"command":"-- 8.0+ dictionary upgrade happens automatically at startup; additionally:\nANALYZE TABLE customer;   -- refresh statistics of critical tables\n-- Legacy 5.7 -> if needed on first startup:\n-- $ mysql_upgrade -u root -p   (before 8.0.16)","note":"在 MySQL 8.0.16 及更高版本中，mysql_upgrade 的功能已移入服务器，因此无需单独运行。升级后，对大型/关键表执行 ANALYZE TABLE 刷新统计信息可保持执行计划质量。","danger":"safe"},"mssql":{"command":"-- 1) Correct page/row usage counters:\nDBCC UPDATEUSAGE(Sales);\n-- 2) Refresh statistics (sp_updatestats uses sampling; FULLSCAN for critical tables):\nEXEC sp_updatestats;\n-- UPDATE STATISTICS dbo.CriticalTable WITH FULLSCAN;\n-- 3) Raise the compatibility level to the new version (while monitoring):\nALTER DATABASE Sales SET COMPATIBILITY_LEVEL = 160;","note":"升级后，DBCC UPDATEUSAGE 可修正从旧版本带来的不准确的空间/行计数器，sp_updatestats 可刷新统计信息。提升兼容级别时，需监控新基数估算器（CE）可能导致的执行计划回归。","danger":"caution"},"mongodb":{"command":"// 1) Verify the FCV upgrade:\ndb.adminCommand({ getParameter: 1, featureCompatibilityVersion: 1 })\n// 2) Check indexes and compatibility warnings in the server logs:\ndb.customer.getIndexes()\n// If an index is incompatible: dropIndex + createIndex (rolling); do NOT use reIndex","note":"大版本升级后，确认 FCV 已达到目标版本。重建索引不是常规的升级步骤；如果发行说明中点名了某个索引，或日志中出现相关警告，用 dropIndex + createIndex 重新创建。在 6.0+ 上，reIndex 仅在独立实例上可用，副本集成员上不允许。","danger":"caution"},"redis":{"command":"# Usually no extra task is needed. Recommended: verify persistence and replication:\nredis-cli INFO replication   # is the master/replica link healthy\nredis-cli INFO persistence   # rdb_last_bgsave_status / aof_last_bgrewrite_status\nredis-cli DBSIZE             # is the key count as expected","note":"Redis 升级后没有 schema 编译/分析任务；要做的是确认复制已重新建立、RDB/AOF 持久化正常且键数量未变。","danger":"safe"},"elasticsearch":{"command":"# Verify cluster health:\nGET _cluster/health\n# Move indices created in an old major version that can no longer be read:\nPOST _reindex\n{ \"source\": { \"index\": \"old_index\" }, \"dest\": { \"index\": \"new_index\" } }","note":"升级后，确认集群状态为 'green' 且所有分片均已分配。格式早于上一大版本的索引无法在新版本中读取，因此必须用 _reindex 迁移到当前格式。","danger":"caution"}}}]},"rosetta/migration.json":{"category":"迁移与传输","categoryId":"migration","order":15,"concepts":[{"id":"compare-schema","title":"比较两个 schema/环境的差异（schema diff）","aliases":["schema diff","diff","compare schemas","compare","schema difference","ddl diff"],"engines":{"oracle":{"command":"-- Extract the schema DDL from both environments and take a text diff\n-- (generate deterministic DDL with DBMS_METADATA)\nEXEC DBMS_METADATA.SET_TRANSFORM_PARAM(DBMS_METADATA.SESSION_TRANSFORM,'SQLTERMINATOR',TRUE);\nEXEC DBMS_METADATA.SET_TRANSFORM_PARAM(DBMS_METADATA.SESSION_TRANSFORM,'STORAGE',FALSE);\nSELECT DBMS_METADATA.GET_DDL('TABLE', table_name) FROM user_tables ORDER BY table_name;\n-- Spool the output to a file in both environments and compare with diff/fc.\n\n-- Export the schema as a changelog with SQLcl's Liquibase integration:\n-- SQL> lb generate-schema\n-- Commercial: the Oracle SQL Developer 'Database Diff' tool is also used.","note":"Oracle 没有内置的单命令 schema 比较。常用方法是用 DBMS_METADATA.GET_DDL（关闭 STORAGE/段属性）从两个环境生成确定性的 DDL，再做文本 diff。可视化/自动化比较可使用 SQL Developer 的 Database Diff 向导或 SQLcl。","danger":"safe","readonly":true,"priv":"SELECT_CATALOG_ROLE 或该 schema 的读取权限"},"postgres":{"command":"# Dump only the schema from both databases and diff them\npg_dump --schema-only --no-owner --no-privileges sourcedb > source_schema.sql\npg_dump --schema-only --no-owner --no-privileges targetdb  > target_schema.sql\ndiff -u source_schema.sql target_schema.sql\n\n# Tool that generates applicable ALTERs: apgdiff (Another PostgreSQL Diff Tool)\n# apgdiff source_schema.sql target_schema.sql > migration.sql\n# Alternatives: migra, Liquibase diffChangeLog","note":"最简单的方法是在两个环境中获取 pg_dump --schema-only 输出并用 diff 比较。apgdiff 和 migra 工具可将差异直接转换为可执行的 ALTER 语句。添加 --no-owner --no-privileges 可获得确定性输出。","danger":"safe","readonly":true,"priv":"SELECT / schema 读取权限（pg_dump 需要对象访问权限）"},"mysql":{"command":"# Schema/object comparison with MySQL Utilities (5.x):\nmysqldbcompare --server1=root:password@host1 --server2=root:password@host2 \\\n  shop:shop --difftype=sql --run-all-tests\n\n# Built-in method: take CREATE statements from both environments and diff them\nmysqldump --no-data --skip-comments shop > source_schema.sql\n# SHOW CREATE TABLE customer;  (to see a single table definition)","note":"MySQL Utilities 中的 mysqldbcompare 工具可报告两台服务器之间的对象/schema 差异（difftype=sql 时输出可执行语句）。不借助工具的方法是从两个环境获取 mysqldump --no-data（仅 schema）或 SHOW CREATE TABLE 输出，再用 diff 比较。mysqldbcompare 现已弃用；对于 MariaDB/8.x，推荐使用 Liquibase 或 Skeema。","danger":"safe","readonly":true,"priv":"两个 schema 上的 SELECT、SHOW VIEW 权限"},"mssql":{"command":"-- Schema comparison/deploy report with SqlPackage (what will change before applying):\nsqlpackage /Action:DeployReport /SourceFile:source.dacpac \\\n  /TargetConnectionString:\"Server=target;Database=shop;Integrated Security=true\" \\\n  /OutputPath:diff_report.xml\n\n-- For data differences (row level), the tablediff utility:\ntablediff -sourceserver S1 -sourcedatabase shop -sourcetable customer \\\n  -destinationserver S2 -destinationdatabase shop -destinationtable customer","note":"对于 schema 差异，SqlPackage /Action:DeployReport 以 XML 报告返回需应用到目标的变更；/Action:Script 生成可执行的差异脚本。Visual Studio / Azure Data Studio 的“架构比较”使用相同的引擎。行级数据差异可使用 tablediff 命令行工具。","danger":"safe","readonly":true,"priv":"VIEW DEFINITION（DeployReport 需要目标的读取权限）"},"mongodb":{"command":"// The schema is implicit; extract and compare the field structure of both environments.\n// Read the $jsonSchema validator definition (collection rules):\ndb.getCollectionInfos({ name: \"customer\" })[0].options.validator\n\n// External tools for inferred schema comparison by sampling:\n// mongodb-schema (npm) or the Compass 'Schema' tab\n// Take samples with mongoexport and diff the field/type distribution of both environments.","note":"MongoDB 是无 schema 的；没有固定的 DDL。比较有两种方式：比较集合上定义的 $jsonSchema 验证规则（通过 getCollectionInfos），或用 mongodb-schema / Compass 从数据推断 schema，并比较字段和类型分布。索引方面，对 db.collection.getIndexes() 的输出做 diff。","danger":"safe","readonly":true,"priv":"read（getCollectionInfos、listIndexes）"},"redis":{"command":"# Not applicable: Redis is a schemaless key-value store; there are no\n# table/column definitions (DDL) to compare.\n# Closest equivalent: compare the key space (namespace) structure\n# redis-cli --scan --pattern 'customer:*' | sort > source_keys.txt","note":"不适用：Redis 没有 schema 概念（表/列/DDL），因此传统的 schema diff 不适用。如需结构比较，只能用 SCAN 列出键命名模式（namespace）进行比较。","danger":"safe","readonly":true},"elasticsearch":{"command":"# Fetch the index mapping from both clusters and compare\ncurl -s 'http://source:9200/customer/_mapping?pretty' > source_mapping.json\ncurl -s 'http://target:9200/customer/_mapping?pretty'  > target_mapping.json\ndiff -u source_mapping.json target_mapping.json\n\n# To compare settings as well:\n# GET customer/_settings   and   GET customer/_mapping","note":"在 Elasticsearch 中，schema 的等价物是索引 mapping。标准方法是从两个集群获取 GET <index>/_mapping 输出（必要时加上 _settings），再用 diff 比较。由于 mapping 字段大多不可变，发现的差异可能需要新建索引 + _reindex。","danger":"safe","readonly":true,"priv":"view_index_metadata / monitor"}}},{"id":"export-for-migration","title":"用于迁移的一致性导出","aliases":["export","consistent export","consistent export","take a dump","migration export","snapshot"],"engines":{"oracle":{"command":"# Consistent logical export with Data Pump (at a single SCN)\nexpdp system/password schemas=shop directory=DIR_DMP \\\n  dumpfile=shop_%U.dmp logfile=shop_exp.log \\\n  flashback_scn=<scn> parallel=4\n\n# Current SCN for point-in-time consistency:\n# SELECT current_scn FROM v$database;\n# Alternative: FLASHBACK_TIME=SYSTIMESTAMP","note":"Data Pump（expdp）是用于迁移的标准逻辑导出工具。为保证读一致性，传入 FLASHBACK_SCN=<scn>（或 FLASHBACK_TIME），使所有表在同一个 SCN 上一致导出；这在在线系统上至关重要。PARALLEL 可加快速度，%U 生成多个文件。","danger":"caution","readonly":true,"priv":"DATAPUMP_EXP_FULL_DATABASE、DIRECTORY 对象","minver":"10g（Data Pump）"},"postgres":{"command":"# Single compressed file in custom format (restorable in parallel)\npg_dump -Fc -Z6 -f shop.dump shop\n\n# Directory format (parallel dump + parallel restore):\npg_dump -Fd -j4 -f shop_dir shop\n\n# For the whole cluster + roles/tablespaces:\n# pg_dumpall --globals-only > roles.sql","note":"迁移时优先使用 pg_dump -Fc（custom）或 -Fd（directory）格式；两者都支持用 pg_restore 进行选择性/并行加载。由于 pg_dump 在单个事务内运行，其输出自动就是一致的快照。角色和表空间等集群级对象需用 pg_dumpall --globals-only 单独导出。","danger":"safe","readonly":true,"priv":"SELECT（针对所有需导出的对象）"},"mysql":{"command":"# Consistent, lock-free export for InnoDB\nmysqldump --single-transaction --quick --routines --triggers --events \\\n  --source-data=2 shop > shop.sql\n\n# Note: older versions use --master-data=2 instead of --source-data.\n# Parallel, fast alternative: mydumper -B shop -o ./backup --threads 4","note":"在 InnoDB 上，--single-transaction 提供单一一致性快照且不锁表。--source-data=2（原 --master-data=2）会在输出顶部以注释写入启动复制/CDC 所需的 binlog 位置/GTID 信息；这对于零停机切换必不可少。大数据量时，mydumper 可并行运行。","danger":"safe","readonly":true,"priv":"SELECT、LOCK TABLES、RELOAD、REPLICATION CLIENT（用于 --source-data）"},"mssql":{"command":"-- Full backup for physical migration (to the same/compatible version):\nBACKUP DATABASE shop TO DISK = 'C:\\backup\\shop.bak' WITH COMPRESSION, CHECKSUM;\n\n-- Portable/version-independent logical export (BACPAC = schema+data):\n-- sqlpackage /Action:Export /SourceConnectionString:\"...\" /TargetFile:shop.bacpac\n\n-- For a single table: bcp shop.dbo.customer out customer.dat -n -S server -T","note":"迁移到相同或更新版本的 SQL Server 时，BACKUP ... RESTORE 是最可靠的途径（使用 CHECKSUM 验证完整性）。对于与版本/平台无关的迁移或迁移到 Azure SQL，推荐使用 SqlPackage /Action:Export 将 schema+数据导出为单个 .bacpac 文件。单张表使用 bcp。","danger":"safe","readonly":true,"priv":"BACKUP DATABASE（db_backupoperator）或 Export 所需的读取权限"},"mongodb":{"command":"# Consistent binary (BSON) export; point-in-time consistency with the oplog\nmongodump --uri=\"mongodb://source:27017\" \\\n  --oplog --out=./backup --numParallelCollections=4\n\n# --oplog captures changes made during the dump (requires a replica set);\n# it works only with a FULL dump and cannot be combined with --db / --collection.\n# On restore, mongorestore --oplogReplay brings it to a single point in time.\n# If you need a single database: take it with --db=shop (without the oplog) or use --nsInclude='shop.*' on restore.","note":"mongodump 是用于迁移的标准二进制（BSON）导出。在副本集上，--oplog 选项会从 oplog 捕获 dump 运行期间写入的变更，提供一致的时间点（配合 mongorestore --oplogReplay）；该选项仅对完整 dump 有效。对于大型集群的集群级迁移，mongosync/在线工具更合适。","danger":"safe","readonly":true,"priv":"read（oplog 需 local db 上的 read / clusterMonitor）"},"redis":{"command":"# Create an RDB snapshot in the background (non-blocking)\nredis-cli BGSAVE\n# Check for completion:\nredis-cli INFO persistence | grep rdb_bgsave_in_progress\n\n# Move the resulting file (path: CONFIG GET dir + dbfilename)\n# e.g. /var/lib/redis/dump.rdb  ->  copy to the target server","note":"在 Redis 中，一致性导出就是 RDB 快照。BGSAVE 通过 fork 在后台生成一致的 dump.rdb，不阻塞主进程（SAVE 会阻塞，不应使用）。文件路径可通过 CONFIG GET dir 和 dbfilename 获取；将该 dump.rdb 复制到目标服务器的数据目录即可完成迁移。","danger":"safe","readonly":true},"elasticsearch":{"command":"# 1) Register a snapshot repository (shared file system / S3)\nPUT _snapshot/migration_repo\n{ \"type\": \"fs\", \"settings\": { \"location\": \"/mnt/es_backup\" } }\n\n# 2) Take a consistent snapshot\nPUT _snapshot/migration_repo/migration_1?wait_for_completion=true\n{ \"indices\": \"customer,orders\", \"include_global_state\": false }","note":"在 Elasticsearch 中，用于迁移的一致性导出方法是快照。先注册一个存储库（fs、S3、GCS 等），然后执行快照；快照是增量的，捕获的是执行时刻的一致状态。小数据量/单个索引也可以使用 elasticdump，但快照是标准方法。","danger":"safe","readonly":true,"priv":"manage / create_snapshot"}}},{"id":"import-for-migration","title":"导入目标库","aliases":["import","import data","restore","load","load into target","load"],"engines":{"oracle":{"command":"# Import with Data Pump; remap schema and tablespace\nimpdp system/password directory=DIR_DMP dumpfile=shop_%U.dmp \\\n  logfile=shop_imp.log \\\n  remap_schema=shop:shop_new \\\n  remap_tablespace=USERS:DATA parallel=4\n\n# For existing objects: TABLE_EXISTS_ACTION=TRUNCATE|APPEND|REPLACE","note":"impdp 将 expdp 导出的 .dmp 文件加载到目标库。REMAP_SCHEMA 将源迁移到不同的 schema，REMAP_TABLESPACE 迁移到不同的表空间；跨环境迁移时经常使用。对已存在表的处理方式通过 TABLE_EXISTS_ACTION 设置，速度通过 PARALLEL 设置。","danger":"caution","priv":"DATAPUMP_IMP_FULL_DATABASE、目标表空间上的配额"},"postgres":{"command":"# Parallel restore (from custom/directory format)\npg_restore -j4 -d targetdb --no-owner --clean --if-exists shop.dump\n\n# A specific table only: pg_restore -d targetdb -t customer shop.dump\n# For a plain .sql dump:  psql -d targetdb -f shop.sql","note":"使用 pg_restore -j 可并行（多进程）加载 custom/directory 格式的 dump；对大型数据库可显著加快加载速度。--clean --if-exists 会先删除目标中的对象，--no-owner 可适配不同的角色结构。纯 SQL dump 使用 psql -f 加载（不支持并行）。","danger":"caution","priv":"目标数据库上的 CREATE / 所有者权限"},"mysql":{"command":"# Load a plain SQL dump\nmysql --binary-mode shop < shop.sql\n\n# Parallel/fast load (if taken with mydumper):\nmyloader --directory=./backup --database=shop --threads=4 --overwrite-tables\n\n# Tip: SET FOREIGN_KEY_CHECKS=0; unique_checks=0 before loading speeds it up.","note":"mysqldump 的 SQL dump 通过重定向给 mysql 客户端加载（mysql shop < dump.sql）。若需并行且更快的加载，用 myloader --threads 恢复 mydumper 导出的目录。加载期间禁用 FOREIGN_KEY_CHECKS 和 UNIQUE_CHECKS 可提升性能。","danger":"caution","priv":"INSERT、CREATE（--overwrite/--clean 还需 DROP）"},"mssql":{"command":"-- Restore from a physical backup (move file paths to the new environment)\nRESTORE DATABASE shop FROM DISK = 'C:\\backup\\shop.bak'\nWITH MOVE 'shop_data' TO 'D:\\data\\shop.mdf',\n     MOVE 'shop_log'  TO 'D:\\log\\shop.ldf', REPLACE;\n\n-- Import from a BACPAC (schema+data):\n-- sqlpackage /Action:Import /TargetConnectionString:\"...\" /SourceFile:shop.bacpac","note":".bak 备份通过 RESTORE DATABASE 加载；迁移到不同的磁盘布局时，WITH MOVE 将数据/日志文件重定向到新路径。对于与版本无关的/Azure SQL 目标，SqlPackage /Action:Import 会将 .bacpac 文件（schema + 数据）创建为新数据库。","danger":"caution","priv":"RESTORE 需 dbcreator/sysadmin；Import 需 CREATE DATABASE"},"mongodb":{"command":"# Restore the BSON backup; replay the oplog to land on a single point in time\nmongorestore --uri=\"mongodb://target:27017\" --oplogReplay \\\n  --numParallelCollections=4 ./backup\n\n# Renaming: --nsFrom='shop.*' --nsTo='shop_new.*'\n# Drop existing data before loading: --drop","note":"mongorestore 将 mongodump 的 BSON 输出加载到目标。--oplogReplay 会重放 --oplog 备份中的 oplog 变更，使 dump 达到单一一致的时间点。数据库/集合名称可通过 --nsFrom/--nsTo 更改，现有数据可通过 --drop 清除。","danger":"caution","priv":"readWrite（oplogReplay 需 restore/clusterAdmin）"},"redis":{"command":"# Stop the server, place dump.rdb in the data directory, start it again\n# 1) systemctl stop redis  (or SHUTDOWN NOSAVE)\n# 2) cp dump.rdb  <path found with CONFIG GET dir>/dump.rdb\n# 3) systemctl start redis   -> Redis loads the RDB into memory at startup\n\n# Alternative (live): feed a RESP command stream with redis-cli --pipe","note":"Redis 只在启动时加载 RDB。因此需停止目标服务器，将迁移来的 dump.rdb 放入数据目录（CONFIG GET dir），然后重启服务器；启动时会从 RDB 填充内存。若要在不停止服务器的情况下加载，用 redis-cli --pipe 以 RESP 格式流式写入键。","danger":"caution"},"elasticsearch":{"command":"# Register the same repository on the target cluster, then restore the snapshot\nPUT _snapshot/migration_repo\n{ \"type\": \"fs\", \"settings\": { \"location\": \"/mnt/es_backup\" } }\n\nPOST _snapshot/migration_repo/migration_1/_restore\n{ \"indices\": \"customer,orders\",\n  \"rename_pattern\": \"(.+)\", \"rename_replacement\": \"restored_$1\" }","note":"在目标集群上注册与源相同的快照仓库（文件必须可访问），然后通过 _snapshot/.../_restore 恢复索引。索引名称可通过 rename_pattern/rename_replacement 更改。已存在的索引在打开状态下无法恢复；需先关闭或重命名。","danger":"caution","priv":"manage（create_index、restore）"}}},{"id":"heterogeneous-migration","title":"迁移到其他数据库（异构）","aliases":["heterogeneous","heterogeneous migration","cross-database","different database","convert database","convert"],"engines":{"oracle":{"command":"# Oracle -> PostgreSQL: Ora2Pg (schema + data + PL/SQL conversion)\nora2pg -c ora2pg.conf -t TABLE   -o schema.sql\nora2pg -c ora2pg.conf -t COPY    -o data.sql   # data in COPY format\nora2pg -t SHOW_REPORT --estimate_cost           # migration cost estimate\n\n# Oracle -> (multiple targets) managed/online: AWS DMS or Oracle GoldenGate","note":"从 Oracle 进行异构迁移（尤其是迁移到 PostgreSQL）时，最常用的开源工具是 Ora2Pg：它将表、约束、PL/SQL 和数据转换为目标方言；--estimate_cost 可估算迁移工作量。在线/低停机迁移使用 AWS DMS 或 Oracle GoldenGate（配合 CDC）。","danger":"caution","readonly":true,"priv":"源 schema 上的 SELECT 权限（DBA 视图的读取权限）"},"postgres":{"command":"# MySQL -> PostgreSQL: pgloader (schema+data+type conversion in one command)\npgloader mysql://app_user:password@source/shop \\\n         postgresql://app_user:password@target/shop\n\n# Also from SQLite/MSSQL sources: with a pgloader ./migration.load script\n# For a managed/online PostgreSQL target: AWS DMS","note":"pgloader 是从其他引擎（MySQL、SQLite、MS SQL）迁移到 PostgreSQL 的标准工具：只需连接字符串，一条命令即可创建 schema、映射类型并快速复制数据；自定义转换/类型映射在 .load 脚本中定义。如果源是 Oracle，推荐使用 Ora2Pg；在线迁移使用 AWS DMS。","danger":"caution","priv":"目标上的 CREATE 权限；源上的 SELECT 权限"},"mysql":{"command":"# PostgreSQL/Oracle/MSSQL -> MySQL: AWS DMS (managed) is the most common path\n# or adapt the source-specific export for MySQL:\n#   pg_dump --inserts ... | (type/syntax fixes) | mysql shop\n\n# MySQL <-> MariaDB (compatible): mysqldump output loads directly\nmysqldump --single-transaction shop | mysql -h target_mariadb shop","note":"从其他引擎迁移到 MySQL 没有一个占主导地位的开源工具；最常见的方案是 AWS DMS（托管、支持 CDC），或调整源 dump 的语法/类型后用 mysql 客户端加载。MySQL 与 MariaDB 基本兼容；mysqldump 输出通常可直接迁移。","danger":"caution","priv":"目标上的 CREATE、INSERT 权限"},"mssql":{"command":"# SQL Server -> PostgreSQL: with pgloader\npgloader mssql://app_user:password@source/shop \\\n         postgresql://app_user:password@target/shop\n\n# SQL Server -> PostgreSQL compatibility layer: Babelfish for Aurora PostgreSQL\n# (runs T-SQL and the TDS protocol on top of PostgreSQL)\n# To other targets: SSMA (SQL Server Migration Assistant) or AWS DMS","note":"从 SQL Server 迁移到 PostgreSQL 使用 pgloader（mssql:// 源）；如果希望保留 T-SQL 而不修改应用，Babelfish for Aurora PostgreSQL 提供兼容层。Microsoft 的 SSMA 工具可自动将其他引擎（Oracle、MySQL、DB2）迁移到 SQL Server。在线迁移使用 AWS DMS。","danger":"caution","priv":"源上的 SELECT 权限；目标上的 CREATE 权限"},"mongodb":{"command":"# Relational <-> MongoDB conversion requires schema mapping (ETL):\n#   - Relational Migrator (official MongoDB tool): maps tables to the document model\n#   - or generate JSON from the source -> load with mongoimport\nmongoexport --db=shop --collection=customer --type=csv --fields=id,name > c.csv\n# (To an SQL target: mongoexport CSV -> relational COPY/LOAD DATA)","note":"MongoDB 与 SQL 引擎之间的迁移需要行<->文档模型转换（ETL）；没有自动的单一命令。从 SQL 到 MongoDB，MongoDB Relational Migrator 将表映射为文档 schema；简单情况下，从源生成 CSV/JSON 后用 mongoimport 加载。反方向则将 mongoexport 的输出用 COPY/LOAD DATA 加载到目标关系表。","danger":"caution","priv":"源上的 read 权限，目标上的 write 权限"},"redis":{"command":"# With RIOT (Redis Input/Output Tools), to/from Redis from other sources:\n#   Relational -> Redis:\nriot db-import --jdbc-url \"jdbc:postgresql://host/shop\" \\\n  --sql \"SELECT * FROM customer\" hset --keyspace customer --keys id\n#   Redis -> another Redis / file:  riot replicate / riot file-export","note":"在异构迁移中，Redis 通常作为缓存层充当目标/源；要从关系型或文件源向 Redis 加载数据，使用 Redis 官方的 RIOT 工具（db-import 将 SQL 结果映射为 HSET/SET 结构）。Redis 到 Redis 或到文件，可使用 riot replicate / file-export 命令。","danger":"caution"},"elasticsearch":{"command":"# Relational/other source -> Elasticsearch: Logstash (JDBC input)\n# logstash.conf: input { jdbc { statement => \"SELECT * FROM customer\" } }\n#                output { elasticsearch { index => \"customer\" } }\n\n# ES -> ES (version/cluster upgrade): reindex from a remote source\nPOST _reindex\n{ \"source\": { \"remote\": { \"host\": \"http://old:9200\" }, \"index\": \"customer\" },\n  \"dest\": { \"index\": \"customer\" } }","note":"从其他数据库迁移到 Elasticsearch 最常用的工具是 Logstash（JDBC/file input -> elasticsearch output）；Elastic 自己的 connectors/Beats 也很常用。从旧的 Elasticsearch 集群迁移到新集群时，常用 remote reindex（带 'remote' 源的 _reindex）；目标上必须定义 reindex.remote.whitelist。","danger":"caution","priv":"目标上的 write/create_index 权限"}}},{"id":"setup-cdc-replication","title":"搭建持续复制 / CDC（零停机切换）","aliases":["cdc","replication","replication","zero-downtime migration","live migration","change data capture"],"engines":{"oracle":{"command":"# CDC-based replication with Oracle GoldenGate (outline)\n# 1) Enable supplemental logging on the source:\nALTER DATABASE ADD SUPPLEMENTAL LOG DATA;\n# 2) Extract (capture changes from redo) and Replicat (apply to the target) processes:\n#   GGSCI> ADD EXTRACT ext1, INTEGRATED TRANLOG, BEGIN NOW\n#   GGSCI> ADD REPLICAT rep1, INTEGRATED\n# Alternative: initial load with Data Pump + incremental capture with GoldenGate","note":"在 Oracle 中，零停机/异构 CDC 的标准工具是 Oracle GoldenGate：Extract 进程从重做日志捕获变更，Replicat 将其应用到目标；必须启用补充日志（supplemental logging）。典型迁移：先用 Data Pump 完成初始加载，此后由 GoldenGate 将变更流式传输到目标，实现近乎零停机的切换。","danger":"caution","priv":"GoldenGate 管理员权限、补充日志"},"postgres":{"command":"-- Source (publisher): create a publication (wal_level must be logical)\nCREATE PUBLICATION migration_pub FOR ALL TABLES;\n\n-- Target (subscriber): create a subscription (initial copy + continuous stream)\nCREATE SUBSCRIPTION migration_sub\n  CONNECTION 'host=source dbname=shop user=repl password=***'\n  PUBLICATION migration_pub;","note":"PostgreSQL 通过内置的逻辑复制提供 CDC：在源上 CREATE PUBLICATION，在目标上 CREATE SUBSCRIPTION。订阅启动时会复制现有数据（initial copy），然后持续从 WAL 流式传输变更；要求 wal_level=logical。对于大型/复杂场景，推荐使用 pglogical 或 Debezium。","danger":"caution","priv":"源上的 REPLICATION 角色；目标上 CREATE SUBSCRIPTION 需要 superuser（在 PG16+ 中 pg_create_subscription 角色也足够）","minver":"10（逻辑复制）"},"mysql":{"command":"-- Source: binlog must be enabled and in ROW format (log_bin, binlog_format=ROW)\n-- Grant a replication user: GRANT REPLICATION SLAVE ON *.* TO 'repl'@'%';\n\n-- Define the source on the target (start from the binlog position of the initial load):\nCHANGE REPLICATION SOURCE TO\n  SOURCE_HOST='source', SOURCE_USER='repl', SOURCE_PASSWORD='***',\n  SOURCE_LOG_FILE='binlog.000001', SOURCE_LOG_POS=1547;\nSTART REPLICA;","note":"在 MySQL 中，零停机切换通过基于 binlog 的复制完成。初始加载用 mysqldump --source-data 获取并记录 binlog 位置/GTID；在目标上使用 CHANGE REPLICATION SOURCE TO（8.0.23+ 语法；原为 CHANGE MASTER TO）从该位置启动复制。START REPLICA 持续应用变更。基于 GTID 的配置更安全。","danger":"caution","priv":"REPLICATION SLAVE（复制用户）、REPLICATION CLIENT","minver":"8.0.23（CHANGE REPLICATION SOURCE）"},"mssql":{"command":"-- Option A: Transactional Replication (publisher->distributor->subscriber) — SSMS wizard\n\n-- Option B: Change Data Capture (track changes as tables)\nEXEC sys.sp_cdc_enable_db;              -- enable CDC at database level\nEXEC sys.sp_cdc_enable_table\n  @source_schema = N'dbo', @source_name = N'customer',\n  @role_name = NULL;                    -- enable CDC at table level\n-- Changes are read from the cdc.dbo_customer_CT table and moved to the target.","note":"SQL Server 的持续复制有两条主要途径：如果需要直接流式传输到目标，配置事务复制（发布服务器/订阅服务器）；如果要用自己的 ETL 迁移变更，启用变更数据捕获（sp_cdc_enable_db + sp_cdc_enable_table）并从 _CT 表读取。对于异构目标，CDC 可与 AWS DMS/Debezium 结合使用。","danger":"caution","priv":"sysadmin（用于启用 CDC）；SQL Server Agent 必须处于运行状态"},"mongodb":{"command":"// Watch changes live with Change Streams (requires a replica set/sharded cluster)\nconst cs = db.customer.watch([], { fullDocument: \"updateLookup\" });\nwhile (cs.hasNext()) { printjson(cs.next()); }  // apply to the target\n\n// Official tool for managed cluster-to-cluster replication/migration: mongosync\n// mongosync --cluster0 \"mongodb://source\" --cluster1 \"mongodb://target\"","note":"在 MongoDB 中，CDC 使用 Change Streams：db.collection.watch() 从 oplog 实时流式传输 insert/update/delete 事件，供应用到目标（需要副本集）。对于完整的集群到集群持续迁移/复制，MongoDB 官方的 Cluster-to-Cluster Sync 工具 mongosync（不限于 Atlas；也适用于自管集群）可管理初始加载 + 持续同步。","danger":"caution","priv":"changeStream（read）；mongosync 需 clusterAdmin"},"redis":{"command":"# Make the target instance a replica of the source (live synchronization)\n# On the target Redis:\nREPLICAOF source_host 6379\n# Monitor sync status:\nINFO replication   # master_link_status must be up\n\n# Once the cutover is complete, make the target standalone:\nREPLICAOF NO ONE","note":"在 Redis 中，零停机切换通过原生复制完成：在目标服务器上执行 REPLICAOF <source host port>（原为 SLAVEOF）；目标先接收完整的 RDB，然后通过命令流保持持续同步。用 INFO replication 确认 master_link_status:up，切换完成后用 REPLICAOF NO ONE 将目标设为独立实例。","danger":"caution"},"elasticsearch":{"command":"# Incremental reindex from the old cluster to the new one within the same version family\nPOST _reindex?wait_for_completion=false\n{ \"source\": { \"remote\": { \"host\": \"http://old:9200\" }, \"index\": \"customer\",\n    \"query\": { \"range\": { \"updated_at\": { \"gt\": \"<last-cursor>\" } } } },\n  \"dest\": { \"index\": \"customer\" } }\n\n# Licensed feature: Cross-Cluster Replication (CCR) — continuous following\n# On the target (follower) cluster: PUT /customer/_ccr/follow { \"remote_cluster\": \"old\", \"leader_index\": \"customer\" }","note":"Elasticsearch 中真正的持续复制功能是 Cross-Cluster Replication（CCR）：目标集群跟随源上的 leader 索引并持续拉取变更（需要商业/Platinum 许可）。没有许可时，可通过定期 remote reindex（基于时间/序列字段的增量方式）实现近乎持续的同步。","danger":"caution","priv":"CCR 需 manage/read_ccr；remote reindex 需 whitelist"}}},{"id":"verify-migration","title":"迁移后验证（行数/校验和）","aliases":["verify","verification","row count","checksum","row count","consistency check"],"engines":{"oracle":{"command":"-- Compare row counts on source and target\nSELECT 'customer' table_name, COUNT(*) row_count FROM customer;\n\n-- To find content differences at object level, DBMS_COMPARISON (via db link)\nBEGIN DBMS_COMPARISON.CREATE_COMPARISON(\n  comparison_name => 'COMPARE_CUSTOMER',\n  schema_name => 'SHOP', object_name => 'CUSTOMER',\n  dblink_name => 'TARGET_DB'); END;\n/\n-- SELECT DBMS_COMPARISON.COMPARE('COMPARE_CUSTOMER', scan_info) FROM dual;","note":"最快的检查是比较两侧每张表的 COUNT(*)。要找出真正的行级内容差异，使用 DBMS_COMPARISON 包：它通过数据库链接逐行比较源表和目标表，可以报告/收敛差异。校验和方面，可用 ORA_HASH 或 STANDARD_HASH 计算列摘要。","danger":"safe","readonly":true,"priv":"两侧的 SELECT 权限；DBMS_COMPARISON 上的 EXECUTE 权限"},"postgres":{"command":"-- Row count\nSELECT count(*) FROM customer;\n\n-- Ordered digest (checksum) of the whole table content — compare on both sides\nSELECT md5(string_agg(t.row_md5, '' ORDER BY t.row_md5)) AS digest\nFROM (SELECT md5(customer::text) AS row_md5 FROM customer) t;","note":"第一步是比较每张表的 count(*)。内容验证时，对每行的文本表示计算 md5，按排序顺序拼接后再次哈希；这样可在两侧得到与顺序无关的校验和。大数据量时，只对可变/键列计算摘要；按 PK 排序对可重复性很重要。","danger":"safe","readonly":true,"priv":"表上的 SELECT 权限"},"mysql":{"command":"-- Built-in table checksum (run on both servers and compare)\nCHECKSUM TABLE customer;\n\n-- Row count\nSELECT COUNT(*) FROM customer;\n\n-- Preferred tool for live/large environments (compares row chunks online):\n-- pt-table-checksum h=source,D=shop  (Percona Toolkit)","note":"在 MySQL 中，CHECKSUM TABLE 给出表内容的单一校验和；在源和目标上运行并比较（引擎/版本必须相同）。行数使用 COUNT(*)。在线生产环境和复制一致性检查的行业标准是 Percona Toolkit 的 pt-table-checksum；它按 chunk 在线比较，影响很小。","danger":"safe","readonly":true,"priv":"表上的 SELECT 权限"},"mssql":{"command":"-- Full row-level comparison (official tool that produces differences)\ntablediff -sourceserver S1 -sourcedatabase shop -sourcetable customer \\\n  -destinationserver S2 -destinationdatabase shop -destinationtable customer\n\n-- Quick aggregate checksum (single query, compare on both sides):\nSELECT COUNT_BIG(*) row_count, CHECKSUM_AGG(BINARY_CHECKSUM(*)) digest FROM dbo.customer;","note":"在 SQL Server 中，查找行级差异的官方命令行工具是 tablediff（报告哪些行被新增/修改，使用可选的 -f 可写出修复脚本）。快速整体检查时，CHECKSUM_AGG(BINARY_CHECKSUM(*)) 给出单一的表摘要；与 COUNT_BIG(*) 一起比较两侧（与行顺序无关）。","danger":"safe","readonly":true,"priv":"两张表上的 SELECT 权限"},"mongodb":{"command":"// Compare document counts (on source and target)\ndb.customer.countDocuments({});\n\n// For a content checksum, dbHash (md5 digest at collection/DB level):\ndb.runCommand({ dbHash: 1, collections: [\"customer\"] });\n// -> compare the md5 field across both environments.","note":"第一项检查是用 countDocuments() 比较两侧的文档数（estimatedDocumentCount 速度快但依赖元数据；精确计数使用 countDocuments）。内容验证时，dbHash 命令在集合/数据库级别生成 md5 摘要；比较两个环境的哈希值。索引用 getIndexes() 单独检查。","danger":"safe","readonly":true,"priv":"read（dbHash 的访问权限）"},"redis":{"command":"# Compare key counts (on source and target)\nredis-cli DBSIZE\n\n# For a memory/data summary and key distribution:\nredis-cli INFO keyspace     # db0:keys=... information\n# For full content verification: DUMP each key and compare with the target\n# (e.g. redis-cli --scan | xargs -I{} redis-cli DUMP {})","note":"在 Redis 中，基本验证是用 DBSIZE 比较键总数；各数据库的分布由 INFO keyspace 显示。Redis 没有内置的表校验和；如需精确的内容验证，按键获取 DUMP 序列化结果并比较两侧（注意 TTL 差异）。也可以使用 riot compare 工具。","danger":"safe","readonly":true},"elasticsearch":{"command":"# Compare document counts (on source and target clusters)\nGET customer/_count\n\n# Details: for health/segment/storage comparison\nGET _cat/indices/customer?v&h=index,docs.count,store.size\n# Content by sampling: compare GET customer/_doc/<id> for the same _ids","note":"在 Elasticsearch 中，基本验证是用 GET <index>/_count 比较两个集群的文档数（_cat/indices 的 docs.count 也可快速汇总）。比较应以 _count 为准：_cat/indices docs.count 在 Lucene 层面计数（包括 nested 子文档，例如含两个 nested 项的一个文档计为 3），因此可能与 _count 不同。内容验证时，对同一组 _id 抽样比较文档，或通过哈希字段比较。","danger":"safe","readonly":true,"priv":"索引上的 read / monitor 权限"}}}]},"rosetta/tuning.json":{"category":"性能调优","categoryId":"tuning","order":16,"concepts":[{"id":"plan-baseline","title":"固定 / 强制查询计划（plan baseline / hint）","aliases":["plan baseline","force plan","pin plan","hint","plan stability","query store force"],"engines":{"oracle":{"command":"-- Load the good plan from the cursor cache as a baseline:\nDECLARE n NUMBER; BEGIN\n  n := DBMS_SPM.LOAD_PLANS_FROM_CURSOR_CACHE(sql_id => '&sql_id');\nEND;\n/","note":"SQL Plan Management 锁定执行计划；是防止计划回归的最强手段。也可以使用 /*+ ... */ hint。","danger":"caution","priv":"ADMINISTER SQL MANAGEMENT OBJECT"},"postgres":{"command":"-- There is no built-in plan pinning; the pg_hint_plan extension:\n/*+ IndexScan(customer customer_city_idx) */ SELECT ... ;","note":"替代方案：通过 enable_seqscan 等设置在会话级进行引导；持久固定使用 pg_hint_plan。","danger":"caution"},"mysql":{"command":"SELECT /*+ INDEX(customer customer_city_idx) */ * FROM customer WHERE city='Ankara';","note":"优化器 hint 嵌入在查询中。持久改写使用 query rewrite 插件。","danger":"safe"},"mssql":{"command":"-- Force the good plan in Query Store:\nEXEC sp_query_store_force_plan @query_id = 42, @plan_id = 17;","note":"必须启用 Query Store。也可以使用 OPTION (USE PLAN N'...') 或计划指南（plan guide）。","danger":"caution","priv":"数据库上的 ALTER 权限"},"mongodb":{"command":"// Force an index for the query:\ndb.customer.find({ city: \"Ankara\" }).hint({ city: 1 })\n// Persistent: db.customer.getPlanCache() / index filter","note":"hint 强制使用特定索引；使用 planCacheClear 清除过时的计划。","danger":"safe"},"redis":{"command":"# Not applicable: there is no query planner.","note":"性能取决于数据结构和命令复杂度。","danger":"safe"},"elasticsearch":{"command":"# There is no plan pinning; optimize the query/index and routing.\nGET <index>/_search { \"profile\": true, \"query\": {...} }","note":"使用 profile 查看哪个查询开销大；通过分片/routing 进行引导。","danger":"safe"}}},{"id":"gather-stats-advanced","title":"使用高级选项收集统计信息（直方图等）","aliases":["gather stats","histogram","statistics","analyze advanced","method_opt","estimate percent"],"engines":{"oracle":{"command":"EXEC DBMS_STATS.GATHER_TABLE_STATS(\n  ownname=>'HR', tabname=>'EMPLOYEE',\n  estimate_percent=>DBMS_STATS.AUTO_SAMPLE_SIZE,\n  method_opt=>'FOR ALL COLUMNS SIZE AUTO', degree=>4);","note":"method_opt 控制直方图，degree 控制并行度。使用 LOCK_TABLE_STATS 锁定统计信息。","danger":"safe"},"postgres":{"command":"ALTER TABLE customer ALTER COLUMN city SET STATISTICS 500;\nANALYZE customer;","note":"按列设置采样目标；default_statistics_target 设置全局默认值。","danger":"safe"},"mysql":{"command":"ANALYZE TABLE customer UPDATE HISTOGRAM ON city WITH 32 BUCKETS;","note":"MySQL 8.0 支持直方图；适用于用作过滤条件但选择性不高的列。","danger":"safe"},"mssql":{"command":"UPDATE STATISTICS dbo.customer WITH FULLSCAN;\n-- Filtered statistics: CREATE STATISTICS ... WHERE ...","note":"FULLSCAN 最准确；大表上使用 SAMPLE。自动更新使用 AUTO_UPDATE_STATISTICS。","danger":"safe"},"mongodb":{"command":"// There are no manual statistics; the planner measures at runtime.\ndb.customer.getPlanCache().clear()","note":"除了清除过时的计划缓存外，无需手动维护统计信息。","danger":"safe"},"redis":{"command":"# Not applicable.","note":"没有统计信息/优化器。","danger":"safe"},"elasticsearch":{"command":"# There is no statistics gathering; refresh searchability:\nPOST <index>/_refresh","note":"查询时使用 field data 和 doc values；force merge 可减少段数量。","danger":"safe"}}},{"id":"perf-report","title":"生成性能报告（AWR / ASH 及等价工具）","aliases":["awr","ash","performance report","performance report","profile","workload report"],"engines":{"oracle":{"command":"-- AWR report (SQL*Plus):\n@?/rdbms/admin/awrrpt.sql\n-- ASH report: @?/rdbms/admin/ashrpt.sql\n-- Live: SELECT * FROM v$active_session_history WHERE sample_time > SYSDATE-1/24;","note":"AWR/ASH 需要 Diagnostics Pack 许可。Statspack 是无需许可的等价工具。","danger":"safe","priv":"SELECT_CATALOG_ROLE"},"postgres":{"command":"-- pg_stat_statements summary + pg_profile/pgBadger:\nSELECT queryid, calls, total_exec_time, mean_exec_time, query\nFROM pg_stat_statements ORDER BY total_exec_time DESC LIMIT 20;","note":"AWR 的等价方案：使用 pg_profile 扩展（定期快照）或 pgBadger 日志分析。","danger":"safe"},"mysql":{"command":"-- sys schema summaries:\nSELECT * FROM sys.statement_analysis LIMIT 20;\nSELECT * FROM sys.io_global_by_file_by_bytes LIMIT 10;","note":"sys schema 将 performance_schema 转换为易读的报告；Enterprise Monitor 更为全面。","danger":"safe"},"mssql":{"command":"-- Query Store reports (SSMS) or:\nSELECT TOP 20 * FROM sys.query_store_runtime_stats ORDER BY avg_duration DESC;","note":"Query Store 持续保存执行计划/性能数据；sp_BlitzFirst（社区工具）可快速诊断。","danger":"safe"},"mongodb":{"command":"# Live from the shell: mongostat --rowcount 10 ; mongotop\n// or Atlas Performance Advisor / profiler:\ndb.system.profile.find().sort({millis:-1}).limit(20)","note":"mongostat/mongotop 显示瞬时负载；profiler 记录慢操作。","danger":"safe"},"redis":{"command":"INFO stats\nredis-cli --latency-history\nSLOWLOG GET 20","note":"--latency 和 --latency-history 给出延迟概况；INFO 给出所有计数器。","danger":"safe"},"elasticsearch":{"command":"GET _nodes/stats\nGET _cat/thread_pool?v&h=node_name,name,active,queue,rejected\nGET _nodes/hot_threads","note":"hot_threads 显示开销最大的线程；_nodes/stats 给出整体指标。Monitoring（Kibana）提供历史数据。","danger":"safe"}}},{"id":"optimizer-params","title":"查看/修改优化器 / 并行设置","aliases":["optimizer","parallelism","parallel","maxdop","optimizer_switch","cost","planner settings"],"engines":{"oracle":{"command":"SHOW PARAMETER optimizer;\n-- Session: ALTER SESSION SET optimizer_mode = FIRST_ROWS_10;\n-- Parallelism: ALTER SESSION FORCE PARALLEL QUERY PARALLEL 4;","note":"optimizer_index_cost_adj 和 parallel_degree_policy 等设置会影响执行计划选择；修改时需谨慎。","danger":"caution"},"postgres":{"command":"SHOW random_page_cost;\nSHOW max_parallel_workers_per_gather;\n-- Session: SET enable_seqscan = off;","note":"在 SSD 上，推荐 random_page_cost ~1.1。enable_* 开关用于诊断，而不是长期解决方案。","danger":"caution"},"mysql":{"command":"SELECT @@optimizer_switch;\nSET SESSION optimizer_switch='index_merge=off';","note":"optimizer_switch 可单独开启/关闭各项优化；应在会话级测试。","danger":"caution"},"mssql":{"command":"-- Instance-level MAXDOP and cost threshold:\nEXEC sp_configure 'max degree of parallelism', 8; RECONFIGURE;\n-- Database level: ALTER DATABASE SCOPED CONFIGURATION SET MAXDOP = 8;","note":"MAXDOP=0 表示不限制；OLTP 通常为 4-8。“cost threshold for parallelism”的默认值 5 对大多数场景来说过低。","danger":"caution"},"mongodb":{"command":"# There are no optimizer parameters; indexes and query shape are decisive.\ndb.customer.find({...}).explain('allPlansExecution')","note":"allPlansExecution 显示规划器评估过的所有计划。","danger":"safe"},"redis":{"command":"# Not applicable.","note":"没有规划器；io-threads 和数据结构选择决定性能。","danger":"safe"},"elasticsearch":{"command":"GET <index>/_settings?include_defaults=true\n# search limits such as search.max_buckets, index.max_result_window","note":"没有优化器；分片数量、routing 和查询结构决定性能。","danger":"safe"}}},{"id":"sql-monitor","title":"实时监控正在运行/最近的 SQL","aliases":["sql monitor","live monitoring","real time sql","live query","current sql","monitor active queries"],"engines":{"oracle":{"command":"SET LONG 1000000 LONGCHUNKSIZE 1000000 LINESIZE 250 PAGESIZE 0\nSELECT DBMS_SQLTUNE.REPORT_SQL_MONITOR(sql_id=>'&sql_id', type=>'TEXT') AS report FROM dual;\n-- or: SELECT sql_id, status, elapsed_time FROM v$sql_monitor WHERE status='EXECUTING';","note":"Real-Time SQL Monitoring 实时显示长时间/并行查询（Tuning Pack）。","danger":"safe"},"postgres":{"command":"SELECT pid, now()-query_start AS dur, state, wait_event, query\nFROM pg_stat_activity WHERE state='active' ORDER BY query_start;","note":"借助 auto_explain，可自动记录慢查询的执行计划。","danger":"safe"},"mysql":{"command":"SELECT thread_id, event_name, timer_wait/1e9 AS ms, sql_text\nFROM performance_schema.events_statements_current;","note":"events_statements_current 显示当前正在运行的语句。","danger":"safe"},"mssql":{"command":"SELECT r.session_id, r.status, r.wait_type, r.percent_complete, t.text\nFROM sys.dm_exec_requests r JOIN sys.dm_exec_sessions s ON s.session_id = r.session_id CROSS APPLY sys.dm_exec_sql_text(r.sql_handle) t\nWHERE s.is_user_process = 1;","note":"SSMS 中的“实时查询统计信息”实时显示计划执行进度。","danger":"safe"},"mongodb":{"command":"db.currentOp({ active: true, secs_running: { $gt: 1 } })","note":"实时显示长时间运行的操作；可用 killOp 终止。","danger":"safe"},"redis":{"command":"MONITOR","note":"实时输出所有命令——在生产环境中会影响性能，只能短时使用。优先使用 SLOWLOG。","danger":"caution"},"elasticsearch":{"command":"GET _tasks?detailed=true&group_by=parents","note":"实时显示正在运行的任务；长任务可通过 POST _tasks/<id>/_cancel 取消。","danger":"safe"}}}]},"rosetta/tuning-adv.json":{"category":"高级调优","categoryId":"tuning-adv","order":17,"concepts":[{"id":"parallel-query","title":"执行并行查询 / 设置并行度","aliases":["parallel","parallelism","dop","degree of parallelism","parallel query"],"engines":{"oracle":{"command":"-- At query level with a hint:\nSELECT /*+ PARALLEL(t, 4) */ * FROM big_table t;\n-- Force at session level:\nALTER SESSION FORCE PARALLEL QUERY PARALLEL 4;","note":"PARALLEL hint 对表执行并行扫描；degree=DOP。过高的 DOP 会耗尽服务器资源，需谨慎选择。parallel_degree_policy=AUTO 可自动管理。","danger":"caution"},"postgres":{"command":"-- Set the number of parallel workers for the session:\nSET max_parallel_workers_per_gather = 4;\n-- The planner adds a Gather/Gather Merge node:\nEXPLAIN (ANALYZE) SELECT count(*) FROM big_table;","note":"如果执行计划中出现 Gather/Parallel Seq Scan，说明并行已生效。max_parallel_workers 和 min_parallel_table_scan_size 也起作用。","danger":"safe"},"mysql":{"command":"-- Limited InnoDB parallelism (certain operations only):\nSET GLOBAL innodb_parallel_read_threads = 4;\n-- Effective for COUNT(*) and primary key scans.","note":"MySQL 不提供通用的并行查询执行；innodb_parallel_read_threads 只能并行化聚集索引扫描（如 COUNT(*)）。复杂的 JOIN 是单线程的。","danger":"safe"},"mssql":{"command":"-- MAXDOP hint at query level:\nSELECT * FROM dbo.big_table OPTION (MAXDOP 4);\n-- Instance level: sp_configure 'max degree of parallelism', 4;","note":"MAXDOP 限制并行线程数；MAXDOP 1 会禁用并行。超过“cost threshold for parallelism”后，执行计划将变为并行。","danger":"safe"},"mongodb":{"command":"// Allow disk use for a large aggregation:\ndb.sale.aggregate([ { $group: { _id: \"$city\", t: { $sum: \"$amount\" } } } ], { allowDiskUse: true })\n// A sharded cluster processes each shard in parallel.","note":"在分片集群中，查询在各分片上并行运行；在单节点上，聚合阶段大多是串行的。allowDiskUse 允许执行超出内存限制的操作。","danger":"safe"},"redis":{"command":"# Not applicable: there is no concept of query parallelism.","note":"命令执行是单线程的；可通过 Cluster（分片）扩展，或使用 io-threads 实现网络 I/O 并行。","danger":"safe"},"elasticsearch":{"command":"# Shard count = unit of parallelism; a query runs in parallel across shards.\n# Parallelism by slicing within a single shard (with scroll/PIT):\nGET <index>/_search\n{ \"slice\": { \"id\": 0, \"max\": 4 }, \"query\": { \"match_all\": {} } }","note":"搜索在所有相关分片上并发运行；主分片数量决定基础并行度。slices 可拆分大型 scroll/reindex 操作。","danger":"safe"}}},{"id":"bind-variables","title":"管理绑定变量 / 参数嗅探","aliases":["bind","bind variable","parameter","parameter sniffing","cursor sharing","prepared statement"],"engines":{"oracle":{"command":"-- Use binds instead of literals (for plan sharing):\nSELECT * FROM customer WHERE city = :city;\n-- Share legacy code that uses literals:\nALTER SESSION SET cursor_sharing = FORCE;","note":"绑定变量可减少硬解析。Adaptive Cursor Sharing 为分布倾斜的绑定值保留多个执行计划，从而缓解嗅探问题。cursor_sharing=FORCE 需谨慎使用。","danger":"caution"},"postgres":{"command":"-- Parameterize with a prepared statement:\nPREPARE q (text) AS SELECT * FROM customer WHERE city = $1;\nEXECUTE q('Ankara');\n-- Set plan cache behavior:\nSET plan_cache_mode = force_custom_plan;","note":"plan_cache_mode=auto 在通用计划和定制计划之间选择；force_custom_plan 在每次执行时生成针对参数的计划（解决类似嗅探问题的方法）。","danger":"safe"},"mysql":{"command":"-- Server-side prepared statement:\nPREPARE stmt FROM 'SELECT * FROM customer WHERE city = ?';\nSET @city = 'Ankara';\nEXECUTE stmt USING @city;\nDEALLOCATE PREPARE stmt;","note":"预处理语句可防止 SQL 注入并降低解析开销。MySQL 也支持在驱动层进行参数化。","danger":"safe"},"mssql":{"command":"-- Parameterized query (sp_executesql):\nEXEC sp_executesql N'SELECT * FROM dbo.customer WHERE city = @city', N'@city nvarchar(50)', @city = N'Ankara';\n-- Handle sniffing with a hint:\nSELECT ... OPTION (RECOMPILE);\n-- or OPTION (OPTIMIZE FOR (@city = 'Ankara'));","note":"OPTION(RECOMPILE) 在每次执行时生成针对参数的计划（CPU 开销大）。OPTIMIZE FOR 将计划固定到特定/典型值；OPTIMIZE FOR UNKNOWN 使用平均选择性。","danger":"caution"},"mongodb":{"command":"// The query shape is the key of the plan cache:\ndb.customer.find({ city: \"Ankara\" }).explain(\"queryPlanner\")\n// Clear a stale plan:\ndb.customer.getPlanCache().clear()","note":"形状相同（字段/运算符相同）的查询共享缓存的计划；不同的字面值不会生成新计划。如果值分布严重倾斜，用 planCacheClear 重置。","danger":"safe"},"redis":{"command":"# Not applicable: there is no query planner or parameter sniffing.","note":"命令直接执行；决定性能的是键设计，而不是参数化。","danger":"safe"},"elasticsearch":{"command":"# Reuse the query with a parameterized search template:\nPOST _scripts/customer_city\n{ \"script\": { \"lang\": \"mustache\", \"source\": { \"query\": { \"term\": { \"city\": \"{{city}}\" } } } } }\nGET <index>/_search/template\n{ \"id\": \"customer_city\", \"params\": { \"city\": \"Ankara\" } }","note":"搜索模板（Mustache）对查询主体进行参数化；应用以不同的值调用固定模板，从而提高查询一致性和缓存利用率。","danger":"safe"}}},{"id":"partition-pruning","title":"验证分区裁剪","aliases":["partition pruning","partition elimination","partition","pruning","pstart pstop"],"engines":{"oracle":{"command":"EXPLAIN PLAN FOR\nSELECT * FROM sale WHERE sale_date = DATE '2026-01-15';\nSELECT * FROM TABLE(DBMS_XPLAN.DISPLAY);\n-- Look for 'PARTITION RANGE ITERATOR/SINGLE' and the Pstart/Pstop columns in the plan.","note":"Pstart/Pstop 显示只读取了相关分区（理想情况是数字而不是 KEY）。对分区键应用函数/隐式转换会阻止裁剪。","danger":"safe"},"postgres":{"command":"SET enable_partition_pruning = on;\nEXPLAIN (ANALYZE)\nSELECT * FROM sale WHERE sale_date = DATE '2026-01-15';\n-- Verify in the plan that only the relevant partition (child table) is scanned.","note":"开启 enable_partition_pruning 后，计划中只出现匹配的分区；其他分区显示为 'never executed'。常量值在计划时裁剪，参数在执行时裁剪。","danger":"safe"},"mysql":{"command":"-- Show which partitions are scanned:\nEXPLAIN SELECT * FROM sale WHERE sale_date = '2026-01-15';\n-- 5.7 and earlier: EXPLAIN PARTITIONS SELECT ... (removed in 8.0)","note":"在 MySQL 5.7+ 中，普通 EXPLAIN 输出的 'partitions' 列列出将要扫描的分区（EXPLAIN PARTITIONS 语法已在 8.0 中移除）；应只出现相关分区。如果列出了所有分区，说明没有进行裁剪。","danger":"safe"},"mssql":{"command":"-- Find which partition a value falls into:\nSELECT $PARTITION.pf_sale_date('2026-01-15') AS partition_no;\n-- Examine 'Actual Partition Count' and 'Actual Partitions Accessed' in the actual plan.","note":"$PARTITION 函数返回某个值所在的分区号。如果实际执行计划中访问的分区数接近 1，说明裁剪已生效；如果扫描了所有分区，说明谓词不匹配。","danger":"safe"},"mongodb":{"command":"// Targeted query using the shard key in a sharded cluster:\ndb.sale.find({ shop_id: 42 }).explain(\"executionStats\")\n// In explain, only the relevant shard should appear under 'shards'.","note":"如果查询包含分片键，mongos 只将其路由到相关分片（targeted）；否则会分发到所有分片（scatter-gather）。在 explain 输出中确认访问了哪些分片。","danger":"safe"},"redis":{"command":"# Not applicable: there is no table/partition concept.","note":"数据通过 Cluster 哈希槽分布；不存在查询裁剪，而是直接按键访问。","danger":"safe"},"elasticsearch":{"command":"# Route to the relevant shard with routing:\nGET <index>/_search?routing=customer42\n{ \"query\": { \"term\": { \"customer_id\": 42 } } }\n# Query only the relevant indices with a time-based index pattern:\nGET sale-2026-01/_search","note":"自定义 routing 将查询收窄到单个分片（类似裁剪）。对于时间序列数据，带日期模式的索引名（sale-2026-01）可确保只搜索相关索引；应使用窄模式而非通配符。","danger":"safe"}}},{"id":"result-cache","title":"使用结果 / 查询缓存","aliases":["result cache","result caching","query cache","query result cache","request cache"],"engines":{"oracle":{"command":"-- Result cache at query level:\nSELECT /*+ RESULT_CACHE */ city, COUNT(*) FROM customer GROUP BY city;\n-- Global mode: ALTER SYSTEM SET result_cache_mode = FORCE;","note":"Server Result Cache 将结果保存在 SGA 中；底层表发生变化时自动失效。RESULT_CACHE_MODE=FORCE 对所有符合条件的查询生效，MANUAL 仅通过 hint 生效。对频繁变化的表收益不大。","danger":"caution"},"postgres":{"command":"-- There is no built-in result cache.\n-- Precompute with a materialized view:\nCREATE MATERIALIZED VIEW mv_city_summary AS\n  SELECT city, COUNT(*) FROM customer GROUP BY city;\nREFRESH MATERIALIZED VIEW mv_city_summary;","note":"PostgreSQL 不提供查询结果缓存；可使用物化视图（手动/定时 REFRESH）或应用层缓存（Redis）。注意：Memoize 节点提供的是嵌套循环内的行级缓存，而不是查询结果缓存。","danger":"safe"},"mysql":{"command":"-- Query Cache was REMOVED in MySQL 8.0.\n-- Alternative: a materialized summary table or an application cache:\nCREATE TABLE city_summary AS SELECT city, COUNT(*) c FROM customer GROUP BY city;","note":"旧的 query_cache_type 已在 MySQL 8.0 中被完全移除（由于可扩展性问题）。请改用 InnoDB 缓冲池、汇总表或外部缓存（Redis/Memcached）。","danger":"safe"},"mssql":{"command":"-- There is no built-in query result cache.\n-- Precompute with an indexed (materialized) view:\nCREATE VIEW dbo.vw_city_summary WITH SCHEMABINDING AS\n  SELECT city, COUNT_BIG(*) c FROM dbo.customer GROUP BY city;\nCREATE UNIQUE CLUSTERED INDEX ix ON dbo.vw_city_summary(city);","note":"SQL Server 不保留结果缓存（它保留的是计划缓存）。对于重复的聚合，可使用自动维护的索引视图；计划中的自动匹配仅在 Enterprise（及 Developer）版中提供，Standard 版必须使用 WITH (NOEXPAND) 提示查询该视图；此外，缓冲池会将频繁读取的页保留在内存中。","danger":"safe"},"mongodb":{"command":"// There is no built-in query result cache.\n// Pre-aggregate and write to a collection ($merge):\ndb.sale.aggregate([\n  { $group: { _id: \"$city\", t: { $sum: \"$amount\" } } },\n  { $merge: \"city_summary\" }\n])","note":"MongoDB 不提供结果缓存；WiredTiger 将频繁访问的数据保留在内存中。对于开销大的聚合，可通过 $merge/$out 使用预计算的汇总集合，或使用应用缓存（Redis）。","danger":"safe"},"redis":{"command":"# Redis is already a cache layer; cache the result manually:\nSET query:city_summary \"{...}\" EX 300\nGET query:city_summary\n# If already computed, return it with GET; otherwise compute it and write it with SET.","note":"常见的 cache-aside 模式：如果键存在，用 GET 快速返回；否则从数据源计算并用带 EX（TTL）的 SET 写入。Redis 通常充当其他数据库的结果缓存。","danger":"safe"},"elasticsearch":{"command":"# Shard request cache (enabled by default for size:0 aggregations):\nGET <index>/_search?request_cache=true\n{ \"size\": 0, \"aggs\": { \"city\": { \"terms\": { \"field\": \"city\" } } } }\n# Setting: PUT <index>/_settings { \"index.requests.cache.enable\": true }","note":"shard request cache 缓存 size:0（仅聚合）请求的每分片结果；索引 refresh 时失效。size>0（返回命中结果）的查询默认不缓存。","danger":"safe"}}}]},"rosetta/advanced-features.json":{"category":"高级功能","categoryId":"advanced-features","order":18,"concepts":[{"id":"full-text-search","title":"全文搜索","aliases":["full text","full text","fts","search","text search"],"engines":{"oracle":{"command":"-- Oracle Text: create a CONTEXT index\nCREATE INDEX article_idx ON article(content) INDEXTYPE IS CTXSYS.CONTEXT;\n-- Search with CONTAINS (label 1 ties CONTAINS to SCORE):\nSELECT id, SCORE(1) FROM article\nWHERE CONTAINS(content, 'database AND performance', 1) > 0\nORDER BY SCORE(1) DESC;","note":"Oracle Text（原 interMedia）通过 CONTEXT 索引提供支持语言特性的全文搜索。CONTAINS 运算符支持 AND/OR/NEAR 等搜索运算符。索引通过 CTX_DDL.SYNC_INDEX 或自动 SYNC 维护。","danger":"safe"},"postgres":{"command":"-- Search with tsvector/tsquery, accelerated by a GIN index:\nCREATE INDEX article_fts_idx ON article\n  USING GIN (to_tsvector('english', content));\nSELECT id, ts_rank(to_tsvector('english', content), q) AS score\nFROM article, to_tsquery('english', 'database & performance') q\nWHERE to_tsvector('english', content) @@ q\nORDER BY score DESC;","note":"PostgreSQL 内置全文搜索通过 @@ 运算符将 tsvector（文档）与 tsquery（查询）进行匹配。GIN 索引可加速对大文本的搜索。语言配置（'english'）负责词干提取和停用词处理。","danger":"safe"},"mysql":{"command":"-- Create a FULLTEXT index (InnoDB/MyISAM):\nALTER TABLE article ADD FULLTEXT INDEX ft_content (content);\n-- Search in natural language / boolean mode:\nSELECT id, MATCH(content) AGAINST('database performance') AS score\nFROM article\nWHERE MATCH(content) AGAINST('+database +performance' IN BOOLEAN MODE);","note":"MySQL FULLTEXT 索引通过 MATCH ... AGAINST 语法查询。IN NATURAL LANGUAGE MODE 返回相关性得分；IN BOOLEAN MODE 通过 +/-/* 运算符提供细粒度控制。InnoDB 从 5.6 起支持全文搜索。","since":"MySQL 5.6","xnote":"InnoDB 全文搜索在 MySQL 5.6 中引入；此前仅 MyISAM 支持。","danger":"safe"},"mssql":{"command":"-- Create a Full-Text Catalog and index:\nCREATE FULLTEXT CATALOG ft_catalog;\nCREATE FULLTEXT INDEX ON dbo.article(content)\n  KEY INDEX PK_article ON ft_catalog;\n-- Search with CONTAINS / FREETEXT:\nSELECT id FROM dbo.article\nWHERE CONTAINS(content, '\"database\" AND \"performance\"');","note":"SQL Server 全文搜索首先需要全文目录，然后在表上创建全文索引（必须有唯一的 KEY INDEX）。CONTAINS 支持精确/邻近搜索和逻辑运算符；FREETEXT 支持基于含义的（语言学）搜索。必须安装全文搜索组件。","danger":"safe"},"mongodb":{"command":"// Create a text index:\ndb.article.createIndex({ content: \"text\" })\n// Search with $text and sort by relevance score:\ndb.article.find(\n  { $text: { $search: \"database performance\" } },\n  { score: { $meta: \"textScore\" } }\n).sort({ score: { $meta: \"textScore\" } })","note":"MongoDB text 索引通过 $text 运算符提供全文搜索；一个集合只能有一个 text 索引。textScore 元字段给出相关性。更高级的搜索使用 Atlas Search（$search，基于 Lucene）。","danger":"safe"},"redis":{"command":"# Requires the RediSearch module (Redis Stack).\n# Define the index:\nFT.CREATE article_idx ON HASH PREFIX 1 article: SCHEMA content TEXT\n# Full-text search:\nFT.SEARCH article_idx \"database performance\"","note":"核心 Redis 不提供全文搜索；RediSearch 模块（随 Redis Stack 提供）通过 FT.CREATE 建立索引、通过 FT.SEARCH 进行搜索。支持词干提取、模糊搜索、过滤和排序。未安装该模块时无法使用此功能。","danger":"safe"},"elasticsearch":{"command":"# Elasticsearch is natively a full-text search engine.\n# match query (analyzed text search):\nGET article/_search\n{\n  \"query\": {\n    \"match\": { \"content\": \"database performance\" }\n  }\n}","note":"Elasticsearch 本质上是基于 Lucene 的全文搜索引擎；text 字段在索引时进行分析（分词、词干提取）。match 查询返回按相关性得分（_score）排序的结果。可指定特定语言的分析器（如 'english'）。","danger":"safe"}}},{"id":"json-query","title":"查询 JSON 数据","aliases":["json","json query","json query","jsonb","document query"],"engines":{"oracle":{"command":"-- Extract a scalar value with JSON_VALUE:\nSELECT JSON_VALUE(data, '$.customer.city') AS city FROM orders;\n-- Expand into rows with JSON_TABLE:\nSELECT t.product, t.quantity\nFROM orders s,\n     JSON_TABLE(s.data, '$.items[*]'\n       COLUMNS (product VARCHAR2(50) PATH '$.product',\n                quantity NUMBER PATH '$.quantity')) t;","note":"Oracle（12c+）通过 SQL/JSON 路径表达式查询 JSON 数据。JSON_VALUE 返回单个标量值，JSON_QUERY 返回对象/数组片段，JSON_TABLE 将 JSON 转换为关系型行列结构。21c+ 支持原生 JSON 数据类型。","since":"Oracle 12c","xnote":"SQL/JSON 查询在 Oracle 12c 中引入，原生 JSON 数据类型在 21c 中引入。","danger":"safe"},"postgres":{"command":"-- jsonb operators:\nSELECT data->'customer'->>'city' AS city FROM orders;\n-- Containment (@>) and a GIN index:\nCREATE INDEX ix ON orders USING GIN (data);\nSELECT * FROM orders\nWHERE data @> '{\"status\": \"approved\"}';","note":"PostgreSQL jsonb 类型提供丰富的运算符：->（json 对象/元素）、->>（文本值）、#>（路径）、@>（包含）、?（键存在）。GIN 索引可加速 @> 和 ? 查询。也可以通过 jsonb_path_query 进行 SQL/JSON 路径查询。","since":"PostgreSQL 9.4","xnote":"json 类型在 PostgreSQL 9.2 中引入，jsonb 类型在 9.4 中引入。","danger":"safe"},"mysql":{"command":"-- JSON_EXTRACT or shorthand operators:\nSELECT JSON_EXTRACT(data, '$.customer.city') AS city FROM orders;\n-- ->> returns unquoted text:\nSELECT data->>'$.customer.city' AS city FROM orders\nWHERE data->>'$.status' = 'approved';","note":"MySQL（5.7+）使用 JSON_EXTRACT() 查询原生 JSON 类型；-> 运算符是 JSON_EXTRACT 的简写，->> 是 JSON_UNQUOTE(JSON_EXTRACT()) 的简写。对 JSON 字段的搜索可通过生成列 + 索引加速。","since":"MySQL 5.7","xnote":"原生 JSON 数据类型在 MySQL 5.7 中引入。","danger":"safe"},"mssql":{"command":"-- Extract a scalar value with JSON_VALUE:\nSELECT JSON_VALUE(data, '$.customer.city') AS city FROM orders;\n-- Expand into rows with OPENJSON:\nSELECT k.product, k.quantity\nFROM orders s\nCROSS APPLY OPENJSON(s.data, '$.items')\n  WITH (product NVARCHAR(50) '$.product', quantity INT '$.quantity') k;","note":"SQL Server（2016+）将 JSON 存储在 NVARCHAR 列中。JSON_VALUE 返回标量值，JSON_QUERY 返回对象/数组，OPENJSON 将 JSON 转换为表格形式。ISJSON() 用于验证。对 JSON 路径的查找可通过计算列 + 索引加速。","since":"SQL Server 2016","xnote":"JSON 函数（JSON_VALUE/OPENJSON）在 SQL Server 2016 中引入。","danger":"safe"},"mongodb":{"command":"// MongoDB is natively a document (BSON/JSON) database.\n// Access nested fields with dot notation:\ndb.orders.find({ \"customer.city\": \"Ankara\" })\n// Array element and projection:\ndb.orders.find(\n  { \"items.product\": \"pen\" },\n  { \"items.$\": 1 }\n)","note":"MongoDB 将所有数据存储为文档（类 JSON 的 BSON）；查询是原生的。点号表示法可访问嵌套字段和数组元素。复杂转换通过 Aggregation Pipeline 完成，索引可实现对嵌套字段的快速查找。","danger":"safe"},"redis":{"command":"# Requires the RedisJSON module (Redis Stack).\n# Write a JSON document:\nJSON.SET order:1 $ '{\"customer\":{\"city\":\"Ankara\"},\"status\":\"approved\"}'\n# Read a value with JSONPath:\nJSON.GET order:1 $.customer.city","note":"核心 Redis 没有 JSON 类型；RedisJSON 模块（Redis Stack）通过 JSON.SET/JSON.GET 提供基于 JSONPath 的读写。与 RediSearch 结合可对 JSON 文档进行索引和查询。没有该模块时，JSON 以字符串存储并在应用中解析。","danger":"safe"},"elasticsearch":{"command":"# Documents are already JSON; query fields directly:\nGET orders/_search\n{ \"query\": { \"term\": { \"customer.city\": \"Ankara\" } } }\n# For nested arrays, use the nested type and a nested query:\nGET orders/_search\n{ \"query\": { \"nested\": { \"path\": \"items\",\n  \"query\": { \"match\": { \"items.product\": \"pen\" } } } } }","note":"Elasticsearch 以 JSON 原生存储和索引文档。要在嵌套对象数组中保留元素边界，使用 'nested' 字段类型和 nested 查询；否则 object 类型的数组会被扁平化（存在交叉匹配风险）。object 与 nested 的区别对结果正确性至关重要。","danger":"safe"}}},{"id":"spatial-geo","title":"地理 / 空间查询","aliases":["spatial","geographic","geo","gis","location","map"],"engines":{"oracle":{"command":"-- Oracle Spatial: SDO_GEOMETRY column and spatial index\nCREATE INDEX location_sidx ON shop(location)\n  INDEXTYPE IS MDSYS.SPATIAL_INDEX;\n-- Shops within 5 km:\nSELECT name FROM shop\nWHERE SDO_WITHIN_DISTANCE(location,\n  SDO_GEOMETRY(2001, 4326,\n    SDO_POINT_TYPE(32.85, 39.92, NULL), NULL, NULL),\n  'distance=5 unit=KM') = 'TRUE';","note":"Oracle Spatial 将地理数据存储在 SDO_GEOMETRY 类型中；需要 USER_SDO_GEOM_METADATA 条目和空间索引。SDO_WITHIN_DISTANCE、SDO_RELATE 和 SDO_NN 等运算符用于距离/相交/最近邻查询。SRID 4326 是 WGS84（纬度/经度）坐标系。","danger":"safe"},"postgres":{"command":"-- PostGIS extension:\nCREATE EXTENSION IF NOT EXISTS postgis;\nCREATE INDEX location_gix ON shop USING GIST (location);\n-- Shops within 5 km (geography, meters):\nSELECT name FROM shop\nWHERE ST_DWithin(location,\n  ST_SetSRID(ST_MakePoint(32.85, 39.92), 4326)::geography,\n  5000);","note":"PostGIS 使 PostgreSQL 成为功能完备的空间数据库。它提供 geometry（平面）和 geography（球面）类型，以及数百个 ST_ 函数，如 ST_Distance/ST_DWithin/ST_Intersects/ST_Contains。GiST/SP-GiST 索引可加速空间查询。","danger":"safe"},"mysql":{"command":"-- Spatial column (with SRID) and SPATIAL index:\nALTER TABLE shop MODIFY location POINT NOT NULL SRID 4326;\nALTER TABLE shop ADD SPATIAL INDEX (location);\n-- Compute distance (meters, geography):\nSELECT name,\n  ST_Distance(location,\n    ST_SRID(POINT(32.85, 39.92), 4326)) AS distance\nFROM shop\nORDER BY distance LIMIT 10;","note":"MySQL（5.7+）支持符合 OGC 标准的空间数据类型（POINT、POLYGON 等）和 ST_ 函数。SPATIAL 索引（R-tree）只能在 InnoDB/MyISAM 上且只能在 NOT NULL 列上创建；要让优化器使用它，列必须定义 SRID 属性（SRID 4326）。使用 SRID 4326 时，ST_Distance 返回以米为单位的测地距离。使用 POINT(x, y) + ST_SRID 时，x 为经度、y 为纬度；对于 SRID 4326 的 WKT 输入（ST_GeomFromText），顺序为纬度-经度。","danger":"safe"},"mssql":{"command":"-- geography type and spatial index:\nCREATE SPATIAL INDEX location_sidx ON dbo.shop(location);\n-- Shops within 5 km (STDistance returns meters):\nSELECT name FROM dbo.shop\nWHERE location.STDistance(\n  geography::Point(39.92, 32.85, 4326)) <= 5000;","note":"SQL Server 提供两种空间类型：geography（球面，纬度/经度）和 geometry（平面）。STDistance、STIntersects 和 STContains 等方法用于查询。空间索引（基于网格）可提升性能。注意 geography::Point(latitude, longitude, SRID) 的参数顺序。","danger":"safe"},"mongodb":{"command":"// 2dsphere index (for GeoJSON):\ndb.shop.createIndex({ location: \"2dsphere\" })\n// Shops nearest to a given point ($near):\ndb.shop.find({\n  location: {\n    $near: {\n      $geometry: { type: \"Point\", coordinates: [32.85, 39.92] },\n      $maxDistance: 5000\n    }\n  }\n})","note":"MongoDB 以 GeoJSON 格式存储地理数据，并通过 2dsphere 索引执行球面查询。支持 $near（按距离排序）、$geoWithin（区域内）和 $geoIntersects 运算符。坐标顺序为 [经度, 纬度]；$maxDistance 以米为单位。","danger":"safe"},"redis":{"command":"# Built-in geospatial commands (built on sorted sets):\nGEOADD shops 32.85 39.92 \"kizilay\"\nGEOADD shops 32.86 39.93 \"cankaya\"\n# Search within 5 km of a point:\nGEOSEARCH shops FROMLONLAT 32.85 39.92\n  BYRADIUS 5 km ASC","note":"Redis 通过 GEOADD（longitude latitude member）将地理位置存储在 sorted set 中。GEOSEARCH（Redis 6.2+，取代旧的 GEORADIUS）返回半径或矩形范围内按距离排序的成员。GEODIST 给出两点间的距离。这是核心功能，无需模块。","danger":"safe"},"elasticsearch":{"command":"# Map the field as geo_point:\nPUT shop\n{ \"mappings\": { \"properties\": { \"location\": { \"type\": \"geo_point\" } } } }\n# Query within 5 km:\nGET shop/_search\n{ \"query\": { \"geo_distance\": {\n  \"distance\": \"5km\",\n  \"location\": { \"lat\": 39.92, \"lon\": 32.85 } } } }","note":"Elasticsearch 支持 geo_point（点）和 geo_shape（多边形/线）字段类型。过滤通过 geo_distance（半径）、geo_bounding_box（矩形）和 geo_polygon 查询完成；排序使用 geo_distance，聚合使用 geohash_grid/geotile_grid。坐标以 lat/lon 或 [lon, lat] 数组给出。","danger":"safe"}}},{"id":"window-functions-adv","title":"窗口函数（ROW_NUMBER/RANK/LAG）","aliases":["window function","window function","row_number","rank","lag","over","analytic"],"engines":{"oracle":{"command":"-- Rank by amount within each city and fetch the previous value:\nSELECT city, name, amount,\n  ROW_NUMBER() OVER (PARTITION BY city ORDER BY amount DESC) AS row_num,\n  RANK()       OVER (PARTITION BY city ORDER BY amount DESC) AS rnk,\n  LAG(amount)   OVER (PARTITION BY city ORDER BY created_at)      AS prev_amount\nFROM sale;","note":"Oracle 是分析（窗口）函数的先驱；完整支持 ROW_NUMBER、RANK、DENSE_RANK、LAG、LEAD、SUM/AVG OVER、NTILE 和 FIRST_VALUE。在 OVER 内，PARTITION BY 定义分组，ORDER BY 定义排序，窗口框架（ROWS/RANGE BETWEEN）定义滑动窗口。","since":"Oracle 8i","danger":"safe"},"postgres":{"command":"-- Window functions with OVER():\nSELECT city, name, amount,\n  ROW_NUMBER() OVER (PARTITION BY city ORDER BY amount DESC) AS row_num,\n  RANK()       OVER (PARTITION BY city ORDER BY amount DESC) AS rnk,\n  LAG(amount)   OVER (PARTITION BY city ORDER BY created_at)      AS prev_amount\nFROM sale;","note":"PostgreSQL 以标准 SQL 语法完整支持窗口函数。提供 ROW_NUMBER/RANK/DENSE_RANK、LAG/LEAD、带 OVER 的聚合、NTILE 和 FIRST_VALUE/LAST_VALUE。重复的窗口定义可通过 WINDOW 子句命名并共享。","since":"PostgreSQL 8.4","danger":"safe"},"mysql":{"command":"-- MySQL 8.0+ window functions:\nSELECT city, name, amount,\n  ROW_NUMBER() OVER (PARTITION BY city ORDER BY amount DESC) AS row_num,\n  RANK()       OVER (PARTITION BY city ORDER BY amount DESC) AS rnk,\n  LAG(amount)   OVER (PARTITION BY city ORDER BY created_at)      AS prev_amount\nFROM sale;","note":"MySQL 在 8.0 版中加入了窗口函数（MariaDB 10.2+ 也支持）。提供 ROW_NUMBER、RANK、DENSE_RANK、LAG、LEAD、NTILE 和带 OVER 的聚合。8.0 之前的版本没有这些函数；过去通过用户变量模拟。","since":"MySQL 8.0","xnote":"MySQL 8.0 之前没有窗口函数；通过用户变量模拟。","danger":"safe"},"mssql":{"command":"-- Window functions with OVER():\nSELECT city, name, amount,\n  ROW_NUMBER() OVER (PARTITION BY city ORDER BY amount DESC) AS row_num,\n  RANK()       OVER (PARTITION BY city ORDER BY amount DESC) AS rnk,\n  LAG(amount)   OVER (PARTITION BY city ORDER BY created_at)      AS prev_amount\nFROM dbo.sale;","note":"SQL Server 自 2005 起支持窗口函数（ROW_NUMBER/RANK），自 2012 起支持 LAG/LEAD、窗口框架和带 OVER 的聚合。PARTITION BY、ORDER BY 和 ROWS/RANGE 框架均完整可用。分页常用 OFFSET/FETCH 或 ROW_NUMBER。","since":"SQL Server 2005","xnote":"排名函数在 2005 中引入，LAG/LEAD 和窗口框架在 2012 中引入。","danger":"safe"},"mongodb":{"command":"// Window calculations with $setWindowFields:\ndb.sale.aggregate([\n  { $setWindowFields: {\n      partitionBy: \"$city\",\n      sortBy: { amount: -1 },\n      output: {\n        row_num: { $documentNumber: {} },\n        rnk: { $rank: {} },\n        prev_amount: { $shift: { output: \"$amount\", by: -1 } }\n      }\n  } }\n])","note":"MongoDB 5.0 引入的 $setWindowFields 聚合阶段提供窗口函数：$documentNumber（ROW_NUMBER）、$rank/$denseRank、$shift（LAG/LEAD）、聚合函数和滑动窗口（window: { documents/range }）。partitionBy 和 sortBy 对应 SQL 中的 PARTITION BY/ORDER BY。","since":"MongoDB 5.0","xnote":"$setWindowFields 阶段在 MongoDB 5.0 中引入。","danger":"safe"},"redis":{"command":"# Not applicable: there are no SQL-like window functions.","note":"Redis 是键值存储；没有分析型窗口函数。有序结果可使用 Sorted Set（通过 ZADD/ZRANGE 实现类似排名的访问）；但 PARTITION BY/LAG 等关系型窗口操作需在应用层完成。","danger":"safe"},"elasticsearch":{"command":"# Not applicable: there are no SQL-like window functions.\n# Closest equivalent: bucket aggregations.\nGET sale/_search\n{ \"size\": 0, \"aggs\": { \"by_city\": {\n  \"terms\": { \"field\": \"city\" },\n  \"aggs\": { \"total\": { \"sum\": { \"field\": \"amount\" } } } } } }","note":"Elasticsearch 不提供传统的窗口函数（ROW_NUMBER/RANK/LAG）。分组和排序通过聚合框架完成：terms 聚合接近 PARTITION BY，top_hits 对应头部记录，derivative/serial_diff（pipeline 聚合）对应类似 LAG 的相邻差值。","danger":"safe"}}},{"id":"columnstore-analytics","title":"列式存储 / 分析","aliases":["columnstore","columnar","column store","analytics","olap","columnar"],"engines":{"oracle":{"command":"-- Database In-Memory: keep the table in memory in columnar format\nALTER TABLE sale INMEMORY;\n-- With priority and compression:\nALTER TABLE sale INMEMORY PRIORITY HIGH\n  MEMCOMPRESS FOR QUERY HIGH;","note":"Oracle Database In-Memory（12.1.0.2+）将表以列式格式保存在内存中（In-Memory Column Store，IM），同时在磁盘上保留行格式（双格式），从而加速分析查询。INMEMORY 是单独许可的选件。inmemory_size 参数设置 IM 区域大小。","since":"Oracle 12.1.0.2","xnote":"Database In-Memory 列存储在 Oracle 12.1.0.2 中引入（单独许可）。","danger":"caution"},"postgres":{"command":"-- Core PostgreSQL is row-based; columnar storage needs an extension:\n-- Citus columnar access method:\nCREATE EXTENSION IF NOT EXISTS citus;\nCREATE TABLE sale_columnar (...) USING columnar;\n-- Alternatives: the Hydra/cstore_fdw extensions.","note":"核心 PostgreSQL 只提供基于行的（heap）存储。列式存储可使用 Citus 'columnar' 访问方法（USING columnar）、Hydra 或较旧的 cstore_fdw 扩展；它们为分析/数据仓库负载提供压缩和扫描速度。","danger":"safe"},"mysql":{"command":"-- Not applicable: core InnoDB does not offer columnar storage.\n-- For analytics: MySQL HeatWave (cloud, in-memory columnar)\n-- or integration with external ClickHouse/Apache Doris.","note":"标准 MySQL（InnoDB）是基于行的，没有内置 columnstore。分析加速使用 MySQL HeatWave（Oracle Cloud 中的内存列式加速器）；本地部署方案通常使用 ClickHouse 或 Apache Doris 等外部列式引擎。","danger":"safe"},"mssql":{"command":"-- Clustered columnstore index (for analytic tables):\nCREATE CLUSTERED COLUMNSTORE INDEX cci_sale ON dbo.sale;\n-- Nonclustered columnstore on a rowstore table (HTAP):\nCREATE NONCLUSTERED COLUMNSTORE INDEX ncci_sale\n  ON dbo.sale (city, product, amount, created_at);","note":"SQL Server columnstore 索引（2012 为只读非聚集；2014 为可更新聚集；2016 为可更新非聚集）以高压缩比按列存储数据；配合 batch mode，可大幅加速以聚合/扫描为主的分析查询。聚集型适合数据仓库，非聚集型适合 HTAP。","since":"SQL Server 2012","xnote":"columnstore 索引在 2012 中引入（只读），可更新的聚集 columnstore 在 2014 中引入，可更新的非聚集 columnstore 在 2016 中引入。","danger":"safe"},"mongodb":{"command":"-- Not applicable: MongoDB is document (row-like) based.\n-- For analytics: dedicated Analytics Nodes in Atlas\n-- or export to a columnar format ($out)/an external engine.","note":"MongoDB 以类似行的 BSON 存储文档；没有内置的列式（columnstore）存储。分析负载可使用 Atlas 副本集中的专用 Analytics Nodes、Atlas Data Federation，或将数据导出到列式系统（如 Parquet）。","danger":"safe"},"redis":{"command":"-- Not applicable: there is no concept of columnar storage.","note":"Redis 是内存键值存储；不区分行存储/列存储。分析聚合通过 Sorted Set、HyperLogLog（近似计数）或 RedisTimeSeries 等特殊结构完成；不适合大规模 OLAP。","danger":"safe"},"elasticsearch":{"command":"# doc_values: fields are stored column-wise on disk (enabled by default).\n# Required for sorting/aggregation; active on non-text fields.\nPUT sale\n{ \"mappings\": { \"properties\":\n  { \"amount\": { \"type\": \"double\", \"doc_values\": true } } } }","note":"为了排序和聚合，Elasticsearch 将字段保存在名为 doc_values 的基于磁盘的列式结构中（numeric/keyword/date 字段默认启用）。这种列式布局可加速分析/聚合查询；text 字段默认禁用。","danger":"safe"}}},{"id":"in-memory","title":"内存表 / 加速","aliases":["in memory","in-memory","memory table","acceleration","in-memory oltp"],"engines":{"oracle":{"command":"-- Keep the table in memory with Database In-Memory:\nALTER TABLE orders INMEMORY;\n-- Memory size (init parameter):\nALTER SYSTEM SET inmemory_size = 4G SCOPE=SPFILE;","note":"Oracle Database In-Memory 将频繁查询的表以列式格式保存在内存中以加速分析（单独许可）。对于 OLTP，Buffer Cache 和 KEEP 池也会将频繁访问的块保留在内存中。内存方案通过磁盘上的行格式保持持久性。","danger":"caution"},"postgres":{"command":"-- Fast table without durability (writes no WAL):\nCREATE UNLOGGED TABLE temp_calc (id int, data text);\n-- Session-private temporary table (tends to stay in memory):\nCREATE TEMP TABLE session_data (id int) ON COMMIT DELETE ROWS;","note":"PostgreSQL 没有专门的内存表引擎；UNLOGGED 表跳过 WAL 写入以加快写入速度（崩溃时内容丢失；不具备持久性）。TEMP 表在会话期间存在，并尽可能通过 temp_buffers 保存在内存中。热数据缓存在 shared_buffers 中。","danger":"caution"},"mysql":{"command":"-- Table using the MEMORY (HEAP) storage engine:\nCREATE TABLE cache_table (\n  id INT PRIMARY KEY,\n  data VARCHAR(100)\n) ENGINE=MEMORY;","note":"MySQL MEMORY（原 HEAP）存储引擎将表数据完全保存在 RAM 中；速度非常快，但服务器重启时数据丢失（结构保留）。它不支持 BLOB/TEXT，并使用固定行长。对于需要持久化的热数据，推荐使用 InnoDB 缓冲池。","danger":"caution"},"mssql":{"command":"-- In-Memory OLTP: memory-optimized table\nCREATE TABLE dbo.session_state (\n  id INT NOT NULL PRIMARY KEY NONCLUSTERED,\n  data NVARCHAR(100)\n) WITH (MEMORY_OPTIMIZED = ON, DURABILITY = SCHEMA_AND_DATA);","note":"SQL Server In-Memory OLTP（Hekaton）通过内存优化表（MEMORY_OPTIMIZED=ON）和无闩锁结构提供高并发 OLTP。DURABILITY=SCHEMA_AND_DATA 保证数据持久；SCHEMA_ONLY 只保留 schema。需要内存优化文件组。","since":"SQL Server 2014","xnote":"In-Memory OLTP（Hekaton）在 SQL Server 2014 中引入。","danger":"caution"},"mongodb":{"command":"-- Hot data is kept in the WiredTiger cache automatically.\n-- Set the cache size (default ~50% of RAM):\n// mongod --wiredTigerCacheSizeGB 8\n// or in the configuration: storage.wiredTiger.engineConfig.cacheSizeGB","note":"MongoDB 没有单独的内存表类型（旧的 In-Memory Storage Engine 仅限 Enterprise，且已弃用）。默认的 WiredTiger 引擎将频繁访问的数据和索引保存在内存缓存中，以提升读取速度；操作系统文件缓存也有贡献。","danger":"safe"},"redis":{"command":"# Redis already runs entirely in memory; nothing extra is needed.\n# Persistence and memory policy settings:\nCONFIG SET maxmemory 4gb\nCONFIG SET maxmemory-policy allkeys-lru","note":"Redis 将所有数据保存在 RAM 中；这正是其速度的设计来源。持久化由 RDB（快照）和/或 AOF（命令日志）提供。maxmemory 上限和淘汰策略（LRU/LFU/random）决定内存满时的行为。","danger":"safe"},"elasticsearch":{"command":"-- Not applicable: there is no fully in-memory table mode.\n# Place hot data on fast nodes with a hot/cold architecture:\nPUT sale/_settings\n{ \"index.routing.allocation.require.data\": \"hot\" }","note":"Elasticsearch 将数据存储在磁盘上（Lucene 段）；没有专门的内存表引擎。速度依赖操作系统文件缓存（page cache）、doc_values 和 hot-warm-cold 架构。充足的 RAM 和 heap 设置对性能至关重要。","danger":"safe"}}},{"id":"time-series","title":"时间序列数据","aliases":["time series","time series","timeseries","iot","metrics","sensor"],"engines":{"oracle":{"command":"-- Range partitioning by date:\nCREATE TABLE measurements (\n  ts TIMESTAMP, sensor_id NUMBER, value NUMBER)\nPARTITION BY RANGE (ts)\nINTERVAL (NUMTODSINTERVAL(1,'DAY'))\n( PARTITION p0 VALUES LESS THAN (TIMESTAMP '2026-01-01 00:00:00') );","note":"在 Oracle 中，时间序列数据通常通过 RANGE（INTERVAL）分区管理；新分区自动创建，旧分区可轻松删除（分区裁剪可加速查询）。也会使用压缩（ILM/ADO）和用于聚合的物化视图。","danger":"safe"},"postgres":{"command":"-- Hypertable with the TimescaleDB extension:\nCREATE EXTENSION IF NOT EXISTS timescaledb;\nCREATE TABLE measurements (ts TIMESTAMPTZ, sensor_id INT, value DOUBLE PRECISION);\nSELECT create_hypertable('measurements', 'ts');\n-- Summarize with a continuous aggregate.","note":"PostgreSQL 可通过原生分区（PARTITION BY RANGE）存储时间序列；TimescaleDB 扩展增加了 hypertable，提供基于时间的自动分块、压缩、保留策略和连续聚合。这是大型 IoT/指标负载的常用方案。","danger":"safe"},"mysql":{"command":"-- RANGE partitioning by date:\nCREATE TABLE measurements (\n  ts DATETIME, sensor_id INT, value DOUBLE,\n  PRIMARY KEY (ts, sensor_id))\nPARTITION BY RANGE (TO_DAYS(ts)) (\n  PARTITION p2026_01 VALUES LESS THAN (TO_DAYS('2026-02-01')),\n  PARTITION pmax VALUES LESS THAN MAXVALUE);","note":"MySQL 没有专门的时间序列引擎；RANGE 分区（配合 TO_DAYS/UNIX_TIMESTAMP）可快速删除旧数据并实现分区裁剪。对于高容量时间序列，通常优先选择外部方案（TimescaleDB、InfluxDB）或汇总表。","danger":"safe"},"mssql":{"command":"-- Partitioned table (partition function + scheme) or\n-- system-versioned temporal table (history kept automatically):\nCREATE TABLE dbo.measurements (\n  id INT PRIMARY KEY, value DECIMAL(10,2),\n  valid_from DATETIME2 GENERATED ALWAYS AS ROW START,\n  valid_to DATETIME2 GENERATED ALWAYS AS ROW END,\n  PERIOD FOR SYSTEM_TIME (valid_from, valid_to))\nWITH (SYSTEM_VERSIONING = ON);","note":"SQL Server 通过分区函数/分区方案对时间序列进行分区管理；这为跨越大日期范围的数据提供裁剪和快速归档。此外，系统版本控制的时态表（SYSTEM_VERSIONING）会自动保存行变更历史，并支持时间点查询（FOR SYSTEM_TIME）。","since":"SQL Server 2016","xnote":"系统版本控制的时态表在 SQL Server 2016 中引入。","danger":"safe"},"mongodb":{"command":"// Time-series collection (MongoDB 5.0+):\ndb.createCollection(\"measurements\", {\n  timeseries: {\n    timeField: \"ts\",\n    metaField: \"sensor_id\",\n    granularity: \"seconds\"\n  }\n})","note":"MongoDB 5.0 引入的时间序列集合会按时间和 meta 字段自动以压缩的类列式（分桶）格式存储数据；可减少磁盘和索引占用，并加速范围查询。timeField 是必需的；metaField（来源/标签）和 granularity 决定性能。","since":"MongoDB 5.0","xnote":"时间序列集合在 MongoDB 5.0 中引入。","danger":"safe"},"redis":{"command":"# Requires the RedisTimeSeries module (Redis Stack).\n# Create a series with a retention period:\nTS.CREATE sensor:1 RETENTION 86400000 LABELS type temperature\n# Add a sample (with an automatic timestamp):\nTS.ADD sensor:1 * 23.5","note":"核心 Redis 没有时间序列类型；RedisTimeSeries 模块（Redis Stack）通过 TS.CREATE/TS.ADD 提供高速样本写入、自动保留（RETENTION）、降采样（TS.CREATERULE），以及通过 TS.RANGE/TS.MRANGE 进行基于标签的范围查询。没有该模块时用 Sorted Set 模拟。","danger":"safe"},"elasticsearch":{"command":"# Data stream: automatically rolling indexes for time series\nPUT _index_template/measurements-tpl\n{ \"index_patterns\": [\"measurements*\"], \"data_stream\": {},\n  \"template\": {\n    \"settings\": { \"index.mode\": \"time_series\", \"index.routing_path\": [\"sensor_id\"] },\n    \"mappings\": { \"properties\": {\n      \"@timestamp\": { \"type\": \"date\" },\n      \"sensor_id\": { \"type\": \"keyword\", \"time_series_dimension\": true },\n      \"temperature\": { \"type\": \"double\", \"time_series_metric\": \"gauge\" } } } } }\nPUT _data_stream/measurements","note":"Elasticsearch 通过 data streams 管理时间序列数据；新的 backing index 会随时间自动创建（rollover），ILM 将旧数据在 hot/warm/cold 层之间迁移并删除。TSDS（index.mode: time_series）提供针对指标数据的存储优化；至少需要一个 time_series_dimension 字段、一个指向它的 index.routing_path 以及一个 @timestamp 字段（否则模板会被拒绝）。","danger":"safe"}}},{"id":"vector-search","title":"向量搜索（AI/embeddings）","aliases":["vector search","vector search","embedding","ai","semantic search","knn"],"engines":{"oracle":{"command":"-- Oracle 23ai native VECTOR type:\nCREATE TABLE docs (id NUMBER, content CLOB, embedding VECTOR);\n-- Similarity search (cosine distance):\nSELECT id FROM docs\nORDER BY VECTOR_DISTANCE(embedding, :query_vector, COSINE)\nFETCH FIRST 5 ROWS ONLY;","note":"Oracle Database 23ai 提供原生 VECTOR 数据类型和 AI Vector Search 功能。VECTOR_DISTANCE 函数使用 COSINE/EUCLIDEAN/DOT 度量计算相似度；近似搜索支持 HNSW 和 IVF 向量索引。embedding 在数据库内或通过外部模型生成。","since":"Oracle 23ai","xnote":"原生 VECTOR 类型和 AI Vector Search 在 Oracle 23ai 中引入。","danger":"safe"},"postgres":{"command":"-- pgvector extension:\nCREATE EXTENSION IF NOT EXISTS vector;\nCREATE TABLE docs (id int, embedding vector(1536));\nCREATE INDEX ON docs USING hnsw (embedding vector_cosine_ops);\n-- 5 nearest neighbors (<-> Euclidean, <=> cosine):\nSELECT id FROM docs ORDER BY embedding <=> :query_vec LIMIT 5;","note":"pgvector 扩展为 PostgreSQL 增加了 vector 类型和相似度运算符：<->（L2/欧氏距离）、<=>（余弦）、<#>（内积）。HNSW 或 IVFFlat 索引可加速近似最近邻（ANN）搜索。广泛用于 RAG 和语义搜索应用。","xnote":"核心 PostgreSQL 没有向量类型；需要 pgvector 扩展。","danger":"safe"},"mysql":{"command":"-- MySQL 9.0+ native VECTOR type:\nCREATE TABLE docs (id INT, embedding VECTOR(1536));\n-- Cosine distance (DISTANCE is HeatWave-only):\nSELECT id, DISTANCE(embedding, :query_vec, 'COSINE') AS d\nFROM docs ORDER BY d LIMIT 5;","note":"MySQL 9.0 增加了原生 VECTOR 数据类型以及 STRING_TO_VECTOR/VECTOR_TO_STRING 和 VECTOR_DIM 函数（Community 版也提供）。DISTANCE() 函数和大规模近似向量搜索仅在 Oracle Cloud 的 MySQL HeatWave 中提供；在 Community/Enterprise 中，需在应用或外部向量存储中计算距离。较旧的本地部署版本需要外部向量存储。","since":"MySQL 9.0","xnote":"原生 VECTOR 数据类型在 MySQL 9.0 中引入。","danger":"safe"},"mssql":{"command":"-- SQL Server 2025 native vector type:\nCREATE TABLE docs (id INT, embedding VECTOR(1536));\n-- Nearest neighbors by cosine distance:\nSELECT TOP 5 id,\n  VECTOR_DISTANCE('cosine', embedding, @query_vec) AS distance\nFROM dbo.docs ORDER BY distance;","note":"SQL Server 2025（以及 Azure SQL）提供原生 vector 数据类型和 VECTOR_DISTANCE 函数（cosine/euclidean/dot）；用于近似最近邻搜索的基于 DiskANN 的向量索引正在开发中。embedding 通过 Azure OpenAI 等外部模型生成并存储。","since":"SQL Server 2025","xnote":"原生 vector 数据类型在 SQL Server 2025 中引入。","danger":"safe"},"mongodb":{"command":"// Atlas Vector Search ($vectorSearch aggregation stage):\ndb.docs.aggregate([\n  { $vectorSearch: {\n      index: \"vector_idx\",\n      path: \"embedding\",\n      queryVector: [0.12, 0.87, /* ... */],\n      numCandidates: 100,\n      limit: 5\n  } }\n])","note":"MongoDB Atlas Vector Search 通过 $vectorSearch 聚合阶段查询 embedding；底层执行基于 HNSW 的近似最近邻搜索。向量索引定义为 Atlas Search 索引。此功能仅限托管的 Atlas 服务（本地部署的 Community 版不提供）。","danger":"safe"},"redis":{"command":"# Requires the RediSearch (Redis Stack) VECTOR field type.\n# Define an HNSW vector index:\nFT.CREATE docs_idx ON HASH PREFIX 1 docs: SCHEMA\n  embedding VECTOR HNSW 6 TYPE FLOAT32 DIM 1536 DISTANCE_METRIC COSINE\n# KNN search (with a query vector):\nFT.SEARCH docs_idx \"*=>[KNN 5 @embedding $vec]\" PARAMS 2 vec \"...\"","note":"Redis 通过 RediSearch 模块（Redis Stack）提供向量搜索；VECTOR 字段类型支持 FLAT（精确）或 HNSW（近似）索引，以及 COSINE/L2/IP 度量。最近邻通过 FT.SEARCH 中的 KNN 语法检索。用于低延迟语义搜索和 RAG。","danger":"safe"},"elasticsearch":{"command":"# dense_vector field and kNN search:\nPUT docs\n{ \"mappings\": { \"properties\": { \"embedding\":\n  { \"type\": \"dense_vector\", \"dims\": 1536, \"similarity\": \"cosine\" } } } }\nGET docs/_search\n{ \"knn\": { \"field\": \"embedding\", \"query_vector\": [0.12, 0.87],\n  \"k\": 5, \"num_candidates\": 100 } }","note":"Elasticsearch 通过 dense_vector 字段类型和内置 knn 搜索支持近似最近邻（HNSW）查询；相似度可以是 cosine/dot_product/l2_norm。将向量搜索与传统文本查询结合（混合搜索 / RRF）可提高相关性。","since":"Elasticsearch 8.0","xnote":"内置的 dense_vector kNN 搜索在 Elasticsearch 8.x 中引入。","danger":"safe"}}},{"id":"cdc-change-tracking","title":"变更跟踪（CDC / change tracking）","aliases":["cdc","change data capture","change tracking","change tracking","replication feed","binlog"],"engines":{"oracle":{"command":"-- Read changes from redo logs with LogMiner:\n-- 1) Add the archived/redo log file to analyze:\nEXECUTE DBMS_LOGMNR.ADD_LOGFILE(\n  LOGFILENAME => '/u01/arch/1_1234_1122334455.arc',\n  OPTIONS => DBMS_LOGMNR.NEW);\n-- 2) Start the session:\nEXECUTE DBMS_LOGMNR.START_LOGMNR(\n  OPTIONS => DBMS_LOGMNR.DICT_FROM_ONLINE_CATALOG);\nSELECT sql_redo, sql_undo FROM V$LOGMNR_CONTENTS\nWHERE seg_name = 'ORDERS';\n-- 3) When finished: EXECUTE DBMS_LOGMNR.END_LOGMNR;\n-- For enterprise CDC/replication, Oracle GoldenGate is used.","note":"Oracle 基于重做日志进行变更跟踪。LogMiner 将重做日志/归档日志转换为 SQL 以检查历史 DML；需分析的日志文件通过 DBMS_LOGMNR.ADD_LOGFILE 添加。要获得有意义的结果，至少必须启用最小补充日志（ALTER DATABASE ADD SUPPLEMENTAL LOG DATA）。对于持续、低延迟的 CDC 和异构复制，Oracle GoldenGate（单独产品）是标准方案；旧的 Streams/Change Data Capture 功能已弃用。","danger":"caution"},"postgres":{"command":"-- Create a logical replication (logical decoding) slot:\nSELECT * FROM pg_create_logical_replication_slot(\n  'cdc_slot', 'wal2json');\n-- Consume changes as JSON:\nSELECT data FROM pg_logical_slot_get_changes('cdc_slot', NULL, NULL);","note":"PostgreSQL 逻辑解码通过输出插件（wal2json、pgoutput、test_decoding）将 WAL 记录转换为可读的变更流；复制槽确保变更不会丢失。wal_level 必须为 logical。Debezium 利用该机制提供企业级 CDC。","danger":"caution"},"mysql":{"command":"-- The row-based binlog is the CDC source (binlog_format=ROW):\nSHOW BINARY LOGS;\nSHOW BINLOG EVENTS IN 'binlog.000042';\n-- Usually Debezium/Maxwell reads the binlog and turns it into a stream.","note":"在 MySQL 中，CDC 的来源是二进制日志。使用 binlog_format=ROW 时会记录每一行变更。Debezium 或 Maxwell 等工具像副本一样读取 binlog，并向 Kafka 生成事件流。启用 GTID 可简化可靠的位点跟踪。","danger":"caution"},"mssql":{"command":"-- Enable Change Data Capture on the database and the table:\nEXEC sys.sp_cdc_enable_db;\nEXEC sys.sp_cdc_enable_table\n  @source_schema = N'dbo', @source_name = N'orders',\n  @role_name = NULL;\n-- For lightweight tracking: ALTER DATABASE ... SET CHANGE_TRACKING = ON;","note":"SQL Server 提供两种机制：变更数据捕获（CDC）将事务日志中的所有 DML（包含旧值/新值）写入变更表，需要 SQL Server Agent。更改跟踪（Change Tracking）只轻量地跟踪哪些行发生了变化（净变更），不保留完整的数据历史。","since":"SQL Server 2008","xnote":"CDC 和 Change Tracking 在 SQL Server 2008 中引入。","danger":"caution"},"mongodb":{"command":"// Watch changes in real time with Change Streams:\nconst stream = db.orders.watch([\n  { $match: { operationType: { $in: [\"insert\", \"update\"] } } }\n]);\nwhile (stream.hasNext()) { printjson(stream.next()); }","note":"基于 oplog 的 MongoDB Change Streams 以实时流的形式在集合/数据库/集群级别传递 insert、update 和 delete 事件。需要副本集或分片集群。借助 resumeToken，流在中断后可从中断处恢复；它是 CDC 集成的基础。","since":"MongoDB 3.6","xnote":"Change Streams 在 MongoDB 3.6 中引入。","danger":"safe"},"redis":{"command":"# Listen for key changes with keyspace notifications:\nCONFIG SET notify-keyspace-events KEA\n# Subscribe to changed keys via Pub/Sub:\nPSUBSCRIBE __keyevent@0__:set\nPSUBSCRIBE __keyevent@0__:expired","note":"Redis keyspace 通知通过 Pub/Sub 频道发布键上的事件（SET、DEL、EXPIRE 等）；通过 notify-keyspace-events 设置启用（K=keyspace，E=keyevent，A=所有事件）。若需要可靠、持久的变更流，推荐使用 Redis Streams（XADD/XREAD）。","danger":"safe"},"elasticsearch":{"command":"-- Not applicable: there is no built-in CDC/change stream.\n# Closest equivalent: incremental query on a timestamp field:\nGET orders/_search\n{ \"query\": { \"range\": { \"updated_ts\": { \"gt\": \"2026-07-05T00:00:00\" } } },\n  \"sort\": [ { \"updated_ts\": \"asc\" } ] }","note":"Elasticsearch 不提供变更数据捕获（CDC）流；它不会原生发布文档何时发生了变化。常见做法是为每个文档添加 updated_ts 字段，并按时间戳执行增量查询（range + sort）。通常是将源数据库的 CDC 输送到 Elasticsearch。","danger":"safe"}}}]},"rosetta/sql-syntax.json":{"category":"SQL 语法与关键字","categoryId":"sql-syntax","order":19,"concepts":[{"id":"with-cte","title":"WITH（公用表表达式 / CTE）","aliases":["with","cte","common table expression","temporary query","temporary result set"],"engines":{"oracle":{"command":"WITH region AS (\n  SELECT city, COUNT(*) AS n\n  FROM customer\n  GROUP BY city\n)\nSELECT city, n FROM region WHERE n > 10;","note":"WITH 在查询内定义一个命名的临时结果集（CTE）；无需重复编写子查询，只需定义一次并按名称引用。它将复杂查询拆分为易读的部分。自 Oracle 9i 起支持。","since":"Oracle 9i","danger":"safe"},"postgres":{"command":"WITH region AS (\n  SELECT city, COUNT(*) AS n\n  FROM customer\n  GROUP BY city\n)\nSELECT city, n FROM region WHERE n > 10;","note":"CTE 将复杂查询拆分为命名块。在 PostgreSQL 12 之前，CTE 是优化屏障（总是物化）；从 12 起可以被内联，旧行为可通过 WITH ... AS MATERIALIZED 强制。","danger":"safe"},"mysql":{"command":"WITH region AS (\n  SELECT city, COUNT(*) AS n\n  FROM customer\n  GROUP BY city\n)\nSELECT city, n FROM region WHERE n > 10;","note":"CTE 在查询中定义一个临时的命名结果集，提高可读性。从 MySQL 8.0（以及 MariaDB 10.2）起支持；旧版本需要使用派生表（FROM 中的子查询）。","since":"MySQL 8.0","xnote":"MySQL 8.0 之前没有 CTE；同样的功能用派生表（FROM 中的子查询）实现。","danger":"safe"},"mssql":{"command":"WITH region AS (\n  SELECT city, COUNT(*) AS n\n  FROM customer\n  GROUP BY city\n)\nSELECT city, n FROM region WHERE n > 10;","note":"CTE 是命名的临时结果集。在 SQL Server 中，紧接在 CTE 之前的语句必须以分号结尾，因此 WITH 常写作 ';WITH'。它在单条语句（SELECT/INSERT/UPDATE/DELETE）执行期间有效。","danger":"safe"},"mongodb":{"command":"// Closest equivalent: aggregation pipeline stages\ndb.customer.aggregate([\n  { $group: { _id: \"$city\", n: { $sum: 1 } } },\n  { $match: { n: { $gt: 10 } } }\n])","note":"MongoDB 没有 SQL/CTE；聚合管道阶段完成同样的工作。每个阶段将上一阶段的输出作为输入，形成类似 CTE 链的逐步处理。中间结果也可以使用 $facet 或 $lookup。","danger":"safe"},"redis":{"command":"# Not applicable: Redis has no concept of SQL or CTEs.","note":"Redis 是没有查询语言的键值数据存储。中间结果需在客户端组合，或使用 Lua 脚本（EVAL）实现服务器端逻辑。","danger":"safe"},"elasticsearch":{"command":"# CTEs are not supported in Elasticsearch SQL.\nPOST _sql\n{ \"query\": \"SELECT city, COUNT(*) n FROM customer GROUP BY city HAVING COUNT(*) > 10\" }","note":"Elasticsearch SQL（_sql API）不支持 WITH/CTE 语法；分组和过滤写在单个 SELECT 中。采用原生方式时，通过 'terms' 聚合和 'bucket_selector' pipeline 聚合实现同样的效果。","danger":"safe"}}},{"id":"recursive-cte","title":"递归 CTE（WITH RECURSIVE）","aliases":["recursive","recursion","with recursive","connect by","hierarchy","tree query"],"engines":{"oracle":{"command":"-- Hierarchy with CONNECT BY:\nSELECT id, name, manager_id, LEVEL\nFROM employee\nSTART WITH manager_id IS NULL\nCONNECT BY PRIOR id = manager_id;\n\n-- Alternative (11gR2+): standard recursive WITH (the RECURSIVE keyword is not written)\nWITH tree (id, name, manager_id) AS (\n  SELECT id, name, manager_id FROM employee WHERE manager_id IS NULL\n  UNION ALL\n  SELECT c.id, c.name, c.manager_id\n  FROM employee c JOIN tree a ON c.manager_id = a.id\n)\nSELECT * FROM tree;","note":"用于遍历层次（树形）数据：组织结构图、物料清单等。Oracle 的传统方式是 CONNECT BY / PRIOR 和 LEVEL 伪列；START WITH 设置根行。自 11gR2 起也支持标准的递归 CTE。","since":"Oracle 11gR2","xnote":"CONNECT BY 是传统方法；标准的递归 WITH（不带 RECURSIVE 关键字）随 Oracle 11gR2 引入。","danger":"safe"},"postgres":{"command":"WITH RECURSIVE tree (id, name, manager_id, level) AS (\n  SELECT id, name, manager_id, 1\n  FROM employee WHERE manager_id IS NULL\n  UNION ALL\n  SELECT c.id, c.name, c.manager_id, a.level + 1\n  FROM employee c JOIN tree a ON c.manager_id = a.id\n)\nSELECT * FROM tree ORDER BY level;","note":"WITH RECURSIVE 用 UNION ALL 将锚点查询和递归部分组合起来；递归部分反复执行，直到结果为空。非常适合层次结构和图遍历。使用 UNION（而非 ALL）或循环保护防止无限循环。","since":"PostgreSQL 8.4","danger":"safe"},"mysql":{"command":"WITH RECURSIVE tree (id, name, manager_id, level) AS (\n  SELECT id, name, manager_id, 1\n  FROM employee WHERE manager_id IS NULL\n  UNION ALL\n  SELECT c.id, c.name, c.manager_id, a.level + 1\n  FROM employee c JOIN tree a ON c.manager_id = a.id\n)\nSELECT * FROM tree ORDER BY level;","note":"MySQL 8.0（以及 MariaDB 10.2+）支持符合标准的 WITH RECURSIVE 语法。递归深度受系统变量 cte_max_recursion_depth 限制（默认 1000）。旧版本需要在存储过程中编写循环。","since":"MySQL 8.0","xnote":"MySQL 8.0（MariaDB 10.2）之前没有递归查询；需在存储过程中编写循环。","danger":"safe"},"mssql":{"command":"WITH tree (id, name, manager_id, level) AS (\n  SELECT id, name, manager_id, 1\n  FROM employee WHERE manager_id IS NULL\n  UNION ALL\n  SELECT c.id, c.name, c.manager_id, a.level + 1\n  FROM employee c JOIN tree a ON c.manager_id = a.id\n)\nSELECT * FROM tree ORDER BY level\nOPTION (MAXRECURSION 100);","note":"在 SQL Server 中，递归 CTE 不写 RECURSIVE 关键字；包含 UNION ALL 且引用自身的 CTE 会被自动视为递归。默认递归上限为 100；可用 OPTION (MAXRECURSION n) 修改（0 = 不限制，谨慎使用）。","since":"SQL Server 2005","danger":"safe"},"mongodb":{"command":"// Closest equivalent: the $graphLookup stage\ndb.employee.aggregate([\n  { $match: { manager_id: null } },\n  { $graphLookup: {\n      from: \"employee\",\n      startWith: \"$id\",\n      connectFromField: \"id\",\n      connectToField: \"manager_id\",\n      as: \"subordinates\"\n  } }\n])","note":"MongoDB 没有递归 CTE；层次遍历使用 $graphLookup 聚合阶段。connectFromField/connectToField 设置关联字段，maxDepth 设置深度上限。可在单个查询中解析树和图关系。","danger":"safe"},"redis":{"command":"# Not applicable: Redis has no recursive queries.","note":"Redis 不提供查询语言。层次/图遍历在应用代码中或通过服务器端 Lua 脚本完成；在核心 Redis 中，树遍历需在客户端编写。（过去使用的 RedisGraph 模块已于 2023 年停止支持。）","danger":"safe"},"elasticsearch":{"command":"# Not applicable: recursive queries are not supported.\n# Approach: model with parent-child (join field) or nested,\n# deep hierarchies are traversed on the application side.","note":"Elasticsearch 不提供递归查询（没有 WITH RECURSIVE / CONNECT BY 的等价物）。层次关系通过 'join'（parent/child）字段类型建模，或将树路径（materialized path）存储在字段中；多层遍历通常在应用中完成。","danger":"safe"}}},{"id":"joins","title":"JOIN 类型（INNER/LEFT/RIGHT/FULL/CROSS）","aliases":["join","inner join","left join","right join","full join","cross join","combine tables","table join"],"engines":{"oracle":{"command":"-- INNER: matching rows\nSELECT m.name, s.amount\nFROM customer m JOIN orders s ON m.id = s.customer_id;\n-- LEFT: customers without orders are included too\nSELECT m.name, s.amount\nFROM customer m LEFT JOIN orders s ON m.id = s.customer_id;\n-- CROSS: Cartesian product\nSELECT r.name, u.name FROM color r CROSS JOIN product u;","note":"JOIN 根据给定条件组合两张表。INNER 只返回匹配行；LEFT/RIGHT 返回一侧的所有行（无匹配处为 NULL）；FULL 返回两侧的行；CROSS 返回所有组合。除了标准 ANSI JOIN，Oracle 还支持旧的 (+) 外连接语法。","danger":"safe"},"postgres":{"command":"SELECT m.name, s.amount\nFROM customer m INNER JOIN orders s ON m.id = s.customer_id;\n\nSELECT m.name, s.amount\nFROM customer m LEFT JOIN orders s ON m.id = s.customer_id;\n\nSELECT m.name, s.amount\nFROM customer m FULL OUTER JOIN orders s ON m.id = s.customer_id;","note":"PostgreSQL 完整支持所有标准 JOIN 类型（INNER、LEFT/RIGHT/FULL OUTER、CROSS）。USING(my_column) 是同名列的简写，而 NATURAL JOIN 会自动按所有同名列连接（谨慎使用）。","danger":"safe"},"mysql":{"command":"SELECT m.name, s.amount\nFROM customer m INNER JOIN orders s ON m.id = s.customer_id;\n\nSELECT m.name, s.amount\nFROM customer m LEFT JOIN orders s ON m.id = s.customer_id;\n\n-- MySQL has no FULL OUTER JOIN:\nSELECT m.name, s.amount FROM customer m LEFT JOIN orders s ON m.id = s.customer_id\nUNION\nSELECT m.name, s.amount FROM customer m RIGHT JOIN orders s ON m.id = s.customer_id;","note":"MySQL/MariaDB 支持 INNER、LEFT、RIGHT 和 CROSS JOIN，但不直接提供 FULL OUTER JOIN；需通过 UNION 合并 LEFT 和 RIGHT 结果来模拟。旧的逗号式连接会产生笛卡尔积，必须用 ON/WHERE 加以限制。","danger":"safe"},"mssql":{"command":"SELECT m.name, s.amount\nFROM customer m INNER JOIN orders s ON m.id = s.customer_id;\n\nSELECT m.name, s.amount\nFROM customer m LEFT JOIN orders s ON m.id = s.customer_id;\n\nSELECT m.name, s.amount\nFROM customer m FULL OUTER JOIN orders s ON m.id = s.customer_id;","note":"SQL Server 支持所有标准 JOIN 类型。旧的 *= / =* 外连接运算符已被移除；必须使用 ANSI JOIN 语法。APPLY（CROSS/OUTER APPLY）是用于逐行相关子查询的附加连接形式。","danger":"safe"},"mongodb":{"command":"// Closest equivalent: $lookup (left outer join)\ndb.customer.aggregate([\n  { $lookup: {\n      from: \"orders\",\n      localField: \"id\",\n      foreignField: \"customer_id\",\n      as: \"orders\"\n  } }\n])","note":"MongoDB 没有传统的 JOIN；$lookup 聚合阶段以数组形式从另一个集合获取匹配的文档（类似左外连接）。要得到类似 INNER 的结果，之后用 $match 过滤掉空数组，并用 $unwind 将数组展开为行。","danger":"safe"},"redis":{"command":"# Not applicable: Redis has no tables or JOINs.","note":"Redis 不提供关系型连接；数据通常被反规范化，或通过读取多个键在客户端建立关系。有限的类连接查询可使用 RediSearch（FT.AGGREGATE）；RedisGraph 模块已于 2023 年停止支持。","danger":"safe"},"elasticsearch":{"command":"# Not applicable: there is no general JOIN in a distributed design.\n# Limited options: nested, parent/child (join field),\n# terms lookup. Data is usually denormalized at indexing time.","note":"由于分布式架构，Elasticsearch 不提供任意的跨表 JOIN。对于有限的关系，可在单个索引内使用 'nested' 字段类型，或在同一分片上使用 'join'（parent/child）字段类型。最常见的方案是在索引时对数据进行反规范化。","danger":"safe"}}},{"id":"window-functions","title":"窗口函数（OVER / PARTITION BY）","aliases":["window","window function","over","partition by","row_number","rank","lag","lead","analytic function"],"engines":{"oracle":{"command":"SELECT name, department, salary,\n  ROW_NUMBER() OVER (PARTITION BY department ORDER BY salary DESC) AS row_no,\n  RANK()       OVER (PARTITION BY department ORDER BY salary DESC) AS rank_no,\n  LAG(salary)    OVER (PARTITION BY department ORDER BY salary)      AS prev_salary\nFROM employee;","note":"窗口函数在不对行分组（不减少行数）的情况下，为每一行在一个“窗口”上进行计算。PARTITION BY 划分窗口，ORDER BY 对其排序。Oracle 是这些分析函数的先驱，提供包括 ROW_NUMBER、RANK、DENSE_RANK、LAG/LEAD 和 SUM() OVER 在内的丰富函数集。","since":"Oracle 8i","danger":"safe"},"postgres":{"command":"SELECT name, department, salary,\n  ROW_NUMBER() OVER w AS row_no,\n  RANK()       OVER w AS rank_no,\n  LEAD(salary)   OVER w AS next_salary\nFROM employee\nWINDOW w AS (PARTITION BY department ORDER BY salary DESC);","note":"PostgreSQL 支持所有标准窗口函数，并允许用 WINDOW ... AS 为重复的窗口定义命名。借助 ROWS/RANGE/GROUPS 框架定义，可计算累计总和、移动平均等。","since":"PostgreSQL 8.4","danger":"safe"},"mysql":{"command":"SELECT name, department, salary,\n  ROW_NUMBER() OVER (PARTITION BY department ORDER BY salary DESC) AS row_no,\n  DENSE_RANK() OVER (PARTITION BY department ORDER BY salary DESC) AS rank_no,\n  LAG(salary)    OVER (PARTITION BY department ORDER BY salary)      AS prev_salary\nFROM employee;","note":"MySQL 8.0（以及 MariaDB 10.2+）支持窗口函数；此前通过用户变量或子查询模拟。提供 ROW_NUMBER、RANK、DENSE_RANK、NTILE、LAG/LEAD、FIRST_VALUE/LAST_VALUE 以及聚合函数的 OVER 形式。","since":"MySQL 8.0","xnote":"MySQL 8.0 之前没有窗口函数；通过变量/自连接模拟。","danger":"safe"},"mssql":{"command":"SELECT name, department, salary,\n  ROW_NUMBER() OVER (PARTITION BY department ORDER BY salary DESC) AS row_no,\n  RANK()       OVER (PARTITION BY department ORDER BY salary DESC) AS rank_no,\n  SUM(salary)    OVER (PARTITION BY department) AS dept_total\nFROM employee;","note":"SQL Server 自 2005 起支持排名窗口函数，自 2012 起支持带框架的聚合以及 LAG/LEAD 等函数。ROW_NUMBER 常用于分页（配合 OFFSET/FETCH）以及查找每组前 N 行。","since":"SQL Server 2005","xnote":"排名窗口函数在 2005 中引入，窗口框架和 LAG/LEAD 在 2012 中引入。","danger":"safe"},"mongodb":{"command":"// Closest equivalent: $setWindowFields (MongoDB 5.0+)\ndb.employee.aggregate([\n  { $setWindowFields: {\n      partitionBy: \"$department\",\n      sortBy: { salary: -1 },\n      output: { row_no: { $rank: {} },\n                prev_salary: { $shift: { output: \"$salary\", by: -1 } } }\n  } }\n])","note":"MongoDB 5.0+ 的 $setWindowFields 阶段等价于 SQL 窗口函数：通过 partitionBy 和 sortBy 定义窗口，生成 $rank/$denseRank/$documentNumber、$shift（LAG/LEAD）和累计总和等输出。在旧版本中，需通过 $group + 代码解决。","danger":"safe"},"redis":{"command":"# Not applicable: Redis has no window functions.","note":"Redis 不提供分析型窗口函数。对于有序结果和排名，使用 Sorted Set（ZSET）结构：用 ZADD 设置分数，用 ZRANK/ZREVRANK 读取排名，用 ZRANGE 读取范围。复杂分析在应用中完成。","danger":"safe"},"elasticsearch":{"command":"# Closest equivalent: top_hits and pipeline aggregations\nGET employee/_search\n{\n  \"size\": 0,\n  \"aggs\": { \"dept\": { \"terms\": { \"field\": \"department\" },\n    \"aggs\": { \"top_paid\": { \"top_hits\": {\n      \"sort\": [ { \"salary\": \"desc\" } ], \"size\": 3 } } } } }\n}","note":"Elasticsearch 没有直接的窗口函数。每组前 N 条记录使用 'top_hits' 子聚合；移动/累计计算使用 'derivative'、'cumulative_sum' 和 'moving_fn' 等 pipeline 聚合。排序和分区通过 terms 聚合完成。","danger":"safe"}}},{"id":"group-rollup","title":"GROUP BY、HAVING、ROLLUP/CUBE/GROUPING SETS","aliases":["group by","having","rollup","cube","grouping sets","grouping","subtotal","subtotals"],"engines":{"oracle":{"command":"-- Basic grouping + HAVING\nSELECT city, COUNT(*) AS n\nFROM customer\nGROUP BY city\nHAVING COUNT(*) > 5;\n\n-- Subtotals and grand total with ROLLUP\nSELECT city, district, SUM(balance)\nFROM customer\nGROUP BY ROLLUP(city, district);","note":"GROUP BY 按给定列对行分组并应用聚合函数；HAVING 过滤分组后的结果（WHERE 在分组前过滤行）。ROLLUP 生成层次化小计 + 总计，CUBE 生成所有组合，GROUPING SETS 生成所选的分组。Oracle 三者均支持。","danger":"safe"},"postgres":{"command":"SELECT city, district, SUM(balance)\nFROM customer\nGROUP BY GROUPING SETS ((city, district), (city), ());\n\n-- CUBE produces all subset combinations\nSELECT city, district, SUM(balance)\nFROM customer\nGROUP BY CUBE(city, district);","note":"PostgreSQL 9.5+ 以符合标准的方式支持 ROLLUP、CUBE 和 GROUPING SETS。GROUPING() 函数可判断某列是否处于汇总行（NULL）。HAVING 在聚合后过滤，WHERE 在聚合前过滤。","since":"PostgreSQL 9.5","xnote":"ROLLUP、CUBE 和 GROUPING SETS 随 PostgreSQL 9.5 引入。","danger":"safe"},"mysql":{"command":"SELECT city, COUNT(*) AS n\nFROM customer\nGROUP BY city\nHAVING n > 5;\n\n-- ROLLUP in MySQL:\nSELECT city, district, SUM(balance)\nFROM customer\nGROUP BY city, district WITH ROLLUP;","note":"MySQL/MariaDB 通过 GROUP BY ... WITH ROLLUP 语法生成小计/总计；不支持 CUBE 和 GROUPING SETS。HAVING 中可以使用 SELECT 别名（这在 Oracle/SQL Server 中通常不可行）。在 ROLLUP 行中，分组列显示为 NULL。","danger":"safe"},"mssql":{"command":"SELECT city, COUNT(*) AS n\nFROM customer\nGROUP BY city\nHAVING COUNT(*) > 5;\n\n-- ROLLUP / CUBE / GROUPING SETS\nSELECT city, district, SUM(balance)\nFROM customer\nGROUP BY ROLLUP(city, district);","note":"除 GROUP BY 外，SQL Server 以现代语法支持 ROLLUP()、CUBE() 和 GROUPING SETS（2008+）；旧的 WITH ROLLUP/WITH CUBE 形式已弃用。GROUPING() 和 GROUPING_ID() 有助于区分汇总行。","since":"SQL Server 2008","xnote":"现代 GROUPING SETS 语法随 SQL Server 2008 引入。","danger":"safe"},"mongodb":{"command":"// Closest equivalent: $group + $sum\ndb.customer.aggregate([\n  { $group: { _id: \"$city\", n: { $sum: 1 } } },\n  { $match: { n: { $gt: 5 } } }\n])\n// $facet is used for ROLLUP-like multi-level totals","note":"在 MongoDB 中，$group 等价于 SQL GROUP BY；_id 设置分组键，累加器（$sum、$avg、$max）定义聚合。分组后的过滤通过 $match 完成（等价于 HAVING）。ROLLUP/CUBE 之类的多级汇总可通过 $facet 运行多个管道获得。","danger":"safe"},"redis":{"command":"# Not applicable: core Redis has no GROUP BY.\n# Aggregation with RediSearch:\nFT.AGGREGATE customer_idx \"*\" GROUPBY 1 @city REDUCE COUNT 0 AS n","note":"核心 Redis 不提供分组；RediSearch 模块的 FT.AGGREGATE 命令通过 GROUPBY 和 REDUCE（COUNT、SUM、AVG）执行类似 SQL 的聚合。FILTER 步骤像 HAVING 一样过滤分组结果。没有该模块时，聚合需在应用中完成。","danger":"safe"},"elasticsearch":{"command":"GET customer/_search\n{\n  \"size\": 0,\n  \"aggs\": {\n    \"by_city\": { \"terms\": { \"field\": \"city\" },\n      \"aggs\": { \"total\": { \"sum\": { \"field\": \"balance\" } } } }\n  }\n}","note":"在 Elasticsearch 中，分组通过 'terms' 桶聚合完成，聚合计算通过指标聚合（sum、avg、cardinality）完成。类似 HAVING 的过滤使用 'bucket_selector' pipeline 聚合。多级小计通过嵌套聚合构建。","danger":"safe"}}},{"id":"case-expr","title":"CASE 表达式（条件值）","aliases":["case","conditional","when","then","decode","if then","conditional value"],"engines":{"oracle":{"command":"-- Searched CASE\nSELECT name,\n  CASE WHEN salary >= 20000 THEN 'high'\n       WHEN salary >= 10000 THEN 'medium'\n       ELSE 'low' END AS tier\nFROM employee;\n\n-- Oracle-specific shortcut: DECODE (equality comparison)\nSELECT name, DECODE(status, 'A', 'Active', 'P', 'Passive', 'Unknown') FROM employee;","note":"CASE 根据条件将表达式映射为不同的值；它有简单形式（CASE my_column WHEN value）和搜索形式（CASE WHEN condition）。Oracle 的 DECODE 作用类似，但只做相等比较，且将 NULL 视为相等；为了可移植性，优先使用 CASE。","danger":"safe"},"postgres":{"command":"SELECT name,\n  CASE WHEN salary >= 20000 THEN 'high'\n       WHEN salary >= 10000 THEN 'medium'\n       ELSE 'low' END AS tier\nFROM employee;","note":"PostgreSQL 完整支持标准 CASE 表达式，可用于任何产生值的地方（SELECT、WHERE、ORDER BY、GROUP BY）。它没有 Oracle 风格的 DECODE；请改用 CASE，短条件可使用 COALESCE/NULLIF。","danger":"safe"},"mysql":{"command":"SELECT name,\n  CASE WHEN salary >= 20000 THEN 'high'\n       WHEN salary >= 10000 THEN 'medium'\n       ELSE 'low' END AS tier\nFROM employee;\n\n-- IF() for a short condition:\nSELECT name, IF(active = 1, 'Yes', 'No') FROM employee;","note":"MySQL/MariaDB 支持标准 CASE 表达式；还有用于快速二分支条件的 IF(condition, if_true, if_false) 函数（类似 SQL Server 的 IIF）。CASE 可用于任何表达式上下文。","danger":"safe"},"mssql":{"command":"SELECT name,\n  CASE WHEN salary >= 20000 THEN 'high'\n       WHEN salary >= 10000 THEN 'medium'\n       ELSE 'low' END AS tier\nFROM employee;\n\n-- Shortcut: IIF() (2012+)\nSELECT name, IIF(active = 1, 'Yes', 'No') FROM employee;","note":"SQL Server 支持标准 CASE；自 2012 起提供用于二分支条件的 IIF() 简写和用于多重匹配的 CHOOSE()。IIF 实际上会被转换为 CASE。CASE 也可用于 ORDER BY 和计算列。","danger":"safe"},"mongodb":{"command":"// Closest equivalent: $switch or $cond\ndb.employee.aggregate([\n  { $project: { name: 1, tier: {\n      $switch: { branches: [\n        { case: { $gte: [\"$salary\", 20000] }, then: \"high\" },\n        { case: { $gte: [\"$salary\", 10000] }, then: \"medium\" } ],\n        default: \"low\" } } } }\n])","note":"在 MongoDB 聚合中，CASE 的等价物是 $switch（多分支）和 $cond（三参数 if-then-else）运算符。它们在 $project 或 $addFields 中用于创建条件派生字段。","danger":"safe"},"redis":{"command":"# Not applicable: Redis has no in-query conditional expression.","note":"Redis 没有类似 CASE 的查询表达式。条件逻辑需写在应用代码或服务器端 Lua 脚本（EVAL）中。在 RediSearch FT.AGGREGATE 中可通过 APPLY 进行有限的表达式计算。","danger":"safe"},"elasticsearch":{"command":"# Closest equivalent: a condition inside a runtime field or script\nGET employee/_search\n{\n  \"runtime_mappings\": { \"tier\": { \"type\": \"keyword\",\n    \"script\": \"if (doc['salary'].value >= 20000) emit('high'); else if (doc['salary'].value >= 10000) emit('medium'); else emit('low');\" } },\n  \"fields\": [\"tier\"]\n}","note":"在 Elasticsearch 中，查询内的 CASE 逻辑以 Painless 脚本编写（runtime field、script_fields 或 scripted metric）。if/else 流程用 Painless 语言定义。'range' 聚合也可以生成类似 CASE 的分类桶。","danger":"safe"}}},{"id":"union-set","title":"UNION / UNION ALL / INTERSECT / EXCEPT（MINUS）","aliases":["union","union all","intersect","except","minus","set operations","combine","intersection","difference"],"engines":{"oracle":{"command":"-- Union without duplicates\nSELECT city FROM customer\nUNION\nSELECT city FROM supplier;\n\n-- Intersection and difference (MINUS instead of EXCEPT in Oracle)\nSELECT city FROM customer INTERSECT SELECT city FROM supplier;\nSELECT city FROM customer MINUS SELECT city FROM supplier;","note":"集合运算符合并两个查询的结果集。UNION 去除重复（较慢），UNION ALL 保留全部（较快），INTERSECT 返回共同行，差集运算符返回仅存在于第一个集合中的行。差集方面，Oracle 使用 MINUS 而不是标准的 EXCEPT（自 21c 起也提供 EXCEPT）。","danger":"safe"},"postgres":{"command":"SELECT city FROM customer\nUNION\nSELECT city FROM supplier;\n\nSELECT city FROM customer INTERSECT SELECT city FROM supplier;\nSELECT city FROM customer EXCEPT SELECT city FROM supplier;","note":"PostgreSQL 完整支持标准的 UNION、UNION ALL、INTERSECT 和 EXCEPT（差集用 EXCEPT，不是 MINUS）。每个运算符的 ALL 形式（INTERSECT ALL、EXCEPT ALL）会保留重复次数。列数和类型必须兼容。","danger":"safe"},"mysql":{"command":"SELECT city FROM customer\nUNION\nSELECT city FROM supplier;\n\n-- INTERSECT/EXCEPT arrive with MySQL 8.0.31+ and MariaDB 10.3+\nSELECT city FROM customer INTERSECT SELECT city FROM supplier;","note":"MySQL 长期以来只支持 UNION / UNION ALL；INTERSECT 和 EXCEPT 直到 MySQL 8.0.31（MariaDB 10.3）才加入。在旧版本中，交集使用 INNER JOIN/EXISTS，差集使用 LEFT JOIN ... IS NULL 或 NOT EXISTS。","since":"MySQL 8.0.31","xnote":"INTERSECT/EXCEPT 随 MySQL 8.0.31（MariaDB 10.3）引入；此前通过 JOIN/EXISTS 解决。","danger":"safe"},"mssql":{"command":"SELECT city FROM customer\nUNION\nSELECT city FROM supplier;\n\nSELECT city FROM customer INTERSECT SELECT city FROM supplier;\nSELECT city FROM customer EXCEPT SELECT city FROM supplier;","note":"SQL Server 支持标准的 UNION、UNION ALL、INTERSECT 和 EXCEPT（2005+）；差集运算符是 EXCEPT（不是 MINUS）。INTERSECT/EXCEPT 比较行时将 NULL 值视为相等。","since":"SQL Server 2005","danger":"safe"},"mongodb":{"command":"// Closest equivalent: $unionWith (MongoDB 4.4+)\ndb.customer.aggregate([\n  { $project: { city: 1, _id: 0 } },\n  { $unionWith: { coll: \"supplier\",\n      pipeline: [ { $project: { city: 1, _id: 0 } } ] } },\n  { $group: { _id: \"$city\" } }  // UNION (distinct) effect\n])","note":"MongoDB 4.4+ 的 $unionWith 阶段合并两个集合的结果（类似 UNION ALL）。要去除重复，之后用 $group 去重。INTERSECT/EXCEPT 没有直接的等价物；通过 $lookup 或集合运算符（$setIntersection、$setDifference）建模。","danger":"safe"},"redis":{"command":"# Set operations with the Set data type:\nSUNION city_customer city_supplier    # union\nSINTER city_customer city_supplier    # intersection\nSDIFF  city_customer city_supplier    # difference","note":"Redis 没有 SQL，但 Set 数据类型提供原生集合运算：SUNION（并集）、SINTER（交集）、SDIFF（差集）及其保存结果的 STORE 变体。它们作用于预先构建的集合成员，而不是查询结果。","danger":"safe"},"elasticsearch":{"command":"# There is no direct set operator.\n# For a union, search multiple indices in one query:\nGET customer,supplier/_search\n{ \"query\": { \"match_all\": {} } }","note":"Elasticsearch 不提供 UNION/INTERSECT/EXCEPT。通过查询多个以逗号分隔的索引可实现并集效果；交集和差集使用 bool 查询的 must/should/must_not 子句，或在应用端进行集合逻辑处理。","danger":"safe"}}},{"id":"exists-in","title":"EXISTS / IN / NOT EXISTS（子查询）","aliases":["exists","in","not exists","not in","subquery","sub-select","correlated subquery"],"engines":{"oracle":{"command":"-- Customers with orders (EXISTS)\nSELECT name FROM customer m\nWHERE EXISTS (SELECT 1 FROM orders s WHERE s.customer_id = m.id);\n\n-- List membership with IN\nSELECT name FROM customer\nWHERE city IN ('Ankara', 'Izmir');\n\n-- Customers with no orders at all (NOT EXISTS)\nSELECT name FROM customer m\nWHERE NOT EXISTS (SELECT 1 FROM orders s WHERE s.customer_id = m.id);","note":"EXISTS 检查相关子查询是否至少返回一行（遇到第一个匹配即停止，因此速度快）。IN 检查某个值是否在列表/子查询结果中。注意：如果子查询可能产生 NULL，NOT IN 会给出意外结果；NOT EXISTS 更安全。Oracle 优化器通常会把两者转换为相同的执行计划。","danger":"safe"},"postgres":{"command":"SELECT name FROM customer m\nWHERE EXISTS (SELECT 1 FROM orders s WHERE s.customer_id = m.id);\n\nSELECT name FROM customer\nWHERE city IN (SELECT city FROM target_cities);\n\nSELECT name FROM customer m\nWHERE NOT EXISTS (SELECT 1 FROM orders s WHERE s.customer_id = m.id);","note":"PostgreSQL 完整支持 EXISTS/IN/NOT EXISTS。使用 NOT IN 时，如果子查询包含 NULL，整个结果可能变为空，因此推荐使用 NOT EXISTS 或 LEFT JOIN ... IS NULL。大多数情况下，规划器会将 IN 和 EXISTS 等同地作为半连接处理。","danger":"safe"},"mysql":{"command":"SELECT name FROM customer m\nWHERE EXISTS (SELECT 1 FROM orders s WHERE s.customer_id = m.id);\n\nSELECT name FROM customer\nWHERE city IN ('Ankara', 'Izmir');\n\nSELECT name FROM customer m\nWHERE NOT EXISTS (SELECT 1 FROM orders s WHERE s.customer_id = m.id);","note":"MySQL/MariaDB 支持 EXISTS/IN/NOT EXISTS。在 MySQL 5.6 之前，相关子查询可能很慢；5.6+ 通过半连接和物化优化加速了 IN 子查询。NOT IN + NULL 的陷阱在这里同样存在。","danger":"safe"},"mssql":{"command":"SELECT name FROM customer m\nWHERE EXISTS (SELECT 1 FROM orders s WHERE s.customer_id = m.id);\n\nSELECT name FROM customer\nWHERE city IN (SELECT city FROM target_cities);\n\nSELECT name FROM customer m\nWHERE NOT EXISTS (SELECT 1 FROM orders s WHERE s.customer_id = m.id);","note":"SQL Server 支持 EXISTS/IN/NOT EXISTS，优化器大多将其作为半连接/反连接处理。如果 NOT IN 子查询包含 NULL，结果可能意外变为空；NOT EXISTS 是安全的选择。INTERSECT/EXCEPT 也可用于类似的成员关系逻辑。","danger":"safe"},"mongodb":{"command":"// IN equivalent: the $in operator\ndb.customer.find({ city: { $in: [\"Ankara\", \"Izmir\"] } })\n\n// EXISTS equivalent: match check with $lookup + $match\ndb.customer.aggregate([\n  { $lookup: { from: \"orders\", localField: \"id\",\n      foreignField: \"customer_id\", as: \"s\" } },\n  { $match: { \"s.0\": { $exists: true } } }  // at least one order\n])","note":"在 MongoDB 中，值列表成员判断使用 $in / $nin 运算符。子查询 EXISTS 的等价做法是用 $lookup 获取匹配文档的数组，再按数组是否为空进行过滤。检查字段是否存在是另一个运算符 { field: { $exists: true } }（判断字段是否存在，而非行间关系）。","danger":"safe"},"redis":{"command":"# Membership check with Set/Hash:\nSISMEMBER target_cities \"Ankara\"   # is it in the set? (IN-like)\nEXISTS customer:1                 # does the key exist?","note":"Redis 没有子查询。值成员判断可对 Set 使用 SISMEMBER（类似 IN）。EXISTS 命令不是子查询；它返回给定键是否存在。关系型的存在性检查需在应用端通过读取多个键完成。","danger":"safe"},"elasticsearch":{"command":"# IN equivalent: terms query\nGET customer/_search\n{ \"query\": { \"terms\": { \"city\": [\"Ankara\", \"Izmir\"] } } }\n\n# Field existence: exists query\nGET customer/_search\n{ \"query\": { \"exists\": { \"field\": \"phone\" } } }","note":"在 Elasticsearch 中，IN 的等价物是 'terms' 查询（匹配多个值之一），NOT IN 是放在 bool must_not 中的 terms。'exists' 查询并不对应 SQL EXISTS；它检查文档中某个字段是否有值。跨表的相关 EXISTS 通常需要反规范化。","danger":"safe"}}},{"id":"limit-paging","title":"行数限制 / 分页（LIMIT/OFFSET、FETCH、ROWNUM、TOP）","aliases":["limit","offset","fetch","rownum","top","paging","page through results","pagination","row limit"],"engines":{"oracle":{"command":"-- 12c+ standard: OFFSET/FETCH\nSELECT name, salary FROM employee\nORDER BY salary DESC\nOFFSET 20 ROWS FETCH NEXT 10 ROWS ONLY;\n\n-- Older versions: with ROWNUM\nSELECT * FROM (\n  SELECT name, salary FROM employee ORDER BY salary DESC\n) WHERE ROWNUM <= 10;","note":"用于限制结果集或分页。自 Oracle 12c 起支持标准的 OFFSET n ROWS FETCH NEXT m ROWS ONLY。此前使用 ROWNUM 伪列；由于 ROWNUM 在排序之前分配，必须外包一层带 ORDER BY 的子查询。","since":"Oracle 12c","xnote":"Oracle 12c 之前，行数限制通过 ROWNUM 实现。","danger":"safe"},"postgres":{"command":"-- LIMIT/OFFSET\nSELECT name, salary FROM employee\nORDER BY salary DESC\nLIMIT 10 OFFSET 20;\n\n-- Standard equivalent: FETCH\nSELECT name, salary FROM employee\nORDER BY salary DESC\nOFFSET 20 ROWS FETCH NEXT 10 ROWS ONLY;","note":"PostgreSQL 同时支持 LIMIT n OFFSET m 和标准的 OFFSET ... FETCH 形式。OFFSET 值很大时速度慢；深度分页使用基于键的（keyset / 'WHERE id > last_id'）分页更高效。没有 ORDER BY 的 LIMIT 不保证结果顺序。","danger":"safe"},"mysql":{"command":"-- LIMIT [offset,] row_count\nSELECT name, salary FROM employee\nORDER BY salary DESC\nLIMIT 10 OFFSET 20;\n\n-- Short form: LIMIT offset, row_count\nSELECT name, salary FROM employee ORDER BY salary DESC LIMIT 20, 10;","note":"MySQL/MariaDB 以两种形式使用 LIMIT 关键字：LIMIT row_count OFFSET offset 或 LIMIT offset, row_count。MySQL 8.0.19+ 也接受标准的 FETCH 语法。为解决深度 OFFSET 的性能问题，推荐使用 keyset 分页。","danger":"safe"},"mssql":{"command":"-- First N with TOP\nSELECT TOP 10 name, salary FROM employee ORDER BY salary DESC;\n\n-- OFFSET/FETCH for paging (2012+), ORDER BY mandatory\nSELECT name, salary FROM employee\nORDER BY salary DESC\nOFFSET 20 ROWS FETCH NEXT 10 ROWS ONLY;","note":"SQL Server 使用 TOP n（及 TOP (n) PERCENT / WITH TIES 变体）获取前 N 行，自 2012 起使用 OFFSET ... FETCH 进行分页。OFFSET/FETCH 必须配合 ORDER BY 才能工作。SQL Server 没有 LIMIT 关键字。","since":"SQL Server 2012","xnote":"OFFSET/FETCH 分页随 2012 引入；此前通过 TOP 或 ROW_NUMBER 实现。","danger":"safe"},"mongodb":{"command":"// Paging with skip/limit\ndb.employee.find()\n  .sort({ salary: -1 })\n  .skip(20)\n  .limit(10)","note":"在 MongoDB 中，limit() 设置返回的文档数，skip() 设置跳过的文档数；与 sort() 结合即可实现分页。skip 值很大时开销高；深度分页推荐基于最后一个已见值的范围（keyset）查询。","danger":"safe"},"redis":{"command":"# Range read on a sorted set (ZSET):\nZREVRANGE salary_sorted 20 29    # members ranked 21-30\n# Range on a list:\nLRANGE queue 0 9              # first 10 elements","note":"Redis 没有 SQL LIMIT，但有范围读取命令：Sorted Set 使用 ZRANGE/ZREVRANGE start stop（基于索引）或 ZRANGEBYSCORE ... LIMIT offset count；List 使用 LRANGE。这些命令提供类似分页的访问方式。","danger":"safe"},"elasticsearch":{"command":"GET employee/_search\n{\n  \"from\": 20,\n  \"size\": 10,\n  \"sort\": [ { \"salary\": \"desc\" } ]\n}","note":"在 Elasticsearch 中，'size' 设置返回的命中数，'from' 设置跳过的数量（等价于 LIMIT/OFFSET）。from+size 最多为 index.max_result_window（默认 10,000）；深度分页使用 'search_after'（keyset）或 PIT（point-in-time）。","danger":"safe"}}},{"id":"merge-upsert","title":"MERGE / UPSERT（存在则更新，否则插入）","aliases":["merge","upsert","on conflict","on duplicate key","insert or update","update if exists"],"engines":{"oracle":{"command":"MERGE INTO customer m\nUSING (SELECT 1 AS id, 'Ali' AS name, 500 AS balance FROM dual) k\nON (m.id = k.id)\nWHEN MATCHED THEN\n  UPDATE SET m.name = k.name, m.balance = k.balance\nWHEN NOT MATCHED THEN\n  INSERT (id, name, balance) VALUES (k.id, k.name, k.balance);","note":"MERGE（upsert）将源与目标进行匹配，更新匹配的行并插入不匹配的行；非常适合批量数据同步。Oracle 对 MERGE 支持完善：还可以编写 WHEN MATCHED ... DELETE 和条件更新。单行源使用 dual 表。","since":"Oracle 9i","danger":"caution"},"postgres":{"command":"-- The PostgreSQL way: INSERT ... ON CONFLICT\nINSERT INTO customer (id, name, balance)\nVALUES (1, 'Ali', 500)\nON CONFLICT (id) DO UPDATE\n  SET name = EXCLUDED.name, balance = EXCLUDED.balance;","note":"PostgreSQL 的传统 upsert 是 INSERT ... ON CONFLICT (unique column) DO UPDATE / DO NOTHING；冲突行的新值通过 EXCLUDED 别名访问。自 PostgreSQL 15 起也支持标准 MERGE 语句。ON CONFLICT 在单表上是原子的，使用广泛。","since":"PostgreSQL 15","xnote":"标准 MERGE 语句随 PostgreSQL 15 引入；此前 upsert 通过 INSERT ... ON CONFLICT（9.5）实现。","danger":"caution"},"mysql":{"command":"INSERT INTO customer (id, name, balance)\nVALUES (1, 'Ali', 500)\nON DUPLICATE KEY UPDATE\n  name = VALUES(name), balance = VALUES(balance);","note":"MySQL/MariaDB 使用 INSERT ... ON DUPLICATE KEY UPDATE 实现 upsert；如果唯一键或主键冲突，则更新该行。新值通过 VALUES(my_column)（或 8.0.19+ 中的 'new.my_column' 行别名）访问。INSERT IGNORE 在冲突时静默跳过该行。MySQL 没有标准 MERGE。","danger":"caution"},"mssql":{"command":"MERGE INTO customer AS m\nUSING (VALUES (1, 'Ali', 500)) AS k(id, name, balance)\nON m.id = k.id\nWHEN MATCHED THEN\n  UPDATE SET m.name = k.name, m.balance = k.balance\nWHEN NOT MATCHED THEN\n  INSERT (id, name, balance) VALUES (k.id, k.name, k.balance);","note":"SQL Server 自 2008 起支持 MERGE 语句；它提供 WHEN NOT MATCHED BY SOURCE 等额外分支，用于删除目标中多余的行。MERGE 语句必须以分号结尾。注意：由于存在一些边界情况的缺陷，简单的 upsert 可能更适合编写单独的 INSERT/UPDATE 语句。","since":"SQL Server 2008","danger":"caution"},"mongodb":{"command":"// Find-and-update or insert with upsert:true\ndb.customer.updateOne(\n  { id: 1 },\n  { $set: { name: \"Ali\", balance: 500 } },\n  { upsert: true }\n)","note":"在 MongoDB 中，upsert 是原生选项：在 updateOne/updateMany/findOneAndUpdate 中加上 { upsert: true }，若有匹配文档则更新，否则创建新文档。对于批量同步，聚合 $merge 阶段（写入/更新到目标集合）更接近 SQL MERGE。","danger":"caution"},"redis":{"command":"# SET is a natural upsert: it overwrites the key if present, otherwise creates it.\nSET customer:1 '{\"name\":\"Ali\",\"balance\":500}'\n# Update/add a hash field:\nHSET customer:1 name Ali balance 500","note":"在 Redis 中，写命令本身就是 upsert：SET/HSET 在键或字段存在时更新，否则创建。使用 SET ... NX 选项仅在不存在时写入，XX 仅在存在时写入。“存在则更新，否则插入”已经是大多数命令的默认行为。","danger":"safe"},"elasticsearch":{"command":"# An index call with a document id acts like an upsert (replaces if present):\nPUT customer/_doc/1\n{ \"name\": \"Ali\", \"balance\": 500 }\n# Partial update + insert if absent:\nPOST customer/_update/1\n{ \"doc\": { \"balance\": 500 }, \"doc_as_upsert\": true }","note":"在 Elasticsearch 中，使用相同 _id 的 index（PUT）调用会在文档存在时完全替换，否则创建（类似 upsert）。对于“不存在则插入”的部分更新，在 _update API 中使用 'doc_as_upsert': true 或 'upsert' 块。也可以使用脚本化 upsert。","danger":"caution"}}},{"id":"returning-output","title":"返回被修改的行（RETURNING / OUTPUT）","aliases":["returning","output","inserted","deleted","returned value","modified row"],"engines":{"oracle":{"command":"-- Single row: RETURNING ... INTO (PL/SQL bind variable)\nUPDATE employee SET salary = salary * 1.1\nWHERE id = 5\nRETURNING salary INTO :new_salary;\n\n-- Get back the key generated by an INSERT\nINSERT INTO employee (name) VALUES ('Ayse')\nRETURNING id INTO :new_id;","note":"RETURNING 无需单独的 SELECT 即可返回 INSERT/UPDATE/DELETE 所修改行的值（如自动生成的 ID）。在 Oracle 中，单行通过 RETURNING ... INTO 绑定变量返回，多行通过 RETURNING ... BULK COLLECT INTO 集合返回。","danger":"safe"},"postgres":{"command":"INSERT INTO employee (name, salary)\nVALUES ('Ayse', 15000)\nRETURNING id, name;\n\nUPDATE employee SET salary = salary * 1.1\nWHERE department = 'IT'\nRETURNING id, salary;","note":"PostgreSQL 的 RETURNING 子句非常灵活：它将 INSERT、UPDATE、DELETE 和 MERGE 语句所影响行的任意列（包括计算表达式）直接作为结果集返回。这是获取生成的 serial/identity 值最简洁的方法。","since":"PostgreSQL 8.2","danger":"safe"},"mysql":{"command":"-- MySQL has no RETURNING; the generated key is retrieved separately:\nINSERT INTO employee (name, salary) VALUES ('Ayse', 15000);\nSELECT LAST_INSERT_ID();\n\n-- MariaDB 10.5+ supports INSERT ... RETURNING:\nINSERT INTO employee (name) VALUES ('Ayse') RETURNING id;","note":"MySQL 不支持标准 RETURNING；INSERT 生成的自增键通过 LAST_INSERT_ID() 获取，更新后的值需通过单独的 SELECT 读取。MariaDB 自 10.5 起提供 INSERT ... RETURNING，自 10.0 起提供 DELETE ... RETURNING。","danger":"safe"},"mssql":{"command":"INSERT INTO employee (name, salary)\nOUTPUT INSERTED.id, INSERTED.name\nVALUES ('Ayse', 15000);\n\nUPDATE employee SET salary = salary * 1.1\nOUTPUT DELETED.salary AS old_salary, INSERTED.salary AS new_salary\nWHERE id = 5;","note":"SQL Server 的等价物是 OUTPUT 子句：它访问 INSERTED（新值）和 DELETED（旧值）伪表；在 UPDATE 中两者都可使用。通过 OUTPUT ... INTO 可将结果写入表/表变量。非常适合将变更复制到审计表。","since":"SQL Server 2005","xnote":"OUTPUT 子句随 SQL Server 2005 引入。","danger":"safe"},"mongodb":{"command":"// findOneAndUpdate returns the modified document\ndb.employee.findOneAndUpdate(\n  { id: 5 },\n  { $mul: { salary: 1.1 } },\n  { returnDocument: \"after\" }   // return the updated version\n)","note":"在 MongoDB 中，findOneAndUpdate / findOneAndDelete / findOneAndReplace 命令在单个原子操作中返回受影响的文档。returnDocument: 'before' 返回修改前的版本，'after' 返回修改后的版本。insertOne 返回所插入文档的 _id。","danger":"safe"},"redis":{"command":"# Some commands return the old/new value directly:\nSET counter 0 GET       # (6.2+) returns the old value and writes the new one (replaces GETSET)\nINCR visits          # increments and returns the new value","note":"Redis 没有单独的 RETURNING，但许多写命令会直接返回结果：INCR/INCRBY 返回新值，SET ... GET 返回旧值（GETSET 自 6.2 起已弃用），LPOP/RPOP 返回被移除的元素。对于原子的读写操作，这种行为可替代 RETURNING。","danger":"safe"},"elasticsearch":{"command":"# The _update response can return the result:\nPOST employee/_update/5\n{\n  \"script\": { \"source\": \"ctx._source.salary *= 1.1\" },\n  \"_source\": true\n}\n# The response returns the updated document in get._source","note":"在 Elasticsearch 中，index/update 操作会返回结果元数据（_id、_version、result）。要获取更新后文档的内容，在 _update 请求中添加 '_source': true；响应的 get 字段包含当前的 _source。无需单独的 GET 调用。","danger":"safe"}}},{"id":"lateral-apply","title":"LATERAL / CROSS APPLY（逐行子查询）","aliases":["lateral","cross apply","outer apply","row-by-row subquery","correlated table function"],"engines":{"oracle":{"command":"-- Each customer's 3 most recent orders (LATERAL, 12c+)\nSELECT m.name, s.created_at, s.amount\nFROM customer m,\n     LATERAL (SELECT created_at, amount FROM orders\n              WHERE customer_id = m.id\n              ORDER BY created_at DESC FETCH FIRST 3 ROWS ONLY) s;\n\n-- Alternative: CROSS APPLY (12c+)\nSELECT m.name, s.amount\nFROM customer m\nCROSS APPLY (SELECT amount FROM orders WHERE customer_id = m.id) s;","note":"LATERAL/APPLY 允许 FROM 中的子查询引用其左侧表的列，因此可以编写“每行的前 N 行”之类的相关连接。Oracle 12c 同时支持 ANSI LATERAL 和 CROSS/OUTER APPLY 语法。","since":"Oracle 12c","xnote":"LATERAL 和 CROSS/OUTER APPLY 随 Oracle 12c 引入。","danger":"safe"},"postgres":{"command":"SELECT m.name, s.created_at, s.amount\nFROM customer m\nCROSS JOIN LATERAL (\n  SELECT created_at, amount FROM orders\n  WHERE customer_id = m.id\n  ORDER BY created_at DESC LIMIT 3\n) s;","note":"在 PostgreSQL 中，依赖左侧行的子查询使用 LATERAL 关键字编写（JOIN LATERAL / CROSS JOIN LATERAL / LEFT JOIN LATERAL）。对“每组前 N 条”以及用返回集合的函数展开行非常方便。没有 CROSS APPLY 语法；使用 LATERAL。","since":"PostgreSQL 9.3","xnote":"LATERAL 随 PostgreSQL 9.3 引入。","danger":"safe"},"mysql":{"command":"-- MySQL 8.0.14+ supports LATERAL\nSELECT m.name, s.amount\nFROM customer m\nJOIN LATERAL (\n  SELECT amount FROM orders\n  WHERE customer_id = m.id\n  ORDER BY created_at DESC LIMIT 3\n) s ON true;","note":"自 MySQL 8.0.14 起支持 LATERAL 派生表；子查询可以引用外部表的列。此前（以及对于 CROSS APPLY）没有等价物；“每组前 N 条”通过窗口函数或相关子查询解决。MariaDB 目前还没有 LATERAL。","since":"MySQL 8.0.14","xnote":"LATERAL 派生表随 MySQL 8.0.14 引入；MariaDB 不支持。","danger":"safe"},"mssql":{"command":"SELECT m.name, s.created_at, s.amount\nFROM customer m\nCROSS APPLY (\n  SELECT TOP 3 created_at, amount FROM orders\n  WHERE customer_id = m.id\n  ORDER BY created_at DESC\n) s;\n-- Use OUTER APPLY to return non-matching rows too","note":"SQL Server 中 LATERAL 的等价物是 APPLY 运算符：CROSS APPLY（类似 INNER JOIN LATERAL，丢弃不匹配的行）和 OUTER APPLY（类似 LEFT JOIN LATERAL，不匹配时为 NULL）。常用于对每一行调用表值函数以及“每组前 N 条”查询。","since":"SQL Server 2005","xnote":"CROSS/OUTER APPLY 运算符随 SQL Server 2005 引入。","danger":"safe"},"mongodb":{"command":"// Closest equivalent: $lookup with a sub-pipeline\ndb.customer.aggregate([\n  { $lookup: {\n      from: \"orders\",\n      let: { mid: \"$id\" },\n      pipeline: [\n        { $match: { $expr: { $eq: [\"$customer_id\", \"$$mid\"] } } },\n        { $sort: { created_at: -1 } }, { $limit: 3 }\n      ],\n      as: \"last3\"\n  } }\n])","note":"在 MongoDB 中，LATERAL/APPLY 的等价物是带子管道和 let 变量的 $lookup 形式：外部文档的字段通过 let 以 $$variable 传入子管道，在其中进行匹配/排序/限制。这解决了“每个文档的相关前 N 条”模式。","danger":"safe"},"redis":{"command":"# Not applicable: Redis has no subqueries / LATERAL.","note":"Redis 不提供逐行的相关子查询。“每个条目的相关前 N 条”模式通过应用端循环（例如对每个键单独执行 ZRANGE）或服务器端 Lua 脚本解决。","danger":"safe"},"elasticsearch":{"command":"# Closest equivalent: terms + top_hits sub-aggregation\nGET orders/_search\n{\n  \"size\": 0,\n  \"aggs\": { \"by_customer\": { \"terms\": { \"field\": \"customer_id\" },\n    \"aggs\": { \"last3\": { \"top_hits\": {\n      \"sort\": [ { \"created_at\": \"desc\" } ], \"size\": 3 } } } } }\n}","note":"Elasticsearch 没有 LATERAL/APPLY；“每组前 N 条记录”模式通过嵌套在 terms 聚合中的 top_hits 子聚合实现。这不是相关子查询，而是在单个索引上进行分组加组内采样。","danger":"safe"}}},{"id":"pivot","title":"PIVOT / UNPIVOT（行↔列）","aliases":["pivot","unpivot","crosstab","cross tab","rows to columns","transpose"],"engines":{"oracle":{"command":"-- Turn sales by month into columns\nSELECT * FROM (\n  SELECT month, amount FROM sale\n)\nPIVOT (SUM(amount) FOR month IN ('Jan' AS jan, 'Feb' AS feb, 'Mar' AS mar));\n\n-- UNPIVOT: turn columns back into rows\nSELECT product, month, amount\nFROM sale_wide\nUNPIVOT (amount FOR month IN (jan AS 'Jan', feb AS 'Feb'));","note":"PIVOT 将行中的不同值转换为列（交叉表），UNPIVOT 将列转换回行；用于汇总/报表。Oracle 自 11g 起提供内置的 PIVOT/UNPIVOT 运算符；列清单必须是静态的（动态列需要 PIVOT XML 或代码）。","since":"Oracle 11g","xnote":"内置的 PIVOT/UNPIVOT 运算符随 Oracle 11g 引入。","danger":"safe"},"postgres":{"command":"-- The tablefunc extension for crosstab\nCREATE EXTENSION IF NOT EXISTS tablefunc;\nSELECT * FROM crosstab(\n  'SELECT product, month, amount FROM sale ORDER BY 1,2'\n) AS ct(product text, jan numeric, feb numeric, mar numeric);\n\n-- Without an extension: conditional aggregation\nSELECT product,\n  SUM(amount) FILTER (WHERE month = 'Jan') AS jan,\n  SUM(amount) FILTER (WHERE month = 'Feb') AS feb\nFROM sale GROUP BY product;","note":"PostgreSQL 没有内置的 PIVOT 运算符。有两种方法：tablefunc 扩展的 crosstab() 函数，或可移植的条件聚合（SUM(...) FILTER (WHERE ...) 或使用 CASE）。FILTER 子句是 PostgreSQL 特有的优雅透视技巧。","danger":"safe"},"mysql":{"command":"-- There is no PIVOT; conditional aggregation with manual CASE\nSELECT product,\n  SUM(CASE WHEN month = 'Jan' THEN amount ELSE 0 END) AS jan,\n  SUM(CASE WHEN month = 'Feb' THEN amount ELSE 0 END) AS feb,\n  SUM(CASE WHEN month = 'Mar' THEN amount ELSE 0 END) AS mar\nFROM sale\nGROUP BY product;","note":"MySQL/MariaDB 没有 PIVOT/UNPIVOT 运算符；交叉表需手动构建：GROUP BY 加上为每个目标列编写的 SUM(CASE WHEN ... THEN ... END)。如果列是动态的，需通过预处理语句以字符串形式生成查询。","danger":"safe"},"mssql":{"command":"SELECT * FROM (\n  SELECT product, month, amount FROM sale\n) src\nPIVOT (\n  SUM(amount) FOR month IN ([Jan], [Feb], [Mar])\n) pvt;\n\n-- Converting back with UNPIVOT is also supported","note":"SQL Server 自 2005 起原生支持 PIVOT 和 UNPIVOT 运算符。PIVOT 列清单以静态的方括号值给出；动态列需通过动态 SQL 构建查询。对于小型汇总，条件聚合（CASE）也是一种选择。","since":"SQL Server 2005","xnote":"PIVOT/UNPIVOT 运算符随 SQL Server 2005 引入。","danger":"safe"},"mongodb":{"command":"// PIVOT equivalent: conditional aggregation inside $group\ndb.sale.aggregate([\n  { $group: {\n      _id: \"$product\",\n      jan: { $sum: { $cond: [ { $eq: [\"$month\", \"Jan\"] }, \"$amount\", 0 ] } },\n      feb: { $sum: { $cond: [ { $eq: [\"$month\", \"Feb\"] }, \"$amount\", 0 ] } }\n  } }\n])","note":"MongoDB 没有 PIVOT 运算符；交叉表在 $group 阶段通过为每个目标列使用 $sum + $cond（条件聚合）构建。反方向（UNPIVOT）则用 $objectToArray 和 $unwind 将字段展开为行。","danger":"safe"},"redis":{"command":"# Not applicable: Redis has no pivot/cross tab.","note":"Redis 没有行列转换的概念。汇总/交叉表在应用端计算；经常请求的汇总可以预先计算并存储在 Hash 字段中（每列一个字段）。RediSearch FT.AGGREGATE 可以进行分组聚合，但不是真正的透视。","danger":"safe"},"elasticsearch":{"command":"# Closest equivalent: nested terms aggregations\nGET sale/_search\n{\n  \"size\": 0,\n  \"aggs\": { \"product\": { \"terms\": { \"field\": \"product\" },\n    \"aggs\": { \"month\": { \"terms\": { \"field\": \"month\" },\n      \"aggs\": { \"total\": { \"sum\": { \"field\": \"amount\" } } } } } }\n}","note":"Elasticsearch 没有 PIVOT 运算符；交叉汇总通过嵌套的 terms 聚合（外层键 / 内层键）构建，结果以嵌套桶而非列的形式返回，再在客户端转换为表格。transform 作业可以生成分组的、类似透视的目标索引。","danger":"safe"}}},{"id":"coalesce-null","title":"NULL 处理（COALESCE/NVL/NULLIF/IS NULL）","aliases":["coalesce","nvl","nullif","is null","ifnull","isnull","null handling","empty value"],"engines":{"oracle":{"command":"-- COALESCE: returns the first non-NULL value (standard)\nSELECT name, COALESCE(phone, mobile, 'none') AS contact FROM customer;\n\n-- Oracle-specific NVL / NVL2\nSELECT NVL(balance, 0) FROM customer;\nSELECT NVL2(discount, 'yes', 'no') FROM orders;\n\n-- NULLIF and IS NULL\nSELECT NULLIF(a, b) FROM t;   -- NULL if a=b\nSELECT name FROM customer WHERE phone IS NULL;","note":"处理 NULL（未知值）：COALESCE 返回列表中第一个非 NULL 值（标准，支持多个参数）。Oracle 的 NVL(a,b) 是两参数的等价函数；NVL2 根据值是否为 NULL 返回不同的值。NULLIF(a,b) 在两者相等时返回 NULL。判断 NULL 务必使用 IS NULL / IS NOT NULL（= NULL 不起作用）。","danger":"safe"},"postgres":{"command":"SELECT name, COALESCE(phone, mobile, 'none') AS contact FROM customer;\nSELECT NULLIF(divisor, 0) FROM t;   -- prevent division by zero\nSELECT name FROM customer WHERE phone IS NULL;\n-- The equivalent of Oracle NVL is COALESCE","note":"PostgreSQL 提供标准的 COALESCE、NULLIF 和 IS NULL/IS NOT NULL；请用 COALESCE 代替 Oracle 的 NVL。NULL 比较使用 IS 而不是 =；如果需要将两个 NULL 视为相等，'IS NOT DISTINCT FROM' 运算符可以做到。","danger":"safe"},"mysql":{"command":"SELECT name, COALESCE(phone, mobile, 'none') AS contact FROM customer;\n\n-- MySQL shortcut: IFNULL (two arguments)\nSELECT IFNULL(balance, 0) FROM customer;\nSELECT NULLIF(a, b) FROM t;\nSELECT name FROM customer WHERE phone IS NULL;","note":"MySQL/MariaDB 支持标准的 COALESCE 和 NULLIF；IFNULL(a,b) 是两参数的简写（类似 Oracle NVL）。<=> 运算符提供 NULL 安全的相等比较（将两个 NULL 视为相等）。NULL 使用 IS NULL / IS NOT NULL 判断。","danger":"safe"},"mssql":{"command":"SELECT name, COALESCE(phone, mobile, 'none') AS contact FROM customer;\n\n-- SQL Server shortcut: ISNULL (two arguments)\nSELECT ISNULL(balance, 0) FROM customer;\nSELECT NULLIF(a, b) FROM t;\nSELECT name FROM customer WHERE phone IS NULL;","note":"SQL Server 提供标准的 COALESCE（多参数，带数据类型提升）和两参数的 ISNULL(a,b)；两者存在细微差别（COALESCE 是标准的，在类型优先级上更灵活）。也支持 NULLIF 和 IS NULL。ISNULL 是 Oracle NVL 的等价物（名称相近）。","danger":"safe"},"mongodb":{"command":"// COALESCE equivalent: $ifNull\ndb.customer.aggregate([\n  { $project: { name: 1,\n      contact: { $ifNull: [\"$phone\", \"$mobile\", \"none\"] } } }\n])\n// NULL / missing field check:\ndb.customer.find({ phone: { $in: [null] } })","note":"在 MongoDB 聚合中，COALESCE 的等价物是 $ifNull 运算符（多参数，返回第一个不为 null/缺失的值）。在查询中，{ field: null } 既匹配值为 null 的文档，也匹配完全缺少该字段的文档；需用 { field: { $type: 'null' } } 或 $exists 明确区分。","danger":"safe"},"redis":{"command":"# Instead of NULL, Redis has the concept of 'no such key/field':\nEXISTS customer:1              # does the key exist?\nHGET customer:1 phone        # returns nil if the field is absent\nGET missing_key                  # (nil)","note":"Redis 没有 SQL 意义上的 NULL；等价情况是键缺失或 Hash 字段缺失，此时命令返回 nil。默认值逻辑（类似 COALESCE）在应用端通过 nil 检查实现。字段是否存在通过 HEXISTS 检查。","danger":"safe"},"elasticsearch":{"command":"# Default assignment when a field is missing (missing bucket / null_value)\n# Find documents without the field in a query:\nGET customer/_search\n{ \"query\": { \"bool\": { \"must_not\": { \"exists\": { \"field\": \"phone\" } } } } }\n# Map empty values to a specific value with null_value in the mapping","note":"在 Elasticsearch 中，null 或缺失字段通过 'exists' 查询检查（must_not exists = 没有该字段的文档）。要实现类似 COALESCE 的默认值，在 mapping 中定义 'null_value'，或在聚合中使用 'missing' 参数为缺失项赋值。默认值也可以通过 runtime field/脚本生成。","danger":"safe"}}}]},"rosetta/overlay-elasticsearch.json":{"engineId":"elasticsearch","byConcept":{"active-sessions":{"command":"GET _tasks?detailed=true&actions=*search*\n# To see node pressure: GET _nodes/hot_threads","note":"Task API 返回当前正在运行的任务（search、reindex、bulk）。对于瞬时 CPU 负载，hot_threads 显示最活跃的线程。","danger":"safe"},"blocking-sessions":{"command":"# Not applicable: Elasticsearch has no concept of row/document locks or blocking.\n# Closest diagnosis: find long-running tasks\nGET _tasks?detailed=true","note":"缓慢通常不是由阻塞引起，而是来自长时间运行的任务（大型搜索、reindex）。通过 running_time_in_nanos 字段评估耗时。","danger":"safe"},"kill-session":{"command":"POST _tasks/<task_id>/_cancel\n# task_id format: nodeId:taskNumber (e.g. oTUltX4IQMOUUVeiohTt8A:12345)","note":"从 GET _tasks 输出中获取 task_id 值。只有可取消的（cancellable:true）任务才能停止。","danger":"danger"},"connection-count":{"command":"GET _nodes/stats/http","note":"current_open 给出当前打开的 HTTP 连接数，total_opened 给出自节点启动以来的连接总数。HTTP 连接的上限由操作系统/socket 限制决定，而不是 http.max_content_length。","danger":"safe"},"database-size":{"command":"GET _cluster/stats\n# For a simple summary: GET _cat/indices?v&h=index,store.size","note":"_cluster/stats 通过 indices.store.size_in_bytes 给出整个集群的磁盘总用量。_cat/indices 以易读形式列出每个索引的大小。","danger":"safe"},"largest-tables":{"command":"GET _cat/indices?v&s=store.size:desc&h=index,docs.count,store.size","note":"在 Elasticsearch 中，“表”的等价物是索引。s=store.size:desc 将最大的索引排在最前面。","danger":"safe"},"free-space":{"command":"GET _cat/allocation?v","note":"disk.used、disk.avail 和 disk.percent 列给出每个节点的磁盘使用情况。超过水位阈值（85%/90%/95%）时，分片分配会停止。","danger":"safe"},"row-count":{"command":"GET <index>/_count\n# Shortcut: GET _cat/count/<index>?v","note":"_count 快速返回文档数。带过滤的计数需在请求体中传入查询：GET <index>/_count {\"query\":{\"term\":{\"status\":\"active\"}}}","danger":"safe"},"top-slow-queries":{"command":"PUT <index>/_settings\n{\n  \"index.search.slowlog.threshold.query.warn\": \"1s\",\n  \"index.search.slowlog.threshold.query.info\": \"500ms\",\n  \"index.search.slowlog.threshold.fetch.warn\": \"1s\"\n}\n# Queries exceeding the threshold are written to the <cluster>_index_search_slowlog.json log file","note":"Elasticsearch 没有集中的慢查询表；search slowlog 会将超过阈值的查询写入节点日志文件。请在那里查看日志。","danger":"caution"},"explain-plan":{"command":"GET <index>/_search\n{\n  \"profile\": true,\n  \"query\": { \"match\": { \"title\": \"test\" } }\n}\n# To check query validity/parsing:\n# GET <index>/_validate/query?explain=true","note":"profile:true 会细分每个查询组件和聚合步骤的耗时。_validate/query?explain 显示查询对应的 Lucene 形式及其被拒绝的原因。","danger":"safe"},"unused-indexes":{"command":"# Not applicable: secondary indexes in ES differ from relational databases.\n# Closest: assess rarely used/unaccessed indices by size and search count\nGET _cat/indices?v&h=index,docs.count,store.size\nGET <index>/_stats/search","note":"不是查找未使用的字段索引，而是通过 _stats/search 找出搜索次数少（search.query_total）或不再被查询的索引，然后将其关闭/删除。","danger":"safe"},"cache-hit-ratio":{"command":"GET _nodes/stats/indices/query_cache\n# Ratio = hit_count / (hit_count + miss_count)","note":"query_cache 缓存 filter 上下文中的结果。request_cache 需通过 GET _nodes/stats/indices/request_cache 单独检查。","danger":"safe"},"logical-dump":{"command":"# First define a repository (once): PUT _snapshot/<repo> {\"type\":\"fs\",\"settings\":{\"location\":\"/mnt/backups\"}}\nPUT _snapshot/<repo>/<snapshot_1>?wait_for_completion=true\n{\n  \"indices\": \"*\",\n  \"include_global_state\": true\n}","note":"在 Elasticsearch 中，逻辑 dump 的等价物是 snapshot API。wait_for_completion=true 会等待操作完成；对于大型快照，将其设为 false 并单独查询状态。","danger":"safe"},"restore-dump":{"command":"POST _snapshot/<repo>/<snapshot_1>/_restore\n{\n  \"indices\": \"*\",\n  \"rename_pattern\": \"(.+)\",\n  \"rename_replacement\": \"restored_$1\"\n}","note":"要恢复已存在的索引，先将其关闭（POST <index>/_close），或使用 rename_pattern 以新名称恢复。不能覆盖恢复到打开的索引上。","danger":"danger"},"last-backup-status":{"command":"GET _snapshot/<repo>/_all\n# For in-progress/latest snapshot details:\n# GET _snapshot/<repo>/<snapshot_1>/_status","note":"state 字段的取值为 SUCCESS / IN_PROGRESS / PARTIAL / FAILED。_status 给出每个分片的详细进度。","danger":"safe"},"list-users":{"command":"GET _security/user","note":"必须启用 X-Pack Security。单个用户：GET _security/user/<user>。内置用户也会出现在列表中。","danger":"safe"},"create-user-grant":{"command":"# 1) Create a role\nPOST _security/role/<role>\n{\n  \"cluster\": [\"monitor\"],\n  \"indices\": [ { \"names\": [\"log-*\"], \"privileges\": [\"read\"] } ]\n}\n# 2) Create the user and assign the role\nPOST _security/user/<user>\n{\n  \"password\": \"StrongPassword123\",\n  \"roles\": [\"<role>\"]\n}","note":"授权基于角色：先定义包含索引/集群权限的角色，再将其分配给用户。密码至少为 6 个字符。","danger":"caution"},"show-privileges":{"command":"GET _security/user/<user>\nGET _security/role/<role>\n# Check whether a specific privilege is held:\n# POST _security/user/<user>/_has_privileges { \"index\": [ { \"names\": [\"log-*\"], \"privileges\": [\"read\"] } ] }","note":"通过 _security/user 查看用户的角色，通过 _security/role 查看角色包含的权限。_has_privileges 可验证某个操作是否被允许。","danger":"safe"},"change-password":{"command":"POST _security/user/<user>/_password\n{\n  \"password\": \"NewStrongPassword123\"\n}","note":"修改自己的密码时，执行 POST _security/user/_password（不带用户名）即可。对于内置的 elastic 用户，也可以使用 bin/elasticsearch-reset-password 工具。","danger":"caution"},"update-statistics":{"command":"# Not applicable: Elasticsearch has no cost-based optimizer or table statistics gathering.\n# Closest: make newly written documents searchable\nPOST <index>/_refresh","note":"Lucene 基于段；无需单独的统计信息更新步骤。_refresh 使尚不可搜索的文档立即可被搜索（默认每 1s 自动执行一次）。","danger":"safe"},"rebuild-index":{"command":"POST _reindex\n{\n  \"source\": { \"index\": \"old_index\" },\n  \"dest\":   { \"index\": \"new_index\" }\n}\n# For large data: run in the background with POST _reindex?wait_for_completion=false","note":"Reindex 用于 mapping 变更或重建损坏的索引。若要无缝切换，将别名指向新索引。","danger":"caution"},"reclaim-space":{"command":"POST <index>/_forcemerge?only_expunge_deletes=true\n# Full merge on read-only/old indices: POST <index>/_forcemerge?max_num_segments=1","note":"被删除/更新的文档以墓碑形式保留在段中；forcemerge 可回收这部分空间。在活跃写入的索引上开销很大；应在非高峰时段执行。","danger":"caution"},"version-uptime":{"command":"GET /\n# Uptime (JVM uptime):\n# GET _nodes/stats/jvm\n# Short summary: GET _cat/nodes?v&h=name,uptime,version","note":"GET / 根端点返回版本、Lucene 版本和集群名称。jvm.uptime_in_millis 是节点运行时间。","danger":"safe"},"show-config-param":{"command":"GET _cluster/settings?include_defaults=true&flat_settings=true","note":"include_defaults=true 还会显示默认值。persistent 和 transient 设置在不同部分返回；节点级设置使用 GET _nodes/settings。","danger":"safe"},"list-tables":{"command":"GET _cat/indices?v","note":"在 Elasticsearch 中，关系表的等价物是索引。通过 health、status、docs.count 和 store.size 列给出概览。默认输出列出打开和关闭的索引，但不包括隐藏索引（如 .ds- backing index、系统索引）；添加 &expand_wildcards=all 可将其包含在内，&expand_wildcards=open 则只显示打开的索引。","danger":"safe"},"describe-table":{"command":"GET <index>/_mapping","note":"mapping 给出字段类型和分析设置；等价于 DESCRIBE TABLE。单个字段：GET <index>/_mapping/field/<field>。","danger":"safe"},"list-indexes":{"command":"GET <index>/_mapping","note":"在 ES 中，字段默认会被索引；没有单独的二级索引表。mapping 中设置了 \"index\": false 的字段不可搜索。集群中的索引（表）：GET _cat/indices?v。","danger":"safe"},"create-index":{"command":"PUT <index>\n{\n  \"settings\": { \"number_of_shards\": 1, \"number_of_replicas\": 1 },\n  \"mappings\": {\n    \"properties\": {\n      \"title\": { \"type\": \"text\" },\n      \"created_at\": { \"type\": \"date\" }\n    }\n  }\n}\n# To add fields later: PUT <index>/_mapping {\"properties\":{...}}","note":"索引创建后，主分片数无法原地修改（副本数是动态的）；如有需要，通过 _split / _shrink API 或 _reindex 生成新索引。现有字段的类型无法修改；可以添加新字段。","danger":"caution"},"show-ddl":{"command":"GET <index>","note":"一次调用即可返回 settings、mappings 和 aliases；这三者合起来就是索引的“DDL”等价物。可以将同样的输出用作新 PUT <index> 的请求体。","danger":"safe"},"replication-status":{"command":"GET _cluster/health\n# For ongoing shard copy/recovery: GET _cat/recovery?v&active_only=true\n# For cross-cluster replication (CCR): GET <index>/_ccr/stats","note":"status 以 green/yellow/red 概括分片健康状况；yellow = 副本未分配。active_shards_percent 给出完成比例。","danger":"safe"},"list-replicas":{"command":"GET _cat/shards?v&h=index,shard,prirep,state,node\n# For node roles: GET _cat/nodes?v","note":"prirep 列的取值为 p（primary）或 r（replica）；显示分片在节点间的分布及其状态（STARTED/RELOCATING）。","danger":"safe"},"promote-failover":{"command":"# Master election is automatic; no manual promote is needed.\n# To convert a CCR follower into an independent (writable) index:\nPOST <index>/_ccr/pause_follow\nPOST <index>/_close\nPOST <index>/_ccr/unfollow\nPOST <index>/_open","note":"节点故障时，Elasticsearch 会自动重新选举 master 并重新分配主分片。只有在需要将 CCR follower 索引与其 leader 断开并使其可写时，才需要手动接管。","danger":"danger"},"open-transactions":{"command":"# Not applicable: Elasticsearch has no multi-document transactions (BEGIN/COMMIT).\n# Operations are atomic at the document level; the closest diagnosis is running tasks:\nGET _tasks?detailed=true","note":"对单个文档的写入是原子的且带版本（乐观并发：if_seq_no / if_primary_term）。没有未提交事务跟踪的概念。","danger":"safe"},"connect-cli":{"command":"curl -u user:password -k https://host:9200\n# Kibana Dev Tools console (recommended): Kibana > Management > Dev Tools\n# With an API key: curl -H \"Authorization: ApiKey <base64key>\" https://host:9200","note":"基于 REST；不使用单独的 CLI 客户端，而是使用 curl 或 Kibana Dev Tools。-k 跳过自签名 TLS 证书验证（生产环境请使用 CA）。","danger":"safe"},"list-databases":{"command":"GET _cat/indices?v\n# For aliases (logical views): GET _aliases","note":"Elasticsearch 没有单独的“数据库”层；最接近的概念是索引以及对其进行分组的别名。data streams：GET _data_stream。","danger":"safe"},"switch-database":{"command":"# Not applicable: the target index is specified directly in the request path; there is no session context to change.\nGET <index>/_search\n# In Kibana, a data view (index pattern) is selected as the default view","note":"没有 USE <db> 式的命令；目标索引/别名在每个请求的路径中给出。要搜索多个索引，用逗号分隔：GET log-2024,log-2025/_search。","danger":"safe"}}},"errors/oracle.json":{"engineId":"oracle","engine":"Oracle","order":1,"errors":[{"code":"ORA-00001","title":"Unique constraint violated（违反唯一约束）","severity":"caution","meaning":"插入或更新的行违反了 UNIQUE 或 PRIMARY KEY 约束，即相同的值已经存在。","cause":"以相同的键值再次插入行、向必须唯一的列写入重复数据，或在复制/重跑过程中对同一条记录处理了两次。","fix":"根据错误消息中的约束名找出被违反的列：SELECT column_name FROM all_cons_columns WHERE constraint_name = 'CONSTRAINT_NAME'。使用 GROUP BY ... HAVING COUNT(*) > 1 查找重复数据。在应用中，对已存在的记录使用 UPDATE 或 MERGE 而不是 INSERT。如果序列值落后，用 ALTER SEQUENCE 将其推进。","tags":["unique","uniqueness","constraint","unique constraint","primary key","duplicate","duplicate key","insert"]},{"code":"ORA-00054","title":"Resource busy and acquire with NOWAIT specified or timeout expired（资源正忙，且指定了 NOWAIT 或等待超时）","severity":"caution","meaning":"要加锁的资源（行或表）已被另一个会话锁定，由于指定了 NOWAIT 或 DDL_LOCK_TIMEOUT 已到期，无法立即获取。","cause":"通常是在表被其他事务锁定时尝试执行 DDL（ALTER TABLE、DROP、CREATE INDEX）；或使用 SELECT ... FOR UPDATE NOWAIT 时遇到了已被锁定的行。","fix":"查找持有锁的会话：SELECT * FROM v$locked_object。关联 v$session 和 v$lock 识别阻塞会话。等待事务结束，必要时用 ALTER SYSTEM KILL SESSION 终止它。对于 DDL 可以设置等待时间：ALTER SESSION SET DDL_LOCK_TIMEOUT = 60; 在业务低峰的维护窗口执行 DDL。","tags":["lock","locked","ddl","nowait","busy","resource busy","timeout"]},{"code":"ORA-00060","title":"Deadlock detected while waiting for resource（等待资源时检测到死锁）","severity":"danger","meaning":"两个或多个会话发生死锁，各自等待对方持有的资源。Oracle 检测到该循环后选择一个牺牲者，并回滚该会话的语句。","cause":"以不同顺序获取行锁、不同事务交叉更新相同的行、未建索引的外键导致更新父表时加表锁，或在位图索引上并发执行 DML。","fix":"检查 alert log 中引用的死锁 trace（图）文件，找出冲突的 SQL 语句和行。在应用中始终以相同顺序访问表/行。为外键列建立索引（未建索引的外键是典型的死锁原因）。保持事务简短并审查提交频率。在应用中捕获发生死锁的事务并重试。","tags":["deadlock","deadlock detected","lock","locking","tx","transaction"]},{"code":"ORA-01017","title":"Invalid username/password; logon denied（用户名/密码无效；登录被拒绝）","severity":"caution","meaning":"连接数据库时提供的用户名或密码不正确，登录被拒绝。","cause":"密码错误、用户名错误、区分大小写的密码设置（SEC_CASE_SENSITIVE_LOGON）与预期不符、密码已被更改，或应用连接字符串中的凭据已过时。","fix":"核实用户名和密码。必要时以 DBA 身份重置密码：ALTER USER app_user IDENTIFIED BY new_password。检查用户是否存在且未被锁定：SELECT username, account_status FROM dba_users。使用 12c+ 的密码哈希版本时，11g 客户端可能无法连接；请检查 SQLNET.ALLOWED_LOGON_VERSION_SERVER 设置。","tags":["auth","credentials","password","password","logon","login","privilege"]},{"code":"ORA-01555","title":"Snapshot too old: rollback segment number too small（快照过旧：回滚段编号过小）","severity":"danger","meaning":"长时间运行的查询无法访问构建其开始时刻读一致性映像所需的旧 undo 数据，因为这些 undo 信息已被覆盖。","cause":"undo 表空间过小、UNDO_RETENTION 过短、在并发 DML 非常繁重时运行长查询，或在查询内部对同一张表循环提交（fetch across commit）。","fix":"增大 UNDO_RETENTION 并扩大 undo 表空间；尽可能交由自动管理（AUTOEXTEND on）。将长查询拆分为更小的部分或在非高峰时段运行。考虑 RETENTION GUARANTEE：ALTER TABLESPACE undotbs1 RETENTION GUARANTEE。修正在循环内提交的应用逻辑。","tags":["undo","snapshot","rollback","read consistency","consistency","long query"]},{"code":"ORA-01652","title":"Unable to extend temp segment by N in tablespace（无法在表空间中将临时段扩展 N）","severity":"danger","meaning":"Oracle 在表空间中找不到足够的可用空间来扩展临时（temp）段。通常发生在排序、哈希连接或全局临时表操作期间。","cause":"TEMP 表空间已满；大型 ORDER BY、GROUP BY、DISTINCT、哈希连接或索引创建操作在 PGA 内存中放不下而溢出到磁盘；数据文件关闭了 AUTOEXTEND，或磁盘空间耗尽。","fix":"监控 TEMP 使用情况：SELECT * FROM v$sort_usage / v$tempseg_usage。为 TEMP 表空间添加文件或扩大它：ALTER TABLESPACE temp ADD TEMPFILE '...' SIZE 2G AUTOEXTEND ON。如果是永久表空间已满，添加数据文件。通过优化查询（合适的索引、更大的 PGA_AGGREGATE_TARGET）减少磁盘排序。","tags":["temp","tablespace","free space","space","sort","sorting","extend"]},{"code":"ORA-04031","title":"Unable to allocate N bytes of shared memory（无法分配 N 字节共享内存）","severity":"danger","meaning":"Oracle 无法在共享内存池（通常是 shared pool 或 large pool）中分配所请求大小的连续内存。某个 SGA 组件处于内存压力之下。","cause":"大量未使用绑定变量（充满字面量）的唯一 SQL 语句导致 shared pool 碎片化、SHARED_POOL_SIZE 不足、并发连接过多、PL/SQL 包过大，或 ASMM/AMM 容量规划不正确。","fix":"确认错误消息指的是哪个池（shared pool、large pool 等）。短期：ALTER SYSTEM FLUSH SHARED_POOL（注意：在生产环境中这会迫使所有 SQL 重新解析，即造成短暂的硬解析风暴，且不能永久解决碎片化）。永久解决：强制使用绑定变量（包括 CURSOR_SHARING），增大 SGA_TARGET / SHARED_POOL_SIZE / LARGE_POOL_SIZE。用 DBMS_SHARED_POOL.KEEP 将常用的包钉在内存中。通过 V$SGASTAT 查看内存分布。","tags":["memory","shared memory","shared pool","sga","ora-4031","bind"]},{"code":"ORA-12154","title":"TNS: could not resolve the connect identifier specified（TNS：无法解析指定的连接标识符）","severity":"caution","meaning":"连接中使用的服务名（连接标识符 / TNS 别名）无法解析。Oracle Net 无法将给定名称映射到服务器/服务信息。","cause":"tnsnames.ora 中缺少该别名、TNS_ADMIN 环境变量指向错误目录、别名拼写错误、缺少 EZConnect/LDAP 命名配置，或 sqlnet.ora 中 NAMES.DIRECTORY_PATH 的顺序错误。","fix":"确认所用别名已在 tnsnames.ora 中定义。测试连接：tnsping service_name。确保 TNS_ADMIN 指向正确的文件夹。也可以尝试 EZConnect：app_user/password@host:1521/service_name。检查 sqlnet.ora 中的 NAMES.DIRECTORY_PATH = (TNSNAMES, EZCONNECT) 行。","tags":["tns","connect","connection","network","networking","tnsnames","service"]},{"code":"ORA-12514","title":"TNS: listener does not currently know of service requested in connect descriptor（TNS：监听程序当前无法识别连接描述符中请求的服务）","severity":"caution","meaning":"监听程序正在运行，但无法识别所请求的服务名；也就是说，该服务未在监听程序中注册。","cause":"数据库实例未完全打开、服务尚未在监听程序中注册（12c+ 由 LREG 注册，11g 及更早版本存在 PMON 注册延迟）、使用了错误的 SERVICE_NAME，或混淆了 SID 与服务名。","fix":"查看监听程序已知的服务：lsnrctl status 和 lsnrctl services。确认数据库处于 OPEN 状态。触发服务注册：在 SQL 中执行 ALTER SYSTEM REGISTER。检查 LOCAL_LISTENER 参数并正确使用 SERVICE_NAME。在可插拔数据库中，确保 PDB 处于 OPEN 状态。","tags":["tns","listener","service","service_name","connection","network","networking"]},{"code":"ORA-00942","title":"Table or view does not exist（表或视图不存在）","severity":"caution","meaning":"查询中引用的表或视图不存在，或对访问用户不可见。","cause":"对象确实不存在、对象位于其他 schema 而未指定 schema 前缀、用户缺少该对象的权限（如 SELECT）、缺少同义词，或对象名拼写错误。","fix":"检查对象是否存在及其属主：SELECT owner, object_type FROM all_objects WHERE object_name = 'TABLE_NAME'。如果位于其他 schema，使用 schema.table 完整限定，或授权 GRANT SELECT ON app_schema.my_table TO app_user。必要时创建公共/私有同义词：CREATE SYNONYM my_table FOR app_schema.my_table。注意大小写和带引号的标识符。","tags":["table","missing table","view","missing view","grant","privilege","synonym","alias"]},{"code":"ORA-01031","title":"Insufficient privileges（权限不足）","severity":"caution","meaning":"用户不具备所尝试操作需要的权限（系统权限或对象权限）。","cause":"未授予所需的系统或对象权限、权限仅通过角色获得（角色在定义者权限的 PL/SQL 块中不生效）、未以 SYSDBA/SYSOPER 身份尝试管理操作，或定义者权限过程缺少直接授权。","fix":"授予缺失的权限，例如：GRANT CREATE TABLE TO app_user 或 GRANT SELECT ON app_schema.my_table TO app_user。注意对于命名 PL/SQL 对象，权限必须直接授予，而不能通过角色。对于需要 SYSDBA 的操作，使用正确的凭据连接。检查当前权限：SELECT * FROM session_privs / user_sys_privs。","tags":["privilege","privileges","grant","permission","auth","role grant","role"]},{"code":"ORA-00257","title":"Archiver error. Connect internal only, until freed（归档进程错误。在释放之前仅允许内部连接）","severity":"danger","meaning":"归档（ARCH）进程无法归档 redo 日志；通常是归档目标（FRA 或归档日志目标）已满。数据库会挂起新的事务。","cause":"Flash Recovery Area（DB_RECOVERY_FILE_DEST）或归档日志目标已满、磁盘空间耗尽、RMAN 备份未删除归档日志，或已达到 DB_RECOVERY_FILE_DEST_SIZE 限制。","fix":"释放空间：用 RMAN 将旧归档日志备份到 FRA 之外的目标并删除：RMAN> BACKUP ARCHIVELOG ALL FORMAT '/backup/arc_%U' DELETE INPUT（FRA 已满时把备份写入 FRA 会失败）。使用 Data Guard 时，不要删除尚未传输到备库或尚未在备库应用的归档；否则备库会出现 gap（CONFIGURE ARCHIVELOG DELETION POLICY TO APPLIED ON ALL STANDBY）。查看 FRA 使用情况：SELECT * FROM v$recovery_file_dest / v$flash_recovery_area_usage。临时增大 DB_RECOVERY_FILE_DEST_SIZE，或将目标移到更大的磁盘。检查磁盘级别的可用空间。建立定期的归档清理策略。","tags":["archive","archivelog","archiver","fra","disk","space","redo"]},{"code":"ORA-01000","title":"Maximum open cursors exceeded（超出打开游标的最大数）","severity":"caution","meaning":"会话打开的游标数超过了 OPEN_CURSORS 参数允许的数量。无法再打开新的游标。","cause":"应用未关闭其打开的 Statement/ResultSet 对象（游标泄漏）、OPEN_CURSORS 相对工作负载设置过低，或在循环中不断准备新的 prepared statement 且从不关闭。","fix":"监控打开的游标：SELECT * FROM v$open_cursor WHERE sid = ...  并通过 v$sesstat 查看每个会话的数量。真正的解决办法是在应用中于 finally 块里关闭每个 Statement/ResultSet（消除游标泄漏）。作为临时办法，增大 OPEN_CURSORS：ALTER SYSTEM SET OPEN_CURSORS = 1000。检查 JDBC 语句缓存大小。","tags":["cursor","open cursor","open_cursors","leak","cursor leak","jdbc"]},{"code":"ORA-00018","title":"Maximum number of sessions exceeded（超出最大会话数）","severity":"danger","meaning":"数据库已达到 SESSIONS 参数允许的最大会话数；无法再打开新的会话。","cause":"SESSIONS/PROCESSES 设置低于工作负载需求、连接池配置过大、连接泄漏，或从不关闭的会话不断累积。","fix":"查看当前会话数：SELECT COUNT(*) FROM v$session。检查限制：SELECT * FROM v$resource_limit WHERE resource_name IN ('sessions','processes')。增大 PROCESSES，并间接增大 SESSIONS（通过 SPFILE，需要重启）：ALTER SYSTEM SET PROCESSES = 500 SCOPE=SPFILE。检查应用中的连接池大小和连接泄漏。","tags":["session","session limit","sessions","processes","connection","connections","limit"]},{"code":"ORA-28000","title":"The account is locked（账户已锁定）","severity":"caution","meaning":"所连接的数据库用户账户已被锁定，因此登录失败。","cause":"超出了 profile 中的 FAILED_LOGIN_ATTEMPTS 限制（多次输错密码），或账户被 DBA 手动锁定（ALTER USER ... ACCOUNT LOCK）。因登录失败而产生的锁定会在 PASSWORD_LOCK_TIME 之后自动解除。注意：PASSWORD_LIFE_TIME 到期不会锁定账户；账户会变为 EXPIRED / EXPIRED(GRACE)，并返回 ORA-28001/ORA-28002。","fix":"检查账户状态：SELECT username, account_status, lock_date FROM dba_users WHERE username = 'APP_USER'。解锁：ALTER USER app_user ACCOUNT UNLOCK。为防止再次发生，找出登录失败的来源，必要时检查 profile 中的 FAILED_LOGIN_ATTEMPTS。如果密码已过期，为其设置新密码。","tags":["account","user account","locked","account locked","auth","password","profile"]},{"code":"ORA-01578","title":"Oracle data block corrupted (file #, block #)（Oracle 数据块损坏（文件号，块号））","severity":"danger","meaning":"指定数据文件中的某个数据块已损坏；Oracle 报告该块未通过一致性检查（校验和）。这属于物理损坏。","cause":"磁盘/硬件故障、存储层错误、操作系统或控制器问题、断电后部分写入的块，或备份/恢复本身已损坏。","fix":"确认损坏及其范围：RMAN> VALIDATE DATABASE / VALIDATE CHECK LOGICAL DATAFILE n；同时运行 DBVERIFY（dbv）并检查 v$database_block_corruption。如果有备份，进行块级恢复：RMAN> RECOVER DATAFILE n BLOCK m；或针对所有列出的块：RMAN> RECOVER CORRUPTION LIST;（旧的 BLOCKRECOVER 命令自 11g 起已弃用）。如果是索引块，重建索引。务必检查硬件/磁盘健康状况；根本原因通常在存储。","tags":["corruption","block corruption","block","corrupt block","datafile","rman","disk"]},{"code":"ORA-01722","title":"Invalid number（无效数字）","severity":"safe","meaning":"在需要数值的上下文中使用了无法转换为数字的字符串。隐式或显式转换失败。","cause":"将数值列与文本比较（如 WHERE num_col = 'ABC'）、向数值字段插入含字母的值、TO_NUMBER 的格式不合适，或小数/分组分隔符不匹配（逗号与句点）。","fix":"找出转换失败的表达式；通常是 WHERE 条件或 INSERT 的值。在文本/数字比较中确保类型兼容，必要时使用显式转换：TO_NUMBER(my_column)，或在文本一侧使用 TO_CHAR。在应用层阻止非数字输入进入数值列。注意 NLS_NUMERIC_CHARACTERS 设置。","tags":["number","numeric","conversion","type conversion","to_number","datatype","data type"]},{"code":"ORA-02049","title":"Timeout: distributed transaction waiting for lock（超时：分布式事务正在等待锁）","severity":"danger","meaning":"分布式事务在等待远程数据库上的锁时超出了 DISTRIBUTED_LOCK_TIMEOUT。事务已超时。","cause":"通过数据库链接运行的事务在等待远端被锁定的行；远程会话中的长事务、网络延迟，或两端持有相互冲突的锁。","fix":"在远程数据库上找到并解决阻塞的锁和会话（v$lock、v$session）。保持分布式事务简短，并尽可能晚地获取所需的锁。必要时针对工作负载增大 DISTRIBUTED_LOCK_TIMEOUT（SPFILE，需要重启）。对于 in-doubt 分布式事务，检查 DBA_2PC_PENDING 视图，必要时手动处理。","tags":["distributed","distributed transaction","dblink","lock","lock wait","timeout","2pc"]},{"code":"enq: TX - row lock contention","title":"等待：会话被另一事务持有的行级锁阻塞","severity":"caution","meaning":"会话正在等待另一事务持有的行锁被释放。这是典型的应用级锁等待事件。","cause":"并发事务尝试更新同一行、事务长时间未提交、未建索引的外键导致大范围加锁，或集中在顺序键上（热块）。","fix":"找出阻塞会话和被阻塞会话：SELECT * FROM v$session WHERE blocking_session IS NOT NULL; 并与 v$lock 关联分析。如果根本原因是长事务，审查提交策略。为外键列建立索引。针对热点行重新考虑设计（sequence、hash）。必要时终止阻塞会话。","tags":["wait","wait event","enq","row lock","row lock wait","tx","contention","lock"]},{"code":"log file sync","title":"等待：会话在提交时等待 LGWR 将 redo 刷新到磁盘","severity":"caution","meaning":"会话执行 COMMIT 时，需要等待 LGWR 将 redo 记录写入（刷新）到 redo 日志文件。如果该等待很高，说明存在提交延迟。","cause":"redo 日志磁盘慢（I/O 延迟高）、应用提交过于频繁（逐行提交）、LGWR 进程受到 CPU 压力，或 redo 量非常大。","fix":"用 AWR/Statspack 测量平均等待时间。将 redo 日志文件移到快速、低延迟的磁盘（SSD）上。减少应用中不必要的提交频率，并将批处理操作分组。必要时根据业务需求评估 COMMIT_LOGGING/COMMIT_WAIT（commit_write）选项；警告：NOWAIT/BATCH 会降低持久性，看似已提交的事务可能在崩溃时丢失。只在可容忍丢失的工作中于会话或语句级别使用；不要在系统范围启用。将 LGWR 与存储延迟结合起来分析。","tags":["wait","wait event","log file sync","commit","lgwr","redo","latency"]},{"code":"db file sequential read","title":"等待：单块物理读，通常为索引或表查找","severity":"caution","meaning":"会话等待从磁盘读取单个块。通常出现在索引访问或按 ROWID 的单块读中；一般属于正常事件。","cause":"不在 buffer cache 中的块需要从磁盘读取、索引选择性差或使用了错误的索引、buffer cache 不足，或存储层慢。","fix":"如果偏高，找出读取最多的 SQL 语句（AWR Top SQL、v$sql）。通过合适的索引和正确的访问路径改进查询，减少不必要的嵌套循环。评估 buffer cache 大小（DB_CACHE_SIZE）。检查存储 I/O 延迟。该事件通常是正常的；如果异常偏高，重点进行 SQL 调优。","tags":["wait","wait event","db file sequential read","io","disk io","index","index access","single block"]},{"code":"db file scattered read","title":"等待：多块物理读，通常为全表扫描或索引快速全扫描","severity":"caution","meaning":"会话等待从磁盘读取多个块（multiblock）。通常发生在全表扫描或索引快速全扫描期间。","cause":"缺少合适的索引导致全表扫描、统计信息过时导致执行计划错误、扫描非常大的表，或所选访问路径退化为表扫描。","fix":"找出执行全扫描的 SQL 语句，添加所需索引或确保使用现有索引。更新统计信息：DBMS_STATS.GATHER_TABLE_STATS。如果确实需要大范围扫描，考虑并行查询或分区。检查 DB_FILE_MULTIBLOCK_READ_COUNT 和存储吞吐量。","tags":["wait","wait event","db file scattered read","full scan","full table scan","io","disk io","multiblock"]},{"code":"buffer busy waits","title":"等待：争用一个正被其他会话读取或修改的缓冲区","severity":"caution","meaning":"会话正在等待访问一个同时被另一会话读取或修改的缓冲块。这是内存中的块争用。","cause":"集中在热块上、单调递增键的索引右侧边缘出现争用、freelist 不足/非 ASSM 段，或在同一块上有大量并发 DML。","fix":"找出受影响的段和块类别（v$session、v$segment_statistics、ASH）。对于热点索引块，考虑反向键索引或哈希分区索引。使用 ASSM（Automatic Segment Space Management）。在高并发下增大 INITRANS/PCTFREE。分散应用的访问模式。","tags":["wait","wait event","buffer busy","hot block","hot blocks","contention","block contention"]},{"code":"gc buffer busy","title":"等待（RAC）：争用正在实例间传输的 cache fusion 块","severity":"caution","meaning":"在 RAC 环境中，会话正在等待访问一个全局块，该块正通过 cache fusion 与另一实例相互传输，或被该实例占用。","cause":"RAC 节点之间共享相同的热块（跨实例热块）、interconnect 延迟/瓶颈、顺序键索引上的跨节点争用，或工作负载分布不均衡。","fix":"通过 v$segment_statistics 和 GV$ 视图找出受影响的段。检查 interconnect 的健康状况和延迟（私有、高速网络）。对热点索引使用反向键或哈希分区。按服务将工作负载分离到不同节点，减少对相同块的跨节点争用。","tags":["wait","wait event","rac","gc buffer busy","cache fusion","interconnect","global"]},{"code":"library cache lock","title":"等待：library cache 对象句柄争用（解析/DDL 串行化）","severity":"caution","meaning":"会话正在等待 library cache 中某个对象（SQL、PL/SQL、表定义）上的锁。通常由解析和 DDL 操作的串行化引起。","cause":"对高频使用的对象执行 DDL（ALTER/CREATE）、PL/SQL 对象频繁重新编译（失效）、硬解析过多、依赖对象失效，或大量并发连接风暴。","fix":"找出阻塞会话和对象（v$session、ASH，必要时使用 library cache lock 诊断）。避免在高峰时段对高频使用的对象执行 DDL。缩短依赖链并防止不必要的重新编译。通过使用绑定变量降低硬解析。用连接池平滑连接风暴。","tags":["wait","wait event","library cache lock","parse","parsing","ddl","hard parse"]}]},"errors/postgres.json":{"engineId":"postgres","engine":"PostgreSQL","order":2,"errors":[{"code":"23505","title":"unique_violation","severity":"caution","meaning":"插入或更新的行违反了 UNIQUE 约束或 PRIMARY KEY；表中已存在相同的值。","cause":"以相同的唯一键再次插入、向唯一列写入重复数据、手动插入导致序列值落后，或两个并发请求插入了相同的键。","fix":"错误详情中的约束名和键值会显示是哪一行冲突。要更新已有行，使用 INSERT ... ON CONFLICT (my_column) DO UPDATE 或 DO NOTHING。查找重复项：SELECT my_column, COUNT(*) FROM my_table GROUP BY my_column HAVING COUNT(*) > 1。如果序列值落后：SELECT setval('seq_name', (SELECT MAX(id) FROM my_table))。","tags":["unique","unique","constraint","constraint","duplicate","duplicate","23505","primary key"]},{"code":"23503","title":"foreign_key_violation","severity":"caution","meaning":"违反了 FOREIGN KEY 约束。插入/更新的行所引用的父行不存在，或要删除的父行仍有依赖的子行。","cause":"INSERT/UPDATE 引用了不存在的父键、尝试删除/更新仍被引用的父行，或数据加载顺序错误（先子后父）。","fix":"先插入父行，再插入子行。如果删除时需要级联行为，使用 ON DELETE CASCADE 或 ON DELETE SET NULL 定义约束。批量加载时按父-子顺序加载数据。查找引用行：SELECT * FROM child WHERE fk_column = value。","tags":["foreign key","foreign key","fk","constraint","constraint","reference","23503"]},{"code":"23502","title":"not_null_violation","severity":"caution","meaning":"尝试向带有 NOT NULL 约束的列插入 NULL，或将该列设置为 NULL。","cause":"INSERT 中遗漏了必填列、显式赋值 NULL、该列没有 DEFAULT，或应用传来空值/缺失值。","fix":"为该列提供有效值，或为其定义合理的 DEFAULT：ALTER TABLE my_table ALTER COLUMN my_column SET DEFAULT value。如果该列实际上不需要必填，删除该约束：ALTER TABLE my_table ALTER COLUMN my_column DROP NOT NULL。在应用层增加缺失数据校验。","tags":["not null","null","constraint","constraint","required","23502","insert"]},{"code":"40P01","title":"deadlock_detected","severity":"danger","meaning":"两个或多个事务各自等待对方持有的锁而陷入死锁。PostgreSQL 检测到该循环并取消其中一个事务。","cause":"以不同顺序获取锁、交叉更新相同的行、长事务，或外键检查期间获取了相互冲突的锁。","fix":"服务器日志中的死锁详情会显示冲突的查询。在所有事务中以相同顺序访问表/行。保持事务简短。在应用中捕获被取消的事务并重试。关联 pg_locks 与 pg_stat_activity 检查锁。必要时用 SELECT ... FOR UPDATE 显式确定加锁顺序。","tags":["deadlock","deadlock","lock","lock","40P01","tx","transaction"]},{"code":"40001","title":"serialization_failure","severity":"caution","meaning":"在 SERIALIZABLE 或 REPEATABLE READ 隔离级别下，事务因与并发修改冲突而无法串行化，已被取消。这是保护数据一致性的预期行为。","cause":"并发访问相同数据的事务无法排列成可串行化的顺序、高并发下的写冲突，或 SSI（Serializable Snapshot Isolation）检测到危险依赖。","fix":"这不是 bug，而是预期的信号：原样重试该事务。在应用中添加自动重试逻辑。为减少冲突，缩短事务；如果并非真正需要，使用较低的隔离级别（READ COMMITTED）。在设计上减少对热点行的争用。","tags":["serialization","serialization","isolation","isolation","retry","retry","40001","mvcc"]},{"code":"53300","title":"too_many_connections","severity":"danger","meaning":"服务器已达到 max_connections 允许的最大并发连接数；无法接受新连接。","cause":"没有连接池或连接池大小不当、连接泄漏、应用打开大量短生命周期连接，或 max_connections 设置低于工作负载需求。","fix":"识别连接来源：SELECT usename, state, COUNT(*) FROM pg_stat_activity GROUP BY 1,2。有针对性地终止长时间空闲的客户端连接（不加过滤的批量终止会影响所有应用和连接池）：SELECT pg_terminate_backend(pid) FROM pg_stat_activity WHERE state = 'idle' AND backend_type = 'client backend' AND usename = 'app_user' AND state_change < now() - interval '10 minutes' AND pid <> pg_backend_pid()。真正的解决办法是使用 PgBouncer 之类的连接池。增大 max_connections 会消耗内存；优先使用连接池。为超级用户保留的连接可确保管理访问始终可用。","tags":["connection","connection","max_connections","too many","53300","pool","pool","pgbouncer"]},{"code":"53100","title":"disk_full","severity":"danger","meaning":"由于磁盘已满，服务器无法写入文件。存放数据目录、WAL 或临时文件的磁盘上已没有剩余空间。","cause":"数据增长、WAL 失控堆积（归档或复制槽落后）、大型临时文件（巨大的排序）、日志文件膨胀，或表/索引膨胀（bloat）。","fix":"检查磁盘使用情况并清理不必要的文件。检查 WAL 堆积：pg_stat_replication 和 pg_replication_slots（删除不用的槽；落后的槽会保留 WAL）。磁盘已满时不要运行 VACUUM FULL 或 REINDEX：它们会重写表，需要与表大小相当的额外空间，可能让情况更糟；普通 VACUUM 也（几乎）不会把空间归还给操作系统。绝对不要手动删除 pg_wal 目录中的文件。先通过处理 WAL/槽、日志和临时文件来源释放空间；检查日志和临时文件的存放位置。作为永久解决办法，增加磁盘容量并建立监控/告警。","tags":["disk","space","space","full","full","wal","53100","vacuum"]},{"code":"57014","title":"query_canceled","severity":"safe","meaning":"查询已被取消。通常由 statement_timeout 到期，或用户/客户端发出的取消（pg_cancel_backend、驱动取消）引起。","cause":"超出 statement_timeout、通过 pg_cancel_backend 取消，或应用/客户端发送了取消请求。类似情况有不同的代码：lock_timeout 到期返回 55P03，idle_in_transaction_session_timeout 返回 25P03（FATAL），hot standby 上因复制冲突引起的取消返回 40001（某些情况下为 40P01）。","fix":"如果查询确实耗时较长，对其调优（EXPLAIN ANALYZE、索引、执行计划）。如果超时设置过低，为相关会话/角色调整 statement_timeout。主动取消无需处理。由锁和备库冲突引起的取消，请参阅 55P03 和 40001 条目。","tags":["cancel","cancel","timeout","timeout","statement_timeout","57014","standby"]},{"code":"42P01","title":"undefined_table","severity":"caution","meaning":"查询中引用的表（或视图）不存在，或在 search_path 中找不到。","cause":"表确实不存在、表位于不在 search_path 中的其他 schema、表名拼写/大小写错误、与带引号标识符的大小写不匹配，或对象尚未创建或已被删除。","fix":"确认表存在及其所属 schema：SELECT schemaname, tablename FROM pg_tables WHERE tablename = 'my_table'。如果位于其他 schema，用 schema.table 限定，或设置 search_path：SET search_path TO app_schema, public。注意不带引号的标识符会被转换为小写；如果创建时使用了引号，引用时也要以相同方式书写。","tags":["table","table","undefined","42P01","search_path","schema","schema"]},{"code":"42703","title":"undefined_column","severity":"caution","meaning":"查询中引用的列在表中或当前作用域中不存在。","cause":"列名拼写错误、列已被删除/重命名、引用了错误的表、带引号标识符的大小写不匹配，或多表查询中的限定不明确/不正确。","fix":"列出列：\\d my_table 或 SELECT column_name FROM information_schema.columns WHERE table_name = 'my_table'。更正列名和表，并在 JOIN 中以 table.column 限定列。如果创建时使用了引号，引用时使用相同的大小写。","tags":["column","column","undefined","42703","typo","typo","schema"]},{"code":"42501","title":"insufficient_privilege","severity":"caution","meaning":"该角色不具备所尝试操作需要的权限。","cause":"未授予表/schema/数据库上所需的 GRANT、缺少 schema 上的 USAGE、对象属于其他角色，或行级安全（RLS）策略阻止了访问。","fix":"授予缺失的权限：GRANT SELECT, INSERT ON my_table TO app_role；对 schema 授予 GRANT USAGE ON SCHEMA app_schema TO app_role。批量授权使用 GRANT ... ON ALL TABLES IN SCHEMA，对以后创建的对象使用 ALTER DEFAULT PRIVILEGES。查看当前权限：\\dp my_table。如果启用了 RLS，检查相关策略。","tags":["privilege","privilege","permission","grant","42501","permission","rls"]},{"code":"28P01","title":"invalid_password","severity":"caution","meaning":"认证时提供的密码错误；该角色的登录被拒绝。","cause":"密码错误、密码已被更改、应用连接字符串中的凭据已过时，或与 pg_hba.conf 中要求的认证方法（scram-sha-256、md5）不匹配。","fix":"核实密码，必要时重置：ALTER ROLE app_role WITH PASSWORD 'new_password'。确保 pg_hba.conf 中的方法与客户端兼容（旧客户端使用 scram-sha-256 可能有问题）。修改后用 SELECT pg_reload_conf() 重新加载配置。","tags":["auth","identity","password","password","28P01","scram","md5","hba"]},{"code":"08006","title":"connection_failure","severity":"danger","meaning":"客户端与服务器之间的连接无法建立或意外断开。","cause":"服务器未运行或正在重启、网络/防火墙问题、主机/端口错误、listen_addresses 设置阻止了访问、空闲连接被网络断开，或服务器过载。","fix":"检查服务器是否启动并在正确的主机/端口上监听（pg_isready host port）。核实 postgresql.conf 中的 listen_addresses 和 pg_hba.conf 中的访问规则。检查网络/防火墙和 DNS 设置。检查连接池中的 TCP keepalive 和超时设置。在服务器日志中查找崩溃/重启的痕迹。","tags":["connection","connection","network","network","08006","failure","disconnect"]},{"code":"55P03","title":"lock_not_available","severity":"caution","meaning":"无法立即获取所请求的锁。通常发生在他人持有锁时以 NOWAIT 请求该锁，或 lock_timeout 到期时。","cause":"使用 SELECT ... FOR UPDATE NOWAIT 或 LOCK TABLE ... NOWAIT 时行/表已被锁定，或 lock_timeout 到期。大多数情况下是某个长事务持有该锁。","fix":"找出阻塞会话：关联 pg_locks 和 pg_stat_activity（等待/阻塞关系）。结束长事务，必要时用 pg_terminate_backend(pid) 终止它。不使用 NOWAIT，而是以合理的 lock_timeout 等待并添加重试逻辑。缩短事务以减少持锁时间。","tags":["lock","lock","nowait","lock_timeout","55P03","not available","busy"]},{"code":"54000","title":"program_limit_exceeded","severity":"caution","meaning":"超出了 PostgreSQL 的实现/格式限制；例如行或字段的最大大小、索引行大小，或查询中参数/列数量的限制。","cause":"尝试存储非常大的值（如超过 1 GB 的字段）、被索引的值超出 B-tree 页面限制（约 2704 字节），或使用了过多的列或查询参数。","fix":"确定超出的是哪项限制（消息通常会说明）。对非常大的文本/数据重新审视或拆分结构。对长文本列使用哈希索引或表达式/部分索引代替 B-tree。减少查询中的参数/列数量或拆分批次。","tags":["limit","limit","program_limit","54000","index size","size","row size"]},{"code":"22001","title":"string_data_right_truncation","severity":"caution","meaning":"文本长度超过目标列定义的长度（如 VARCHAR(n)），值将被截断；PostgreSQL 拒绝该值并报错。","cause":"向 VARCHAR(n)/CHAR(n) 列插入或更新长度超过 n 的文本、应用传来超出预期长度的数据，或列长度定义过窄。","fix":"缩短值以适应该列，或增大列长度：ALTER TABLE my_table ALTER COLUMN my_column TYPE VARCHAR(500)。如果需要不限长度的文本，使用 TEXT 类型。在应用中校验长度。如果需要有意截断，显式使用 LEFT(value, n)。","tags":["truncation","truncation","varchar","string","text","22001","length","length"]},{"code":"25P02","title":"in_failed_sql_transaction","severity":"caution","meaning":"事务中之前已经发生错误；该事务中不能再执行其他命令。事务处于中止状态，只接受 ROLLBACK。","cause":"事务块中的前一条语句失败（如违反约束），但仍在同一事务中继续发送新命令。应用未捕获错误，继续使用该事务。","fix":"回滚事务：ROLLBACK，然后重新开始。如果希望隔离某个块的错误并继续，使用 SAVEPOINT，并用 ROLLBACK TO SAVEPOINT 返回到该点。在应用中捕获每条语句的错误并正确管理事务（明确提交/回滚决策）。","tags":["transaction","transaction","aborted","aborted","25P02","rollback","savepoint"]},{"code":"53200","title":"out_of_memory","severity":"danger","meaning":"服务器无法分配操作所需的内存。某个后端进程或整个服务器处于内存压力之下。","cause":"work_mem/maintenance_work_mem 设置过高并在高并发下成倍放大、单个查询中哈希/排序操作过多、连接数过多、内存泄漏，或操作系统 OOM killer 造成的压力。","fix":"检查服务器日志中的 memory context 以及 OOM killer 的痕迹。综合考虑查询数和并发数合理设置 work_mem（每个排序/哈希都会单独分配）。用连接池限制连接数。检查操作系统的内存/overcommit 设置。优化重型查询以降低其内存需求。","tags":["memory","memory","out of memory","53200","work_mem","oom"]}]},"errors/mysql.json":{"engineId":"mysql","engine":"MySQL","order":3,"errors":[{"code":"1045","title":"Access denied for user (using password: YES/NO)（用户访问被拒绝（使用密码：YES/NO））","severity":"caution","meaning":"针对用户名、密码或连接来源主机的认证失败；登录被拒绝。","cause":"密码错误、用户在其连接来源主机上没有权限（user@host）、账户不存在、认证插件不匹配（caching_sha2_password 与 mysql_native_password），或连接字符串中的凭据已过时。","fix":"检查用户与主机是否匹配：SELECT user, host, plugin FROM mysql.user。为正确的主机授权（如 'app_user'@'%'）。重置密码：ALTER USER 'app_user'@'host' IDENTIFIED BY 'new_password';（ALTER USER/GRANT 立即生效；只有在用 DML 直接修改授权表时才需要 FLUSH PRIVILEGES）。如果看到 'using password: NO'，说明客户端根本没有发送密码。对于插件不匹配，检查认证插件：MySQL 8.4 默认禁用 mysql_native_password 插件（仅在 --mysql-native-password=ON 时加载）；使用该插件的旧账户和旧客户端无法连接。用 ALTER USER ... IDENTIFIED WITH caching_sha2_password BY '...'; 迁移账户，并升级客户端驱动。","tags":["auth","authentication","access denied","access","password","password","1045","privilege"]},{"code":"1062","title":"Duplicate entry for key（键的重复条目）","severity":"caution","meaning":"插入或更新的行违反了 UNIQUE 索引或 PRIMARY KEY；相同的键值已经存在。","cause":"以相同唯一键重复 INSERT、录入重复数据、AUTO_INCREMENT 计数器落后，或为已有表添加唯一索引时表中已存在重复行。","fix":"错误消息会显示冲突的键值和索引名。要更新已有行，考虑使用 INSERT ... ON DUPLICATE KEY UPDATE 或 INSERT IGNORE / REPLACE。查找重复项：SELECT my_column, COUNT(*) FROM my_table GROUP BY my_column HAVING COUNT(*) > 1。如果无法添加唯一索引，先清理重复数据。","tags":["duplicate","duplicate entry","unique","unique key","1062","primary key","insert"]},{"code":"1213","title":"Deadlock found when trying to get lock; try restarting transaction（尝试获取锁时发现死锁；请尝试重新开始事务）","severity":"danger","meaning":"InnoDB 检测到两个或多个事务互相等待对方锁的死锁，并通过回滚其中一个事务打破了循环。","cause":"以不同顺序获取行锁、交叉更新相同的行、缺少索引导致锁定大范围的行（gap lock），或外键检查期间的冲突。","fix":"查看最近的死锁详情：SHOW ENGINE INNODB STATUS（LATEST DETECTED DEADLOCK 部分）。在所有事务中以相同顺序访问行/表，并保持事务简短。通过添加合适的索引缩小锁定范围。在应用中捕获被回滚的事务并重试（重试逻辑）。","tags":["deadlock","deadlock detected","lock","row lock","innodb","1213","tx","transaction"]},{"code":"1205","title":"Lock wait timeout exceeded; try restarting transaction（超出锁等待超时；请尝试重新开始事务）","severity":"danger","meaning":"事务等待所需的行锁达 innodb_lock_wait_timeout 秒仍未获取，已超时。","cause":"另一事务长时间持有锁而未提交或回滚；空闲的未结束事务、长时间运行的批量更新，或争用激烈的热点行。","fix":"找出阻塞事务：关联 performance_schema.data_lock_waits 与 data_locks（旧版本中为 information_schema.innodb_lock_waits/innodb_trx）。提交/回滚阻塞事务，必要时用 KILL <trx_mysql_thread_id> 终止它。保持事务简短，不要让事务处于空闲状态（autocommit/应用逻辑）。必要时根据工作负载调整 innodb_lock_wait_timeout。","tags":["lock","lock wait","timeout","timed out","innodb","1205","wait","waiting"]},{"code":"1040","title":"Too many connections（连接过多）","severity":"danger","meaning":"服务器已达到 max_connections 允许的最大并发连接数；新连接被拒绝。","cause":"连接池大小不当、连接泄漏、大量短生命周期连接、长时间处于休眠（Sleep）的连接，或 max_connections 值相对工作负载过低。","fix":"检查连接：SHOW PROCESSLIST 或 SELECT * FROM performance_schema.processlist。用 KILL 关闭不必要的休眠连接。增大 max_connections：SET GLOBAL max_connections = 500（写入 my.cnf 以持久化）。根本解决办法是使用连接池，并用 wait_timeout 关闭空闲连接。系统会为 SUPER/CONNECTION_ADMIN 额外保留一个紧急管理连接。","tags":["connection","connections","too many","max_connections","1040","pool","connection pool"]},{"code":"1064","title":"You have an error in your SQL syntax（SQL 语法错误）","severity":"safe","meaning":"SQL 语句存在语法错误；服务器无法解析。消息会显示错误附近的文本。","cause":"拼写错误、缺少/多余的逗号或括号、将保留字不加引号用作列名/表名、使用了当前 MySQL 版本不支持的语法，或引号/转义错误。","fix":"消息中的 'near ...' 部分显示错误开始的位置；检查其紧前面的内容。用反引号引用保留字：`order`、`group`。使括号和逗号配对。确认使用的是当前版本支持的语法。如果生成动态 SQL，记录并检查生成的完整查询。","tags":["syntax","syntax error","1064","parse","parsing","reserved word","reserved keyword"]},{"code":"1146","title":"Table doesn't exist（表不存在）","severity":"caution","meaning":"查询中引用的表不存在，或在当前数据库中找不到。","cause":"表确实不存在、选择了错误的数据库、表名拼写错误、Linux 上表名区分大小写（lower_case_table_names），或 InnoDB 表文件（.ibd）丢失/损坏（5.7 及更早版本还包括 .frm 定义文件；8.0 中定义保存在数据字典中）。","fix":"核实表：SHOW TABLES; 并选择正确的数据库：USE mydb。注意大小写敏感（尤其在 Linux 上）。通过 SELECT ... FROM information_schema.tables 检查表名和 schema。如果存在 InnoDB 文件问题，可能需要恢复/修复。","tags":["table","table missing","doesn't exist","not found","1146","schema","database"]},{"code":"1054","title":"Unknown column in field list（字段列表中存在未知列）","severity":"caution","meaning":"查询中引用的列在表中或当前作用域中不存在。","cause":"列名拼写错误、列已被删除/重命名、引用了错误的表、JOIN 中限定不明确/错误，或字符串值未加引号而被当作列（写成 WHERE name = value 而不是 WHERE name = 'value'）。","fix":"列出列：SHOW COLUMNS FROM my_table 或 DESCRIBE my_table。更正列名，并在 JOIN 中以 my_table.my_column 限定。字符串字面量用单引号括起来；反引号仅用于标识符。可以通过 information_schema.columns 检查。","tags":["column","column name","unknown","unknown column","1054","typo","misspelling"]},{"code":"2002","title":"Can't connect to local MySQL server through socket（无法通过 socket 连接到本地 MySQL 服务器）","severity":"danger","meaning":"客户端无法通过 Unix socket 连接到本地 MySQL 服务器。通常是服务器未运行或 socket 路径错误。","cause":"MySQL/MariaDB 服务未运行或已崩溃、客户端与服务器的 socket 文件路径（如 /var/run/mysqld/mysqld.sock）不一致、权限问题，或服务器尚未启动。","fix":"检查服务状态并启动（systemctl status mysql / start mysql）。核实正确的 socket 路径：SHOW VARIABLES LIKE 'socket' 以及 my.cnf 中的 socket 设置。如果路径不同，用 --socket 显式指定或使用 TCP：mysql -h 127.0.0.1 -P 3306。在服务器日志中检查启动/崩溃错误。","tags":["connection","connect","socket","unix socket","2002","server","local","localhost"]},{"code":"2003","title":"Can't connect to MySQL server (TCP/IP)（无法连接到 MySQL 服务器（TCP/IP））","severity":"danger","meaning":"客户端无法通过 TCP/IP 在指定主机和端口上连接到 MySQL 服务器。","cause":"服务器未运行、主机/端口错误、防火墙阻止了 3306 端口、服务器仅绑定到 localhost（bind-address = 127.0.0.1），或启用了 skip-networking。","fix":"检查服务器是否启动并在正确的端口上监听。如需远程访问，在 my.cnf 中设置 bind-address = 0.0.0.0（或相应 IP），并确保 skip-networking 已关闭。在防火墙中开放 3306 端口。核实主机/端口：mysql -h host -P 3306 -u app_user -p。确保用户拥有从远程主机访问的权限。","tags":["connection","connect","tcp","port","2003","network","firewall","bind-address"]},{"code":"2006","title":"MySQL server has gone away（MySQL 服务器已断开）","severity":"caution","meaning":"客户端与服务器之间的连接在执行命令期间丢失。服务器可能关闭了连接或拒绝了数据包。","cause":"空闲连接因 wait_timeout/interactive_timeout 到期而被关闭、查询/数据大于 max_allowed_packet、服务器重启或崩溃，或网络中断。","fix":"对于空闲连接问题，在连接池中使用连接校验并检查 wait_timeout。如果发送大量数据，增大 max_allowed_packet（如 SET GLOBAL max_allowed_packet = 64*1024*1024，写入 my.cnf 以持久化）。在服务器日志中检查重启/崩溃和 OOM 痕迹。在应用中为断开的连接添加重连逻辑。","tags":["connection","connection lost","gone away","disconnect","2006","timeout","max_allowed_packet"]},{"code":"1114","title":"The table is full（表已满）","severity":"danger","meaning":"由于达到存储限制或可用空间耗尽，无法再向表中写入数据。","cause":"磁盘已满、MEMORY 表达到 max_heap_table_size 限制、临时表超出 tmp_table_size、由 innodb_data_file_path 定义的固定大小表空间被占满，或文件系统/表特定的大小限制。","fix":"首先检查并释放磁盘空间。对于 MEMORY/临时表，增大 max_heap_table_size 和 tmp_table_size。对于 InnoDB，确保 innodb_data_file_path 为 autoextend 或增加空间。优化会将大量临时结果溢出到磁盘的查询。作为永久解决办法，增加存储容量。","tags":["table full","table is full","disk","space","1114","space","memory","innodb"]},{"code":"1215","title":"Cannot add foreign key constraint（无法添加外键约束）","severity":"caution","meaning":"无法创建所定义的外键约束；MySQL 无法添加该约束。","cause":"子列与被引用列的数据类型/长度/符号（signed-unsigned）或字符集/排序规则不匹配、被引用列上没有合适的索引/PRIMARY KEY、表使用不同的存储引擎（其中一个是 MyISAM），或被引用的表尚不存在。","fix":"确保两侧的列类型完全相同（如都是 INT UNSIGNED）。确认被引用列有索引支撑（通常是 PRIMARY KEY 或 UNIQUE）。确保两张表都是 InnoDB。字符串键使用相同的 charset/collation。查看详情：SHOW ENGINE INNODB STATUS（LATEST FOREIGN KEY ERROR）。","tags":["foreign key","foreign key constraint","fk","1215","constraint","constraint error","innodb","type"]},{"code":"1451","title":"Cannot delete or update a parent row: a foreign key constraint fails（无法删除或更新父行：外键约束失败）","severity":"caution","meaning":"你尝试删除父行或修改其键，但由于存在依赖的子行，外键约束不允许该操作。","cause":"被引用的父行仍有子行，且约束的 ON DELETE/UPDATE 使用 RESTRICT/NO ACTION。","fix":"先删除依赖的子行或将其移到另一个父行下，然后再删除父行。如果需要自动处理，用 ON DELETE CASCADE 或 ON DELETE SET NULL 重新定义约束。查找依赖行：SELECT * FROM child_table WHERE fk_column = value。","tags":["foreign key","foreign key constraint","fk","1451","parent","parent row","delete","reference"]},{"code":"1452","title":"Cannot add or update a child row: a foreign key constraint fails（无法添加或更新子行：外键约束失败）","severity":"caution","meaning":"你正在插入或更新子行，但其指向的父键值在被引用表中不存在；外键约束拒绝了该操作。","cause":"INSERT/UPDATE 引用了不存在的父键、数据加载顺序错误（先加载子行），或父行已被删除。","fix":"先插入父行，再插入子行。确认被引用的值存在：SELECT * FROM parent_table WHERE key_column = value。批量加载时保持父-子顺序。仅在受控的维护操作中使用 SET FOREIGN_KEY_CHECKS=0，完成后将其恢复为 1。","tags":["foreign key","foreign key constraint","fk","1452","child","child row","reference","insert"]},{"code":"1366","title":"Incorrect string/integer value for column（列的字符串/整数值不正确）","severity":"caution","meaning":"向列中插入了与其类型或字符集不兼容的值。通常由字符编码或类型不匹配引起。","cause":"向 latin1 列写入 4 字节的 emoji/字符（utf8mb4）、向数值列写入非数字文本、连接字符集错误，或发送的值类型与预期不同。","fix":"对于字符问题，将列/表转换为 utf8mb4：ALTER TABLE my_table CONVERT TO CHARACTER SET utf8mb4 COLLATE utf8mb4_unicode_ci，并将连接字符集设为 utf8mb4。对数值字段在应用中校验类型。注意服务器运行在严格 SQL 模式（STRICT）下；根本原因在于数据的类型/编码。","tags":["charset","character","incorrect value","1366","utf8mb4","encoding","type","collation"]},{"code":"1044","title":"Access denied for user to database（用户对数据库的访问被拒绝）","severity":"caution","meaning":"用户虽然认证成功，但没有在指定数据库上操作的权限。","cause":"用户在该数据库上没有 GRANT、数据库名错误、权限是为其他主机（user@host）定义的，或数据库不存在。","fix":"查看用户权限：SHOW GRANTS FOR 'app_user'@'host'。授予所需权限：GRANT ALL PRIVILEGES ON mydb.* TO 'app_user'@'host';（GRANT 立即生效，不需要 FLUSH PRIVILEGES）。核实主机匹配和数据库名是否正确。遵循最小权限原则，只授予所需的权限。","tags":["access denied","access","database","database access","1044","grant","privilege","permission"]},{"code":"1264","title":"Out of range value for column（列的值超出范围）","severity":"caution","meaning":"向数值或日期列插入了超出类型允许范围的值。该错误在严格 SQL 模式下引发。","cause":"超出 TINYINT/INT 等类型的上限（如向 TINYINT 写入 200，或向 UNSIGNED 列写入负值）、日期/数字溢出，或发送的值大于预期。","fix":"根据需要加宽列类型（如用 INT 代替 TINYINT，用 BIGINT 代替 INT）或修正符号（SIGNED/UNSIGNED）。在应用中校验取值范围。即使希望静默截断（不推荐），也应使数据与类型兼容，而不是从 sql_mode 中移除 STRICT。","tags":["out of range","value out of range","1264","value","int","tinyint","unsigned","overflow"]}]},"errors/mssql.json":{"engineId":"mssql","engine":"SQL Server","order":4,"errors":[{"code":"1205","title":"Transaction (Process ID) was deadlocked on lock resources with another process and has been chosen as the deadlock victim（事务（进程 ID）与另一个进程在锁资源上发生死锁，已被选为死锁牺牲者）","severity":"danger","meaning":"两个或多个事务各自等待对方持有的锁（死锁）。为打破循环，SQL Server 选择一个事务作为牺牲者并将其回滚。","cause":"事务以不同顺序锁定表/行、长时间运行的事务、缺少索引导致的大范围扫描、锁升级以及高并发是典型原因。","fix":"在应用中捕获错误 1205 并自动重试事务。在每个事务中以相同顺序访问对象，并保持事务简短。检查死锁图：使用 Extended Events（system_health 会话）或跟踪标志 1222 获取详情。添加合适的索引以减少扫描和加锁。必要时评估 READ COMMITTED SNAPSHOT（RCSI）隔离级别。","tags":["deadlock","lock","locking","victim","rollback","1205","retry"]},{"code":"2627","title":"Violation of PRIMARY KEY / UNIQUE KEY constraint. Cannot insert duplicate key in object（违反 PRIMARY KEY / UNIQUE KEY 约束。不能在对象中插入重复键）","severity":"caution","meaning":"你尝试插入或更新的值在受 PRIMARY KEY 或 UNIQUE 约束保护的列中已经存在。不接受重复键。","cause":"第二次插入相同的唯一值、UPDATE 与已有值冲突、缺少唯一性检查，或两个并发请求生成了相同的行。","fix":"插入前检查行是否已存在，或使用 MERGE / IF NOT EXISTS。对于幂等插入，采用 \"INSERT ... WHERE NOT EXISTS\" 模式。捕获错误并返回有意义的消息。如果确实需要重复数据，重新审视该约束。生成键时使用 IDENTITY 或 SEQUENCE 以避免冲突。","tags":["unique","primary key","uniqueness","duplicate","constraint","constraint violation","2627"]},{"code":"2601","title":"Cannot insert duplicate key row in object with unique index. The duplicate key value is ...（不能在具有唯一索引的对象中插入重复键行。重复键值为 ...）","severity":"caution","meaning":"UNIQUE INDEX 因重复键值拒绝了一行。约束引发 2627，唯一索引引发 2601。","cause":"向受 UNIQUE INDEX 保护的列再次写入已有值、并发插入，或筛选唯一索引覆盖的行之间发生冲突。","fix":"删除或更新重复值。如果必须容忍重复，考虑 IGNORE_DUP_KEY = ON 选项（重复行会被静默跳过）。插入前添加 NOT EXISTS 检查。如果存在筛选索引，核实其 WHERE 子句和冲突情况。","tags":["unique index","duplicate key","unique idx","duplicate","2601","index"]},{"code":"547","title":"The INSERT/UPDATE/DELETE statement conflicted with the FOREIGN KEY / CHECK constraint（INSERT/UPDATE/DELETE 语句与 FOREIGN KEY / CHECK 约束冲突）","severity":"caution","meaning":"该语句违反了 FOREIGN KEY 或 CHECK 约束：要么引用了不存在的父行，要么写入了违反 CHECK 规则的值，要么尝试删除仍被引用的父行。","cause":"FK：插入的子行所引用的父行不存在。DELETE/UPDATE：有子行依赖于要删除/修改的父行。CHECK：列值不满足定义的条件。","fix":"先插入父行，再插入子行。删除时先删除依赖行，或在合适的情况下定义 ON DELETE CASCADE。对于 CHECK 违规，使值符合规则。根据错误文本中的约束名确定被违反的约束。","tags":["foreign key","fk","check","constraint","constraint violation","referential","547"]},{"code":"18456","title":"Login failed for user（用户登录失败）","severity":"danger","meaning":"登录尝试在认证阶段被拒绝。可能是用户名/密码错误、账户被禁用，或用户无权访问该数据库。","cause":"密码错误、登录名不存在、账户被禁用/锁定、在仅启用 Windows 身份验证时使用 SQL 登录，或无权访问默认数据库。错误文本中的 State 代码指出真正原因（State 5：登录名不存在；State 7：登录名已禁用且密码也错误；State 8：密码错误（最常见）；State 18：必须更改密码；State 38：无法打开指定的数据库；State 58：服务器处于仅 Windows 身份验证模式时尝试 SQL 登录）。","fix":"根据 SQL Server 错误日志中的 State 值找出根本原因（出于安全原因，详细信息不会发送给客户端）。核实用户名/密码。检查服务器是否运行在 'SQL Server and Windows Authentication Mode'（混合模式）；如果做了更改，需重启服务。核实该登录名在目标数据库中的用户映射和访问权限。","tags":["login","session","authentication","authentication failed","password","18456","state"]},{"code":"8152","title":"String or binary data would be truncated（字符串或二进制数据将被截断）","severity":"caution","meaning":"你尝试写入长度超过列定义长度的数据。SQL Server 为防止数据丢失而终止该语句（2628 是同一错误的新版本，会报告列/值）。","cause":"例如向 VARCHAR(10) 列写入 15 个字符、目标列比源列窄、数据类型/长度不匹配，或 ETL/传输中字段长度不足。","fix":"加宽列以容纳数据（ALTER TABLE ... ALTER COLUMN）或缩短数据。在 SQL Server 2019+ 上使用错误 2628，它会显示溢出的表/列/值；在旧版本上，用 LEN() 逐列检查以找出有问题的行。在传输中对齐源与目标的 schema 长度。","tags":["truncation","truncate","overflow","varchar","length","column length","8152","2628"]},{"code":"2628","title":"String or binary data would be truncated in table, column ...; truncated value（表、列 ... 中的字符串或二进制数据将被截断；截断的值）","severity":"caution","meaning":"8152 的增强版本（SQL Server 2019 及更高版本）。它会明确报告哪个表、哪个列以及哪个值将被截断。","cause":"写入的文本/二进制值长度超过目标列的定义长度。通常是 schema 不匹配或输入数据长度超出预期。","fix":"查看错误文本中的表名和列名；加宽列或缩短源值。在 SQL Server 2019 中，2628 仅在兼容级别 150+ 时默认引发；在较低级别下用 ALTER DATABASE SCOPED CONFIGURATION SET VERBOSE_TRUNCATION_WARNINGS = ON 或跟踪标志 460 启用。从 SQL Server 2016 SP2 CU6 和 2017 CU12 起，跟踪标志 460 可提供相同的详细消息。在传输层使字段长度与目标一致。","tags":["truncation","truncate","overflow","column","column name","length","2628","460"]},{"code":"1222","title":"Lock request time out period exceeded（已超过锁请求超时时段）","severity":"caution","meaning":"查询在等待获取锁时超时。由于另一事务长时间锁定所需资源，该请求被取消。","cause":"超出 SET LOCK_TIMEOUT 设置的等待时间、长时间运行的阻塞事务、未提交的打开事务、长扫描和锁冲突。","fix":"找出阻塞者：检查 sys.dm_exec_requests、sys.dm_tran_locks 和 blocking_session_id 列，或使用 sp_who2 / sp_whoisactive。缩短长事务并提交。必要时调整 LOCK_TIMEOUT。在读密集型系统上，用 RCSI/快照隔离减少读写冲突。","tags":["lock timeout","lock wait timeout","blocking","blocked session","1222","lock_timeout"]},{"code":"9002","title":"The transaction log for database is full due to ...（数据库的事务日志已满，原因为 ...）","severity":"danger","meaning":"数据库的事务日志文件已满，新事务无法记录日志。数据库拒绝写入，表现得如同只读。","cause":"FULL 恢复模式下没有日志备份、长时间打开的事务、复制/AlwaysOn 延迟，或日志无法增长（禁用了自动增长或磁盘已满）。log_reuse_wait_desc 会显示原因。","fix":"检查 sys.databases 中的 log_reuse_wait_desc。如果在 FULL 模式下为 LOG_BACKUP，执行日志备份（BACKUP LOG）。如果为 ACTIVE_TRANSACTION，找到打开的事务并提交/回滚（DBCC OPENTRAN）。释放磁盘空间或修正日志文件的自动增长设置。如果日志确实已经增长，在备份后用 DBCC SHRINKFILE 收缩。如果不需要日志链，重新审视恢复模式。","tags":["transaction log","log is full","log full","9002","backup log","recovery model"]},{"code":"1105","title":"Could not allocate space for object in database because the filegroup is full（由于文件组已满，无法在数据库中为对象分配空间）","severity":"danger","meaning":"存放数据文件的文件组已满，无法为新数据分配空间。插入/更新失败。","cause":"数据文件无法增长（禁用了自动增长或已达到 MAXSIZE）、没有可用磁盘空间、PRIMARY 文件组耗尽，或文件大小限制。","fix":"释放磁盘空间或添加新磁盘/文件。为数据文件启用自动增长或将其扩大（ALTER DATABASE ... MODIFY FILE）。向文件组添加新文件。归档并清除不需要的旧数据。对于 tempdb，检查 tempdb 文件及其所在磁盘。","tags":["filegroup full","file full","allocate space","disk","autogrow","1105"]},{"code":"208","title":"Invalid object name（对象名无效）","severity":"safe","meaning":"未找到查询中引用的表、视图或对象。SQL Server 无法解析指定的名称。","cause":"对象名拼写错误、数据库/schema 上下文错误（非 dbo 的 schema）、对象确实不存在或尚未创建，或缺少两部分名称（schema.table）。","fix":"正确书写对象名和 schema；使用限定名（如 dbo.Customers）。确保连接到了正确的数据库（USE）。检查对象是否存在：SELECT * FROM sys.objects WHERE name = 'YourObjectName'。确保临时表在同一会话中创建。","tags":["invalid object","invalid object name","table not found","table does not exist","schema","app schema","208"]},{"code":"229","title":"The SELECT/EXECUTE permission was denied on the object（拒绝了对对象的 SELECT/EXECUTE 权限）","severity":"caution","meaning":"用户无权对该对象执行所请求的操作（SELECT、INSERT、EXECUTE 等）。出于安全原因访问被拒绝。","cause":"未向用户/角色授予所需权限、schema 所有权错误（所有权链断开）、对象上存在显式 DENY，或用户不是具备足够权限的角色成员。","fix":"授予所需权限：GRANT SELECT ON dbo.MyTable TO UserName（或 GRANT EXECUTE ON dbo.MyProcedure TO ...）。将用户加入合适的角色（如 db_datareader）。删除任何显式 DENY。按照最小权限原则安排权限。用 sys.database_permissions 审计现有权限。","tags":["permission denied","access denied","grant","privilege","security","security permissions","229"]},{"code":"4060","title":"Cannot open database requested by the login. The login failed（无法打开登录所请求的数据库。登录失败）","severity":"danger","meaning":"认证可能成功，但由于无法打开所请求的数据库，连接被拒绝。通常与 18456 一起出现。","cause":"数据库不存在、处于 OFFLINE/SINGLE_USER/RESTORING/SUSPECT 状态、登录名在该数据库中没有用户映射，或连接字符串中的默认数据库不可访问。","fix":"确认数据库存在且处于 ONLINE：SELECT name, state_desc FROM sys.databases。在数据库中为该登录名创建用户并授予权限。检查连接字符串中的 Initial Catalog/Database 值。如果数据库处于 RESTORING/SUSPECT，按照修复/恢复步骤处理。","tags":["cannot open database","database cannot be opened","4060","login","state","offline"]},{"code":"233","title":"A connection was successfully established with the server, but then an error occurred (no process is on the other end of the pipe)（已成功与服务器建立连接，但随后发生错误（管道的另一端上无任何进程））","severity":"danger","meaning":"客户端已到达服务器，但连接在握手期间断开。通常由 Named Pipes/TCP 配置、协议设置或服务器端拒绝引起。与 10054（连接被强制关闭）相关。","cause":"Shared Memory/Named Pipes/TCP 协议被禁用、不允许远程连接、账户无法登录（参见 18456）、防火墙/网络中断，或服务器强制关闭了连接（10054）。","fix":"在 SQL Server Configuration Manager 中启用 TCP/IP，必要时启用 Named Pipes，然后重启服务。确认允许远程连接。在防火墙中开放 SQL 端口（默认 1433）。对于认证问题，检查错误日志中 18456 的 State 代码。检查网络稳定性和 TLS/加密设置。","tags":["connection","connection error","pipe","10054","tcp","named pipes","233","network"]},{"code":"823","title":"The operating system returned error to SQL Server during a read/write at offset in file (I/O error)（操作系统在文件的偏移量处读/写时向 SQL Server 返回了错误（I/O 错误））","severity":"danger","meaning":"SQL Server 在读写数据/日志文件时收到了操作系统级别的 I/O 错误。这是严重的完整性警告，指向磁盘硬件或存储子系统问题。","cause":"磁盘坏扇区、控制器/驱动故障、存储/SAN 问题、文件系统错误，或页面完全无法读取（硬 I/O 错误）。通常与硬件有关。","fix":"立即进行一次完好的备份（如果可能）。检查 Windows 事件日志和存储日志，测试硬件/磁盘健康状况。运行 DBCC CHECKDB 检查完整性。检查 suspect_pages 表（msdb.dbo.suspect_pages）。如果存在损坏，从完好的备份恢复或执行页级还原。更换/修复硬件。","tags":["io error","i/o error","disk","corruption","data corruption","823","checkdb"]},{"code":"824","title":"SQL Server detected a logical consistency-based I/O error (page corruption)（SQL Server 检测到基于逻辑一致性的 I/O 错误（页损坏））","severity":"danger","meaning":"SQL Server 读取了一个页，但其内容在逻辑上不一致（校验和错误、残缺页或页 ID 错误）。该数据页已损坏。","cause":"存储子系统错误、突然断电/残缺页、控制器或驱动问题、由 PAGE_VERIFY CHECKSUM 捕获的静默损坏。与 823 一样，大多与硬件/存储有关。","fix":"运行 DBCC CHECKDB 确定损坏范围。从最近一次完好的备份恢复，或用页级还原修复受影响的页。检查 msdb.dbo.suspect_pages。确保启用了 PAGE_VERIFY = CHECKSUM。检查硬件/存储。REPAIR_ALLOW_DATA_LOSS 必须作为最后手段，并且会导致数据丢失。","tags":["page corruption","corrupt page","consistency","824","checkdb","checksum","torn page"]},{"code":"701","title":"There is insufficient system memory in resource pool to run this query（资源池中的系统内存不足，无法运行此查询）","severity":"danger","meaning":"SQL Server 无法从相关资源池分配运行查询所需的内存。操作因内存压力而失败。","cause":"服务器内存不足、max server memory 设置过低、消耗大量内存的查询（大型排序/哈希）、内存泄漏、大量并发请求，或 Resource Governor 池限制。","fix":"检查 max server memory 设置并提供足够的物理 RAM。优化消耗大量内存的查询（合适的索引、更少的排序/哈希）。监控内存：sys.dm_os_memory_clerks、DBCC MEMORYSTATUS、Perfmon 内存计数器。调整 Resource Governor 池限制。必要时降低并发度。","tags":["memory","memory pressure","insufficient memory","resource pool","701","oom"]},{"code":"845","title":"Time-out occurred while waiting for buffer latch（等待缓冲区闩锁时超时）","severity":"danger","meaning":"等待数据页上的缓冲区闩锁（buffer latch）时发生超时。SQL Server 无法在合理时间内获取访问页面所需的短期内部锁（latch）；通常是 I/O 繁重或内存压力的迹象。","cause":"磁盘子系统缓慢/过载、内存压力导致页面不断从磁盘读取、硬件问题、过多的分页，或存储瓶颈。","fix":"测量磁盘 I/O 延迟（sys.dm_io_virtual_file_stats、Perfmon 磁盘延迟）。检查存储性能和硬件健康状况。检查内存是否充足以及 max server memory 设置。检查等待类型（sys.dm_os_wait_stats、PAGEIOLATCH 等待）。消除存储瓶颈；必要时提供更快的磁盘/更多的 RAM。","tags":["buffer latch","latch timeout","845","io","pageiolatch","memory","memory pressure"]},{"code":"3201","title":"Cannot open backup device. Operating system error (backup device error)（无法打开备份设备。操作系统错误（备份设备错误））","severity":"caution","meaning":"在备份或恢复期间无法打开目标备份设备/文件。SQL Server 无法访问指定路径。","cause":"文件夹路径错误/不存在、SQL Server 服务账户对目标文件夹没有写权限、无法访问网络共享（UNC）、磁盘已满，或文件被其他进程锁定。","fix":"确认备份路径存在且拼写正确。授予 SQL Server 服务账户对目标文件夹的写权限。对于 UNC 共享，检查服务账户的访问权限。确保有足够的可用磁盘空间。对于本地路径，确认该盘符在服务器上可见（而非客户端的驱动器）。","tags":["backup device","backup file","backup","restore","3201","permission denied","permission","os error"]}]},"errors/mongodb.json":{"engineId":"mongodb","engine":"MongoDB","order":5,"errors":[{"code":"E11000","title":"E11000 duplicate key error collection（E11000 集合中的重复键错误）","severity":"caution","meaning":"插入或更新尝试向受唯一索引保护的字段写入集合中已存在的值。MongoDB 拒绝该重复键。","cause":"_id 冲突、向具有唯一索引的字段第二次写入相同值、upsert 期间的竞态条件，或唯一索引（非 sparse 索引）中多个文档的 null 值发生冲突。","fix":"写入前检查文档是否存在，或使用 upsert：db.collection.updateOne(filter, {$set:{...}}, {upsert:true})。查看错误消息中的索引名和 dup key 值。如果实际上不需要唯一性，删除该索引；如果问题出在 null 值，使用 partial/sparse 索引。在应用中捕获错误并重试或报告给用户。","tags":["duplicate key","duplicate key","unique index","unique","e11000","upsert","_id"]},{"code":"11000","title":"DuplicateKey（错误代码 11000）","severity":"caution","meaning":"重复键错误的数字代码（与 E11000 文本一起返回）。写操作违反了唯一索引约束。","cause":"再次写入相同的唯一字段、并发 upsert 生成相同的键，或批量写入中存在重复文档。","fix":"在应用代码中捕获错误代码 11000 并实现幂等行为（存在则更新，否则插入）。在批量操作中使用 ordered:false，使重复文档以外的文档仍能写入。重新审视唯一性规则和索引定义。","tags":["duplicatekey","11000","duplicate","unique","unique","bulk write","write error"]},{"code":"13","title":"Unauthorized（错误代码 13）","severity":"caution","meaning":"已认证的用户对所尝试的操作或资源没有权限。用户已登录，但其角色不包含该操作。","cause":"未向用户授予所需角色（如 readWrite、dbAdmin）、操作针对的是错误的数据库，或执行管理命令的权限不足。RBAC 配置不完整。","fix":"检查用户的角色：db.getUser(\"app_user\")。授予所需角色：db.grantRolesToUser(\"app_user\", [{role:\"readWrite\", db:\"mydb\"}])。确保操作在正确的数据库中运行。遵循最小权限原则分配合适的内置或自定义角色。核实连接字符串中的 authSource 值。","tags":["unauthorized","unauthorized","authorization","role","role","rbac","13","permission"]},{"code":"18","title":"AuthenticationFailed（错误代码 18）","severity":"caution","meaning":"认证失败。提供的用户名、密码或凭据无效；用户无法登录。","cause":"用户名/密码错误、authSource 数据库错误、认证机制不匹配（SCRAM-SHA-1/SCRAM-SHA-256），或用户未在指定数据库中定义。","fix":"核实用户名和密码。在连接字符串中指定正确的 authSource（创建用户的数据库，通常是 admin）：...?authSource=admin。检查认证机制是否与服务器兼容。确保用户是在正确的数据库中创建的。如果密码包含特殊字符，对其进行 URL 编码。","tags":["authentication failed","authentication","password","password","authsource","18","scram"]},{"code":"11600","title":"InterruptedAtShutdown（错误代码 11600）","severity":"danger","meaning":"由于服务器开始关闭，操作被中断。由于 mongod 进程正在停止，进行中的操作无法完成。","cause":"mongod 重启/关闭、副本集主节点 stepdown 期间的关闭、计划内维护、崩溃，或进程被 OOM killer 杀掉。","fix":"等待服务器恢复后重试该操作。在应用中捕获瞬时错误并自动重试；启用 retryable writes。对于意外关闭，检查 mongod 日志和系统日志（OOM、崩溃）。使用副本集实现高可用。","tags":["interrupted","shutdown","shutdown","downtime","11600","retry","replica set"]},{"code":"50","title":"MaxTimeMSExpired（错误代码 50）","severity":"caution","meaning":"操作超出了 maxTimeMS 设置的时间限制，服务器将其取消。查询未能在规定时间内完成。","cause":"长时间运行的查询、缺少索引导致的集合扫描（COLLSCAN）、数据量大、繁重的聚合操作或服务器负载高。","fix":"优化查询：用 explain() 检查执行计划并添加合适的索引。如果确实需要更多时间，增大 maxTimeMS。简化繁重的聚合，或尽早用 $match 过滤。通过分页拆分结果。检查服务器负载和资源容量。","tags":["maxtimems","timeout","timeout","50","slow query","slow query","index"]},{"code":"251","title":"NoSuchTransaction（错误代码 251）","severity":"caution","meaning":"未找到所引用的多文档事务。该事务已经结束、超时，或使用了无效的事务编号。","cause":"事务超出 transactionLifetimeLimitSeconds 而被自动中止、会话已结束、txnNumber 错误/过时，或在出错后已中止的事务上继续操作。","fix":"从头重新开始事务（使用新会话）。保持事务简短；设计为在默认生命周期（60 s）内完成。对于带 TransientTransactionError 标签的错误，安全地重试整个事务。如果需要长事务，检查 transactionLifetimeLimitSeconds 设置。","tags":["nosuchtransaction","transaction","transaction","251","session","abort","retry"]},{"code":"112","title":"WriteConflict（错误代码 112）","severity":"caution","meaning":"两个并发操作尝试修改相同文档时发生的写冲突。由于采用乐观并发控制，WiredTiger 会回滚其中一个操作。","cause":"对相同文档的高并发写入、长时间运行事务期间的冲突更新、对热点文档的激烈争用。","fix":"重试该操作；WriteConflict 通常是瞬时的，重试即可解决（它带有 TransientTransactionError 标签）。保持事务简短并减少涉及的文档数。重新审视数据模型以减少对同一文档的争用。在应用中实现带指数退避的重试。","tags":["writeconflict","write conflict","112","concurrency","concurrency","retry","wiredtiger"]},{"code":"133","title":"FailedToSatisfyReadPreference（错误代码 133）","severity":"danger","meaning":"找不到满足指定读偏好（如 primary、secondary）的合适副本集成员。驱动无法选择节点来路由请求。","cause":"副本集成员不可达/宕机、选举进行中不存在主节点、网络分区、没有成员匹配 tag set 条件，或无法达到多数（quorum）。","fix":"检查副本集状态：rs.status()。核实节点健康状况和网络连通性。等待主节点选举完成后重试。检查读偏好设置；必要时使用更灵活的偏好（如 primaryPreferred）。如果使用 tag set，确保存在匹配的成员。","tags":["read preference","readpreference","133","replica set","election","secondary","primary"]},{"code":"6","title":"HostUnreachable（错误代码 6）","severity":"danger","meaning":"驱动/客户端无法到达目标 MongoDB 服务器（主机）。在网络层面无法建立连接。","cause":"服务器宕机、主机/端口错误、防火墙阻止、DNS 解析问题、网络中断，或 bindIp 配置不包含该客户端。","fix":"检查主机名和端口是否正确（默认 27017）。确认 mongod 服务正在运行。测试防火墙和网络访问（ping/telnet）。确保 mongod.conf 中的 bindIp 设置允许客户端访问。核实 DNS/SRV 记录（针对 mongodb+srv 连接）。","tags":["hostunreachable","host","connection","network","network","6","firewall","bindip"]},{"code":"89","title":"NetworkTimeout（错误代码 89）","severity":"danger","meaning":"与服务器通信期间的网络操作未在规定时间内完成而超时。连接已建立，但响应未能及时到达。","cause":"网络缓慢或不稳定、服务器过载、长时间运行的操作延迟了响应、socket/connect 超时值设置过低，或网络拥塞。","fix":"测量网络延迟和稳定性。检查服务器负载和资源。在驱动中将 connectTimeoutMS 和 socketTimeoutMS 设为合理的值。捕获瞬时错误并重试。优化慢查询以缩短响应时间。","tags":["networktimeout","network timeout","89","timeout","socket","network","retry"]},{"code":"262","title":"ExceededTimeLimit（错误代码 262）","severity":"caution","meaning":"操作超出服务器端允许的时间限制而被终止。超出 maxTimeMS 或内部时间限制都可能导致该错误。","cause":"长时间运行的查询/命令、缺少索引导致执行缓慢、繁重的聚合、扫描大型数据集，或服务器负载过重。","fix":"用 explain() 检查查询并添加合适的索引。将操作拆分为更小的块。根据实际需要设置 maxTimeMS。在聚合管道中尽早用 $match/$project 减少数据。确认服务器资源充足。","tags":["exceededtimelimit","time limit","262","timeout","maxtimems","slow","index"]},{"code":"292","title":"QueryExceededMemoryLimitNoDiskUseAllowed（错误代码 292）","severity":"caution","meaning":"查询或聚合阶段超出了允许的内存限制（默认 100 MB），并且由于不允许写入磁盘而失败。通常出现在 sort 或 group 阶段。","cause":"对未建索引的大量数据进行内存排序、大型 $group/$sort 聚合阶段、非常大的中间结果集、allowDiskUse:false 设置。","fix":"在聚合中使用 allowDiskUse:true 选项允许溢出到磁盘：db.collection.aggregate(pipeline, {allowDiskUse:true})。为排序字段建立索引以减少内存使用。在管道中尽早用 $match 过滤以减少处理的数据量。必要时对结果分页。在 MongoDB 6.0+ 中，大多数情况下默认启用磁盘使用。","tags":["memory limit","memory limit","292","allowdiskuse","sort","aggregation","group"]},{"code":"26","title":"NamespaceNotFound（错误代码 26）","severity":"safe","meaning":"未找到所引用的命名空间（database.collection）。指定的集合或索引不存在。","cause":"集合名拼写错误、集合尚未创建、数据库上下文错误，或尝试对不存在的集合执行 drop/索引操作。","fix":"核实集合名和数据库：show collections。注意名称区分大小写。MongoDB 会在首次写入时自动创建集合；必要时用 db.createCollection() 显式创建。对于 drop 等操作，事先检查集合是否存在。","tags":["namespacenotfound","namespace","collection missing","26","collection","not found","not found"]},{"code":"43","title":"CursorNotFound（错误代码 43）","severity":"caution","meaning":"服务器端游标已不存在。客户端请求下一批数据（getMore）时，由于找不到该游标而返回错误。","cause":"游标超时（默认 10 分钟无活动）、批次之间等待时间过长、服务器重启，或游标被显式关闭。","fix":"更快地处理游标结果；不要在批次之间长时间等待。对于长时间运行的操作，谨慎使用 noCursorTimeout 选项（mongosh：cursor.noCursorTimeout()；PyMongo：no_cursor_timeout=True），并务必关闭游标；即使使用该选项，会话内的游标也会在会话超时（localLogicalSessionTimeoutMinutes，默认 30 分钟）时被关闭。对于大型结果集，使用更小的 batchSize 迭代或采用基于范围的分页。重新开始操作并再次查询。","tags":["cursornotfound","cursor","cursor","43","getmore","timeout","batch","pagination"]},{"code":"211","title":"KeyNotFound（错误代码 211）","severity":"caution","meaning":"未找到预期的键/值。通常表示与内部密钥管理、会话密钥或 oplog/keys 集合相关的不一致。","cause":"admin.system.keys 集合中的签名密钥缺失/被删除、副本集/分片集群中的时钟同步问题、keyfile/密钥轮换问题，或内部状态损坏。","fix":"核实副本集成员之间的时钟同步（NTP）。检查 admin.system.keys 的内容和密钥生成情况。检查集群状态（rs.status()、sh.status()）。如果问题持续，分析 mongod 日志并检查 keyfile/身份配置。确保主节点运行正常，以便重新生成密钥。","tags":["keynotfound","key","211","keys","session","ntp","cluster","cluster"]}]},"errors/redis.json":{"engineId":"redis","engine":"Redis","order":6,"errors":[{"code":"WRONGTYPE","title":"WRONGTYPE Operation against a key holding the wrong kind of value（对持有错误类型值的键执行了操作）","severity":"caution","meaning":"对某个键执行了不适合该键数据类型的命令。例如，对持有字符串的键执行列表（LPUSH）或哈希（HSET）命令。","cause":"键的类型与预期不同、同一键名用于不同的数据类型、应用逻辑中的类型混淆，或旧的/残留的键被覆盖。","fix":"检查键类型：TYPE mykey。根据键的实际类型选择命令。在键命名中区分类型（如 user:1:tags 用 set，user:1:name 用 string）。必要时删除旧键（DEL）并以正确类型重新创建。在应用层保证类型一致性。","tags":["wrongtype","wrong type","type","data type","key","key"]},{"code":"OOM","title":"OOM command not allowed when used memory > 'maxmemory'（已用内存 > 'maxmemory' 时不允许执行命令）","severity":"danger","meaning":"Redis 使用的内存已达到 maxmemory 限制，由于淘汰策略无法为新数据腾出空间，写命令被拒绝。内存已满；无法写入。","cause":"超出 maxmemory 限制、选择了 noeviction 策略（从不删除任何键）、没有 TTL 的数据不断增长、内存泄漏，或 RAM 不足以支撑该限制。","fix":"检查内存使用：INFO memory 和 MEMORY USAGE。选择合适的淘汰策略（如 allkeys-lru 或 volatile-lru）：CONFIG SET maxmemory-policy allkeys-lru。为键设置 TTL 以便自动清理。增大 maxmemory/RAM。找出并缩小大键（redis-cli --bigkeys）。清除不必要的数据。","tags":["oom","out of memory","memory","maxmemory","eviction","memory","danger"]},{"code":"NOAUTH","title":"NOAUTH Authentication required（需要认证）","severity":"caution","meaning":"Redis 要求认证，但客户端在认证之前就尝试执行命令。必须先用 AUTH 登录。","cause":"服务器设置了 requirepass/ACL 密码、客户端未发送 AUTH 命令、连接字符串中缺少密码，或连接重建后未再次执行 AUTH。","fix":"执行命令前先进行认证：AUTH password（使用 ACL 时为 AUTH app_user password）。将密码添加到客户端/连接字符串中。如果使用连接池，确保每个新连接都执行 AUTH。安全地存储密码（环境变量/密钥管理器）。","tags":["noauth","authentication","authentication","auth","password","requirepass","acl"]},{"code":"WRONGPASS","title":"WRONGPASS invalid username-password pair or user is disabled（用户名-密码组合无效或用户已禁用）","severity":"caution","meaning":"AUTH 命令中给出的用户名/密码组合无效，或 ACL 用户已被禁用。认证被拒绝。","cause":"密码错误、ACL 用户名错误、用户在 ACL 中被禁用、requirepass 值已更改，或密码中存在转义/编码问题。","fix":"输入正确的用户名和密码。检查 ACL 用户：ACL LIST 和 ACL GETUSER app_user。如果用户被禁用，启用它（ACL SETUSER app_user on ...）。核实用 requirepass 设置的密码（CONFIG GET requirepass）。重置密码时，确保所有客户端都已更新。","tags":["wrongpass","password","invalid password","auth","acl","user","invalid"]},{"code":"READONLY","title":"READONLY You can't write against a read only replica（不能对只读副本执行写入）","severity":"caution","meaning":"写命令被发送到副本（slave）节点。副本默认是只读的；写入只能在 master 上执行。","cause":"客户端连接的是副本而非 master、故障转移后旧 master 变成了副本、Sentinel/Cluster 中错误路由到了错误节点，或负载均衡器将写请求转发到了副本。","fix":"将写请求路由到 master 节点。如果使用 Sentinel，从 Sentinel 查询当前 master（SENTINEL get-master-addr-by-name）。在 Cluster 中，写入正确分片的 master。在客户端库中配置读写分离。故障转移后刷新连接，使其指向新的 master。","tags":["readonly","read only","replica","slave","write","write","master","failover"]},{"code":"MASTERDOWN","title":"MASTERDOWN Link with MASTER is down and replica-serve-stale-data is set to 'no'（与 MASTER 的连接已断开，且 replica-serve-stale-data 设为 'no'）","severity":"danger","meaning":"副本与 master 之间的连接已断开，由于 replica-serve-stale-data 设为 'no'，副本拒绝用过时数据响应请求。","cause":"master 节点崩溃或不可达、网络中断/分区、master-副本复制链路断开，或 master 在高负载下无法响应。","fix":"检查 master 节点的状态和网络连通性。检查复制状态：INFO replication。恢复 master，或通过 Sentinel/Cluster 进行故障转移。如果可以接受提供过时数据，考虑 replica-serve-stale-data yes（注意一致性风险）。使用 Sentinel/Cluster 实现高可用。","tags":["masterdown","master down","replication","replication","stale","failover","sentinel","danger"]},{"code":"CLUSTERDOWN","title":"CLUSTERDOWN The cluster is down / Hash slot not served（集群已宕机 / 哈希槽未被服务）","severity":"danger","meaning":"Redis Cluster 未完全正常运行。部分哈希槽没有任何节点提供服务，因此集群拒绝请求。","cause":"可达的 master 节点不足、一个或多个槽未分配/未覆盖、失去多数（quorum）、节点崩溃，或在 cluster-require-full-coverage 为 yes 时部分槽不可达。","fix":"检查集群状态：CLUSTER INFO 和 CLUSTER NODES。恢复宕机的 master，或让副本被提升。重新分配未分配的槽（redis-cli --cluster fix 或 CLUSTER ADDSLOTS）。确认所有槽都已覆盖。如果可以接受部分运行，考虑 cluster-require-full-coverage no。","tags":["clusterdown","cluster","cluster","hash slot","slot","quorum","coverage","danger"]},{"code":"LOADING","title":"LOADING Redis is loading the dataset in memory（Redis 正在将数据集加载到内存中）","severity":"caution","meaning":"Redis 正在启动，并将数据集（RDB/AOF）从磁盘加载到内存。在加载完成前，大多数命令会被暂时拒绝。","cause":"服务器刚刚启动、正在加载大型 RDB/AOF 文件、副本正在从 master 进行全量同步，或重启后的恢复过程。","fix":"等待加载完成；耗时取决于数据大小。监控状态：INFO persistence（loading:1 时等待；为 0 时就绪）。在应用中捕获该瞬时错误并以短间隔重试。为缩短加载时间，审视数据集/持久化策略（RDB 与 AOF）。提供足够的 RAM 和快速磁盘。","tags":["loading","loading","startup","startup","rdb","aof","sync","persistence"]},{"code":"BUSY","title":"BUSY Redis is busy running a script. You can only call SCRIPT KILL or SHUTDOWN NOSAVE（Redis 正忙于运行脚本。只能调用 SCRIPT KILL 或 SHUTDOWN NOSAVE）","severity":"caution","meaning":"一个长时间运行的 Lua 脚本或函数正在执行，并已超过 busy-reply-threshold（Redis 7 之前名为 lua-time-limit；默认 5000 ms）。由于 Redis 是单线程的，在此期间无法处理其他命令；只接受 SCRIPT KILL（函数为 FUNCTION KILL）或 SHUTDOWN NOSAVE。","cause":"无限循环或运行时间非常长的 Lua 脚本/函数、繁重的计算、遍历大量数据的脚本，或编写不当的 EVAL 调用。","fix":"如果脚本尚未修改数据，停止它：SCRIPT KILL。如果脚本已经执行了写入，SCRIPT KILL 无效；作为保持数据一致性的最后手段，可能需要 SHUTDOWN NOSAVE（未保存的数据会丢失；务必谨慎）。将脚本写得简短且确定。将长操作拆分为多个块。检查 busy-reply-threshold（原 lua-time-limit）设置。","tags":["busy","busy server","lua","script","script kill","eval","time limit"]},{"code":"MISCONF","title":"MISCONF Redis is configured to save RDB snapshots, but it's currently unable to persist to disk（Redis 已配置为保存 RDB 快照，但当前无法持久化到磁盘）","severity":"danger","meaning":"Redis 已配置为生成 RDB 快照，但无法写入磁盘。在这种状态下，如果启用了 stop-writes-on-bgsave-error，Redis 会拒绝所有写命令。","cause":"磁盘已满、文件系统权限问题、目录不可写、后台保存（BGSAVE）错误、内存不足导致 fork 失败，或磁盘硬件问题。","fix":"释放磁盘空间并检查 RDB 目录（dir）的写权限。检查最近的 BGSAVE 错误：INFO persistence 和 Redis 日志。如需在问题修复前临时允许写入，可设置 stop-writes-on-bgsave-error no（注意持久性风险）。确保有足够的内存用于 fork，并设置 vm.overcommit_memory=1。核实持久化配置（save/appendonly）。","tags":["misconf","persist","durability","rdb","bgsave","disk","snapshot","danger"]},{"code":"CROSSSLOT","title":"CROSSSLOT Keys in request don't hash to the same slot（请求中的键未哈希到同一槽）","severity":"caution","meaning":"在 Redis Cluster 中使用了多键命令（如 MGET、MSET、SUNION），但这些键落在不同的哈希槽中。多键操作要求所有键位于同一槽中。","cause":"在集群模式下，同时访问多个键的命令使用了分布在不同槽中的键。由于未使用 hash tag，键被分布到了不同节点上。","fix":"使用 hash tag 将相关键放入同一槽：用花括号固定键名的一部分（如 {user1}:profile 和 {user1}:settings 会进入同一槽）。仅对共享同一 hash tag 的键使用多键命令。或者，将操作拆分为单键命令。","tags":["crossslot","cross slot","cluster","cluster","hash tag","slot","mget","mset"]},{"code":"MOVED","title":"MOVED slot ip:port（集群重定向）","severity":"safe","meaning":"在 Redis Cluster 中，持有所请求键的哈希槽属于另一个节点。服务器将客户端永久重定向到正确的节点。这是重定向而非错误。","cause":"不支持集群的客户端将请求发送到错误的节点、槽在节点之间重新分布（resharding），或客户端缓存的槽映射已过时。","fix":"使用支持集群的客户端库；它们会自动跟随 MOVED 回复，重定向到正确节点并更新槽映射。用 redis-cli 连接时使用 -c 参数。确保客户端定期刷新槽拓扑（CLUSTER SLOTS/SHARDS）。","tags":["moved","cluster","cluster","redirect","redirect","slot","resharding"]},{"code":"ASK","title":"ASK slot ip:port（集群重定向）","severity":"safe","meaning":"在 Redis Cluster 中，某个槽正在迁移到另一个节点，所请求的键可能暂时位于目标节点上。服务器仅针对本次请求将客户端重定向到目标节点（临时重定向）。","cause":"在 resharding 期间访问正在迁移的槽中已经转移的键。与 MOVED 不同，它不是永久性的；只在当时生效。","fix":"支持集群的客户端会自动处理 ASK 重定向：向目标节点发送 ASKING 命令并重试请求，但不会永久更新槽映射。使用合适的客户端库，redis-cli 使用 -c 参数。resharding 完成后，重定向会变为永久性的 MOVED。","tags":["ask","asking","cluster","cluster","migration","migration","redirect","redirect"]},{"code":"NOSCRIPT","title":"NOSCRIPT No matching script. Please use EVAL（没有匹配的脚本。请使用 EVAL）","severity":"caution","meaning":"在服务器的脚本缓存中找不到用 EVALSHA 调用的 Lua 脚本的 SHA1 摘要。Redis 不知道与该哈希对应的脚本。","cause":"服务器已重启或脚本缓存被 SCRIPT FLUSH 清除、脚本从未加载过，或故障转移后新节点上的缓存为空。","fix":"通过 EVAL 以完整脚本体运行（Redis 会自动缓存），或先用 SCRIPT LOAD 加载，再在 EVALSHA 中使用返回的 SHA。在应用中捕获 NOSCRIPT 错误，实现从 EVALSHA 回退到 EVAL 的逻辑。许多 Redis 客户端库会自动完成这一点。","tags":["noscript","script","evalsha","eval","lua","sha","script load","cache"]},{"code":"EXECABORT","title":"EXECABORT Transaction discarded because of previous errors（由于之前的错误，事务已被丢弃）","severity":"caution","meaning":"MULTI/EXEC 块中的事务因排队阶段发生的错误而中止。EXEC 拒绝该事务，不执行任何命令。","cause":"MULTI 与 EXEC 之间的命令存在语法错误，或排队时捕获的错误，如命令名不存在或参数数量错误。此类错误会使整个事务失效。","fix":"修正 MULTI 块中命令的语法和参数。排队命令时检查回复；如果任何命令返回错误而非 QUEUED，事务会在 EXEC 时中止。用有效命令重建事务。注意：Redis 事务不会回滚；只有排队错误会导致 EXECABORT，运行时错误不会阻止其他命令执行。","tags":["execabort","transaction","transaction","multi","exec","discard","queue","queue"]}]},"errors/elasticsearch.json":{"engineId":"elasticsearch","engine":"Elasticsearch","order":7,"errors":[{"code":"cluster_block_exception","title":"ClusterBlockException[blocked by: [TOO_MANY_REQUESTS/12/disk usage exceeded flood-stage watermark, index has read-only-allow-delete block]] (before 7.x: FORBIDDEN/12/index read-only / allow delete (api))（磁盘使用超过 flood-stage 水位线，索引被设为 read-only-allow-delete）","severity":"danger","meaning":"由于磁盘使用超过 flood stage 水位线（默认 95%；大容量磁盘上由 max_headroom 决定），Elasticsearch 为在受影响节点上有分片的索引设置了 read_only_allow_delete 块，以防磁盘被完全写满。由于该块，无法向索引写入（索引/更新/删除文档）；只允许删除索引本身。ES 8.x 以 HTTP 429 返回该错误。","cause":"至少一个节点的磁盘使用超过了 cluster.routing.allocation.disk.watermark.flood_stage 阈值。即使手动移除该块，只要磁盘仍然已满，Elasticsearch 就会重新设置它。没有日志轮转的大索引、未清理的快照或磁盘容量不足是典型原因。","fix":"首先释放磁盘空间：删除旧的/不需要的索引（DELETE /old-index），生成快照后移除索引，或扩容磁盘。在 7.4+ 中，当磁盘使用降到 high 水位线以下时，该块会自动移除；如需立即清除而不等待，手动移除：PUT /_all/_settings {\"index.blocks.read_only_allow_delete\": null}。作为永久解决办法，用 ILM（Index Lifecycle Management）自动删除/归档旧数据，并检查水位线设置（disk.watermark.low/high/flood_stage）。","tags":["disk","read-only","watermark","flood-stage","cluster-block","danger"]},{"code":"index_read_only_allow_delete","title":"TOO_MANY_REQUESTS/12/disk usage exceeded flood-stage watermark, index has read-only-allow-delete block (legacy: FORBIDDEN/12/index read-only / allow delete (api))（磁盘使用超过 flood-stage 水位线，索引带有 read-only-allow-delete 块）","severity":"danger","meaning":"由于 index.blocks.read_only_allow_delete 为 true，索引处于只读状态；无法写入或更新新文档，只能删除索引。Elasticsearch 通常在磁盘超过 flood stage 水位线时自动设置该项；ES 8.x 对写请求返回 HTTP 429。","cause":"由于磁盘使用超过 flood stage 阈值而自动设置了该块，或该设置被手动设为 true。如果在未释放磁盘空间的情况下移除该块，Elasticsearch 会重新设置它。","fix":"释放足够的磁盘空间。在 7.4+ 中，自动设置的块会在磁盘使用降到 high 水位线以下时自行清除；对于手动设置的块，或希望不等待立即清除，移除该块：PUT /_all/_settings {\"index.blocks.read_only_allow_delete\": null}。针对单个索引：PUT /index-name/_settings {\"index.blocks.read_only_allow_delete\": null}。如果反复出现，增加磁盘容量、设置 ILM 策略，并用 _cat/allocation 监控哪个节点在被写满。","tags":["read-only","disk","forbidden","watermark","block","danger"]},{"code":"high_disk_watermark_exceeded","title":"high disk watermark [90%] exceeded / flood stage disk watermark [95%] exceeded（超过磁盘 high 水位线 [90%] / flood stage 水位线 [95%]）","severity":"danger","meaning":"某个节点的磁盘使用超过了 high 水位线（默认 90%）或 flood stage（默认 95%）阈值。超过 high 水位线时，Elasticsearch 会尝试将分片从该节点移到其他节点；超过 flood stage 时，会将索引设为只读。","cause":"磁盘正在被写满：持续增长的日志/指标索引、从未删除的旧索引、未清理的快照仓库、磁盘过小，或分片分布不均衡导致单个节点过满。","fix":"检查磁盘使用：GET _cat/allocation?v。删除旧索引或生成快照后移除，并扩容磁盘。可以临时提高阈值（谨慎操作；在 8.x 中由于 max_headroom 校验，需要同时设置全部三项）：PUT _cluster/settings {\"persistent\":{\"cluster.routing.allocation.disk.watermark.low\":\"88%\",\"cluster.routing.allocation.disk.watermark.high\":\"93%\",\"cluster.routing.allocation.disk.watermark.flood_stage\":\"96%\"}}。问题解决后，将相同设置设为 null 以恢复默认值（transient 设置已弃用；不要使用）。永久解决办法：用 ILM 进行生命周期管理、定期清理索引并做好容量规划。如果索引因 flood stage 变为只读，需要单独移除该块。","tags":["disk","watermark","flood-stage","allocation","capacity","danger"]},{"code":"circuit_breaking_exception","title":"CircuitBreakingException[[parent] Data too large, data for [<...>] would be [...], which is larger than the limit of [...]]（数据过大，超过熔断器限制）","severity":"danger","meaning":"由于请求将超出内存（堆）熔断器限制而被拒绝。Elasticsearch 预先终止该请求，以防节点因 OutOfMemoryError 崩溃。[parent] 熔断器控制总内存使用；[fielddata] 熔断器控制 text 字段上的 fielddata 内存。","cause":"JVM 堆不足、非常大/复杂的聚合、大型结果集、大型 bulk 请求、在 text 字段上加载 fielddata，或大量并发重型查询占满堆。通常是堆压力的症状。","fix":"缩小查询：减少聚合规模、bulk 批次大小和返回字段。在 keyword 子字段（.keyword）上聚合，而不是使用 text 字段上的 fielddata。增大 JVM 堆（约为服务器 RAM 的 50%，最多约 31GB）。监控堆压力：GET _cat/nodes?v&h=name,heap.percent。解决根本原因（内存/查询），而不是临时提高熔断器限制。用 GET _nodes/stats/breaker 检查是哪个熔断器触发的。","tags":["circuit-breaker","data-too-large","heap","memory","fielddata","oom","danger"]},{"code":"mapper_parsing_exception","title":"mapper_parsing_exception: failed to parse field [...] of type [...]（无法解析类型为 [...] 的字段 [...]）","severity":"caution","meaning":"索引文档时，为某字段发送的值与 mapping 中为该字段定义的类型不匹配，无法解析。例如，向定义为 integer 的字段发送文本，或向定义为 date 的字段发送无效的日期格式。","cause":"字段类型不匹配（数值字段收到字符串、日期字段格式错误）、日期与预期日期格式不匹配、JSON 格式错误/不完整、动态映射推断出错误类型，或在需要对象的地方发送了标量值。","fix":"检查错误消息中的字段名和预期类型：GET /index-name/_mapping。使文档中的值与字段类型相符，或以正确类型重新定义 mapping。对于日期字段，使 format 参数与文档格式一致。已有字段的类型无法更改；必要时创建新索引，并用 _reindex 迁移数据。在应用层校验数据。","tags":["mapping","parse","mapper","field-type","indexing","caution"]},{"code":"strict_dynamic_mapping_exception","title":"strict_dynamic_mapping_exception: mapping set to strict, dynamic introduction of [...] within [...] is not allowed（mapping 设为 strict，不允许动态添加字段）","severity":"caution","meaning":"由于索引或对象的 mapping 设为 \"dynamic\": \"strict\"，包含 mapping 中未定义新字段的文档被拒绝。strict 模式可防止 schema 之外的字段被静默添加。","cause":"在 mapping 为 dynamic:strict 时，文档中发送了 mapping 中不存在的字段（拼写错误、新字段、意外字段）。这通常是为严格控制 schema 而有意设置的保护措施。","fix":"修正或删除文档中多余/拼写错误的字段。如果确实需要该字段，先将其添加到 mapping：PUT /index-name/_mapping {\"properties\": {\"new_field\": {\"type\": \"...\"}}}。如果希望自动添加新字段，将 dynamic 设为 \"true\"；如果希望存储但不索引，设为 \"false\"。有意识地使用 strict 模式以保持 schema 规范。","tags":["mapping","strict","dynamic","schema","field","caution"]},{"code":"illegal_argument_exception_fielddata","title":"illegal_argument_exception: Fielddata is disabled on text fields by default. Set fielddata=true on [your_field] in order to load fielddata in memory（text 字段默认禁用 fielddata）","severity":"caution","meaning":"尝试在经过分析的 text 字段上执行聚合、排序或脚本。由于这些操作需要占用大量内存的 fielddata，text 字段上默认禁用这些操作。","cause":"直接对 text 字段进行了聚合/排序。在 Elasticsearch 中，text 用于全文搜索，keyword 用于精确值聚合/排序；混用两者会产生该错误。","fix":"使用字段的 keyword 子字段进行聚合/排序（如 \"my_field.keyword\"）。如果该字段只有 text 类型，在 mapping 中添加 keyword 子字段（multi-field）并执行 _reindex。可以在 text 字段上启用 fielddata（fielddata=true），但由于内存消耗高并有熔断风险，不推荐；只在不可避免时使用。","tags":["fielddata","text","keyword","aggregation","sorting","mapping","caution"]},{"code":"search_phase_execution_exception","title":"search_phase_execution_exception: all shards failed / Failed to execute phase [query]（所有分片均失败 / 执行 [query] 阶段失败）","severity":"danger","meaning":"搜索请求在一个或全部所需分片上失败。这是一个包装了实际底层原因的通用搜索错误；\"caused by\" 部分说明了真正的问题（解析错误、熔断器、字段类型不合适、分片不可达等）。","cause":"查询语法无效、在错误的字段类型上聚合、too_many_clauses、熔断器触发、分片未分配/不可达、查询超时或节点崩溃。根本原因在响应的内层异常中。","fix":"在响应的 \"caused by\" / \"failed_shards\" 详情中找到实际异常并相应修正（修正查询、使用 keyword 字段、熔断器/堆设置、分片分配）。检查集群健康状况：GET _cluster/health 和 GET _cat/shards?v。如果允许部分结果且部分分片失败，检查 allow_partial_search_results 行为。","tags":["search","shard","search","all-shards-failed","query","danger"]},{"code":"too_many_clauses","title":"too_many_clauses: maxClauseCount is set to 1024 (query contains too many clauses)（查询包含的子句过多）","severity":"caution","meaning":"查询超出了允许的 Lucene 子句最大数量。在 ES 8.x 中该限制不是固定的；它根据节点堆大小和搜索线程数自动计算（在小堆节点上可能降至 1024），错误消息会显示当前值。非常大的 bool 查询以及展开为大量词项的查询会触发该错误。","cause":"通过程序生成的带有成千上万个 should/must 子句的 bool 查询；展开为大量词项的 wildcard/prefix/fuzzy/query_string 查询；深度嵌套的 bool 结构。注意：terms 查询（单个字段上的值列表）不受此限制；其上限为 index.max_terms_count（默认 65,536）。","fix":"简化查询：将单个字段上的大量 term 子句合并为一个 terms 查询（大型列表使用 terms lookup）。使用更具体的查询类型，或用 index_prefixes/keyword 字段代替 wildcard/prefix。在 ES 8.x 中 indices.query.bool.max_clause_count 设置已弃用并被忽略；提高该限制的方法是增大节点堆。检查查询生成逻辑。","tags":["too-many-clauses","max-clause-count","bool","terms","query","caution"]},{"code":"version_conflict_engine_exception","title":"version_conflict_engine_exception: [_doc][id]: version conflict, current version [x] is different than the one provided [y] (HTTP 409)（版本冲突，当前版本与提供的版本不同）","severity":"caution","meaning":"文档更新/删除请求被乐观并发控制拒绝。文档版本（或 _seq_no/_primary_term）与请求中的不匹配；也就是说，在你读取之后有人修改了该文档。返回 HTTP 409。","cause":"对同一文档的并发更新、使用过时的 version/seq_no 尝试写入、尝试创建（op_type=create）已存在的文档，或 _bulk 期间的竞态条件。","fix":"重新读取文档，并用当前的 _seq_no 和 _primary_term（或 version）重试请求（读-改-写循环）。如需自动重试，在 _update 请求中使用 retry_on_conflict 参数；在 _update_by_query/_delete_by_query 中传入 conflicts=proceed 以跳过冲突文档并继续。如果意图是仅创建，冲突属于正常情况；在应用中捕获 409 并做相应处理（重试或跳过）。","tags":["version-conflict","409","concurrency","optimistic","seq-no","caution"]},{"code":"document_missing_exception","title":"document_missing_exception: [_doc][id]: document missing（文档不存在）","severity":"caution","meaning":"在索引中找不到要更新（_update）的文档。更新要求文档已存在；如果文档不存在，就会出现该异常。","cause":"文档之前已被删除、从未创建、使用了错误的 _id、请求发到了错误的索引，或以自定义 routing 写入的文档在访问时未指定 routing。（GET 和 _update 是实时的；refresh 延迟不会导致该错误。）","fix":"确认 _id 和目标索引正确：GET /index-name/_doc/id。如果需要存在则更新/不存在则创建的行为，在 _update 请求中使用 upsert（或 doc_as_upsert:true）。直接用 index（PUT）创建文档。在应用中捕获该异常并回退到 upsert 逻辑。","tags":["document-missing","update","upsert","document","not-found","caution"]},{"code":"index_not_found_exception","title":"index_not_found_exception: no such index [index_name] (HTTP 404)（索引不存在）","severity":"caution","meaning":"所请求的索引（或别名）不存在。由于找不到给定名称的索引，操作以 HTTP 404 被拒绝。","cause":"索引从未创建、已被删除、索引/别名名称拼写错误、客户端连接到了错误的环境（集群），或在禁用 action.auto_create_index 时尝试写入不存在的索引。","fix":"列出现有索引：GET _cat/indices?v。核实名称和别名：GET _alias。创建索引：PUT /index-name（附带所需的 mappings/settings），或通过 index template 配置自动创建。如果需要自动创建，检查 action.auto_create_index 设置。确认没有连接到错误的集群。","tags":["index-not-found","404","index","alias","not-found","caution"]},{"code":"resource_already_exists_exception","title":"resource_already_exists_exception: index [index_name/uuid] already exists (HTTP 400)（索引已存在）","severity":"safe","meaning":"要创建的索引（或资源）已经存在。由于存在同名索引，创建请求被拒绝。在旧版本中也表现为 index_already_exists_exception。","cause":"尝试两次创建同一索引、非幂等的部署脚本、并发创建请求，或尝试以不同设置重新创建同名索引。","fix":"如果索引已存在，无需创建；继续即可。如果必须重新创建，先删除（DELETE /index-name）再创建（注意：会丢失数据）。使部署脚本幂等：先用 GET _cat/indices 检查是否存在。通过 index template 或 ILM/rollover 进行 schema 管理以避免冲突。","tags":["already-exists","resource-already-exists","index","create","idempotent","safe"]},{"code":"cluster_health_red","title":"cluster health status: RED / unassigned shards（集群健康状态：RED / 未分配的分片）","severity":"danger","meaning":"集群健康状态为 RED 表示至少有一个主分片未分配且不可用。这是一种关键状态，部分数据无法读写，并有部分数据丢失的风险。（YELLOW 表示仅副本未分配；主分片完好。）","cause":"节点崩溃/离开，其上的主分片没有副本可用、分配被磁盘水位线阻止、分片损坏、快照恢复期间出错，或分配设置阻止了分片分配。","fix":"查看分片未分配的原因：GET _cluster/allocation/explain 或 GET _cat/shards?v&h=index,shard,prirep,state,unassigned.reason。恢复崩溃的节点；如果是磁盘问题，释放空间（水位线）。如果分配被暂时阻止，触发重新分配：POST _cluster/reroute?retry_failed=true。如果主分片完全丢失，从快照恢复。在 RED 状态下，首先集中精力恢复主分片。","tags":["cluster-health","red","unassigned","shard","primary","danger"]},{"code":"no_shard_available_action_exception","title":"no_shard_available_action_exception: No shard available for [...]（没有可用的分片）","severity":"danger","meaning":"找不到能够处理请求的活动分片副本。由于相关分片未分配、正在恢复或其所在节点不可达，请求无法完成。","cause":"节点崩溃/网络分区、分片未分配、分片恢复正在进行、磁盘水位线导致的分配阻止，或集群处于 RED/YELLOW 状态。","fix":"检查集群和分片状态：GET _cluster/health 和 GET _cat/shards?v。查看分片未分配的原因：GET _cluster/allocation/explain。恢复崩溃的节点，解决磁盘问题，并等待恢复完成：GET _cat/recovery?v&active_only=true。必要时用 POST _cluster/reroute?retry_failed=true 重试分配。","tags":["no-shard-available","shard","unassigned","recovery","cluster","danger"]},{"code":"es_rejected_execution_exception","title":"es_rejected_execution_exception: rejected execution of ... on ... (queue capacity ...) (HTTP 429)（线程池队列已满，执行被拒绝）","severity":"danger","meaning":"由于相关线程池（search、write/bulk 等）的队列容量已满，操作被拒绝。在过载情况下，Elasticsearch 通过拒绝请求（背压）来保护自身，并返回 HTTP 429（Too Many Requests）。","cause":"并发请求量非常高、bulk 请求非常大或非常频繁、繁重的聚合/搜索负载、节点/CPU 资源不足，或线程池队列相对工作负载过小。","fix":"在客户端以指数退避方式重试；429 通常是瞬时的。减小 bulk 批次大小和发送速率，降低并发度。监控被拒绝的操作：GET _cat/thread_pool?v&h=node_name,name,active,queue,rejected。对于持续负载，增加节点/资源（水平扩展）。增大队列大小是最后手段；根本原因在于容量。","tags":["rejected-execution","429","thread-pool","queue","bulk","backpressure","danger"]},{"code":"master_not_discovered_exception","title":"master_not_discovered_exception / MasterNotDiscoveredException（未发现 master 节点）","severity":"danger","meaning":"集群无法选举/找到活动的 master 节点。没有 master，就无法进行分片分配、mapping 变更和集群状态更新；集群将无法处理操作。","cause":"多数（quorum）master-eligible 节点不可达、网络分区（防止脑裂）、discovery.seed_hosts / cluster.initial_master_nodes 配置错误、新安装未执行 bootstrap，或 master-eligible 节点过少。","fix":"确认 master-eligible 节点正在运行且彼此可见：GET _cat/nodes?v 和 GET _cluster/state。检查 discovery.seed_hosts 和 cluster.initial_master_nodes 设置（首次部署时）。核实网络/防火墙以及节点间（transport 9300）的连通性。保持足够数量的 master-eligible 节点（最好为 3 个）运行。在日志中查看 master 选举相关消息。","tags":["master","discovery","quorum","split-brain","cluster","danger"]},{"code":"security_exception","title":"security_exception: unable to authenticate / action [...] is unauthorized for user [...] (HTTP 401/403)（无法认证 / 用户无权执行该操作）","severity":"danger","meaning":"认证（401 Unauthorized）或授权（403 Forbidden）失败。401 表示凭据缺失/不正确；403 表示用户已认证，但对该操作/索引缺少所需的角色/权限。","cause":"用户名/密码或 API key 错误、缺少 Authorization 头、token 已过期/无效、用户缺少相关索引或集群权限，或角色分配缺失/不正确。","fix":"对于 401：发送正确的凭据/API key 并检查 Authorization 头。对于 403：检查用户的角色和权限（GET _security/user/app_user、GET _security/role/app_role），并添加所需的索引/集群权限。遵循最小权限原则。如果使用 API key，核实其有效期和权限。","tags":["security","401","403","authentication","authorization","security","danger"]},{"code":"validation_exception","title":"validation_exception / illegal_argument_exception: Validation Failed: ...（校验失败）","severity":"caution","meaning":"请求的参数或设置未通过校验。Elasticsearch 未执行包含无效或不一致值的请求，而是直接拒绝（如分片数无效、设置组合不兼容、缺少必填字段）。","cause":"无效的索引设置（如 number_of_shards 为负数/零）、尝试在已有索引上更新不可修改的设置、不兼容的参数组合、缺少必填字段，或值超出范围。","fix":"阅读错误消息中的校验详情；其中会明确说明哪个参数无效以及原因。将值调整到有效的范围/格式。不可修改的（static）设置应在创建新索引时指定，而不是在已有索引上设置（必要时使用 _reindex）。提供 API 所需的必填字段。核实该设置在文档中列出的有效值。","tags":["validation","validation","settings","illegal-argument","invalid","caution"]},{"code":"task_cancelled_exception","title":"task_cancelled_exception: Task was cancelled / TaskCancelledException（任务已被取消）","severity":"caution","meaning":"正在运行的任务（search、_reindex、_update_by_query 等）被取消。取消可能来自用户向 _tasks/_cancel 发出的请求、客户端关闭 HTTP 连接（客户端超时/中止；Elasticsearch 会自动取消关联的搜索），或父任务被取消、异步搜索过期。","cause":"用户通过 POST _tasks/<task_id>/_cancel 取消了任务（X-Opaque-Id 只用于标识和查找任务，不会取消任务）、客户端超时断开了连接、长时间运行的昂贵查询，或自适应资源管理停止了繁重的任务。","fix":"如果任务被意外取消，检查客户端超时和连接设置；优化繁重的查询（更少的数据、更具体的过滤条件）。对于长时间操作，以异步方式运行 _reindex/_update_by_query（wait_for_completion=false），并用 GET _tasks 监控。查看活动任务：GET _tasks?detailed=true。必要时拆分昂贵的查询或使用 scroll/PIT。","tags":["task-cancelled","task","timeout","reindex","cancel","caution"]}]},"errors/oracle-more.json":{"engineId":"oracle","engine":"Oracle","order":1,"errors":[{"code":"ORA-00604","title":"error occurred at recursive SQL level（递归 SQL 级别出错）","severity":"danger","meaning":"Oracle 在后台内部运行的递归 SQL 执行期间发生错误。这是一个包装（外层）错误，隐藏了实际错误；真正的原因在其正下方列出的第二个错误代码中。","cause":"数据字典访问、DDL 触发器失败、SYSTEM 表空间已满、临时空间不足、数据字典对象损坏，或会话登录时运行的 LOGON 触发器失败。","fix":"阅读错误栈中 ORA-00604 之后的第二行；那才是真正的原因，并据此修正。例如，如果看到 ORA-01653，则扩展表空间。如果怀疑是 DDL/LOGON 触发器，暂时禁用它：ALTER TRIGGER trigger_name DISABLE; 通过 DBA_FREE_SPACE 检查 SYSTEM 和 TEMP 表空间的使用情况。","tags":["recursive","recursion","dictionary","data dictionary","trigger","wrapper"]},{"code":"ORA-01438","title":"value larger than specified precision allowed for this column（值大于此列允许的指定精度）","severity":"caution","meaning":"向 NUMBER 列写入了超出所定义精度和小数位数限制的数字。例如，整数部分为 3 位的值放不进 NUMBER(4,2) 列。","cause":"列的 NUMBER(p,s) 定义相对数据过窄；计算结果大于预期；小数位数减少了整数部分可用的位数（NUMBER(5,3) 只允许 2 位整数）。","fix":"根据实际数据范围加宽列容量：ALTER TABLE my_table MODIFY (my_column NUMBER(12,2)); 在应用中对值取整，或用 ROUND/TRUNC 使其符合小数位数。要找出溢出的行，在测试时逐步缩小查询范围，并添加输入校验。","tags":["number","precision","precision","scale","scale","overflow","numeric"]},{"code":"ORA-01400","title":"cannot insert NULL into (column)（无法将 NULL 插入（列））","severity":"caution","meaning":"向带有 NOT NULL 约束的列插入或设置了 NULL 值。必填字段不能为空。","cause":"INSERT 中未为必填列提供值、UPDATE 将该列设为 NULL、该列未定义 DEFAULT，或子查询/应用传来空值。","fix":"为错误消息中指出的列提供有效值。为该列定义合理的默认值：ALTER TABLE my_table MODIFY (my_column DEFAULT 0);（DEFAULT 仅在 INSERT 中完全省略该列时生效；对于显式发送的 NULL 值，使用 12c+ 的 DEFAULT ON NULL：ALTER TABLE my_table MODIFY (my_column DEFAULT ON NULL 0);）如果该规则并非真正必需，删除约束：ALTER TABLE my_table MODIFY (my_column NULL); 在发送前于应用中校验空值。","tags":["not null","null","empty","insert","constraint","constraint","mandatory"]},{"code":"ORA-02291","title":"integrity constraint violated - parent key not found（违反完整约束条件 - 未找到父项关键字）","severity":"danger","meaning":"违反了外键约束。找不到与插入子表的值对应的父行；也就是说，所引用的记录不存在。","cause":"INSERT/UPDATE 引用了不存在的父记录、父表行尚未插入、加载顺序错误（先子后父），或因大小写或空白差异导致匹配失败。","fix":"先向父表插入相关记录，再插入子表。确认引用存在：SELECT * FROM parent_table WHERE key_col = value; 在批量加载中按正确顺序处理表（先父表）。要找出违规的行，用 NOT IN / 反连接将子表中的值与父表比较。","tags":["fk","foreign key","foreign key","constraint","constraint","parent","reference"]},{"code":"ORA-02292","title":"integrity constraint violated - child record found（违反完整约束条件 - 已找到子记录）","severity":"danger","meaning":"删除了父行或修改了其键，但由于存在依赖的子记录，外键约束不允许该操作。","cause":"尝试 DELETE 在子表中有记录的父行；FK 定义时没有 ON DELETE CASCADE；未事先清理依赖记录。","fix":"先删除依赖的子记录或将其移到另一条父记录下，然后再删除父行。如果业务规则允许，用 ON DELETE CASCADE 重新创建外键：ALTER TABLE child DROP CONSTRAINT fk_name; ALTER TABLE child ADD CONSTRAINT fk_name FOREIGN KEY (my_column) REFERENCES parent(key_col) ON DELETE CASCADE; 要查找依赖行，按父键查询子表。","tags":["fk","foreign key","foreign key","child","child record","delete","cascade"]},{"code":"ORA-00979","title":"not a GROUP BY expression（不是 GROUP BY 表达式）","severity":"caution","meaning":"SELECT 列表中将未聚合的列与聚合函数（SUM、COUNT 等）一起使用，但该列未添加到 GROUP BY 子句中。Oracle 不知道如何对该列分组。","cause":"某列在 SELECT 中但不在 GROUP BY 中；聚合函数之外的表达式未包含在 GROUP BY 列表中；HAVING 或 select 表达式不一致。","fix":"将所有未聚合的列添加到 GROUP BY 列表：SELECT department, COUNT(*) FROM employee GROUP BY department; 或者也将该列包在聚合函数（MAX、MIN）中，或用分析函数（OVER()）重新设计分组。","tags":["group by","grouping","aggregate","aggregation","sum","count","query"]},{"code":"ORA-00918","title":"column ambiguously defined（列定义不明确）","severity":"caution","meaning":"查询中有多个表包含同名的列，由于使用时未加表/别名前缀，Oracle 无法确定它属于哪个表。","cause":"连接的两个表都有同名列（如两者都有 ID）；在 SELECT、WHERE 或 ORDER BY 中未限定（未加前缀）地使用该列。","fix":"用表名或别名限定每个不明确的列：SELECT a.id, b.id FROM tableA a JOIN tableB b ON a.id = b.a_id; 为表指定简短的别名，并为所有列加上前缀，以提高可读性和清晰度。","tags":["ambiguous","ambiguous column","column","column","join","alias","query"]},{"code":"ORA-06550","title":"line/column PLS-... PL/SQL compilation error（行/列 PLS-... PL/SQL 编译错误）","severity":"caution","meaning":"编译 PL/SQL 块、过程或匿名块时发现语法或语义错误。代码在运行前的编译阶段就已失败。","cause":"拼写错误、引用了未声明的变量或对象、调用时参数数量/类型错误、缺少分号、调用了不存在的过程/包，或缺少权限。","fix":"定位到错误消息中的行号和列号；阅读 PLS-XXXX 子代码。查看所有编译错误：SHOW ERRORS; 或 SELECT * FROM user_errors WHERE name = 'OBJECT_NAME'; 核实对象名、参数签名和所需的 GRANT，然后重新编译。","tags":["plsql","compilation","compile","pls","syntax","procedure","code"]},{"code":"ORA-04068","title":"existing state of packages has been discarded（已丢弃程序包的当前状态）","severity":"caution","meaning":"在运行中的会话使用某个包的状态时，该包被重新编译，因此该状态失效。会话持有的包实例已不再有效。","cause":"在依赖它的会话处于活动状态时，用 ALTER/CREATE OR REPLACE 重新编译了包体或包规范；通常发生在在线系统上部署应用时。与 ORA-04061/04065 一起出现。","fix":"重新执行调用（重试）通常即可解决；第二次尝试时包会以新形式加载。在应用中捕获 ORA-04068 并自动重试。在用户活动较少的维护窗口中应用包变更。无状态的包设计可减少此问题。","tags":["package","package","state","package state","recompile","compile","retry"]},{"code":"ORA-01427","title":"single-row subquery returns more than one row（单行子查询返回多个行）","severity":"caution","meaning":"预期返回单行的子查询返回了多行。例如，等值（=）比较右侧的子查询产生了多个值。","cause":"在需要 = 或赋值的地方子查询返回了多行；WHERE 条件不够精确；关联条件中的匹配不唯一；SELECT 列表中的标量子查询返回多行。","fix":"缩小子查询范围，使其只返回一行（附加 WHERE、唯一键）。如果确实需要多个值，用 IN 代替 =：WHERE id IN (SELECT id FROM ...)。或者在子查询中添加聚合（MAX、MIN），或用 ROWNUM = 1 限制为一行。先单独运行子查询，检查其返回的行数。","tags":["subquery","subquery","single row","single row","in","scalar","query"]},{"code":"ORA-12899","title":"value too large for column（列的值太大）","severity":"caution","meaning":"向字符/数据列写入了长度超过定义的最大长度的值。例如，15 个字符的文本放不进 VARCHAR2(10) 列。","cause":"列宽不足以容纳数据；在多字节字符集（UTF8）中字符数超过了字节限制（BYTE 语义）；数据迁移期间使用了不同的字符集；拼接结果变长。","fix":"加宽列：ALTER TABLE my_table MODIFY (my_column VARCHAR2(100)); 对于多字节内容，使用 CHAR 语义：my_column VARCHAR2(100 CHAR)。如果必须缩短长值，用 SUBSTR 截断。错误消息会给出实际长度和允许的长度；用 LENGTH/LENGTHB 找出溢出的记录。","tags":["varchar2","length","length","too large","column","column","charset"]},{"code":"ORA-30926","title":"unable to get a stable set of rows in the source tables（无法在源表中获得一组稳定的行）","severity":"danger","meaning":"在 MERGE（或可更新连接）语句中，源端为同一目标行产生了多个匹配行。Oracle 无法决定用哪个源值更新，因此停止了操作。","cause":"MERGE ... USING 的源中包含 ON 条件的重复匹配；源查询在目标键上不唯一；多个源行映射到同一目标行。","fix":"使源查询在目标键上唯一。查找重复匹配：SELECT key_col, COUNT(*) FROM (source_query) GROUP BY key_col HAVING COUNT(*) > 1; 用 DISTINCT 或聚合/分析函数（ROW_NUMBER）对源去重，然后重新运行 MERGE。确保 ON 条件具有足够的选择性。","tags":["merge","merge","duplicate","duplicate rows","source","source","stable"]},{"code":"ORA-08103","title":"object no longer exists（对象不再存在）","severity":"danger","meaning":"运行中的查询所访问的对象（表、索引、LOB 段）已不再存在，或其物理结构已改变。通常是在查询进行期间，对象底层发生了 DDL/TRUNCATE。","cause":"在查询打开期间对同一对象执行了 TRUNCATE/DROP/SHRINK；移动（MOVE）或分区维护；索引/段损坏；访问 LOB 段时底层结构消失；并发 DDL 冲突。","fix":"确认对象仍然存在且有效：SELECT status FROM user_objects WHERE object_name = 'NAME'; 重建相关索引/对象（REBUILD）或重新运行查询。避免在长时间运行的查询使用对象时对其执行 TRUNCATE/DROP。如果怀疑损坏，用 ANALYZE ... VALIDATE STRUCTURE 检查。","tags":["object","object","truncate","drop","ddl","corruption","segment"]},{"code":"ORA-01843","title":"not a valid month（无效的月份）","severity":"caution","meaning":"将文本转换为 DATE 时，月份部分无法解释为有效的月份。通常是日期格式与数据格式不匹配。","cause":"TO_DATE 未传入格式掩码且会话的 NLS_DATE_FORMAT 与数据不匹配；日和月颠倒（DD/MM 与 MM/DD 混淆）；语言/区域差异（月份名称）；日期文本损坏或不符合预期。","fix":"在 TO_DATE 调用中始终传入显式格式：TO_DATE('2026-07-04', 'YYYY-MM-DD')。对于包含月份名称的文本，指定语言：TO_DATE(str, 'DD-MON-YYYY', 'NLS_DATE_LANGUAGE=ENGLISH')。必要时设置会话格式：ALTER SESSION SET NLS_DATE_FORMAT = 'YYYY-MM-DD'。加载前校验无效的日期文本。","tags":["date","date","to_date","nls","format","month","month"]},{"code":"ORA-14400","title":"inserted partition key does not map to any partition（插入的分区关键字未映射到任何分区）","severity":"danger","meaning":"插入行中的分区键值未映射到表上定义的任何分区。不存在覆盖该值的范围或列表分区。","cause":"在 RANGE 分区中插入了高于最高范围的日期/值且没有 MAXVALUE 分区；在 LIST 分区中出现未定义的新值；在月/年边界未提前创建新分区。","fix":"添加缺失的分区：ALTER TABLE my_table ADD PARTITION p_2026_08 VALUES LESS THAN (TO_DATE('2026-09-01','YYYY-MM-DD')); 对于 RANGE，添加 MAXVALUE 分区作为保障。为 LIST 定义 DEFAULT 分区以捕获意外值。使用自动分区（INTERVAL 分区），使新分区自动创建。","tags":["partition","partition","partition key","range","list","maxvalue","insert"]},{"code":"ORA-01536","title":"space quota exceeded for tablespace（超出表空间的空间限额）","severity":"danger","meaning":"用户在指定表空间上的空间配额已用完。即使表空间有可用空间，由于超出了授予用户的限额，也无法分配新空间。","cause":"用户的配额有限（非 UNLIMITED）；配额随数据增长被占满；向表/索引加载了过多数据；写入了错误的表空间；段增长快于预期。","fix":"增大用户配额：ALTER USER app_user QUOTA UNLIMITED ON tablespace_name; 或指定具体值：QUOTA 500M ON users。查看当前使用情况：SELECT * FROM dba_ts_quotas WHERE username = 'APP_USER'。归档/清除不必要的大段，或将对象移到合适的表空间。","tags":["quota","quota","tablespace","space","space","user","user"]},{"code":"direct path read","title":"等待：会话绕过 buffer cache 将块直接读入 PGA","severity":"caution","meaning":"会话在将块直接读入 PGA 内存（而不是通过数据库 buffer cache）时消耗在此等待事件上。通常与大型全表扫描和并行查询相关。","cause":"大表全扫描、并行查询、排序/哈希操作溢出到临时表空间导致从磁盘读取大量块，串行全扫描采用串行直接路径读（serial direct path read）的决策，以及无法使用索引的查询。","fix":"用 AWR/ASH 找出执行繁重全扫描的 SQL，并添加合适的索引。通过查询调优减少对大表的不必要扫描。调查存储延迟（I/O），必要时迁移到更快的磁盘。检查并行度。对于溢出到 TEMP 的操作，评估 PGA_AGGREGATE_TARGET 的大小。","tags":["wait","wait","direct path read","full scan","full table scan","io","pga"]},{"code":"latch: cache buffers chains","title":"等待：cache buffers chains latch 争用","severity":"danger","meaning":"保护 buffer cache 中块的 cache buffers chains（CBC）latch 上存在争用。当许多会话试图访问相同的高频访问（热）块时，该等待会上升。","cause":"频繁读取的热块；产生大量逻辑读的低效 SQL；嵌套循环连接中反复访问相同的索引根块；分布不均的高并发访问；体积小但被大量读取的表。","fix":"找出产生最多逻辑读的 SQL（V$SQL、ASH），通过改进索引/访问计划减少缓冲区访问。识别热块（X$BH / 相关视图）。考虑将不必要的嵌套循环改为哈希连接。对于体积小且被大量读取的参照表，分散访问模式或将其缓存。","tags":["latch","cache buffers chains","cbc","contention","contention","hot block","wait","wait"]}]},"errors/postgres-more.json":{"engineId":"postgres","engine":"PostgreSQL","order":2,"errors":[{"code":"42883","title":"undefined_function","severity":"caution","meaning":"在该名称和这些参数类型下找不到查询中调用的函数（或运算符）。PostgreSQL 同时按名称和参数类型解析函数；如果签名不匹配，就会引发该错误。","cause":"函数名拼写错误；参数类型与定义的签名不匹配（如传入数字而非文本）；缺少显式类型转换；函数所在的 schema 不在 search_path 中；未安装所需的扩展。","fix":"确认函数存在并检查其签名：\\df function_name。对参数进行显式转换：SELECT fn(value::text)。添加 schema 前缀或设置 search_path。如果需要扩展，安装它：CREATE EXTENSION extension_name; 对于运算符错误，使两侧类型一致。","tags":["function","function not found","undefined","operator","cast","type conversion","42883"]},{"code":"42804","title":"datatype_mismatch","severity":"caution","meaning":"预期的数据类型与提供的数据类型不匹配。例如，向列赋予错误类型的值、CASE 分支返回不同类型，或 UNION 各分支类型不一致。","cause":"与列类型不兼容的赋值；CASE/COALESCE 分支产生不同类型；UNION 中对应列类型不同；RETURN 的值与函数返回类型不匹配；在需要 boolean 的地方使用了非 boolean 值。","fix":"将表达式转换为正确类型：value::integer 或 CAST(value AS numeric)。使所有 CASE 分支返回相同类型。在 UNION 中，对齐两侧对应列的类型。确保 WHERE 条件产生 boolean 值。","tags":["type","data type","mismatch","type mismatch","cast","union","42804"]},{"code":"22P02","title":"invalid_text_representation","severity":"caution","meaning":"转换时文本值不是目标数据类型的有效表示形式。例如，尝试将文本 'abc' 转换为 integer 会引发该错误。","cause":"向数值列/参数发送了非数字文本；将空字符串（''）转换为数字/日期；UUID 或 enum 的文本无效；JSON 格式错误；输入数据不符合预期格式。","fix":"确认被转换的值确实有效。在类型转换前于应用中校验输入。将空字符串转换为 NULL：NULLIF(value, '')。在 PostgreSQL 16+ 上进行安全转换时，事先用 pg_input_is_valid('abc', 'integer') 测试该值，并用 pg_input_error_info() 获取错误详情；在旧版本上，考虑使用校验检查（正则表达式）。逐步缩小范围找出有问题的行。","tags":["cast","type conversion","invalid text","invalid input","integer","uuid","22P02"]},{"code":"22003","title":"numeric_value_out_of_range","severity":"caution","meaning":"数值超出了目标类型允许的范围。例如，向 integer 列写入大于 20 亿的值，或数字对 numeric(5,2) 列来说过大。","cause":"超出 smallint/integer/bigint 限制；超出 numeric(p,s) 的精度/小数位数；算术溢出；和/积超出类型容量；序列达到其类型的上限。","fix":"将列/参数改为更宽的类型（用 bigint 代替 integer，增大 numeric 精度）：ALTER TABLE my_table ALTER COLUMN my_column TYPE bigint; 根据业务规则对值取整（ROUND）或进行校验。如果中间计算溢出，以 numeric 类型计算表达式。对于序列溢出，加宽类型。","tags":["numeric","number","out of range","overflow","numeric overflow","integer","22003"]},{"code":"23514","title":"check_violation","severity":"caution","meaning":"违反了 CHECK 约束。插入或更新的行不满足表上定义的逻辑条件（如 price > 0）。","cause":"违反业务规则的数据（负价格、无效状态码、违反日期范围）；应用端没有校验；约束相对数据过于严格；批量加载期间存在违反规则的记录。","fix":"根据错误消息中的约束名找出被违反的条件；查看定义：\\d table_name。将条件取反来查询违规的行。修正数据；如果规则确实已变更，更新约束：ALTER TABLE my_table DROP CONSTRAINT name; ALTER TABLE my_table ADD CONSTRAINT name CHECK (new_condition);","tags":["check","constraint","check constraint","violation","constraint violation","validation","23514"]},{"code":"22012","title":"division_by_zero","severity":"caution","meaning":"算术表达式尝试除以零（或对零取模）。PostgreSQL 不允许除以零，并中止查询。","cause":"作为除数的列/表达式计算结果为 0；比率/百分比计算中分母为零；除以为零的 COUNT 或 SUM；未过滤的数据中存在意外的零值。","fix":"使用 NULLIF，在分母为零时返回 NULL：numerator / NULLIF(denominator, 0)。如有需要，用 COALESCE 将结果转为默认值：COALESCE(numerator / NULLIF(denominator,0), 0)。或者使用 CASE WHEN denominator = 0 THEN NULL ELSE numerator/denominator END。事先过滤掉数据中的零值情况。","tags":["division","divide","zero","divide by zero","nullif","arithmetic","22012"]},{"code":"42601","title":"syntax_error","severity":"caution","meaning":"SQL 语句中发现语法错误。PostgreSQL 无法解析该查询，并报告错误的大致位置（position）。","cause":"缺少/多余的逗号或括号；将保留关键字（如 user、order）用作不带引号的列名；缺少分号；命令拼写错误；字符串字面量未闭合；使用了特定版本的语法。","fix":"根据错误消息中的 'position' 值定位问题；检查其紧前面的部分。用双引号引用保留字：\"order\"。检查括号和引号是否配对。将复杂查询拆分成多个部分进行测试。查阅对应版本的文档以确认正确语法。","tags":["syntax","syntax error","parse","parser","keyword","position","42601"]},{"code":"42P07","title":"duplicate_table","severity":"caution","meaning":"要创建的表（或类似对象）已经存在。由于存在同名对象，CREATE 失败。","cause":"用 CREATE TABLE 重新创建已有的表；脚本运行了两次；重复应用迁移；与同名的视图/序列/索引发生名称冲突。","fix":"使用 IF NOT EXISTS，在对象已存在时跳过创建：CREATE TABLE IF NOT EXISTS my_table (...)。如果确实要重新创建，先删除：DROP TABLE IF EXISTS my_table; 使迁移可以安全地重复运行（幂等）。用 \\dt 检查已有对象。","tags":["duplicate","duplicate object","table","relation","create","already exists","42P07"]},{"code":"42701","title":"duplicate_column","severity":"caution","meaning":"在表定义或 ALTER 操作中多次使用了相同的列名，或再次添加了已存在的列。","cause":"在 CREATE TABLE 中两次写入相同的列名；用 ALTER TABLE ADD COLUMN 添加已存在的列；复制粘贴错误；迁移再次运行。","fix":"删除或重命名表定义中重复的列名。先检查，以免重复添加已有列：\\d table_name。在版本支持的情况下，安全地添加：ALTER TABLE my_table ADD COLUMN IF NOT EXISTS my_column type; 确保迁移是幂等的。","tags":["duplicate","duplicate column","column","column exists","alter","add column","42701"]},{"code":"3D000","title":"invalid_catalog_name","severity":"danger","meaning":"你尝试连接或访问的数据库不存在。找不到给定名称的数据库。","cause":"连接字符串中的数据库名拼写错误；数据库尚未创建或已被删除；大小写敏感；连接到了错误的服务器；环境（env）配置中的名称错误。","fix":"列出现有数据库：psql -l 或 SELECT datname FROM pg_database。更正连接字符串中的数据库名。必要时创建：CREATE DATABASE database_name; 确认连接的是正确的服务器/端口。检查应用配置中的数据库名。","tags":["database","database not found","catalog","does not exist","missing","connection","3D000"]},{"code":"3F000","title":"invalid_schema_name","severity":"caution","meaning":"所引用的 schema 不存在，或在 search_path 中找不到。不存在给定名称的 schema。","cause":"schema 名拼写错误；schema 从未创建；search_path 未包含该 schema；大小写差异；使用 schema 前缀访问对象时前缀错误。","fix":"列出现有 schema：\\dn 或 SELECT schema_name FROM information_schema.schemata。更正 schema 名。必要时创建：CREATE SCHEMA schema_name; 设置 search_path：SET search_path TO schema_name, public; 使用正确的 schema 前缀访问对象。","tags":["schema","schema not found","search_path","does not exist","missing","invalid","3F000"]},{"code":"0A000","title":"feature_not_supported","severity":"caution","meaning":"你尝试使用的功能在此上下文或此 PostgreSQL 版本中不受支持。即使所请求的操作可能有效，也无法在当前情况下应用。","cause":"该版本尚未提供的语法/功能；针对特定列/表类型不支持的 ALTER；外部数据包装器（FDW）不支持的操作；逻辑复制/约束组合的限制；某些数据类型不允许的转换。","fix":"阅读完整的错误消息；通常会明确说明哪些内容不受支持。采用替代方案（如不直接修改列类型，而是新增列 + 复制 + 重命名）。如果该功能在较新版本中提供，考虑升级。如果遇到 FDW/扩展的限制，查阅文档。","tags":["not supported","unsupported","feature","unsupported feature","version","upgrade","0A000"]},{"code":"57P03","title":"cannot_connect_now","severity":"danger","meaning":"服务器当前无法接受连接；通常是数据库仍在启动或正在执行恢复。系统就绪后即可建立连接。","cause":"PostgreSQL 刚刚启动，仍处于启动/恢复阶段；崩溃恢复；正在从备份恢复；备库尚未准备好接受查询；正在关闭。","fix":"稍候片刻后重新连接；启动完成后服务器即可接受连接。在服务器日志中监控恢复进度。如果耗时较长，可能是 WAL 重放量大或存在恢复问题。在应用中添加重试逻辑。","tags":["connection","connection refused","starting up","startup","recovery","crash recovery","57P03"]},{"code":"2BP01","title":"dependent_objects_still_exist","severity":"caution","meaning":"由于其他对象依赖于要删除的对象，删除被阻止。只要依赖对象存在，DROP 就会失败。","cause":"在有视图/外键/函数依赖时尝试删除表；对象依赖于某个类型或 schema；DROP 未带 CASCADE；列/序列被其他对象引用。","fix":"先删除依赖对象，或使用 CASCADE 删除：DROP TABLE my_table CASCADE;（注意 CASCADE 也会删除依赖对象；务必谨慎）。要查看依赖关系，阅读错误详情（DETAIL 行）或检查 pg_depend/相关系统目录。正确规划删除顺序。","tags":["dependency","dependent objects","cascade","drop","objects","object","2BP01"]},{"code":"55006","title":"object_in_use","severity":"danger","meaning":"由于目标对象（通常是数据库）正被其他会话使用，无法对其执行操作。例如，无法删除有活动连接的数据库。","cause":"执行 DROP DATABASE 时数据库仍有打开的连接；需要对正在使用的对象加排他锁的操作；另一会话正持有该对象。","fix":"找到并终止到目标数据库的打开连接：SELECT pg_terminate_backend(pid) FROM pg_stat_activity WHERE datname = 'db_name' AND pid <> pg_backend_pid(); 阻止新连接（必要时）。在 PostgreSQL 13+ 上，可在强制关闭连接的同时删除：DROP DATABASE db_name WITH (FORCE);","tags":["in use","being accessed","object","object in use","drop database","connection","55006"]},{"code":"22007","title":"invalid_datetime_format","severity":"caution","meaning":"将文本值转换为日期/时间类型（date、time、timestamp）时格式无效。该值与预期的日期时间格式不匹配。","cause":"日期文本格式不符合预期（日/月/年顺序混淆）；日/小时值无效；将空字符串（''）转换为日期；缺少或多出部分组成；区域/格式不匹配；to_timestamp/to_date 的格式掩码错误。","fix":"以正确格式转换日期文本：to_date('2026-07-04', 'YYYY-MM-DD') 或 to_timestamp(str, 'YYYY-MM-DD HH24:MI:SS')。优先使用 ISO 8601 格式（YYYY-MM-DD）。用 NULLIF(value,'') 将空值转为 NULL。加载前校验输入数据；逐步缩小范围找出有问题的行。","tags":["date","date format","datetime","format","timestamp","to_date","22007"]}]},"errors/mysql-more.json":{"engineId":"mysql","engine":"MySQL","order":3,"errors":[{"code":"1052","title":"Column 'x' in field list is ambiguous（字段列表中的列 'x' 不明确）","severity":"caution","meaning":"查询中有多个表包含同名列，由于使用时未加表/别名前缀，MySQL 无法判断它属于哪个表。","cause":"连接的两个表都有同名列（如两者都有 id）；在 SELECT、WHERE、ON、GROUP BY 或 ORDER BY 中未限定（未加前缀）地书写该列。","fix":"用表名或别名限定每个不明确的列：SELECT a.id, b.id FROM a JOIN b ON a.id = b.a_id; 为表指定简短的别名，并为所有冲突的列加上前缀。在基于公共列的连接中，用 USING(my_column) 减少歧义。","tags":["ambiguous","ambiguous column","column","column name","join","alias","1052"]},{"code":"1136","title":"Column count doesn't match value count（列数与值数不匹配）","severity":"caution","meaning":"INSERT 语句中的列数与 VALUES 列表中的值数不匹配。每一列必须恰好对应一个值。","cause":"INSERT 中的列列表与值列表长度不同；在没有列列表的 INSERT 中，值的数量与表的列数不符；缺少/多余的逗号；表结构已更改但脚本未更新。","fix":"使列数与值数相等。始终使用显式的列列表：INSERT INTO my_table (a, b, c) VALUES (1, 2, 3); 检查表结构：DESCRIBE my_table; 使值与列一一对应，并修正多余/缺少的逗号。","tags":["column count","column count mismatch","insert","values","mismatch","value count","1136"]},{"code":"1048","title":"Column 'x' cannot be null（列 'x' 不能为 null）","severity":"caution","meaning":"尝试向定义为 NOT NULL 的列插入 NULL，或将该列设为 NULL。必填字段不能为空。","cause":"INSERT 中未为必填列提供值；UPDATE 将该列设为 NULL；该列没有 DEFAULT；应用/子查询传来空值；严格 SQL 模式不会静默填充空值。","fix":"为必填列提供有效值。为该列定义合理的默认值：ALTER TABLE my_table MODIFY my_column INT NOT NULL DEFAULT 0; 如果该规则不必要，允许 NULL：ALTER TABLE my_table MODIFY my_column INT NULL; 在发送前于应用中校验空值。","tags":["not null","null","empty value","insert","cannot be null","mandatory","1048"]},{"code":"1292","title":"Incorrect datetime value / truncated incorrect value（日期时间值不正确 / 截断了不正确的值）","severity":"caution","meaning":"转换为目标列类型（尤其是日期/时间或数值）时，值无效或格式错误。在严格模式下，MySQL 将其作为错误拒绝。","cause":"'0000-00-00' 之类的无效日期或值；日期格式错误；数值列中的非数字文本；启用了严格 SQL 模式（STRICT_TRANS_TABLES）；不同的字符集/排序规则导致的转换问题。","fix":"使值符合列类型的有效格式（日期为 'YYYY-MM-DD'）。用 NULL 代替空/无效日期。必要时用 STR_TO_DATE 指定显式格式：STR_TO_DATE(str, '%Y-%m-%d')。如果需要临时容忍，检查 sql_mode，但最好修正数据。逐步缩小范围找出有问题的行。","tags":["datetime","date","truncated","incorrect value","strict mode","format","1292"]},{"code":"1406","title":"Data too long for column（列的数据过长）","severity":"caution","meaning":"写入的值长度超过了列定义的最大长度。例如，20 个字符的字符串放不进 VARCHAR(10) 列。","cause":"列宽对数据来说过小；多字节字符集（utf8mb4）中的字符占用空间超出预期；拼接结果变长；启用了严格 SQL 模式（否则值会被静默截断）。","fix":"加宽列：ALTER TABLE my_table MODIFY my_column VARCHAR(255); 对于非常长的内容，考虑使用 TEXT 类型。如果必须缩短值，在应用中或用 SUBSTRING 截取。找出溢出的行：SELECT * FROM my_table WHERE CHAR_LENGTH(my_column) > 10; 添加输入校验。","tags":["data too long","length","varchar","column","column length","truncate","1406"]},{"code":"1071","title":"Specified key was too long; max key length is 767 bytes（指定的键过长；最大键长度为 767 字节）","severity":"caution","meaning":"要创建的索引（或键）超出了允许的最大字节长度。尤其在多字节字符集下，VARCHAR 索引很快就会达到此限制。","cause":"在 utf8mb4（每字符 4 字节）的宽 VARCHAR 列上建索引；旧的 InnoDB 行格式（REDUNDANT/COMPACT；在 5.7 及更早版本中还包括禁用的 innodb_large_prefix）；多列复合索引的总长度超过限制；主键过大。","fix":"缩短被索引列的长度或使用前缀索引：CREATE INDEX idx ON my_table (my_column(191)); 使用 DYNAMIC/COMPRESSED 行格式以获得 3072 字节的限制（DYNAMIC 是 MySQL 8.0 的默认值；innodb_large_prefix 已在 8.0 中移除，仅在 5.7 及更早版本中需要）。必要时检查列的字符集/长度。","tags":["index","index length","key too long","key","prefix","utf8mb4","1071"]},{"code":"1050","title":"Table already exists（表已存在）","severity":"caution","meaning":"要创建的表已经存在。由于存在同名表，CREATE TABLE 失败。","cause":"重新创建已有的表；脚本运行了两次；再次应用迁移；另一个对象（视图）使用了相同的名称。","fix":"使用 IF NOT EXISTS，仅在表不存在时创建：CREATE TABLE IF NOT EXISTS my_table (...); 如果确实要重新创建，先删除：DROP TABLE IF EXISTS my_table; 列出现有表：SHOW TABLES; 使迁移可以安全地重复运行（幂等）。","tags":["table exists","table already exists","duplicate","duplicate table","create table","1050"]},{"code":"1051","title":"Unknown table 'x'（未知表 'x'）","severity":"caution","meaning":"所引用的表不存在。在删除、修改或查询时找不到给定名称的表。","cause":"表名拼写错误；表从未创建或已被删除；选择了错误的数据库（schema）；大小写敏感（Linux 上表名区分大小写）；对不存在的表执行 DROP TABLE。","fix":"列出现有表：SHOW TABLES; 并选择正确的数据库：USE mydb; 修正表名及其大小写。为避免删除时报错：DROP TABLE IF EXISTS my_table; 如果位于其他 schema，用 schema.table 限定。","tags":["unknown table","table not found","does not exist","drop","not exists","1051"]},{"code":"1091","title":"Can't DROP 'x'; check that column/key exists（无法 DROP 'x'；请检查该列/键是否存在）","severity":"caution","meaning":"要用 ALTER TABLE 删除的列、索引或外键不存在。无法删除不存在的对象。","cause":"在 DROP COLUMN/INDEX/FOREIGN KEY 中给出了不存在的对象名；名称拼写错误；对象已被删除；将外键名与索引名混淆；迁移第二次运行。","fix":"核实对象的真实名称：SHOW CREATE TABLE my_table; 或 SHOW INDEX FROM my_table; 使用正确的名称删除。对于外键，使用其符号名：ALTER TABLE my_table DROP FOREIGN KEY fk_name; MySQL 没有 DROP COLUMN IF EXISTS 语法（只有 MariaDB 有）；在幂等迁移中，先通过 information_schema.columns 检查列是否存在：SELECT COUNT(*) FROM information_schema.columns WHERE table_schema = DATABASE() AND table_name = 'my_table' AND column_name = 'my_column';","tags":["cant drop","cannot drop","column","index","foreign key","alter","1091"]},{"code":"1093","title":"You can't specify target table 'x' for update in FROM clause（不能在 FROM 子句中指定要更新的目标表 'x'）","severity":"caution","meaning":"在 UPDATE 或 DELETE 语句中，正在更新/删除的表本身不能直接在子查询（FROM 中）中用作源。MySQL 不允许同一张表以这种方式同时作为目标和源。","cause":"子查询直接读取同一目标表，如 UPDATE my_table ... WHERE id IN (SELECT ... FROM my_table ...)；DELETE 中的子查询引用了同一张表。","fix":"将子查询包装在派生表中并赋予新的别名，使 MySQL 将其视为单独的副本：UPDATE my_table SET c = 1 WHERE id IN (SELECT id FROM (SELECT id FROM my_table WHERE condition) AS t); 或者改写为自连接：UPDATE my_table a JOIN my_table b ON ... SET a.c = ...;","tags":["update","delete","target table","same table","subquery","derived table","1093"]},{"code":"1305","title":"PROCEDURE does not exist（PROCEDURE 不存在）","severity":"caution","meaning":"在指定数据库中找不到所调用的存储过程或函数。未定义此名称的例程。","cause":"过程名拼写错误；过程位于错误的数据库中或从未创建；用 USE 选择了错误的 schema；没有调用该过程的 EXECUTE 权限；大小写敏感。","fix":"确认过程是否存在以及位于哪个 schema：SHOW PROCEDURE STATUS WHERE Db = 'mydb'; 选择正确的数据库：USE mydb; 或使用完全限定名调用：CALL mydb.my_procedure(...); 授予所需权限：GRANT EXECUTE ON PROCEDURE mydb.my_procedure TO app_user;","tags":["procedure","routine","does not exist","not found","stored procedure","call","1305"]},{"code":"1227","title":"Access denied; you need (at least one of) the PRIVILEGE（访问被拒绝；需要（至少一项）PRIVILEGE 权限）","severity":"danger","meaning":"用户缺少所尝试操作所需的权限。该操作需要超出账户已授予权限的权利。","cause":"缺少 SUPER、PROCESS、RELOAD 或 CREATE USER 等管理权限；未授予读取全局视图/表所需的权限；特定操作（SET GLOBAL、binlog 访问）需要更高权限。","fix":"查看当前权限：SHOW GRANTS FOR CURRENT_USER(); 用有权限的账户授予所需权限：GRANT PROCESS ON *.* TO 'app_user'@'host';（GRANT 立即生效；不需要 FLUSH PRIVILEGES）。尽可能使用动态权限（如 SYSTEM_VARIABLES_ADMIN）代替 SUPER。遵循最小权限原则。","tags":["access denied","privilege","missing privilege","permission","grant","access","1227"]},{"code":"3730","title":"Cannot drop table referenced by a foreign key constraint（无法删除被外键约束引用的表）","severity":"danger","meaning":"由于另一张表的外键引用了该表，无法将其删除。只要引用存在，DROP 就会被阻止。","cause":"在通过 FK 与之关联的子表仍存在时尝试删除父表；未遵守依赖顺序；未先删除外键。","fix":"先删除引用的外键：ALTER TABLE child_table DROP FOREIGN KEY fk_name; 再删除表。查找依赖的表/约束：SELECT * FROM information_schema.KEY_COLUMN_USAGE WHERE REFERENCED_TABLE_NAME = 'my_table'; 在批量操作中可以临时使用 SET FOREIGN_KEY_CHECKS = 0;，但要谨慎，并在之后将其恢复为 1。","tags":["foreign key","fk","drop table","referenced","reference","constraint","3730"]},{"code":"2013","title":"Lost connection to MySQL server during query（查询期间与 MySQL 服务器的连接丢失）","severity":"danger","meaning":"查询运行期间，客户端与 MySQL 服务器之间的连接断开。在服务器响应或返回结果之前连接就已中断。","cause":"长时间运行的查询超过 net_read_timeout/net_write_timeout；服务器因内存不足（OOM）终止查询/连接；网络中断；超出 max_allowed_packet；服务器崩溃或重启；结果集非常大。","fix":"检查网络稳定性和服务器错误日志；确认是否发生了 OOM 或崩溃。增大超时：SET GLOBAL net_read_timeout = 120; 将大查询拆分为多个批次。增大 max_allowed_packet。在应用中添加连接重试逻辑。优化长查询。","tags":["lost connection","connection dropped","timeout","network","network outage","oom","2013"]},{"code":"1153","title":"Got a packet bigger than 'max_allowed_packet' bytes（收到的数据包大于 'max_allowed_packet' 字节）","severity":"danger","meaning":"客户端或服务器尝试发送大于允许的最大数据包大小（max_allowed_packet）的数据包。通常发生在大型 BLOB/TEXT 值或非常大的批量 INSERT 中。","cause":"插入非常大的 BLOB/TEXT 数据；巨大的单条 INSERT 或多行批量 INSERT；返回大结果的查询；max_allowed_packet 值过小；用 mysqldump 导入大行。","fix":"增大 max_allowed_packet（服务器和客户端两侧）：SET GLOBAL max_allowed_packet = 67108864;（64MB）然后重新连接。要永久生效，在 my.cnf 的 [mysqld] 下添加 max_allowed_packet = 64M。将大型批量操作拆分为更小的批次。评估是否确实需要存储非常大的值。","tags":["max_allowed_packet","packet","packet size","blob","large data","insert","1153"]},{"code":"1969","title":"MariaDB: Query execution was interrupted (max_statement_time exceeded)（MariaDB：查询执行被中断（超出 max_statement_time））","severity":"caution","meaning":"MariaDB 特有（ER_STATEMENT_TIMEOUT）：查询因超过 max_statement_time 定义的最大执行时间而被中断。MySQL 没有此代码；MySQL 中的对应错误是 3024（ER_QUERY_TIMEOUT，max_execution_time）。","cause":"在 MariaDB 中，超出会话/全局 max_statement_time 限制，或超出用 SET STATEMENT max_statement_time=N FOR ... 设置的限制；低效的执行计划、缺少索引、大量数据扫描或锁等待。","fix":"用 EXPLAIN 分析查询，通过添加索引和改进执行计划加快速度。如果查询确实需要长时间运行，在 MariaDB 中提高限制：SET SESSION max_statement_time = 60;（秒）或 SET STATEMENT max_statement_time = 60 FOR SELECT ...; 如果使用 MySQL，请参阅 3024 条目（max_execution_time，毫秒；MAX_EXECUTION_TIME 提示）。拆分大型操作。","tags":["timeout","statement timeout","interrupted","query interrupted","max_execution_time","query","1969"]}]},"errors/mssql-more.json":{"engineId":"mssql","engine":"SQL Server","order":4,"errors":[{"code":"8134","title":"Divide by zero error encountered（遇到以零作除数错误）","severity":"caution","meaning":"由于算术表达式中的除数为零，发生了除零错误。SQL Server 无法完成除法并终止查询。","cause":"用作除数的列或表达式计算结果为 0、平均值/比率计算中分母为零、未使用 NULLIF 的除法，或数据中出现意外的零值。","fix":"用 NULLIF 保护分母（如 value / NULLIF(denominator, 0)）；结果变为 NULL，可用 ISNULL/COALESCE 提供默认值。使用 CASE WHEN denominator = 0 THEN NULL ELSE value/denominator END。在除法前用 WHERE 过滤掉零值。必要时评估 SET ARITHABORT/ANSI_WARNINGS 的行为。","tags":["math","arithmetic","divide by zero","division by zero","8134"]},{"code":"245","title":"Conversion failed when converting the value to data type（将值转换为数据类型时失败）","severity":"caution","meaning":"无法将值转换为目标数据类型。例如，将字符串值转换为 int 或 date 时，因内容不合适导致隐式/显式转换失败。","cause":"将非数字文本转换为 int/decimal、日期格式无效、列比较中的隐式类型转换、WHERE 或 JOIN 条件中类型不兼容，或输入数据格式错误。","fix":"使用安全的转换变体：TRY_CAST 或 TRY_CONVERT（失败时返回 NULL）。用 ISNUMERIC/TRY_CONVERT 识别有问题的行。对齐列类型并消除隐式转换。校验输入，并为日期使用显式格式（如 CONVERT 样式）。","tags":["conversion","type conversion","cast","convert","data type","245"]},{"code":"515","title":"Cannot insert the value NULL into column（不能将值 NULL 插入列）","severity":"caution","meaning":"尝试向定义为 NOT NULL 的列插入或更新 NULL 值。由于该列不允许为空，操作被拒绝。","cause":"INSERT 语句中遗漏了必填列、源数据中字段缺失/为空、NOT NULL 列没有默认值，或应用发送了 NULL。","fix":"为必填列提供有效值。为合适的列添加 DEFAULT 约束。校验源数据中的缺失字段，并用 ISNULL/COALESCE 赋予默认值。如果该列实际上不需要必填，审视 schema 以允许 NULL。","tags":["null","not null","insert","constraint","constraint violation","515"]},{"code":"8115","title":"Arithmetic overflow error converting expression to data type（将表达式转换为数据类型时发生算术溢出错误）","severity":"caution","meaning":"算术运算或转换产生的值超出了目标数据类型能容纳的范围。由于数字过大而发生溢出。","cause":"和/积（SUM）超出 int 范围、将大值赋给类型较小的列、decimal 精度/小数位数不足，或 IDENTITY 值达到类型上限。","fix":"在中间计算中使用更宽的类型（如 SUM 中使用 CAST(my_column AS bigint)）。增大 decimal 精度/小数位数。扩大列类型以容纳数据。在计算前转换有溢出风险的表达式，并限制输入范围。","tags":["overflow","arithmetic overflow","arithmetic","math","int","bigint","8115"]},{"code":"512","title":"Subquery returned more than 1 value（子查询返回的值不止一个）","severity":"caution","meaning":"在需要单个值的上下文（=、<、>、比较或赋值）中使用的子查询返回了多行。SQL Server 预期得到单个标量值，却遇到了多个值。","cause":"本应为比较返回单个值的子查询返回了多行、匹配条件不唯一、缺少过滤条件，或关联查询构造错误。","fix":"将子查询缩减为单行（如 TOP 1、MAX/MIN 或合适的 GROUP BY）。如果预期有多个值，用 IN 或 EXISTS 代替 =。用 JOIN 改写。调查子查询为何返回多行，并修正过滤/键条件。","tags":["subquery","scalar subquery","more than one","512","in","exists"]},{"code":"207","title":"Invalid column name（列名无效）","severity":"caution","meaning":"在表/视图中找不到查询中引用的列名。不存在该名称的列，或其不在作用域内。","cause":"拼写/大小写错误、表别名错误、列尚未添加或已重命名、schema/表上下文错误，或未使用方括号（代替引号）。","fix":"核实列名和表（检查 sys.columns 或 Object Explorer）。使用正确的别名和 schema。对于新添加的列，确保对象是最新的；必要时重新编译存储过程。对包含空格/特殊字符的名称使用方括号。","tags":["column","column name","invalid column","invalid column name","alias","207"]},{"code":"156","title":"Incorrect syntax near the keyword（关键字附近有语法错误）","severity":"caution","meaning":"在某个关键字附近存在语法错误。由于保留字出现在意外的位置，SQL Server 无法解析该语句。","cause":"将保留字（如 ORDER、USER、TABLE）不加方括号用作列/对象名、缺少逗号/括号、子句顺序错误，或子句位置不当（WHERE、GROUP BY）。","fix":"检查错误所指关键字附近的区域。如果将保留字用作对象名，用方括号括起来（如 [User]）。修正缺少的逗号、括号和子句顺序。将查询拆分成多个部分来测试语法。","tags":["syntax","syntax error","keyword","reserved word","156"]},{"code":"2812","title":"Could not find stored procedure（找不到存储过程）","severity":"caution","meaning":"在当前数据库/schema 范围内找不到用 EXEC 调用的存储过程。不存在该名称的过程。","cause":"过程名拼写错误、缺少或错误的 schema 前缀（如省略了 dbo.）、过程位于其他数据库、尚未创建，或因权限原因不可见。","fix":"核实过程名和 schema；在调用中使用完全限定名（如 EXEC dbo.ProcedureName）。确保位于正确的数据库中（USE DatabaseName）。检查过程是否存在（sys.procedures / OBJECT_ID）。授予所需的 EXECUTE 权限，并确认部署脚本已执行。","tags":["stored procedure","procedure","sp","not found","procedure not found","2812"]},{"code":"8623","title":"The query processor ran out of internal resources and could not produce a query plan（查询处理器耗尽了内部资源，无法生成查询计划）","severity":"danger","meaning":"对于过于复杂的查询，查询处理器耗尽了内部资源，无法生成执行计划。由于查询过大/过于复杂，无法对其进行优化。","cause":"包含非常长的 IN 列表或大量 OR/UNION 子句的查询、JOIN 数量过多、自动生成的巨大查询、参数过多的查询，或表达式树过深。","fix":"简化查询：将长 IN 列表移入临时表/表值参数，用 UNION ALL 或改写减少 OR 条件，并将复杂查询拆分成多个部分。减少 JOIN 数量。对自动生成的查询分批处理。必要时审视索引和查询设计。","tags":["query processor","internal resources","plan","complex query","8623","danger"]},{"code":"4104","title":"The multi-part identifier could not be bound（无法绑定由多个部分组成的标识符）","severity":"caution","meaning":"无法解析以点分隔的多部分标识符（如 table.column 或 alias.column）。SQL Server 无法将该名称绑定到查询中的表/列。","cause":"表别名错误或未定义、引用了不在 FROM/JOIN 中的表、列被归属到错误的表、定义别名后仍使用真实表名，或拼写错误。","fix":"确认所用别名已在 FROM/JOIN 子句中定义。用正确的表/别名限定列。如果为表指定了别名，在所有地方都使用该别名（而不是真实名称）。将缺失的表添加到 JOIN 中并检查名称。","tags":["identifier","multi-part","multi-part identifier","bind","alias","4104"]},{"code":"2714","title":"There is already an object named in the database（数据库中已存在名为 ... 的对象）","severity":"caution","meaning":"数据库中已存在与要创建的对象（表、视图、过程等）同名的对象。CREATE 语句因名称冲突而失败。","cause":"第二次运行相同的 CREATE 脚本、未先删除现有对象、对 schema 的假设不同、部署/迁移脚本非幂等，或命名冲突。","fix":"先检查对象是否存在：用 IF OBJECT_ID('dbo.Name') IS NULL 有条件地创建，或使用 CREATE OR ALTER（适用于过程/视图/函数）。必要时 DROP 现有对象后重新创建。将迁移脚本编写为幂等的。确认在正确的 schema/数据库中操作。","tags":["already exists","object exists","create","object","duplicate object","2714"]},{"code":"601","title":"Could not continue scan with NOLOCK due to data movement（由于数据移动，无法继续以 NOLOCK 方式扫描）","severity":"caution","meaning":"在以 NOLOCK（READ UNCOMMITTED）方式扫描期间，由于并发的数据移动（页拆分/移动），扫描无法继续。无锁读取遇到了不一致的状态。","cause":"在使用 NOLOCK 提示或 READ UNCOMMITTED 隔离级别读取时，被扫描的页因并发的更新/删除/页拆分而被移动。","fix":"重新运行查询（通常是瞬时的）。尽量避免使用 NOLOCK 提示；使用 READ COMMITTED SNAPSHOT（RCSI）隔离实现非阻塞且一致的读取。在写密集型表上，优先使用快照隔离而非脏读。","tags":["nolock","scan","data movement","601","isolation","read uncommitted"]},{"code":"50000","title":"用 RAISERROR 引发的自定义错误","severity":"caution","meaning":"这是用 RAISERROR 引发自定义（用户定义）错误且未指定消息 ID 时的默认编号（50000）。应用/过程逻辑有意引发了错误。","cause":"在存储过程或脚本中，因违反业务规则、校验失败、有意的流程控制或自定义异常消息，用 RAISERROR 或 THROW 引发的自定义错误。","fix":"阅读 RAISERROR/THROW 的消息文本，以了解实际违反的业务规则。在应用端用 TRY...CATCH 捕获该错误，并通过 ERROR_MESSAGE()/ERROR_NUMBER() 获取详情。用 sp_addmessage 定义消息，为自定义错误赋予有意义的编号。在业务逻辑中修正触发条件。","tags":["raiserror","custom error","user-defined error","throw","50000","try catch"]},{"code":"3902","title":"The COMMIT TRANSACTION request has no corresponding BEGIN TRANSACTION（COMMIT TRANSACTION 请求没有对应的 BEGIN TRANSACTION）","severity":"caution","meaning":"执行了 COMMIT TRANSACTION，但没有对应的已打开的 BEGIN TRANSACTION。由于事务计数（@@TRANCOUNT）为零，找不到可提交的事务。","cause":"BEGIN/COMMIT/ROLLBACK 使用不平衡、事务已在出错后回滚、嵌套事务计数管理不当，或在 CATCH 块中 ROLLBACK 之后再次调用 COMMIT。","fix":"使 BEGIN 与 COMMIT 语句的数量平衡。提交前检查 @@TRANCOUNT（IF @@TRANCOUNT > 0 COMMIT）。在 TRY...CATCH 中，出错时根据 XACT_STATE()/@@TRANCOUNT 执行 ROLLBACK，并避免不必要的 COMMIT。审查嵌套事务逻辑。","tags":["transaction","commit","begin","trancount","transaction count","3902"]}]},"errors/mongodb-more.json":{"engineId":"mongodb","engine":"MongoDB","order":5,"errors":[{"code":"121","title":"DocumentValidationFailure","severity":"caution","meaning":"要插入或更新的文档因不满足集合上定义的 JSON Schema 校验规则（validator）而被拒绝。","cause":"文档不符合 schema：缺少必填字段、BSON 类型错误、值超出允许的范围/模式、禁止附加字段，或在 validationAction 为 'error' 时违反规则。","fix":"检查错误详情（errInfo）以了解违反了哪条规则。使文档符合 schema：添加必填字段并修正类型。用 collMod 检查集合的 validator。可以临时放宽 validationLevel/validationAction，但为保证数据质量，最好保留这些规则。","tags":["validation","validation","schema","schema","121","document"]},{"code":"40","title":"ConflictingUpdateOperators","severity":"caution","meaning":"一次更新对同一字段（路径）应用了多个相互冲突的更新运算符。MongoDB 无法在单个操作中两次修改同一路径。","cause":"一个更新文档中有多个针对同一字段的冲突运算符（如同时使用 $set 和 $inc，或同时修改父路径和子路径）、更新构造不当，或嵌套路径冲突。","fix":"将同一字段上的冲突运算符合并为一个运算符，或将更新拆分为多个独立操作。不要同时修改父路径和子路径（如 a 与 a.b）。检查更新文档，确保每个字段只被修改一次。","tags":["update","update","operator","conflict","conflict","40"]},{"code":"66","title":"ImmutableField（无法更新 _id）","severity":"caution","meaning":"尝试更新不可变字段，通常是文档的 _id 字段。文档创建后 _id 无法更改。","cause":"更新尝试修改 _id 字段、以不允许的方式更新分片键字段，或将被视为不可变的字段设为新值。","fix":"不要更新 _id 值；如果需要不同的 _id，用新的 _id 插入文档并删除旧文档（insert + delete）。分片键更新遵循 MongoDB 的规则。从更新文档中移除不可变字段，只设置可修改的字段。","tags":["immutable","_id","immutable","update","shard key","66"]},{"code":"2","title":"BadValue","severity":"caution","meaning":"向命令或查询传入了无效或意外的值。MongoDB 无法将该参数解释为可接受的值。","cause":"无效的运算符参数、超出范围的数字、不支持的选项值、格式错误的排序规范、格式错误的查询表达式，或不恰当的参数组合。","fix":"根据错误消息中的字段/值提示修正参数。对照文档校验命令和查询选项。检查 sort/projection/aggregate 表达式的格式。将值的范围和类型调整到有效范围内。","tags":["badvalue","invalid value","argument","2","query"]},{"code":"14","title":"TypeMismatch","severity":"caution","meaning":"所提供值的类型与预期的 BSON 类型不匹配。字段或参数需要的是另一种数据类型。","cause":"在需要数字的地方提供了文本、用字符串代替日期、在需要数组的地方发送了单个值、向聚合/查询运算符传入了错误类型的参数，或 schema 中类型不一致。","fix":"为字段/参数提供正确的 BSON 类型。在保存数据前于应用层转换类型（如将数字写为真正的 Number 值）。核实查询和聚合运算符所期望的类型。用 schema validator 保证类型一致性。","tags":["typemismatch","type mismatch","bson","type","14"]},{"code":"10334","title":"BSONObjectTooLarge","severity":"danger","meaning":"BSON 文档超出了允许的最大大小（16 MB）。MongoDB 无法存储或返回超过此限制的单个文档。","cause":"不断向文档追加数据/数组元素而超出 16 MB 限制、非常大的内嵌数组、聚合生成的巨大文档，或直接存储在文档中的大型二进制数据。","fix":"审视数据模型：将大型数组拆分到单独的集合中，并采用引用模型。大型二进制文件使用 GridFS。为无界数组设置边界（如 capped/bucket 模式）。在聚合阶段控制文档大小。","tags":["bson","16mb","too large","large document","gridfs","10334","danger"]},{"code":"46","title":"LockBusy","severity":"caution","meaning":"所请求的锁或资源被其他操作持有而无法立即获取，操作未等待而是被直接拒绝。以超时告终的锁等待是另一个代码（24 LockTimeout）。","cause":"对同一集合或数据库的并发管理/DDL 操作（如同时进行两个索引构建、collMod、drop 或 rename）、与正在进行的维护操作冲突，或某个连短暂等待锁都不能的内部操作遇到了繁忙的资源。","fix":"找出冲突的操作：用 db.currentOp() 检查在同一命名空间上运行的 DDL 或维护操作。在冲突操作完成后重试命令。将管理/DDL 操作串行化，不要在同一集合上并行运行。如果问题持续，在 mongod 日志中调查持有该资源的操作。","tags":["lock","lock","lockbusy","busy","ddl","46"]},{"code":"96","title":"OperationFailed","severity":"danger","meaning":"通用的操作失败；命令运行期间发生意外错误，操作无法完成。这是在没有更具体代码时使用的通用错误。","cause":"服务器端内部错误、资源耗尽、状态不一致、子系统故障（存储、复制），或特定命令特有的各种失败情况。","fix":"检查完整的错误消息文本和服务器日志（mongod.log）以确定根本原因。重试操作；该情况可能是瞬时的。检查资源（磁盘、内存）和副本集健康状况。如果问题反复出现，检查命令及其输入数据。","tags":["operationfailed","operation failed","generic error","96","danger"]},{"code":"72","title":"InvalidOptions","severity":"caution","meaning":"向命令传入了无效或相互不兼容的选项。该命令不支持指定的选项组合。","cause":"不支持的选项名、同时使用了互斥的选项、对当前版本无效的选项、无效的 write concern/read concern 组合，或索引/collation 选项冲突。","fix":"在文档中核实命令接受的选项，并删除不兼容的选项。确保你的版本支持该选项。检查 write/read concern 和 collation 设置。只使用有效的选项组合。","tags":["invalidoptions","invalid option","options","72","command"]},{"code":"9","title":"FailedToParse","severity":"caution","meaning":"服务器无法解析提交的命令、查询或表达式。其结构在语法上无效或不符合预期格式。","cause":"JSON/BSON 结构格式错误、缺少或多余的字段、运算符使用不正确、聚合管道语法无效，或命令文档不符合预期格式。","fix":"根据文档修正命令/查询/管道的结构。校验 JSON 结构（缺少括号/逗号、字段名错误）。检查聚合阶段的语法。确保运算符使用正确，并以预期格式提供参数。","tags":["failedtoparse","parse","parsing","syntax","9","query"]},{"code":"134","title":"ReadConcernMajorityNotAvailableYet","severity":"caution","meaning":"请求了 majority read concern 的读取，但 majority 已提交的快照尚不可用。服务器当前无法提供该一致性级别。","cause":"副本集刚刚启动或不稳定、majority 写入尚未提交、复制延迟、可达成员不足，或集群仍处于启动阶段。","fix":"稍候片刻后重试；majority 快照可用后读取即可成功。检查副本集健康状况和复制延迟（rs.status()）。确保有足够的成员处于运行状态。如果并非必需，考虑使用较低的 read concern（如 local）。","tags":["readconcern","majority","majority","replica","134","consistency"]},{"code":"202","title":"NetworkInterfaceExceededTimeLimit","severity":"danger","meaning":"网络请求（如成员间通信或客户端连接）超出了时间限制。由于网络超时，操作无法完成。","cause":"网络延迟/中断、目标成员缓慢或不可达、负载过高、超时值配置不当、DNS/连接问题，或成员之间地理距离较远。","fix":"检查网络连通性和延迟；确认成员之间可以互相访问。检查目标成员的健康状况和负载。根据你的环境配置超时设置（connect/socket timeout）。添加重试逻辑。如果问题持续，检查基础设施/网络配置。","tags":["network","network","timeout","timeout","202","danger"]}]},"errors/redis-more.json":{"engineId":"redis","engine":"Redis","order":6,"errors":[{"code":"NOPERM","title":"NOPERM this user has no permissions to run the command or access one of the keys（该用户无权运行此命令或访问其中某个键）","severity":"caution","meaning":"已认证的 ACL 用户对要运行的命令或访问的键没有授权。Redis 根据 ACL 规则拒绝该操作。","cause":"ACL 用户缺少命令/类别权限、键模式不在其允许列表中、存在频道（pub/sub）限制，或用户被授予了范围很窄的权限。","fix":"检查用户权限：ACL GETUSER app_user。添加所需的命令/类别和键模式（如 ACL SETUSER app_user +get +set ~app:*）。遵循最小权限原则，只授予所需权限。确认以正确的用户身份连接。","tags":["noperm","acl","privilege","permission denied","permission","user"]},{"code":"ERR value is not an integer or out of range","title":"ERR value is not an integer or out of range（值不是整数或超出范围）","severity":"caution","meaning":"需要整数的命令（如 INCR、EXPIRE、LPOP count）收到了非数字或超出范围的值。Redis 无法将该值解释为整数。","cause":"INCR/DECR 针对的键持有文本（非整数）值、参数以文本而非数字形式给出、数字相对范围过大/过小，或在不允许的地方使用了负值。","fix":"检查键的值：GET mykey 和 TYPE mykey。对于数值操作，确保键持有整数值。确认参数是在正确范围内的有效整数。必要时重置该值（SET mykey 0）或以正确类型重新创建。","tags":["err","integer","not an integer","range","incr","value"]},{"code":"ERR value is not a valid float","title":"ERR value is not a valid float（值不是有效的浮点数）","severity":"caution","meaning":"需要小数（float）的命令（如 INCRBYFLOAT、ZADD 的 score）收到了无效的浮点数值。Redis 无法将该值解析为 float。","cause":"INCRBYFLOAT 针对的键未持有有效的 float、参数不是数字、小数分隔符错误（需要使用点而非逗号），或数字格式错误。","fix":"用 GET 检查键的值，确保其包含有效的 float。使用点作为小数分隔符（如 3.14）。确认参数是数字且格式正确。必要时将该值重置为有效的 float。","tags":["err","float","decimal","incrbyfloat","zadd","value"]},{"code":"ERR increment or decrement would overflow","title":"ERR increment or decrement would overflow（递增或递减将导致溢出）","severity":"caution","meaning":"由于结果将溢出 64 位有符号整数范围，INCR/DECR（或 INCRBY/DECRBY）被拒绝。该值将超出上限或下限。","cause":"键的值非常接近 int64 上限（约 922 京），使递增/递减发生溢出、INCRBY 参数非常大，或计数器失控增长。","fix":"将计数器值设计为保持在 int64 范围内。对于大规模计数，采用缩放或定期重置。将增量（INCRBY 参数）保持在合理范围内。如果需要非常大的值，在应用层采用其他计数策略。","tags":["err","overflow","integer overflow","incr","decr","int64"]},{"code":"ERR max number of clients reached","title":"ERR max number of clients reached（已达到最大客户端数）","severity":"danger","meaning":"已连接的客户端数达到 maxclients 限制，无法接受新连接。Redis 拒绝更多的客户端连接。","cause":"连接泄漏（连接从未关闭）、maxclients 值不足、连接池配置不当、客户端数量突然激增，或操作系统文件描述符限制过低。","fix":"检查当前连接：CLIENT LIST 和 INFO clients。修复连接泄漏；正确设置客户端连接池大小并复用连接。必要时提高 maxclients（CONFIG SET maxclients）和操作系统 ulimit（文件描述符）限制。为空闲连接设置 timeout。","tags":["err","maxclients","connection","client","limit","danger"]},{"code":"BUSYKEY","title":"BUSYKEY Target key name already exists（目标键名已存在）","severity":"caution","meaning":"由于目标键已存在，RESTORE（或类似命令）失败。默认情况下 Redis 不会覆盖已有的键。","cause":"要用 RESTORE 恢复的键名在数据库中已存在、之前的数据未清理，或多次尝试恢复到同一个键。","fix":"如果要覆盖已有键，在 RESTORE 中添加 REPLACE 选项。或者先删除目标键（DEL mykey），或使用不同的键名。事先用 EXISTS mykey 检查是否存在。","tags":["busykey","restore","already exists","target key","replace","key"]},{"code":"ERR no such key","title":"ERR no such key（键不存在）","severity":"caution","meaning":"在数据库中找不到要操作的键。RENAME、DUMP 和 MOVE 等需要已有键的命令无法作用于不存在的键。","cause":"键从未创建、因 TTL 到期被删除、键名/拼写错误、数据库（SELECT）索引错误，或键已被 DEL 删除。","fix":"检查键是否存在：EXISTS mykey。确保使用正确的键名和正确的数据库（SELECT db）。RENAME 要求源键存在。检查通过 TTL 过期的键的相关逻辑。必要时重新创建该键。","tags":["err","no such key","key not found","rename","dump","key"]},{"code":"ERR DUMP payload version or checksum are wrong","title":"ERR DUMP payload version or checksum are wrong（DUMP 负载的版本或校验和错误）","severity":"caution","meaning":"传给 RESTORE 的序列化数据（DUMP 输出）无效；版本不兼容或校验和验证失败。Redis 无法安全地恢复该数据。","cause":"DUMP 数据已损坏/被修改、在不兼容的不同 Redis 版本之间迁移、编码/解码过程中损坏（如字节处理错误），或负载缺失/被截断。","fix":"确保 DUMP 数据完整传输（二进制安全）；以原始字节而非文本编码方式传输。确认源和目标 Redis 版本兼容。重新执行 DUMP 并再次传输。迁移时考虑使用 MIGRATE 命令代替 RESTORE。","tags":["err","dump","restore","checksum","checksum mismatch","version"]},{"code":"NOREPLICAS","title":"NOREPLICAS Not enough good replicas to write（没有足够的正常副本可供写入）","severity":"danger","meaning":"由于 min-replicas-to-write 设置，在健康副本不足时写入会被拒绝。Redis 为保证数据安全而阻止写入。","cause":"已连接/数据最新的副本不足、副本崩溃、复制延迟超过 min-replicas-max-lag 限制、网络问题，或故障转移后缺少副本。","fix":"检查副本状态：INFO replication。恢复宕机的副本并减少复制延迟。修复网络连通性。根据你的环境检查 min-replicas-to-write / min-replicas-max-lag 设置（在持久性与可用性之间取得平衡）。提供足够数量的副本。","tags":["noreplicas","replica","write","min-replicas","replication","danger"]},{"code":"ERR This instance has cluster support disabled","title":"ERR This instance has cluster support disabled（此实例已禁用集群支持）","severity":"caution","meaning":"运行了集群命令（CLUSTER ...），但该 Redis 实例未以集群模式启动。由于集群支持已禁用，命令被拒绝。","cause":"未设置 cluster-enabled yes、实例以 standalone 模式运行、向 standalone 服务器发送了集群命令，或连接到了错误的服务器。","fix":"如果要使用集群功能，以集群模式启动实例：在配置中设置 cluster-enabled yes，重启，并用各节点组建集群。如果保持 standalone 模式，不要使用集群命令。确认连接的是正确的（集群）服务器。","tags":["err","cluster","cluster mode","disabled","standalone","config"]},{"code":"ERR unknown subcommand","title":"ERR unknown subcommand or wrong number of arguments（未知子命令或参数数量错误）","severity":"caution","meaning":"容器命令（如 CONFIG、CLIENT、ACL、XINFO）收到了无效的子命令，或子命令的参数数量错误。Redis 无法识别该子命令。","cause":"子命令名拼写错误、所用 Redis 版本中没有该子命令、参数数量错误/缺失，或子命令语法不正确。","fix":"使用正确的子命令名和参数；参考该命令的 HELP 子命令（如 CONFIG HELP、CLIENT HELP）。确认你的 Redis 版本支持该子命令。根据文档修正参数数量和语法。","tags":["err","subcommand","unknown subcommand","argument","config","client"]},{"code":"ERR Client sent AUTH, but no password is set","title":"ERR AUTH <password> called without any password configured for the default user (Redis 6+) / ERR Client sent AUTH, but no password is set (Redis <6)（调用了 AUTH，但服务器未配置密码）","severity":"caution","meaning":"客户端尝试用 AUTH 认证，但服务器上未定义密码（requirepass）或 ACL 密码。由于没有可供验证的凭据，AUTH 被拒绝。这种情况下 Redis 6+ 返回 \"ERR AUTH <password> called without any password configured for the default user. Are you sure your configuration is correct?\"；\"Client sent AUTH, but no password is set\" 是 Redis 6 之前的消息。","cause":"服务器上 requirepass 为空、客户端不必要地发送了 AUTH、连接字符串中定义了密码而服务器没有设置密码，或配置不一致。","fix":"如果服务器不需要密码，从客户端移除 AUTH 命令/密码设置。如果需要密码保护，在服务器上设置 requirepass（CONFIG SET requirepass password），或为 ACL 用户定义密码。使客户端与服务器的配置保持一致。","tags":["err","auth","password","requirepass","authentication","acl"]}]},"errors/elasticsearch-more.json":{"engineId":"elasticsearch","engine":"Elasticsearch","order":7,"errors":[{"code":"action_request_validation_exception","title":"action_request_validation_exception","severity":"caution","meaning":"请求在执行前未通过校验。缺少必填字段或字段无效，因此 Elasticsearch 拒绝该操作。","cause":"缺少必需参数（如未指定索引名）、请求体为空、字段组合无效，或在 bulk/index 请求中未提供必需信息。","fix":"查看错误消息中缺失/无效字段的列表，并提供必需字段。根据 API 文档补全请求体和参数。在 bulk 请求中，确认每个 action 行都有正确的元数据和请求体。","tags":["validation","validation error","request","request error","action"]},{"code":"index_closed_exception","title":"index_closed_exception","severity":"caution","meaning":"目标索引已关闭。已关闭的索引不响应读/写请求，因此无法执行该操作。","cause":"索引之前已通过 close API 关闭、在维护/ILM 操作期间被关闭，或为节省资源而被停用。","fix":"重新打开索引：POST /index_name/_open。打开后，等待分片分配完成（green/yellow）。调查索引被关闭的原因（ILM 策略、手动操作）。重新审视让很少访问的索引保持关闭的策略。","tags":["index closed","closed index","open","index","closed"]},{"code":"unavailable_shards_exception","title":"unavailable_shards_exception","severity":"danger","meaning":"操作所需的一个或多个分片不可用，因此请求（通常是写入）无法完成。找不到足够的活动分片副本。","cause":"节点崩溃/离开导致分片未分配、超出磁盘水位线、缺少副本、未满足 wait_for_active_shards 条件，或集群健康状态为 red。","fix":"检查集群和分片健康状况：GET _cluster/health 和 GET _cat/shards?v。调查分片未分配的原因：GET _cluster/allocation/explain。恢复宕机的节点、释放磁盘空间（水位线），并检查副本设置。必要时合理设置 wait_for_active_shards。","tags":["shard","unavailable","inaccessible","cluster","allocation","danger"]},{"code":"query_shard_exception","title":"query_shard_exception","severity":"caution","meaning":"查询在某个特定分片上执行时失败。通常是因为查询与 mapping 不兼容或结构无效。","cause":"查询不存在的字段或类型错误的字段、尝试在 text 字段上聚合/排序（fielddata 已禁用）、查询语法无效、分析方式错误，或字段类型不兼容。","fix":"查看错误详情中的字段/原因。检查被查询字段的 mapping：GET /index/_mapping。排序/聚合时使用 keyword 子字段（field.keyword）而不是 text。修正查询语法，并确保字段类型与查询兼容。","tags":["query","query error","shard","mapping","fielddata","query shard"]},{"code":"script_exception","title":"script_exception","severity":"caution","meaning":"编译或执行脚本（通常为 Painless）时发生错误。脚本编写有误，或在运行时抛出了异常。","cause":"Painless 语法错误、访问 null 值、类型不匹配、使用了不存在的字段/变量、超出脚本编译限制，或调用了不允许的 API。","fix":"查看错误消息中的编译/运行时详情。修正脚本语法和字段访问；添加 null 检查（如 doc['field'].size() > 0）。将常用脚本参数化（stored/params），以免超出编译限制。使用正确的字段名和类型。","tags":["script","painless","compilation","runtime","script exception"]},{"code":"number_format_exception","title":"number_format_exception","severity":"caution","meaning":"本应为数值的值无法转换为数字。在字段或参数需要数字的地方提供了无效/非数字的字符串。","cause":"向数值（integer/long/double）字段发送了非数字文本、尝试将空字符串转换为数字、小数格式错误，或输入数据与 mapping 不兼容。","fix":"向该字段发送有效的数值。在索引前校验/转换源数据。空值使用 null 或省略该字段。检查 mapping 中的 coerce 设置和字段类型。使用句点作为小数分隔符。","tags":["number format","number","conversion","numeric","format"]},{"code":"connect_transport_exception","title":"connect_transport_exception","severity":"danger","meaning":"无法通过 transport（9300）层连接到节点。无法建立节点间或客户端与集群之间的通信，因此无法执行操作。","cause":"目标节点宕机/不可达、网络/防火墙问题、transport 地址/端口错误、TLS 配置不一致、DNS 问题，或集群名称/版本不匹配。","fix":"确认目标节点正在运行，且 transport 端口（默认 9300）可通过网络访问。检查防火墙/网络规则和 discovery 配置。确保所有节点的 TLS/证书设置一致。查看节点日志，并核实 cluster.name/版本兼容性。","tags":["transport","connection","connect","network","node","danger"]},{"code":"repository_missing_exception","title":"repository_missing_exception","severity":"caution","meaning":"找不到 snapshot/restore 操作中引用的快照仓库。没有以该名称注册的仓库。","cause":"仓库从未注册、名称拼写错误、仓库已被删除、仓库定义在其他集群上，或集群状态重置后未重新注册。","fix":"列出已注册的仓库：GET _snapshot/_all。用正确的名称和设置重新注册仓库：PUT _snapshot/repo_name（附带 type 和 settings）。确保仓库名拼写正确。确认所有节点都能访问共享仓库（fs/s3 等）。","tags":["repository","snapshot repository","snapshot","missing","backup","restore"]},{"code":"concurrent_snapshot_execution_exception","title":"concurrent_snapshot_execution_exception","severity":"caution","meaning":"由于同时有一个冲突的快照操作正在运行，新的 snapshot/restore 请求被拒绝。仓库或索引上仍有另一个快照操作正在进行。","cause":"之前的快照仍在进行中、影响同一索引的并发 snapshot/restore 冲突、SLM（快照生命周期）策略与手动快照冲突，或以很短的间隔触发。","fix":"检查正在运行的快照：GET _snapshot/repo/_current 和 GET _snapshot/_status。等待当前操作完成，必要时取消：DELETE _snapshot/repo/snapshot_name。合理安排快照时间，避免重叠。将手动操作与 SLM 相协调。","tags":["snapshot","concurrent","conflict","simultaneous","slm","backup"]},{"code":"invalid_index_name_exception","title":"invalid_index_name_exception","severity":"caution","meaning":"给定的索引名不符合 Elasticsearch 命名规则。名称中包含无效字符或违反格式规则。","cause":"包含大写字母、不允许的字符（\\ / * ? \" < > | 空格 , #）、名称以 _、- 或 + 开头、名称过长（超过 255 字节），或使用 . / ... 等保留名称。（以点开头的名称并未被禁止，但保留给隐藏/系统索引使用，会产生警告。）","fix":"使索引名符合规则：只使用小写字母；避免禁用字符；名称不要以 _、- 或 + 开头，除系统/隐藏索引外不要使用以点开头的名称。名称长度保持在 255 字节以内。在应用中生成索引名时强制执行这些规则。","tags":["index name","index naming","invalid","invalid name","naming"]},{"code":"resource_not_found_exception","title":"resource_not_found_exception","severity":"caution","meaning":"找不到所请求的资源（文档、索引、任务、pipeline、模型等）。给定的 ID/名称不对应任何资源。","cause":"文档 ID 不存在、索引/pipeline 已被删除或从未创建、名称拼写错误、任务尚未完成或已被清理，或请求发送到了错误的集群。","fix":"核实资源名称/ID 以及资源是否存在（如 GET /index/_doc/id、GET _ingest/pipeline/name）。确保将请求发送到正确的索引和集群。检查删除/创建操作的顺序。对于任务，用 _tasks API 检查状态。","tags":["not found","resource not found","resource","missing resource","404"]},{"code":"process_cluster_event_timeout_exception","title":"process_cluster_event_timeout_exception","severity":"danger","meaning":"集群状态更新（如创建索引、修改 mapping）未在规定时间内完成而超时。master 节点无法及时处理该更新。","cause":"master 节点负载过重、集群状态非常大/复杂、频繁且密集的 mapping/template 变更、master 缓慢，或 master 选举问题。","fix":"检查 master 节点的负载和健康状况：GET _cluster/health、GET _cat/master，并用 GET _cluster/pending_tasks 查看待处理任务。缩小集群状态（减少不必要的索引/mapping/template，防止动态 mapping 爆炸）。在低流量时段分批执行类似 DDL 的繁重操作。必要时增加 master 资源。","tags":["cluster event","timeout","timed out","master","cluster state","danger"]}]},"errors/oracle-more2.json":{"engineId":"oracle","engine":"Oracle","order":1,"errors":[{"code":"ORA-00955","title":"name is already used by an existing object（名称已由现有对象使用）","severity":"caution","meaning":"你要创建的对象（表、索引、视图、序列、同义词等）的名称已被同一 schema 中的另一个对象使用。在 Oracle 中，对象名在 schema 内唯一，即使是表、视图和序列等不同类型的对象也共享同一命名空间。","cause":"CREATE 语句中选择的名称与现有对象冲突。常见陷阱：尝试创建与表同名的序列或视图；或自动创建的索引名（如主键索引）与你指定的名称冲突。","fix":"选择其他名称或删除冲突的对象。要找出使用该名称的对象：SELECT object_type, object_name FROM user_objects WHERE object_name = 'YOUR_NAME'; 如果确实要替换，DROP 旧对象（先备份其数据）。CREATE OR REPLACE 适用于视图、同义词、类型和 PL/SQL 对象（过程、函数、包、触发器）；不适用于表、索引和序列。","tags":["ddl","create","name","conflict","duplicate","object"]},{"code":"ORA-02449","title":"unique/primary keys in table referenced by foreign keys（表中的唯一/主键被外键引用）","severity":"caution","meaning":"你要删除的表的主键或唯一键被其他表中的外键引用。Oracle 不允许在未先移除这些依赖关系的情况下删除该表。","cause":"在其他表通过 FK 引用此表时，执行了不带 CASCADE CONSTRAINTS 的 DROP TABLE。为保证引用完整性，Oracle 会阻止产生孤立的外键。","fix":"连同依赖的外键一起删除：DROP TABLE table_name CASCADE CONSTRAINTS; 这也会移除其他表中相关的 FK 约束（不会删除这些表中的数据）。要查看哪些约束依赖于它：SELECT table_name, constraint_name FROM all_constraints WHERE r_constraint_name IN (SELECT constraint_name FROM user_constraints WHERE table_name = 'TABLE_NAME');","tags":["drop","foreign key","foreign key reference","constraint","cascade","reference"]},{"code":"ORA-01950","title":"no privileges on tablespace 'name'（对表空间 'name' 无权限）","severity":"danger","meaning":"用户在其要写入的表空间上没有配额。在 Oracle 中，用户必须在表空间上拥有配额才能为对象分配空间。","cause":"未授予用户该表空间上的配额；通常是没有 UNLIMITED TABLESPACE 系统权限的新用户尝试在其默认表空间中创建表/索引或插入数据。出现在 INSERT、CREATE TABLE 或索引增长期间。","fix":"以 DBA 身份为用户授予配额：ALTER USER app_user QUOTA UNLIMITED ON tablespace_name; 或指定限额：ALTER USER app_user QUOTA 500M ON users; 也可以（谨慎使用）授予 GRANT UNLIMITED TABLESPACE TO app_user;，但这会赋予对所有表空间的无限权利，一般不推荐。","tags":["tablespace","quota","tablespace quota","privilege","privilege","insert"]},{"code":"ORA-01110","title":"data file N: 'path'（数据文件 N：'path'）","severity":"danger","meaning":"一条补充（次要）消息，指出给定编号和路径的数据文件存在问题。它从不单独出现；前面总有另一条报告实际问题的 ORA 错误，这一行只说明受影响的是哪个数据文件。","cause":"通常伴随某个主错误出现，例如数据文件脱机（ORA-00376）、需要介质恢复（ORA-01113）、块损坏（ORA-01578）或文件无法读取。磁盘故障、误删除、文件被移动或从备份还原不完整都可能触发。","fix":"阅读此行之前的实际 ORA 错误并加以解决。检查受影响的文件：SELECT file#, name, status FROM v$datafile; 如需恢复：RECOVER DATAFILE 'path'; 然后 ALTER DATABASE DATAFILE 'path' ONLINE; 如果文件已物理丢失，用 RMAN 还原并恢复：RESTORE DATAFILE N; RECOVER DATAFILE N;","tags":["datafile","data file","recovery","media recovery","offline","rman"]},{"code":"ORA-16038","title":"log N sequence# M cannot be archived（日志 N 序列号 M 无法归档）","severity":"danger","meaning":"Oracle 无法归档某个联机重做日志文件，因此无法重用该日志组。在 ARCHIVELOG 模式下归档卡住时，数据库可能停止接受新事务并挂起。","cause":"通常是归档日志目标或快速恢复区（FRA）已满；磁盘空间耗尽；归档目标不可达；或存在权限/挂载问题。常与 ORA-19809（limit exceeded for recovery files）或 ORA-00312 同时出现。","fix":"释放归档目标/FRA 的空间：用 RMAN 将旧归档日志备份到 FRA 之外的目标后删除（DELETE ARCHIVELOG ALL COMPLETED BEFORE ...; 使用 Data Guard 时，不要删除尚未传送/应用到备库的归档日志），或增大 db_recovery_file_dest_size。检查目标状态：ARCHIVE LOG LIST; 以及 SELECT dest_name, status, error FROM v$archive_dest WHERE status != 'INACTIVE'; 问题解决后，归档会从中断处继续。","tags":["archivelog","archive","redo","fra","recovery area","archiver stuck"]},{"code":"ORA-19809","title":"limit exceeded for recovery files（恢复文件超出限制）","severity":"danger","meaning":"分配给快速恢复区（FRA）的空间（db_recovery_file_dest_size）已满。归档日志、闪回日志和备份没有剩余空间，Oracle 无法创建新的恢复文件，数据库可能挂起。","cause":"FRA 中的归档日志未定期备份和清理而不断累积；闪回日志用量增长；或分配的大小不足以支撑实际负载。常与 ORA-16038 等归档错误同时出现。","fix":"查看 FRA 使用情况：SELECT * FROM v$recovery_file_dest; 以及 SELECT * FROM v$flash_recovery_area_usage; 用 RMAN 删除不需要的归档日志：DELETE ARCHIVELOG ALL BACKED UP 1 TIMES TO DISK;（使用 Data Guard 时，先确认已在备库应用；建议设置 CONFIGURE ARCHIVELOG DELETION POLICY TO APPLIED ON ALL STANDBY），或作为永久方案增大空间：ALTER SYSTEM SET db_recovery_file_dest_size = 50G SCOPE=BOTH; 设置定期备份归档日志的 RMAN 作业。","tags":["fra","recovery area","db_recovery_file_dest_size","archivelog","space","rman"]},{"code":"ORA-00376","title":"file N cannot be read at this time（文件 N 当前无法读取）","severity":"danger","meaning":"给定编号的数据文件因脱机或不可访问而暂时无法读取。访问受影响表空间中数据的查询和操作都会以此错误失败。","cause":"数据文件在出错后被自动脱机或被手动脱机；正在等待介质恢复；磁盘不可访问；或在 NOARCHIVELOG 模式下无法恢复的文件一直处于脱机状态。通常伴随一行标明该文件的 ORA-01110。","fix":"查看文件状态：SELECT file#, name, status FROM v$datafile WHERE status != 'ONLINE'; 恢复并联机：RECOVER DATAFILE N; 然后 ALTER DATABASE DATAFILE N ONLINE; 如有物理损坏/丢失，用 RMAN 还原并恢复：RESTORE DATAFILE N; RECOVER DATAFILE N; 同时修复底层磁盘/硬件问题。","tags":["datafile","data file","offline","recovery","media recovery","read"]},{"code":"ORA-12520","title":"TNS:listener could not find available handler for requested type of server（监听程序找不到所请求服务器类型的可用处理程序）","severity":"danger","meaning":"监听程序无法为所请求的连接类型（专用或共享）找到合适的服务器进程（handler）。客户端能够连到监听程序，但连接无法移交给服务器进程。","cause":"实例未在监听程序中注册或注册已失效；达到 PROCESSES/SESSIONS 限制，无法派生新的专用进程；共享服务器配置中没有调度程序或调度程序不足；或实例刚启动，尚未在监听程序中注册。","fix":"检查监听程序看到的服务：lsnrctl services（handler 数量和状态）。将实例重新注册到监听程序：ALTER SYSTEM REGISTER; 检查 PROCESSES 限制：SELECT resource_name, current_utilization, max_utilization, limit_value FROM v$resource_limit WHERE resource_name IN ('processes','sessions'); 如有需要，用 ALTER SYSTEM SET processes=... SCOPE=SPFILE; 增大并重启实例。","tags":["listener","tns","connection","handler","processes","dispatcher"]},{"code":"ORA-28001","title":"the password has expired（密码已过期）","severity":"caution","meaning":"所连接的数据库用户的密码已过期，因为其概要文件的 PASSWORD_LIFE_TIME 已到期。在修改密码之前无法打开任何会话。","cause":"用户概要文件中定义的密码有效期已结束，宽限期也已过。在 Oracle 11g 及更高版本中，DEFAULT 概要文件中的密码每 180 天过期一次；这尤其会给长期使用的应用服务账户造成意外中断。","fix":"修改密码：ALTER USER app_user IDENTIFIED BY new_password; 为避免应用账户反复出现此问题，使用密码不过期的概要文件：CREATE PROFILE app_profile LIMIT PASSWORD_LIFE_TIME UNLIMITED; 然后 ALTER USER app_user PROFILE app_profile; 在过期前发出告警的监控也很有用。通过 DBA_USERS 中的 ACCOUNT_STATUS 检查状态。","tags":["password","password expired","expired","profile","account","login"]},{"code":"ORA-38029","title":"object statistics are locked（对象统计信息已锁定）","severity":"caution","meaning":"由于表（或模式）上的优化器统计信息已锁定，统计信息收集/删除被拒绝。锁定的统计信息受保护，不会被 DBMS_STATS 重新计算。","cause":"之前调用过 DBMS_STATS.LOCK_TABLE_STATS 或 LOCK_SCHEMA_STATS（例如为保持统计信息固定），或 Data Pump 导入（尤其是 CONTENT=METADATA_ONLY）无意中使统计信息处于锁定状态。在锁定期间运行 GATHER_TABLE_STATS 会引发此错误。","fix":"解锁统计信息：EXEC DBMS_STATS.UNLOCK_TABLE_STATS('APP_SCHEMA','MY_TABLE'); 或在模式级别 DBMS_STATS.UNLOCK_SCHEMA_STATS('APP_SCHEMA'); 如果不想解除锁定但仍要收集，使用 FORCE=>TRUE：EXEC DBMS_STATS.GATHER_TABLE_STATS('APP_SCHEMA','MY_TABLE', force=>TRUE); 查看锁定状态：SELECT table_name, stattype_locked FROM dba_tab_statistics WHERE stattype_locked IS NOT NULL;","tags":["statistics","optimizer statistics","dbms_stats","locked","lock","optimizer"]}]},"errors/postgres-more2.json":{"engineId":"postgres","engine":"PostgreSQL","order":2,"errors":[{"code":"54001","title":"statement_too_complex","severity":"caution","meaning":"语句的复杂度超出 PostgreSQL 的处理能力；通常是嵌套极深的表达式或大量嵌套运算符使表达式树超过解析器/规划器的栈限制。属于 Class 54（program_limit_exceeded）。","cause":"过深的括号/嵌套子查询或 OR/AND 链；自动生成的巨大 WHERE ... IN (...) 列表；或递归视图产生的超大表达式树。达到了 max_stack_depth 限制。","fix":"简化查询：将长 IN 列表放入临时表或 VALUES 集合并与之 JOIN；拆分嵌套表达式；审查 ORM 生成的巨型查询。如确有必要且操作系统栈限制允许，谨慎调高 max_stack_depth（例如 SET max_stack_depth = '4MB';），但要保持低于操作系统 ulimit。","tags":["program_limit","complex","stack","nested","query","max_stack_depth"]},{"code":"58030","title":"io_error","severity":"danger","meaning":"PostgreSQL 在读写文件时遇到操作系统级的输入/输出（I/O）错误。这表明访问数据库文件时存在物理或文件系统问题。属于 Class 58（system_error）。","cause":"磁盘故障或坏扇区；磁盘已满；文件系统错误；网络附加存储（NFS/SAN）中断；权限问题；或操作系统文件描述符/inode 限制耗尽。","fix":"检查磁盘健康状况和可用空间（df -h、dmesg、SMART 数据）。在 PostgreSQL 日志中查看受影响的是哪个文件。修复文件系统错误；必要时更换硬件。如怀疑数据损坏，计划从最后一个完好的备份还原，并在今后启用校验和（initdb --data-checksums）。","tags":["system_error","io","disk","hardware","file","corruption"]},{"code":"22004","title":"null_value_not_allowed","severity":"caution","meaning":"在不接受 NULL 的地方传入了 NULL。这与列的 NOT NULL 约束（23502）不同；通常是在函数或过程上下文中，向不接受 NULL 的参数或操作传入了 NULL。属于 Class 22（data_exception）。","cause":"给声明为 NOT NULL 的 PL/pgSQL 变量赋值 NULL；向 EXECUTE 传入 NULL 查询字符串；或向不接受 NULL 的内置函数参数传入 NULL。注意：标记为 STRICT 的函数在输入 NULL 时不会报错，而是不执行直接返回 NULL。","fix":"找到出错的行（CONTEXT），对到达该处的值做 NULL 检查，或用 COALESCE 提供默认值：COALESCE(value, default_value)。在 PL/pgSQL 中，先校验值再赋给 NOT NULL 变量；在动态 SQL 中确保传给 EXECUTE 的字符串不为 NULL。在应用层防止必填字段以 NULL 发送。","tags":["data_exception","null","function","strict","parameter","coalesce"]},{"code":"42P18","title":"indeterminate_datatype","severity":"caution","meaning":"PostgreSQL 无法根据上下文推断表达式或参数的数据类型。最常见于使用了无类型的占位符（如 $1）或周围没有任何类型提示的裸 NULL。","cause":"预处理语句中的无类型参数；两边都无法确定类型的比较，例如 \"WHERE $1 = $2\"；或在无上下文的情况下使用 NULL/数组字面量。规划器无法决定使用哪种类型。","fix":"为无法确定类型的表达式添加显式类型转换：$1::text、$1::integer、NULL::date 等。例如：SELECT $1::int + 1; 为驱动/ORM 参数指定类型。在列比较中，确保至少一边的类型已知。","tags":["syntax","type","indeterminate","cast","parameter","prepared"]},{"code":"42P02","title":"undefined_parameter","severity":"caution","meaning":"查询引用了不存在的参数。PostgreSQL 报告所引用的位置参数（例如 $2）未定义。属于 Class 42（syntax_error_or_access_rule_violation）。","cause":"引用的参数多于用 PREPARE 预处理的语句所提供的参数（例如发送两个参数却使用 $3）；参数编号有跳号；或动态拼接的 SQL 中 $N 索引错误。","fix":"确保参数编号与所提供值的数量一致；确认从 $1 开始且连续无间断。在 PREPARE 语句中完整定义参数类型：PREPARE p(int, text) AS SELECT ... WHERE a=$1 AND b=$2; 如果使用 ORM/驱动，比较绑定参数的数量与查询中的占位符数量。","tags":["syntax","parameter","prepared","undefined","placeholder","sql"]},{"code":"2F003","title":"prohibited_sql_statement_attempted","severity":"caution","meaning":"在 SQL 函数/例程中尝试执行了在该上下文中被禁止的语句。属于 Class 2F（sql_routine_exception）。例如，预期为只读的函数试图修改数据。","cause":"PostgreSQL 核心很少引发此代码；它通常来自扩展或过程语言代码。注意：标记为 IMMUTABLE/STABLE 的函数执行 INSERT/UPDATE/DELETE 时不会引发 2F003，而是以代码 0A000（feature_not_supported）引发 '... is not allowed in a non-volatile function'。","fix":"在错误消息中查看被禁止的是哪条语句，并查阅相关例程/扩展的文档。如果遇到 non-volatile function 错误（0A000），将修改数据的函数定义为 VOLATILE（这是默认值）：CREATE FUNCTION ... LANGUAGE plpgsql VOLATILE。从必须只读的位置（索引表达式、约束检查）中移除修改数据的调用。","tags":["sql_routine","function","volatile","prohibited","immutable","side effect"]},{"code":"40003","title":"statement_completion_unknown","severity":"danger","meaning":"事务的结果未知；在确认事务是否完成之前（尤其是在 COMMIT 期间）与服务器的连接已断开。客户端无法确定更改是否已持久化。属于 Class 40（transaction_rollback）。","cause":"COMMIT 已发送但在收到响应之前，网络中断、服务器崩溃或发生了故障转移。事务可能已在服务器上完成，也可能已被回滚。","fix":"不要盲目重试事务；先确认它是否真正生效（例如查询预期的行）。将事务设计为幂等；使用唯一的业务键/幂等键。在连接池中实现健壮的重连和校验逻辑。","tags":["transaction","commit","unknown","connection","idempotent","failover"]},{"code":"08003","title":"connection_does_not_exist","severity":"danger","meaning":"尝试在不存在（已关闭）的数据库连接上执行操作。应用试图使用无效的连接句柄发送查询。属于 Class 08（connection_exception）。","cause":"连接超时、被服务器关闭、网络中断，或从连接池取出的连接实际上已失效；或在应用代码中复用了已 close() 的连接。","fix":"在连接池中启用存活检查（validation query / keepalive）；配置从连接池中剔除失效连接的设置（例如 HikariCP 中的 connectionTestQuery/maxLifetime）。在应用中，使用连接前确认其处于打开状态，断开时自动重连。检查 TCP keepalive 和 idle_in_transaction_session_timeout 的值。","tags":["connection","connection","closed","pool","pool","timeout"]},{"code":"25006","title":"read_only_sql_transaction","severity":"caution","meaning":"在只读事务中尝试执行修改数据的命令（INSERT/UPDATE/DELETE/DDL）。该事务被配置为只能读取。属于 Class 25（invalid_transaction_state）。","cause":"SET TRANSACTION READ ONLY 或 default_transaction_read_only=on 设置；或者最常见的情况：对只读副本（hot standby / 流复制副本）尝试写入。备库按定义就是只读的。","fix":"将写入路由到主库；检查拆分读写的连接路由。如果是误设：SET TRANSACTION READ WRITE; 或在服务器范围内设置 default_transaction_read_only=off。确保应用将读写流量发送到正确的节点。","tags":["transaction","read only","read only","replica","standby","primary"]},{"code":"21000","title":"cardinality_violation","severity":"caution","meaning":"在期望单行的地方返回了多行。标量子查询返回了多行，或 MERGE/ON CONFLICT DO UPDATE 试图第二次影响同一目标行。属于 Class 21。","cause":"在 SELECT 列表中或以 WHERE = (subquery) 形式使用的子查询返回了多行；在单条 INSERT ... ON CONFLICT DO UPDATE 中同一冲突键被提出多次（'cannot affect row a second time'）；或在 MERGE 中多个源行匹配同一目标行。UPDATE ... FROM 在多重匹配时不会报错；目标行只会以不可预测的方式由其中一个源行更新。","fix":"确保子查询只返回一行：添加 LIMIT 1、使用聚合函数（MAX/MIN），或提供使匹配唯一的条件。在 MERGE 和 ON CONFLICT 模式中，用 DISTINCT ON/聚合对源端去重；对 UPDATE ... FROM 也这样做，以免静默产生错误结果。要找出意外的多重匹配，单独运行子查询并检查行数。","tags":["cardinality","subquery","subquery","single row","join","scalar"]}]},"errors/mysql-more2.json":{"engineId":"mysql","engine":"MySQL","order":3,"errors":[{"code":"1927","title":"MariaDB: ER_CONNECTION_KILLED: Connection was killed（连接已被终止）","severity":"danger","meaning":"MariaDB 特有：正在运行的连接/线程被服务器终止，执行中的查询被中断，客户端连接被断开。MySQL 中不存在此代码；在 MySQL 中，同样的情况在查询被中断时表现为 1317（ER_QUERY_INTERRUPTED），在连接断开时于客户端表现为 2013（Lost connection）。","cause":"管理员或监控工具发送了 KILL 命令；服务器正在关闭；MariaDB 中的空闲连接限制，例如 idle_transaction_timeout/wait_timeout；或服务器在资源压力下断开连接。在云/代理环境的维护期间也会出现。","fix":"调查查询被终止的原因：SHOW PROCESSLIST 和慢查询日志中的长时间运行查询；服务器维护/重启计划；连接终止策略。在应用中捕获此错误并安全重试（如果是幂等的）。优化并拆分长查询；在连接池中使用自动重连。","tags":["connection","connection killed","kill","thread","shutdown","retry"]},{"code":"1290","title":"ER_OPTION_PREVENTS_STATEMENT: running with the --read-only option（以 --read-only 选项运行）","severity":"caution","meaning":"MySQL 服务器以 --read-only 或 --super-read-only 选项运行，因此拒绝了修改数据的语句（INSERT/UPDATE/DELETE/DDL）。在 read_only 下，具有 CONNECTION_ADMIN/SUPER 权限的用户仍可写入；启用 super_read_only 后他们也无法写入，消息显示为 '--super-read-only option'。","cause":"read_only=ON 或 super_read_only=ON；最常见的是误向副本写入。为保证数据一致性，副本和 Group Replication 的从节点保持只读；故障转移自动化会将旧主库置于 super_read_only。在故障转移/switchover 期间写入错误节点也会触发。","fix":"将写入路由到主库（source）。如果此服务器确实应成为新主库：SET GLOBAL read_only = OFF;（关闭 read_only 会自动同时关闭 super_read_only；开启 super_read_only 也会同时开启 read_only）。不要在由 HA 工具（Orchestrator/Group Replication/InnoDB Cluster）管理的节点上手动修改。检查状态：SELECT @@read_only, @@super_read_only;","tags":["read only","super_read_only","read-only mode","replica","primary","write","failover","1290"]},{"code":"1836","title":"ER_READ_ONLY_MODE: Running in read-only mode（以只读模式运行）","severity":"caution","meaning":"InnoDB 以只读模式运行（innodb_read_only=ON），或数据目录/介质为只读；对 InnoDB 表的写入和 DDL 被拒绝。注意：启用 read_only/super_read_only 时的写入尝试返回 1290（ER_OPTION_PREVENTS_STATEMENT），而不是此代码。","cause":"服务器以 innodb_read_only=ON 启动（例如只读介质、共享数据目录、用于分析的副本）；在此模式下无法对 InnoDB 表进行 INSERT/UPDATE/DELETE 和 CREATE/DROP/ALTER TABLE 等更改。","fix":"如需写入，不带 innodb_read_only 启动服务器：从 my.cnf 中删除 innodb_read_only=ON 行并重启（此变量不是动态的，需要重启）。检查状态：SELECT @@innodb_read_only; 确认数据目录和文件系统可写。如果是在向副本/HA 从节点写入（1290），请将写入路由到主库。","tags":["innodb_read_only","read only","read-only mode","innodb","1836"]},{"code":"3819","title":"ER_CHECK_CONSTRAINT_VIOLATED: Check constraint is violated（违反 CHECK 约束）","severity":"caution","meaning":"插入或更新的行违反了表上定义的 CHECK 约束。在 MySQL 8.0.16 及更高版本中，CHECK 约束会真正强制执行（早期版本会忽略它们）。","cause":"列值不满足 CHECK 表达式中的条件（例如存在 CHECK (price >= 0) 时输入负价格）。从旧版 MySQL 升级到 8.0.16+ 后，由于以前被忽略的约束现在开始强制执行，可能会意外出现。","fix":"确保值满足约束条件；在应用端添加校验。查看约束定义：SHOW CREATE TABLE my_table; 如果约束确实不需要：ALTER TABLE my_table DROP CHECK constraint_name; 或临时使用 ALTER TABLE my_table ALTER CHECK constraint_name NOT ENFORCED; 同时修复错误数据的来源。","tags":["check","constraint","check constraint","validation","8.0","violation"]},{"code":"1118","title":"ER_TOO_BIG_ROWSIZE: Row size too large（行大小过大）","severity":"caution","meaning":"所定义表的行大小超过了存储引擎允许的最大行大小。非 BLOB/TEXT 列的最大行大小为 65535 字节；InnoDB 还有取决于页大小的额外限制。","cause":"定义了许多宽列（尤其是长 VARCHAR/CHAR）；在 utf8mb4 等多字节字符集中每个字符最多占 4 字节；或在 InnoDB 的 COMPACT/REDUNDANT 行格式中大列以内联方式存储。","fix":"将部分大列转换为 TEXT/BLOB（这些列可以页外存储）；缩小不必要或过宽的列，或将表一分为二。在 InnoDB 中，使用 ROW_FORMAT=DYNAMIC 或 COMPRESSED 可将大列移到页外：ALTER TABLE my_table ROW_FORMAT=DYNAMIC; 同时考虑 innodb_page_size。","tags":["row size","row size too large","innodb","varchar","row_format","limit"]},{"code":"1170","title":"ER_BLOB_KEY_WITHOUT_LENGTH: BLOB/TEXT column used in key specification without a key length（在键定义中使用 BLOB/TEXT 列但未指定键长度）","severity":"caution","meaning":"在索引/键定义中使用了 BLOB 或 TEXT 列，但未指定前缀长度。这些类型无法整体索引；只能索引前 N 个字符/字节。","cause":"在 CREATE TABLE 或 CREATE INDEX 中对 TEXT/BLOB 列定义 INDEX/UNIQUE/PRIMARY KEY 时未指定长度。由于无法整体索引这些可能非常大的值，MySQL 要求指定前缀长度。","fix":"在索引中指定前缀长度，例如 INDEX (description(255))。示例：ALTER TABLE my_table ADD INDEX idx_description (description(191));（由于 InnoDB 键前缀限制，utf8mb4 下常用 191）。如需全文搜索，考虑使用 FULLTEXT 索引。对于应当简短且唯一的字段，将列改为 VARCHAR 可能更合适。","tags":["index","key","blob","text","prefix","prefix length"]},{"code":"1592","title":"ER_BINLOG_UNSAFE_STATEMENT: Unsafe statement written to the binary log（不安全的语句被写入二进制日志）","severity":"caution","meaning":"在使用基于 STATEMENT 的 binlog 格式时，执行了结果不确定的语句（在副本上可能产生不同结果）。由于源与副本之间存在数据不一致的风险，MySQL 在将其写入 binlog 时发出警告。","cause":"在 binlog_format=STATEMENT 下：UUID()、SYSDATE() 或 USER() 等不确定性函数（NOW() 在 STATEMENT 下可安全复制）、带 LIMIT 但没有 ORDER BY 的 UPDATE/DELETE、与 AUTO_INCREMENT 混用的 INSERT，或某些用户自定义/系统函数。这些语句不一定在每台服务器上产生相同结果。","fix":"将 binlog 格式切换为基于行（推荐；自 MySQL 5.7.7 起 ROW 为默认值）：在 my.cnf 中设置 binlog_format=ROW 或 SET PERSIST binlog_format = ROW;（binlog_format 变量自 8.0.34 起已弃用；更改对新会话生效）。ROW 格式可安全复制不确定性语句。如果必须保留 STATEMENT，用常量值替换不确定性函数/表达式，并为带 LIMIT 的 DML 添加确定性的 ORDER BY。","tags":["binlog","replication","unsafe","statement","row","nondeterministic"]},{"code":"3140","title":"ER_INVALID_JSON_TEXT: Invalid JSON text（无效的 JSON 文本）","severity":"caution","meaning":"向 JSON 列或 JSON 函数传入了无效（语法损坏）的 JSON 文本。MySQL 无法将该值解析为 JSON，因此拒绝该操作。","cause":"未闭合的引号/括号、末尾多余的逗号、单引号、转义字符错误，或向 JSON 列发送了纯文本。在向 JSON_SET、JSON_INSERT 等函数或 JSON 类型列传入格式错误的文本时出现。","fix":"将 JSON 文本修正为合法语法：使用双引号、去掉末尾逗号、检查转义。测试文本是否有效：SELECT JSON_VALID('{...}');（有效时返回 1）。在应用层使用可靠的库序列化 JSON，并在发送到服务器之前进行校验。","tags":["json","invalid","syntax","json_valid","parse","data"]},{"code":"1526","title":"ER_NO_PARTITION_FOR_GIVEN_VALUE: Table has no partition for value（表中没有对应该值的分区）","severity":"caution","meaning":"插入行的分区键值不属于任何已定义的分区。在 RANGE 或 LIST 分区中，由于没有分区覆盖传入的值，插入失败。","cause":"在 RANGE 分区中，未定义 MAXVALUE 分区时插入了超出最高边界的值（例如未来日期）；在 LIST 分区中，出现了未列出的值。在按时间分区的表中，新周期的分区未及时添加时很常见。","fix":"添加覆盖该值的分区。为 RANGE 添加新的上限分区：ALTER TABLE my_table ADD PARTITION (PARTITION p2026 VALUES LESS THAN (2027)); 如需兜底分区，RANGE 定义 MAXVALUE，LIST 定义相应的值。对于基于时间的表，设置自动添加分区的维护作业（event/scheduler）。","tags":["partition","partitioning","range","list","maxvalue","insert"]}]},"errors/mssql-more2.json":{"engineId":"mssql","engine":"SQL Server","order":4,"errors":[{"code":"1101","title":"Could not allocate a new page because of insufficient disk space in filegroup（文件组磁盘空间不足，无法分配新页）","severity":"danger","meaning":"SQL Server 尝试为对象分配新页，但文件组的磁盘空间不足。数据文件既无法增长也找不到空闲页，因此写入失败。","cause":"存放数据文件的磁盘已满；文件禁用了自动增长或已达到 MAXSIZE；或文件组中的每个文件都已达到大小上限。在大批量 INSERT/索引创建期间很常见。","fix":"释放磁盘空间或为文件组增加空间。扩大文件或启用自动增长：ALTER DATABASE db MODIFY FILE (NAME = logical_name, SIZE = 20GB, MAXSIZE = UNLIMITED, FILEGROWTH = 512MB); 或者，向文件组添加新文件（放在另一块磁盘上）。查看文件使用情况可检查 sys.database_files，用 DBCC SQLPERF 查看可用空间。","tags":["disk","filegroup","file group","autogrow","space","allocation"]},{"code":"3729","title":"Cannot DROP because it is being referenced by object（无法 DROP，因为它正被对象引用）","severity":"caution","meaning":"要删除的对象（表、类型、函数等）被另一个对象引用，该依赖关系阻止了删除。尤其是架构绑定的对象会以这种方式受到保护。","cause":"试图删除以 WITH SCHEMABINDING 创建的视图或函数所依赖的表/对象；删除被外键引用的表；或在表/参数仍在使用时删除用户定义类型。","fix":"先删除引用对象或解除依赖关系。查找依赖关系：SELECT referencing_schema_name, referencing_entity_name FROM sys.dm_sql_referencing_entities('app_schema.object', 'OBJECT'); 对于架构绑定的视图，先用 ALTER VIEW 去掉 SCHEMABINDING 或删除视图，然后再删除表。","tags":["drop","reference","schemabinding","view","dependencies","dependency"]},{"code":"3961","title":"Snapshot isolation transaction failed because the object was modified by a DDL statement（快照隔离事务失败，因为对象已被 DDL 语句修改）","severity":"caution","meaning":"在快照隔离级别下运行的事务失败，因为它访问的对象被另一个并发事务中的 DDL 语句（架构更改）修改。元数据没有版本控制，因此快照事务无法继续。","cause":"在以 SNAPSHOT 隔离级别（SET TRANSACTION ISOLATION LEVEL SNAPSHOT）启动的事务运行期间，另一个会话对其访问的表执行了 ALTER TABLE/CREATE INDEX 等 DDL。快照事务可以看到行版本，但看不到架构版本。在 READ COMMITTED SNAPSHOT（RCSI）下不会出现此错误；RCSI 为每条语句获取新的快照。","fix":"重试事务；DDL 完成后通常会成功。作为永久方案，在没有大量快照负载的维护窗口内执行架构更改（DDL）。在应用中捕获此错误并实现自动重试逻辑。缩短长时间运行的快照事务。","tags":["snapshot","isolation level","ddl","concurrency","retry","isolation"]},{"code":"1785","title":"Introducing FOREIGN KEY constraint may cause cycles or multiple cascade paths（引入 FOREIGN KEY 约束可能导致循环或多重级联路径）","severity":"caution","meaning":"要添加的外键可能在级联（ON DELETE/UPDATE CASCADE）操作中形成循环，或形成通向同一张表的多条级联删除/更新路径。为避免级联行为不明确，SQL Server 拒绝该操作。","cause":"带级联操作的 FK 在表之间形成循环引用（A->B->A），或形成通向同一张表的多条级联路径。SQL Server 不允许这样做，以防止级联顺序不明确和无限循环。","fix":"将外键定义为 ON DELETE NO ACTION / ON UPDATE NO ACTION（去掉级联）：ALTER TABLE child ADD CONSTRAINT fk ... FOREIGN KEY (...) REFERENCES parent(...) ON DELETE NO ACTION; 用 INSTEAD OF DELETE 触发器实现所需的级联删除：触发器先删除子行，再删除父行（例如 DELETE c FROM child c JOIN deleted d ON c.parent_id = d.id; DELETE e FROM parent e JOIN deleted d ON e.id = d.id;）。AFTER DELETE 触发器不起作用；FK 检查在触发器之前执行，因此删除会以错误 547 被拒绝。审查数据模型中的循环依赖。","tags":["foreign key","fk","cascade","circular reference","cycle","constraint"]},{"code":"9421","title":"XML parsing: illegal xml character（XML 解析：非法 XML 字符）","severity":"caution","meaning":"SQL Server 在将文本解析为 XML 时遇到了不允许（无效）的字符。由于该字符，转换为 XML 类型或赋值给 XML 列/变量失败。","cause":"XML 1.0 中禁止的控制字符（例如 0x00-0x1F 范围内的部分字符）、编码错误（编码不匹配），或来自其他来源的损坏/未清洗数据。通常出现在 CAST(... AS XML) 或向 XML 列 INSERT 时。","fix":"在转换为 XML 之前，从文本中去除无效字符。使源编码（UTF-8/UTF-16）与目标兼容；在 XML 声明中声明正确的编码。用 REPLACE/NCHAR 去除控制字符，或在应用层使用标准库生成并校验 XML。如果必须存储原始文本，考虑使用 NVARCHAR(MAX)。","tags":["xml","parse","character","encoding","invalid","cast"]},{"code":"15151","title":"Cannot find the object/principal because it does not exist or you do not have permission（找不到对象/主体，因为它不存在或您没有权限）","severity":"caution","meaning":"所引用的对象或安全主体（用户、角色、登录名）不存在，或您没有访问/操作它的权限。出于安全原因，SQL Server 将这两种情况合并为一条消息。","cause":"在 GRANT/ALTER/DROP 等命令中使用了不存在的用户、角色、架构或对象名；在错误的数据库上下文中运行（对象位于另一个 DB）；或缺少执行该操作的权限。拼写错误、大小写和架构前缀是常见原因。","fix":"确认对象/主体名称拼写正确且位于正确的数据库中。对于用户：SELECT name, type_desc FROM sys.database_principals; 对于对象，查询 sys.objects。确保执行账户拥有所需权限（例如 ALTER ANY USER、CONTROL）。使用正确的架构前缀（如 dbo.object）。","tags":["permission","principal","privilege","not found","grant","security"]},{"code":"4834","title":"You do not have permission to use the bulk load statement（您没有使用大容量加载语句的权限）","severity":"caution","meaning":"您缺少使用 BULK INSERT（或 OPENROWSET BULK）执行大容量加载所需的权限。大容量加载会直接访问文件系统和服务器资源，因此需要单独的权限。","cause":"登录名缺少服务器级 ADMINISTER BULK OPERATIONS 权限，或不是 bulkadmin 服务器角色的成员（Azure SQL Database 则需要数据库级 ADMINISTER DATABASE BULK OPERATIONS）。还需要目标表上的 INSERT 权限，以及 SQL Server 服务账户对源文件的读取权限。","fix":"授予所需权限：GRANT ADMINISTER BULK OPERATIONS TO login_name;（服务器级），或将用户加入相应角色。Azure SQL DB 使用 ADMINISTER DATABASE BULK OPERATIONS。同时确保 SQL Server 服务账户对目标文件/共享具有读取权限。遵循最小权限原则，只授予需要的账户。","tags":["bulk insert","privilege","permission","bulk load","bulkadmin","file"]},{"code":"10928","title":"Resource limit for the database has been reached (Azure SQL)（已达到数据库的资源限制）","severity":"danger","meaning":"在 Azure SQL Database 中，数据库达到了资源限制（如并发请求/工作线程或会话数），新请求被拒绝。这表明已触及所选服务层级（DTU 或 vCore）的上限。","cause":"超过所选层级允许的最大并发请求（工作线程）或会话数；负载突增；低效/长时间运行的查询占用工作线程；或连接池过大。","fix":"降低并发：优化查询以缩短执行时间，关闭不必要的未结事务，保持合理的连接池大小。对于持续负载，提升服务层级（更高的 DTU/vCore）。用 sys.dm_db_resource_stats 监控资源使用情况。对于可重试的错误，在应用中实现指数退避重试。","tags":["azure","limit","resource","dtu","vcore","concurrency"]},{"code":"40544","title":"The database has reached its size quota (Azure SQL)（数据库已达到大小配额）","severity":"danger","meaning":"Azure SQL Database 达到了服务层级设定的最大数据大小配额。数据库无法再增长，因此新的写入失败。","cause":"数据增长到层级上限；大型索引/日志占用空间；或所选层级允许的 MAXSIZE 较小（例如 Basic/Standard S 系列）。未归档的历史数据也会很快占满配额。","fix":"释放空间：删除/归档不需要的数据，删除未使用的索引，压缩表（数据压缩）。作为永久方案，增大 MAXSIZE 或升级到更高的服务层级：ALTER DATABASE db MODIFY (MAXSIZE = 250 GB); 用 sys.database_files 及相关 DMV 监控大小使用情况。根据增长趋势提前规划扩容。","tags":["azure","size","quota","maxsize","size quota","scaling"]},{"code":"40613","title":"Database is not currently available (Azure SQL)（数据库当前不可用）","severity":"danger","meaning":"在 Azure SQL 中，目标数据库暂时无法访问。通常是暂时性的；在平台移动、重新配置或恢复数据库期间短暂出现。","cause":"Azure 端的自动扩缩容/故障转移、打补丁、负载均衡或资源迁移；少数情况下是持续时间较长的平台事件。在数据库重新联机之前，连接会被拒绝。","fix":"稍等后重试；这是暂时性错误。在应用中实现指数退避自动重试；Azure SDK/重试库会将此错误代码识别为暂时性错误。如果问题持续，检查 Azure Service Health 和区域性事件。为实现高可用，考虑使用异地复制/故障转移组。","tags":["azure","temporary","transient","availability","retry","failover"]}]},"errors/mongodb-more2.json":{"engineId":"mongodb","engine":"MongoDB","order":5,"errors":[{"code":"31 RoleNotFound","title":"Role not found（未找到角色）","severity":"caution","meaning":"所引用的角色未在 MongoDB 中定义。在向用户授予角色、更新角色或查询角色权限时使用了不存在的角色名。","cause":"grantRolesToUser/createUser 中的角色名拼写错误或从未创建；在错误的数据库中查找角色（角色绑定到特定 DB）；或自定义（非内置）角色尚未用 createRole 定义。","fix":"确认角色名及其所属数据库。列出现有角色：db.getRoles({showBuiltinRoles: false}) 或在所有 DB 上执行相应的管理查询。如果自定义角色不存在，先创建：db.createRole({role:'reader', privileges:[...], roles:[]})。授予时指定正确的 db：db.grantRolesToUser('app_user', [{role:'reader', db:'app'}])。","tags":["role","roles","authorization","access control","createrole","user"]},{"code":"263 OperationNotSupportedInTransaction","title":"Operation not supported inside a multi-document transaction（多文档事务中不支持该操作）","severity":"caution","meaning":"在多文档事务中尝试执行了在该上下文中不支持的操作。某些命令（尤其是部分 DDL 和元数据操作）无法在事务中运行。","cause":"事务中不支持的操作，例如创建集合/索引（取决于版本）；某些 count/list 类命令；或在事务会话中无效的管理命令。旧版 MongoDB 不支持在事务中执行 DDL（部分支持自 4.4+ 起提供）。","fix":"将不支持的操作移出事务（例如在事务开始前创建所需的集合/索引）。检查 MongoDB 版本；4.4+ 支持在事务中执行部分 DDL。在驱动中用单独的会话运行该操作。将事务限制在受支持的 CRUD 操作范围内。","tags":["transaction","multi-document transaction","ddl","unsupported","session","crud"]},{"code":"20 IllegalOperation","title":"Illegal operation（非法操作）","severity":"caution","meaning":"所请求的操作对于服务器当前状态或目标对象类型无效/被禁止。MongoDB 在拒绝逻辑上不允许的动作时返回此通用错误。","cause":"对视图尝试执行仅适用于集合的操作；在两个数据库之间进行无效的重命名；对系统集合进行被禁止的更改；或副本集/分片配置不允许的管理操作。因上下文而异。","fix":"阅读错误消息的详细内容；它通常会明确说明哪个操作被禁止以及原因。使操作适应目标对象的类型（例如改为在底层集合而不是视图上操作）。对于重命名/移动操作，查看 MongoDB 的限制（例如 renameCollection 只能在同一 DB 内）。检查集群状态（副本集/分片）。","tags":["illegal","invalid","operation","view","rename","system"]},{"code":"48 NamespaceExists","title":"Namespace (collection) already exists（命名空间（集合）已存在）","severity":"safe","meaning":"要创建的集合（命名空间）已存在。MongoDB 报告它已存在，因此无需再用相同名称和选项创建新集合。","cause":"试图用 createCollection 重新创建已存在的集合（可能带有不同选项）；架构/初始化脚本运行了多次；或试图将已存在的名称重新定义为 capped 集合或带特殊选项的集合。","fix":"通常可以安全忽略；它表示集合已存在。使初始化脚本幂等：创建前检查是否存在（db.getCollectionNames().includes('name')）。要更改选项，必须删除并重新创建集合（先备份数据）：db.my_collection.drop() 然后 createCollection。","tags":["namespace","collection","collection exists","createcollection","already exists","idempotent"]},{"code":"11601 Interrupted","title":"Operation was interrupted（操作被中断）","severity":"caution","meaning":"正在运行的操作被中断。由于外部干预或服务器状态变化，MongoDB 在其完成前终止了该操作。","cause":"管理员/工具用 db.killOp() 终止了操作；服务器正在关闭；副本集中主节点发生变更（stepdown/故障转移）。（超过 maxTimeMS 返回的是 50 MaxTimeMSExpired，而不是此错误。）长时间运行的查询可以通过这种方式停止。","fix":"调查操作被中断的原因：currentOp/killOp 历史、故障转移事件、服务器维护。如果操作是幂等的，可安全重试。优化并缩短长查询；添加合适的索引。在驱动中使用暂时性错误重试逻辑；确保副本集故障转移时自动重连。","tags":["interrupted","interruption","killop","failover","stepdown","retry"]},{"code":"85 IndexOptionsConflict","title":"Index already exists with different options / different name（索引已存在但选项不同/名称不同）","severity":"caution","meaning":"要创建的索引与已有索引冲突：同名索引的选项不同，或已有键和选项相同的等价索引以其他名称存在。MongoDB 不允许以冲突的定义重新创建同一索引。","cause":"以相同名称和键但不同选项（例如 expireAfterSeconds、partialFilterExpression、collation）创建索引；或以不同名称重新定义已有索引的等价索引（'Index already exists with a different name'）。应用/迁移脚本中的索引定义随时间漂移是典型原因。名称相同但键不同时，返回的是 86 IndexKeySpecsConflict。","fix":"查看已有索引：db.my_collection.getIndexes()。如果确实要更改选项，删除旧索引后创建新索引：db.my_collection.dropIndex('index_name') 然后 createIndex(...)。否则，使定义与已有索引完全一致，或为新索引取不同的名称。使迁移脚本幂等。","tags":["index","indexes","options","conflict","unique","createindex"]},{"code":"67 CannotCreateIndex","title":"Cannot create index（无法创建索引）","severity":"caution","meaning":"由于约束或限制，索引创建失败。受现有数据或配置影响，MongoDB 无法创建所请求的索引。","cause":"超过每个集合允许的最大索引数（64）；键过大；或选项组合不兼容（例如 TTL 索引使用了不合适的字段类型）。注意：在现有数据包含重复值时创建唯一索引，引发的是 E11000 DuplicateKey，而不是此代码。","fix":"如果达到索引数量限制，删除未使用的索引（用 $indexStats 分析使用情况）。检查键大小/类型兼容性和选项组合。在大型集合上，于低负载时段创建索引（自 4.2 起索引创建本就不会长时间阻塞写入；background 选项会被忽略）。","tags":["index","indexes","unique","duplicate","limit","ttl"]},{"code":"27 IndexNotFound","title":"Index not found（未找到索引）","severity":"safe","meaning":"在集合上未找到所引用的索引。通常在试图删除或重命名不存在的索引时出现。","cause":"在 dropIndex/hideIndex 等命令中给出了错误的索引名或键规范；索引之前已被删除；或自动生成的索引名与预期不同（MongoDB 生成类似 field_1_field2_-1 的名称）。","fix":"列出现有索引及其确切名称：db.my_collection.getIndexes()。用正确的名称或键文档重复操作：db.my_collection.dropIndex({field: 1}) 或 db.my_collection.dropIndex('field_1')。编写删除脚本时，使其在索引不存在时不会失败（加入存在性检查）。","tags":["index","indexes","dropindex","not found","getindexes","name"]},{"code":"79 UnknownReplWriteConcern","title":"Unknown replica set write concern（未知的副本集写关注）","severity":"caution","meaning":"指定的写关注（write concern）无法识别。操作中使用的 w 值（自定义/标签写关注名称或数值）未在副本集配置中定义。","cause":"使用了未在 getLastErrorModes 中定义的自定义写关注名称（例如把 'majority' 拼错，或自定义标签名称）。配置与请求不匹配。注意：请求的数值 w 大于有投票权成员数是另一个错误（100 UnsatisfiableWriteConcern）。","fix":"确保所用的写关注名称已在副本集设置中定义。查看当前配置：rs.conf()（settings.getLastErrorModes）。如果使用自定义模式，先定义它，或改用标准值（'majority' 或有效的数值 w）。检查驱动/应用连接字符串中的 writeConcern 设置。","tags":["write concern","write acknowledgement","replica set","majority","getlasterrormodes","config"]}]},"errors/redis-more2.json":{"engineId":"redis","engine":"Redis","order":6,"errors":[{"code":"ERR DEBUG command not allowed","title":"DEBUG command not allowed（不允许使用 DEBUG 命令）","severity":"caution","meaning":"您试图运行 DEBUG 命令，但服务器不允许。DEBUG 是底层的危险诊断命令；出于安全原因，较新的 Redis 版本默认禁用或限制该命令。","cause":"在 Redis 7+ 中，未设置 enable-debug-command 时 DEBUG 子命令无法运行；托管云 Redis 服务通常完全禁用该命令；或该命令被 ACL 拒绝。测试/诊断脚本（例如 DEBUG SLEEP、DEBUG OBJECT）经常遇到此错误。","fix":"避免在生产环境中使用 DEBUG。如果在本地/测试环境中确实需要，在配置中启用：在 redis.conf 中设置 enable-debug-command yes（或 'local'）。在云服务上通常根本无法启用该命令；使用 MEMORY USAGE、OBJECT ENCODING 和 INFO 等安全命令获取等效信息。如果存在 ACL 限制，请检查权限。","tags":["debug","command","prohibited","security","acl","config"]},{"code":"ERR Can't save in background: fork failed","title":"Background save failed: fork failed（后台保存失败：fork 失败）","severity":"danger","meaning":"Redis 无法为后台 RDB 快照或 AOF 重写创建子进程（fork）。由于持久化无法运行，数据无法写入磁盘。","cause":"操作系统无法为 fork 分配足够的内存；通常是禁用了 overcommit_memory，当 Redis 试图预留大约一份其所用内存的副本时，系统拒绝。物理/虚拟内存不足或进程数限制也可能导致此问题。","fix":"在 Linux 上启用内存超额分配：sysctl vm.overcommit_memory=1（添加到 /etc/sysctl.conf 中使其永久生效）。为服务器增加内存或降低 Redis 内存使用量（maxmemory）。提供足够的交换空间。检查进程/文件描述符限制（ulimit）。如果持久化至关重要，在没有这些设置的情况下不要上线生产。","tags":["fork","rdb","aof","persistence","overcommit","memory"]},{"code":"NOPROTO unsupported protocol version","title":"Unsupported RESP protocol version（不支持的 RESP 协议版本）","severity":"caution","meaning":"客户端请求了服务器不支持的 RESP 协议版本。通常在用 HELLO 命令请求 RESP3（版本 3）而服务器仅支持 RESP2 时返回。","cause":"发送了 HELLO 3，但服务器运行的是旧版 Redis（6.0 之前）或不支持 RESP3；客户端库默认请求 RESP3，但服务器版本较旧。属于版本不匹配。","fix":"将客户端配置为使用 RESP2，或用 HELLO 请求有效版本（HELLO 2）。将服务器升级到支持 RESP3 的版本（Redis 6+）。检查客户端库的协议版本设置；大多数库可以自动回退到 RESP2。确认服务器版本：INFO server（redis_version）。","tags":["resp","protocol","hello","resp3","version","compatibility"]},{"code":"ERR Unknown option or number of arguments for CONFIG SET","title":"Unknown option or number of arguments for CONFIG SET (older versions: Unsupported CONFIG parameter)（CONFIG SET 的选项未知或参数数量错误）","severity":"caution","meaning":"您试图用 CONFIG SET 修改的配置参数在此 Redis 版本中无法识别，或参数数量错误。在 Redis 7 中完整消息为 \"ERR Unknown option or number of arguments for CONFIG SET - '<name>'\"；旧版本返回 \"ERR Unsupported CONFIG parameter\"。CONFIG GET 对未知参数不会报错，而是返回空列表。","cause":"参数名拼写错误；使用了其他 Redis 版本的参数（参数可能在版本之间增加或移除）；或托管云服务不允许修改某些参数。在较新/较旧版本上运行旧脚本是典型原因。","fix":"确认有效的参数名。用模式列出当前设置，例如 CONFIG GET 'maxmemory*'。查阅与您 Redis 版本对应的文档中的参数列表；用 INFO server 查看版本。如果云服务有限制，使用提供商允许的参数集。针对目标版本更新脚本。","tags":["config","parameter","unsupported","version","config set","config get"]},{"code":"ERR MULTI calls can not be nested","title":"MULTI calls can not be nested（MULTI 调用不能嵌套）","severity":"caution","meaning":"在已打开 MULTI（事务）块的情况下再次发送了 MULTI。Redis 不支持嵌套事务块；同一时间只能有一个 MULTI 处于活动状态。","cause":"应用逻辑在未用 EXEC/DISCARD 关闭前一个 MULTI 的情况下启动了新的 MULTI；从连接池复用同一连接时未正确重置事务状态；或库封装在不知情的情况下调用了两次 MULTI。","fix":"每个 MULTI 块都用一个 EXEC（或用 DISCARD 取消）关闭，且只在前一个块结束后才打开新块。在代码流程中明确区分事务的开始和结束。确保在将连接归还连接池之前没有遗留未结束的事务。如果需要嵌套逻辑，考虑用 Lua 脚本（EVAL）完成原子操作。","tags":["multi","transaction","nested","nested","exec","discard"]},{"code":"ERR Lua script attempted to access nonexistent global variable","title":"Lua script attempted to access a nonexistent global variable（Lua 脚本试图访问不存在的全局变量）","severity":"caution","meaning":"通过 EVAL/EVALSHA 运行的 Lua 脚本试图访问未定义的全局变量。为保护脚本环境，Redis 限制创建/读取全局变量，并将此类访问作为错误返回。","cause":"在脚本中误用了全局变量而非局部变量（忘记写 local 关键字）；因拼写错误访问了未定义的名称；或试图在全局变量中保存状态。Redis 的 Lua 沙箱环境不允许这样做。","fix":"在脚本中用 local 声明所有变量：local x = ... 修正拼写错误；确保每个变量在使用前已定义。脚本应当无状态；持久状态请使用 Redis 键（KEYS/redis.call），而不是全局 Lua 变量。保持脚本简短且具有确定性。","tags":["lua","eval","script","global","variable","local"]},{"code":"ERR unknown command","title":"Unknown command（未知命令）","severity":"caution","meaning":"Redis 无法识别您发送的命令。命令名可能有误、在此版本中不可用，或已被禁用/重命名。","cause":"命令名拼写错误；该命令是在较新的 Redis 版本中加入的（旧服务器上不存在）；调用了需要模块（例如 RedisJSON、RediSearch）的命令但模块未加载；或该命令已通过 rename-command/ACL 禁用。有时客户端错误地拆分参数也会使命令名看起来已损坏。","fix":"确认命令名和语法（COMMAND DOCS 命令或文档）。检查服务器版本：INFO server。对于模块命令，确认模块已加载：MODULE LIST。如果命令被 rename-command 隐藏，检查配置。确保客户端库正确编码命令。","tags":["command","unknown","unknown","version","module","acl"]},{"code":"TRYAGAIN Multiple keys request during rehashing of slot","title":"Multiple keys request during rehashing of slot (cluster)（槽重新哈希期间的多键请求）","severity":"caution","meaning":"在 Redis Cluster 中，当某个哈希槽正在节点之间迁移（rehashing/resharding）时，发出了包含属于同一槽的多个键的请求，而其中部分键已迁移、部分尚未迁移。服务器要求您稍后重试该操作。","cause":"在槽迁移进行期间运行多键命令（MGET、MSET、多键 Lua、事务）。在迁移中的槽里，部分键位于源节点、部分位于目标节点，因此命令无法以原子方式执行。","fix":"在客户端捕获此错误并稍等后重试（TRYAGAIN 是暂时性的；重新分片完成后即消失）。对于多键操作，用哈希标签保证所有键位于同一槽中，例如 {app_user123}.profile、{app_user123}.settings。在低流量时段执行集群再平衡（重新分片）。","tags":["cluster","slot","rehash","resharding","tryagain","hash tag"]},{"code":"ERR DISCARD without MULTI","title":"DISCARD without MULTI（未执行 MULTI 就调用 DISCARD）","severity":"caution","meaning":"在没有打开 MULTI 块的情况下发送了 DISCARD。DISCARD 仅用于取消正在进行的事务；没有可取消的内容时返回此错误。","cause":"在没有 MULTI 的情况下调用 DISCARD，或在事务已用 EXEC/DISCARD 关闭后再次调用；错误处理代码中无条件调用 DISCARD；或连接状态与预期不同（从连接池取出的连接上并没有打开 MULTI）。","fix":"仅在确实打开了 MULTI 时才调用 DISCARD；在代码中跟踪事务状态。在错误处理程序中，调用 DISCARD 之前检查是否有打开的事务。使用库提供的事务封装，由其自动管理状态。保证连接池中的连接状态干净。","tags":["discard","multi","transaction","cancel","state","error handling"]}]},"errors/elasticsearch-more2.json":{"engineId":"elasticsearch","engine":"Elasticsearch","order":7,"errors":[{"code":"search_context_missing_exception","title":"No search context found for id (scroll expired)（找不到该 id 的搜索上下文，scroll 已过期）","severity":"caution","meaning":"scroll 查询所使用的搜索上下文在服务器上已不存在。通常在 scroll 上下文已超时或被清除后，仍使用保存的 scroll_id 继续查询时出现。","cause":"为 scroll 指定的 keep-alive 已过期（请求之间的间隔太长）；上下文被手动清除（clear scroll）；或节点重启导致上下文丢失。scroll 上下文保存在节点内存中，因此生命周期有限。","fix":"在每个 scroll 请求中传入足够的 keep-alive（例如 scroll=5m），并缩短翻页之间的延迟。在新版 Elasticsearch 中，深度分页应使用 search_after + Point in Time（PIT）代替 scroll；这种方式扩展性更好，也是推荐做法。用完后释放上下文（clear scroll），以免浪费内存。","tags":["scroll","search_after","pit","context","timeout","pagination"]},{"code":"aliases_not_found_exception","title":"Aliases missing（别名不存在）","severity":"safe","meaning":"要操作的别名不存在。通常在删除或操作一个不存在的别名时返回。","cause":"别名拼写错误；别名之前已被删除；或 _aliases API 中的 remove 操作指定了从未关联的别名。通配符模式没有匹配到任何别名时也会出现。","fix":"列出现有别名：GET _alias，或针对特定索引 GET my-index/_alias。用正确的名称重试操作。编写删除脚本时，使其在别名不存在时不会失败；在 _aliases 批量操作中有条件地处理各项操作。要以原子方式切换别名，在单个 _aliases 请求中同时使用 add 和 remove。","tags":["alias","alias name","not found","aliases","index","routing"]},{"code":"snapshot_missing_exception","title":"Snapshot is missing（快照不存在）","severity":"caution","meaning":"在指定的仓库中未找到所引用的快照。这表明在还原、删除或状态请求中使用了不存在的快照名称。","cause":"快照名称拼写错误；快照之前已被删除；查询了错误的仓库；或备份从未完成。仓库定义已更改或不可达时也会出现类似结果。","fix":"列出仓库中的快照：GET _snapshot/repo_name/_all（或针对特定名称 GET _snapshot/repo_name/snapshot_name）。用正确的仓库和快照名称重试。检查仓库定义：GET _snapshot/repo_name。确认备份策略（SLM）运行成功并生成了快照。","tags":["snapshot","backup","repository","repo","restore","not found"]},{"code":"index_template_missing_exception","title":"Index template is missing（索引模板不存在）","severity":"safe","meaning":"未找到所引用的索引模板。在获取、删除模板或执行基于模板的操作时使用了不存在的模板名称。","cause":"模板名称拼写错误；模板之前已被删除；混淆了旧版模板 API 与可组合索引模板 API；或模板从未创建。初始化脚本未执行完成时也会出现。","fix":"列出现有模板。可组合模板：GET _index_template 或 GET _index_template/name。旧版模板：GET _template。用正确的 API 和名称重试。如果模板不存在，创建它：PUT _index_template/name {...}。使删除/更新脚本幂等，以免在模板不存在时失败。","tags":["index template","template","composable","legacy","not found","mapping"]},{"code":"settings_exception","title":"Invalid settings（无效的设置）","severity":"caution","meaning":"由于给定的设置无效，操作被拒绝。在应用索引或集群设置时，设置名称、设置值或该设置能否在当前上下文中修改存在问题。","cause":"未知/拼写错误的设置名称；值的类型错误或超出允许范围；试图在运行中的索引上修改只能在创建索引时设置的静态设置（例如 number_of_shards）；或使用了已移除/已重命名的设置。","fix":"对照文档校验设置名称和值。在运行中的索引上更新动态设置：PUT my-index/_settings {\"index.number_of_replicas\": 2}。静态设置（例如 number_of_shards）只能在创建时设置；修改需要 reindex。查看当前设置：GET my-index/_settings。","tags":["settings","setting","invalid","static","dynamic","reindex"]},{"code":"shard_not_found_exception","title":"Shard not found（未找到分片）","severity":"danger","meaning":"未找到目标分片。Elasticsearch 无法访问指定索引的相关分片，因此无法完成请求。","cause":"分片尚未分配（unassigned）或正在恢复；节点离开集群导致分片暂时缺失；或直接路由到分片的底层请求携带了已失效的路由信息。在集群健康状况下降时出现。","fix":"检查集群和分片健康状况：GET _cluster/health 和 GET _cat/shards?v。查看分片未分配的原因：GET _cluster/allocation/explain。如果丢失了节点，将其恢复；检查磁盘/分配设置（watermark）。如果恢复正在进行，等待其完成。保持足够的副本数，使单个节点丢失时数据仍然可用。","tags":["shard","shard","unassigned","unassigned shard","recovery","cluster health"]},{"code":"not_x_content_exception","title":"Compressor detection error / body is not valid content（压缩格式检测错误/请求体不是有效内容）","severity":"caution","meaning":"Elasticsearch 无法将请求体识别为有效的结构化内容（JSON 等 XContent）。发送的数据不是预期格式或已损坏。","cause":"请求体是无效的 JSON；Content-Type 头错误或缺失（未发送 application/json）；请求体被意外以压缩/二进制形式发送；或向 _bulk 等需要换行分隔 JSON（NDJSON）的端点发送了普通 JSON。转义/编码错误也会触发。","fix":"确认请求体是有效的 JSON，并发送 Content-Type: application/json 头。对 _bulk 端点使用 NDJSON：每行是一个独立的 JSON 对象，最后一行以换行符结尾（Content-Type: application/x-ndjson）。确保客户端库正确序列化请求体。如果使用压缩，发送相应的 Content-Encoding 头。","tags":["json","xcontent","content-type","bulk","ndjson","request body"]},{"code":"document_source_missing_exception","title":"Document source is missing（文档 source 不存在）","severity":"caution","meaning":"操作需要文档的 _source 字段，但该字段未被存储。在禁用了 source 的索引上尝试执行需要 _source 的操作（例如部分更新、获取 source）。","cause":"索引映射设置了 _source: {enabled: false}；由于文档的原始 JSON 未被存储，部分更新、reindex 以及返回 _source 的查询都无法工作。为节省空间而禁用 source 的配置会导致此问题。","fix":"需要 _source 的操作要求在映射中启用 source。创建新索引时，保持 _source 启用（默认即启用）。如果现有索引上 _source 已禁用，改为重新索引完整文档而不是更新它（用 index API 覆盖写入）。如需节省空间，考虑使用 _source 过滤（includes/excludes）或 synthetic _source，而不是完全禁用 source。","tags":["_source","source","update","mapping","reindex","document source"]}]},"errors/oracle-more3.json":{"engineId":"oracle","engine":"Oracle","order":1,"errors":[{"code":"ORA-00845","title":"MEMORY_TARGET not supported on this system（此系统不支持 MEMORY_TARGET）","severity":"danger","meaning":"由于操作系统的共享内存配置不支持通过 MEMORY_TARGET / MEMORY_MAX_TARGET 请求的自动内存管理，实例无法启动。在 Linux 上，这通常是因为 /dev/shm 小于所请求的内存目标。","cause":"MEMORY_TARGET 设置得大于 /dev/shm（以 tmpfs 挂载）；或在启用 HugePages 的同时使用 MEMORY_TARGET（AMM）；AMM 与 HugePages 不兼容，而 ASMM（SGA_TARGET）兼容。Oracle 自动内存管理通过在 /dev/shm 上创建文件来工作，如果该空间不足，实例将无法启动。","fix":"使 /dev/shm 大于 MEMORY_TARGET：重新挂载 tmpfs（mount -o remount,size=8G /dev/shm），并在 /etc/fstab 中使其永久生效。或者，不使用 AMM 而采用单独设置：将 MEMORY_TARGET 和 MEMORY_MAX_TARGET 设为 0，并定义 SGA_TARGET 和 PGA_AGGREGATE_TARGET。如果使用 HugePages，禁用 AMM 并优先使用 ASMM（SGA_TARGET）。","tags":["memory_target","memory","amm","dev/shm","hugepages","startup"]},{"code":"ORA-27102","title":"out of memory（内存不足）","severity":"danger","meaning":"在启动期间，Oracle 实例无法从操作系统分配所请求的共享内存（SGA）段。通常与 ORA-27102 以及更底层的 OSD/Linux 错误代码一同出现（例如 Linux-x86_64 Error: 28: No space left on device）。","cause":"为 SGA 请求的内存超过内核共享内存限制（shmmax、shmall）；服务器物理 RAM/swap 不足；或 HugePages 配置缺失/不足，无法分配大型 SGA。在增大 SGA_MAX_SIZE 之后很常见。","fix":"增大内核共享内存参数：在 /etc/sysctl.conf 中将 kernel.shmmax 和 kernel.shmall 设置为足以覆盖 SGA 的值，然后运行 sysctl -p。如果使用 HugePages，根据 SGA 计算并设置 vm.nr_hugepages。检查服务器的总 RAM 和 swap；必要时降低 SGA_MAX_SIZE / SGA_TARGET。","tags":["sga","memory","shmmax","hugepages","startup","osd"]},{"code":"ORA-04030","title":"out of process memory when trying to allocate N bytes（尝试分配 N 字节时进程内存不足）","severity":"danger","meaning":"服务器进程无法在其 PGA（Program Global Area，即进程私有内存）中分配空间。与 SGA 中的共享内存不同，这是单个会话/进程自身内存的耗尽。","cause":"超大的排序/哈希操作、过大的 PL/SQL 集合、在失控循环中不断累积的变量，或操作系统进程内存限制（ulimit -v/-d、vm.max_map_count、地址空间）耗尽了进程内存。在 32 位实例上，地址空间限制也可能触发此问题。","fix":"通过查看错误第二行中的分配类别（例如 'pga heap'、'koh-kghu sessi'）找出消耗内存的对象。检查 PGA_AGGREGATE_LIMIT / PGA_AGGREGATE_TARGET。检查操作系统限制（ulimit -a；oracle 用户的 memlock、as、stack）。优化查询/PL/SQL，并减小批量处理的批次大小。如有需要，携带跟踪文件联系 Oracle Support。","tags":["pga","process memory","memory","ulimit","sort","plsql"]},{"code":"ORA-12537","title":"TNS:connection closed（TNS：连接已关闭）","severity":"danger","meaning":"由于通信通道意外关闭，客户端与服务器之间的 TNS 连接已结束。会话在建立连接期间或建立后立即断开。","cause":"监听程序已将客户端移交给服务器进程，但该进程无法创建或立即终止：实例崩溃、达到 PROCESSES 限制、操作系统资源不足、防火墙/中间层切断连接，或 sqlnet.ora 中的 TCP.VALIDNODE_CHECKING/TCP.INVITED_NODES 规则拒绝了该客户端。有时客户端与服务器之间的空闲防火墙会断开连接。","fix":"检查 listener.log 和 sqlnet.log。检查 PROCESSES/SESSIONS 限制：SELECT * FROM v$resource_limit WHERE resource_name IN ('processes','sessions'); 确认服务器端 sqlnet.ora 中的 TCP.INVITED_NODES/TCP.EXCLUDED_NODES 列表没有拒绝客户端 IP（登录版本不匹配引发的是 ORA-28040，而不是此错误）。增大防火墙的空闲超时，或用 SQLNET.EXPIRE_TIME 发送 keepalive。在 alert.log 中检查实例崩溃记录。","tags":["tns","connection","listener","processes","firewall","sqlnet"]},{"code":"ORA-03135","title":"connection lost contact（连接失去联系）","severity":"danger","meaning":"一个先前已建立且正常工作的会话与服务器失去了联系。长时间运行的查询或空闲连接因双方无法再互相访问而被切断。","cause":"服务器进程意外终止（ORA-00600/07445、kill、资源限制）；RESOURCE_LIMIT 概要文件终止了会话；空闲连接被防火墙/负载均衡器断开；或出现网络中断/丢包。通常发生在长时间无响应的查询或空闲连接上。","fix":"在 alert.log 和相关跟踪文件中查找进程终止的原因（是否为 ORA-00600/ORA-07445？）。在网络方面，增大防火墙空闲超时，并在 sqlnet.ora 中用 SQLNET.EXPIRE_TIME 启用失效连接检测。检查用户概要文件中的 CONNECT_TIME/IDLE_TIME 限制。检查服务器资源压力（内存、CPU、PROCESSES）。","tags":["connection","sqlnet","idle","firewall","dead connection","network"]},{"code":"ORA-01033","title":"ORACLE initialization or shutdown in progress（ORACLE 正在初始化或关闭）","severity":"danger","meaning":"您要连接的数据库实例正处于启动（mount/open）或关闭过程中。在这种中间状态下不接受普通用户连接。","cause":"实例卡在 NOMOUNT/MOUNT（例如等待介质恢复）、正在执行 SHUTDOWN，或数据库在到达 OPEN 之前因错误挂起。在刚启动的实例完成启动前进行连接也会返回此错误。","fix":"以 SYSDBA 身份连接并检查状态：sqlplus / as sysdba; 然后 SELECT status FROM v$instance; 以及 SELECT open_mode FROM v$database; 尝试打开实例：ALTER DATABASE OPEN; 如需恢复，运行 RECOVER DATABASE; 然后打开数据库。在 alert.log 中找出阻止打开的根本错误（数据文件丢失/损坏、redo）。如果关闭过程挂起，尝试先 SHUTDOWN ABORT 再 STARTUP。","tags":["startup","shutdown","mount","open","recovery","instance"]},{"code":"ORA-00600","title":"internal error code, arguments: [...]（内部错误代码，参数：[...]）","severity":"danger","meaning":"Oracle 内核中的内部一致性检查失败。这是一个通用的内部错误，表明内核代码遇到了意外情况；方括号中的参数标识触发的是哪条代码路径。","cause":"可能由块/数据损坏、未修复的 Oracle 软件缺陷、补丁级别不一致、内存/硬件问题或意外的边界情况触发。第一个参数（例如 [ktbdchk1]）指明问题涉及哪一层，是支持分析的关键。","fix":"此错误几乎总是需要与 Oracle Support 一起处理。保留生成的跟踪文件和 alert.log 条目；在 My Oracle Support 中用 ORA-600 Troubleshooter 和 'ORA-600 Lookup Tool' 搜索第一个参数。应用相应的补丁/PSU/RU。如果怀疑存在损坏，用 DBVERIFY/RMAN VALIDATE 验证块完整性。在关键环境中，未提交 SR 之前不要在生产环境中尝试。","tags":["internal","ora-600","kernel","bug","trace","support"]},{"code":"ORA-07445","title":"exception encountered: core dump（遇到异常：core dump）","severity":"danger","meaning":"Oracle 服务器进程在收到操作系统级异常（例如 SIGSEGV 段错误、访问冲突）后意外崩溃，并生成了 core dump。与 ORA-00600 不同，此错误由操作系统发出的信号触发。","cause":"通常是 Oracle 软件缺陷、内存损坏、数据/索引块损坏、库不兼容或硬件/内存故障。消息中的函数名和信号类型（例如 SIGSEGV）标识了崩溃的代码路径。","fix":"保留生成的跟踪文件、incident 文件和 alert.log，并在 My Oracle Support 中用函数名 + 信号进行搜索；通常需要打补丁。运行硬件/内存测试，并统一操作系统和 Oracle 的补丁级别。如果怀疑存在损坏，运行 RMAN VALIDATE / DBVERIFY。如果反复出现，提交 SR 并将 core dump 发送给 Support。","tags":["ora-7445","core dump","sigsegv","kernel","bug","trace"]},{"code":"ORA-01102","title":"cannot mount database in EXCLUSIVE mode（无法以 EXCLUSIVE 模式装载数据库）","severity":"danger","meaning":"由于另一个实例（或旧实例的残留文件）似乎仍持有数据库的锁，无法以 EXCLUSIVE 模式装载数据库。实例停在 MOUNT 阶段。","cause":"之前的实例没有正常关闭，锁文件（$ORACLE_HOME/dbs 下的 lk<SID>，或残留的共享内存/信号量）仍然存在；另一个实例已连接到同一数据库；或在非 RAC 安装中启动了第二个实例。之前实例残留的进程/共享内存或过期的 lk<SID> 文件是最常见的原因。","fix":"确认确实没有其他实例在运行：ps -ef | grep pmon 和 ipcs -m（共享内存段）。如果存在残留，ABORT 该实例并清理；必要时用 ipcrm 删除残留的段/信号量。删除 $ORACLE_HOME/dbs 下的 lk<SID> 锁文件（仅在确定实例已停止时）。然后尝试 STARTUP MOUNT。在 RAC 上，检查 CLUSTER_DATABASE 和节点状态。","tags":["mount","exclusive","lock","startup","pmon","instance"]},{"code":"ORA-29913","title":"error in executing ODCIEXTTABLEOPEN callout（执行 ODCIEXTTABLEOPEN callout 时出错）","severity":"caution","meaning":"访问外部表时，读取/解析源文件出错。Oracle 在调用访问驱动程序（ORACLE_LOADER / ORACLE_DATAPUMP）例程以获取外部表数据时失败。","cause":"通常是 DIRECTORY 对象指向的路径中不存在该文件、Oracle 操作系统用户对文件/目录没有读取权限、没有写入 log/bad 文件的权限，或访问参数（字段分隔符、记录格式）错误。常伴有 ORA-29400 和 KUP-04xxx 等子错误。","fix":"阅读子错误（KUP-xxxx）以确定根本原因。确认 DIRECTORY 路径以及文件是否存在：SELECT directory_name, directory_path FROM all_directories; 授予 Oracle 操作系统用户对文件和目录的读取权限，以及对 log/bad 目录的写入权限。使外部表定义中的 ACCESS PARAMETERS 部分与源文件格式一致。必要时检查 DIRECTORY 上的 READ/WRITE 授权。","tags":["external table","external table","directory","oracle_loader","permission","kup"]}]},"errors/postgres-more3.json":{"engineId":"postgres","engine":"PostgreSQL","order":2,"errors":[{"code":"08001","title":"sqlclient_unable_to_establish_sqlconnection","severity":"danger","meaning":"客户端无法与 PostgreSQL 服务器建立连接。连接在成为会话之前，就在网络/地址层面失败了。","cause":"主机/端口错误；服务器没有在该地址上监听（listen_addresses = 'localhost' 时从远程连接）；服务器宕机/不可达；防火墙阻止了端口（默认 5432）；或 DNS 解析失败。配置错误的连接池也可能触发。","fix":"确认服务器已启动并在正确的地址上监听：检查 postgresql.conf 中的 listen_addresses = '*'（或相应 IP）和端口设置，然后重启服务器（listen_addresses 和 port 需要重启；reload 不够）。从客户端测试访问：psql -h host -p 5432 -U app_user。在防火墙中开放 5432/tcp。检查 host/port/DNS 值和连接字符串。","tags":["connection","connection","network","listen_addresses","port","firewall"]},{"code":"08004","title":"sqlserver_rejected_establishment_of_sqlconnection","severity":"danger","meaning":"已连到服务器，但服务器拒绝建立连接。网络可达，但服务器根据策略不接受此连接。","cause":"PostgreSQL 服务器很少产生此代码；它大多由客户端驱动或连接池报告。服务器针对类似拒绝的实际代码不同：pg_hba.conf 拒绝（'no pg_hba.conf entry for host'、reject 方法）返回 28000（invalid_authorization_specification），max_connections 耗尽返回 53300（too_many_connections），启动/关闭期间拒绝连接返回 57P03。","fix":"在服务器日志中查看拒绝的真实 SQLSTATE 和原因。对于 pg_hba 拒绝（28000），为客户端 IP、用户和数据库添加匹配的行（例如 host all all 10.0.0.0/24 scram-sha-256），并用 SELECT pg_reload_conf(); 重新加载。对于连接饱和（53300），用 SELECT count(*) FROM pg_stat_activity WHERE backend_type = 'client backend'; 将客户端连接数与 max_connections 进行比较。","tags":["connection","pg_hba","rejected","authentication","max_connections","reject"]},{"code":"57P01","title":"admin_shutdown","severity":"danger","meaning":"您的活动连接因管理员命令而被终止。当服务器正在关闭或会话被手动终止时（'terminating connection due to administrator command'），客户端会收到此错误。","cause":"DBA 用 pg_terminate_backend() 终止了会话；服务器正以 fast/immediate 模式关闭（smart 关闭不会终止会话，而是等待其结束）；或因配置更改/计划维护而断开了现有连接。在某些故障转移场景中，旧主库也会以这种方式切断连接。","fix":"这通常不是永久性错误；应用必须重新连接（重试）。如果出乎意料，在服务器日志中调查关闭/终止的原因。针对计划维护，在应用中实现自动重连和连接池健康检查。服务器日志不会显示是谁终止了会话；要追踪 pg_terminate_backend 调用，用 log_statement（或 pgaudit）保留审计记录。","tags":["shutdown","terminate","connection","maintenance","failover","retry"]},{"code":"53400","title":"configuration_limit_exceeded","severity":"danger","meaning":"由于超出配置限制，操作失败。服务器因某个已定义的限制（例如 temp_file_limit、复制槽或 WAL sender 的数量）拒绝了请求。","cause":"典型示例：查询的临时文件用量超过 temp_file_limit（'temporary file size exceeds temp_file_limit'）；所有复制槽都在使用中（'all replication slots are in use'，max_replication_slots）；超过 max_wal_senders 限制。注意：max_prepared_transactions 耗尽返回 53200，预备事务被禁用（值为 0）返回 55000。","fix":"错误消息会说明超出了哪个限制。对于 temp_file_limit，优化查询（用 EXPLAIN ANALYZE 找出溢出到磁盘的排序/哈希步骤），必要时在会话级增大 work_mem，或有意识地提高限制。如果槽已耗尽，在确认所有者后清理未使用的槽：SELECT slot_name, slot_type, active FROM pg_replication_slots; 或增大 max_replication_slots / max_wal_senders 并重启服务器。","tags":["limit","temp_file_limit","replication slot","max_wal_senders","configuration","resource"]},{"code":"XX001","title":"data_corrupted","severity":"danger","meaning":"PostgreSQL 在表/堆页中检测到数据损坏。由于读取的块内容不一致（'invalid page in block'），查询无法安全完成。","cause":"磁盘/存储故障、文件系统损坏、突然断电时 fsync 保证未被遵守、硬件故障（RAM/控制器），或少数情况下的软件缺陷。启用校验和后能更早发现损坏。","fix":"首先检查硬件/存储，并尽可能立即计划回退到物理备份（PITR）。如果有完好的备份，进行还原。为了恢复，可以在承担数据风险的前提下、仅作为最后手段，用 zero_damaged_pages = on 将损坏页清零并导出可读数据（这会导致数据丢失）。启用 data_checksums（在 PG12+ 的现有集群上，于服务器停止时执行 pg_checksums --enable），运行 RAM 测试并定期进行 PITR 备份。","tags":["corruption","corruption","checksum","page","hardware","pitr"]},{"code":"XX002","title":"index_corrupted","severity":"danger","meaning":"在索引结构中检测到损坏。由于索引指向的数据与堆中的实际行不一致，使用该索引的查询可能返回错误结果或失败。","cause":"硬件/存储故障、操作系统 glibc/ICU 升级后排序规则（collation）变化导致 B-tree 顺序被破坏，或突然宕机。尤其是操作系统排序规则的更新会在文本索引中造成静默损坏。","fix":"重建损坏的索引：REINDEX INDEX idx_name; 或 REINDEX TABLE my_table; 在大表上使用 REINDEX INDEX CONCURRENTLY 以减少锁定。如果原因是排序规则，重建所有受影响的文本索引，并用 amcheck 扩展（bt_index_check）进行验证。如果根本原因是硬件，更换存储。","tags":["index","index","corruption","reindex","collation","amcheck"]},{"code":"25001","title":"active_sql_transaction","severity":"caution","meaning":"在事务处于活动状态时，调用了只能在事务之外或事务开始时运行的命令。某些命令不能在事务块中运行。","cause":"SET TRANSACTION ISOLATION LEVEL 等设置必须在块中的第一个查询之前发出（'SET TRANSACTION ISOLATION LEVEL must be called before any query'）。同样，某些命令在已打开的 BEGIN/事务中会被拒绝。通常源于应用禁用了 autocommit 并在不知不觉中启动了事务。","fix":"在事务的最开始、任何查询运行之前发出事务级设置：BEGIN; SET TRANSACTION ISOLATION LEVEL SERIALIZABLE; ... 检查客户端库是否会隐式打开事务（autocommit 设置）。对于不能在事务块中运行的命令，先用 COMMIT/ROLLBACK 结束当前事务。","tags":["transaction","transaction","isolation","set transaction","autocommit","begin"]},{"code":"42P16","title":"invalid_table_definition","severity":"caution","meaning":"由于表定义无效，DDL 命令被拒绝。所请求的表/列结构违反了 PostgreSQL 的规则。","cause":"例如定义了多个主键（'multiple primary keys for table are not allowed'）、与继承冲突的定义、生成列/标识列的限制，或要求 ALTER TABLE 生成其无法产生的结构。在自动生成的迁移脚本中很常见。","fix":"阅读错误消息中的具体原因。只定义一个 PRIMARY KEY；如需多列键，使用 PRIMARY KEY (a, b) 形式的复合键。检查标识列/生成列的规则。用 \\d my_table 检查当前表定义并修正迁移脚本。","tags":["ddl","table","primary key","definition","alter table","migration"]},{"code":"P0001","title":"raise_exception","severity":"caution","meaning":"PL/pgSQL 函数或触发器用 RAISE EXCEPTION 主动引发了错误。此错误不是数据库引擎产生的，而是由您（或某个库）编写的业务规则代码产生的。","cause":"当业务逻辑未通过某项校验时，开发人员调用了 RAISE EXCEPTION 'message' USING ERRCODE = ...;。例如，触发器引发自定义错误以阻止不允许的更新或强制执行一致性规则。","fix":"错误消息以及 HINT/DETAIL 字段说明了函数拒绝的内容；据此满足相应的业务规则。要找到来源，检查相关的函数/触发器代码（RAISE 语句）。如果需要特定的 SQLSTATE，用 RAISE ... USING ERRCODE 指定有意义的代码，而不是通用的 P0001，以便应用加以区分。","tags":["plpgsql","raise","trigger","function","business rule","exception"]}]},"errors/mysql-more3.json":{"engineId":"mysql","engine":"MySQL","order":3,"errors":[{"code":"1049","title":"Unknown database 'name'（未知数据库 'name'）","severity":"caution","meaning":"您要连接或使用的数据库（schema）在服务器上不存在。USE 语句或连接字符串中的默认数据库名指向一个不存在的 schema。","cause":"数据库从未创建、名称拼写错误（大小写敏感性可能因操作系统而异）、已被删除，或应用连接到了错误的环境（dev/prod）。迁移到新服务器时未迁移 schema 也是常见原因。","fix":"列出现有数据库：SHOW DATABASES; 确保名称拼写正确且大小写一致（lower_case_table_names 设置很关键）。如果不存在，创建它：CREATE DATABASE database_name CHARACTER SET utf8mb4; 或从备份还原。在应用的连接配置中确认目标环境。","tags":["database","database","schema","use","name","connection"]},{"code":"1364","title":"Field 'name' doesn't have a default value（字段 'name' 没有默认值）","severity":"caution","meaning":"插入的行没有为定义为 NOT NULL 且无 DEFAULT 的列提供值。在严格 SQL 模式下，MySQL 无法自动填充缺失的必填列，因此报错。","cause":"INSERT 中遗漏了必填（NOT NULL、无 DEFAULT）列，且 sql_mode 中启用了 STRICT_TRANS_TABLES/STRICT_ALL_TABLES。为旧的宽松模式编写的应用迁移到启用严格模式的服务器上时会遇到此错误。","fix":"在 INSERT 中为缺失的列添加值，或在该列上定义合理的 DEFAULT：ALTER TABLE my_table MODIFY my_column ... NOT NULL DEFAULT 'value'; 如果该列确实是可选的，允许 NULL。可以放宽 sql_mode 作为临时/兼容性的权宜之计，但为保证数据质量，建议保持严格模式。","tags":["insert","not null","default","strict mode","sql_mode","column"]},{"code":"1206","title":"The total number of locks exceeds the lock table size（锁的总数超过了锁表大小）","severity":"danger","meaning":"InnoDB 已耗尽为其持有的锁分配的内存（buffer pool 中的锁结构）。当单个事务累积了过多的行锁/间隙锁时发生，事务会被回滚。","cause":"超大的单个事务：一次性 UPDATE/DELETE 数百万行、没有索引的 WHERE 子句锁住整张表，或 innodb_buffer_pool_size 对于该负载过小。大型批处理作业是典型的触发因素。","fix":"将大型 DML 拆分为小批次（例如在循环中用 LIMIT 每次删除/更新 10,000 行，每批之后 COMMIT）。为 WHERE 列建立索引以减少被锁定的行数。作为永久方案，增大 innodb_buffer_pool_size（锁结构从中分配）。避免不必要的长事务。","tags":["innodb","lock","lock","buffer pool","batch","memory"]},{"code":"1236","title":"Got fatal error 1236 from master when reading data from binary log（从 master 读取二进制日志数据时收到致命错误 1236）","severity":"danger","meaning":"副本（slave）服务器在从源（master）的二进制日志读取数据时收到致命错误，复制已停止。副本在源的 binlog 中找不到它期望的位置。","cause":"所需的二进制日志文件在源上已被删除（binlog_expire_logs_seconds —— 8.0 之前为 expire_logs_days，已在 8.4 中移除 —— 或 PURGE BINARY LOGS）；GTID 存在缺口/不匹配；源崩溃后 binlog 位置损坏；或副本落后太多，请求了一个已不存在的日志。典型的子消息是 'Could not find first log file name in binary log index file'。","fix":"检查复制状态：SHOW REPLICA STATUS\\G（8.0.22+；旧版本中为 SHOW SLAVE STATUS，旧语法已在 8.4 中移除）。列出源上可用的 binlog：SHOW BINARY LOGS; 如果缺失的日志已被清除，必须从新的备份/GTID 位置重建副本（mysqldump --source-data —— 8.0.26 之前为 --master-data，在 8.0.26+ 中是已弃用的别名 —— 或 CLONE 插件）。根据负载增加源上的 binlog 保留时间；如果使用 GTID，检查 GTID 缺口。","tags":["replication","replication","binlog","master","gtid","slave"]},{"code":"1129","title":"Host 'host' is blocked because of many connection errors（主机 'host' 因连接错误过多而被阻止）","severity":"danger","meaning":"由于来自某客户端主机的连续连接错误超过了阈值（max_connect_errors），MySQL 阻止了该主机。该主机因迄今为止失败的握手而被列入黑名单。","cause":"负载均衡器/监控工具向 TCP 端口发送不完整或格式错误的握手；网络问题使连接中途断开；或配置错误的客户端不断尝试失败的连接。当错误计数达到 max_connect_errors 时，该主机被阻止。","fix":"立即解除阻止：在 SQL 中执行 TRUNCATE TABLE performance_schema.host_cache;（8.0.23+；FLUSH HOSTS 在 8.0.23 中已弃用并在 8.4 中移除），或在命令行执行 mysqladmin flush-hosts。查看被阻止的主机：SELECT host, ip, sum_connect_errors FROM performance_schema.host_cache; 修复根本原因：将健康检查/监控工具配置为执行完整的 MySQL 握手，并检查网络稳定性。在健康的环境中，可以提高 max_connect_errors 以增加容忍度，但真正重要的是解决实际的连接错误。","tags":["connection","host blocked","max_connect_errors","flush hosts","handshake","network"]},{"code":"1194","title":"Table 'name' is marked as crashed and should be repaired（表 'name' 被标记为已崩溃，应当修复）","severity":"danger","meaning":"一张通常使用 MyISAM 引擎存储的表（MariaDB 中也包括 Aria）被标记为损坏/崩溃，在修复之前无法使用。访问该表的查询会以此错误失败。","cause":"在服务器突然崩溃/断电、磁盘写满、硬件故障或 mysqld 进程被终止时，MyISAM 表未被正常关闭。由于 MyISAM 不提供事务和崩溃安全，这种情况比 InnoDB 频繁得多。","fix":"修复表：REPAIR TABLE table_name; 或离线使用 myisamchk -r。修复前务必备份数据文件。作为永久方案，将表迁移到 InnoDB：ALTER TABLE my_table ENGINE=InnoDB; 使其具备崩溃安全性。检查磁盘健康状况和 myisam_recover_options 设置。","tags":["myisam","crashed","repair","corruption","corruption","innodb"]},{"code":"1615","title":"Prepared statement needs to be re-prepared（预处理语句需要重新准备）","severity":"caution","meaning":"服务器端预处理语句的执行被拒绝，因为其依赖的元数据（表/视图定义）发生变化后该语句已失效。必须重新准备该语句。","cause":"语句准备后对底层表执行了 DDL（ALTER TABLE）、表被重新创建、元数据缓存已满并淘汰了该条目，或 table_definition_cache/table_open_cache 限制过小。在 DDL 频繁的系统以及某些 ORM/连接池场景中很常见。","fix":"大多数客户端库会捕获此错误并自动重新准备语句；在应用中捕获此错误并重试 prepare + execute。如果频繁出现，增大 table_definition_cache 和 table_open_cache。避免在应用流量期间执行不必要的 DDL。","tags":["prepared statement","re-prepare","ddl","metadata","cache","retry"]},{"code":"1553","title":"Cannot drop index needed in a foreign key constraint（无法删除外键约束所需的索引）","severity":"caution","meaning":"您试图删除外键约束所需的索引。InnoDB 不允许删除 FK 所依赖的索引。","cause":"在 InnoDB 中，每个外键都需要在其引用列上有索引。用 DROP INDEX 删除该索引会使 FK 约束失去支撑，因此被拒绝。通常在重组表（清理索引）时遇到。","fix":"先删除外键约束，再删除索引：ALTER TABLE my_table DROP FOREIGN KEY fk_name; 然后 DROP INDEX idx_name ON my_table; 如有需要，重新创建 FK，使其依赖另一个合适的索引。列出现有 FK：SELECT constraint_name, table_name FROM information_schema.key_column_usage WHERE referenced_table_name IS NOT NULL;","tags":["foreign key","foreign key","index","drop index","innodb","constraint"]},{"code":"3948","title":"Loading local data is disabled; this must be enabled on both the client and server sides（加载本地数据已禁用；必须在客户端和服务器端同时启用）","severity":"caution","meaning":"您试图用 LOAD DATA LOCAL INFILE 从客户端文件加载数据，但该功能在客户端和/或服务器端被禁用。出于安全原因，MySQL 8 默认禁用 LOCAL。","cause":"服务器上的 local_infile 系统变量为 OFF，和/或客户端驱动未启用 LOCAL INFILE。由于该功能可能让恶意服务器读取客户端文件，默认受到限制。","fix":"如果确实需要，在两端同时启用：在服务器上执行 SET GLOBAL local_infile = 1; 在客户端启用驱动的 LOCAL INFILE 标志（例如 allowLoadLocalInfile 连接选项 / --local-infile=1）。从安全角度看，推荐的做法是将文件放到服务器上，使用不带 LOCAL 的 LOAD DATA INFILE，或使用批量 INSERT/导入工具。完成后再次禁用 local_infile。","tags":["load data","local infile","local_infile","security","import","loading"]},{"code":"1075","title":"Incorrect table definition; there can be only one auto column and it must be defined as a key（表定义不正确；只能有一个自增列，且必须将其定义为键）","severity":"caution","meaning":"由于违反了 AUTO_INCREMENT 规则，表定义无效：一张表只能有一个自增列，且该列必须是某个键（PRIMARY KEY 或 UNIQUE/索引）的一部分。","cause":"定义了多个 AUTO_INCREMENT 列，或 AUTO_INCREMENT 列不属于任何键（索引）。在 CREATE TABLE 或 ALTER TABLE 期间出现。","fix":"只保留一个 AUTO_INCREMENT 列，并使其成为键的一部分：CREATE TABLE t (id INT AUTO_INCREMENT PRIMARY KEY, ...); 要修复现有表，先将该列加入键：ALTER TABLE t ADD PRIMARY KEY (id); 然后再定义 AUTO_INCREMENT。如果需要多个自增列，请重新审视设计；MySQL 不支持这样做。","tags":["auto_increment","table","primary key","key","ddl","definition"]}]},"errors/mssql-more3.json":{"engineId":"mssql","engine":"SQL Server","order":4,"errors":[{"code":"102","title":"Incorrect syntax near 'token'（'token' 附近有语法错误）","severity":"caution","meaning":"Transact-SQL 解析器在指定标记附近发现无效语法，无法编译该语句。查询在运行之前即被拒绝。","cause":"拼写错误、缺少逗号/括号、未加方括号使用了保留关键字（例如 USER、ORDER）、未结束的字符串字面量，或从其他 SQL 方言复制的不兼容语法。动态 SQL 中的拼接错误也是常见诱因。","fix":"仔细检查所报告标记附近的区域；错误往往在前一行。用方括号括住保留字（[Order]），并检查括号和逗号是否配对。如果构造动态 SQL，用 PRINT @sql 查看生成的文本并运行它。确认该语法受当前版本/兼容级别支持。","tags":["syntax","syntax error","parse","tsql","dynamic sql","keyword"]},{"code":"8114","title":"Error converting data type X to Y（将数据类型 X 转换为 Y 时出错）","severity":"caution","meaning":"由于类型不兼容，值无法从一种数据类型转换为另一种。与错误 245 不同，这通常是参数/表达式层面的类型转换失败（例如 varchar 转 numeric）。","cause":"向数值列/参数发送了非数字或格式错误的文本；小数位数/精度溢出；试图将空字符串（''）转换为数值；或在参数化查询中传入了类型错误的参数。在 ORM 生成的隐式转换中很常见。","fix":"确认被转换的值符合目标类型。使用 TRY_CONVERT / TRY_CAST 进行安全转换；它们在失败时返回 NULL，因此可以用 WHERE 过滤这些行。使参数类型与列类型一致。用 ISNUMERIC/TRY_CAST 扫描数据以找出问题行，并清理空字符串。","tags":["convert","cast","data type","conversion","numeric","try_convert"]},{"code":"1204","title":"The instance of the SQL Server Database Engine cannot obtain a LOCK resource（SQL Server 数据库引擎实例无法获取 LOCK 资源）","severity":"danger","meaning":"SQL Server 无法分配锁管理器所需的内存，无法再创建更多的锁。由于锁内存池已耗尽，操作失败。","cause":"同时持有过多细粒度（行/页）锁：大型、无索引或批量的 DML；锁升级被阻止；服务器上的整体内存压力；或长时间运行的未结事务。偶尔外部内存压力也是诱因。","fix":"将大型操作拆分为小批次，以减少并发锁的数量。使用合适的索引减少扫描/锁定的行数。检查禁用锁升级的配置：跟踪标志 1211/1224 以及 ALTER TABLE ... SET (LOCK_ESCALATION = DISABLE); 用 SELECT name, lock_escalation_desc FROM sys.tables; 和 DBCC TRACESTATUS(-1); 检查。检查服务器内存和 max server memory 设置，并缓解内存压力。","tags":["lock","lock memory","memory","lock escalation","batch","resource"]},{"code":"3906","title":"Failed to update database because the database is read-only（无法更新数据库，因为数据库为只读）","severity":"caution","meaning":"您试图在标记为只读的数据库中修改数据（INSERT/UPDATE/DELETE/DDL）。SQL Server 拒绝了写入。","cause":"数据库被设置为 READ_ONLY；连接到了可用性组中的辅助副本（用于读取）；数据库在快照或还原之后仍为只读；或文件系统为只读。应用被误路由到辅助副本是典型原因。","fix":"如果确实必须可写：ALTER DATABASE mydb SET READ_WRITE; 在可用性组中，将应用路由到主副本（检查 ReadWrite 连接路由 / ApplicationIntent）。只读快照无法写入；改为以源数据库为目标。同时检查文件权限和文件系统。","tags":["read only","read-only database","availability group","replica","read_write","write"]},{"code":"15023","title":"User already exists in the current database（用户在当前数据库中已存在）","severity":"caution","meaning":"向数据库添加新用户时，发现该数据库中已存在同名用户。将登录名映射到现有用户（孤立用户）时也会出现。","cause":"典型情况是数据库从备份还原到另一台服务器后，登录名的 SID 与数据库用户不匹配（孤立用户）。尝试用 CREATE USER 再次添加同名用户，或尝试手动建立登录名与用户的映射，都会引发此错误。","fix":"将孤立用户重新映射到现有登录名：ALTER USER app_user WITH LOGIN = login_name;（推荐使用此方式而非旧的 sp_change_users_login）。如果该用户确实多余：DROP USER app_user; 查找孤立用户：SELECT dp.name FROM sys.database_principals dp LEFT JOIN sys.server_principals sp ON dp.sid = sp.sid WHERE sp.sid IS NULL AND dp.type IN ('S','U');","tags":["user","database user","login","orphaned","restore","orphaned user"]},{"code":"18452","title":"Login failed. The login is from an untrusted domain and cannot be used with Windows authentication（登录失败。该登录名来自不受信任的域，不能用于 Windows 身份验证）","severity":"danger","meaning":"Windows 身份验证登录尝试被拒绝，因为它来自不受信任的域（或在需要 SQL 身份验证的地方使用了 Windows 身份验证）。服务器不信任此身份。","cause":"客户端使用 SQL Server 不信任的域/工作组中的 Windows 身份进行连接；计算机位于不同/不受信任的域中；Kerberos/SPN 配置错误；或在应使用 SQL 身份验证时，连接字符串使用了 Integrated Security。在禁用混合模式时尝试 SQL 登录也会出现类似症状。","fix":"如果需要 SQL 身份验证，将服务器切换为混合模式，并在连接字符串中使用用户名/密码（Integrated Security=false）。如果需要 Windows 身份验证，检查域信任以及 DNS/Kerberos SPN 配置。确保客户端和服务器位于同一个域或受信任的域中。借助错误日志中的 'state' 值缩小原因范围。","tags":["login","authentication","windows","domain","kerberos","mixed mode"]},{"code":"605","title":"Attempt to fetch logical page belongs to a different allocation unit（尝试获取的逻辑页属于另一个分配单元）","severity":"danger","meaning":"在读取页时，SQL Server 发现该页属于另一个对象，而不是预期的对象/分配单元。这是严重的数据库物理/逻辑损坏的有力迹象。","cause":"存储/磁盘故障、控制器或驱动程序缺陷、突然断电时写缓存损坏，或文件系统损坏导致页链接被破坏。“暂时性 605”偶尔可能源于临时 I/O 问题，但持续出现的则是真正的损坏。","fix":"立即运行 DBCC CHECKDB ('mydb') WITH NO_INFOMSGS, ALL_ERRORMSGS 确定损坏范围。如果可能，从已知完好的备份还原（优先选择能将数据丢失降到最低的 PITR）。测试存储/硬件，并确认 PAGE_VERIFY CHECKSUM 设置。REPAIR 选项可能导致数据丢失，因此仅在备份之后作为最后手段使用；在关键环境中，请联系 Microsoft 支持。","tags":["corruption","database corruption","page","allocation unit","dbcc checkdb","storage"]},{"code":"4922","title":"ALTER TABLE ALTER COLUMN failed because one or more objects access this column（ALTER TABLE ALTER COLUMN 失败，因为有一个或多个对象访问此列）","severity":"caution","meaning":"您试图更改列的类型/属性，但依赖该列的对象（索引、约束、计算列、统计信息、架构绑定视图等）阻止了此操作。在解除依赖之前无法更改该列。","cause":"该列被用于索引、PRIMARY KEY/UNIQUE/CHECK/DEFAULT 约束、计算列表达式、以 WITH SCHEMABINDING 定义的视图/函数，或统计信息对象。类型/大小的更改会使这些对象失效，因此 ALTER 被拒绝。","fix":"先找出依赖对象并暂时删除或禁用它们，修改列后再重新创建。查询 sys.sql_expression_dependencies 和 sys.indexes/sys.default_constraints 查看依赖关系。典型顺序：DROP CONSTRAINT/INDEX -> ALTER COLUMN -> 重新 CREATE。对于架构绑定视图，暂时去掉 SCHEMABINDING，之后再加回。","tags":["alter column","dependency","index","constraint","schemabinding","ddl"]},{"code":"7202","title":"Could not find server 'name' in sys.servers（在 sys.servers 中找不到服务器 'name'）","severity":"caution","meaning":"查询中通过链接服务器或四部分名称（server.database.schema.object）引用的服务器名不在已配置的链接服务器中。SQL Server 无法解析该名称。","cause":"链接服务器从未定义；名称拼写错误；在其他环境（dev/prod）中存在的链接服务器在生产环境中缺失；或 OPENQUERY/四部分名称指向了错误的服务器名。在服务器之间迁移脚本时很常见。","fix":"列出已定义的链接服务器：SELECT name FROM sys.servers; 或 EXEC sp_linkedservers。如有需要则添加：EXEC sp_addlinkedserver @server='SERVER', ...; 并用 sp_addlinkedsrvlogin 设置登录映射。确认查询中的四部分名称使用了正确的链接服务器名；如果只访问本地对象，去掉服务器前缀。","tags":["linked server","remote server","sys.servers","openquery","four-part name","configuration"]},{"code":"40501","title":"The service is currently busy. Retry the request after 10 seconds（服务当前繁忙。请在 10 秒后重试请求）","severity":"danger","meaning":"在 Azure SQL Database 中，服务因资源压力（引擎限流）暂时拒绝了请求。系统繁忙，客户端应稍后重试。","cause":"CPU、连接、事务日志写入速率或工作线程数等资源达到所选服务层级（DTU/vCore）的上限；负载突增；或长时间运行/低效的查询消耗了资源。错误附带的 'reason code' 指明了被限流的是哪种资源。","fix":"在应用中实现指数退避重试逻辑；这是处理暂时性错误的预期方式。检查资源使用情况：sys.dm_db_resource_stats 和 Azure 门户指标。优化查询/索引，减少不必要的并发。如果资源持续饱和，提升服务层级（DTU/vCore）。","tags":["azure","throttling","busy","retry","resource","dtu"]}]},"errors/mongodb-more3.json":{"engineId":"mongodb","engine":"MongoDB","order":5,"errors":[{"code":"10107 NotWritablePrimary","title":"not master / not writable primary（不是 master / 不是可写的主节点）","severity":"danger","meaning":"写入（或其他需要主节点的操作）被发送到了当前不是主节点的副本集成员。只有主节点接受写入，因此操作被拒绝。","cause":"应用正在向从节点写入；在故障转移（选举）期间旧主节点变为从节点，而客户端仍连接着它；或驱动的读写偏好和拓扑信息已过期。连接字符串中缺少 replicaSet 参数是典型原因。","fix":"为驱动配置所有副本集成员和副本集名称（在连接字符串中写明 replicaSet=... 和所有主机），使驱动能自动发现主节点并在故障转移时重新路由。使用默认的读写偏好，它会将写入路由到主节点。由于这是暂时性错误，在应用中启用可重试写入。用 rs.status() 确认哪个成员是 PRIMARY。","tags":["replica set","primary","failover","write","election","retryable"]},{"code":"91 ShutdownInProgress","title":"The server is in the process of shutting down（服务器正在关闭）","severity":"danger","meaning":"由于服务器（mongod/mongos）正在关闭，操作无法完成。正在关闭的节点会拒绝新请求并终止正在进行的操作。","cause":"计划内的重启/维护、副本集中主节点 stepdown 之前的关闭、容器/编排器发送的终止信号，或进程因资源耗尽而被停止。在滚动升级期间这是预期现象。","fix":"这通常是暂时性的；在应用中使用重试逻辑和副本集连接，使请求被路由到其他健康成员。如果出乎意料，在 mongod 日志中查看关闭原因（信号、OOM killer、资源）。在滚动维护期间，关闭主节点之前先将其正常降级（rs.stepDown()）。","tags":["shutdown","shutting down","maintenance","stepdown","replica set","retry"]},{"code":"189 PrimarySteppedDown","title":"Primary stepped down while operation was in progress（操作进行期间主节点已降级）","severity":"danger","meaning":"在操作运行期间主节点已降级，不再接受写入。因此正在进行的命令被中止。","cause":"副本集中触发了选举：主节点因维护被手动降级、与多数成员失去联系、优先级发生变化，或出现网络分区。故障转移时正在进行的操作会收到此错误。","fix":"这通常是可重试的错误；在驱动中启用可重试写入/读取后，操作可以被自动路由到新的主节点。在应用中捕获此错误并重试。如果频繁发生，检查副本集健康状况：用 rs.status() 检查成员状态、网络延迟和 oplog 延迟，并减少不必要的选举（heartbeat/timeout 设置）。","tags":["stepdown","election","failover","primary","replica set","retry"]},{"code":"24 LockTimeout","title":"Operation exceeded time limit waiting for a lock（操作等待锁时超出时间限制）","severity":"caution","meaning":"操作在等待所需的锁（例如数据库/集合级别）时超出了时间限制，被中止。未能及时获取到锁。","cause":"长时间运行的操作或 DDL 命令（例如对集合执行的冲突管理操作）持有所需的锁；大量并发写入；或受 maxTimeMS 限制的操作在等待锁时到期。在索引/集合操作相互冲突时出现。","fix":"找出持有锁的操作：用 db.currentOp() 检查活动和等待中的操作；必要时用 db.killOp() 终止它。将冲突的管理命令（索引创建、collMod、compact）移到非高峰时段。保持操作简短，避免不必要的长事务。如有需要，根据负载调整 maxTimeMS。","tags":["lock","lock wait","timeout","currentop","ddl","concurrency"]},{"code":"7 HostNotFound","title":"Could not find host（找不到主机）","severity":"danger","meaning":"MongoDB 无法解析/找到它尝试连接的主机名。驱动或副本集成员无法访问指定主机，因此无法建立连接。","cause":"DNS 解析失败、连接字符串中的主机名错误/过期、客户端无法解析副本集成员的主机名（rs.conf），或网络/访问问题。在容器环境中，无法从外部解析的内部主机名是典型原因。","fix":"确认主机名正确且可解析（用 ping/nslookup 测试 DNS）。更新连接字符串中的主机。确保客户端和成员都能解析副本集成员名称；必要时将 rs.conf() 中的主机名更新为可访问的 FQDN/IP。如果使用 SRV（mongodb+srv），检查 DNS SRV 记录。","tags":["host","dns","connection","replica set","network","srv"]},{"code":"175 QueryPlanKilled","title":"Query plan killed during execution（查询计划在执行期间被终止）","severity":"caution","meaning":"由于底层数据/集合发生变化，正在运行的查询的计划失效并被终止。查询因无法安全继续而被中止。","cause":"在查询运行期间（尤其是游标处于打开状态时），集合被删除、其使用的索引被删除，或发生了重命名/重新配置。长时间运行的 getMore 调用期间发生底层变更是典型诱因。","fix":"重新启动查询/游标（重试）。确保在查询运行期间不对集合或索引执行破坏性 DDL（drop、dropIndex、rename）；将此类维护安排在查询流量之外。缩短长时间运行的读取，并确保所需的索引是长期保留的。","tags":["query","cursor","getmore","drop","index","plan"]},{"code":"11602 InterruptedDueToReplStateChange","title":"Operation interrupted due to a replication state change（操作因复制状态变化而被中断）","severity":"danger","meaning":"由于副本集状态发生变化（例如主节点变更），操作被中断。正在进行的命令因无法在新拓扑中安全完成而被终止。","cause":"在选举、主节点 stepdown、成员角色变更或配置更改期间，正在进行的操作会被中断。它与 189 PrimarySteppedDown 根源相同，通常在故障转移时出现。","fix":"这是可重试的错误；在驱动中启用可重试写入/读取后，可以针对新的主节点重试操作。在应用中捕获此错误并重试。如果频繁出现，调查选举的原因：网络不稳定、heartbeat 超时、过载或 oplog 延迟。用 rs.status() 监控副本集健康状况。","tags":["replica set","election","failover","interrupted","retry","state change"]},{"code":"136 CappedPositionLost","title":"Capped collection position lost（固定集合位置丢失）","severity":"caution","meaning":"读取固定大小（capped）集合的游标（通常是 tailable 游标）丢失了位置。由于集合循环覆盖，游标所在位置的文档已不存在。","cause":"capped 集合写满后，最旧的文档会被自动删除并覆盖；如果集合的写入速度超过 tailable 游标的消费速度，游标就会落后，其位置被覆盖。出现在跟踪 oplog 等 capped 结构的消费者中。","fix":"重新定位游标（打开新的 tailable 游标）并重新开始跟踪；在应用中捕获此错误并刷新读取。如果根本原因是消费者跟不上写入速率：增大 capped 集合/oplog 的大小或加快消费者速度。要可靠地跟踪变更，尽可能使用 change streams 代替 tailable 游标。","tags":["capped","tailable cursor","oplog","cursor","position","change stream"]},{"code":"28 PathNotViable","title":"Cannot use the part of the path to traverse the element（无法使用路径的一部分遍历元素）","severity":"caution","meaning":"更新试图遍历以点分隔的字段路径（例如 a.b.c），但路径的一部分遇到了不能包含子字段的类型（标量值或类型错误的元素）。因此无法遍历该路径。","cause":"在文档中的字段 'a' 是标量（例如数字/字符串）而非对象/数组时，向 a.b.c 这样的嵌套路径写入；或类型不匹配，例如在需要数组索引的位置遇到对象。在架构不一致的文档中很常见（用 $set 更新深层路径时）。","fix":"更新前确保路径为预期类型（对象/数组）。修正不兼容的文档，或使更新以文档结构为条件。必要时先将字段转换为正确类型（例如用对象替换标量值），然后再写入嵌套字段。为保证架构一致性，考虑使用 schema validation。","tags":["update","path","dotted","type","set","schema"]}]},"errors/redis-more3.json":{"engineId":"redis","engine":"Redis","order":6,"errors":[{"code":"BUSYGROUP Consumer Group name already exists","title":"Consumer group already exists（消费者组已存在）","severity":"caution","meaning":"您试图用 XGROUP CREATE 在 stream 上创建消费者组，但该 stream 上已存在同名组。Redis 不会覆盖已有的组。","cause":"应用在启动时无条件地尝试创建组，而该组是上次运行遗留下来的；或多个实例并行尝试创建同一个组。是非幂等启动逻辑的典型表现。","fix":"使组的创建幂等：捕获并忽略该错误（组已存在没有问题）。使用 XGROUP CREATE stream group $ MKSTREAM，在 stream 尚不存在时自动创建它。运行 XINFO GROUPS stream 查看现有组。如果确实要重置，先用 XGROUP DESTROY stream group 删除它。","tags":["stream","consumer group","xgroup","busygroup","idempotent","mkstream"]},{"code":"NOGROUP No such key or consumer group","title":"No such key or consumer group in stream（stream 中不存在该键或消费者组）","severity":"caution","meaning":"您运行了 stream 消费命令（XREADGROUP、XACK、XCLAIM 等），但指定的键（stream）或消费者组不存在。未找到要操作的组/stream。","cause":"组从未创建（跳过了 XGROUP CREATE）；stream 键已被删除或从未写入；组名/键拼写错误；或组已被 XGROUP DESTROY 删除。在组创建之前消费者就已启动时很常见。","fix":"首先确认 stream 和组存在：XINFO STREAM stream 和 XINFO GROUPS stream。如果组不存在，创建它：XGROUP CREATE stream group $ MKSTREAM（必要时也会创建 stream）。在应用启动时增加创建组的步骤（忽略 BUSYGROUP）。确认键名和组名拼写正确。","tags":["stream","consumer group","nogroup","xreadgroup","xgroup","key"]},{"code":"NOTBUSY No scripts in execution right now","title":"No scripts in execution right now（当前没有正在执行的脚本）","severity":"safe","meaning":"您发送了 SCRIPT KILL 或 FUNCTION KILL，但当前没有正在运行（且可终止）的 Lua 脚本/函数。由于没有可终止的对象，命令被拒绝。","cause":"在您尝试停止脚本时，它已经执行完毕；SCRIPT KILL 发送到了错误的节点（而脚本在另一个实例上运行）；或 BUSY 状态在 SCRIPT KILL 之前已自行解除。通常是时序问题。","fix":"如果实际上没有卡住的脚本，则无需处理；这是一个安全/提示性的回复。如果收到 BUSY 错误（脚本超过了 busy-reply-threshold，Redis 7 之前名为 lua-time-limit），在脚本实际运行的正确节点上尝试 SCRIPT KILL（函数使用 FUNCTION KILL）。如果脚本已修改数据，SCRIPT KILL 无效；作为最后手段可能需要 SHUTDOWN NOSAVE。将长脚本拆分，并遵守时间限制。","tags":["lua","script kill","notbusy","busy","script","function"]},{"code":"DENIED Redis is running in protected mode","title":"Redis is running in protected mode（Redis 正以保护模式运行）","severity":"danger","meaning":"Redis 正以保护模式运行，拒绝了来自外部（非回环地址）的无密码连接。出于安全考虑，未经身份验证的远程访问被阻止。","cause":"绑定到所有接口（或 bind 设置宽松）的 Redis 在未配置密码（requirepass）的情况下收到了外部客户端的连接。protected-mode 默认开启，在这种情况下会阻止非回环请求。这表明一个未加防护的 Redis 被意外暴露到了互联网上。","fix":"正确的修复方式是加固：用 requirepass（或 ACL 用户）设置强密码，并用 bind 只在受信任的接口上监听。如果需要远程访问，用密码/TLS 保护，并用防火墙限制 6379 端口。protected-mode no 只能在封闭/安全的网络中临时设置，对于面向互联网的部署绝对不推荐。","tags":["protected mode","security","denied","requirepass","bind","auth"]},{"code":"ERR Can't execute '<cmd>': only (P|S)SUBSCRIBE / (P|S)UNSUBSCRIBE / PING / QUIT / RESET are allowed in this context","title":"Command not allowed in subscribe context（在订阅上下文中不允许该命令）","severity":"caution","meaning":"连接进入 Pub/Sub 订阅模式后，您试图运行在此模式下不允许的普通命令（例如 GET/SET）。处于订阅模式的连接只接受有限的命令集。在旧版本中，消息为 \"ERR only (P)SUBSCRIBE / (P)UNSUBSCRIBE / PING / QUIT allowed in this context\"。","cause":"在已通过 SUBSCRIBE/PSUBSCRIBE/SSUBSCRIBE 切换到订阅模式的连接上发送数据命令。在 RESP2 协议中，已订阅的连接只允许 (P|S)SUBSCRIBE、(P|S)UNSUBSCRIBE、PING、QUIT 和 RESET。用同一个连接同时处理 pub/sub 和普通工作是典型错误。","fix":"为 Pub/Sub 使用专用连接，并为普通命令另开一个连接（大多数客户端库会自动这样做）。发送 UNSUBSCRIBE/PUNSUBSCRIBE 退出订阅模式；之后普通命令即可正常工作。在 RESP3 协议（HELLO 3）中，已订阅的连接上也允许其他命令；如果驱动支持，切换到 RESP3。","tags":["pubsub","subscribe","context","resp","connection","hello"]},{"code":"ERR SELECT is not allowed in cluster mode","title":"SELECT is not allowed in cluster mode（集群模式下不允许 SELECT）","severity":"caution","meaning":"在 Redis Cluster 模式下，您试图用 SELECT 切换到其他逻辑数据库（DB 索引）。集群模式只支持数据库 0；无法使用多个 DB。","cause":"在单机 Redis 上用 SELECT 在 DB 0-15 之间切换的应用，迁移到 Redis Cluster 后仍在发送相同的命令。由于集群架构按槽分布数据，不支持多 DB 的概念。","fix":"在集群模式下删除 SELECT 调用；只使用 DB 0。如果需要逻辑隔离，使用键前缀（命名空间）（例如 app1:key）。如果某些键必须一起处理，使用哈希标签（{user123}）使它们落在同一槽中。使应用配置能够感知集群。","tags":["cluster","select","database","namespace","hash tag","slot"]},{"code":"ERR wrong number of arguments for command","title":"Wrong number of arguments for command（命令的参数数量错误）","severity":"caution","meaning":"发送的 Redis 命令参数少于或多于预期。由于不满足语法，命令未被处理即被拒绝。","cause":"缺少/多出参数（例如只写了 SET key 而不是 SET key value）；变量以空值/undefined 传入；以错误的参数顺序调用命令；或客户端库中数组/参数展开（spread）有误。在动态构造的命令中很常见。","fix":"检查命令的正确签名（例如 SET key value [options]）；查阅官方命令参考。在客户端代码中，确认参数（尤其是来自数组的参数）不为空/未缺失。如果根据用户输入构造命令，事先校验参数数量。在 redis-cli 中手动尝试该命令，确认预期的参数数量。","tags":["syntax","arguments","argument","command","syntax","cli"]},{"code":"ERR Background save already in progress","title":"Background save already in progress（后台保存已在进行中）","severity":"caution","meaning":"您试图启动新的后台保存（BGSAVE）或 AOF 重写，但已有后台保存正在进行。Redis 不允许同时运行第二个保存进程。","cause":"在上一次完成之前触发了新的 BGSAVE/BGREWRITEAOF；在大数据集上 fork/保存耗时较长时，监控脚本或应用反复调用 BGSAVE。磁盘较慢或 RDB 文件较大会延长该过程。","fix":"等待正在运行的保存完成；用 INFO persistence 中的 rdb_bgsave_in_progress 和 aof_rewrite_in_progress 字段以及 LASTSAVE 时间戳检查其状态。为应用/自动化逻辑加上条件，使其在上一次保存完成之前不触发新的 BGSAVE。如果保存耗时过长，调查磁盘速度和 RDB 大小。","tags":["bgsave","persistence","rdb","aof","background save","fork"]},{"code":"ERR bit offset is not an integer or out of range","title":"Bit offset is not an integer or out of range（位偏移量不是整数或超出范围）","severity":"caution","meaning":"传给 SETBIT/GETBIT/BITCOUNT 等位操作的位偏移量无效：不是整数或超出允许范围。Redis 无法在该偏移量处执行位操作。","cause":"传入非数字值作为偏移量；使用负偏移量；或给出超过允许上限的超大偏移量（位串最大 512 MB，即约 2^32 位）。源于基于 bitmap 的计数器/标志结构中的计算错误。","fix":"确保偏移量是允许范围内的非负整数（0 到 ~2^32-1；字符串最大 512 MB）。校验偏移量计算（例如由用户 ID 推导出的偏移量）是否溢出/为负。如果需要非常大的偏移量，将数据设计为拆分到多个键中（分片）。","tags":["bitmap","setbit","offset","integer","range","bit"]}]},"errors/elasticsearch-more3.json":{"engineId":"elasticsearch","engine":"Elasticsearch","order":7,"errors":[{"code":"node_not_connected_exception","title":"Node is not connected（节点未连接）","severity":"danger","meaning":"某个节点当前未与其需要通信的另一个节点建立连接。由于集群内部（传输层）连接已断开，请求无法转发到目标节点。","cause":"网络中断或丢包、目标节点崩溃/重启、防火墙/端口阻断（默认传输端口 9300）、节点因 GC 停顿暂时无响应，或 DNS/主机解析问题。在滚动重启期间可能暂时出现。","fix":"检查集群和节点健康状况：用 GET _cluster/health 和 GET _cat/nodes?v 查看缺少的是哪个节点。确认节点之间的网络访问以及传输端口（9300）的防火墙规则。检查受影响节点的日志中是否有崩溃、GC 停顿或 OOM。检查网络稳定性以及 discovery/seed host 配置。","tags":["transport","node","connection","network","9300","cluster"]},{"code":"receive_timeout_transport_exception","title":"Request timed out waiting for a response from another node（等待另一节点响应时请求超时）","severity":"danger","meaning":"一个节点向另一个节点发送了请求，但响应未在配置的时间内到达，请求超时。节点间（transport）调用无法完成。","cause":"目标节点负载过高、长时间 GC 停顿、磁盘缓慢/CPU 压力、网络延迟/丢包，或目标节点过于繁忙而无法响应。在大型查询、大批量操作和分片再平衡期间很常见。","fix":"检查受影响节点的资源使用情况：用 GET _nodes/stats 检查 JVM 堆、GC 和线程池饱和情况。用 GET _cat/thread_pool?v 检查被拒绝/排队的任务。测量网络延迟。降低负载（更小的查询/批量大小），并检查堆和硬件资源。如果问题持续，增加节点以分摊负载。","tags":["transport","timeout","gc","load","network","thread pool"]},{"code":"routing_missing_exception","title":"Routing value is required for this document（此文档需要 routing 值）","severity":"caution","meaning":"访问/索引该文档需要 routing 值，但未提供。没有 routing，Elasticsearch 无法确定文档属于哪个分片。","cause":"索引映射定义了 _routing required=true；使用了 parent-join（父/子）关系，子文档需要 routing；或 index/get/update/delete 请求中遗漏了 routing 参数。读取用自定义 routing 写入的文档时未提供 routing 是典型原因。","fix":"在请求中显式指定 routing 值（例如在 index/get/update/delete 调用中使用 ?routing=value）。使用 parent-join 时，为子文档传入父文档的 id 作为 routing。读取/更新/删除时使用与写入文档时相同的 routing。检查映射中的 _routing 要求（required）。","tags":["routing","shard","parent-child","join","mapping","index"]},{"code":"too_many_buckets_exception","title":"Trying to create too many buckets（试图创建过多的桶）","severity":"caution","meaning":"聚合查询试图生成的桶数超过了允许的上限（search.max_buckets，默认 65,536）。为保护内存，查询被拒绝。","cause":"在高基数字段上以非常大的 size 或非常细的间隔运行 terms/date_histogram 等聚合；嵌套聚合使桶数成倍增加。由于创建过多的桶可能耗尽节点内存，因此强制执行该限制。","fix":"减少生成的桶数：降低 terms 聚合的 size，放宽 date_histogram 的间隔，删除不必要的嵌套聚合。如果需要大量结果，用 composite 聚合分页获取。提高限制应作为最后手段，并且存在内存风险：可以用 PUT _cluster/settings 修改 search.max_buckets，但要谨慎。","tags":["aggregation","bucket","max_buckets","terms","composite","memory"]},{"code":"shard_lock_obtain_failed_exception","title":"Obtaining shard lock timed out（获取分片锁超时）","severity":"danger","meaning":"Elasticsearch 未能在超时时间内获取操作分片所需的本地目录锁（shard lock）。分片无法启动或重新分配。","cause":"同一分片的先前副本（关闭/迁移之后）尚未释放锁；I/O 缓慢/挂起；磁盘问题；或节点快速重启后未清理过期的分片状态。通常是暂时性的，但如果持续存在，分片会一直处于 UNASSIGNED 状态。","fix":"检查分片未分配的原因：GET _cluster/allocation/explain。大多数情况下锁会很快释放，集群会自行重试；必要时用 POST _cluster/reroute?retry_failed=true 触发重新分配。如果问题持续，检查受影响节点日志中的 I/O/磁盘错误并确认磁盘健康状况。干净地重启有问题的节点可以清除该锁。","tags":["shard","lock","unassigned","allocation","io","reroute"]},{"code":"parsing_exception","title":"Failed to parse the search request / query DSL（无法解析搜索请求/query DSL）","severity":"caution","meaning":"提交的查询体（query DSL）无法解析。由于请求的 JSON 结构或查询语法与 Elasticsearch 预期的格式不符，查询无法运行。","cause":"无效的 JSON、位置错误/未知的查询键、查询类型（例如 bool、range）的结构使用错误、在版本之间被移除/更改的字段名，或多出/缺少括号。在手写或动态构造的查询中很常见。","fix":"根据错误消息中的行/位置提示检查查询体；确认 JSON 有效。将 query DSL 结构与官方参考进行比较（bool/must/filter 的位置等）。更新在版本升级中被移除的语法。逐步简化查询以定位出错部分；尽可能使用客户端库的查询构造器。","tags":["query dsl","parse","json","syntax","search","bool"]},{"code":"max_bytes_length_exceeded_exception","title":"Document contains at least one immense term that is too long（文档中至少包含一个过长的超大词项）","severity":"caution","meaning":"在被索引的文档中，某个词项（尤其是 keyword/不分析字段的值）超过了允许的最大字节长度（32766 字节）。Lucene 无法索引如此长的词项。","cause":"向 keyword 字段（作为整体）写入了非常长的文本值；例如把整个文档正文、base64 数据或很长的 URL/标识符放进 keyword 字段。由于 keyword 字段不做分析，该值被作为单个词项索引，从而超出限制。","fix":"长文本使用 text（分析型）字段；keyword 仅用于需要精确匹配的短值。要限制值的长度，在映射中使用 ignore_above（超过该长度的值不会被索引，但仍会被存储）。如果不需要，将超长字段设置为 index:false。根据数据类型重新评估字段设计（text 还是 keyword）。","tags":["keyword","term","ignore_above","lucene","mapping","length"]},{"code":"illegal_argument_exception_total_fields","title":"Limit of total fields has been exceeded（已超出字段总数限制）","severity":"caution","meaning":"索引映射中的字段总数超过了允许的上限（index.mapping.total_fields.limit，默认 1000）。由于无法添加新字段，索引失败。","cause":"通常是映射爆炸：在启用动态映射时，非结构化/键可变的 JSON 文档（例如键来自用户数据的对象）会将每个新字段都加入映射。随着时间推移字段数迅速增长，直至达到限制。","fix":"要解决根本原因，用 flattened 字段或键值对数组（而非对象）来建模键可变的数据；限制动态映射（dynamic: false 或 strict）。如果确实需要更多字段，谨慎提高限制：PUT idx/_settings {\"index.mapping.total_fields.limit\": 2000}。用 GET idx/_mapping 检查字段数，以监控映射爆炸。","tags":["mapping","total_fields","dynamic mapping","flattened","limit","field"]},{"code":"verification_exception","title":"verification_exception: Found N problem(s) — Unknown column / Unknown index (ES|QL, SQL, EQL)（验证异常：发现 N 个问题 —— 未知列/未知索引）","severity":"caution","meaning":"ES|QL（_query）、SQL（_sql）或 EQL 查询在语法上解析成功，但未通过语义验证：未找到所引用的索引、字段（列）或函数，或类型不兼容。错误文本会给出问题所在的行/列位置（例如 \"line 1:18: Unknown column [field]\"）。快照仓库验证失败不是此代码，而是 repository_verification_exception。","cause":"字段名拼写错误或不在映射中；索引模式不存在或未匹配到任何索引；对 text 字段执行不受支持的操作（例如在 text 字段而不是其 keyword 子字段上分组）；不兼容类型之间的比较或未知函数。","fix":"根据错误消息中的 行:列 信息修正查询。确认字段名和类型：GET idx/_mapping（SQL 使用 DESCRIBE idx / SHOW COLUMNS）。用 GET _cat/indices 检查索引模式。分组/排序使用 keyword 子字段（field.keyword）；对于类型不匹配，进行显式转换（ES|QL：TO_STRING、TO_INTEGER 等）。","tags":["esql","sql","eql","verification","unknown column","query"]}]},"errors/oracle-more4.json":{"engineId":"oracle","engine":"Oracle","order":1,"errors":[{"code":"ORA-19804","title":"cannot reclaim N bytes disk space from M bytes limit（无法从 M 字节的限制中回收 N 字节磁盘空间）","severity":"danger","meaning":"Oracle 试图在快速恢复区（FRA）中为新文件（归档日志、备份、闪回日志）腾出空间，但由于已达到 DB_RECOVERY_FILE_DEST_SIZE 定义的逻辑限制，无法回收所需空间。","cause":"FRA 大小（DB_RECOVERY_FILE_DEST_SIZE）是独立于物理磁盘的上限，归档日志、RMAN 备份或闪回日志已将其占满。根本原因通常是归档日志未定期备份和删除，或过时的备份未被清理。","fix":"查看 FRA 使用情况：SELECT * FROM v$recovery_area_usage; 以及 SELECT * FROM v$recovery_file_dest; 将归档日志备份到 FRA 之外的目标并删除（RMAN：BACKUP ARCHIVELOG ALL FORMAT '/backup/arc_%U' DELETE INPUT;），或清理不再需要的备份（RMAN：DELETE OBSOLETE;）。使用 Data Guard 时，不要删除尚未在备库应用的归档日志。作为永久方案，如果有磁盘空间，增大 DB_RECOVERY_FILE_DEST_SIZE。如果不需要 Flashback Database，考虑将其禁用。","tags":["fra","recovery area","archived log","rman","db_recovery_file_dest_size","disk"]},{"code":"ORA-00494","title":"enqueue held for too long by 'inst N, osid M'（enqueue 被 'inst N, osid M' 持有时间过长）","severity":"danger","meaning":"某个关键资源 enqueue（例如 CF —— 控制文件 enqueue）被某进程持有的时间远超允许值，Oracle 为防止系统挂起而终止了该进程。在 alert.log 中与 'killing enqueue blocker' 条目一同出现。","cause":"由于 I/O 瓶颈、严重的 CPU 压力或操作系统停顿（swapping、磁盘缓慢），某个后台或用户进程持有重要 enqueue 的时间超过了允许值；对于控制文件（CF）enqueue，此时间由 _controlfile_enqueue_timeout 设定（默认 900 s）。隐含（下划线）参数只应在 Oracle Support 指导下修改。通常由存储延迟或 LGWR/CKPT 等进程缓慢触发。","fix":"查看 alert.log 和被终止进程的跟踪文件，确定涉及哪个 enqueue（通常是控制文件）以及哪个后台进程。真正的原因通常是存储/I/O 延迟：检查磁盘响应时间、多路径和控制器健康状况。降低 CPU/内存压力和 swap 使用。如果反复出现，将跟踪分析结果提供给 Oracle Support。","tags":["enqueue","control file","i-o","storage","hang","background process"]},{"code":"ORA-04021","title":"timeout occurred while waiting to lock object（等待锁定对象时超时）","severity":"caution","meaning":"在等待获取对象（包、过程、类型等）上的 library cache lock/pin 时发生超时。通常在试图编译或修改正被另一会话执行或锁定的 PL/SQL 对象时出现。","cause":"要修改/编译的对象正在另一个会话中执行，或被另一个 DDL 锁定，因此无法获取排他的 library cache 锁。长时间运行的过程调用或挂起的会话可能正持有该锁。","fix":"找出锁定该对象的会话：SELECT * FROM dba_kgllock WHERE kgllkhdl IN (SELECT p1raw FROM v$session WHERE event LIKE 'library cache%'); 或用 v$access 和 v$session 找出正在使用该对象的会话。在负载较低的维护窗口内重试 DDL/编译。必要时安全地终止该会话。短暂停止应用可以更容易地获取锁。","tags":["library cache","lock","plsql","ddl","compile","timeout"]},{"code":"ORA-04061","title":"existing state of package/procedure has been invalidated（包/过程的现有状态已失效）","severity":"caution","meaning":"由于被调用的 PL/SQL 对象（包、过程、函数）所依赖的对象发生了变化，其现有状态已失效。这是伴随 ORA-04068 出现的状态丢失情形的一种变体；会话持有的包状态已不再有效。","cause":"在会话使用某个包期间，对该包或其依赖对象执行了 DDL（表结构更改、重新编译、授权更改）。结果会话现有的包状态失效。在部署频繁、应用运行期间进行架构更改的环境中出现。","fix":"同一调用通常第二次就会成功（因为状态已被重置）；在应用中捕获此错误并安全重试。在应用流量较低时/维护窗口内进行架构更改。批量编译无效对象：EXEC DBMS_UTILITY.COMPILE_SCHEMA('APP_SCHEMA'); 尽量减少包状态（全局变量）的设计可以降低此影响。","tags":["plsql","package","invalidate","ddl","state","recompile"]},{"code":"ORA-01466","title":"unable to read data - table definition has changed（无法读取数据 —— 表定义已更改）","severity":"caution","meaning":"读取过去某一时间点数据的查询（Flashback Query、AS OF 或一致性读）失败，因为在所查询的过去时间与当前之间表定义（DDL）发生了变化。Oracle 无法用当前定义安全地读取该时间点的数据。","cause":"在 Flashback/AS OF 查询所针对的时间戳之后，对表执行了 DDL（添加/删除列、类型更改）。此外，客户端与服务器之间的时钟差异，或查询非常近的过去（几秒钟）时，也可能因 SCN/时间精度而引发此错误。","fix":"将闪回查询指向表发生 DDL 更改之前的时间/SCN。尽可能使用确切的 SCN 而不是时间戳（AS OF SCN）。对于非常近的过去的查询，再往前回退几秒。如果需要长期访问历史数据，考虑采用版本化或归档表设计，而不是依赖 DDL。","tags":["flashback","as of","ddl","consistent read","scn","query"]},{"code":"ORA-08176","title":"consistent read failure; rollback data not available（一致性读失败；回滚数据不可用）","severity":"danger","meaning":"在构建一致性读映像时，查询遇到了被不生成 undo 的操作修改过的块；由于这些块不存在 undo/回滚数据，无法构建一致性映像。与 ORA-01555 不同，这里的 undo 是从未生成，而不是被覆盖。（可串行化隔离错误是另一个代码：ORA-08177。）","cause":"在查询进行期间，同一对象上运行了不生成 undo 的操作：直接路径加载（INSERT /*+ APPEND */、SQL*Loader DIRECT=TRUE）、CREATE INDEX / 索引重建或离散事务。","fix":"在直接路径加载或索引操作完成后重新运行查询（重试）。不要在同一时间窗口内对同一对象同时安排长报表和直接路径加载/索引维护作业。如果反复出现，借助 alert 日志和 ASH 找出冲突的作业并错开调度。","tags":["undo","consistent read","direct path","append","create index","retry"]},{"code":"ORA-00020","title":"maximum number of processes (N) exceeded（超出最大进程数 N）","severity":"danger","meaning":"实例的进程数达到了 PROCESSES 初始化参数定义的上限，无法创建新的连接/进程。新会话无法连接；甚至管理员也可能无法连接。","cause":"应用连接池过大、连接从不关闭（泄漏）、并发用户多于预期，或 PROCESSES 参数设置过低，导致超出限制。有时挂起的后台作业或并行查询进程也会占用进程。","fix":"可以用 sqlplus -prelim / as sysdba 连接，但此连接无法运行 SQL（无法查询 v$session）；它只能用于 oradebug（例如 hanganalyze）和 SHUTDOWN ABORT。要释放槽位，在操作系统级别谨慎终止空闲的应用服务器进程（ps -ef | grep 'LOCAL=NO'）；然后用普通 SYSDBA 连接，通过 v$session/v$process 找出泄漏的会话，用 ALTER SYSTEM KILL SESSION 关闭它们。作为永久方案，增大 PROCESSES 并重启实例（如果 spfile 中显式设置了 SESSIONS，它不会被重新推导；也要一并更新）。在应用端，修正连接池最大值和空闲连接清理。","tags":["processes","process","connection","limit","pool","init parameter"]},{"code":"ORA-01113","title":"file N needs media recovery（文件 N 需要介质恢复）","severity":"danger","meaning":"正在使数据文件联机或打开数据库，但该文件不一致；必须先进行介质恢复才能继续。该文件的 SCN 比控制文件预期的更旧/不一致。","cause":"数据文件是从旧备份还原的、在脱机期间未应用 redo，或在实例/存储崩溃后文件不是最新的。通常是还原后跳过了 recover 步骤，或文件长时间处于脱机状态所致。","fix":"恢复该文件或数据库：RECOVER DATAFILE N; 或 RECOVER DATABASE; 然后 ALTER DATABASE DATAFILE N ONLINE; 或 ALTER DATABASE OPEN; 确保所需的归档日志可访问。使用 RMAN：必要时在 RESTORE 之后运行 RECOVER DATAFILE N;。跟踪 alert.log 查看请求的是哪些日志。","tags":["media recovery","datafile","redo","restore","recover","scn"]},{"code":"ORA-02393","title":"exceeded call limit on CPU usage（超出单次调用的 CPU 使用限制）","severity":"caution","meaning":"分配给会话的资源概要文件中定义的单次调用允许 CPU 时间（CPU_PER_CALL）被超出，Oracle 终止了该调用。这是一种资源限制的保护措施。","cause":"用户的概要文件中定义了 CPU_PER_CALL 限制且 RESOURCE_LIMIT=TRUE；某个 SQL/PLSQL 调用超过了此 CPU 阈值。通常由消耗 CPU 远超预期的低效/失控查询触发。","fix":"优化查询（合适的索引、正确的执行计划、去除不必要的全表扫描），使 CPU 消耗降到限制以下。如果业务需求合理，为相应用户在概要文件中增大 CPU_PER_CALL 或设置为 UNLIMITED：ALTER PROFILE ... LIMIT CPU_PER_CALL ...; 考虑使用 Database Resource Manager 代替概要文件，更灵活地管理资源。","tags":["profile","cpu_per_call","resource_limit","resource","query","optimization"]},{"code":"ORA-12801","title":"error signaled in parallel query server Pnnn（并行查询服务器 Pnnn 中发出错误信号）","severity":"danger","meaning":"在并行运行的查询中，某个并行执行服务器（parallel query slave）遇到了错误，并将该错误传递给了查询协调器。ORA-12801 是一个外层包装；实际错误在其后的第二行。","cause":"某个 slave 进程在并行操作期间遇到了真正的错误：TEMP 表空间不足（ORA-01652）、PGA 内存耗尽（ORA-04030）、数据损坏、并行进程限制或其他底层 SQL 错误。ORA-12801 只说明错误发生在并行上下文中。","fix":"关注紧随 ORA-12801 之后的实际错误代码并加以解决（例如扩大 TEMP、调整 PGA、修复损坏块）。必要时通过串行运行查询、去掉 PARALLEL 提示或降低并行度来定位实际错误。检查 PARALLEL_MAX_SERVERS 和资源使用情况。","tags":["parallel","parallel query","slave","temp","pga","wrapper"]}]},"errors/postgres-more4.json":{"engineId":"postgres","engine":"PostgreSQL","order":2,"errors":[{"code":"23P01","title":"exclusion_violation","severity":"danger","meaning":"违反了 EXCLUDE（排除）约束。此类约束根据特定运算符（例如范围重叠 &&）防止两行发生冲突；插入/更新的行因与现有行冲突而被拒绝。","cause":"通常定义了基于 GiST 的 EXCLUDE 约束（例如在预约/会议室预订中用 tstzrange && 禁止时间重叠），而新行的范围/值与现有行重叠。两个并发事务插入重叠范围时也会出现。","fix":"插入前用查询检查是否重叠，或在应用中捕获该错误并向用户显示有意义的消息。查询相关的范围/条件，找出冲突的现有行。如果业务规则允许重叠，放宽约束；否则修正输入。在并发情况下，安全地重试事务以使其串行化。","tags":["exclude","constraint violation","gist","range","overlap","constraint"]},{"code":"23001","title":"restrict_violation","severity":"danger","meaning":"SQL 标准中的 restrict_violation 代码。重要：对于定义为 ON DELETE/ON UPDATE RESTRICT 或 NO ACTION 的外键，PostgreSQL 报告违反时使用的不是此代码，而是 23503（foreign_key_violation）；实际上您不会从 PostgreSQL 核心看到此代码。","cause":"删除带有依赖子行的父行或更改其键时，PostgreSQL 返回 23503。23001 通常出现在从其他数据库移植过来的应用代码中，或作为扩展的自定义错误。与 NO ACTION 不同，RESTRICT 不能将检查推迟到事务结束，会立即失败。","fix":"先删除/更新依赖的子行或将其移到另一个父行下；然后再处理父行。如果业务规则允许，考虑以 ON DELETE CASCADE 或 SET NULL 重新定义外键。在批量删除时，采用正确的删除顺序（先子后父）。","tags":["foreign key","restrict","fk","delete","reference","constraint"]},{"code":"42P17","title":"invalid_object_definition","severity":"danger","meaning":"对象（函数、排序规则、触发器、规则等）的定义无效。PostgreSQL 表明给定的定义内部不一致或不可接受；无法按所写内容创建/修改该对象。","cause":"常见原因：函数中相互矛盾的属性组合（例如标记为 IMMUTABLE 却表现为 volatile）、无效的排序规则/字符集定义，或不合适的触发器/规则定义。行级安全（RLS）和某些 DDL 中不一致的选项也可能触发。","fix":"阅读错误详情（DETAIL/HINT），确定定义的哪一部分被拒绝。使函数的易变性标签与实际行为一致（VOLATILE/STABLE/IMMUTABLE）。确认排序规则名称存在于 pg_collation 中。按照文档规定的语法/规则修正定义后重新运行。","tags":["function","collation","definition","volatility","ddl","object"]},{"code":"42710","title":"duplicate_object","severity":"caution","meaning":"要创建的对象（角色、类型、约束、扩展、模式、索引名等）已存在。PostgreSQL 不允许存在同名/同定义的第二个对象。","cause":"在已存在同名对象的情况下运行了 CREATE ROLE、CREATE TYPE、CREATE EXTENSION 或 ADD CONSTRAINT 等命令。通常由脚本运行两次、非幂等的迁移，或两个并发会话试图创建同一对象引起。","fix":"检查对象是否存在（角色查 pg_roles，类型查 pg_type，扩展查 pg_extension）。以幂等方式编写迁移：在支持的地方使用 CREATE ... IF NOT EXISTS，或先运行 DROP ... IF EXISTS。对于约束冲突，重命名现有约束或删除后重新创建。","tags":["duplicate","role","type","extension","constraint","migration"]},{"code":"22008","title":"datetime_field_overflow","severity":"caution","meaning":"在计算或解析日期/时间值时，某个字段超出了有效范围。例如产生了 13 月或 32 日这样不可能的组成部分，或结果超出了支持的时间范围。","cause":"无效的日期输入（例如 '2026-02-30'）、超出范围的组成部分，或 INTERVAL 加法超出了支持的日期范围。用错误的格式/区域设置进行解析或插入未经校验的用户输入是典型原因。","fix":"在应用中插入前校验日期输入，并以正确的格式发送（最好使用 ISO 8601：YYYY-MM-DD）。用 TO_DATE/TO_TIMESTAMP 解析时，使格式掩码与输入一致。将运算结果保持在支持的日期范围内；对可疑值进行边界检查。","tags":["date","time","overflow","interval","validation","format"]},{"code":"22026","title":"string_data_length_mismatch","severity":"caution","meaning":"位串的长度与操作所要求的固定长度不匹配。在 PostgreSQL 中最常见于将不同长度的位串写入定长 bit(n) 类型，或对不同长度的位串执行按位 AND/OR/XOR。","cause":"向 bit(n) 列写入短于/长于 n 的位串（例如将 B'101' 写入 bit(4) 列：'bit string length 3 does not match type bit(4)'），或对不同长度的位串使用 &、|、#（'cannot AND bit strings of different sizes'）。它与文本或字符编码无关；char(n)/varchar(n) 的值过长引发的是 22001。","fix":"将值调整为目标长度：使用显式类型转换（B'101'::bit(4) 会在右侧补零并截断多余部分），或在长度可变时将列定义为 bit varying(n)。在按位运算之前将操作数转换为相同长度；用 length() 检查长度。","tags":["bit","length","bit string","varbit","conversion","validation"]},{"code":"2201B","title":"invalid_regular_expression","severity":"caution","meaning":"传给正则表达式操作（~、~*、SIMILAR TO、regexp_matches、substring 等）的模式无效。由于语法错误，PostgreSQL 无法编译该模式。","cause":"正则模式中括号不配对、字符类无效、转义序列错误或使用了不支持的构造。常见原因是直接将用户输入用作模式，而没有转义其中的特殊字符。","fix":"按照 POSIX/ARE 语法修正正则模式；检查圆括号、方括号和转义序列。如果要将用户输入作为字面文本而非模式来搜索：PostgreSQL 没有内置的 regexp_quote 函数，因此用 regexp_replace 在特殊字符前加反斜杠进行转义，或完全不用正则，改用 strpos()/position() 或 LIKE ... ESCAPE。在单独的工具中测试以验证模式。","tags":["regex","regular expression","pattern","similar to","escape","syntax"]},{"code":"44000","title":"with_check_option_violation","severity":"danger","meaning":"通过以 WITH CHECK OPTION 定义的视图执行的 INSERT/UPDATE 被拒绝，因为结果行不再满足视图的 WHERE 条件。视图会阻止写入落在其自身过滤条件之外的行。","cause":"可更新视图以 WITH CHECK OPTION 定义，而写入操作使行移出了视图的定义条件（例如视图过滤 status='active'，却将某行设为 'inactive'）。行级安全（RLS）策略中也可能触发类似的检查。","fix":"确保写入的值满足视图的 WHERE 条件，或直接在基表而非视图上执行操作（如果权限允许）。如果业务规则允许行离开过滤范围，不带 WITH CHECK OPTION 重新定义视图。对于 RLS，检查策略的 WITH CHECK 表达式。","tags":["view","check option","updatable view","rls","insert","update"]},{"code":"40002","title":"transaction_integrity_constraint_violation","severity":"danger","meaning":"SQL 标准中表示在事务结束时检查的完整性约束于 COMMIT 时被违反的代码。重要：PostgreSQL 实际上不会产生此代码；它在 COMMIT 时以约束自身的代码返回延迟（DEFERRED）约束的违反：外键为 23503，唯一性为 23505。","cause":"定义为 DEFERRABLE INITIALLY DEFERRED 的外键或唯一约束在事务的中间步骤中被暂时破坏，且在 COMMIT 时仍未修复；在 PostgreSQL 中这会报告为 23503/23505。例如，以错误的顺序插入相互引用的行且未加修正，就会导致此问题。","fix":"修正事务逻辑，使所有延迟约束在 COMMIT 之前得到满足（补齐缺失的引用、解决冲突）。必要时在事务中用 SET CONSTRAINTS ... IMMEDIATE 提前检查约束，以便更早发现有问题的步骤。针对相互引用调整插入/更新的顺序。","tags":["deferred","constraint violation","commit","foreign key","transaction","constraint"]}]},"errors/mysql-more4.json":{"engineId":"mysql","engine":"MySQL","order":3,"errors":[{"code":"1055","title":"Expression not in GROUP BY (only_full_group_by)（表达式不在 GROUP BY 中）","severity":"caution","meaning":"在 SELECT 列表或 HAVING/ORDER BY 中使用了既不在 GROUP BY 中、也未包裹在聚合函数中的列。由于启用了 ONLY_FULL_GROUP_BY SQL 模式，MySQL 拒绝这种不确定的表达式。","cause":"查询直接选择了分组列之外的列，但该列在每个组中可能不止一个值。MySQL 5.7+ 默认启用 ONLY_FULL_GROUP_BY；依赖旧的宽松 GROUP BY 行为的查询会引发此错误。","fix":"将该列加入 GROUP BY 列表，或将其包裹在聚合函数中（MAX()、MIN()、ANY_VALUE()）。如果您确定该值在组内确实唯一，ANY_VALUE() 可以有意地消除歧义。禁用该模式（从 sql_mode 中移除 ONLY_FULL_GROUP_BY）而不修正查询，会重新带来结果错误的风险；应避免这样做。","tags":["group by","only_full_group_by","sql_mode","any_value","aggregate","query"]},{"code":"1267","title":"Illegal mix of collations（非法的排序规则混用）","severity":"caution","meaning":"在比较或组合操作（JOIN、WHERE、UNION、CONCAT）中，组合了两个排序规则不同的字符串，MySQL 无法确定应采用哪种排序规则。","cause":"通常是比较字符集/排序规则不同的列（例如 utf8mb4_general_ci 与 utf8mb4_unicode_ci，或 latin1 与 utf8mb4），或列与连接排序规则不匹配。在用不同默认值创建表/数据库时很常见。","fix":"使比较两边采用相同的排序规则：用 COLLATE 指定显式排序规则（例如 col1 = col2 COLLATE utf8mb4_unicode_ci）或使用 CONVERT(... USING ...)。作为永久方案，将受影响的列/表转换为统一的字符集和排序规则。同时统一连接排序规则（collation_connection）。","tags":["collation","collation mismatch","character set","join","utf8mb4","convert"]},{"code":"1030","title":"Got error N from storage engine（从存储引擎收到错误 N）","severity":"danger","meaning":"存储引擎（InnoDB/MyISAM）无法完成操作，并返回了底层错误代码。这是对 SQL 层之下的磁盘、文件系统或引擎内部问题的通用报告。括号/消息中的子代码指明了实际原因。","cause":"常见原因：磁盘/临时空间已满（例如 errno 28 - No space left on device）、文件权限问题、表文件损坏、InnoDB 表空间问题或操作系统级 I/O 错误。生成大型临时表的排序/分组查询也可能触发。","fix":"解读子错误代码（在 Linux 上，perror N 会显示其含义，例如 perror 28）。检查磁盘使用情况和 tmpdir 空间；释放空间。确认文件属主/权限。对于 InnoDB，在错误日志中查看详细信息；如果怀疑损坏，运行 CHECK TABLE，必要时进行恢复。优化生成巨大临时结果的查询。","tags":["storage engine","innodb","disk","errno","tmpdir","i-o"]},{"code":"1197","title":"Multi-statement transaction required more than 'max_binlog_cache_size'（多语句事务所需空间超过了 'max_binlog_cache_size'）","severity":"danger","meaning":"单个事务生成的 binlog 数据超过了 max_binlog_cache_size 定义的内存/缓存限制。该事务无法放入二进制日志，因此失败并被回滚。","cause":"超大事务（一次性 INSERT/UPDATE/DELETE 数百万行，或巨大的批量加载）占满了 binlog 缓存。在启用二进制日志（复制/时间点恢复）时，大事务很容易超出此限制。","fix":"将大事务拆分为小批次；每批之后 COMMIT。如果确实无法避免大事务，增大 max_binlog_cache_size（必要时也增大 binlog_cache_size）。在 ETL/批量加载中，带中间 COMMIT 的分块加载既能避免此错误，也能避免长时间持有锁。","tags":["binlog","max_binlog_cache_size","transaction","batch","replication","bulk"]},{"code":"1785","title":"Statement violates GTID consistency（语句违反 GTID 一致性）","severity":"danger","meaning":"在启用基于 GTID（全局事务标识符）的复制时，所执行的语句违反了 GTID 一致性规则。GTID 模式不允许某些无法安全分配到单个 GTID 的语句类型。","cause":"1785（ER_GTID_UNSAFE_NON_TRANSACTIONAL_TABLE）专门出现在同一事务中同时更新非事务型（例如 MyISAM）表和事务型（InnoDB）表时，或在未启用 autocommit 的多语句事务中。在旧版本中，CREATE TABLE ... SELECT（8.0.21 之前；在 8.0.21+ 中对支持原子 DDL 的引擎允许）以及在事务中执行 CREATE/DROP TEMPORARY TABLE（8.0.13 之前；在 8.0.13+ 中当 binlog_format 为 ROW/MIXED 时允许）也违反 GTID 一致性（这些以单独的错误代码报告）。当 enforce_gtid_consistency=ON 时它们会被拒绝。","fix":"不要在同一事务中混用非事务型引擎表和事务型表；用单条 autocommit 语句更新 MyISAM 表，或将所有表迁移到 InnoDB：ALTER TABLE my_table ENGINE=InnoDB; 在旧版本中（8.0.21 之前），将 CREATE TABLE ... SELECT 拆为两步（先 CREATE TABLE，再 INSERT ... SELECT）；在 8.0.13 之前，在显式事务之外创建和删除临时表。","tags":["gtid","replication","consistency","temporary table","myisam","innodb"]},{"code":"3024","title":"Query execution was interrupted, maximum statement execution time exceeded（查询执行被中断，超出最大语句执行时间）","severity":"caution","meaning":"SELECT 查询因超过为其定义的最大执行时间（max_execution_time / MAX_EXECUTION_TIME 提示）而被 MySQL 中断。这是一种超时保护措施，防止长时间运行的查询占用资源。","cause":"查询的运行时间超过了会话/全局 max_execution_time 值或 SELECT 上的 MAX_EXECUTION_TIME(N) 优化器提示。通常由低效的执行计划、缺失索引、大范围扫描或高并发下的性能下降引起。（仅适用于只读 SELECT。）","fix":"用 EXPLAIN 分析并优化查询（合适的索引、选择性更高的谓词、更少的全表扫描）。如果查询确实需要长时间运行，为该 SELECT 增大 MAX_EXECUTION_TIME 提示或会话的 max_execution_time。将繁重的分析查询放到离线环境/副本上运行。","tags":["timeout","max_execution_time","query","explain","index","select"]},{"code":"1298","title":"Unknown or incorrect time zone（未知或不正确的时区）","severity":"caution","meaning":"指定的时区名称未被 MySQL 识别。CONVERT_TZ() 调用、time_zone 设置或引用命名时区（例如 'Europe/Istanbul'）的操作失败，因为服务器上未定义该时区。","cause":"MySQL 要使用命名时区，必须加载 mysql 模式中的时区表（time_zone、time_zone_name 等）。如果这些表为空，只有数字偏移量（'+03:00'）可用；命名时区未知，会引发此错误。","fix":"加载时区表。在 Linux 上，用 mysql_tzinfo_to_sql /usr/share/zoneinfo | mysql -u root -p mysql 导入系统 zoneinfo 数据；然后重启服务器或运行 FLUSH TABLES。如果无法加载命名时区，使用数字偏移量（'+03:00'）。确认名称拼写正确（大小写、'Europe/Istanbul'）。","tags":["time zone","timezone","convert_tz","zoneinfo","mysql_tzinfo_to_sql","offset"]},{"code":"1418","title":"Function has none of DETERMINISTIC, NO SQL, or READS SQL DATA (binary logging enabled)（函数未声明 DETERMINISTIC、NO SQL 或 READS SQL DATA 中的任何一项）","severity":"caution","meaning":"正在创建存储函数，但已启用二进制日志，且该函数没有声明任何确定性/数据访问特性。从复制安全的角度看此类函数存在歧义，因此 MySQL 拒绝创建。","cause":"在 log_bin 为 ON 且 log_bin_trust_function_creators=OFF 时，定义函数时未指定 DETERMINISTIC、NO SQL 或 READS SQL DATA 特性。没有这些特性，MySQL 无法确定该函数在副本上会产生相同的结果。","fix":"用正确的特性声明函数的行为：如果确实是确定性的，添加 DETERMINISTIC；不访问数据则用 NO SQL；只读取数据则用 READS SQL DATA。如果确定这些函数是安全的，可以作为统一策略设置 log_bin_trust_function_creators=1；但正确声明特性才是最安全的做法。确保特性与函数的实际行为一致。","tags":["stored function","deterministic","binary log","replication","log_bin_trust_function_creators","reads sql data"]},{"code":"1231","title":"Variable can't be set to the value of ...（变量不能设置为 ... 的值）","severity":"caution","meaning":"（用 SET）赋给系统/会话变量的值无效。该值超出允许的集合、类型错误或格式不合适，因此被拒绝。","cause":"常见示例：向 sql_mode 赋予无效/拼写错误的模式名、给枚举变量指定未定义的选项，或向不接受 NULL 的变量赋 NULL。单位/范围错误的数值也可能触发。","fix":"确认变量接受的有效值和类型；对于 ENUM/SET 类型的变量（例如 sql_mode 是 SET 类型），准确拼写值/模式名，多个值之间用逗号分隔。在文档中查看有效值范围。对于 sql_mode 这样的列表，先读取当前值（SELECT @@sql_mode），在此基础上安全地添加/删除项。","tags":["set","variable","sql_mode","enum","value","invalid"]}]},"errors/mssql-more4.json":{"engineId":"mssql","engine":"SQL Server","order":4,"errors":[{"code":"8645","title":"A time out occurred while waiting for memory resources to execute the query（等待执行查询所需的内存资源时超时）","severity":"danger","meaning":"查询在资源信号量上等待执行所需的内存授予时超时。由于内存压力导致无法分配授予，操作被取消。","cause":"并发的内存密集型查询（大型排序、哈希连接、哈希聚合）耗尽了整个查询内存池；新查询在信号量上排队并超时。过高的内存授予估算（统计信息过期、执行计划不佳）或服务器内存不足是根本原因。","fix":"用 sys.dm_exec_query_memory_grants 和资源信号量相关 DMV 找出等待或持有内存的查询。更新统计信息，修正导致内存授予膨胀的执行计划（合适的索引、更准确的估算）。降低内存密集型查询的并发度；必要时用 Resource Governor 限制内存授予。检查服务器内存配置（max server memory）。","tags":["memory grant","resource semaphore","memory","timeout","hash join","statistics"]},{"code":"8651","title":"Could not perform the operation because the requested memory grant was not available（无法执行操作，因为请求的内存授予不可用）","severity":"danger","meaning":"查询所需的内存授予不可用，操作无法执行。与 8645 不同，这不是等待，而是直接无法提供所请求的大块内存；通常表明存在严重的内存压力。","cause":"服务器上可用的查询内存无法满足单个运算符（例如非常大的排序/哈希）请求的授予。内存估算过高、max server memory 过低、外部内存压力，或 Resource Governor 限制过低（REQUEST_MAX_MEMORY_GRANT_PERCENT（工作负载组）或 MAX_MEMORY_PERCENT（资源池））都可能是原因。","fix":"优化导致内存授予膨胀的查询（更新统计信息、合适的索引、拆分查询）。监控总内存和可用内存；检查 max server memory 设置以及来自外部应用的内存压力。如果使用 Resource Governor，检查工作负载组的 REQUEST_MAX_MEMORY_GRANT_PERCENT 和资源池的 MAX_MEMORY_PERCENT 设置。将内存密集型批处理作业移到非高峰时段。","tags":["memory grant","memory","resource governor","statistics","hash","pressure"]},{"code":"5061","title":"ALTER DATABASE failed because a lock could not be placed on database（ALTER DATABASE 失败，因为无法在数据库上加锁）","severity":"danger","meaning":"ALTER DATABASE 操作（例如切换到单用户模式、更改文件、设置选项）无法在数据库上获取所需的锁，因为其他活动连接/会话正在使用该数据库。","cause":"在数据库正在使用时尝试了该操作；由于存在活动用户连接，无法获取 SET SINGLE_USER、RESTRICTED_USER 或类似选项更改所需的排他锁。在应用连接处于打开状态时频繁部署/维护时很常见。","fix":"找出连接到该数据库的其他会话（sys.dm_exec_sessions、sp_who2）并安全地关闭它们。进行维护时，使用 ROLLBACK 选项在回滚未结事务的同时更改模式：ALTER DATABASE [db] SET SINGLE_USER WITH ROLLBACK IMMEDIATE; 在应用流量较低时或维护窗口内执行操作。完成后记得切换回 MULTI_USER。","tags":["alter database","lock","single_user","connection","maintenance","rollback"]},{"code":"3702","title":"Cannot drop database because it is currently in use（无法删除数据库，因为它当前正在使用）","severity":"caution","meaning":"尝试删除或重命名数据库，但由于该数据库仍被活动连接使用而被拒绝。SQL Server 无法删除正在使用的数据库。","cause":"其他会话（应用连接池、打开的 SSMS 选项卡、后台作业）连接着要删除的数据库。即使是您自己的会话以该数据库作为上下文（USE）也会触发。监控/备份工具的打开连接也是常见原因。","fix":"删除前切换到其他数据库（USE master;）。找出并关闭已连接的会话，或先将数据库置于单用户模式：ALTER DATABASE [db] SET SINGLE_USER WITH ROLLBACK IMMEDIATE; 然后运行 DROP DATABASE [db]; 停止应用连接池，并确保监控工具没有打开连接。","tags":["drop database","connection","in use","single_user","use","maintenance"]},{"code":"15138","title":"The database principal owns a schema in the database, and cannot be dropped（数据库主体在数据库中拥有架构，无法删除）","severity":"caution","meaning":"尝试删除数据库用户/角色（数据库主体），但由于该主体拥有数据库中的一个或多个架构而无法删除。在将其拥有的架构转移给其他主体之前无法删除。","cause":"要删除的用户是至少一个架构的 OWNER（例如默认以其名称创建的架构，或通过 AUTHORIZATION 指定的架构）。为避免留下孤立架构，SQL Server 阻止删除。","fix":"找出该用户拥有的架构：SELECT name FROM sys.schemas WHERE principal_id = USER_ID('app_user'); 将这些架构的所有权转移给其他主体（例如 dbo）：ALTER AUTHORIZATION ON SCHEMA::[app_schema] TO dbo; 如果该架构不再需要（且为空），执行 DROP SCHEMA。所有权转移后，再次运行 DROP USER。","tags":["schema","principal","user","owner","alter authorization","drop user"]},{"code":"1934","title":"INSERT/UPDATE failed because the following SET options have incorrect settings（INSERT/UPDATE 失败，因为以下 SET 选项的设置不正确）","severity":"danger","meaning":"由于所需的 SET 选项设置不正确，DML 操作（INSERT/UPDATE/DELETE）失败。通常在不满足索引视图、计算列上的索引、筛选索引或空间/XML 索引所必需的 SET 设置时出现。","cause":"受影响的表/视图上存在需要特定 SET 选项（尤其是 QUOTED_IDENTIFIER 和 ANSI_NULLS 为 ON、NUMERIC_ROUNDABORT 为 OFF 等）的对象，但会话当前的 SET 选项不满足该要求。旧的客户端驱动或显式设置了错误 SET 选项的脚本会触发此问题。","fix":"在连接/会话上设置所需选项：SET QUOTED_IDENTIFIER ON; SET ANSI_NULLS ON; SET ANSI_PADDING ON; SET ANSI_WARNINGS ON; SET CONCAT_NULL_YIELDS_NULL ON; SET ARITHABORT ON; SET NUMERIC_ROUNDABORT OFF; 确保应用驱动在连接时正确发送这些选项。错误消息会列出不正确的选项；修正这些选项。","tags":["set options","indexed view","computed column","quoted_identifier","ansi_nulls","dml"]},{"code":"468","title":"Cannot resolve the collation conflict（无法解决排序规则冲突）","severity":"caution","meaning":"在某个操作（JOIN、UNION、WHERE 比较或等值判断）中，两个排序规则不同的字符串表达式相遇，SQL Server 无法确定应采用哪种排序规则。操作因歧义而被取消。","cause":"被比较的列以不同的排序规则定义；最常见于数据库排序规则与服务器/tempdb 排序规则不同，或连接排序规则不同的数据库中的表。将临时表（tempdb 排序规则）与永久表连接是典型诱因。","fix":"在比较中指定显式排序规则：... WHERE a.col = b.col COLLATE DATABASE_DEFAULT（或具体的排序规则名）。创建临时表列时使用 COLLATE DATABASE_DEFAULT。作为永久方案，将受影响的列/数据库统一为一致的排序规则。如果与 tempdb 的不匹配经常发生，DATABASE_DEFAULT 是最实用的解决方法。","tags":["collation","collation","join","tempdb","database_default","conflict"]},{"code":"4864","title":"Bulk load data conversion error (type mismatch or invalid character)（大容量加载数据转换错误（类型不匹配或无效字符））","severity":"caution","meaning":"在大容量加载（BULK INSERT、bcp 或 OPENROWSET(BULK...)）期间，源文件中的某个字段无法转换为目标列的数据类型。所报告的行/列中的数据与预期类型不符。","cause":"源数据文件中的值不符合目标列类型：数值列中出现文本、日期格式错误、意外的小数/千位分隔符、字符编码错误（UTF-8/UTF-16），或由于字段/行终止符错误导致列错位。未跳过标题行也是常见原因。","fix":"定位到错误报告的行和列并检查源数据。使字段终止符（FIELDTERMINATOR）和行终止符（ROWTERMINATOR）与文件一致；用 FIRSTROW=2 跳过标题行。为正确的代码页/编码设置 CODEPAGE 或 DATAFILETYPE。使日期/数字格式与目标类型兼容。使用格式文件可以使映射更可靠。","tags":["bulk insert","bcp","conversion","fieldterminator","encoding","format file"]},{"code":"916","title":"The server principal is not able to access the database under the current security context（服务器主体无法在当前安全上下文中访问数据库）","severity":"caution","meaning":"某个登录名尝试访问特定数据库，但在当前安全上下文中没有该数据库的访问权限。该登录名可以连接到服务器，但在目标数据库中没有对应的用户/权限。","cause":"该登录名在数据库中没有映射的用户（孤立或从未创建）、数据库访问受到限制，或 guest 账户已禁用。常见情况是 SSMS 对象资源管理器试图读取登录名无权访问的数据库的元数据。","fix":"在目标数据库中为该登录名创建用户并授予所需权限：USE [db]; CREATE USER [login] FOR LOGIN [login]; 并分配相应的角色/权限。如果存在孤立用户，用 ALTER USER ... WITH LOGIN = ... 进行映射。如果确实需要访问，将其加入相应的数据库角色；否则，该错误属于预期行为（无权访问该数据库）。","tags":["login","user","access","privilege","security context","orphaned user"]}]},"errors/mongodb-more4.json":{"engineId":"mongodb","engine":"MongoDB","order":5,"errors":[{"code":"216 ElectionInProgress","title":"ElectionInProgress","severity":"caution","meaning":"副本集中当前正在进行主节点选举。在选举完成之前主节点尚未确定，因此暂时不接受写入（以及部分读取）。","cause":"在当前主节点宕机、网络分区、因维护而降级或节点重启之后，集群正在选举新的主节点。选举通常需要几秒钟；在此期间到达的操作会遇到此情况。","fix":"这是暂时性的；启用驱动的重试功能（可重试写入/读取），使操作在选举完成后自动重试。如果选举频繁或耗时较长，调查根本原因：网络稳定性、节点健康状况、投票配置以及选举超时设置（settings.electionTimeoutMillis）。用 rs.status() 检查集群状态。","tags":["replica set","election","primary","retry","step-down","high availability"]},{"code":"225 TransactionTooOld","title":"TransactionTooOld","severity":"caution","meaning":"在多文档事务中运行了某条命令，但其事务编号比服务器为该会话所见的最新事务编号更旧。也就是说，已经启动了一个更新的事务，因此这个旧事务已不再有效。","cause":"在旧事务完成之前，同一会话中启动了新事务，或由于并发导致事务编号乱序递增。通常发生在会话对象被错误共享或多个线程使用同一会话时。","fix":"为每个逻辑事务正确管理会话生命周期；不要在多个线程之间并发共享一个会话。使用驱动推荐的 withTransaction/回调 API 运行事务（它会处理重试和编号管理）。在启动新事务之前，用 commit/abort 结束前一个事务。","tags":["transaction","session","session misuse","concurrency","withtransaction","multi-document transaction"]},{"code":"246 SnapshotUnavailable","title":"SnapshotUnavailable","severity":"caution","meaning":"无法以所请求的快照读关注（readConcern: snapshot）执行读取，因为服务器无法为该时间点提供一致的快照。通常与读时间戳推进不够或已落后有关。","cause":"使用了 readConcern 'snapshot'（主要在多文档事务中），但所请求的 atLeast/at 时间戳的一致视图尚不可用；例如新创建的集合/索引、目录变更或复制延迟。此时服务器拒绝读取。","fix":"这通常是暂时性的；稍等后重试操作（驱动的暂时性事务错误重试机制涵盖了这种情况）。如果刚执行过 DDL（创建集合/索引），在其之后再启动事务。检查复制延迟和节点健康状况。如果并不真正需要快照读，选择更合适的 readConcern。","tags":["snapshot","readconcern","transaction","timestamp","retry","replication"]},{"code":"257 TransactionTooLarge","title":"TransactionTooLarge","severity":"danger","meaning":"多文档事务超出了允许的大小/操作数限制。由于事务中累积的变更（oplog 条目或内存使用）超过了上限，事务被拒绝。","cause":"在单个事务中修改/插入大量文档，使事务产生的总数据量过高（例如必须保存在 WiredTiger 缓存中的变更量，或 transactionLimitBytes 等服务器限制）。自 4.2 起事务可以拆分到多个 oplog 条目中，因此仅 16 MB 的 oplog 条目限制并不起决定作用。将事务用于批量数据迁移/ETL 很容易达到此限制。","fix":"将事务拆分为更小的部分；每个批次在单独的事务中提交。多文档事务的设计初衷是短小；大型批量更新优先在不使用事务的情况下进行（幂等、逐块处理），或用 bulk write 拆分。避免在单个事务中修改成千上万的文档。","tags":["transaction","size","oplog","batch","bulk","bulk write"]},{"code":"276 IndexBuildAborted","title":"IndexBuildAborted","severity":"danger","meaning":"正在进行的索引创建在完成之前被中止/停止。目标索引未能成功创建。","cause":"索引创建可能因以下原因被中止：节点磁盘/内存不足、通过 dropIndexes 显式中止、主节点降级/副本状态变化、违反唯一性（创建唯一索引时存在冲突文档）或节点重启。","fix":"在 mongod 日志中查找中止原因（磁盘空间、内存、唯一性冲突等）并加以解决。如果创建的是唯一索引，先清理冲突的文档。提供充足的磁盘/内存，并确保副本状态稳定。问题修复后，用 createIndex 重新创建索引；在大型集合上选择低负载时段。","tags":["index","index build","aborted","unique","disk","replica set"]},{"code":"86 IndexKeySpecsConflict","title":"IndexKeySpecsConflict","severity":"caution","meaning":"要创建的索引与已有索引的键规范相同（字段/顺序相同），但选项或名称不同。MongoDB 不允许存在两个键相同但定义冲突的索引。","cause":"尝试在相同字段上以不同名称或不同选项（例如不同的 collation、partialFilterExpression、unique 设置）创建第二个索引。通常发生在迁移/代码中的索引定义被修改后重新运行时。","fix":"检查已有索引：db.collection.getIndexes() 找出键相同的冲突索引。要改用所需的选项，先删除旧索引（dropIndex），再以新定义重新创建。如果只是名称不同，继续使用现有索引；不要创建不必要的第二个定义。","tags":["index","getindexes","collation","unique","conflict","createindex"]},{"code":"166 CommandNotSupportedOnView","title":"CommandNotSupportedOnView","severity":"caution","meaning":"视图不支持该命令。MongoDB 视图是只读的，只接受特定的读操作；写入或不受支持的命令会被拒绝。","cause":"在视图上尝试了 insert/update/delete 等写操作，或视图不支持的某些命令（例如部分 text/geo 操作、直接创建索引）。在应用误以为视图是集合而试图写入时出现。","fix":"在视图所基于的底层（源）集合上执行写入；视图仅用于读取。确认该命令是否受视图支持，并使用受支持的读取/聚合操作。如果需要可写的结构，设计物化集合（$merge/$out）而不是视图。","tags":["view","read-only view","read only","aggregation","write","collection"]},{"code":"168 InvalidPipelineOperator","title":"InvalidPipelineOperator","severity":"caution","meaning":"聚合管道中使用了无法识别或无效的运算符/阶段。MongoDB 无法解析指定的运算符名称，拒绝了该管道。","cause":"运算符名称拼写错误（例如把 '$group' 写成 '$groups'）、所用 MongoDB 版本中不存在的新运算符，或在错误的上下文中使用了表达式。缺少 $ 前缀或在无效位置使用运算符也会触发。","fix":"确认运算符/阶段名称的拼写和 $ 前缀。确保服务器的 MongoDB 版本支持该运算符（新运算符在旧版本中不存在）。将管道与文档中的语法进行比较；在正确的阶段中使用表达式（例如 $expr、$project）。单独拿出该阶段，用小样本进行测试。","tags":["aggregation","pipeline","operator","syntax","version","expr"]},{"code":"118 NamespaceNotSharded","title":"NamespaceNotSharded","severity":"caution","meaning":"对尚未分片的集合/命名空间运行了只对分片集合有意义的操作。由于目标集合未分片，命令无法应用。","cause":"在未启用分片的集合上调用了 moveChunk、split 或某些基于分片键的管理命令。通常是因为集合未用 shardCollection 分片，或目标命名空间有误。","fix":"检查集合的分片状态：sh.status() 或 config.collections。如果操作确实需要分片，先在数据库上启用分片（sh.enableSharding），再对集合分片（使用合适的分片键执行 sh.shardCollection）。否则，未分片的集合不需要这些管理命令；确认您针对的是正确的命名空间。","tags":["sharding","shard","namespace","shardcollection","movechunk","cluster"]}]},"errors/redis-more4.json":{"engineId":"redis","engine":"Redis","order":6,"errors":[{"code":"ERR Protocol error: invalid multibulk length","title":"Protocol error: invalid multibulk length（协议错误：无效的 multibulk 长度）","severity":"danger","meaning":"Redis 服务器无法按照 RESP（Redis Serialization Protocol）解析来自客户端的字节流。由于 multibulk 命令声明的元素数量无效，连接因协议错误而被关闭。","cause":"通常由非 Redis 客户端（例如 HTTP 请求、连错端口的应用）向 Redis 端口发送原始数据、损坏/不完整的客户端库、管道中的帧格式错误，或超过协议限制（proto-max-bulk-len）的超大请求触发。","fix":"确认只有真正的 Redis 客户端连接到 Redis 端口（没有混入 HTTP/其他协议流量）。更新客户端库，避免会破坏 RESP 帧格式的自定义/手工命令生成。如果发送非常大的值，检查 proto-max-bulk-len 限制和请求大小。确保网络中间的代理没有损坏数据。","tags":["protocol","resp","multibulk","client","pipeline","connection"]},{"code":"ERR Number of keys can't be greater than number of args","title":"EVAL/EVALSHA: numkeys exceeds argument count（EVAL/EVALSHA：numkeys 超过参数数量）","severity":"caution","meaning":"运行 Lua 脚本（EVAL/EVALSHA/FCALL）时，指定的键数量（numkeys）大于实际传给命令的参数数量。Redis 无法拆分 KEYS 和 ARGV 数组，因此拒绝该命令。","cause":"EVAL 的语法是 'EVAL script numkeys key [key ...] arg [arg ...]'。numkeys 值计算错误（设置过大），或预期的键从未加入命令。这是常见的编程错误。","fix":"使 numkeys 与实际传给命令的键数量完全相等。使用客户端库的 EVAL 封装（通常将 KEYS 和 ARGV 作为单独的列表接收），以自动计算 numkeys。调用脚本时，正确地将键放入 KEYS，将其他参数放入 ARGV。","tags":["lua","eval","evalsha","numkeys","keys","script"]},{"code":"ERR WATCH inside MULTI is not allowed","title":"WATCH inside MULTI is not allowed（不允许在 MULTI 中使用 WATCH）","severity":"caution","meaning":"在打开的 MULTI 块中发送了 WATCH。为实现乐观锁，WATCH 必须在事务开始之前将键置于监视之下；在 MULTI 之后调用会被拒绝。该错误不会中止事务；只有 WATCH 失败，没有任何键被监视。","cause":"代码中 WATCH 和 MULTI 的顺序写反了（先 MULTI 再 WATCH）；在客户端库的管道/事务封装中调用 WATCH；或使用了处于 MULTI 状态的连接池连接。在这种情况下，即使本应被监视的键已发生变化，EXEC 仍会执行；预期的冲突保护不会生效。","fix":"正确的顺序：WATCH key -> （读取值）-> MULTI -> 命令 -> EXEC。如果 EXEC 返回 nil，说明某个被监视的键已变化；从头重试该操作。使用客户端库的 WATCH/事务 API（例如 watch + pipeline 辅助方法）。注意：SUBSCRIBE 等 pub/sub 命令在 MULTI 中不会被拒绝，它们会被排队，EXEC 之后连接切换为订阅模式；这同样不推荐，请为 pub/sub 使用专用连接。","tags":["multi","watch","transaction","optimistic locking","exec","connection"]},{"code":"ERR WAIT cannot be used with replica instances","title":"WAIT cannot be used with replica instances（WAIT 不能用于副本实例）","severity":"caution","meaning":"在副本（replica/slave）实例上运行了 WAIT。WAIT 用于等待写入到达指定数量的副本，只在主节点（master）上有意义；不能在副本上使用。","cause":"应用误通过副本连接发送了 WAIT。通常是因为连接被路由到只读副本，或在角色变更（故障转移）后客户端仍连接着副本。","fix":"只向主节点发送 WAIT。确保客户端/代理配置将写入和 WAIT 调用路由到 master。在故障转移后检测角色变化，并将连接重定向到新的主节点（可以用 ROLE 命令确认角色）。","tags":["wait","replica","master","write","failover","replication"]},{"code":"ERR Background append only file rewriting already in progress","title":"AOF rewrite already in progress（AOF 重写已在进行中）","severity":"caution","meaning":"尝试启动 AOF（Append Only File）重写（BGREWRITEAOF），但已有 AOF 重写正在进行。两个 AOF 重写不能同时运行。","cause":"在之前的 BGREWRITEAOF（手动，或由 auto-aof-rewrite-percentage 自动触发）仍在运行时到达了新的重写请求。在大数据集上，AOF 重写可能耗时较长，从而导致这种冲突。","fix":"等待正在运行的重写完成；关注 INFO persistence 输出中的 aof_rewrite_in_progress 字段。如果操作正在正常运行，不要再发送请求。如果冲突频繁，检查自动重写阈值（auto-aof-rewrite-percentage/min-size）和磁盘 I/O 性能。","tags":["aof","bgrewriteaof","persistence","rewrite","info","disk"]},{"code":"IOERR error or timeout reading from target instance","title":"MIGRATE: I/O error or timeout to target（MIGRATE：与目标之间发生 I/O 错误或超时）","severity":"danger","meaning":"MIGRATE 命令（通常在 Redis Cluster 中迁移槽/键时）在连接目标实例或读取其回复时遇到 I/O 错误或超时。键迁移无法安全完成。","cause":"源节点与目标节点之间的网络延迟/中断、目标节点过载或无响应、MIGRATE 超时设置过短，或大键无法在给定时间内传输完毕。在集群再平衡（重新分片）期间很常见。","fix":"检查源节点与目标节点之间的网络连通性和延迟。为 MIGRATE 指定更长的超时（尤其是针对大键）。确认目标节点的健康/负载和内存状态。在重新分片期间迁移大键时，选择低负载时段；必要时重试迁移。","tags":["migrate","cluster","resharding","timeout","network","i-o"]},{"code":"ERR Error compiling script (new function)","title":"Error compiling Lua script（编译 Lua 脚本出错）","severity":"caution","meaning":"用 EVAL 发送的 Lua 脚本无法被 Redis 内嵌的 Lua 解释器编译。由于脚本包含语法错误，在开始执行之前即被拒绝。","cause":"Lua 脚本中的语法错误：未闭合的括号/引号、关键字拼写错误、缺少 'end'、无效的运算符或不受支持的 Lua 构造。客户端对脚本的拼接/转义不正确也可能触发。","fix":"根据错误消息中的行/字符信息修正 Lua 语法（检查括号、引号和 'end' 是否配对）。在单独的 Lua 环境中编译脚本进行验证。确保客户端正确构造脚本（没有转义/拼接错误）。用 SCRIPT LOAD 预加载经过验证的脚本，并用 EVALSHA 调用。","tags":["lua","eval","compilation","syntax","script load","script"]},{"code":"ERR FAILOVER requires connected replicas.","title":"FAILOVER requires connected replicas（FAILOVER 需要已连接的副本）","severity":"caution","meaning":"运行了 FAILOVER 以启动协调式（平滑）故障转移，但由于没有连接到主节点且可接管的合适副本，该命令被拒绝。没有可移交角色的目标，故障转移就无法进行。","cause":"主节点当前没有已连接的副本（副本已宕机、与网络断开或未同步），或指定的目标副本不符合条件/未连接。调用手动 FAILOVER（Redis 6.2+）时，复制拓扑不满足所需条件。注意：FAILOVER 仅适用于单机（master/replica）复制；在 Redis Cluster 中应在副本上使用 CLUSTER FAILOVER。","fix":"确保至少存在一个健康、已连接且数据最新的副本（用 INFO replication 检查 connected_slaves 和同步状态）。修复副本上的网络/访问问题，使其连接到主节点。要故障转移到特定目标，使用 FAILOVER TO host port，并确认该副本符合条件。","tags":["failover","replica","master","replication","info replication","high availability"]},{"code":"ERR invalid expire time in 'set' command","title":"invalid expire time in SET（SET 中的过期时间无效）","severity":"caution","meaning":"向 SET（使用 EX/PX/EXAT/PXAT 选项）提供了无效的过期时间。Redis 不接受零或负值这类无意义的生存时间，因此拒绝了该命令。","cause":"用 EX/PX 发送了 0 或负值，或应用错误地计算了过期时间并产生了无效数字。SETEX/GETEX 等命令对于非正的时长同样返回此错误。某些客户端缺陷源于混淆了秒和毫秒。","fix":"始终以严格为正的整数发送过期时间；EX 以秒为单位，PX 以毫秒为单位。在应用中校验时长计算（不得产生负数/零），不要混淆单位（秒与毫秒）。要立即删除键，使用 DEL/UNLINK，而不是设置过期时间。对于绝对时间，向 EXAT/PXAT 传入有效的未来 Unix 时间戳。","tags":["set","expire","ttl","ex","px","expiration"]}]},"errors/elasticsearch-more4.json":{"engineId":"elasticsearch","engine":"Elasticsearch","order":7,"errors":[{"code":"bind_transport_exception","title":"bind_transport_exception (Address already in use)（地址已被占用）","severity":"danger","meaning":"Elasticsearch 节点在启动时无法绑定套接字以监听 transport（节点间通信）或 HTTP 端口。通常伴随 'Address already in use' 子错误，节点无法启动。","cause":"请求的端口（默认 transport 为 9300，HTTP 为 9200）已被其他进程占用；最常见的是同一节点的前一个实例未正常关闭、同一主机上的另一个 ES 实例/服务发生冲突，或 network.host/port 配置错误。权限/访问限制也可能导致无法绑定。","fix":"找出占用端口的进程（Linux：ss -ltnp | grep 9300 或 lsof -i:9300）并终止冲突的进程，或为节点分配其他端口（transport.port / http.port）。确认之前的 ES 进程确实已退出。确保 network.host 和端口范围设置正确且可绑定。然后重启节点。","tags":["transport","port","9300","bind","network.host","startup"]},{"code":"translog_corrupted_exception","title":"translog_corrupted_exception","severity":"danger","meaning":"由于分片的事务日志（translog）已损坏，无法读取。在恢复期间，Elasticsearch 检测到 translog 未通过完整性检查，分片无法启动。","cause":"translog 文件通常因突然断电、磁盘写满、文件系统错误、硬件/磁盘故障或对数据目录的外部操作（复制、手动干预）而损坏。强制关闭（kill -9）期间部分完成的写入也可能导致此问题。","fix":"首先尝试从副本或快照恢复数据；如果存在完好的副本，用它重建损坏的分片是最安全的途径。如果没有其他副本且您接受数据丢失，停止节点并用 bin/elasticsearch-shard remove-corrupted-data --index <index> --shard-id <n> 清理损坏的 translog/数据（这会永久丢失尚未提交的数据；旧的 elasticsearch-translog 工具已在 7.0 中移除）。该工具会输出节点重启后分配分片所需的 POST _cluster/reroute 命令（allocate_stale_primary, accept_data_loss: true）。操作前备份数据目录。检查磁盘健康状况和可用空间。","tags":["translog","shard","recovery","corruption","snapshot","disk"]},{"code":"recovery_engine_exception","title":"recovery_engine_exception","severity":"danger","meaning":"在分片恢复期间发生了引擎级错误，分片无法成功打开。恢复是在节点重启、副本分配或快照还原期间执行的步骤。","cause":"恢复期间的底层问题：Lucene 段或 translog 损坏、磁盘空间不足、文件权限、源节点与目标节点之间的网络中断，或版本/兼容性问题。它通常包裹着一个更具体的子错误（损坏、I/O）。","fix":"检查底层（caused_by）错误以确定真正的问题（损坏、磁盘、网络等）。确认磁盘空间和文件权限。如果副本分配失败，用 _cluster/allocation/explain 查看原因，必要时触发重新分配。如果存在损坏，从完好的副本/快照恢复。如果与网络有关，检查节点间连通性和恢复设置。","tags":["recovery","shard","engine","allocation","corruption","disk"]},{"code":"snapshot_in_progress_exception","title":"snapshot_in_progress_exception","severity":"caution","meaning":"所请求的操作与当前正在进行的快照冲突，因此被拒绝。例如，在创建快照期间尝试删除/关闭索引或修改仓库会被阻止。","cause":"在快照仍在运行时，尝试删除/关闭该快照包含的索引、删除/修改仓库，或启动另一个冲突的快照/还原操作。为保证一致性，Elasticsearch 不允许这些冲突操作。","fix":"等待正在运行的快照完成：用 GET _snapshot/_status 监控进度。如果确实必须取消操作，通过删除快照来停止它：DELETE _snapshot/repo/snapshot_name（这会中止正在运行的快照）。快照完成或取消后，重新运行原来的操作（删除索引、更改仓库）。","tags":["snapshot","repository","restore","index deletion","conflict","backup"]},{"code":"repository_verification_exception","title":"repository_verification_exception","severity":"danger","meaning":"在注册或验证快照仓库时，检查所有节点能否访问该仓库的步骤失败。由于仓库验证未通过，无法可靠地执行快照/还原操作。","cause":"共享文件系统（fs）仓库没有在所有节点上以相同路径挂载/可访问、配置中缺少 path.repo、文件权限不足，或云/对象存储（S3、GCS、Azure）的凭据/网络访问不正确。部分节点无法写入仓库是典型原因。","fix":"对于共享 fs 仓库，确保所有节点的 elasticsearch.yml 中都定义了 path.repo，且每个节点上都以可读写方式挂载了相同的路径。修正文件权限（elasticsearch 用户）。对于云仓库，确认凭据（keystore）、bucket 名称以及网络/访问权限。用 POST _snapshot/repo/_verify 重新验证仓库。","tags":["repository","snapshot","path.repo","permission","s3","verification"]},{"code":"reduce_search_phase_exception","title":"reduce_search_phase_exception","severity":"danger","meaning":"搜索的 reduce 阶段发生错误。协调节点在合并（reduce）来自不同分片的部分结果/聚合时遇到问题，无法生成最终结果。","cause":"通常是合并结果时超出内存/资源限制（例如非常大的聚合、circuit breaker 触发）、聚合结构不兼容/无效，或分片返回的部分结果无法合并。在基数非常高的聚合中很常见。","fix":"阅读 caused_by 详情以确定根本原因。减轻繁重的聚合：降低高基数 terms 聚合的 size，用 composite 聚合分页，并减少不必要的子聚合。监控内存压力，必要时将查询拆分为更小的部分。如果 circuit breaker 触发，降低查询开销。","tags":["search","reduce","aggregation","coordinating node","memory","cardinality"]},{"code":"aggregation_execution_exception","title":"aggregation_execution_exception","severity":"caution","meaning":"执行聚合时发生错误。虽然聚合定义已被解析，但在运行时处理目标字段/值时出现了问题。","cause":"聚合与字段类型不兼容（例如在未启用 fielddata 的 text 字段上聚合）、排序/计算期间出现无效值、管道聚合引用了不存在的路径（bucket path），或不受支持的字段类型组合。基于脚本的聚合中的运行时错误也会触发。","fix":"检查错误详情中的字段/聚合名称。确保被聚合的字段类型合适（使用 keyword/数值类型而非 text；必要时使用启用了 doc_values 的字段）。在管道聚合中，确认 buckets_path 与实际的桶名称一致。在脚本聚合中添加 null/类型检查。用合适的字段类型修正映射。","tags":["aggregation","execution","fielddata","keyword","pipeline","mapping"]},{"code":"failed_node_exception","title":"failed_node_exception","severity":"danger","meaning":"在集群范围的操作（例如搜索、统计、管理命令）期间，集群中的某个节点无法处理或响应请求。协调节点将来自该节点的失败包装在此异常中。","cause":"节点崩溃、脱离网络、因过载/GC 停顿而无法响应、存在磁盘/资源问题，或该节点上发生了分片错误。在集群重新配置（节点离开/加入）期间也可能暂时出现。","fix":"用 caused_by 确定失败发生在哪个节点以及原因。检查节点健康状况：GET _cat/nodes、GET _nodes/stats（CPU、内存、GC、磁盘）。如果节点承受资源压力，检查负载/堆设置；确认网络连通性。如果节点离开了集群，等待其重新加入并等待分片分配恢复（GET _cluster/health）。","tags":["node","cluster","network","gc","shard","resources"]},{"code":"elasticsearch_parse_exception","title":"elasticsearch_parse_exception","severity":"caution","meaning":"Elasticsearch 在解析请求体（JSON 查询、设置或配置）时遇到通用解析错误。请求在结构上与预期格式不符。","cause":"格式错误/无效的 JSON（缺少逗号、未闭合的括号）、字段位置错误、意外的值类型（例如在需要数字的地方出现文本），或无效的时间/范围表达式。DSL 查询嵌套不正确也会产生此异常。","fix":"用 JSON 校验工具检查请求体；检查括号、引号和逗号。对照 Elasticsearch DSL 文档，确认字段位置和类型正确。根据错误消息中的位置/字段提示修正有问题的部分。如果使用客户端库，确保序列化生成的是有效的 JSON。","tags":["parse","json","dsl","query","parsing","request"]}]},"health/oracle.json":{"engineId":"oracle","engine":"Oracle","order":1,"checks":[{"id":"version-uptime","group":"常规","title":"版本与运行时间","command":"SELECT i.instance_name, i.version_full, i.status, i.startup_time, ROUND(SYSDATE - i.startup_time, 2) AS uptime_days FROM v$instance i;","expect":"实例状态应为 OPEN。如果 startup_time 意外地很近（刚刚启动），可能发生了计划外重启；需调查。version_full 列适用于 18c+；12c 及更早版本使用 version 列。","severity":"safe"},{"id":"alertlog-errors","group":"常规","title":"告警日志中的近期严重错误（ORA-）","command":"SELECT originating_timestamp, message_text FROM v$diag_alert_ext WHERE message_text LIKE '%ORA-%' AND originating_timestamp > SYSTIMESTAMP - INTERVAL '1' DAY ORDER BY originating_timestamp DESC FETCH FIRST 50 ROWS ONLY;","expect":"最近 24 小时内不应返回任何 ORA- 错误。ORA-00600、ORA-07445 和 ORA-01578（块损坏）之类的条目需要立即处理。","severity":"danger"},{"id":"invalid-objects","group":"常规","title":"INVALID 对象","command":"SELECT owner, object_type, COUNT(*) AS invalid_count FROM dba_objects WHERE status = 'INVALID' GROUP BY owner, object_type ORDER BY invalid_count DESC;","expect":"理想情况下不应存在 INVALID 对象。如果存在，使用 EXEC DBMS_UTILITY.COMPILE_SCHEMA 或 utlrp.sql 重新编译；SYS/SYSTEM 下的无效对象优先处理。","severity":"caution"},{"id":"tablespace-usage","group":"容量","title":"表空间使用率","command":"SELECT df.tablespace_name, ROUND(df.total_mb, 1) AS total_mb, ROUND(df.total_mb - NVL(fs.free_mb, 0), 1) AS used_mb, ROUND((df.total_mb - NVL(fs.free_mb, 0)) / df.total_mb * 100, 1) AS pct_used FROM (SELECT tablespace_name, SUM(bytes)/1024/1024 total_mb FROM dba_data_files GROUP BY tablespace_name) df LEFT JOIN (SELECT tablespace_name, SUM(bytes)/1024/1024 free_mb FROM dba_free_space GROUP BY tablespace_name) fs ON df.tablespace_name = fs.tablespace_name ORDER BY pct_used DESC;","expect":"pct_used 超过 85% 为警告阈值，超过 90% 为严重。关闭 autoextend 的表空间被写满的风险更高；添加数据文件或启用 autoextend。","severity":"danger"},{"id":"largest-segments","group":"容量","title":"最大的段（表/索引）","command":"SELECT owner, segment_name, segment_type, ROUND(bytes/1024/1024/1024, 2) AS size_gb FROM dba_segments ORDER BY bytes DESC FETCH FIRST 20 ROWS ONLY;","expect":"显示占用空间最多的段。意外增长可能说明需要归档/清理或分区。","severity":"safe"},{"id":"undo-temp-usage","group":"容量","title":"UNDO 和 TEMP 表空间使用率","command":"SELECT ts.tablespace_name, ts.contents, ROUND(SUM(ss.used_blocks) * ts.block_size / 1024 / 1024, 1) AS used_mb FROM v$sort_segment ss JOIN (SELECT tablespace_name, contents, block_size FROM dba_tablespaces WHERE contents = 'TEMPORARY') ts ON ss.tablespace_name = ts.tablespace_name GROUP BY ts.tablespace_name, ts.contents, ts.block_size UNION ALL SELECT tablespace_name, 'UNDO', ROUND(SUM(bytes)/1024/1024, 1) FROM dba_undo_extents WHERE status = 'ACTIVE' GROUP BY tablespace_name;","expect":"如果 TEMP 使用率持续触顶，说明存在大型排序/哈希操作。如果活动 UNDO 空间无法满足保留要求，存在 ORA-01555（snapshot too old）风险。","severity":"caution"},{"id":"sessions-vs-limit","group":"容量","title":"会话数与上限","command":"SELECT resource_name, current_utilization, max_utilization, limit_value FROM v$resource_limit WHERE resource_name IN ('sessions','processes','transactions');","expect":"如果 current_utilization 接近 limit_value（尤其是超过 85%），新连接可能会被拒绝。max_utilization 显示历史峰值；检查 processes 参数。","severity":"caution"},{"id":"fra-usage","group":"容量","title":"快速恢复区（FRA）使用率","command":"SELECT name, ROUND(space_limit/1024/1024/1024, 2) AS limit_gb, ROUND(space_used/1024/1024/1024, 2) AS used_gb, ROUND(space_used/space_limit*100, 1) AS pct_used FROM v$recovery_file_dest;","expect":"FRA 达到 85% 时会在告警日志中写入警告；达到 100% 时归档日志写入停止，数据库挂起（archiver hung，ORA-00257）。清理 RMAN 备份/归档，或增大 db_recovery_file_dest_size。","severity":"danger"},{"id":"buffer-cache-hit","group":"性能","title":"Buffer cache 命中率","command":"SELECT ROUND((1 - (phy.value - dir.value) / (con.value + cur.value)) * 100, 2) AS buffer_hit_ratio FROM v$sysstat con, v$sysstat cur, v$sysstat phy, v$sysstat dir WHERE con.name = 'consistent gets' AND cur.name = 'db block gets' AND phy.name = 'physical reads' AND dir.name = 'physical reads direct';","expect":"在 OLTP 系统上，通常期望高于 90%。持续偏低可能说明 SGA/buffer cache 过小或索引设计不佳；应结合等待事件解读，而不是孤立看待。","severity":"safe"},{"id":"top-sql","group":"性能","title":"开销最大的 SQL 语句（耗时）","command":"SELECT sql_id, ROUND(elapsed_time/1000000, 1) AS elapsed_sec, executions, ROUND(elapsed_time/1000000/GREATEST(executions,1), 3) AS sec_per_exec, SUBSTR(sql_text, 1, 100) AS sql_text FROM v$sqlarea ORDER BY elapsed_time DESC FETCH FIRST 15 ROWS ONLY;","expect":"总耗时最高的查询。sec_per_exec 较高或 executions 极高的查询是调优对象；使用 AWR/SQL Tuning Advisor 进行分析。","severity":"safe"},{"id":"long-active-sessions","group":"性能","title":"长时间运行的活动会话","command":"SELECT s.sid, s.serial#, s.username, s.status, s.last_call_et AS active_seconds, s.event, SUBSTR(q.sql_text, 1, 80) AS sql_text FROM v$session s LEFT JOIN v$sqlarea q ON s.sql_id = q.sql_id WHERE s.status = 'ACTIVE' AND s.type = 'USER' AND s.last_call_et > 60 ORDER BY s.last_call_et DESC;","expect":"last_call_et（秒）非常高的活动会话可能已卡住或正在执行重型查询。event 列显示正在等待的资源。","severity":"caution"},{"id":"blocking-sessions","group":"性能","title":"阻塞与等待的会话（锁）","command":"SELECT bs.sid AS blocker_sid, bs.username AS blocker_user, ws.sid AS waiter_sid, ws.username AS waiter_user, ws.seconds_in_wait, ws.event FROM v$session ws JOIN v$session bs ON ws.blocking_session = bs.sid WHERE ws.blocking_session IS NOT NULL ORDER BY ws.seconds_in_wait DESC;","expect":"不应返回任何行。阻塞会话会导致应用延迟/死锁；如果 blocker_sid 长时间持有锁（seconds_in_wait 很大），与应用团队一起评估，必要时执行 ALTER SYSTEM KILL SESSION。","severity":"danger"},{"id":"rman-last-backup","group":"备份与恢复","title":"最近一次成功的 RMAN 备份状态","command":"SELECT input_type, status, start_time, end_time, ROUND(output_bytes/1024/1024/1024, 2) AS output_gb FROM v$rman_backup_job_details ORDER BY start_time DESC FETCH FIRST 10 ROWS ONLY;","expect":"最近一次备份应为 status = COMPLETED，且 start_time 足够新以满足 RPO 策略（例如最近 24 小时内）。FAILED 或 'COMPLETED WITH WARNINGS' 条目必须调查。","severity":"danger"},{"id":"archivelog-mode","group":"备份与恢复","title":"归档模式与归档进程状态","command":"SELECT log_mode, force_logging, (SELECT COUNT(*) FROM v$archived_log WHERE first_time > SYSDATE - 1) AS logs_last_24h FROM v$database;","expect":"生产数据库应为 log_mode = ARCHIVELOG（否则无法进行时间点恢复）。如果最近 24 小时内没有生成归档，归档进程可能已卡住。","severity":"danger"},{"id":"locked-expired-accounts","group":"安全","title":"被锁定或已过期的账户","command":"SELECT username, account_status, lock_date, expiry_date, profile FROM dba_users WHERE account_status != 'OPEN' ORDER BY username;","expect":"LOCKED/EXPIRED 的关键应用账户会导致意外中断。默认账户（SCOTT、HR 等）处于锁定/禁用状态是好的。EXPIRED(GRACE) 表示密码即将过期。","severity":"caution"},{"id":"privileged-users","group":"安全","title":"高权限用户（DBA 角色）","command":"SELECT grantee, granted_role, admin_option FROM dba_role_privs WHERE granted_role IN ('DBA','PDB_DBA') AND grantee NOT IN ('SYS','SYSTEM') ORDER BY grantee;","expect":"DBA 角色只应授予必要且已知的账户。意外的被授权者或 admin_option = YES 的账户存在权限提升风险；按照最小权限原则进行审查。对于具有 SYSDBA/SYSOPER 权限的账户，还需检查：SELECT username, sysdba, sysoper FROM v$pwfile_users;","severity":"caution"},{"id":"default-password-users","group":"安全","title":"未修改默认密码的开放账户","command":"SELECT du.username, du.account_status, du.profile, CASE WHEN dp.username IS NOT NULL THEN 'YES' ELSE 'NO' END AS has_default_password FROM dba_users du LEFT JOIN (SELECT username FROM dba_users_with_defpwd) dp ON du.username = dp.username WHERE du.account_status = 'OPEN' ORDER BY du.username;","expect":"has_default_password = YES 的账户必须立即修改密码或锁定（ALTER USER ... ACCOUNT LOCK）。关于密码有效期策略，还需检查 DBA_PROFILES 中的 PASSWORD_LIFE_TIME。","severity":"caution"},{"id":"dataguard-lag","group":"复制","title":"Data Guard 备库延迟","command":"SELECT name, value, unit, time_computed FROM v$dataguard_stats WHERE name IN ('apply lag','transport lag');","expect":"apply lag 和 transport lag 应尽可能接近 0。分钟/小时级别的延迟表明存在网络或 MRP（Managed Recovery Process）问题；故障转移目标面临风险。","severity":"danger"},{"id":"dataguard-mrp","group":"复制","title":"备库应用进程（MRP）状态","command":"SELECT name, role, action, thread#, sequence#, block# FROM v$dataguard_process WHERE name LIKE 'MRP%' OR role LIKE 'RFS%' ORDER BY name;","expect":"在物理备库上，应能看到 action 为 APPLYING_LOG 或 WAIT_FOR_LOG 的 MRP0 进程。如果没有 MRP，说明应用已停止；如果没有 RFS，说明日志未从主库到达。v$dataguard_process 适用于 12.2+；12.1 及更早版本使用（现已弃用的）v$managed_standby（process、status 列）。","severity":"danger"}]},"health/postgres.json":{"engineId":"postgres","engine":"PostgreSQL","order":2,"checks":[{"id":"version-uptime","group":"常规","title":"版本与运行时间","command":"SELECT version() AS pg_version, pg_postmaster_start_time() AS started_at, date_trunc('second', now() - pg_postmaster_start_time()) AS uptime;","expect":"显示版本和启动时间。如果运行时间意外地短（刚刚启动），可能发生了计划外重启或崩溃；请检查日志。","severity":"safe"},{"id":"recent-errors","group":"常规","title":"检查点统计","command":"-- PG17+:\nSELECT num_timed, num_requested, buffers_written, stats_reset FROM pg_stat_checkpointer;\n-- PG16 and earlier:\nSELECT checkpoints_timed, checkpoints_req, buffers_checkpoint, buffers_backend, stats_reset FROM pg_stat_bgwriter;","expect":"如果请求型检查点数（num_requested / checkpoints_req）相对于定时检查点数（num_timed / checkpoints_timed）偏高，说明 WAL 填满得太快；增大 max_wal_size。在 PG17 中，这些计数器从 pg_stat_bgwriter 移到了 pg_stat_checkpointer。","severity":"caution"},{"id":"table-bloat","group":"常规","title":"膨胀的表与死元组比例","command":"SELECT schemaname, relname, n_live_tup, n_dead_tup, CASE WHEN n_live_tup > 0 THEN round(n_dead_tup * 100.0 / n_live_tup, 1) ELSE 0 END AS dead_pct, last_autovacuum FROM pg_stat_user_tables WHERE n_dead_tup > 1000 ORDER BY n_dead_tup DESC LIMIT 20;","expect":"dead_pct 较高（例如超过 20%）的表已经膨胀；这会影响 VACUUM 性能和磁盘使用。如果 last_autovacuum 非常久远，检查 autovacuum 设置或手动执行 VACUUM (ANALYZE)。","severity":"caution"},{"id":"database-size","group":"容量","title":"数据库大小","command":"SELECT datname, pg_size_pretty(pg_database_size(datname)) AS size, pg_database_size(datname) AS size_bytes FROM pg_database WHERE datistemplate = false ORDER BY pg_database_size(datname) DESC;","expect":"显示数据库大小。应结合磁盘容量（df -h）评估意外增长；磁盘写满时 PostgreSQL 会停止写操作。","severity":"danger"},{"id":"largest-tables","group":"容量","title":"最大的表（包括索引）","command":"SELECT schemaname, relname, pg_size_pretty(pg_total_relation_size(relid)) AS total_size, pg_size_pretty(pg_relation_size(relid)) AS table_size, pg_size_pretty(pg_total_relation_size(relid) - pg_relation_size(relid)) AS index_toast_size FROM pg_catalog.pg_statio_user_tables ORDER BY pg_total_relation_size(relid) DESC LIMIT 20;","expect":"显示占用空间最多的表。如果 index_toast_size 远大于表本身的大小，可能存在过多/未使用的索引；考虑归档或分区。","severity":"safe"},{"id":"temp-files","group":"容量","title":"临时文件使用情况","command":"SELECT datname, temp_files, pg_size_pretty(temp_bytes) AS temp_size, stats_reset FROM pg_stat_database WHERE temp_files > 0 ORDER BY temp_bytes DESC;","expect":"temp_files/temp_bytes 偏高说明 work_mem 不足，排序/哈希操作溢出到磁盘。考虑增大 work_mem 或优化查询。","severity":"caution"},{"id":"connections-vs-limit","group":"容量","title":"连接数与 max_connections","command":"SELECT (SELECT count(*) FROM pg_stat_activity WHERE backend_type = 'client backend') AS current_connections, (SELECT setting::int FROM pg_settings WHERE name = 'max_connections') AS max_connections, round((SELECT count(*) FROM pg_stat_activity WHERE backend_type = 'client backend') * 100.0 / (SELECT setting::int FROM pg_settings WHERE name = 'max_connections'), 1) AS pct_used;","expect":"只统计客户端连接（由于 pg_stat_activity 还包含后台进程，需要 backend_type = 'client backend' 过滤条件）。pct_used 超过 80% 时，新连接有被拒绝的风险（尤其是达到 superuser_reserved_connections 之后）。推荐使用连接池（PgBouncer）。","severity":"caution"},{"id":"wal-directory-size","group":"容量","title":"WAL 目录大小与复制槽保留","command":"SELECT (SELECT count(*) FROM pg_ls_waldir()) AS wal_file_count, pg_size_pretty((SELECT sum(size) FROM pg_ls_waldir())) AS wal_total_size, slot_name, active, pg_size_pretty(pg_wal_lsn_diff(pg_current_wal_lsn(), restart_lsn)) AS retained_wal FROM pg_replication_slots ORDER BY slot_name;","expect":"不活动的（active = false）复制槽会导致 WAL 堆积，使 pg_wal 目录被写满。如果 retained_wal 很大，确认该复制槽的归属（slot_type、database、plugin）：active = false 也可能表示暂时断开的备库或逻辑订阅者/CDC（如 Debezium）的复制槽。如果确实未使用，用 pg_drop_replication_slot 删除；依赖已删除复制槽的备库必须重建，订阅者/CDC 端必须重新同步。为长期防护，用 max_slot_wal_keep_size（PG13+）设置上限。","severity":"danger"},{"id":"cache-hit-ratio","group":"性能","title":"Buffer cache 命中率","command":"SELECT sum(heap_blks_read) AS heap_read, sum(heap_blks_hit) AS heap_hit, round(sum(heap_blks_hit) * 100.0 / NULLIF(sum(heap_blks_hit) + sum(heap_blks_read), 0), 2) AS cache_hit_ratio FROM pg_statio_user_tables;","expect":"对于 OLTP 负载，目标通常高于 99%。持续偏低可能说明 shared_buffers 过小或工作集无法装入内存。","severity":"safe"},{"id":"top-queries","group":"性能","title":"开销最大的查询（pg_stat_statements）","command":"SELECT round(total_exec_time::numeric, 1) AS total_ms, calls, round(mean_exec_time::numeric, 2) AS mean_ms, round((100 * total_exec_time / sum(total_exec_time) OVER ())::numeric, 1) AS pct, left(query, 100) AS query FROM pg_stat_statements ORDER BY total_exec_time DESC LIMIT 15;","expect":"pg_stat_statements 必须位于 shared_preload_libraries 中（需要重启），并已安装在数据库中（CREATE EXTENSION pg_stat_statements）。total_exec_time/mean_exec_time 名称适用于 PG13+；PG12 使用 total_time/mean_time。消耗总时间最多的查询是调优对象；对 mean_ms 较高的查询执行 EXPLAIN ANALYZE。","severity":"safe"},{"id":"long-running-queries","group":"性能","title":"长时间运行的活动查询","command":"SELECT pid, usename, state, now() - query_start AS duration, wait_event_type, wait_event, left(query, 80) AS query FROM pg_stat_activity WHERE state = 'active' AND now() - query_start > interval '1 minute' AND backend_type = 'client backend' ORDER BY duration DESC;","expect":"运行超过 1 分钟的活动查询可能已卡住或较重。wait_event 显示正在等待的对象。该查询只列出活动查询；'idle in transaction' 会话（会阻止 VACUUM 清理死元组）需通过 state = 'idle in transaction' 和 xact_start 单独检查。","severity":"caution"},{"id":"blocking-locks","group":"性能","title":"阻塞会话与等待的锁","command":"SELECT blocked.pid AS blocked_pid, blocked.usename AS blocked_user, blocking.pid AS blocking_pid, blocking.usename AS blocking_user, now() - blocked.query_start AS blocked_duration, left(blocked.query, 60) AS blocked_query FROM pg_stat_activity blocked JOIN pg_stat_activity blocking ON blocking.pid = ANY(pg_blocking_pids(blocked.pid)) WHERE cardinality(pg_blocking_pids(blocked.pid)) > 0;","expect":"不应返回任何行。阻塞会话（blocking_pid）会导致应用延迟和超时；调查长时间阻塞的事务，必要时用 pg_terminate_backend 终止。","severity":"danger"},{"id":"archiver-status","group":"备份与恢复","title":"WAL 归档健康状况（pg_stat_archiver）","command":"SELECT archived_count, last_archived_wal, last_archived_time, failed_count, last_failed_wal, last_failed_time, now() - last_archived_time AS since_last_archive FROM pg_stat_archiver;","expect":"failed_count 应为 0，且 last_failed_time 应早于 last_archived_time。如果 since_last_archive 非常大，说明归档已停止（archive_command 失败）；PITR 备份链已断裂。","severity":"danger"},{"id":"last-basebackup","group":"备份与恢复","title":"最近一次备份与 WAL 归档的新鲜度","command":"-- Base backup time is not stored in the database; read it from the backup tool: pgbackrest info / barman list-backup <server>\nSELECT pg_is_in_recovery() AS in_recovery, last_archived_wal, last_archived_time, now() - last_archived_time AS since_last_archive, failed_count, last_failed_time FROM pg_stat_archiver;","expect":"通过备份工具（pgBackRest / Barman / pg_basebackup 作业日志）确认最近一次基础备份的时间；它必须足够新以满足 RPO 策略。PITR 还要求 WAL 归档连续：如果 since_last_archive 很大或 failed_count 在增加，归档链可能已断裂。","severity":"danger"},{"id":"locked-expired-roles","group":"安全","title":"已过期或即将过期的登录角色","command":"SELECT rolname, rolcanlogin, rolvaliduntil, rolconnlimit FROM pg_roles WHERE rolcanlogin = true AND (rolvaliduntil IS NOT NULL AND rolvaliduntil < now() + interval '7 days') ORDER BY rolvaliduntil;","expect":"rolvaliduntil 已过期的应用角色无法登录，会导致停机。对未来 7 天内过期的密码提前规划续期。","severity":"caution"},{"id":"superuser-roles","group":"安全","title":"高权限角色（SUPERUSER / 创建权限）","command":"SELECT rolname, rolsuper, rolcreaterole, rolcreatedb, rolbypassrls FROM pg_roles WHERE rolsuper = true OR rolcreaterole = true OR rolbypassrls = true ORDER BY rolsuper DESC, rolname;","expect":"SUPERUSER 只应存在于必要且已知的账户上。rolbypassrls = true 的角色会绕过行级安全（RLS）；意外的高权限应按最小权限原则撤销。","severity":"caution"},{"id":"hba-trust-auth","group":"安全","title":"弱认证（trust）规则","command":"SELECT type, database, user_name, address, auth_method FROM pg_hba_file_rules WHERE auth_method IN ('trust','password') ORDER BY line_number;","expect":"auth_method = trust 允许无密码访问，在生产环境中很危险；'password' 以明文发送密码。请替换为 scram-sha-256。（需要 PG 10+。）","severity":"danger"},{"id":"replication-lag","group":"复制","title":"流复制延迟（主库端）","command":"SELECT client_addr, state, sync_state, pg_size_pretty(pg_wal_lsn_diff(pg_current_wal_lsn(), sent_lsn)) AS sent_lag, pg_size_pretty(pg_wal_lsn_diff(pg_current_wal_lsn(), replay_lsn)) AS replay_lag, write_lag, flush_lag, replay_lag AS replay_time_lag FROM pg_stat_replication ORDER BY client_addr;","expect":"在主库上执行。state 应为 streaming，且 replay_lag（字节/时间）应较小。如果备库根本没有出现，说明连接已断开；如果 sync_state = async，说明同步复制未按预期工作。","severity":"danger"},{"id":"replica-lag-standby","group":"复制","title":"复制延迟（备库端）","command":"SELECT pg_is_in_recovery() AS in_recovery, CASE WHEN pg_is_in_recovery() THEN date_trunc('second', now() - pg_last_xact_replay_timestamp()) ELSE NULL END AS replication_delay, pg_last_wal_receive_lsn() AS receive_lsn, pg_last_wal_replay_lsn() AS replay_lsn;","expect":"在备库服务器上执行。in_recovery 应为 true。如果 replication_delay 达到分钟/小时级别，可能存在应用延迟；但主库上没有写入时该值也会增长（误报），因此需结合 LSN 差值解读；如果 receive_lsn 与 replay_lsn 之间差距较大，说明 WAL 回放跟不上。","severity":"danger"}]},"health/mysql.json":{"engineId":"mysql","engine":"MySQL","order":3,"checks":[{"id":"version-uptime","group":"常规","title":"版本与运行时间","command":"SELECT VERSION() AS mysql_version, VARIABLE_VALUE AS uptime_seconds, ROUND(VARIABLE_VALUE/86400, 2) AS uptime_days FROM performance_schema.global_status WHERE VARIABLE_NAME = 'Uptime';","expect":"显示版本和以秒为单位的运行时间。如果 uptime_seconds 意外地小，可能发生了计划外重启或崩溃；请检查错误日志。","severity":"safe"},{"id":"error-log-status","group":"常规","title":"近期错误日志条目（performance_schema）","command":"SELECT logged, prio, error_code, subsystem, LEFT(data, 120) AS message FROM performance_schema.error_log WHERE prio IN ('Error','Warning') ORDER BY logged DESC LIMIT 30;","expect":"显示近期的错误和警告（需要 MySQL 8.0+）。调查 prio = 'Error' 的条目（尤其是 InnoDB、锁和磁盘错误）。在旧版本上，检查服务器的 log_error 文件。","severity":"danger"},{"id":"crashed-tables","group":"常规","title":"损坏的表 / 需检查的表","command":"SELECT table_schema, table_name, engine, table_rows, update_time FROM information_schema.tables WHERE table_schema NOT IN ('mysql','information_schema','performance_schema','sys') AND engine IS NOT NULL ORDER BY table_schema, table_name;","expect":"列出每张表的引擎和最近更新时间。MyISAM 表可能崩溃；如有疑问，使用 CHECK TABLE ... 或 mysqlcheck 验证完整性。关键数据推荐使用 InnoDB。","severity":"caution"},{"id":"schema-sizes","group":"容量","title":"Schema（数据库）大小","command":"SELECT table_schema, ROUND(SUM(data_length + index_length)/1024/1024, 1) AS size_mb, ROUND(SUM(data_free)/1024/1024, 1) AS free_mb FROM information_schema.tables GROUP BY table_schema ORDER BY SUM(data_length + index_length) DESC;","expect":"显示 schema 大小和可回收的 data_free 空间。应结合磁盘容量评估；如果 free_mb 较高，可通过 OPTIMIZE TABLE 回收空间。","severity":"danger"},{"id":"largest-tables","group":"容量","title":"最大的表（包括 data_free / 碎片）","command":"SELECT table_schema, table_name, engine, ROUND((data_length + index_length)/1024/1024, 1) AS total_mb, ROUND(data_free/1024/1024, 1) AS data_free_mb, table_rows FROM information_schema.tables WHERE table_schema NOT IN ('mysql','information_schema','performance_schema','sys') ORDER BY (data_length + index_length) DESC LIMIT 20;","expect":"显示最大的表。data_free_mb 较高的表存在碎片；可通过 OPTIMIZE TABLE 回收空间。对于非常大的表，考虑分区。","severity":"safe"},{"id":"innodb-tablespace","group":"容量","title":"InnoDB 表空间与临时空间使用情况","command":"SELECT FILE_NAME, TABLESPACE_NAME, ROUND(TOTAL_EXTENTS * EXTENT_SIZE / 1024 / 1024, 1) AS total_mb, ROUND((TOTAL_EXTENTS - FREE_EXTENTS) * EXTENT_SIZE / 1024 / 1024, 1) AS used_mb FROM information_schema.files WHERE FILE_TYPE = 'TABLESPACE' ORDER BY TOTAL_EXTENTS DESC LIMIT 20;","expect":"显示 InnoDB 数据文件的填充程度。如果 innodb_file_per_table 关闭，ibdata1 会持续增长且永不收缩。对于临时表空间膨胀，还需检查 SHOW ENGINE INNODB STATUS 的输出。","severity":"caution"},{"id":"connections-vs-limit","group":"容量","title":"连接数与 max_connections 上限","command":"SELECT (SELECT VARIABLE_VALUE FROM performance_schema.global_status WHERE VARIABLE_NAME = 'Threads_connected') AS connected, (SELECT VARIABLE_VALUE FROM performance_schema.global_variables WHERE VARIABLE_NAME = 'max_connections') AS max_connections, (SELECT VARIABLE_VALUE FROM performance_schema.global_status WHERE VARIABLE_NAME = 'Max_used_connections') AS max_used, (SELECT VARIABLE_VALUE FROM performance_schema.global_status WHERE VARIABLE_NAME = 'Aborted_connects') AS aborted_connects;","expect":"如果 max_used 接近 max_connections（超过 85%），存在 'Too many connections' 错误风险。aborted_connects 偏高说明存在认证/网络问题；请使用连接池。","severity":"caution"},{"id":"binlog-size","group":"容量","title":"二进制日志文件与总大小","command":"SHOW BINARY LOGS;","expect":"列出 binlog 文件及其大小。如果总大小对磁盘空间造成压力，检查 binlog_expire_logs_seconds（8.0 之前为 expire_logs_days；该变量在 8.0 中已弃用，并在 8.4 中移除）；如果使用了复制，在副本读取之前不要清除。","severity":"caution"},{"id":"buffer-pool-hit","group":"性能","title":"InnoDB 缓冲池命中率","command":"SELECT (SELECT VARIABLE_VALUE FROM performance_schema.global_status WHERE VARIABLE_NAME = 'Innodb_buffer_pool_read_requests') AS read_requests, (SELECT VARIABLE_VALUE FROM performance_schema.global_status WHERE VARIABLE_NAME = 'Innodb_buffer_pool_reads') AS disk_reads, ROUND((1 - (SELECT VARIABLE_VALUE FROM performance_schema.global_status WHERE VARIABLE_NAME = 'Innodb_buffer_pool_reads') / (SELECT VARIABLE_VALUE FROM performance_schema.global_status WHERE VARIABLE_NAME = 'Innodb_buffer_pool_read_requests')) * 100, 3) AS hit_ratio_pct;","expect":"hit_ratio_pct 的目标通常高于 99%。如果持续偏低，说明 innodb_buffer_pool_size 不足；在专用服务器上可考虑增大到 RAM 的 ~70-80%（共享服务器上应更少）。","severity":"safe"},{"id":"top-queries","group":"性能","title":"开销最大的查询（sys.statement_analysis）","command":"SELECT query, db, exec_count, total_latency, avg_latency, rows_sent_avg, rows_examined_avg FROM sys.statement_analysis ORDER BY total_latency DESC LIMIT 15;","expect":"必须启用 sys schema 和 performance_schema。消耗 total_latency 最多的查询是调优对象。如果 rows_examined_avg 远大于 rows_sent_avg，说明缺少索引；用 EXPLAIN 分析。","severity":"safe"},{"id":"long-running-queries","group":"性能","title":"长时间运行的活动查询","command":"SELECT id, user, host, db, command, time AS seconds, state, LEFT(info, 80) AS query FROM information_schema.processlist WHERE command != 'Sleep' AND time > 60 ORDER BY time DESC;","expect":"运行超过 60 秒的查询可能已卡住或较重。state 列显示正在等待的内容。处于 'Sleep' 状态的长会话也会耗尽连接池；必要时使用 KILL <id>。","severity":"caution"},{"id":"blocking-locks","group":"性能","title":"阻塞会话与等待的锁","command":"SELECT r.trx_id AS waiting_trx, r.trx_mysql_thread_id AS waiting_thread, TIMESTAMPDIFF(SECOND, r.trx_wait_started, NOW()) AS wait_seconds, LEFT(r.trx_query, 60) AS waiting_query, b.trx_mysql_thread_id AS blocking_thread, LEFT(b.trx_query, 60) AS blocking_query FROM performance_schema.data_lock_waits w JOIN information_schema.innodb_trx r ON r.trx_id = w.requesting_engine_transaction_id JOIN information_schema.innodb_trx b ON b.trx_id = w.blocking_engine_transaction_id ORDER BY wait_seconds DESC;","expect":"不应返回任何行。阻塞事务（blocking_thread）会导致应用延迟和 lock wait timeout 错误。调查长时间阻塞的事务，必要时用 KILL 终止。（MySQL 8.0 语法。）","severity":"danger"},{"id":"binlog-enabled","group":"备份与恢复","title":"二进制日志与恢复设置","command":"SELECT (SELECT VARIABLE_VALUE FROM performance_schema.global_variables WHERE VARIABLE_NAME = 'log_bin') AS log_bin, (SELECT VARIABLE_VALUE FROM performance_schema.global_variables WHERE VARIABLE_NAME = 'binlog_format') AS binlog_format, (SELECT VARIABLE_VALUE FROM performance_schema.global_variables WHERE VARIABLE_NAME = 'sync_binlog') AS sync_binlog, (SELECT VARIABLE_VALUE FROM performance_schema.global_variables WHERE VARIABLE_NAME = 'innodb_flush_log_at_trx_commit') AS flush_log_commit;","expect":"要进行时间点恢复，log_bin 必须为 ON。为保证持久性，推荐 sync_binlog = 1 和 innodb_flush_log_at_trx_commit = 1（否则崩溃时可能丢失事务）。binlog_format = ROW 对复制最安全。","severity":"danger"},{"id":"last-backup-note","group":"备份与恢复","title":"最近一次备份时间（binlog / 工具指标）","command":"SELECT (SELECT VARIABLE_VALUE FROM performance_schema.global_status WHERE VARIABLE_NAME = 'Uptime') AS uptime_seconds, (SELECT MAX(update_time) FROM information_schema.tables WHERE table_schema NOT IN ('mysql','information_schema','performance_schema','sys')) AS last_data_change;","expect":"MySQL 本身不跟踪备份状态；请从 mysqldump / MySQL Shell dump / Percona XtraBackup 工具日志中确认最近一次成功备份的时间（mysqlpump 已在 8.0.34 中弃用，并在 8.4 中移除）。备份必须满足 RPO 策略要求的新鲜度（例如最近 24 小时内）。","severity":"danger"},{"id":"locked-expired-accounts","group":"安全","title":"被锁定或密码已过期的账户","command":"SELECT user, host, account_locked, password_expired, password_lifetime FROM mysql.user ORDER BY user, host;","expect":"account_locked = 'Y' 或 password_expired = 'Y' 的关键应用账户会导致中断。如果 password_lifetime 为 NULL 且全局过期关闭，密码将永不过期；这可能是策略漏洞。","severity":"caution"},{"id":"anonymous-nopassword-users","group":"安全","title":"匿名用户与无密码用户","command":"SELECT user, host, plugin, IF(authentication_string = '', 'EMPTY_PASSWORD', 'set') AS password_status FROM mysql.user WHERE user = '' OR authentication_string = '' ORDER BY host;","expect":"不应返回任何行。匿名用户（user = ''）和无密码账户是严重的安全漏洞；使用 mysql_secure_installation 删除，或为其设置强密码。","severity":"danger"},{"id":"super-privilege-users","group":"安全","title":"高权限（SUPER / GRANT）用户","command":"SELECT grantee, privilege_type FROM information_schema.user_privileges WHERE privilege_type IN ('SUPER','GRANT OPTION','SHUTDOWN','FILE','CREATE USER') ORDER BY grantee, privilege_type;","expect":"SUPER、FILE 和 GRANT OPTION 等权限只应存在于必要且已知的账户上。意外的被授权者存在权限提升风险；尤其要注意以 %（任意主机）定义的高权限账户。","severity":"caution"},{"id":"replica-status","group":"复制","title":"副本（备库）状态与延迟","command":"SHOW REPLICA STATUS;","expect":"在副本上执行（MySQL 8.0.22+）。Replica_IO_Running 和 Replica_SQL_Running 必须都为 'Yes'。Seconds_Behind_Source 应较小；如果为 NULL，说明复制已停止。检查 Last_Error 字段。（旧版本使用 SHOW SLAVE STATUS。）","severity":"danger"},{"id":"replication-threads","group":"复制","title":"复制连接与应用线程状态（performance_schema）","command":"SELECT c.channel_name, c.service_state AS io_state, a.service_state AS sql_state, c.last_error_message AS io_error, w.last_error_message AS sql_error FROM performance_schema.replication_connection_status c JOIN performance_schema.replication_applier_status a ON c.channel_name = a.channel_name LEFT JOIN performance_schema.replication_applier_status_by_coordinator w ON w.channel_name = a.channel_name;","expect":"io_state 和 sql_state 必须为 'ON'。如果 io_error/sql_error 有值，说明复制已失败并停止；在多源复制配置中，每个 channel_name 都必须各自健康。","severity":"danger"}]},"health/mssql.json":{"engineId":"mssql","engine":"SQL Server","order":4,"checks":[{"id":"version-uptime","group":"常规","title":"版本与运行时间","command":"SELECT @@VERSION AS version, sqlserver_start_time AS start_time, DATEDIFF(HOUR, sqlserver_start_time, GETDATE()) AS uptime_hours FROM sys.dm_os_sys_info;","expect":"确认版本和补丁级别。如果运行时间很短（不到几个小时），可能发生了意外重启；请查看 SQL Server 错误日志。","severity":"safe"},{"id":"database-states","group":"常规","title":"处于问题状态的数据库（SUSPECT / RECOVERY_PENDING）","command":"SELECT name AS database_name, state_desc AS state, recovery_model_desc AS recovery_model FROM sys.databases WHERE state_desc <> N'ONLINE' ORDER BY name;","expect":"所有生产数据库都必须为 ONLINE。SUSPECT、RECOVERY_PENDING 或 EMERGENCY 需要立即处理；启动恢复并查看错误日志。","severity":"danger"},{"id":"recent-errors","group":"常规","title":"近期重要错误（内存/错误环形缓冲区）","command":"SELECT TOP 20 CAST(record AS XML).value('(//Record/@id)[1]', 'int') AS record_id, DATEADD(ms, timestamp - (SELECT ms_ticks FROM sys.dm_os_sys_info), GETDATE()) AS event_time, CAST(record AS XML) AS record_xml FROM sys.dm_os_ring_buffers WHERE ring_buffer_type = N'RING_BUFFER_EXCEPTION' ORDER BY timestamp DESC;","expect":"查看近期的异常/错误记录。频繁重复出现的 823/824/825（I/O 一致性）错误表明磁盘损坏。如需详细信息，还可通过 xp_readerrorlog 读取服务器上的错误日志。","severity":"caution"},{"id":"data-log-file-usage","group":"容量","title":"数据文件与日志文件使用情况","command":"SELECT DB_NAME() AS database_name, name AS logical_name, type_desc AS file_type, size/128.0 AS allocated_mb, CAST(FILEPROPERTY(name, 'SpaceUsed') AS INT)/128.0 AS used_mb, (size - CAST(FILEPROPERTY(name, 'SpaceUsed') AS INT))/128.0 AS free_mb, CASE WHEN max_size = -1 THEN N'Unlimited' WHEN max_size = 268435456 THEN N'2 TB (log)' ELSE CAST(max_size/128 AS VARCHAR(20)) + N' MB' END AS max_size_limit FROM sys.database_files;","expect":"在每个数据库中执行。如果已用空间非常接近已分配空间（85%+）且自动增长受限，规划扩容或添加磁盘。如果日志文件持续写满，检查备份/恢复模式设置。","severity":"caution"},{"id":"largest-tables","group":"容量","title":"最大的表（大小与行数）","command":"SELECT TOP 20 s.name AS schema_name, t.name AS table_name, p.rows AS row_count, SUM(a.total_pages)*8/1024 AS total_mb, SUM(a.used_pages)*8/1024 AS used_mb FROM sys.tables t JOIN sys.schemas s ON t.schema_id = s.schema_id JOIN sys.indexes i ON t.object_id = i.object_id JOIN sys.partitions p ON i.object_id = p.object_id AND i.index_id = p.index_id JOIN sys.allocation_units a ON p.partition_id = a.container_id WHERE i.index_id <= 1 GROUP BY s.name, t.name, p.rows ORDER BY total_mb DESC;","expect":"找出占用空间最多的表。意外增长可能说明需要归档、索引维护或分区。","severity":"safe"},{"id":"tempdb-usage","group":"容量","title":"tempdb 空间使用情况","command":"SELECT SUM(user_object_reserved_page_count)*8/1024 AS user_objects_mb, SUM(internal_object_reserved_page_count)*8/1024 AS internal_objects_mb, SUM(version_store_reserved_page_count)*8/1024 AS version_store_mb, SUM(unallocated_extent_page_count)*8/1024 AS free_mb FROM sys.dm_db_file_space_usage;","expect":"如果 tempdb 即将耗尽可用空间（unallocated 偏低），查询可能失败。如果版本存储持续增长，说明存在长时间运行的未提交事务；使用 sys.dm_tran_active_snapshot_database_transactions 调查。","severity":"caution"},{"id":"connection-count","group":"容量","title":"连接数与分布","command":"SELECT DB_NAME(dbid) AS database_name, COUNT(*) AS connection_count, SUM(CASE WHEN status = 'running' THEN 1 ELSE 0 END) AS active FROM sys.sysprocesses WHERE dbid > 0 GROUP BY dbid ORDER BY connection_count DESC;","expect":"按应用/数据库跟踪连接数。单个应用的连接数异常高，可能说明连接池泄漏。不要接近 max worker threads 上限。","severity":"safe"},{"id":"vlf-count","group":"容量","title":"日志文件 VLF 数量（碎片）","command":"SELECT DB_NAME(database_id) AS database_name, COUNT(*) AS vlf_count FROM sys.dm_db_log_info(DB_ID()) GROUP BY database_id;","expect":"VLF 数量过多（超过数百个，例如 500+）意味着日志文件以非常小的增量增长；这会降低恢复和备份性能。收缩日志后一次性大幅重新扩展。","severity":"caution"},{"id":"buffer-cache-ple","group":"性能","title":"Buffer Cache Hit Ratio 与 Page Life Expectancy","command":"SELECT (SELECT cntr_value FROM sys.dm_os_performance_counters WHERE counter_name = 'Buffer cache hit ratio') AS hit_ratio_raw, (SELECT cntr_value FROM sys.dm_os_performance_counters WHERE counter_name = 'Buffer cache hit ratio base') AS hit_ratio_base, (SELECT cntr_value FROM sys.dm_os_performance_counters WHERE counter_name = 'Page life expectancy' AND object_name LIKE '%Buffer Manager%') AS page_life_expectancy_sec;","expect":"Buffer cache 命中率理想情况下应为 99%+（hit_ratio_raw / hit_ratio_base）。如果 Page Life Expectancy 持续偏低（经验法则：每个 NUMA 节点每 4 GB 低于 300 s），说明存在内存压力；需要增加 RAM 或优化查询。","severity":"caution"},{"id":"expensive-queries","group":"性能","title":"开销最大的查询（CPU 与读取）","command":"SELECT TOP 15 qs.total_worker_time/1000 AS total_cpu_ms, qs.execution_count AS execution_count, qs.total_logical_reads AS total_logical_reads, qs.total_worker_time/qs.execution_count/1000 AS avg_cpu_ms, SUBSTRING(st.text, (qs.statement_start_offset/2)+1, ((CASE qs.statement_end_offset WHEN -1 THEN DATALENGTH(st.text) ELSE qs.statement_end_offset END - qs.statement_start_offset)/2)+1) AS query_text FROM sys.dm_exec_query_stats qs CROSS APPLY sys.dm_exec_sql_text(qs.sql_handle) st ORDER BY qs.total_worker_time DESC;","expect":"按 CPU 和逻辑读找出开销最大的查询。读取量高的查询是缺少索引的迹象；检查其执行计划并添加索引或重写查询。","severity":"safe"},{"id":"blocking-sessions","group":"性能","title":"阻塞会话","command":"SELECT r.session_id AS blocked_session, r.blocking_session_id AS blocking_session, r.wait_type AS wait_type, r.wait_time/1000 AS wait_sec, r.status AS status, t.text AS query FROM sys.dm_exec_requests r CROSS APPLY sys.dm_exec_sql_text(r.sql_handle) t WHERE r.blocking_session_id <> 0 ORDER BY r.wait_time DESC;","expect":"阻塞会话（blocking_session_id <> 0）表示查询之间在相互等待。长时间阻塞会导致应用超时；找出阻塞链头（head blocker），并考虑终止长事务。","severity":"danger"},{"id":"wait-stats","group":"性能","title":"主要等待统计（wait stats）","command":"SELECT TOP 15 wait_type AS wait_type, waiting_tasks_count AS waiting_tasks, wait_time_ms/1000 AS total_wait_sec, (wait_time_ms - signal_wait_time_ms)/1000 AS resource_wait_sec, signal_wait_time_ms/1000 AS cpu_wait_sec FROM sys.dm_os_wait_stats WHERE wait_type NOT IN (N'CLR_SEMAPHORE', N'CLR_AUTO_EVENT', N'CLR_MANUAL_EVENT', N'LAZYWRITER_SLEEP', N'SQLTRACE_BUFFER_FLUSH', N'SQLTRACE_INCREMENTAL_FLUSH_SLEEP', N'WAITFOR', N'BROKER_TASK_STOP', N'BROKER_EVENTHANDLER', N'BROKER_RECEIVE_WAITFOR', N'BROKER_TRANSMITTER', N'CHECKPOINT_QUEUE', N'CHKPT', N'REQUEST_FOR_DEADLOCK_SEARCH', N'XE_TIMER_EVENT', N'XE_DISPATCHER_WAIT', N'XE_LIVE_TARGET_TVF', N'BROKER_TO_FLUSH', N'DISPATCHER_QUEUE_SEMAPHORE', N'HADR_FILESTREAM_IOMGR_IOCOMPLETION', N'HADR_WORK_QUEUE', N'HADR_TIMER_TASK', N'HADR_CLUSAPI_CALL', N'HADR_LOGCAPTURE_WAIT', N'HADR_NOTIFICATION_DEQUEUE', N'DIRTY_PAGE_POLL', N'SP_SERVER_DIAGNOSTICS_SLEEP', N'SOS_WORK_DISPATCHER', N'LOGMGR_QUEUE', N'ONDEMAND_TASK_QUEUE', N'FT_IFTS_SCHEDULER_IDLE_WAIT', N'PWAIT_EXTENSIBILITY_CLEANUP_TASK', N'PWAIT_ALL_COMPONENTS_INITIALIZED', N'STARTUP_DEPENDENCY_MANAGER', N'PREEMPTIVE_XE_DISPATCHER', N'PVS_PREALLOCATE', N'WAIT_XTP_OFFLINE_CKPT_NEW_LOG', N'WAIT_XTP_CKPT_CLOSE', N'WAIT_XTP_HOST_WAIT', N'PARALLEL_REDO_DRAIN_WORKER', N'PARALLEL_REDO_LOG_CACHE', N'PARALLEL_REDO_TRAN_LIST', N'PARALLEL_REDO_WORKER_SYNC', N'PARALLEL_REDO_WORKER_WAIT_WORK') AND wait_type NOT LIKE N'SLEEP[_]%' AND wait_type NOT LIKE N'QDS[_]%' AND waiting_tasks_count > 0 ORDER BY wait_time_ms DESC;","expect":"解读占主导地位的等待类型：PAGEIOLATCH = 磁盘 I/O 瓶颈，CXPACKET/CXCONSUMER = 并行，LCK_M = 锁，WRITELOG = 日志磁盘，RESOURCE_SEMAPHORE = 内存。signal wait 百分比高表示存在 CPU 压力。","severity":"safe"},{"id":"last-backup","group":"备份与恢复","title":"最近备份时间（完整 / 差异 / 日志）","command":"SELECT d.name AS database_name, d.recovery_model_desc AS recovery_model, MAX(CASE WHEN b.type = 'D' THEN b.backup_finish_date END) AS last_full_backup, MAX(CASE WHEN b.type = 'I' THEN b.backup_finish_date END) AS last_differential, MAX(CASE WHEN b.type = 'L' THEN b.backup_finish_date END) AS last_log_backup FROM sys.databases d LEFT JOIN msdb.dbo.backupset b ON d.name = b.database_name WHERE d.database_id > 4 GROUP BY d.name, d.recovery_model_desc ORDER BY last_full_backup;","expect":"每个生产数据库都必须有近期的完整备份。如果 last_full_backup 为 NULL 或非常久远（超出 RPO），说明备份作业失败。在 FULL 恢复模式下，没有日志备份时日志文件会持续增长。","severity":"danger"},{"id":"recovery-model","group":"备份与恢复","title":"恢复模式一致性","command":"SELECT name AS database_name, recovery_model_desc AS recovery_model, log_reuse_wait_desc AS log_reuse_wait FROM sys.databases WHERE database_id > 4 ORDER BY name;","expect":"关键数据库必须使用 FULL 以支持时间点恢复。log_reuse_wait_desc = LOG_BACKUP 表示有待执行的日志备份；ACTIVE_TRANSACTION 表示存在长时间运行的事务。","severity":"caution"},{"id":"sysadmin-members","group":"安全","title":"sysadmin 角色成员","command":"SELECT p.name AS login_name, p.type_desc AS type, p.is_disabled AS disabled FROM sys.server_role_members rm JOIN sys.server_principals r ON rm.role_principal_id = r.principal_id JOIN sys.server_principals p ON rm.member_principal_id = p.principal_id WHERE r.name = N'sysadmin' ORDER BY p.name;","expect":"sysadmin 成员应尽量少。意外的登录名（尤其是应用账户或个人用户）存在权限提升风险；应用最小权限原则。","severity":"danger"},{"id":"sa-and-password-policy","group":"安全","title":"sa 账户与禁用了密码策略的登录名","command":"SELECT name AS login_name, is_disabled AS disabled, is_policy_checked AS password_policy_on, is_expiration_checked AS expiration_check, LOGINPROPERTY(name, 'PasswordLastSetTime') AS last_password_change FROM sys.sql_logins WHERE is_policy_checked = 0 OR name = N'sa' ORDER BY name;","expect":"sa 账户应被禁用或重命名。is_policy_checked = 0 的 SQL 登录名存在弱密码/陈旧密码风险；启用密码策略（CHECK_POLICY）。","severity":"caution"},{"id":"alwayson-sync","group":"复制","title":"Always On 同步状态与队列","command":"SELECT ag.name AS availability_group, ar.replica_server_name AS replica_server, DB_NAME(drs.database_id) AS database_name, drs.synchronization_state_desc AS sync_state, drs.synchronization_health_desc AS health, drs.log_send_queue_size AS log_send_queue_kb, drs.redo_queue_size AS redo_queue_kb FROM sys.dm_hadr_database_replica_states drs JOIN sys.availability_replicas ar ON drs.replica_id = ar.replica_id JOIN sys.availability_groups ag ON ar.group_id = ag.group_id ORDER BY ag.name, replica_server;","expect":"synchronization_state_desc 应为 SYNCHRONIZED（同步）或 SYNCHRONIZING（异步），synchronization_health 应为 HEALTHY。如果 log_send_queue 或 redo_queue 在增长，说明副本正在落后；故障转移时的数据丢失/RTO 风险会增加。","severity":"caution"}]},"health/mongodb.json":{"engineId":"mongodb","engine":"MongoDB","order":5,"checks":[{"id":"version-uptime","group":"常规","title":"版本与运行时间","command":"db.serverStatus().version; db.serverStatus().uptime","expect":"确认版本受支持且已打补丁。uptime 以秒为单位；如果非常小（几千秒），说明发生了意外重启；请查看 mongod 日志。","severity":"safe"},{"id":"replset-status","group":"常规","title":"副本集成员状态（health）","command":"rs.status().members.forEach(function(m) { print(m.name + \" | \" + m.stateStr + \" | health=\" + m.health); })","expect":"每个成员都应为 health=1，且必须恰好有一个 PRIMARY。health=0 或（not reachable/healthy）、DOWN 和 RECOVERING 状态表示故障。如果有多个 PRIMARY 或没有 PRIMARY，说明存在选举问题。","severity":"danger"},{"id":"dbstats-sizes","group":"容量","title":"数据库大小（data / storage / index）","command":"db.stats(1024*1024)","expect":"监控 dataSize、storageSize 和 indexSize（MB）。如果 storageSize 远大于 dataSize，可能存在碎片。让 indexSize 保持在 RAM（WiredTiger 缓存）之内对性能至关重要。","severity":"safe"},{"id":"disk-space","group":"容量","title":"磁盘空间使用情况（fsUsedSize / fsTotalSize）","command":"var st = db.stats(); printjson({fsUsedSize_MB: (st.fsUsedSize/1048576).toFixed(0), fsTotalSize_MB: (st.fsTotalSize/1048576).toFixed(0), usedPct: (100*st.fsUsedSize/st.fsTotalSize).toFixed(1)})","expect":"如果 fsUsedSize 与 fsTotalSize 之比超过 85%，磁盘即将写满；磁盘写满时 MongoDB 会停止写入。规划添加磁盘或归档数据。","severity":"caution"},{"id":"largest-collections","group":"容量","title":"最大的集合","command":"db.getCollectionNames().map(function(c) { var s = db.getCollection(c).stats(); return {collection: c, size_MB: (s.size/1048576).toFixed(1), storage_MB: (s.storageSize/1048576).toFixed(1), index_MB: (s.totalIndexSize/1048576).toFixed(1), docs: s.count}; }).sort(function(a,b){return b.size_MB - a.size_MB;})","expect":"找出占用空间最多的集合。意外增长可能说明需要归档、TTL 索引或分片。检查索引大小超过数据大小的集合。","severity":"safe"},{"id":"connections","group":"容量","title":"连接数（current / available）","command":"db.serverStatus().connections","expect":"如果 current 接近 available，连接上限即将达到，新连接将被拒绝。检查应用连接池设置和可能的泄漏。","severity":"caution"},{"id":"wiredtiger-cache","group":"性能","title":"WiredTiger 缓存使用与淘汰","command":"var wt = db.serverStatus().wiredTiger.cache; printjson({used_MB: (wt['bytes currently in the cache']/1048576).toFixed(0), max_MB: (wt['maximum bytes configured']/1048576).toFixed(0), dirty_MB: (wt['tracked dirty bytes in the cache']/1048576).toFixed(0), pages_evicted: wt['unmodified pages evicted']})","expect":"已用内存超过最大缓存的 80% 时会触发淘汰；超过 95% 时，应用线程会被迫参与淘汰，性能下降。脏页比例高表示存在写入压力。","severity":"caution"},{"id":"slow-queries-profiler","group":"性能","title":"慢查询（profiler）与 COLLSCAN","command":"printjson(db.getProfilingStatus()); db.system.profile.find({millis: {$gt: 100}}).sort({ts: -1}).limit(10).forEach(function(d){ print(d.ts + \" | \" + d.millis + \"ms | \" + (d.planSummary || '') + \" | \" + JSON.stringify(d.command)); })","expect":"该检查是只读的：先显示 profiler 状态（was: 0 关闭，1 仅慢操作，2 全部），再列出已记录的慢操作。如果 profiler 关闭（was: 0），system.profile 为空；诊断时可作为单独步骤用 db.setProfilingLevel(1, {slowms: 100}) 启用，完成后用 db.setProfilingLevel(0) 关闭。planSummary 中显示 COLLSCAN 的查询执行了全集合扫描，缺少索引。对 millis 值较高的查询通过建立索引进行优化。profiler 会增加开销，在生产环境中应谨慎使用。","severity":"caution"},{"id":"long-running-ops","group":"性能","title":"长时间运行的操作（currentOp）","command":"db.getSiblingDB('admin').aggregate([{$currentOp: {}}, {$match: {active: true, secs_running: {$gt: 5}}}, {$project: {opid: 1, secs_running: 1, ns: 1, op: 1, planSummary: 1, waitingForLock: 1}}])","expect":"secs_running 值较高的活动操作可能导致阻塞。必要时用 db.killOp(opid) 终止。waitingForLock=true 的操作正在等待锁；调查根源操作。","severity":"danger"},{"id":"oplog-window","group":"备份与恢复","title":"Oplog 窗口（复制历史）","command":"rs.printReplicationInfo()","expect":"oplog 窗口（配置的 oplog 大小和以小时计的日志时长）应足够长。如果窗口很短（几个小时），慢速从节点可能陷入完整重新同步（initial sync）。备份和时间点恢复策略依赖于 oplog。","severity":"caution"},{"id":"backup-note","group":"备份与恢复","title":"备份状态与最近备份说明","command":"var r = db.getSiblingDB('local').getCollectionNames().includes('oplog.rs') ? db.getReplicationInfo() : null; printjson(r ? {oplogWindowHours: r.timeDiffHours, tFirst: r.tFirst, tLast: r.tLast} : 'standalone: no oplog'); print('Backup verification: check mongodump/mongorestore or Ops Manager / Percona Backup (PBM) status externally.')","expect":"MongoDB 没有内置的计划备份；使用 mongodump、文件系统快照、Ops Manager 或 PBM。在外部工具中确认最近一次成功备份的时间和恢复测试。在副本集上，oplog 窗口（oplogWindowHours）限制了基于 oplog 的 PITR 能回溯多远；它不得短于备份间隔。检查是否满足 RPO/RTO 目标。","severity":"danger"},{"id":"users-roles","group":"安全","title":"用户与角色列表","command":"db.getSiblingDB('admin').system.users.find({}, {user: 1, db: 1, roles: 1, _id: 0}).forEach(printjson)","expect":"检查已定义的用户及其角色。意外的账户或范围过广的角色（root、__system、dbOwner）存在权限提升风险。应用最小权限原则。","severity":"caution"},{"id":"auth-enabled","group":"安全","title":"是否启用了认证（authorization）","command":"db.getSiblingDB('admin').runCommand({getCmdLineOpts: 1}).parsed.security","expect":"security.authorization 必须为 'enabled'。如果被禁用（或没有 security 配置块），数据库无需认证即可访问；这是严重的安全漏洞，请立即启用。","severity":"danger"},{"id":"secondary-lag","group":"复制","title":"从节点复制延迟","command":"rs.printSecondaryReplicationInfo()","expect":"每个从节点的 'replication lag' 值应较低（几秒）。如果延迟很大（分钟级），说明从节点跟不上负载或存在网络/磁盘问题；故障转移时的数据丢失和读取不一致风险会增加。","severity":"caution"},{"id":"member-health","group":"复制","title":"成员健康与选举状态","command":"rs.status().members.map(function(m){ return {member: m.name, state: m.stateStr, health: m.health, configVersion: m.configVersion, last_heartbeat: m.lastHeartbeatMessage || 'OK'}; })","expect":"所有成员都应健康（health=1）并处于预期状态（PRIMARY/SECONDARY）。lastHeartbeatMessage 中的错误消息表示存在网络分区或不可达成员。失去多数派会导致写入停止。","severity":"caution"}]},"health/redis.json":{"engineId":"redis","engine":"Redis","order":6,"checks":[{"id":"server-version-uptime","group":"常规","title":"版本与运行时间","command":"redis-cli INFO server | grep -E \"redis_version|uptime_in_days|redis_mode|os\"","expect":"redis_version 应为受支持且已打补丁的版本。如果 uptime_in_days 非常小，说明发生了意外重启；redis_mode（standalone/sentinel/cluster）可确认配置。","severity":"safe"},{"id":"persistence-status","group":"常规","title":"持久化状态（RDB / AOF）","command":"redis-cli INFO persistence | grep -E \"rdb_last_bgsave_status|aof_enabled|aof_last_bgrewrite_status|aof_last_write_status|rdb_bgsave_in_progress|loading\"","expect":"rdb_last_bgsave_status 和 aof_last_write_status 必须为 'ok'。'err' 值表示存在磁盘/权限问题或磁盘已满，有数据丢失风险。loading:1 表示服务器仍在加载数据。","severity":"danger"},{"id":"memory-usage","group":"容量","title":"内存使用（used_memory / maxmemory）","command":"redis-cli INFO memory | grep -E \"used_memory_human|used_memory:|maxmemory_human|maxmemory:|maxmemory_policy\"","expect":"如果 used_memory 接近 maxmemory（85%+），存在淘汰或 OOM 风险。如果 maxmemory=0 则不设上限，Redis 可能耗尽所有 RAM 并拖垮系统；务必设置上限和合适的 maxmemory_policy。","severity":"caution"},{"id":"memory-fragmentation","group":"容量","title":"内存碎片率","command":"redis-cli INFO memory | grep -E \"mem_fragmentation_ratio|mem_allocator\"","expect":"mem_fragmentation_ratio 理想情况下介于 1.0 和 1.5 之间。高于 1.5 表示碎片严重（内存浪费）。低于 1.0 表示 Redis 被交换到磁盘（严重的性能问题）；检查系统 RAM。","severity":"caution"},{"id":"evicted-keys","group":"容量","title":"被淘汰的键（evicted_keys）","command":"redis-cli INFO stats | grep -E \"evicted_keys|expired_keys|keyspace_misses\"","expect":"如果 evicted_keys 持续增加，说明 Redis 已达到内存上限并正在丢弃数据；这可能造成非预期的数据丢失。考虑增加内存、使用 TTL 或检查 maxmemory_policy。","severity":"caution"},{"id":"dbsize-keyspace","group":"容量","title":"键数量与 keyspace 分布","command":"redis-cli DBSIZE; redis-cli INFO keyspace","expect":"监控键总数（DBSIZE）和各数据库的分布（INFO keyspace：keys、expires、avg_ttl）。不带 TTL 的 keyspace 持续增长（expires 偏低）会导致内存膨胀。键数量突然激增可能是异常行为的迹象。","severity":"safe"},{"id":"hit-ratio","group":"性能","title":"缓存命中率（keyspace hits/misses）","command":"redis-cli INFO stats | grep -E \"keyspace_hits|keyspace_misses\"","expect":"命中率 = hits / (hits + misses)。用作缓存时命中率应较高（80%+）。命中率低表示缓存效率低、TTL 设置不当或存在缓存未命中模式；确保经常请求的数据已被缓存。","severity":"safe"},{"id":"slowlog","group":"性能","title":"慢命令日志（SLOWLOG）","command":"redis-cli SLOWLOG GET 10","expect":"检查慢命令（默认阈值 10ms）。O(N) 命令（KEYS、SMEMBERS、大型 HGETALL、SORT）会阻塞单线程的 Redis，延迟所有请求。用 SCAN 等替代方案替换它们。","severity":"caution"},{"id":"clients","group":"性能","title":"已连接与被阻塞的客户端（clients）","command":"redis-cli INFO clients | grep -E \"connected_clients|blocked_clients|maxclients|client_recent_max_input_buffer\"","expect":"如果 connected_clients 接近 maxclients，新连接将被拒绝。blocked_clients 数量较高（等待 BLPOP/BRPOP）可能是设计使然，但意外上升是拥塞的迹象。检查连接泄漏。","severity":"safe"},{"id":"ops-per-sec","group":"性能","title":"瞬时吞吐量（ops/sec）","command":"redis-cli INFO stats | grep -E \"instantaneous_ops_per_sec|total_commands_processed|rejected_connections|total_net_input_bytes\"","expect":"instantaneous_ops_per_sec 显示当前负载。突然下降/激增是异常的迹象。如果 rejected_connections > 0，说明已达到 maxclients 上限。由于是单线程，极高的 ops 可能造成 CPU 瓶颈。","severity":"safe"},{"id":"rdb-last-save","group":"备份与恢复","title":"最近一次 RDB 保存与待保存的变更","command":"redis-cli INFO persistence | grep -E \"rdb_last_save_time|rdb_changes_since_last_save|rdb_last_bgsave_status\"; redis-cli LASTSAVE","expect":"如果 rdb_changes_since_last_save 非常高，说明自上次快照以来已积累大量数据；崩溃时这些变更会丢失。如果 rdb_last_save_time（LASTSAVE，Unix 时间）非常久远，检查保存间隔或备份频率。","severity":"caution"},{"id":"aof-rewrite","group":"备份与恢复","title":"AOF 重写状态","command":"redis-cli INFO persistence | grep -E \"aof_enabled|aof_last_bgrewrite_status|aof_rewrite_in_progress|aof_current_size|aof_base_size\"","expect":"在要求持久性的场景中，必须设置 aof_enabled=1。aof_last_bgrewrite_status 必须为 'ok'；'err' 表示存在磁盘/持久化问题。如果 aof_current_size 远大于 base_size，需要进行重写。","severity":"caution"},{"id":"acl-auth","group":"安全","title":"ACL / 认证（requirepass）设置","command":"redis-cli ACL LIST; redis-cli ACL GETUSER default","expect":"必须至少有一个受密码保护的用户。如果 ACL GETUSER default 输出中的 flags 显示 'on' 和 'nopass'（requirepass 也就是该用户的密码），Redis 无需认证即可访问；这是严重的安全漏洞。密码仅以哈希形式显示；不要用 CONFIG GET requirepass 做此项检查，因为它会以明文输出密码。通过 ACL 定义最小权限用户。","severity":"danger"},{"id":"dangerous-commands","group":"安全","title":"危险命令与 protected-mode","command":"redis-cli CONFIG GET protected-mode; redis-cli CONFIG GET bind; redis-cli COMMAND INFO flushall flushdb config keys","expect":"protected-mode 应为 'yes'，bind 应限制在安全的接口上。FLUSHALL、FLUSHDB、CONFIG、KEYS 和 DEBUG 等危险命令在生产环境中应通过 rename-command 禁用或通过 ACL 限制。","severity":"caution"},{"id":"replication-status","group":"复制","title":"复制状态（role、master link）","command":"redis-cli INFO replication","expect":"确认 role（master/slave）、connected_slaves 以及每个副本的状态。在副本上，master_link_status 必须为 'up'；'down' 表示链路已断开。如果 master_last_io_seconds_ago 较高（延迟），数据丢失和不一致的风险会增加。","severity":"danger"}]},"health/elasticsearch.json":{"engineId":"elasticsearch","engine":"Elasticsearch","order":7,"checks":[{"id":"cluster-health","group":"常规","title":"集群健康状况（green / yellow / red）","command":"GET _cluster/health","expect":"status 应为 'green'：所有主分片和副本分片均已分配。'yellow' 表示主分片完好但部分副本未分配（冗余风险）；'red' 表示至少一个主分片丢失/未分配（部分数据不可用，需要立即处理）。unassigned_shards 应为 0。","severity":"danger"},{"id":"cluster-version","group":"常规","title":"版本与集群信息","command":"GET /","expect":"version.number 应为已应用安全补丁的受支持版本。旧版本/EOL（生命周期结束）版本存在安全和稳定性风险。还需通过 GET _cat/nodes?v&h=name,version 确认所有节点运行相同版本（滚动升级期间暂时混合是正常的）。","severity":"safe"},{"id":"pending-tasks","group":"常规","title":"待处理的集群任务","command":"GET _cluster/pending_tasks","expect":"列表应为空或非常短。持续堆积且长时间等待（time_in_queue 较高）的任务表明 master 节点过载或集群状态更新停滞；这会降低整个集群的响应时间。","severity":"caution"},{"id":"disk-allocation-watermark","group":"容量","title":"磁盘分配与水位状态","command":"GET _cat/allocation?v","expect":"每个节点的 disk.percent 应低于高水位（默认 90%）。超过 85% 为警告，90%（high）会触发分片迁移，95%（flood_stage）会使索引变为只读。确认 shards 列在各节点间均衡；分布不均会导致单个节点过早写满。","severity":"danger"},{"id":"largest-indices","group":"容量","title":"最大的索引（存储大小）","command":"GET _cat/indices?v&s=store.size:desc","expect":"跟踪占用空间最多的索引及其 doc.count 值。日志/指标索引的失控增长是磁盘写满的主要原因；通过 ILM（Index Lifecycle Management）为其定义 rollover 和删除策略。特别注意状态为 'close' 或健康状况为 yellow/red 的索引。","severity":"caution"},{"id":"jvm-heap-usage","group":"容量","title":"JVM heap 与 RAM 使用（按节点）","command":"GET _cat/nodes?v&h=name,heap.percent,ram.percent,cpu,load_1m","expect":"heap.percent 应持续低于 75%；持续处于 85%+ 意味着内存压力、频繁的 GC 停顿和 circuit breaker 风险。JVM heap 应约为服务器 RAM 的 50%，且最多约 31GB（compressed oops 限制）。对于居高不下的 heap，优化查询或增加节点。","severity":"caution"},{"id":"shard-count-limits","group":"容量","title":"分片数量与每节点分片上限","command":"GET _cluster/health?level=shards","expect":"active_shards 和每个节点的分片数应合理。默认上限为 cluster.max_shards_per_node = 1000（每个数据节点）；接近该上限以及总计数千个小分片（过度分片）会给 master 带来压力并浪费内存。旧的“每 GB heap 约 20 个分片”规则不适用于 8.3+；当前的建议是每个分片 10-50GB，并根据每个节点的字段（mappings）数量规划 heap。initializing_shards / relocating_shards 不应长时间偏高。如果分片过多，使用更大/更少的分片或 rollover。","severity":"caution"},{"id":"http-connections","group":"容量","title":"HTTP 连接数","command":"GET _nodes/stats/http","expect":"http.current_open 应处于合理范围内。如果 http.total_opened 增长非常快，说明客户端没有使用连接池，而是每个请求都打开新连接（效率低下）。current_open 异常偏高表明存在连接泄漏或可能的资源耗尽。","severity":"safe"},{"id":"thread-pool-rejections","group":"性能","title":"线程池拒绝与队列饱和","command":"GET _cat/thread_pool?v&h=node_name,name,active,queue,rejected","expect":"rejected 列应为 0。write/bulk 和 search 线程池中 rejected 值不断增加，说明集群过载并返回 HTTP 429（es_rejected_execution_exception）。队列持续处于满载是拥塞的迹象。在客户端实施退避（backoff）、减小批量大小或增加容量。","severity":"danger"},{"id":"query-cache-hit","group":"性能","title":"查询缓存命中率","command":"GET _nodes/stats/indices/query_cache","expect":"对于重复的过滤查询，hit_count / (hit_count + miss_count) 比值应较高。命中率低且淘汰量高，说明缓存大小不足或查询变化太大而无法被缓存。使用稳定的过滤条件（filter context）可提高命中率。","severity":"safe"},{"id":"slowlog-config","group":"性能","title":"慢查询日志（slowlog）设置","command":"GET /_all/_settings?filter_path=**.search.slowlog,**.indexing.slowlog","expect":"应在重要索引上定义搜索和索引 slowlog 阈值（例如 index.search.slowlog.threshold.query.warn: 10s）。禁用 slowlog 后，诊断慢查询会更困难。启用阈值并监控日志，以识别开销大的查询（宽聚合、通配符、深度分页）。","severity":"safe"},{"id":"hot-threads","group":"性能","title":"热点线程（hot threads）","command":"GET _nodes/hot_threads","expect":"显示 CPU 消耗最高的线程。在 CPU 高/响应慢时用于判断哪个操作（重型聚合、merge、GC、脚本）是瓶颈。如果同一个开销大的查询/merge 反复出现，优化该查询或索引策略。","severity":"safe"},{"id":"fielddata-memory","group":"性能","title":"Fielddata 内存使用","command":"GET _cat/fielddata?v","expect":"Fielddata 内存使用应保持在较低水平。对 text 字段进行聚合/排序会将 fielddata 加载到内存，增加 circuit breaker（Data too large）错误和 OOM 的风险。对 fielddata 偏高的字段，改用 keyword 子字段（doc_values）。","severity":"caution"},{"id":"snapshot-status","group":"备份与恢复","title":"快照仓库与最新快照状态","command":"GET _snapshot/_all","expect":"应至少定义一个快照仓库。确认存储库存在后，检查最新快照的状态（结合下面的 verify 检查）。如果没有存储库，说明集群没有备份，在 RED 状态下数据可能永久丢失。配置 SLM（Snapshot Lifecycle Management）以定期自动执行快照。","severity":"danger"},{"id":"snapshot-latest-state","group":"备份与恢复","title":"最新快照的成功状态","command":"GET _snapshot/<repo>/_all?verbose=false","expect":"最新快照的 state 应为 'SUCCESS'。'PARTIAL' 表示部分分片未能备份，'FAILED' 表示备份失败；两者在恢复时都意味着数据丢失。最近一次快照的日期（end_time）不应太旧（按满足 RPO 目标的频率进行备份）。","severity":"danger"},{"id":"snapshot-repo-verify","group":"备份与恢复","title":"快照仓库验证","command":"POST _snapshot/<repo>/_verify","expect":"验证可确认所有节点都能访问存储库（共享文件系统、S3、HDFS 等），并在响应中列出能够访问的节点。如果返回错误，说明部分节点无法写入备份存储库；这种情况下快照将不完整/失败。修复存储库访问和凭据。","severity":"caution"},{"id":"security-enabled","group":"安全","title":"是否启用了安全功能（X-Pack security）","command":"GET _xpack/usage","expect":"security.enabled 和 security.available 应为 true。禁用安全功能时，集群无需认证即可访问；这样的集群暴露在互联网或不可信网络中是严重的漏洞（数据泄露/删除）。必须启用认证、基于角色的访问控制和 TLS。","severity":"danger"},{"id":"users-roles","group":"安全","title":"用户与角色","command":"GET _security/user","expect":"检查已定义的用户和角色分配。确保内置账户（elastic、kibana_system 等）受强密码保护。遵循最小权限原则，为每个应用定义具有受限角色的独立用户；避免不必要地广泛使用 superuser 角色。角色使用 GET _security/role 查看。","severity":"caution"},{"id":"tls-transport-http","group":"安全","title":"TLS / HTTPS 状态（transport 与 HTTP）","command":"GET _nodes/settings?filter_path=**.xpack.security.transport.ssl.enabled,**.xpack.security.http.ssl.enabled","expect":"xpack.security.transport.ssl.enabled 应为 true（节点间加密，必需）。理想情况下 http.ssl.enabled 也应为 true（客户端到集群的 HTTPS）。禁用 TLS 时，凭据和数据会以明文在网络上传输。还需检查证书过期时间和验证模式（verification_mode: certificate/full）。","severity":"danger"},{"id":"unassigned-shards","group":"复制","title":"未分配的分片及其原因","command":"GET _cat/shards?v&h=index,shard,prirep,state,unassigned.reason","expect":"所有分片的 state 都应为 'STARTED'。应调查处于 UNASSIGNED 状态的分片及其 unassigned.reason（NODE_LEFT、ALLOCATION_FAILED、DISK_THRESHOLD 等）。未分配的主分片意味着 RED 健康状态，未分配的副本意味着 YELLOW。使用 GET _cluster/allocation/explain 查找根本原因。","severity":"danger"},{"id":"recovery-active","group":"复制","title":"正在进行的分片恢复","command":"GET _cat/recovery?v&active_only=true","expect":"正常情况下不应有活动的恢复（列表为空）。添加/移除节点或故障转移后的临时恢复是正常的，但长时间卡住且没有进展（%progress 不增加）的恢复表明存在网络/磁盘瓶颈。如果再平衡（relocation）影响了负载，检查恢复限流设置（indices.recovery.max_bytes_per_sec）。","severity":"caution"},{"id":"replica-count","group":"复制","title":"每个索引的副本数","command":"GET _cat/indices?v&h=index,health,pri,rep,docs.count","expect":"生产索引的 rep（number_of_replicas）应至少为 1；在副本数为 0 的索引上，丢失一个节点就意味着数据永久丢失。但在多节点集群中，副本数不应超过（数据节点数 - 1）；否则多余的副本无法分配，健康状态将保持 YELLOW。调查 health 列不是 green 的索引。","severity":"danger"}]},"recipes/oracle.json":{"engineId":"oracle","engine":"Oracle","order":1,"recipes":[{"id":"redo-switch","task":"重做日志切换（切换当前日志文件）","category":"重做与归档","command":"ALTER SYSTEM SWITCH LOGFILE;","what":"关闭当前重做日志组并切换到下一组；在归档模式下会触发归档。","when":"用于手动生成归档、强制日志切换或进行测试。","caution":"频繁切换会降低性能；如果日志文件过小，请检查其大小设置。","tags":["redo","switch","logfile","archive","archive","alter system"]},{"id":"redo-add-group","task":"添加新的重做日志组","category":"重做与归档","command":"ALTER DATABASE ADD LOGFILE GROUP 4\n  ('/u01/oradata/ORCL/redo04a.log',\n   '/u02/oradata/ORCL/redo04b.log') SIZE 512M;","what":"以指定的大小和成员创建新的重做日志组（多个成员 = 镜像）。","when":"当重做吞吐量不足或需要增加组数时。","caution":"确保组号不与现有组冲突；用 V$LOG 检查。","tags":["redo","logfile","group","add","add","alter database"]},{"id":"redo-resize","task":"调整重做日志组大小（drop + add）","category":"重做与归档","command":"-- 1) The group to be resized must be INACTIVE; if not, switch:\nALTER SYSTEM SWITCH LOGFILE;\nALTER SYSTEM CHECKPOINT;\n-- 2) Drop the old group:\nALTER DATABASE DROP LOGFILE GROUP 1;\n-- 3) Add it again with the new size:\nALTER DATABASE ADD LOGFILE GROUP 1\n  ('/u01/oradata/ORCL/redo01a.log',\n   '/u02/oradata/ORCL/redo01b.log') SIZE 1G REUSE;","what":"重做日志无法直接调整大小；需删除该组后以新大小重新添加。","when":"当重做文件过小/过大时，用于调整检查点频率。","caution":"只能删除 INACTIVE 或 UNUSED 的组；不要删除 CURRENT/ACTIVE 组。任何时候都必须至少保留两个组。REUSE 会重用现有文件。","tags":["redo","resize","size","drop","add","logfile","reuse"]},{"id":"enable-archivelog","task":"将数据库置于 ARCHIVELOG 模式","category":"重做与归档","command":"SHUTDOWN IMMEDIATE;\nSTARTUP MOUNT;\nALTER DATABASE ARCHIVELOG;\nALTER DATABASE OPEN;\n-- Verify:\nARCHIVE LOG LIST;","what":"将数据库置于归档日志模式；写满的重做日志会被归档，从而可以进行热备份和时间点恢复。","when":"用于生产数据库的备份与恢复策略（使用 RMAN、Data Guard 之前必需）。","caution":"需要重启实例；请规划停机时间。确保归档目标（FRA 或 LOG_ARCHIVE_DEST）有足够空间；写满时数据库会停止。在 RAC 上：用 srvctl stop database -d DB 停止所有实例，以 STARTUP MOUNT 打开单个实例，切换到 ARCHIVELOG 后关闭，再用 srvctl start database -d DB 启动（11gR2+ 无需 cluster_database=false）。","tags":["archivelog","archive","mode","mount","recovery","recovery"]},{"id":"fra-full-fix","task":"修复已满的 FRA / 归档目标","category":"重做与归档","command":"-- Check usage:\nSELECT name, space_limit/1024/1024/1024 AS limit_gb,\n       space_used/1024/1024/1024  AS used_gb,\n       ROUND(space_used/space_limit*100,1) AS pct_used\nFROM   v$recovery_file_dest;\n\n-- Fix A) Increase the FRA size:\nALTER SYSTEM SET db_recovery_file_dest_size=100G SCOPE=BOTH;\n\n-- Fix B) Clean up already backed-up archives with RMAN:\n-- RMAN> DELETE ARCHIVELOG ALL BACKED UP 1 TIMES TO DISK COMPLETED BEFORE 'SYSDATE-3';\n-- If there is no backup (CAREFUL): RMAN> DELETE ARCHIVELOG ALL COMPLETED BEFORE 'SYSDATE-1';","what":"当快速恢复区或归档目标写满时，数据库会挂起（ORA-00257）。通过扩大空间或清理归档解决。","when":"出现 ORA-00257 归档错误、数据库停止接受新事务时。","caution":"删除尚未备份的归档会破坏可恢复性。先执行 RMAN 备份，再删除。不要在操作系统中用 rm 删除；那样需要 RMAN CROSSCHECK。FRA 已满时，将归档备份到 FRA 之外的目标（FORMAT '/backup/arc_%U'）。使用 Data Guard 时，不要删除尚未传送/应用到备库的归档；否则备库会出现 gap。使用 CONFIGURE ARCHIVELOG DELETION POLICY TO APPLIED ON ALL STANDBY。","tags":["fra","recovery area","ora-00257","archiver","archive","full","cleanup"]},{"id":"redo-status","task":"查看重做日志状态","category":"重做与归档","command":"SELECT l.group#, l.thread#, l.sequence#,\n       ROUND(l.bytes/1024/1024) AS mb,\n       l.members, l.archived, l.status,\n       lf.member AS file_path\nFROM   v$log l\n  JOIN v$logfile lf ON lf.group# = l.group#\nORDER BY l.group#, lf.member;","what":"显示重做日志组的状态（CURRENT/ACTIVE/INACTIVE）、大小、成员文件和归档状态。","when":"检查重做配置时，或在调整大小之前查看哪个组处于活动状态。","caution":"仅查询；无风险。","tags":["redo","status","status","v$log","v$logfile","logfile"]},{"id":"asm-add-disk","task":"向 ASM 磁盘组添加磁盘","category":"ASM","command":"-- Connect to the ASM instance (sqlplus / as sysasm)\nALTER DISKGROUP DATA ADD DISK\n  '/dev/oracleasm/disks/DATA05' NAME DATA_0005,\n  '/dev/oracleasm/disks/DATA06' NAME DATA_0006\n  REBALANCE POWER 4;","what":"向 DATA 磁盘组添加新磁盘并自动启动 rebalance；容量随之增加。","when":"当 ASM 磁盘组开始写满或需要扩展存储时。","caution":"需要 SYSASM 权限。磁盘大小应相近。rebalance 会产生 I/O 负载；繁忙时段使用较低的 POWER。","tags":["asm","diskgroup","add disk","add disk","rebalance","alter diskgroup"]},{"id":"asm-drop-disk","task":"从 ASM 磁盘组移除磁盘","category":"ASM","command":"ALTER DISKGROUP DATA DROP DISK DATA_0005 REBALANCE POWER 4;\n\n-- WAIT until the drop completes (the disk can be physically removed once V$ASM_OPERATION is empty):\nSELECT group_number, operation, state, power,\n       sofar, est_work, est_minutes\nFROM   v$asm_operation;","what":"从磁盘组中移除磁盘；其 extent 会被迁移到其余磁盘（rebalance）。完成后，该磁盘的 HDR_STATUS 变为 FORMER。","when":"更换故障磁盘、缩减容量或迁移存储时。","caution":"在 rebalance 完成之前切勿物理拔出磁盘，否则数据会丢失。确保其余磁盘有足够的可用空间。","tags":["asm","drop disk","remove disk","rebalance","v$asm_operation","alter diskgroup"]},{"id":"asm-rebalance-power","task":"设置 ASM rebalance POWER","category":"ASM","command":"-- Change the power of a running rebalance (1-1024, 0 stops it):\nALTER DISKGROUP DATA REBALANCE POWER 8;\n\n-- Stop the rebalance completely:\nALTER DISKGROUP DATA REBALANCE POWER 0;","what":"设置正在进行的 rebalance 的并行度/速度。POWER 越高越快，但 I/O 也越多。","when":"用于加快过慢的 rebalance，或减慢/停止正在影响生产的 rebalance。","caution":"POWER 0 会暂停 rebalance（磁盘删除将无法完成）。值过高会影响生产 I/O。","tags":["asm","rebalance","power","power","diskgroup","tuning"]},{"id":"asm-diskgroup-space","task":"查看 ASM 磁盘组可用空间","category":"ASM","command":"SELECT name,\n       state,\n       type,\n       total_mb/1024                 AS total_gb,\n       free_mb/1024                  AS free_gb,\n       ROUND((1-free_mb/total_mb)*100,1) AS pct_used,\n       usable_file_mb/1024           AS usable_gb\nFROM   v$asm_diskgroup\nORDER BY name;","what":"显示每个磁盘组的总空间、可用空间和（考虑冗余后的）可用文件空间。","when":"用于容量规划、空间不足时以及决定添加磁盘之前。","caution":"usable_file_mb 已考虑冗余；在 NORMAL/HIGH 冗余下，该列才是真正可用的空间。仅查询；无风险。","tags":["asm","diskgroup","space","space","free","v$asm_diskgroup","capacity"]},{"id":"asm-monitor-rebalance","task":"监控 ASM rebalance","category":"ASM","command":"SELECT group_number, operation, pass, state, power,\n       sofar, est_work,\n       ROUND(sofar/NULLIF(est_work,0)*100,1) AS pct_done,\n       est_minutes\nFROM   v$asm_operation\nORDER BY group_number;","what":"显示正在进行的 rebalance/resync 操作的进度、power 和预计剩余时间。","when":"添加/移除磁盘后，跟踪 rebalance 是否已完成时。","caution":"结果为空 = 没有活动操作（已完成）。仅查询；无风险。","tags":["asm","rebalance","monitor","monitor","v$asm_operation","progress"]},{"id":"add-tablespace","task":"添加新表空间","category":"存储","command":"-- File system:\nCREATE TABLESPACE customer_data\n  DATAFILE '/u01/oradata/ORCL/customer_data01.dbf'\n  SIZE 1G AUTOEXTEND ON NEXT 256M MAXSIZE 20G\n  EXTENT MANAGEMENT LOCAL\n  SEGMENT SPACE MANAGEMENT AUTO;\n\n-- If you use ASM:\nCREATE TABLESPACE customer_data\n  DATAFILE '+DATA' SIZE 1G AUTOEXTEND ON NEXT 256M MAXSIZE 20G;","what":"创建可放置表/索引段的新永久表空间。","when":"为新的应用/schema 分配独立存储时。","caution":"请指定 MAXSIZE，否则磁盘可能被写满。在 ASM 中文件名会自动生成。","tags":["tablespace","create","create","datafile","storage","storage","autoextend"]},{"id":"add-datafile","task":"向现有表空间添加数据文件","category":"存储","command":"ALTER TABLESPACE customer_data\n  ADD DATAFILE '/u01/oradata/ORCL/customer_data02.dbf'\n  SIZE 2G AUTOEXTEND ON NEXT 256M MAXSIZE 30G;\n\n-- ASM:\nALTER TABLESPACE customer_data\n  ADD DATAFILE '+DATA' SIZE 2G AUTOEXTEND ON NEXT 256M MAXSIZE 30G;","what":"通过添加新数据文件扩大现有表空间的容量。","when":"当表空间已满且单个文件接近 smallfile 上限（32G/64G）时。","caution":"smallfile 表空间的每个文件都有块数上限；必要时添加新文件。请指定 MAXSIZE。","tags":["datafile","add","add","tablespace","alter tablespace","storage"]},{"id":"resize-datafile","task":"调整数据文件大小 / 设置 autoextend","category":"存储","command":"-- Grow/shrink to a fixed size:\nALTER DATABASE DATAFILE '/u01/oradata/ORCL/customer_data01.dbf' RESIZE 5G;\n\n-- Enable autoextend:\nALTER DATABASE DATAFILE '/u01/oradata/ORCL/customer_data01.dbf'\n  AUTOEXTEND ON NEXT 256M MAXSIZE 30G;\n\n-- Disable autoextend:\nALTER DATABASE DATAFILE '/u01/oradata/ORCL/customer_data01.dbf' AUTOEXTEND OFF;","what":"将数据文件设为固定大小或配置其自动增长行为。","when":"用于回收空间（收缩）、预分配空间或阻止失控增长。","caution":"使用 RESIZE 收缩时，不能小于文件末尾已使用的块（ORA-03297）。请先迁移段。","tags":["datafile","resize","size","autoextend","maxsize","alter database"]},{"id":"move-datafile-online","task":"在线移动数据文件","category":"存储","command":"ALTER DATABASE MOVE DATAFILE\n  '/u01/oradata/ORCL/customer_data01.dbf'\n  TO '/u03/oradata/ORCL/customer_data01.dbf';\n\n-- Move into ASM:\nALTER DATABASE MOVE DATAFILE\n  '/u01/oradata/ORCL/customer_data01.dbf' TO '+DATA';\n\n-- Use KEEP to retain the old file instead of deleting it:\n-- ALTER DATABASE MOVE DATAFILE '...' TO '...' KEEP;","what":"在数据库打开的状态下将数据文件移动到新位置（12c+）；无需停机即可更换存储。","when":"更换磁盘/挂载点、迁移到 ASM 或迁移到高速存储时。","caution":"需要 12c 或更高版本以及 Enterprise Edition。移动会产生额外 I/O。如果不使用 KEEP，旧文件会被删除。","since":"Oracle 12c","note":"在线移动数据文件（ALTER DATABASE MOVE DATAFILE）随 Oracle 12c（12.1）引入；此前移动数据文件需将表空间/数据文件置为 OFFLINE，在操作系统层面复制，再用 ALTER DATABASE RENAME FILE 重新映射。","tags":["datafile","move","move","online","alter database move datafile","asm"]},{"id":"temp-tablespace","task":"添加临时表空间 / 更改默认值","category":"存储","command":"-- Create a new temp tablespace:\nCREATE TEMPORARY TABLESPACE temp2\n  TEMPFILE '/u01/oradata/ORCL/temp2_01.dbf'\n  SIZE 2G AUTOEXTEND ON NEXT 256M MAXSIZE 20G;\n\n-- Change the database default temp:\nALTER DATABASE DEFAULT TEMPORARY TABLESPACE temp2;\n\n-- Add a tempfile to the old temp:\nALTER TABLESPACE temp ADD TEMPFILE '/u01/oradata/ORCL/temp_02.dbf' SIZE 2G;","what":"为排序/哈希/全局临时表操作提供临时空间；可将默认临时表空间切换到新表空间。","when":"用于 ORA-01652（unable to extend temp segment）、临时空间瓶颈或调整临时空间大小。","caution":"更改默认值后，如果旧临时表空间仍在使用，不要立即 DROP；待会话释放后再删除。","tags":["temp","tablespace","tempfile","ora-01652","default temporary","storage"]},{"id":"tablespace-space","task":"表空间可用空间查询","category":"存储","command":"SELECT df.tablespace_name,\n       ROUND(df.total_mb/1024,2)                 AS total_gb,\n       ROUND(NVL(fs.free_mb,0)/1024,2)           AS free_gb,\n       ROUND((df.total_mb-NVL(fs.free_mb,0))/df.total_mb*100,1) AS pct_used\nFROM  (SELECT tablespace_name, SUM(bytes)/1024/1024 total_mb\n         FROM dba_data_files GROUP BY tablespace_name) df\n  LEFT JOIN\n       (SELECT tablespace_name, SUM(bytes)/1024/1024 free_mb\n          FROM dba_free_space GROUP BY tablespace_name) fs\n    ON df.tablespace_name = fs.tablespace_name\nORDER BY pct_used DESC;","what":"显示每个表空间的总空间、可用空间和使用百分比（基于当前已分配空间，不含 autoextend 潜力）。","when":"空间监控，找出需要扩展的表空间。","caution":"该查询基于当前数据文件大小；通过 autoextend 还可以继续增长。临时表空间使用 DBA_TEMP_FREE_SPACE。仅查询；无风险。","tags":["tablespace","space","space","free","dba_free_space","usage","storage"]},{"id":"alter-parameter","task":"修改参数（带 SCOPE）","category":"内存与参数","command":"-- Both the running instance and the spfile (default, dynamic parameters):\nALTER SYSTEM SET open_cursors=500 SCOPE=BOTH;\n\n-- Only for the next restart (static parameters):\nALTER SYSTEM SET processes=1000 SCOPE=SPFILE;\n\n-- Immediate only, do not write to the spfile:\nALTER SYSTEM SET optimizer_mode='ALL_ROWS' SCOPE=MEMORY;\n\n-- View the current value:\nSHOW PARAMETER open_cursors;","what":"在运行中的实例和/或 spfile 中修改初始化参数。SCOPE 决定其行为。","when":"用于需要修改参数的调优、容量调整和错误修复。","caution":"静态参数（processes、sga_max_size...）只接受 SCOPE=SPFILE，并需要重启。错误的值可能导致实例无法启动；修改关键参数前先备份 pfile。","tags":["parameter","parameter","alter system","scope","spfile","tuning"]},{"id":"flush-pool","task":"刷新 shared pool / buffer cache","category":"内存与参数","command":"-- Clear the SQL plan/cursor cache:\nALTER SYSTEM FLUSH SHARED_POOL;\n\n-- Clear the buffer cache (usually only for test/benchmark):\nALTER SYSTEM FLUSH BUFFER_CACHE;","what":"刷新 shared pool 会将所有缓存的 SQL/PL-SQL 和数据字典信息逐出内存；刷新 buffer cache 会逐出所有数据块。","when":"用于测试执行计划问题、一次性大型操作之后，或为基准测试提供一致的起点。","caution":"在生产环境中务必小心！刷新后所有 SQL 都会重新解析（硬解析风暴），块需从磁盘读取；随后会出现严重的暂时性性能下降。","tags":["flush","shared pool","buffer cache","memory","alter system","cache"]},{"id":"spfile-pfile","task":"从 spfile 创建 pfile 及反向操作","category":"内存与参数","command":"-- spfile -> pfile (for backup/editing):\nCREATE PFILE='/tmp/init_orcl_backup.ora' FROM SPFILE;\n\n-- pfile -> spfile (make the edited pfile permanent; effective after restart):\nCREATE SPFILE FROM PFILE='/tmp/init_orcl_backup.ora';\n\n-- Create a pfile from the running instance values (11g+):\nCREATE PFILE='/tmp/init_current.ora' FROM MEMORY;","what":"在二进制 spfile 与文本 pfile 之间转换；pfile 可手工编辑。","when":"用于参数修改前的备份、修复损坏的 spfile、手工修正静态参数。","caution":"执行 CREATE SPFILE FROM PFILE 后，变更需重启后才会生效。覆盖现有 spfile 之前请确认路径。","tags":["spfile","pfile","create pfile","create spfile","parameter","backup"]},{"id":"sga-pga-target","task":"修改 SGA / PGA 目标","category":"内存与参数","command":"-- Automatic memory management (ASMM) targets:\nALTER SYSTEM SET sga_target=8G   SCOPE=BOTH;\nALTER SYSTEM SET pga_aggregate_target=4G SCOPE=BOTH;\n\n-- Upper limit (static, requires restart):\nALTER SYSTEM SET sga_max_size=12G SCOPE=SPFILE;\n\n-- View the current memory distribution:\nSELECT component, current_size/1024/1024 AS mb\nFROM   v$sga_dynamic_components ORDER BY component;","what":"设置 SGA 和 PGA 内存目标；Oracle 自动管理各组件（ASMM/AMM）。","when":"服务器增加 RAM 后、出现内存瓶颈或内存压力时。","caution":"sga_target 不能超过 sga_max_size。sga_max_size 是静态参数，需要重启。为操作系统和其他实例预留 RAM；过度分配会导致 swap/OOM。","tags":["sga","pga","memory","memory","target","sga_target","pga_aggregate_target"]},{"id":"kill-session","task":"终止会话（kill session）","category":"会话与进程","command":"-- First find the session to kill:\nSELECT sid, serial#, username, status, machine, program\nFROM   v$session\nWHERE  username = 'CUSTOMER_APP' AND status = 'ACTIVE';\n\n-- Kill it (starts rollback immediately):\nALTER SYSTEM KILL SESSION '145,12345' IMMEDIATE;\n\n-- If it hangs, the OS process can be terminated:\n-- ALTER SYSTEM DISCONNECT SESSION '145,12345' IMMEDIATE;","what":"终止指定 SID,SERIAL# 的会话；其未提交的事务会被回滚。","when":"用于停止持有锁、运行失控查询或泄漏连接的会话。","caution":"如果存在未提交的事务，回滚可能耗时很长（如果做的工作不多则很快完成）。先用查询确认，以免终止错误的 SID。大型 DML 的回滚会产生 I/O 负载。","tags":["kill session","kill session","v$session","alter system","sid","serial"]},{"id":"long-running-queries","task":"查看长时间运行的查询 / 会话","category":"会话与进程","command":"-- Active, long-running SQL:\nSELECT s.sid, s.serial#, s.username, s.status,\n       s.last_call_et AS seconds_active,\n       s.sql_id, q.sql_text,\n       s.event, s.machine, s.program\nFROM   v$session s\n  LEFT JOIN v$sql q ON q.sql_id = s.sql_id\n                   AND q.child_number = s.sql_child_number\nWHERE  s.type = 'USER'\n  AND  s.status = 'ACTIVE'\n  AND  s.last_call_et > 60\nORDER BY s.last_call_et DESC;\n\n-- Long operations with percentage progress:\nSELECT sid, serial#, opname, sofar, totalwork,\n       ROUND(sofar/NULLIF(totalwork,0)*100,1) AS pct, time_remaining\nFROM   v$session_longops\nWHERE  sofar < totalwork ORDER BY start_time DESC;","what":"列出运行超过 60 秒的活动会话以及长时间运行的批处理操作的进度。","when":"在收到响应慢的投诉时，找出是哪个查询让系统保持繁忙。","caution":"last_call_et 是自上次调用以来的时间；空闲会话的该值也可能很大，因此 status='ACTIVE' 过滤条件很重要。仅查询；无风险。","tags":["long running","long query","v$session","v$sql","v$session_longops","active"]},{"id":"find-blocking","task":"查找阻塞会话","category":"会话与进程","command":"SELECT bl.sid       AS blocker_sid,\n       bl.serial#   AS blocker_serial,\n       bl.username  AS blocker_user,\n       w.sid        AS waiter_sid,\n       w.serial#    AS waiter_serial,\n       w.username   AS waiter_user,\n       w.seconds_in_wait AS wait_sec,\n       w.event\nFROM   v$session w\n  JOIN v$session bl ON bl.sid = w.blocking_session\nWHERE  w.blocking_session IS NOT NULL\nORDER BY w.seconds_in_wait DESC;","what":"列出阻塞者与等待者会话对，以及等待时间和事件。","when":"用于在“应用挂起 / enq: TX 等待”之类的锁问题中找出元凶。","caution":"终止阻塞者之前先与业务负责人确认；它可能是关键操作。仅查询；无风险。","tags":["blocking","lock","lock","blocker","waiter","v$session","blocking_session"]},{"id":"gather-stats","task":"收集统计信息（DBMS_STATS）","category":"维护","command":"-- Single table (including cascaded indexes):\nEXEC DBMS_STATS.GATHER_TABLE_STATS(ownname => 'CUSTOMER_APP', tabname => 'CUSTOMER', cascade => TRUE, estimate_percent => DBMS_STATS.AUTO_SAMPLE_SIZE, degree => 4);\n\n-- Whole schema:\nEXEC DBMS_STATS.GATHER_SCHEMA_STATS(ownname => 'CUSTOMER_APP', estimate_percent => DBMS_STATS.AUTO_SAMPLE_SIZE, cascade => TRUE, degree => 4);\n\n-- Whole database (run only in a maintenance window / off-peak hours):\nEXEC DBMS_STATS.GATHER_DATABASE_STATS(estimate_percent => DBMS_STATS.AUTO_SAMPLE_SIZE, degree => 4);","what":"为优化器收集表/索引统计信息；确保生成正确的执行计划。","when":"大量 DML/加载之后、执行计划回归时、新建索引/表之后。","caution":"统计信息变化可能改变执行计划；在关键系统上先用 AWR 测试/对比。DEGREE 并行度会消耗 CPU。GATHER_DATABASE_STATS 在业务高峰期开销很大。","tags":["dbms_stats","statistics","statistics","gather","optimizer","maintenance"]},{"id":"rebuild-index-online","task":"在线重建索引","category":"维护","command":"-- Regular index:\nALTER INDEX customer_app.idx_customer_email REBUILD ONLINE PARALLEL 4;\n\n-- Moving it to a different tablespace:\nALTER INDEX customer_app.idx_customer_email\n  REBUILD ONLINE TABLESPACE customer_idx PARALLEL 4;\n\n-- Reset parallelism after the rebuild (so plans are not affected):\nALTER INDEX customer_app.idx_customer_email NOPARALLEL;","what":"在不阻塞 DML 的情况下重建索引（ONLINE）；消除碎片，并可更换表空间。","when":"索引膨胀/碎片、将索引迁移到其他表空间、修复 UNUSABLE 索引。","caution":"ONLINE 会使用额外的临时空间和 I/O。PARALLEL 设置会保留；重建后用 NOPARALLEL 重置，否则查询会意外并行执行。并非所有版本的位图索引都支持 ONLINE。ONLINE 重建仅在 Enterprise Edition 中可用。","tags":["index","rebuild","online","rebuild","alter index","fragmentation","maintenance"]},{"id":"rman-full-backup","task":"使用 RMAN 快速全量备份","category":"维护","command":"# From the shell: rman target /\nRUN {\n  ALLOCATE CHANNEL c1 DEVICE TYPE DISK;\n  ALLOCATE CHANNEL c2 DEVICE TYPE DISK;\n  BACKUP AS COMPRESSED BACKUPSET\n    DATABASE\n    PLUS ARCHIVELOG\n    TAG 'FULL_DAILY';\n  BACKUP CURRENT CONTROLFILE;\n  DELETE NOPROMPT OBSOLETE;\n  RELEASE CHANNEL c1;\n  RELEASE CHANNEL c2;\n}","what":"通过两个通道并行对整个数据库和归档日志进行压缩全量备份，备份控制文件并清除过期备份。","when":"每日/每周全量备份，用于创建恢复点。","caution":"COMPRESSED 会消耗 CPU。DELETE OBSOLETE 按保留策略删除旧备份；请正确设置策略（CONFIGURE RETENTION POLICY）。ARCHIVELOG 要求数据库处于 ARCHIVELOG 模式。确保备份目标有足够空间。","tags":["rman","backup","backup","full","compressed","archivelog","maintenance"]},{"id":"controlfile-trace","task":"生成控制文件 trace 备份","category":"维护","command":"-- Generate a trace (CREATE CONTROLFILE script):\nALTER DATABASE BACKUP CONTROLFILE TO TRACE\n  AS '/tmp/create_controlfile.sql' REUSE;\n\n-- Binary controlfile copy:\nALTER DATABASE BACKUP CONTROLFILE TO '/backup/controlfile_orcl.ctl' REUSE;","what":"生成用于重建控制文件的 SQL 脚本（trace）和/或其二进制副本。","when":"结构变更后（添加数据文件/redo）、准备灾难恢复时，或在所有副本丢失时重建控制文件。","caution":"每次结构变化后都要刷新 trace 文件；旧脚本会创建错误的结构。REUSE 会覆盖已有文件。对系统无风险，但请安全保存生成的文件。","tags":["controlfile","trace","backup","backup","alter database","recovery","maintenance"]}]},"recipes/oracle-ha.json":{"engineId":"oracle","engine":"Oracle","order":1,"recipes":[{"id":"rac-srvctl-db-status","task":"使用 srvctl 检查 RAC 数据库、实例和服务状态","category":"RAC 与 Grid","command":"# Which node each instance runs on, and in what state:\nsrvctl status database -db <db_unique_name> -verbose\n# Configuration registered in Clusterware (Oracle home, spfile, disk group, policy, role):\nsrvctl config database -db <db_unique_name>\n# A single instance:\nsrvctl status instance -db <db_unique_name> -instance <instance>\n# Which instances the services are running on:\nsrvctl status service -db <db_unique_name>","what":"显示 Clusterware（CRS）所见的数据库、各实例及其服务的运行状态，以及已注册的配置；-verbose 会附加实例的打开模式。","when":"作为任何 RAC 操作前后的首项检查；故障期间用于查看受影响的节点/实例。","caution":"只读。请从数据库 Oracle home（oracle 用户）运行；使用不同版本 Grid home 中的 srvctl 可能返回误导性结果或报错。","since":"12c","note":"长选项 -db/-instance/-service 为 12c+；11g 的短选项（-d、-i、-s）在 19c 中仍可用，但已弃用。","tags":["rac","srvctl","status","instance","service","cluster","config"],"untested":true},{"id":"rac-srvctl-start-stop","task":"使用 srvctl 停止/启动 RAC 数据库或单个实例（滚动）","category":"RAC 与 Grid","command":"# Whole database (all instances):\nsrvctl stop database -db <db_unique_name> -stopoption immediate\nsrvctl start database -db <db_unique_name>\n# Rolling maintenance: stop one instance, moving its services to the other instances\nsrvctl stop instance -db <db_unique_name> -instance <instance> \\\n  -stopoption immediate -drain_timeout 300 -failover\nsrvctl start instance -db <db_unique_name> -instance <instance>\n# By node:\nsrvctl stop instance -db <db_unique_name> -node <node> -stopoption immediate\n# Start in a specific open mode (e.g. a physical standby in mount):\nsrvctl start database -db <db_unique_name> -startoption mount","what":"通过 Clusterware 有控制地停止/启动数据库或所选实例；使用 -failover 时，被停止实例上的服务会迁移到 available 实例，-drain_timeout 会等待会话排空。","when":"用于打补丁、操作系统维护或参数变更期间的滚动（逐节点）重启；用于计划内的整体停机。","caution":"在 RAC 中请使用 srvctl 而不是 SQL*Plus SHUTDOWN；否则 Clusterware 不知道资源的 TARGET，可能会重新启动实例。stop database 会关闭所有实例 = 完全停机。如果服务未定义 available 实例，-failover 无法迁移连接。","since":"12c","note":"-drain_timeout 和 -failover 为 12.2+。-stopoption abort 仅在紧急情况下使用；下次启动需要实例恢复。","tags":["rac","srvctl","stop","start","rolling","drain","shutdown"],"untested":true},{"id":"rac-service-add","task":"添加带 preferred/available 实例的 RAC 服务（含 Application Continuity）","category":"RAC 与 Grid","command":"srvctl add service -db <db_unique_name> -service <service> \\\n  -preferred <inst1>,<inst2> -available <inst3> \\\n  -pdb <pdb> -role PRIMARY -policy AUTOMATIC \\\n  -failovertype TRANSACTION -commit_outcome TRUE \\\n  -failoverretry 30 -failoverdelay 10 \\\n  -clbgoal SHORT -rlbgoal SERVICE_TIME -notification TRUE \\\n  -drain_timeout 300 -stopoption IMMEDIATE\nsrvctl start service -db <db_unique_name> -service <service>\n# Verify:\nsrvctl config service -db <db_unique_name> -service <service>\nsrvctl status service -db <db_unique_name> -service <service>","what":"为应用定义一个服务：正常情况下运行在 preferred 实例上，当这些实例故障时迁移到 available 实例；使用 -role PRIMARY 时，在 Data Guard 下仅在主库角色中启动。","when":"让应用连接服务而不是实例，在节点间划分负载，并在计划内维护期间实现无缝故障转移（drain/failover）。","caution":"应用必须使用此服务名（通过 SCAN）连接，而不是 SID 或实例名。-failovertype TRANSACTION（Application Continuity）需要应用/驱动支持；不确定时先用 -failovertype SELECT（TAF）或 NONE。不要让应用使用 SYS$ 服务或以 db_unique_name 命名的默认服务。","since":"12c","note":"-pdb 为 12c+，-drain_timeout 和 -stopoption 为 12.2+。使用 srvctl modify service 修改；永久更改 preferred 需要 -modifyconfig。","tags":["rac","service","srvctl","preferred","available","taf","application continuity"],"untested":true},{"id":"rac-service-relocate","task":"将 RAC 服务迁移到其他实例","category":"RAC 与 Grid","command":"# First check the current placement:\nsrvctl status service -db <db_unique_name> -service <service>\n# Relocate by instance (sessions are drained):\nsrvctl relocate service -db <db_unique_name> -service <service> \\\n  -oldinst <inst1> -newinst <inst3> -drain_timeout 120 -wait YES\n# By node (policy-managed / singleton service):\nsrvctl relocate service -db <db_unique_name> -service <service> \\\n  -currentnode <node1> -targetnode <node2>\n# Session distribution per instance (SQL):\nSELECT inst_id, service_name, COUNT(*) AS sessions\nFROM   gv$session\nWHERE  type = 'USER'\nGROUP  BY inst_id, service_name\nORDER  BY service_name, inst_id;","what":"将正在运行的服务从一个实例/节点迁移到另一个；新连接进入目标实例，现有会话在 drain_timeout 期间自行关闭。","when":"维护前清空节点、故障转移后将服务迁回 preferred 实例，或重新平衡负载。","caution":"迁移是临时的：不会改变服务配置（preferred/available）；实例重启后服务会回到 preferred 实例。-force 会终止现有会话；如果应用无法重连，用户会遇到中断。","since":"12c","note":"-drain_timeout 和 -wait 为 12.2+。SQL 查询在单实例上也可运行；srvctl 部分需要 RAC。","tags":["rac","service","relocate","move","srvctl","drain","gv$session"],"untested":true},{"id":"rac-crsctl-stat-res","task":"检查集群资源状态（crsctl stat res -t）","category":"RAC 与 Grid","command":"# From the Grid home, as the grid user:\ncrsctl stat res -t\n# OHASD (init) layer: ora.cssd, ora.crsd, ora.asm, ora.ctssd...\ncrsctl stat res -t -init\n# Only problem resources (TARGET is ONLINE but STATE is not):\ncrsctl stat res -t -w \"((TARGET = ONLINE) and (STATE != ONLINE))\"\n# Profile and runtime details of a single resource:\ncrsctl stat res ora.<db_unique_name>.db -p\ncrsctl stat res ora.<db_unique_name>.db -v","what":"以表格形式按节点列出 Clusterware 管理的所有资源（数据库、实例、监听、SCAN、VIP、ASM、磁盘组、网络）及其 TARGET/STATE。","when":"节点启动后、打补丁后以及每次故障排查时；用于找出 OFFLINE、INTERMEDIATE 或 UNKNOWN 资源。","caution":"只读。不要直接用 crsctl start/stop/modify 管理 ora.* 资源；数据库资源请使用 srvctl（受支持的方法）。","note":"资源名包含小写的 db_unique_name（ora.orcl.db）。-init 的输出只有以 root/grid 身份才能完整看到。","tags":["rac","grid","crsctl","resource","resource","clusterware","status"],"untested":true},{"id":"rac-crsctl-check-cluster","task":"检查集群栈健康状况、节点列表和 Clusterware 版本","category":"RAC 与 Grid","command":"# CRS, CSS, EVM health check on all nodes:\ncrsctl check cluster -all\n# Full stack on this node, including OHASD:\ncrsctl check crs\n# Node list: number, status (Active/Inactive), pinned:\nolsnodes -n -s -t\n# Active version, patch level and upgrade state (after a rolling patch):\ncrsctl query crs activeversion -f\ncrsctl query crs softwareversion -all","what":"显示 Cluster Ready Services、Cluster Synchronization Services 和 Event Manager 守护进程在每个节点上都在运行，以及集群成员和 Clusterware 的 active/software 版本。","when":"早间健康检查时、节点驱逐或重启后，以及滚动补丁/升级后确认所有节点处于同一级别。","caution":"只读。如果 activeversion -f 显示 \"ROLLING PATCH\" 或不同的补丁级别，说明滚动操作未完成；在进行新变更前先完成它（crsctl stop rollingpatch / rootcrs -postpatch）。","since":"12c","note":"activeversion -f 和 softwareversion -all 为 12c+。","tags":["rac","grid","crsctl","olsnodes","health","cluster","version"],"untested":true},{"id":"rac-ocr-backup","task":"检查 OCR 完整性并备份 OCR（ocrcheck / ocrconfig）","category":"RAC 与 Grid","command":"# OCR integrity and capacity check (run as root for the logical check):\nocrcheck\nocrcheck -config\n# Automatic backups (every 4 hours, daily, weekly):\nocrconfig -showbackup\n# Manual backup before a risky change (root):\nocrconfig -manualbackup\nocrconfig -showbackup manual\n# Move the backup location to a disk group (root):\nocrconfig -backuploc +<diskgroup>","what":"显示 Oracle Cluster Registry（集群配置库）的完整性、位置和备份；-manualbackup 会立即进行一次 OCR 物理备份。","when":"在集群配置变更之前执行，例如添加/删除节点、Grid 补丁/升级、表决磁盘或网络（oifcfg）变更。","caution":"ocrconfig -restore 必须在所有节点 CRS 停止且处于独占模式下运行；恢复错误的备份可能导致集群无法启动，因此请在 Oracle Support 协助下进行。自动备份默认可能只保存在单个节点的本地磁盘上；请将位置改到共享磁盘组。","note":"以 grid 用户运行时，ocrcheck 会跳过逻辑损坏检查（\"Logical corruption check bypassed\"）。备份到磁盘组为 12c+。","tags":["rac","ocr","ocrcheck","ocrconfig","backup","backup","grid"],"untested":true},{"id":"rac-votedisk-query","task":"检查表决磁盘和 CSS 超时","category":"RAC 与 Grid","command":"# Voting disks, their state and disk group:\ncrsctl query css votedisk\n# Node eviction thresholds (seconds):\ncrsctl get css misscount\ncrsctl get css disktimeout\ncrsctl get css reboottime\n# Move the voting disks to another ASM disk group (root/grid):\ncrsctl replace votedisk +<diskgroup>","what":"显示用于集群成员关系的表决磁盘（voting files）状态以及网络/磁盘心跳超时；replace votedisk 会将所有表决文件迁移到目标磁盘组。","when":"存储变更、磁盘组重新设计或节点驱逐排查期间；确认多数（quorum）表决磁盘可访问。","caution":"无法访问超过半数表决磁盘的节点会自行重启。目标磁盘组需要足够的故障组（normal 冗余：3，high：5；必要时使用 quorum failgroup）。未经 Oracle Support 批准不要修改 misscount/disktimeout。","note":"默认值：misscount 30 s，disktimeout 200 s（Linux）。","tags":["rac","voting disk","votedisk","css","misscount","quorum","eviction"],"untested":true},{"id":"rac-cluvfy-checks","task":"使用 Cluster Verification Utility（cluvfy）执行前置/后置检查","category":"RAC 与 Grid","command":"# Full check after a Grid installation/change:\ncluvfy stage -post crsinst -n all -verbose\n# Before adding a node:\ncluvfy stage -pre nodeadd -n <new_node> -verbose\n# Component checks:\ncluvfy comp nodecon -n all -verbose\ncluvfy comp clocksync -n all -verbose\ncluvfy comp ocr -n all -verbose\ncluvfy comp asm -n all -verbose\n# Report only failed checks:\ncluvfy stage -post crsinst -n all -verbose | grep -iE \"failed|warning\"","what":"在所有节点上验证网络连通性、时钟同步、OCR、ASM、用户等效性和操作系统先决条件；报告问题并给出修复建议。","when":"Grid 安装、添加节点和升级前后；作为排查原因不明的集群问题的基线验证。","caution":"只读，但会通过 ssh 连接所有节点；grid 用户需要 ssh 等效性。未经审查不要运行 -fixup 脚本（root）；它们会修改操作系统参数。","note":"如需更全面的最佳实践扫描，请使用 AHF 中的 orachk/exachk。从 My Oracle Support 下载最新的 cluvfy 可获得更多检查项。","tags":["rac","cluvfy","check","verification","precheck","grid","orachk"],"untested":true},{"id":"rac-node-eviction-diag","task":"诊断节点驱逐/节点意外重启的原因","category":"RAC 与 Grid","command":"# 1) When, which node, evicted by whom: CRS alert log (on every node)\ngrep -E \"CRS-1601|CRS-1607|CRS-1608|CRS-1609|CRS-1610|CRS-1611|CRS-1612|CRS-8011\" \\\n  $ORACLE_BASE/diag/crs/$(hostname -s)/crs/trace/alert.log\n# 2) CSS details: was the network heartbeat or the disk heartbeat lost\ngrep -iE \"evict|misscount|heartbeat|disk HB|network HB\" \\\n  $ORACLE_BASE/diag/crs/$(hostname -s)/crs/trace/ocssd.trc | tail -100\n# 3) Who triggered the reboot (cssdagent/cssdmonitor):\nls -lt $ORACLE_BASE/diag/crs/$(hostname -s)/crs/trace/ohasd_cssd*.trc\n# 4) OS resources at the time of the event (Cluster Health Monitor):\noclumon dumpnodeview -allnodes -s \"2026-01-15 03:10:00\" -e \"2026-01-15 03:20:00\"\n# 5) Diagnostic collection from all nodes (AHF/TFA):\ntfactl diagcollect -crs -os -last 4h\n# 6) OS side: /var/log/messages, journalctl -k (OOM, NIC, multipath, I/O errors)","what":"按顺序追踪节点驱逐链：CRS alert log 中的 CRS-16xx 消息、ocssd.trc 中的心跳丢失、CHM 的操作系统指标，以及用 TFA 收集的所有节点日志。","when":"节点意外重启或被驱逐出集群时，用于根因分析（RCA）和提交 Oracle Support SR。","caution":"从每个节点收集覆盖事件时间的日志；trace 文件会轮转，证据可能丢失。确认各节点时区一致（CTSS/NTP）。在收集证据之前，不要应用诸如调大 misscount 之类的\"修复\"。","note":"典型原因：私有互联中断/丢包、表决磁盘 I/O 延迟、CPU/内存耗尽（OOM、swap）、时钟跳变。该路径为 12c+ 的 ADR 布局；在 11g 中位于 $GRID_HOME/log/<host>/ 下。","tags":["rac","eviction","reboot","ocssd","tfactl","oclumon","rca"],"untested":true},{"id":"rac-gv-cluster-waits","task":"使用 GV$ 按实例分析集群范围的等待事件（gc 等待）","category":"RAC 与 Grid","command":"-- Cluster-class waits per instance (since startup):\nSELECT inst_id, event, total_waits,\n       ROUND(time_waited_micro/1e6) AS time_waited_s,\n       ROUND(time_waited_micro/NULLIF(total_waits,0)/1000, 2) AS avg_ms\nFROM   gv$system_event\nWHERE  wait_class = 'Cluster'\nORDER  BY time_waited_micro DESC\nFETCH FIRST 15 ROWS ONLY;\n\n-- Sessions waiting right now, by instance and event:\nSELECT inst_id, event, COUNT(*) AS sessions\nFROM   gv$session\nWHERE  wait_class <> 'Idle' AND state = 'WAITING'\nGROUP  BY inst_id, event\nORDER  BY sessions DESC;\n\n-- ASH for the last 15 minutes: which instance is waiting on gc\nSELECT inst_id, event, COUNT(*) AS samples\nFROM   gv$active_session_history\nWHERE  sample_time > SYSTIMESTAMP - INTERVAL '15' MINUTE\n  AND  wait_class = 'Cluster'\nGROUP  BY inst_id, event\nORDER  BY samples DESC\nFETCH FIRST 10 ROWS ONLY;","what":"使用 GV$ 视图在一个查询中显示所有实例的 Cluster 类等待（gc cr/current block 2-way/3-way、gc buffer busy、gc cr request）及其平均延迟。","when":"用户反映 RAC 变慢时，用于判断问题是否来自私有互联/全局缓存，以及集中在哪个实例上。","caution":"gv$active_session_history 需要 Diagnostics Pack 许可。如果 avg_ms 超过几毫秒，先检查私有互联（MTU、丢包、netstat -s）和 LMS CPU；gc buffer busy 通常指向应用热块（sequence、右侧增长索引）。","note":"单实例上也可运行（返回 inst_id = 1）。","tags":["rac","gv$","gc","wait","wait event","interconnect","global cache"]},{"id":"asm-asmcmd-lsdg-du","task":"使用 asmcmd 显示磁盘组、磁盘和目录使用情况（lsdg / lsdsk / du）","category":"ASM","command":"# As the grid user, with ORACLE_SID=+ASM1 (or +ASM):\nasmcmd lsdg\n# Disks in the disk group: -k sizes, -p state (header/mount/mode):\nasmcmd lsdsk -k -G <diskgroup>\nasmcmd lsdsk -p -G <diskgroup>\n# I/O error and timing statistics:\nasmcmd lsdsk --statistics -G <diskgroup>\n# Disks that can be added (CANDIDATE/PROVISIONED/FORMER):\nasmcmd lsdsk --candidate -p\n# Space used by a database directory (MB):\nasmcmd du +<diskgroup>/<db_unique_name>\nasmcmd ls -l -s +<diskgroup>/<db_unique_name>/DATAFILE","what":"从命令行显示磁盘组的冗余、总空间/可用空间和 Usable_file_MB，磁盘状态以及某个数据库在 ASM 中占用的空间。","when":"用于容量检查、添加磁盘前识别候选磁盘，以及在触发 \"disk group full\" 告警时找出哪个数据库在消耗空间。","caution":"只读。如果 lsdg 中的 Usable_file_MB 为负，磁盘组在丢失一块磁盘/一个故障组后无法恢复冗余；请立即释放空间或添加磁盘。du 返回两个值：Used_MB 和 Mirror_used_MB（含镜像的使用量）。","note":"SQL 等价方法见 asm-diskgroup-space 和 asm-disk-health 操作指南。","tags":["asm","asmcmd","lsdg","lsdsk","du","space","disk group"],"untested":true},{"id":"asm-disk-health","task":"使用 SQL 检查 ASM 磁盘状态、错误计数和候选磁盘","category":"ASM","command":"-- On the ASM instance (or from a DB instance; there only the disk groups in use are visible):\nSELECT dg.name AS diskgroup, d.disk_number, d.name AS disk_name, d.path,\n       d.header_status, d.mount_status, d.mode_status, d.state,\n       d.failgroup, d.total_mb, d.free_mb, d.read_errs, d.write_errs\nFROM   v$asm_disk d\nLEFT   JOIN v$asm_diskgroup dg ON dg.group_number = d.group_number\nORDER  BY dg.name, d.disk_number;\n\n-- Disk group summary: AU size, offline disks, voting files:\nSELECT name, type, allocation_unit_size/1048576 AS au_mb,\n       compatibility, database_compatibility, offline_disks, voting_files\nFROM   v$asm_diskgroup;\n\n-- Databases using this ASM:\nSELECT instance_name, db_name, status, software_version, compatible_version\nFROM   v$asm_client;","what":"列出每块磁盘的 header_status（MEMBER、CANDIDATE、PROVISIONED、FORMER）、mount/mode 状态、故障组和读/写错误计数；以及每个磁盘组的离线磁盘数和 ASM 客户端。","when":"添加磁盘前，检查新 LUN 是否显示为 CANDIDATE/PROVISIONED；磁盘故障时，查看哪块磁盘 OFFLINE 以及是否有 I/O 错误。","caution":"只读。v$asm_disk 每次调用都会执行磁盘发现，在磁盘较多的系统上很慢；频繁监控请使用 v$asm_disk_stat（不执行发现，不显示新磁盘）。切勿用 FORCE 将 header_status 为 MEMBER 的磁盘添加到另一个磁盘组；这会破坏现有数据。","note":"没有 ASM 时不返回任何行；需要 ASM 实例才有有意义的输出。","tags":["asm","v$asm_disk","disk","header_status","error","candidate","failgroup"]},{"id":"asm-diskgroup-attributes","task":"查看和修改 ASM 磁盘组属性（compatible、disk_repair_time）","category":"ASM","command":"-- Current attributes:\nSELECT dg.name AS diskgroup, a.name, a.value\nFROM   v$asm_attribute a\nJOIN   v$asm_diskgroup dg ON dg.group_number = a.group_number\nWHERE  a.name IN ('compatible.asm','compatible.rdbms','au_size','disk_repair_time',\n                  'failgroup_repair_time','content.type','sector_size')\nORDER  BY dg.name, a.name;\n\n-- Change (ASM instance, as SYSASM):\nALTER DISKGROUP <diskgroup> SET ATTRIBUTE 'disk_repair_time' = '8h';\nALTER DISKGROUP <diskgroup> SET ATTRIBUTE 'compatible.asm'   = '19.0.0.0.0';\nALTER DISKGROUP <diskgroup> SET ATTRIBUTE 'compatible.rdbms' = '19.0.0.0.0';\n\n-- asmcmd equivalent:\n-- asmcmd lsattr -l -G <diskgroup>\n-- asmcmd setattr -G <diskgroup> disk_repair_time '8h'","what":"显示并修改磁盘组兼容级别（compatible.asm/rdbms）、AU 大小，以及临时故障磁盘在被删除前保留的时长（disk_repair_time）。","when":"Grid 升级后启用新的 ASM 功能（flex 磁盘组、更大的文件限制），或在计划内存储维护期间延长 disk_repair_time。","caution":"compatible.asm 和 compatible.rdbms 一旦调高便无法降低。如果 compatible.rdbms 高于使用该磁盘组的最旧数据库的 COMPATIBLE 设置，该数据库将无法挂载它。先调高 compatible.asm，再调高 compatible.rdbms。au_size 只能在创建磁盘组时设置。","note":"没有 ASM 时查询不返回任何行。disk_repair_time 默认值为 12h。","tags":["asm","attribute","compatible","disk_repair_time","disk group","au_size","v$asm_attribute"],"untested":true},{"id":"dg-show-configuration","task":"显示 Data Guard broker 配置和数据库状态（DGMGRL）","category":"Data Guard","command":"# From the shell (connect with a password; also required for switchover/failover):\ndgmgrl sys@<primary_db_unique_name>\nSHOW CONFIGURATION VERBOSE;\nSHOW DATABASE VERBOSE '<standby_db_unique_name>';\n# Monitorable properties:\nSHOW DATABASE '<primary_db_unique_name>' 'LogXptStatus';\nSHOW DATABASE '<standby_db_unique_name>' 'RecvQEntries';\nSHOW DATABASE '<primary_db_unique_name>' 'InconsistentLogXptProps';\nSHOW DATABASE '<standby_db_unique_name>' 'StatusReport';","what":"显示 broker 配置的整体状态（SUCCESS/WARNING/ERROR）、保护模式、各数据库的角色、传输/应用延迟，以及 broker 与 spfile 之间的不一致。","when":"每次 Data Guard 操作（主备切换、故障转移、参数变更）前后；日常健康检查中。","caution":"只读。启用 broker 时，不要用 SQL 修改 log_archive_dest_n 或 log_archive_config 等 Data Guard 参数；broker 会用自己的值覆盖它们。请用 EDIT DATABASE ... SET PROPERTY 修改，在 19c+ 中对初始化参数使用 EDIT DATABASE ... SET PARAMETER。","note":"StatusReport 按数据库列出 ORA- 错误；这是查找 WARNING 状态原因的最快方法。InconsistentProperties 在 19c 中已弃用且始终不返回值；redo 传输不一致请使用 InconsistentLogXptProps。","tags":["data guard","dgmgrl","broker","configuration","standby","status","lag"],"untested":true},{"id":"dg-readiness-sql","task":"使用 SQL 检查主库/备库角色和主备切换就绪状态","category":"Data Guard","command":"SELECT name, db_unique_name, database_role, open_mode, protection_mode,\n       switchover_status, force_logging, flashback_on, dataguard_broker\nFROM   v$database;\n\n-- State of redo destinations and gaps (on the primary):\nSELECT dest_id, dest_name, status, type, database_mode,\n       recovery_mode, gap_status, error\nFROM   v$archive_dest_status\nWHERE  status <> 'INACTIVE';\n\n-- Missing archivelog range on the standby:\nSELECT thread#, low_sequence#, high_sequence# FROM v$archive_gap;","what":"显示数据库角色、打开模式、保护模式、switchover_status（TO STANDBY / TO PRIMARY / SESSIONS ACTIVE / NOT ALLOWED）、FORCE LOGGING 和 flashback 状态，以及 redo 目标的错误和间隙（gap）。","when":"作为主备切换/故障转移前的预检查、新建备库后，以及在未使用 broker 的环境中进行基础监控。","caution":"只读。如果 FORCE_LOGGING=NO，NOLOGGING 操作会在备库上留下损坏块；Data Guard 主库必须启用 ALTER DATABASE FORCE LOGGING。如果 FLASHBACK_ON=NO，故障转移后旧主库无法 reinstate，必须重建。","note":"在主库上也可运行；v$archive_gap 仅在物理备库上有意义。","tags":["data guard","v$database","switchover_status","force logging","gap","role","standby"]},{"id":"dg-lag-apply-rate","task":"在备库上测量传输/应用延迟、应用速率和 Data Guard 进程","category":"Data Guard","command":"-- Run on the standby:\nSELECT name, value, unit, time_computed, datum_time\nFROM   v$dataguard_stats\nWHERE  name IN ('transport lag','apply lag','apply finish time','estimated startup time');\n\n-- Redo apply rate (KB/s):\nSELECT item, units, sofar, total, TO_CHAR(timestamp,'YYYY-MM-DD HH24:MI:SS') AS ts\nFROM   v$recovery_progress\nWHERE  item IN ('Active Apply Rate','Average Apply Rate','Maximum Apply Rate','Last Applied Redo')\nORDER  BY start_time DESC, item;\n\n-- Transport and apply processes (12.2+; replaces v$managed_standby):\nSELECT name, role, action, thread#, sequence#, block#, delay_mins\nFROM   v$dataguard_process\nORDER  BY role;","what":"显示备库落后主库多少（transport lag = 尚未接收的 redo，apply lag = 尚未应用的 redo）、积压何时应用完、应用速率，以及哪个进程正在处理哪个 sequence。","when":"用于 RPO/RTO 监控；出现延迟告警时判断问题在网络（传输）还是在备库 I/O/CPU（应用）。","caution":"只读。如果 datum_time 很旧（远落后于 time_computed），说明备库没有从主库接收数据，延迟值不可靠。检查是否设置了 DelayMins；有意的延迟看起来就像 apply lag。","since":"12c","note":"v$dataguard_process 为 12.2+。没有备库时 v$dataguard_stats 不返回任何行。","tags":["data guard","lag","lag","apply","transport","v$dataguard_stats","rpo"]},{"id":"dg-validate-database","task":"主备切换/故障转移前用 broker 验证备库（VALIDATE DATABASE）","category":"Data Guard","command":"VALIDATE DATABASE '<standby_db_unique_name>';\nVALIDATE DATABASE VERBOSE '<standby_db_unique_name>';\n# spfile parameter differences between the two sides:\nVALIDATE DATABASE '<standby_db_unique_name>' SPFILE;\n# Network and static listener registrations (18c+):\nVALIDATE NETWORK CONFIGURATION FOR ALL;\nVALIDATE STATIC CONNECT IDENTIFIER FOR ALL;","what":"检查备库是否已准备好进行角色转换（\"Ready for Switchover / Failover: Yes\"）、备用 redo 日志的数量/大小、flashback 和应用状态、spfile 差异，以及 broker 重启实例所需的静态服务。","when":"作为每次计划内主备切换前的必需步骤；新建备库后以及网络/监听变更后。","caution":"只读。如果看到 \"Ready for Switchover: No\" 或备用 redo 日志警告，不要继续主备切换。静态连接标识符错误意味着 broker 在主备切换期间无法启动实例，需要人工干预。","since":"12c","note":"VALIDATE DATABASE 为 12.1+，SPFILE 选项为 12.2+，NETWORK CONFIGURATION 和 STATIC CONNECT IDENTIFIER 为 18c+。","tags":["data guard","dgmgrl","validate","validate","switchover","standby redo log","broker"],"untested":true},{"id":"dg-switchover","task":"执行计划内主备切换（DGMGRL，无数据丢失的角色转换）","category":"Data Guard","command":"# Connect with a password (with OS authentication \"/\" the broker cannot restart the remote instance):\ndgmgrl sys@<primary_db_unique_name>\n# 1) Precheck:\nSHOW CONFIGURATION;\nVALIDATE DATABASE '<standby_db_unique_name>';\n# 2) Role change:\nSWITCHOVER TO '<standby_db_unique_name>';\n# 3) Verify: new primary and new standby\nSHOW CONFIGURATION;\nSHOW DATABASE '<primary_db_unique_name>';","what":"有控制地交换主库和备库的角色：主库所有 redo 都会传送并在备库上应用，旧主库以备库身份重启，redo 应用自动开始。","when":"用于主库站点的计划内维护（操作系统、硬件、滚动升级）、DR 演练或数据中心迁移。","caution":"主库会有短暂中断；应用连接必须通过以 -role PRIMARY 定义的服务和 FAN/TAF/AC 路由到新主库。长时间运行的事务和较高的应用延迟会延长主备切换时间；请先将延迟降到接近零。在 RAC 中 broker 会自行管理实例；不要用 srvctl 干预。","since":"12c","note":"在 12.1+ 中 broker 用一条命令完成所有步骤；没有 broker 时，11g 在 SQL 中使用 ALTER DATABASE COMMIT TO SWITCHOVER（12c+：ALTER DATABASE SWITCHOVER TO <db> VERIFY）。","tags":["data guard","switchover","dgmgrl","role change","planned","standby","primary"],"untested":true},{"id":"dg-failover","task":"主库丢失后执行故障转移（DGMGRL）","category":"Data Guard","command":"# Connect to the standby (the primary is unreachable):\ndgmgrl sys@<standby_db_unique_name>\n# 1) Assess the data that will be lost (Transport Lag / Apply Lag):\nSHOW DATABASE '<standby_db_unique_name>';\n# 2) Complete failover: all received redo is applied, then the role changes (recommended)\nFAILOVER TO '<standby_db_unique_name>';\n# 3) Emergency: immediately, without applying redo (more data loss)\n# FAILOVER TO '<standby_db_unique_name>' IMMEDIATE;\nSHOW CONFIGURATION;","what":"在主库不可用时将备库提升为主库角色；完全故障转移会先应用所有已接收的 redo，IMMEDIATE 则跳过应用。","when":"当主库站点、存储或数据库丢失且无法恢复，而 RTO 不允许等待主备切换时。","caution":"此角色转换不可逆。首先确认主库确实已宕机；如果旧主库仍处于打开状态且应用继续向其写入，就会出现脑裂和数据分叉（关闭/隔离旧主库）。在 MaxPerformance 模式下，丢失的数据量等于传输延迟。之后为新主库检查所有备库和备份策略。","note":"恢复旧主库请参见 dg-reinstate-database 操作指南（需要 flashback）。","tags":["data guard","failover","dgmgrl","disaster","disaster recovery","data loss","split-brain"],"untested":true},{"id":"dg-reinstate-database","task":"故障转移后将旧主库恢复为备库（REINSTATE）","category":"Data Guard","command":"# 1) Bring the old primary to MOUNT (sqlplus / as sysdba):\n#    SQL> STARTUP MOUNT;\n#    (RAC: srvctl start database -db <old_primary_db_unique_name> -startoption mount)\n# 2) From DGMGRL connected to the new primary:\ndgmgrl sys@<new_primary_db_unique_name>\nREINSTATE DATABASE '<old_primary_db_unique_name>';\n# 3) Verify:\nSHOW CONFIGURATION;\nSHOW DATABASE '<old_primary_db_unique_name>';","what":"使用 flashback database 将旧主库闪回到故障转移 SCN 之前，转换为物理备库并开始从新主库接收 redo；无需从头重建备库。","when":"故障转移后，当旧主库服务器/存储再次可访问时；FSFO 自动故障转移后（observer 通常会自行 reinstate）。","caution":"必须在故障转移之前就已在旧主库上启用 flashback，并且 flashback 日志必须覆盖故障转移 SCN（db_flashback_retention_target、FRA 大小）；否则 reinstate 会失败，必须用 RMAN duplicate 重建备库。故障转移后写入旧主库的数据会永久丢失。","note":"先决条件检查：dg-readiness-sql 操作指南中的 flashback_on 列。","tags":["data guard","reinstate","flashback","failover","old primary","standby","dgmgrl"],"untested":true},{"id":"dg-apply-transport-state","task":"使用 broker 停止/启动 redo 应用或 redo 传输；配置延迟应用","category":"Data Guard","command":"# Redo apply on the standby:\nEDIT DATABASE '<standby_db_unique_name>' SET STATE='APPLY-OFF';\nEDIT DATABASE '<standby_db_unique_name>' SET STATE='APPLY-ON';\n# Redo transport on the primary (to all standbys):\nEDIT DATABASE '<primary_db_unique_name>' SET STATE='TRANSPORT-OFF';\nEDIT DATABASE '<primary_db_unique_name>' SET STATE='TRANSPORT-ON';\n# Delayed apply (minutes; protection against logical errors):\nEDIT DATABASE '<standby_db_unique_name>' SET PROPERTY DelayMins=240;\nSHOW DATABASE '<standby_db_unique_name>' DelayMins;","what":"通过 broker 持久地停止/启动备库上的 redo 应用（MRP）或主库上的 redo 传送；DelayMins 会在应用前将 redo 保留指定时间。","when":"用于备库上的维护或备份、计划内网络中断、主库大批量加载期间控制传输，或构建防误删的延迟备库。","caution":"启用 broker 时，不要在 SQL 中使用 ALTER DATABASE RECOVER MANAGED STANDBY DATABASE CANCEL；broker 会恢复原状态。APPLY-OFF 期间应用延迟和故障转移时间（RTO）会增长；TRANSPORT-OFF 期间主库上产生的数据不受保护（RPO）。如果启用了 FSFO，这些操作可能会阻止故障转移。","note":"DelayMins > 0 时，redo 会在延迟后从归档日志中应用，而不是通过实时应用；延迟期间备库仍持续接收 redo。","tags":["data guard","apply","transport","mrp","delaymins","stop","dgmgrl"],"untested":true},{"id":"dg-fsfo-observer","task":"启用 Fast-Start Failover（FSFO）并启动 observer","category":"Data Guard","command":"# 1) Targets and threshold (seconds):\nEDIT DATABASE '<primary_db_unique_name>' SET PROPERTY FastStartFailoverTarget='<standby_db_unique_name>';\nEDIT DATABASE '<standby_db_unique_name>' SET PROPERTY FastStartFailoverTarget='<primary_db_unique_name>';\nEDIT CONFIGURATION SET PROPERTY FastStartFailoverThreshold=30;\n# 2) MaxAvailability for zero data loss (SYNC/FASTSYNC transport):\nEDIT DATABASE '<primary_db_unique_name>' SET PROPERTY LogXptMode='FASTSYNC';\nEDIT DATABASE '<standby_db_unique_name>' SET PROPERTY LogXptMode='FASTSYNC';\nEDIT CONFIGURATION SET PROTECTION MODE AS MAXAVAILABILITY;\n# Acceptable loss in MaxPerformance: EDIT CONFIGURATION SET PROPERTY FastStartFailoverLagLimit=30;\nENABLE FAST_START FAILOVER;\n# 3) Observer: on a third server, separate from primary and standby (passwordless connection via wallet)\ndgmgrl /@<primary_tns> \"START OBSERVER obs1 IN BACKGROUND FILE IS '/u01/app/oracle/dg/fsfo_obs1.dat' LOGFILE IS '/u01/app/oracle/dg/obs1.log' CONNECT IDENTIFIER IS <primary_tns>\"\n# 4) Verify:\nSHOW FAST_START FAILOVER;\nSHOW OBSERVER;","what":"当主库不可达时，observer 在超过阈值后自动故障转移到备库，并在旧主库恢复时自动 reinstate。","when":"用于 RTO 目标为分钟级、无法等待人工干预的关键系统。","caution":"observer 不得与主库或备库运行在同一服务器/数据中心；否则 FSFO 在站点丢失时会失效，或在网络分区期间做出错误决定。两个数据库都必须启用 flashback。阈值过低会把短暂的网络抖动变成不必要的故障转移。计划内维护时，在关闭主库前执行 DISABLE FAST_START FAILOVER 或考虑 observer 的影响。","since":"12c","note":"FASTSYNC 为 12.2+，IN BACKGROUND 和多个 observer（SHOW OBSERVER）为 12.2+。IN BACKGROUND 需要将凭据保存在 Oracle wallet（mkstore）中。","tags":["data guard","fsfo","fast-start failover","observer","automatic failover","maxavailability","dgmgrl"],"untested":true},{"id":"dg-snapshot-standby","task":"将物理备库转换为快照备库并转回（测试环境）","category":"Data Guard","command":"# Make the standby read-write (redo is still received, apply stops):\nCONVERT DATABASE '<standby_db_unique_name>' TO SNAPSHOT STANDBY;\nSHOW DATABASE '<standby_db_unique_name>';\n# ... testing / application validation ...\n# Convert back: all test changes are discarded, accumulated redo is applied\nCONVERT DATABASE '<standby_db_unique_name>' TO PHYSICAL STANDBY;\nSHOW CONFIGURATION;","what":"在备库上自动创建一个担保还原点（guaranteed restore point）并以读写方式打开；转回时闪回到该点并应用从主库累积的 redo。","when":"无需另建副本，即可在生产数据上演练升级、测试应用版本或运行 Real Application Testing（DB Replay）。","caution":"作为快照备库期间不会应用 redo：RPO 得以保持，但故障转移时间（RTO）会随累积的 redo 增长，且不能作为 FSFO 目标。flashback 日志可能填满 FRA；请监控 FRA 空间。转回时，在快照中所做的更改会被永久丢弃。","note":"SQL 等价命令：ALTER DATABASE CONVERT TO SNAPSHOT STANDBY / CONVERT TO PHYSICAL STANDBY（不使用 broker 时）。","tags":["data guard","snapshot standby","test","restore point","flashback","convert","dgmgrl"],"untested":true},{"id":"rman-catalog-create","task":"创建 RMAN 恢复目录并注册数据库","category":"RMAN","command":"-- 1) In the catalog database (separate DB/PDB), with sqlplus:\nCREATE TABLESPACE rcat_ts DATAFILE '/u02/oradata/RCAT/rcat01.dbf' SIZE 1G AUTOEXTEND ON NEXT 256M;\nCREATE USER rcat IDENTIFIED BY \"<password>\"\n  DEFAULT TABLESPACE rcat_ts QUOTA UNLIMITED ON rcat_ts;\nGRANT RECOVERY_CATALOG_OWNER TO rcat;\n# 2) From the shell: create the catalog schema\nrman catalog rcat@<rcat_tns>\nCREATE CATALOG;\n# 3) Register the target database and verify:\nrman target / catalog rcat@<rcat_tns>\nREGISTER DATABASE;\nREPORT SCHEMA;\n# Resync controlfile records into the catalog (done automatically after backups):\nRESYNC CATALOG;","what":"构建一个恢复目录，将备份元数据保存在独立于控制文件保留期（control_file_record_keep_time）的集中 schema 中，并注册目标数据库。","when":"用于将多个数据库的备份历史集中保存，在 Data Guard 中让主库/备库备份互换使用，以及长期（KEEP）备份和存储脚本。","caution":"目录数据库必须与其保护的数据库位于不同服务器上，且自身也必须备份。目录 schema 版本不能低于所使用的最新 RMAN 客户端；更新的客户端需要 UPGRADE CATALOG。在 Data Guard 中只注册主库；备库通过 CONFIGURE DB_UNIQUE_NAME ... CONNECT IDENTIFIER 按 DB_UNIQUE_NAME 定义。","note":"将目录 schema 放在单独的数据库中；如果与目标库放在一起，灾难发生时目录也会一并丢失。","tags":["rman","catalog","catalog","recovery catalog","register","backup","backup"],"untested":true},{"id":"rman-duplicate-standby","task":"使用 RMAN DUPLICATE 从活动数据库创建物理备库","category":"RMAN","command":"# Prerequisites: standby instance in NOMOUNT with a pfile containing only db_name,\n# primary password file copied, static listener entries and tnsnames on both sides.\nrman target sys@<primary_tns> auxiliary sys@<standby_tns>\nRUN {\n  ALLOCATE CHANNEL p1 DEVICE TYPE DISK;\n  ALLOCATE CHANNEL p2 DEVICE TYPE DISK;\n  ALLOCATE AUXILIARY CHANNEL s1 DEVICE TYPE DISK;\n  ALLOCATE AUXILIARY CHANNEL s2 DEVICE TYPE DISK;\n  DUPLICATE TARGET DATABASE FOR STANDBY FROM ACTIVE DATABASE\n    USING COMPRESSED BACKUPSET SECTION SIZE 32G\n    DORECOVER\n    SPFILE\n      SET db_unique_name='<standby_db_unique_name>'\n      SET control_files='+DATA','+FRA'\n      SET db_create_file_dest='+DATA'\n      SET db_recovery_file_dest='+FRA'\n      SET fal_server='<primary_tns>'\n      SET log_archive_dest_2=''\n      SET dg_broker_start='FALSE'\n    NOFILENAMECHECK;\n}","what":"通过网络将主库数据文件复制到备库服务器（无需中间备份文件），创建适用于备库的 spfile，并用 DORECOVER 应用可用的归档日志。","when":"新建 Data Guard 备库或重建无法 reinstate 的备库时。","caution":"会在主库上产生网络和 I/O 负载；不要在高峰时段运行，或限制通道数量。NOFILENAMECHECK 仅在备库位于独立服务器上时才安全；在同一服务器上可能覆盖主库文件。在之前或之后添加备用 redo 日志（与主库在线 redo 大小相同，每个 thread 多一组），然后配置 broker。","since":"12c","note":"使用 USING [COMPRESSED] BACKUPSET 和 SECTION SIZE 的 active duplicate 为 12c+；11g 使用镜像副本。如果使用文件系统而非 ASM，请设置 db_file_name_convert/log_file_name_convert。","tags":["rman","duplicate","standby","active database","data guard","setup","clone"],"untested":true},{"id":"rman-crosscheck-cleanup","task":"验证并清理 RMAN 备份记录（CROSSCHECK、EXPIRED、OBSOLETE）","category":"RMAN","command":"SHOW RETENTION POLICY;\n# 1) Compare records with the physical files (missing ones become EXPIRED):\nCROSSCHECK BACKUP;\nCROSSCHECK COPY;\nCROSSCHECK ARCHIVELOG ALL;\n# 2) Review before deleting:\nLIST EXPIRED BACKUP;\nLIST EXPIRED ARCHIVELOG ALL;\nREPORT OBSOLETE;\n# 3) Clean up:\nDELETE NOPROMPT EXPIRED BACKUP;\nDELETE NOPROMPT EXPIRED COPY;\nDELETE NOPROMPT EXPIRED ARCHIVELOG ALL;\nDELETE NOPROMPT OBSOLETE;","what":"CROSSCHECK 检查 RMAN 记录中的备份是否仍存在于磁盘/磁带上；DELETE EXPIRED 删除缺失文件的记录，DELETE OBSOLETE 从记录和磁盘中删除保留策略不再需要的备份。","when":"备份文件在操作系统层面被删除/移动时、FRA 填满时，以及作为常规备份维护作业的一部分。","caution":"DELETE OBSOLETE 会物理删除文件；先用 REPORT OBSOLETE 核对清单，并确认保留策略（RECOVERY WINDOW / REDUNDANCY）符合预期。如果在磁带/介质管理器暂时不可达时运行 CROSSCHECK，有效备份会显示为 EXPIRED；此时不要运行 DELETE EXPIRED。","note":"在 Data Guard 中，归档日志删除请使用 CONFIGURE ARCHIVELOG DELETION POLICY TO APPLIED ON ALL STANDBY。","tags":["rman","crosscheck","expired","obsolete","cleanup","retention","backup"],"untested":true},{"id":"rman-validate","task":"验证数据库和备份是否存在损坏（VALIDATE）","category":"RMAN","command":"# Physical + logical block check (without producing a backup):\nVALIDATE CHECK LOGICAL DATABASE;\n# Scan large datafiles in parallel (11g+, needs multiple channels):\nVALIDATE CHECK LOGICAL DATABASE SECTION SIZE 16G;\n# A single datafile / tablespace:\nVALIDATE DATAFILE 7;\nVALIDATE TABLESPACE users;\n# Read as if taking a backup and check (including archivelogs):\nBACKUP VALIDATE CHECK LOGICAL DATABASE ARCHIVELOG ALL;\n# Readability of a specific backup set:\nLIST BACKUP SUMMARY;\nVALIDATE BACKUPSET <bs_key>;\n-- Corrupt blocks found and the affected segment (SQL):\nSELECT file#, block#, blocks, corruption_change#, corruption_type\nFROM   v$database_block_corruption ORDER BY file#, block#;\nSELECT DISTINCT e.owner, e.segment_name, e.segment_type\nFROM   dba_extents e\nJOIN   v$database_block_corruption c\n  ON   e.file_id = c.file#\n AND   c.block# BETWEEN e.block_id AND e.block_id + e.blocks - 1;","what":"读取数据文件和备份集以检测物理块损坏以及（使用 CHECK LOGICAL 时）逻辑块损坏，并将结果记录到 v$database_block_corruption。SQL 查询显示损坏块属于哪个表/索引。","when":"出现 ORA-01578/ORA-08103 等损坏错误后、存储问题后，以及作为常规（每周）完整性检查；用于证明备份确实可恢复。","caution":"它会执行全量读取；在大型数据库上会产生大量 I/O，请在高峰时段之外运行。dba_extents 查询在大型数据库上可能很慢。如果损坏块位于临时（TEMP）空间或空闲空间中，则不会返回匹配的对象。","since":"11g","note":"没有损坏时查询不返回任何行。带 SECTION SIZE 的 VALIDATE 为 11g+。","tags":["rman","validate","corruption","corruption","check logical","block","ora-01578"],"untested":true},{"id":"rman-restore-preview","task":"查看还原将使用哪些备份并进行验证（PREVIEW / VALIDATE）","category":"RMAN","command":"# Backups and archivelogs a restore needs (without reading the files):\nRESTORE DATABASE PREVIEW SUMMARY;\n# For a return to a point in time:\nRESTORE DATABASE UNTIL TIME \"TO_DATE('2026-01-15 03:00:00','YYYY-MM-DD HH24:MI:SS')\" PREVIEW;\n# Validate the backups by actually reading them (without restoring):\nRESTORE DATABASE VALIDATE;\nRESTORE ARCHIVELOG FROM TIME 'SYSDATE-1' VALIDATE;\nRESTORE CONTROLFILE VALIDATE;\nRESTORE SPFILE VALIDATE;","what":"PREVIEW 根据目录/控制文件报告还原将使用哪些备份集/副本以及哪个归档日志范围；VALIDATE 会实际读取这些备份并验证没有损坏或缺失文件。","when":"用于实际还原前的规划、证明备份策略满足 RPO 目标，以及定期还原测试。","caution":"PREVIEW 只查看记录；如果文件已被删除，结果会有误导性，因此请先运行 CROSSCHECK 或使用 RESTORE ... VALIDATE。VALIDATE 会读取整个备份；通过磁带/网络可能耗时较长。让日期依赖 TO_DATE 格式掩码，而不是会话的 NLS 设置。","note":"PREVIEW RECALL 还可以请求必须从磁带召回的备份（需要介质管理器支持）。","tags":["rman","restore","preview","validate","restore test","backup","rpo"],"untested":true},{"id":"rman-block-media-recovery","task":"在线修复损坏块（块介质恢复，RECOVER CORRUPTION LIST）","category":"RMAN","command":"# 1) Detect corrupt blocks (populates v$database_block_corruption):\nVALIDATE CHECK LOGICAL DATABASE;\n# 2) Repair all blocks in the list (the datafile stays online):\nRECOVER CORRUPTION LIST;\n# or the file/block from the error message (ORA-01578: file# 7, block# 233):\nRECOVER DATAFILE 7 BLOCK 233;\nRECOVER DATAFILE 7 BLOCK 233 DATAFILE 9 BLOCK 1001;\n# 3) Verify:\nVALIDATE DATAFILE 7;\nSELECT * FROM v$database_block_corruption;","what":"仅从备份（或 flashback 日志 / Active Data Guard 备库）还原损坏的块并用 redo 前滚；数据文件和表空间保持在线，应用只有在访问这些块时才会报错。","when":"出现带 ORA-01578 / ORA-01110 的孤立块损坏时；还原整个数据文件会造成不必要停机时。","caution":"需要一个包含该块的干净备份以及从那时到现在的所有归档日志；NOLOGGING 操作导致的损坏（ORA-26040）无法用这种方式修复，必须重建对象。这是 Enterprise Edition 功能。如果不找出损坏源头（存储、HBA、固件），问题会再次出现。","note":"使用 Active Data Guard 时，11.2+ 的自动块介质恢复会自动从备库修复损坏块。","tags":["rman","block recovery","corrupt block","corruption","ora-01578","recover","repair"],"untested":true},{"id":"rman-tspitr","task":"将表空间恢复到过去某个时间点（TSPITR）","category":"RMAN","command":"-- 1) Is the tablespace self-contained (SQL, on the target DB):\nEXEC DBMS_TTS.TRANSPORT_SET_CHECK(ts_list => 'USERS', incl_constraints => TRUE, full_check => TRUE);\nSELECT * FROM transport_set_violations;\n-- 2) Objects created AFTER the target time that TSPITR will lose:\nSELECT owner, name, tablespace_name,\n       TO_CHAR(creation_time,'YYYY-MM-DD HH24:MI:SS') AS created\nFROM   ts_pitr_objects_to_be_dropped\nWHERE  tablespace_name = 'USERS'\n  AND  creation_time > TO_DATE('2026-01-15 03:00:00','YYYY-MM-DD HH24:MI:SS');\n# 3) RMAN: with an automatic auxiliary instance\nRECOVER TABLESPACE users\n  UNTIL TIME \"TO_DATE('2026-01-15 03:00:00','YYYY-MM-DD HH24:MI:SS')\"\n  AUXILIARY DESTINATION '/u03/aux';\n# 4) The tablespace is left OFFLINE: take a backup first, then bring it online\nBACKUP TABLESPACE users;\nALTER TABLESPACE users ONLINE;","what":"RMAN 在临时辅助实例中将 SYSTEM/UNDO/SYSAUX 和目标表空间恢复到指定时间，然后通过可传输表空间将其插回目标数据库；数据库其余部分保持最新。","when":"当错误的批处理作业或误操作的 DROP/TRUNCATE 只损坏了一个表空间（或一组自包含的表空间）中的数据，且 flashback 选项不足以解决时。","caution":"该表空间中在目标时间之后所做的所有更改以及创建的所有对象都会丢失。表空间集必须是自包含的（索引/表、分区、LOB 或 FK 不能位于其他表空间）。不能用于包含 SYSTEM、UNDO 或 SYS 对象的表空间。TSPITR 后，该表空间之前的备份不可用；请立即备份。AUXILIARY DESTINATION 需要足够容纳 SYSTEM+SYSAUX+UNDO+目标表空间的空闲空间。","note":"对于单个表，rman-recover-table 的破坏性更小。","tags":["rman","tspitr","tablespace","pitr","point-in-time","auxiliary","rollback"],"untested":true},{"id":"rman-recover-table","task":"从 RMAN 备份将单个表恢复到过去某个时间点（RECOVER TABLE）","category":"RMAN","command":"# Restore under a new name (the original table is not touched):\nRECOVER TABLE hr.employees\n  UNTIL TIME \"TO_DATE('2026-01-15 03:00:00','YYYY-MM-DD HH24:MI:SS')\"\n  AUXILIARY DESTINATION '/u03/aux'\n  REMAP TABLE hr.employees:employees_rec;\n# Multitenant: a table inside a PDB\nRECOVER TABLE hr.employees OF PLUGGABLE DATABASE <pdb>\n  UNTIL SCN 12345678\n  AUXILIARY DESTINATION '/u03/aux'\n  REMAP TABLE hr.employees:employees_rec;\n# Only produce a Data Pump dump, do not import:\nRECOVER TABLE hr.employees\n  UNTIL SCN 12345678\n  AUXILIARY DESTINATION '/u03/aux'\n  DATAPUMP DESTINATION '/u03/dump'\n  DUMP FILE 'employees_rec.dmp'\n  NOTABLEIMPORT;","what":"RMAN 将自动创建的辅助实例恢复到指定时间，用 Data Pump 导出该表（或分区），并通过 REMAP 以不同名称导入目标数据库；之后删除辅助实例。","when":"用于恢复误删除/误更新或用 PURGE 删除的单个表的数据，且超出了 Flashback Table/Query 的 undo 保留范围时。","caution":"AUXILIARY DESTINATION 需要足够容纳 SYSTEM、SYSAUX、UNDO 和该表所在表空间的空闲空间；操作可能耗时数小时。不使用 REMAP TABLE 时，如果存在同名表则操作失败；始终使用 REMAP 或 NOTABLEIMPORT 以避免覆盖原表。SYS/SYSTEM schema 中以及 SYSTEM/SYSAUX 表空间中的表无法用这种方式恢复。需要 Enterprise Edition。","since":"12c","note":"必需条件：一个全量备份以及覆盖目标时间的归档日志。对于分区：RECOVER TABLE hr.sales:p2025 ... REMAP TABLE hr.sales:p2025:sales_p2025_rec。","tags":["rman","recover table","table recovery","pitr","auxiliary","remap","accidental delete"],"untested":true},{"id":"pdb-create-from-seed","task":"从 PDB$SEED 创建新 PDB","category":"多租户","command":"-- In CDB$ROOT, as SYSDBA:\nCREATE PLUGGABLE DATABASE <pdb>\n  ADMIN USER pdbadmin IDENTIFIED BY \"<password>\" ROLES = (DBA)\n  DEFAULT TABLESPACE users\n    DATAFILE '/u02/oradata/<cdb>/<pdb>/users01.dbf' SIZE 512M AUTOEXTEND ON NEXT 64M\n  FILE_NAME_CONVERT = ('/u02/oradata/<cdb>/pdbseed/', '/u02/oradata/<cdb>/<pdb>/')\n  STORAGE (MAXSIZE 50G);\n-- With OMF/ASM, instead of FILE_NAME_CONVERT: CREATE_FILE_DEST = '+DATA'\nALTER PLUGGABLE DATABASE <pdb> OPEN;\nALTER PLUGGABLE DATABASE <pdb> SAVE STATE;\nSELECT con_id, name, open_mode, restricted FROM v$pdbs ORDER BY con_id;","what":"从种子模板创建一个新的空可插拔数据库；在同一命令中定义本地管理员用户、默认表空间和总大小上限。","when":"为新应用/schema 开设隔离的数据库时；准备测试和开发环境时。","caution":"新 PDB 启动后处于 MOUNTED 状态；需要 OPEN 并 SAVE STATE（以便 CDB 重启后保持打开）。错误的 FILE_NAME_CONVERT 路径会把文件写到错误的目录；不使用 OMF 时，DATAFILE 必须指定完整文件路径（ORA-02236）。许可：在 19c EE 中，每个 CDB 可在没有 Multitenant 选件的情况下使用 3 个用户 PDB。","since":"12c","note":"在 RAC 中，使用 srvctl add service -pdb 为 PDB 添加应用服务。","tags":["multitenant","pdb","create","create pluggable database","seed","cdb","pdb$seed"]},{"id":"pdb-clone-local","task":"在同一 CDB 内克隆 PDB（本地/热克隆）","category":"多租户","command":"-- Hot clone prerequisites: local undo + ARCHIVELOG\nSELECT property_value AS local_undo\nFROM   database_properties WHERE property_name = 'LOCAL_UNDO_ENABLED';\nSELECT log_mode FROM v$database;\n\nCREATE PLUGGABLE DATABASE <new_pdb> FROM <pdb>\n  FILE_NAME_CONVERT = ('/<pdb>/', '/<new_pdb>/');\n-- OMF/ASM: CREATE PLUGGABLE DATABASE <new_pdb> FROM <pdb> CREATE_FILE_DEST = '+DATA';\nALTER PLUGGABLE DATABASE <new_pdb> OPEN;\nSELECT name, open_mode FROM v$pdbs WHERE name = UPPER('<new_pdb>');","what":"通过复制源 PDB 的数据文件在同一 CDB 中创建新 PDB；在本地 undo 和 ARCHIVELOG 模式下，可在源库以读写方式打开时运行（热克隆）。","when":"用于创建生产 PDB 的测试/开发副本、演练升级或补丁，或在高风险变更前快速做一份副本。","caution":"复制会在源端产生额外 I/O，并在目标端需要与源相同的空间。在共享 undo 模式或 NOARCHIVELOG 下，源必须为 READ ONLY（19c 文档）。克隆包含生产数据：打开前请检查数据脱敏、DB link、作业（DBMS_SCHEDULER）以及与外部系统的连接。","since":"12c","note":"在 19c 中，源库读写状态下的热克隆需要本地 undo + ARCHIVELOG。如需快速精简副本，请使用 SNAPSHOT COPY（需要合适的存储）。","tags":["multitenant","pdb","clone","clone","hot clone","copy","local undo"]},{"id":"pdb-clone-remote","task":"通过 DB link 从另一个 CDB 克隆 PDB（远程克隆）","category":"多租户","command":"-- 1) In the SOURCE CDB, a common user:\nCREATE USER c##clone_user IDENTIFIED BY \"<password>\" CONTAINER = ALL;\nGRANT CREATE SESSION, CREATE PLUGGABLE DATABASE TO c##clone_user CONTAINER = ALL;\n\n-- 2) In the TARGET CDB root, DB link and test:\nCREATE DATABASE LINK clone_link\n  CONNECT TO c##clone_user IDENTIFIED BY \"<password>\"\n  USING '//<source_host>:1521/<source_cdb_service>';\nSELECT * FROM dual@clone_link;\n\n-- 3) Clone and open:\nCREATE PLUGGABLE DATABASE <new_pdb> FROM <pdb>@clone_link\n  FILE_NAME_CONVERT = ('/<pdb>/', '/<new_pdb>/');\nALTER PLUGGABLE DATABASE <new_pdb> OPEN;\n\n-- 4) Violations (version, options, parameters, patches):\nSELECT name, cause, type, message\nFROM   pdb_plug_in_violations\nWHERE  name = UPPER('<new_pdb>') AND status <> 'RESOLVED';","what":"通过 DB link 经网络将 PDB 从源 CDB 复制到目标 CDB；无需中间文件或 unplug。","when":"用于将 PDB 迁移到另一台服务器/CDB、在独立服务器上创建生产测试副本，或迁移到更新版本的 CDB。","caution":"源 CDB 和目标 CDB 在字节序、字符集（或目标为 AL32UTF8）和选件上必须兼容；克隆到更新版本的 PDB 首次会以 RESTRICTED 方式打开，需要执行 datapatch/升级。克隆完成后删除 DB link 和公用用户。热克隆要求源端为本地 undo + ARCHIVELOG。","since":"12c","note":"如需迁移（删除源 PDB），请使用 12.2+ 的 RELOCATE 选项：CREATE PLUGGABLE DATABASE <pdb> FROM <pdb>@clone_link RELOCATE。","tags":["multitenant","pdb","remote clone","db link","clone","migration","relocate"]},{"id":"pdb-unplug-plug","task":"拔出（unplug）PDB 并插入另一个（或同一个）CDB","category":"多租户","command":"-- SOURCE CDB: close and unplug to an XML manifest\nALTER PLUGGABLE DATABASE <pdb> CLOSE IMMEDIATE;\nALTER PLUGGABLE DATABASE <pdb> UNPLUG INTO '/u01/stage/<pdb>.xml';\n-- (Single-file archive: UNPLUG INTO '/u01/stage/<pdb>.pdb' - includes datafiles, 12.2+)\nDROP PLUGGABLE DATABASE <pdb> KEEP DATAFILES;\n\n-- TARGET CDB: check compatibility first\nSET SERVEROUTPUT ON\nDECLARE\n  l_ok BOOLEAN;\nBEGIN\n  l_ok := DBMS_PDB.CHECK_PLUG_COMPATIBILITY(\n            pdb_descr_file => '/u01/stage/<pdb>.xml',\n            pdb_name       => '<pdb>');\n  DBMS_OUTPUT.PUT_LINE(CASE WHEN l_ok THEN 'COMPATIBLE' ELSE 'NOT COMPATIBLE - see PDB_PLUG_IN_VIOLATIONS' END);\nEND;\n/\nSELECT name, cause, type, status, message\nFROM   pdb_plug_in_violations\nWHERE  name = UPPER('<pdb>') AND status <> 'RESOLVED';\n\n-- NOCOPY if the files stay in place, COPY FILE_NAME_CONVERT if they move:\nCREATE PLUGGABLE DATABASE <pdb> USING '/u01/stage/<pdb>.xml' NOCOPY TEMPFILE REUSE;\n-- From a .pdb archive: CREATE PLUGGABLE DATABASE <pdb> USING '/u01/stage/<pdb>.pdb'\n--                    FILE_NAME_CONVERT = ('/u01/stage/', '/u02/oradata/<cdb>/<pdb>/');\nALTER PLUGGABLE DATABASE <pdb> OPEN;","what":"将 PDB 从 CDB 中分离，并把元数据写入 XML 清单（或与数据文件一起打包为单个 .pdb 归档）；兼容性检查后，使用相同文件将其插入目标 CDB。","when":"用于将 PDB 迁移到同一服务器上更新版本的 CDB（unplug/plug 升级）、CDB 整合，或无法通过网络克隆的迁移。","caution":"拔出后，PDB 在源 CDB 中不可用（停机）。使用 NOCOPY 插入的文件此后归目标 CDB 所有；请先做文件级副本或 RMAN 备份，以便失败时回退。在 ERROR 类违规解决之前，PDB 会以 RESTRICTED 方式打开。重新插入同一 CDB 时，省略 AS CLONE 可能导致 GUID 冲突。","since":"12c","note":"一个 .pdb 归档（12.2+）包含数据文件，因此可作为单个文件迁移；使用 XML 方法时数据文件需单独复制。","tags":["multitenant","pdb","unplug","plug","migration","xml manifest","check_plug_compatibility"]},{"id":"pdb-open-save-state","task":"打开 PDB 并在 CDB 重启后保持打开（SAVE STATE）","category":"多租户","command":"-- Open all PDBs (RAC: INSTANCES = ALL):\nALTER PLUGGABLE DATABASE ALL OPEN;\n-- ALTER PLUGGABLE DATABASE <pdb> OPEN INSTANCES = ALL;\n-- Save the current open state (opens automatically after a CDB restart):\nALTER PLUGGABLE DATABASE <pdb> SAVE STATE;\n-- Saved states:\nSELECT con_name, instance_name, state, restricted FROM dba_pdb_saved_states;\n-- Remove the saved state:\nALTER PLUGGABLE DATABASE <pdb> DISCARD STATE;\n-- If it opens RESTRICTED, find the reason:\nSELECT name, cause, type, message\nFROM   pdb_plug_in_violations\nWHERE  status <> 'RESOLVED';","what":"以 READ WRITE 方式打开 PDB 并保存其当前打开状态，使 CDB 重启时 PDB 以相同模式自动打开。","when":"创建、插入或克隆 PDB 之后；发现 CDB 重启后 PDB 仍处于 MOUNTED 时。","caution":"SAVE STATE 记录的是执行命令时的状态：如果在 PDB 关闭时运行，记录的是 \"closed\" 状态（或删除该记录）。在 RAC 中状态按实例保存；采用基于服务的管理（通过 srvctl 的 PDB 服务）时，优先由 Clusterware 启动而不是 SAVE STATE。在 Data Guard 备库上，打开模式取决于角色。","since":"12c","note":"12.1.0.2+.","tags":["multitenant","pdb","open","save state","auto open","restricted","dba_pdb_saved_states"]},{"id":"pdb-resource-limits","task":"设置每个 PDB 的 CPU、内存、I/O 和存储上限","category":"多租户","command":"ALTER SESSION SET CONTAINER = <pdb>;\n-- CPU (instance caging with Resource Manager; at PDB level in 19c):\nALTER SYSTEM SET cpu_count = 2 SCOPE = BOTH;\n-- Memory (sga_min_size guarantee, sga_target upper limit):\nALTER SYSTEM SET sga_target = 512M SCOPE = BOTH;\nALTER SYSTEM SET sga_min_size = 96M SCOPE = BOTH;\nALTER SYSTEM SET pga_aggregate_limit = 2G SCOPE = BOTH;\n-- I/O (non-Exadata):\nALTER SYSTEM SET max_iops = 5000 SCOPE = BOTH;\nALTER SYSTEM SET max_mbps = 200 SCOPE = BOTH;\n-- Number of sessions (after the PDB is reopened):\nALTER SYSTEM SET sessions = 200 SCOPE = SPFILE;\n-- Storage:\nALTER PLUGGABLE DATABASE STORAGE (MAXSIZE 10G MAX_SHARED_TEMP_SIZE 2G);\n\n-- Settings across all PDBs, from the root:\nALTER SESSION SET CONTAINER = CDB$ROOT;\nSELECT p.name AS pdb, sp.name, sp.value\nFROM   v$system_parameter sp JOIN v$pdbs p ON p.con_id = sp.con_id\nWHERE  sp.name IN ('cpu_count','sga_target','sga_min_size','max_iops','max_mbps','pga_aggregate_limit')\nORDER  BY p.name, sp.name;","what":"通过 PDB 级参数限制一个 PDB 可从 CDB 使用多少 CPU、SGA/PGA、IOPS/MBPS 和总存储；防止\"吵闹邻居\"PDB 影响其他 PDB。","when":"在整合的 CDB 中、在同一 CDB 中运行不同 SLA 的应用时，或发现某个 PDB 耗尽整台服务器资源时。","caution":"PDB 的 sga_target 必须小于 CDB 的 sga_target；过低的值会导致 ORA-04031 和性能问题。sga_min_size 的总和不能占满整个 CDB SGA。要使 PDB 中的 cpu_count 生效，CDB 中必须激活资源管理器计划。达到 MAXSIZE 时 PDB 会报 ORA-65114。","since":"12c","note":"23ai 要求 sga_min_size 低于 sga_target 的 20%（ORA-56746），19c 中的限制为 50%。MAX_IOPS/MAX_MBPS 为 12.2+，PDB 内存参数为 12.2+。","tags":["multitenant","pdb","resource","resource","cpu_count","sga_target","maxsize"]},{"id":"pdb-container-context","task":"确认当前所在容器，并从 root 查询所有 PDB（CDB_ / CONTAINERS）","category":"多租户","command":"-- Current container:\nSHOW CON_NAME\nSHOW CON_ID\nSHOW PDBS\nSELECT SYS_CONTEXT('USERENV','CON_NAME') AS con_name,\n       SYS_CONTEXT('USERENV','CON_ID')   AS con_id\nFROM   dual;\n\n-- All containers, from the root:\nSELECT con_id, name, open_mode FROM v$containers ORDER BY con_id;\n\n-- CDB_ views combine all open PDBs with CON_ID:\nSELECT c.name AS container, t.tablespace_name, t.status\nFROM   cdb_tablespaces t JOIN v$containers c ON c.con_id = t.con_id\nORDER  BY c.name, t.tablespace_name;\n\n-- Query any table/view across all PDBs with CONTAINERS():\nSELECT con_id, COUNT(*) AS users\nFROM   CONTAINERS(dba_users)\nGROUP  BY con_id ORDER BY con_id;","what":"显示会话所连接的容器；在 root 中，CDB_ 视图和 CONTAINERS() 子句可在单个查询中收集所有已打开 PDB 的数据字典信息。","when":"在 DDL/参数变更前确认所处容器正确，以及建立所有 PDB 的空间、用户和对象清单。","caution":"只读。不要在 root（CDB$ROOT）中误建应用对象或本地用户；在 root 中执行 ALTER SYSTEM 会影响整个 CDB。CDB_ 视图和 CONTAINERS() 只返回已打开的 PDB；MOUNTED 的 PDB 不会出现在结果中。","since":"12c","note":"切换容器：ALTER SESSION SET CONTAINER = <pdb>;（参见 rosetta \"切换当前数据库\"）。","tags":["multitenant","container","con_name","cdb_","containers","pdb","root"]},{"id":"pdb-refreshable-clone","task":"创建可刷新 PDB、刷新它并转换为独立 PDB","category":"多租户","command":"-- In the target CDB root (DB link to the source ready: see pdb-clone-remote):\nCREATE PLUGGABLE DATABASE <new_pdb> FROM <pdb>@clone_link\n  FILE_NAME_CONVERT = ('/<pdb>/', '/<new_pdb>/')\n  REFRESH MODE EVERY 60 MINUTES;\nSELECT pdb_name, status, refresh_mode, refresh_interval, last_refresh_scn\nFROM   dba_pdbs WHERE pdb_name = UPPER('<new_pdb>');\n\n-- Can only be opened READ ONLY; close it first to refresh manually:\nALTER PLUGGABLE DATABASE <new_pdb> OPEN READ ONLY;\nALTER PLUGGABLE DATABASE <new_pdb> CLOSE IMMEDIATE;\nALTER PLUGGABLE DATABASE <new_pdb> REFRESH;\n\n-- Change the mode (MANUAL) or convert to an independent, writable PDB (irreversible):\nALTER PLUGGABLE DATABASE <new_pdb> REFRESH MODE MANUAL;\nALTER PLUGGABLE DATABASE <new_pdb> REFRESH MODE NONE;\nALTER PLUGGABLE DATABASE <new_pdb> OPEN;","what":"创建一个副本，通过定期或按需应用源 PDB 的 redo 保持最新；它以 READ ONLY 方式打开用于报表，使用 REFRESH MODE NONE 后成为独立的读写 PDB。","when":"用于定期刷新的生产报表/测试副本、低停机 PDB 迁移（最后一次刷新 + REFRESH MODE NONE），或\"廉价的 PDB 级备库\"。","caution":"刷新期间 PDB 必须关闭；以 READ ONLY 打开时自动刷新会等待。刷新所需的归档日志必须在源端可访问。REFRESH MODE NONE 不可逆；副本将无法再刷新。要求源端为本地 undo + ARCHIVELOG。它不能替代 Data Guard 用于真正的 DR。","since":"12c","note":"12.2+。REFRESH MODE NONE 后克隆成为普通 PDB，可以 READ WRITE 方式打开；此操作无法撤销。","tags":["multitenant","pdb","refreshable","refresh","refresh","reporting","clone"]}]},"recipes/oracle-ops.json":{"engineId":"oracle","engine":"Oracle","order":2,"recipes":[{"id":"awr-snapshot-report","task":"创建 AWR 快照并生成两个快照之间的 AWR 报告","category":"性能","command":"-- 1) Manual snapshot before and after the event (run in CDB root):\nEXEC DBMS_WORKLOAD_REPOSITORY.CREATE_SNAPSHOT;\n\n-- 2) List snapshots:\nSELECT snap_id,\n       TO_CHAR(begin_interval_time,'YYYY-MM-DD HH24:MI') AS begin_time,\n       TO_CHAR(end_interval_time,'YYYY-MM-DD HH24:MI')   AS end_time\nFROM   dba_hist_snapshot\nWHERE  dbid = (SELECT dbid FROM v$database)\nORDER  BY snap_id DESC\nFETCH FIRST 24 ROWS ONLY;\n\n-- 3a) Interactive report (prompts for HTML/TEXT and snapshot range):\n-- @?/rdbms/admin/awrrpt.sql      (RAC: awrgrpt.sql, specific instance: awrrpti.sql)\n\n-- 3b) TEXT report from a script:\nSET LINESIZE 8000 PAGESIZE 0 TRIMSPOOL ON HEADING OFF FEEDBACK OFF\nSPOOL /tmp/awr_<begin_snap>_<end_snap>.txt\nSELECT output\nFROM   TABLE(DBMS_WORKLOAD_REPOSITORY.AWR_REPORT_TEXT(\n         l_dbid     => (SELECT dbid FROM v$database),\n         l_inst_num => (SELECT instance_number FROM v$instance),\n         l_bid      => <begin_snap>,\n         l_eid      => <end_snap>));\nSPOOL OFF\n-- For HTML: DBMS_WORKLOAD_REPOSITORY.AWR_REPORT_HTML (same parameters)","what":"生成覆盖问题时间窗口的两个 AWR 快照之间的报告：负载概况、Top 等待事件、Top SQL 和实例统计信息。表函数可让你从脚本或调度作业中获取报告。","when":"分析特定时间窗口内的性能下降、负载测试或批处理前后对比，以及容量报告时。","caution":"需要 Diagnostics Pack 许可（CONTROL_MANAGEMENT_PACK_ACCESS='DIAGNOSTIC' 或 'DIAGNOSTIC+TUNING'）；没有许可时，将该参数设为 'NONE' 并使用 Statspack。报告不能跨越实例重启。超过一小时的范围会被平均化并掩盖短时尖峰；请保持窗口紧凑。","note":"对于 PDB 级 AWR（12.2+），检查 AWR_PDB_AUTOFLUSH_ENABLED=TRUE 以及 AWR_SNAPSHOT_TIME_OFFSET/AWR_PDB_MAX_PARALLEL_SLAVES；在 PDB 内生成的报告只显示该 PDB 的数据。间隔/保留：DBMS_WORKLOAD_REPOSITORY.MODIFY_SNAPSHOT_SETTINGS(interval => 30, retention => 43200)。","tags":["awr","awr report","snapshot","awrrpt","dbms_workload_repository","performance report","diagnostics pack"]},{"id":"awr-diff-report","task":"两个时间段之间的 AWR diff（比较周期）报告","category":"性能","command":"-- Interactive: @?/rdbms/admin/awrddrpt.sql   (RAC: awrgdrpt.sql)\n\n-- From a script: period 1 (good) = <bid1>-<eid1>, period 2 (bad) = <bid2>-<eid2>\nSET LINESIZE 8000 PAGESIZE 0 TRIMSPOOL ON HEADING OFF FEEDBACK OFF\nSPOOL /tmp/awr_diff.txt\nSELECT output\nFROM   TABLE(DBMS_WORKLOAD_REPOSITORY.AWR_DIFF_REPORT_TEXT(\n         dbid1 => (SELECT dbid FROM v$database), inst_num1 => 1,\n         bid1  => <bid1>, eid1 => <eid1>,\n         dbid2 => (SELECT dbid FROM v$database), inst_num2 => 1,\n         bid2  => <bid2>, eid2 => <eid2>));\nSPOOL OFF\n-- HTML: DBMS_WORKLOAD_REPOSITORY.AWR_DIFF_REPORT_HTML","what":"将两个快照范围并排显示，并给出 DB time、等待事件、负载概况和各 SQL 统计信息的百分比差异。直接回答\"昨天还好，今天变慢\"的问题。","when":"升级/补丁、参数变更、发布或统计信息收集后的回退分析；比较两天中相同时段。","caution":"需要 Diagnostics Pack 许可。比较的时间段应长度相同、负载相近，否则差值会有误导性。要比较不同数据库（例如 prod 与 test），请用 awrextr.sql/awrload.sql 迁移 AWR 数据并传入对应的 dbid2。","tags":["awr diff","comparison","compare period","awrddrpt","regression","performance","diagnostics pack"]},{"id":"ash-top-activity","task":"从 ASH 获取最近几分钟的 Top 等待事件和 Top SQL","category":"性能","command":"-- Last 30 min: wait event breakdown (each sample ~1 s of active session time)\nSELECT NVL(event, 'ON CPU') AS event, wait_class,\n       COUNT(*) AS samples,\n       ROUND(RATIO_TO_REPORT(COUNT(*)) OVER () * 100, 1) AS pct\nFROM   v$active_session_history\nWHERE  sample_time > SYSTIMESTAMP - INTERVAL '30' MINUTE\nGROUP  BY NVL(event, 'ON CPU'), wait_class\nORDER  BY samples DESC\nFETCH FIRST 10 ROWS ONLY;\n\n-- Last 30 min: top SQL (with CPU / User I/O breakdown)\nSELECT sql_id, sql_opname,\n       COUNT(*) AS samples,\n       SUM(CASE WHEN session_state = 'ON CPU' THEN 1 ELSE 0 END) AS cpu,\n       SUM(CASE WHEN wait_class = 'User I/O' THEN 1 ELSE 0 END)  AS user_io,\n       ROUND(RATIO_TO_REPORT(COUNT(*)) OVER () * 100, 1) AS pct\nFROM   v$active_session_history\nWHERE  sample_time > SYSTIMESTAMP - INTERVAL '30' MINUTE\n  AND  sql_id IS NOT NULL\nGROUP  BY sql_id, sql_opname\nORDER  BY samples DESC\nFETCH FIRST 10 ROWS ONLY;\n\n-- Older periods: DBA_HIST_ACTIVE_SESS_HISTORY (one sample every 10 s; COUNT(*)*10 = seconds)\n\n-- Ready-made ASH report (last 30 min):\nSET LINESIZE 8000 PAGESIZE 0 TRIMSPOOL ON HEADING OFF FEEDBACK OFF\nSELECT output\nFROM   TABLE(DBMS_WORKLOAD_REPOSITORY.ASH_REPORT_TEXT(\n         l_dbid     => (SELECT dbid FROM v$database),\n         l_inst_num => (SELECT instance_number FROM v$instance),\n         l_btime    => SYSDATE - 30/1440,\n         l_etime    => SYSDATE));\n-- Interactive: @?/rdbms/admin/ashrpt.sql","what":"使用 Active Session History 样本，以百分比显示最近几分钟内哪些等待事件和 SQL 语句消耗了 DB time；无需等待 AWR 快照即可诊断事件。","when":"实时性能下降期间、在 AWR 平均值中消失的短时尖峰，以及查看特定会话或 SQL 在等待什么时。","caution":"查询 V$ACTIVE_SESSION_HISTORY 和 DBA_HIST_ACTIVE_SESS_HISTORY 需要 Diagnostics Pack 许可。ASH 是采样数据：样本数用于估算时间，并非精确测量，极短的 SQL 可能不会出现。在繁忙系统上，内存中的 ASH 缓冲区只能保存几个小时。","tags":["ash","active session history","top wait","top sql","wait","ashrpt","real-time analysis","diagnostics pack"]},{"id":"addm-task-report","task":"运行 ADDM 分析并获取报告","category":"性能","command":"-- Analyze the range between two snapshots (in CDB root):\nVAR tname VARCHAR2(60)\nBEGIN\n  :tname := 'ADDM_<begin_snap>_<end_snap>';\n  DBMS_ADDM.ANALYZE_DB(\n    task_name      => :tname,\n    begin_snapshot => <begin_snap>,\n    end_snapshot   => <end_snap>);\nEND;\n/\n\nSET LONG 1000000 LONGCHUNKSIZE 1000000 LINESIZE 200 PAGESIZE 0\nSELECT DBMS_ADDM.GET_REPORT(task_name => :tname) FROM dual;\n\n-- Findings summary (by impact):\nSET PAGESIZE 100\nSELECT finding_name, type, ROUND(impact/1e6) AS impact_sec, message\nFROM   dba_advisor_findings\nWHERE  task_name = :tname\nORDER  BY impact DESC;\n\n-- Interactive alternative: @?/rdbms/admin/addmrpt.sql\n-- Clean up the task when done:\nEXEC DBMS_ADDM.DELETE(task_name => :tname);","what":"Automatic Database Diagnostic Monitor 分析两个 AWR 快照之间的 DB time，报告瓶颈及其影响（findings）并给出具体建议。","when":"在阅读 AWR 报告前快速获得\"应该看哪里\"的摘要，或在每次快照后自动运行的 ADDM 之外分析自定义范围。","caution":"需要 Diagnostics Pack 许可。不要盲目应用 ADDM 建议（调大参数、运行 SQL Tuning Advisor 等）；请对照 AWR advisory 部分验证内存建议。范围不能跨越重启。","tags":["addm","dbms_addm","advisor","finding","finding","recommendation","diagnostics pack","performance"]},{"id":"sql-monitor-report","task":"获取 SQL Monitor 报告（正在运行或已完成的长 SQL）","category":"性能","command":"-- List monitored SQL (longer than 5 s, parallel, or /*+ MONITOR */):\nSELECT sql_id, status, username, sql_exec_id,\n       TO_CHAR(sql_exec_start,'YYYY-MM-DD HH24:MI:SS') AS exec_start,\n       ROUND(elapsed_time/1e6,1) AS elapsed_s,\n       ROUND(cpu_time/1e6,1)     AS cpu_s,\n       buffer_gets, px_servers_allocated\nFROM   v$sql_monitor\nORDER  BY sql_exec_start DESC\nFETCH FIRST 20 ROWS ONLY;\n\n-- TEXT report (actual rows, time and activity per plan line):\nSET LONG 10000000 LONGCHUNKSIZE 10000000 LINESIZE 300 PAGESIZE 0 TRIMSPOOL ON\nSELECT DBMS_SQLTUNE.REPORT_SQL_MONITOR(\n         sql_id       => '<sql_id>',\n         type         => 'TEXT',\n         report_level => 'ALL') AS report\nFROM   dual;\n\n-- Shareable HTML (interactive in a browser):\nSPOOL /tmp/sqlmon_<sql_id>.html\nSELECT DBMS_SQLTUNE.REPORT_SQL_MONITOR(\n         sql_id => '<sql_id>', type => 'ACTIVE', report_level => 'ALL')\nFROM   dual;\nSPOOL OFF","what":"Real-Time SQL Monitoring 针对长时间运行或并行 SQL 执行计划的每一步，显示实际行数、耗时、内存/temp 使用和 ASH 活动；可在 SQL 仍在运行时观察进度。","when":"判断批处理/报表查询是否卡住以及还剩多少、分析并行查询分布（PX skew），以及找出估算行数（E-Rows）与实际行数之间的差距时。","caution":"需要 Tuning Pack 许可（以 Diagnostics Pack 为前提）。默认只监控消耗 5 s CPU/I-O 或并行运行的 SQL；其他 SQL 请使用 /*+ MONITOR */ hint。非常大的计划（_sqlmon_max_planlines，默认 300）不会被监控。条目只在内存中保留有限时间；在 12c+ 中较早的报告也可从 DBA_HIST_REPORTS 获取。","tags":["sql monitor","report_sql_monitor","v$sql_monitor","real-time","long running query","parallel","tuning pack"]},{"id":"sql-trace-10046-tkprof","task":"为会话启用/禁用 10046 trace 并用 TKPROF 汇总","category":"性能","command":"-- 1) Find the target session and its trace file:\nSELECT s.sid, s.serial#, s.username, s.module, p.tracefile\nFROM   v$session s\n  JOIN v$process p ON p.addr = s.paddr\nWHERE  s.username = 'CUSTOMER_APP';\n\n-- 2) Enable trace (waits + binds = equivalent of level 12):\nBEGIN\n  DBMS_MONITOR.SESSION_TRACE_ENABLE(\n    session_id => <sid>,\n    serial_num => <serial#>,\n    waits      => TRUE,\n    binds      => TRUE,\n    plan_stat  => 'ALL_EXECUTIONS');\nEND;\n/\n-- Verify: SELECT sql_trace, sql_trace_waits, sql_trace_binds FROM v$session WHERE sid = <sid>;\n\n-- 3) Disable once the problem operation completes:\nEXEC DBMS_MONITOR.SESSION_TRACE_DISABLE(session_id => <sid>, serial_num => <serial#>);\n\n-- Alternatives:\n-- Your own session: ALTER SESSION SET EVENTS '10046 trace name context forever, level 12';\n-- Single SQL_ID, all sessions: ALTER SYSTEM SET EVENTS 'sql_trace[SQL: <sql_id>] wait=true, bind=true';\n--   disable: ALTER SYSTEM SET EVENTS 'sql_trace[SQL: <sql_id>] off';\n-- For a connection pool: DBMS_MONITOR.CLIENT_ID_TRACE_ENABLE / SERV_MOD_ACT_TRACE_ENABLE\n\n-- 4) Summarize with TKPROF on the OS (most expensive SQL first):\n-- $ tkprof /u01/app/oracle/diag/rdbms/orcl/ORCL/trace/ORCL_ora_12345.trc /tmp/ora_12345.prf \\\n--     sys=no sort=exeela,fchela,prsela waits=yes aggregate=yes","what":"扩展 SQL trace（10046）将会话执行的每条 SQL 的 parse/execute/fetch 时间、等待事件和绑定值写入 trace 文件；TKPROF 将其转换为按 SQL 汇总的可读报告。","when":"当 AWR/ASH 不够用、必须端到端证明单个用户操作的时间花在哪里时；用于应用与数据库之间的往返（SQL*Net）问题。","caution":"无需许可。trace 文件增长很快，并会给目标会话带来开销；请保持时间短并务必禁用（检查 DBA_ENABLED_TRACES 中的残留）。binds=TRUE 会将敏感数据（身份证号、卡号等）写入 trace 文件。ALTER SESSION SET EVENTS 需要 ALTER SESSION 权限（否则 ORA-01031）。TKPROF 默认不显示绑定值；请保留原始 .trc 文件。","note":"连接池或服务级跟踪会生成多个文件；先用 trcsess 合并（trcsess output=combined.trc clientid=<id> *.trc），再运行 TKPROF。","tags":["10046","trace","tkprof","dbms_monitor","sql trace","wait","bind","session tracing"]},{"id":"xplan-allstats-last","task":"显示带行源统计信息的实际执行计划（ALLSTATS LAST）","category":"性能","command":"-- 1) Collect row source statistics (for this query only):\nSET SERVEROUTPUT OFF\nSELECT /*+ gather_plan_statistics */ customer_id, SUM(amount)\nFROM   customer_app.sales\nWHERE  created_at >= DATE '2026-06-01'\nGROUP  BY customer_id;\n-- or for the whole session: ALTER SESSION SET statistics_level = ALL;\n\n-- 2) Plan of the last cursor run in the same session (E-Rows vs A-Rows):\nSELECT *\nFROM   TABLE(DBMS_XPLAN.DISPLAY_CURSOR(\n         sql_id          => NULL,\n         cursor_child_no => NULL,\n         format          => 'ALLSTATS LAST +PEEKED_BINDS +OUTLINE'));\n\n-- 3) A cursor from another session / from earlier:\nSELECT *\nFROM   TABLE(DBMS_XPLAN.DISPLAY_CURSOR('<sql_id>', NULL, 'ALLSTATS LAST +PEEKED_BINDS'));","what":"EXPLAIN PLAN 只是估算；这里显示的是实际运行的计划，每一步包括 Starts、E-Rows（估算）、A-Rows（实际）、A-Time、Buffers 和内存使用。这是找出基数估算错误从哪一步开始的最直接方法。","when":"调优单条慢 SQL、证明需要统计信息/直方图，以及衡量 hint 或索引变更的效果时。","caution":"无需许可。在 SET SERVEROUTPUT ON 时，sql_id=>NULL 会把 DBMS_OUTPUT 调用显示为最后一个游标；请先将其设为 OFF。不要在系统范围启用 statistics_level=ALL；它会带来明显的 CPU 开销。hint 会改变 SQL 文本，因此会产生新的 sql_id。用户需要 V$SQL_PLAN_STATISTICS_ALL 的读取权限（SELECT_CATALOG_ROLE）。","tags":["dbms_xplan","display_cursor","allstats last","gather_plan_statistics","actual plan","execution plan","a-rows","cardinality"]},{"id":"sql-tuning-advisor-task","task":"创建并运行 SQL Tuning Advisor 任务并阅读其建议","category":"性能","command":"-- 1) Create and run a task for a SQL in the cursor cache:\nVAR task VARCHAR2(64)\nBEGIN\n  :task := DBMS_SQLTUNE.CREATE_TUNING_TASK(\n             sql_id      => '<sql_id>',\n             scope       => DBMS_SQLTUNE.SCOPE_COMPREHENSIVE,\n             time_limit  => 300,\n             task_name   => 'STA_<sql_id>',\n             description => 'Slow report query');\n  DBMS_SQLTUNE.EXECUTE_TUNING_TASK(task_name => :task);\nEND;\n/\n-- For a historical SQL in AWR: begin_snap => <b>, end_snap => <e>, sql_id => '<sql_id>'\n\n-- 2) Status and report:\nSELECT task_name, status FROM dba_advisor_log WHERE task_name = :task;\n\nSET LONG 1000000 LONGCHUNKSIZE 1000000 LINESIZE 200 PAGESIZE 0\nSELECT DBMS_SQLTUNE.REPORT_TUNING_TASK(:task) FROM dual;\n\n-- 3) Get the recommendations as an executable script:\nSELECT DBMS_SQLTUNE.SCRIPT_TUNING_TASK(:task) FROM dual;\n\n-- 4) When done:\nEXEC DBMS_SQLTUNE.DROP_TUNING_TASK(task_name => :task);","what":"以扩展的\"调优模式\"针对该 SQL 运行优化器，给出缺失统计信息、新索引、SQL profile、替代计划和 SQL 重构方面的建议；通过测试执行验证 profile 建议。","when":"当单条问题 SQL 需要快速获得第二意见，或在不修改代码的情况下为计划回归寻找 SQL profile 修复方案时。","caution":"需要 Tuning Pack 许可。COMPREHENSIVE 范围可能会为测试而执行该 SQL（DML 只执行查询部分）；在生产环境中请设置 time_limit。索引建议不考虑其余负载；请单独评估 DML 成本。用户需要 ADVISOR 权限。","tags":["sql tuning advisor","dbms_sqltune","tuning task","recommendation","sql profile","index recommendation","tuning pack"]},{"id":"sql-profile-accept","task":"接受、禁用和删除 SQL profile","category":"性能","command":"-- 1) Accept the profile recommended by the tuning task:\nDECLARE\n  l_name VARCHAR2(128);\nBEGIN\n  l_name := DBMS_SQLTUNE.ACCEPT_SQL_PROFILE(\n              task_name   => '<task_name>',\n              name        => 'PROF_<sql_id>',\n              description => 'STA recommendation, CHG-1234',\n              force_match => TRUE,     -- also matches statements differing only in literals\n              replace     => TRUE);\n  DBMS_OUTPUT.PUT_LINE('Profile: ' || l_name);\nEND;\n/\n\n-- 2) Profiles and the cursors using them:\nSELECT name, category, status, force_matching,\n       TO_CHAR(created,'YYYY-MM-DD HH24:MI') AS created\nFROM   dba_sql_profiles\nORDER  BY created DESC;\n\nSELECT sql_id, child_number, plan_hash_value, sql_profile\nFROM   v$sql\nWHERE  sql_profile IS NOT NULL;\n\n-- 3) Temporarily disable / drop:\nEXEC DBMS_SQLTUNE.ALTER_SQL_PROFILE(name => 'PROF_<sql_id>', attribute_name => 'STATUS', value => 'DISABLED');\nEXEC DBMS_SQLTUNE.DROP_SQL_PROFILE(name => 'PROF_<sql_id>', ignore => TRUE);","what":"SQL profile 为优化器提供额外的基数/选择性修正（OPT_ESTIMATE 等），使其在不改变 SQL 文本的情况下生成更好的计划；使用 FORCE_MATCH 时，它也适用于仅字面量不同的语句。","when":"当 SQL 无法修改（套装应用）、STA 推荐了经过验证的改进，或作为计划回归的快速修复时。","caution":"需要 Tuning Pack 许可。profile 不会\"锁定\"计划；它修正估算，因此数据分布变化时计划仍可能改变。如果必须保证计划稳定性，请使用 SQL plan baseline。两者同时存在时，优化器在 baseline 的已接受计划中进行选择。接受之前务必阅读验证结果（报告中的 'Validation results' 部分）。","tags":["sql profile","accept_sql_profile","dbms_sqltune","force_match","plan fix","dba_sql_profiles","tuning pack"]},{"id":"spm-load-from-cursor-cache","task":"从游标缓存将良好计划加载为 SQL plan baseline","category":"性能","command":"-- 1) Compare the SQL's plans and their performance:\nSELECT sql_id, child_number, plan_hash_value, executions,\n       ROUND(elapsed_time/1e3/NULLIF(executions,0),1) AS ms_per_exec,\n       sql_plan_baseline\nFROM   v$sql\nWHERE  sql_id = '<sql_id>';\n\n-- 2) Load the chosen plan as a baseline:\nSET SERVEROUTPUT ON\nDECLARE\n  n PLS_INTEGER;\nBEGIN\n  n := DBMS_SPM.LOAD_PLANS_FROM_CURSOR_CACHE(\n         sql_id          => '<sql_id>',\n         plan_hash_value => <plan_hash_value>,\n         fixed           => 'NO',\n         enabled         => 'YES');\n  DBMS_OUTPUT.PUT_LINE(n || ' plan(s) loaded');\nEND;\n/\n\n-- 3) Verify the baseline:\nSELECT sql_handle, plan_name, origin, enabled, accepted, fixed, reproduced,\n       TO_CHAR(last_executed,'YYYY-MM-DD HH24:MI') AS last_exec\nFROM   dba_sql_plan_baselines\nWHERE  signature = (SELECT exact_matching_signature FROM v$sql\n                    WHERE sql_id = '<sql_id>' AND ROWNUM = 1);\n-- On subsequent executions v$sql.sql_plan_baseline should be populated.","what":"使用 SQL Plan Management 将所选计划设为 ACCEPTED baseline；此后优化器只使用已接受的 baseline 计划，新发现的计划会搁置直到被演进（evolve）。","when":"在计划回归时回到已知的良好计划，以及在升级或统计信息变更前保护关键 SQL 的计划。","caution":"Enterprise Edition 上无需额外许可（在 SE2 上每条 SQL 限一个 baseline 计划）。需要 ADMINISTER SQL MANAGEMENT OBJECT。baseline 仅适用于完全相同的 SQL 文本（signature）；对使用字面量的 SQL 无效。如果计划使用的索引被删除，计划将无法重现（REPRODUCED=NO），优化器会退回基于成本的选择。OPTIMIZER_USE_SQL_PLAN_BASELINES 必须为 TRUE（默认）。","note":"如果良好计划已不在游标缓存中，可从 AWR 加载：12.2+ 使用 DBMS_SPM.LOAD_PLANS_FROM_AWR(begin_snap, end_snap, basic_filter => q'[sql_id='<sql_id>']')；更早的版本通过 SQL Tuning Set 使用 LOAD_PLANS_FROM_SQLSET。","tags":["spm","sql plan baseline","dbms_spm","load_plans_from_cursor_cache","plan pinning","plan regression","plan_hash_value"]},{"id":"spm-evolve-baseline","task":"演进未接受的 SQL plan baseline 计划","category":"性能","command":"-- 1) Plans waiting for acceptance:\nSELECT sql_handle, plan_name, origin, enabled, accepted, fixed,\n       TO_CHAR(created,'YYYY-MM-DD HH24:MI') AS created\nFROM   dba_sql_plan_baselines\nWHERE  accepted = 'NO'\nORDER  BY created DESC;\n\n-- 2) Evolve task: test-executes the candidate plan against the accepted plan\nVAR tname VARCHAR2(128)\nVAR ename VARCHAR2(128)\nBEGIN\n  :tname := DBMS_SPM.CREATE_EVOLVE_TASK(\n              sql_handle => '<sql_handle>',\n              time_limit => 600,\n              task_name  => 'EVOLVE_<sql_handle>');\n  :ename := DBMS_SPM.EXECUTE_EVOLVE_TASK(task_name => :tname);\nEND;\n/\n\nSET LONG 1000000 LONGCHUNKSIZE 1000000 LINESIZE 200 PAGESIZE 0\nSELECT DBMS_SPM.REPORT_EVOLVE_TASK(\n         task_name => :tname, execution_name => :ename,\n         type => 'TEXT', level => 'TYPICAL', section => 'ALL')\nFROM   dual;\n\n-- 3) If the report has recommendations (plans proven better), accept them:\nSET SERVEROUTPUT ON\nDECLARE\n  n PLS_INTEGER;\nBEGIN\n  n := DBMS_SPM.IMPLEMENT_EVOLVE_TASK(task_name => :tname);\n  DBMS_OUTPUT.PUT_LINE(n || ' plan(s) accepted');\nEND;\n/\nEXEC DBMS_SPM.DROP_EVOLVE_TASK(task_name => :tname);","what":"存在 baseline 时，优化器发现的新计划以 ACCEPTED=NO 存储。evolve 任务会将这些候选计划与已接受计划进行测试执行对比，只接受明显更优的计划。","when":"对于已有 baseline 的 SQL，当新索引或新统计信息可能产生了更好的计划时；无需等待自动的 SYS_AUTO_SPM_EVOLVE_TASK（12c+）而手动决定时。","caution":"需要 Enterprise Edition。evolve 会实际执行 SQL（DML 只执行查询部分）；对重型 SQL 请设置 time_limit 并避开高峰时段。需要 ADMINISTER SQL MANAGEMENT OBJECT 和 ADVISOR。以 SYS 解析的 SQL 无法演进（ORA-16963）；请以应用用户身份运行。","since":"Oracle 12c","note":"12c 之前的 DBMS_SPM.EVOLVE_SQL_PLAN_BASELINE 函数已弃用；12c+ 使用基于任务的 API（CREATE/EXECUTE/IMPLEMENT_EVOLVE_TASK）。自动任务行为：DBMS_SPM.SET_EVOLVE_TASK_PARAMETER('SYS_AUTO_SPM_EVOLVE_TASK', 'ACCEPT_PLANS', 'FALSE')。","tags":["spm","evolve","create_evolve_task","baseline","plan acceptance","dbms_spm","accepted"]},{"id":"spm-alter-drop-baseline","task":"固定、禁用或删除 SQL plan baseline 计划","category":"性能","command":"-- Show the plan content:\nSELECT *\nFROM   TABLE(DBMS_XPLAN.DISPLAY_SQL_PLAN_BASELINE(\n         sql_handle => '<sql_handle>',\n         plan_name  => '<plan_name>',\n         format     => 'TYPICAL'));\n\nSET SERVEROUTPUT ON\nDECLARE\n  n PLS_INTEGER;\nBEGIN\n  -- FIXED: this plan is preferred, new plans are not evolved automatically\n  n := DBMS_SPM.ALTER_SQL_PLAN_BASELINE(\n         sql_handle => '<sql_handle>', plan_name => '<plan_name>',\n         attribute_name => 'fixed', attribute_value => 'YES');\n\n  -- Take the bad plan out of use (without dropping it):\n  n := DBMS_SPM.ALTER_SQL_PLAN_BASELINE(\n         sql_handle => '<sql_handle>', plan_name => '<bad_plan_name>',\n         attribute_name => 'enabled', attribute_value => 'NO');\nEND;\n/\n\n-- Drop one plan or the SQL's entire baseline:\nDECLARE\n  n PLS_INTEGER;\nBEGIN\n  n := DBMS_SPM.DROP_SQL_PLAN_BASELINE(sql_handle => '<sql_handle>', plan_name => '<plan_name>');\n  -- without plan_name, all plans of the SQL are dropped:\n  -- n := DBMS_SPM.DROP_SQL_PLAN_BASELINE(sql_handle => '<sql_handle>');\n  DBMS_OUTPUT.PUT_LINE(n || ' plan(s) dropped');\nEND;\n/","what":"管理 baseline 计划属性：FIXED 计划优先于其他已接受计划，并停止对新计划的自动演进；ENABLED=NO 禁用计划但不删除；DROP 将其永久删除。","when":"为关键 SQL 牢固锁定计划、禁用误接受的坏计划，或清理不再需要的 baseline。","caution":"需要 Enterprise Edition。FIXED 计划可能随数据增长而劣化，并阻止优化器使用更好的计划；请定期审查。所有计划均为 ENABLED=NO 的 SQL 将被自由优化。DROP 不可逆；请先用 DBMS_SPM.PACK_STGTAB_BASELINE 备份到暂存表。","tags":["spm","baseline","fixed","alter_sql_plan_baseline","drop_sql_plan_baseline","disable plan","display_sql_plan_baseline"]},{"id":"bind-sensitivity-acs-check","task":"检查绑定变量窥探和自适应游标共享状态","category":"性能","command":"-- 1) Child cursors: bind-sensitive / bind-aware / shareable?\nSELECT sql_id, child_number, executions, plan_hash_value,\n       is_bind_sensitive, is_bind_aware, is_shareable,\n       ROUND(buffer_gets/NULLIF(executions,0)) AS gets_per_exec\nFROM   v$sql\nWHERE  sql_id = '<sql_id>'\nORDER  BY child_number;\n\n-- 2) ACS histogram and selectivity ranges:\nSELECT child_number, bucket_id, count\nFROM   v$sql_cs_histogram\nWHERE  sql_id = '<sql_id>'\nORDER  BY child_number, bucket_id;\n\nSELECT child_number, predicate, range_id, low, high\nFROM   v$sql_cs_selectivity\nWHERE  sql_id = '<sql_id>';\n\n-- 3) Peeked / captured bind values:\nSELECT child_number, name, position, datatype_string, value_string,\n       TO_CHAR(last_captured,'YYYY-MM-DD HH24:MI:SS') AS captured\nFROM   v$sql_bind_capture\nWHERE  sql_id = '<sql_id>'\nORDER  BY child_number, position;\n\nSELECT * FROM TABLE(DBMS_XPLAN.DISPLAY_CURSOR('<sql_id>', NULL, 'TYPICAL +PEEKED_BINDS'));\n\n-- 4) Why was a new child created?\nSELECT child_number, bind_mismatch, bind_equiv_failure,\n       optimizer_mismatch, load_optimizer_stats\nFROM   v$sql_shared_cursor\nWHERE  sql_id = '<sql_id>';","what":"优化器在首次硬解析时窥探绑定值来选择计划。自适应游标共享可在有直方图的列上按绑定值创建不同的子游标/计划；这些查询显示游标是否为 bind-sensitive/aware、窥探了哪个值，以及创建子游标的原因。","when":"用于\"同一条 SQL 时快时慢\"的投诉、数据分布倾斜，以及子游标数量激增（high version count）时。","caution":"无需许可；只读。V$SQL_BIND_CAPTURE 是采样数据（默认约每 15 分钟一次），且只保存 WHERE/HAVING 中的绑定变量；并非每次执行的值。ACS 只有在几次糟糕的执行之后才会使游标变为 bind-aware。对于关键 SQL，将多个计划加载到 SQL plan baseline 比依赖 ACS 更确定。","since":"Oracle 11g","note":"自适应游标共享在 11g 中引入。分析 high version count 时，V$SQL_SHARED_CURSOR 的 REASON 列（XML）给出详细原因。","tags":["bind peeking","adaptive cursor sharing","acs","bind variable","v$sql_bind_capture","child cursor","version count"]},{"id":"top-sql-awr-history","task":"查找过去某段时间内按耗时/CPU 排序的 Top SQL（AWR + V$SQLAREA）","category":"性能","command":"-- AWR: top 20 by elapsed time within a snapshot range (per plan)\nSELECT *\nFROM  (SELECT st.sql_id,\n              st.plan_hash_value,\n              SUM(st.executions_delta)                       AS execs,\n              ROUND(SUM(st.elapsed_time_delta)/1e6, 1)       AS elapsed_s,\n              ROUND(SUM(st.cpu_time_delta)/1e6, 1)           AS cpu_s,\n              ROUND(SUM(st.iowait_delta)/1e6, 1)             AS io_wait_s,\n              SUM(st.buffer_gets_delta)                      AS buffer_gets,\n              ROUND(SUM(st.elapsed_time_delta)/1e3\n                    / NULLIF(SUM(st.executions_delta),0), 2) AS ms_per_exec,\n              MAX(st.parsing_schema_name)                    AS parsing_schema\n       FROM   dba_hist_sqlstat st\n       WHERE  st.dbid = (SELECT dbid FROM v$database)\n         AND  st.snap_id BETWEEN <begin_snap> AND <end_snap>\n       GROUP  BY st.sql_id, st.plan_hash_value\n       ORDER  BY elapsed_s DESC)\nWHERE ROWNUM <= 20;\n-- For CPU ranking: ORDER BY cpu_s DESC\n-- SQL text: SELECT DBMS_LOB.SUBSTR(sql_text, 4000, 1) FROM dba_hist_sqltext WHERE sql_id = '<sql_id>';\n-- Plan history of a single SQL: @?/rdbms/admin/awrsqrpt.sql\n\n-- Current (since instance startup, still in cache) by CPU:\nSELECT sql_id,\n       ROUND(cpu_time/1e6,1)     AS cpu_s,\n       ROUND(elapsed_time/1e6,1) AS elapsed_s,\n       executions, buffer_gets, disk_reads,\n       ROUND(cpu_time/1e3/NULLIF(executions,0),2) AS cpu_ms_per_exec,\n       parsing_schema_name,\n       SUBSTR(sql_text,1,80) AS sql_text\nFROM   v$sqlarea\nWHERE  parsing_schema_name NOT IN ('SYS','SYSTEM')\nORDER  BY cpu_time DESC\nFETCH FIRST 20 ROWS ONLY;","what":"汇总 DBA_HIST_SQLSTAT 中的 *_DELTA 列，列出某快照范围内开销最大的 SQL，并按 plan_hash_value 细分，从而也能发现同一 SQL 的计划变化。V$SQLAREA 查询给出仍在缓存中的 SQL 的累计值。","when":"用于回答\"昨晚 02:00 到 04:00 之间是什么压垮了系统？\"、检测计划变化（相同 sql_id，不同 plan_hash_value），以及建立调优候选清单。","caution":"查询 DBA_HIST_* 需要 Diagnostics Pack 许可；V$SQLAREA 不需要。AWR 只保留 Top SQL（默认 top-N），因此小而极其频繁的语句可能缺失。游标老化淘汰后 V$SQLAREA 的值会丢失。差值在重启后仍然正确，但基于缓存的查询会从零开始。","tags":["top sql","dba_hist_sqlstat","v$sqlarea","elapsed","cpu","most expensive query","plan change","diagnostics pack"]},{"id":"stats-stale-prefs","task":"查找过期统计信息并设置表级统计信息偏好（prefs）","category":"统计信息","command":"-- 1) Flush DML counters to the dictionary and list stale objects:\nEXEC DBMS_STATS.FLUSH_DATABASE_MONITORING_INFO;\n\nSELECT owner, table_name, partition_name, num_rows,\n       TO_CHAR(last_analyzed,'YYYY-MM-DD HH24:MI') AS last_analyzed,\n       stale_stats, stattype_locked\nFROM   dba_tab_statistics\nWHERE  owner = 'CUSTOMER_APP'\n  AND  (stale_stats = 'YES' OR last_analyzed IS NULL)\nORDER  BY table_name, partition_name;\n\nSELECT table_name, partition_name, inserts, updates, deletes, truncated\nFROM   dba_tab_modifications\nWHERE  table_owner = 'CUSTOMER_APP';\n\n-- 2) Per-table preferences (used by the auto stats job and manual GATHER calls):\nEXEC DBMS_STATS.SET_TABLE_PREFS('CUSTOMER_APP', 'SALES', 'STALE_PERCENT', '5');\nEXEC DBMS_STATS.SET_TABLE_PREFS('CUSTOMER_APP', 'SALES', 'METHOD_OPT', 'FOR ALL COLUMNS SIZE AUTO FOR COLUMNS SIZE 254 CUSTOMER_ID');\nEXEC DBMS_STATS.SET_TABLE_PREFS('CUSTOMER_APP', 'SALES', 'DEGREE', '4');\n\nSELECT DBMS_STATS.GET_PREFS('STALE_PERCENT', 'CUSTOMER_APP', 'SALES') AS stale_pct FROM dual;\nSELECT table_name, preference_name, preference_value\nFROM   dba_tab_stat_prefs WHERE owner = 'CUSTOMER_APP';\n\n-- 3) Gather only stale objects:\nEXEC DBMS_STATS.GATHER_SCHEMA_STATS(ownname => 'CUSTOMER_APP', options => 'GATHER STALE');\n-- Revert a preference: EXEC DBMS_STATS.DELETE_TABLE_PREFS('CUSTOMER_APP', 'SALES', 'STALE_PERCENT');","what":"当变更比例超过 STALE_PERCENT（默认 10%）时统计信息变为过期。偏好会持久保存表级的采样、直方图、并行度和过期阈值，使夜间自动统计信息任务应用相同的规则。","when":"当 10% 阈值在大表上触发太晚、特定列必须始终收集直方图，以及希望在数据字典而不是脚本中定义收集策略时。","caution":"无需许可。手动 GATHER 调用中的显式参数会覆盖偏好。修改 METHOD_OPT 会增加或删除直方图并可能改变计划。DBA_TAB_MODIFICATIONS 只有在 flush 之后才是最新的（大约每 15 分钟自动 flush 一次）。","tags":["stale stats","stale_percent","set_table_prefs","dba_tab_statistics","dba_tab_modifications","statistics preference","gather stale"]},{"id":"stats-lock-unlock","task":"锁定和解锁表/schema 统计信息","category":"统计信息","command":"-- Lock (the auto stats job and normal GATHER calls no longer touch them):\nEXEC DBMS_STATS.LOCK_TABLE_STATS(ownname => 'CUSTOMER_APP', tabname => 'ORDERS');\nEXEC DBMS_STATS.LOCK_SCHEMA_STATS(ownname => 'CUSTOMER_APP');\n-- A single partition only: DBMS_STATS.LOCK_PARTITION_STATS('CUSTOMER_APP', 'SALE_PART', 'P2026_H1');\n\n-- Locked objects:\nSELECT owner, table_name, partition_name, stattype_locked\nFROM   dba_tab_statistics\nWHERE  owner = 'CUSTOMER_APP' AND stattype_locked IS NOT NULL;\n\n-- Deliberate gathering while locked (instead of ORA-20005):\nEXEC DBMS_STATS.GATHER_TABLE_STATS('CUSTOMER_APP', 'ORDERS', force => TRUE);\n\n-- Unlock:\nEXEC DBMS_STATS.UNLOCK_SCHEMA_STATS(ownname => 'CUSTOMER_APP');\nEXEC DBMS_STATS.UNLOCK_TABLE_STATS(ownname => 'CUSTOMER_APP', tabname => 'ORDERS');","what":"锁定统计信息防止变更；自动任务会跳过已锁定的对象，表级 GATHER 调用会报 ORA-20005。force=>TRUE 可在不解除锁定的情况下收集。","when":"用于在白天被清空并重新填充的暂存/易变表上保留有代表性的统计信息、保护关键表的计划不受统计信息变化影响，或保留用 DBMS_STATS.SET_TABLE_STATS 设置的值。","caution":"无需许可。被锁定的统计信息会随时间偏离实际，导致基数错误；请定期报告已锁定的对象。schema 级锁定不覆盖之后加入该 schema 的表。在已锁定的表上，CREATE/REBUILD INDEX 也不会自动收集索引统计信息。","tags":["lock stats","lock statistics","unlock_table_stats","lock_schema_stats","ora-20005","stattype_locked","dbms_stats"]},{"id":"stats-restore-history","task":"将统计信息恢复到过去某个时间点","category":"统计信息","command":"-- 1) Retention and the oldest point you can return to:\nSELECT DBMS_STATS.GET_STATS_HISTORY_RETENTION    AS retention_days,\n       DBMS_STATS.GET_STATS_HISTORY_AVAILABILITY AS oldest_available\nFROM   dual;\n\n-- 2) Statistics history of the table:\nSELECT table_name, partition_name,\n       TO_CHAR(stats_update_time,'YYYY-MM-DD HH24:MI:SS TZH:TZM') AS stats_update_time\nFROM   dba_tab_stats_history\nWHERE  owner = 'CUSTOMER_APP' AND table_name = 'SALES'\nORDER  BY stats_update_time DESC;\n\n-- 3) Return to the point before the plan regressed:\nBEGIN\n  DBMS_STATS.RESTORE_TABLE_STATS(\n    ownname         => 'CUSTOMER_APP',\n    tabname         => 'SALES',\n    as_of_timestamp => TIMESTAMP '2026-10-07 22:00:00 +03:00',\n    no_invalidate   => FALSE);\nEND;\n/\n-- Whole schema: DBMS_STATS.RESTORE_SCHEMA_STATS('CUSTOMER_APP', <timestamp>)\n\n-- Change the retention (days):\n-- EXEC DBMS_STATS.ALTER_STATS_HISTORY_RETENTION(31);","what":"DBMS_STATS 在每次收集前将之前的统计信息保存在 SYSAUX 中（默认 31 天）。RESTORE_* 过程将表/schema 统计信息恢复到指定时间的状态；使用 no_invalidate=>FALSE 时游标会立即重新解析。","when":"作为夜间统计信息收集后出现计划回归时最快的回滚手段，以及测试变化是否由统计信息引起。","caution":"无需许可。已锁定的统计信息无法恢复（先解锁）。恢复后下一次自动收集会重新带来新的统计信息；如需永久修复，请设置表偏好、锁定统计信息或使用 SQL plan baseline。保留期过长会使 SYSAUX 增长（WRI$_OPTSTAT_* 表）。","tags":["restore stats","restore statistics","restore_table_stats","dba_tab_stats_history","stats history","plan regression","dbms_stats"]},{"id":"stats-pending-publish","task":"以 pending 方式收集统计信息、测试后发布","category":"统计信息","command":"-- 1) Disable publishing for the table (new statistics go to pending):\nEXEC DBMS_STATS.SET_TABLE_PREFS('CUSTOMER_APP', 'ORDERS', 'PUBLISH', 'FALSE');\nEXEC DBMS_STATS.GATHER_TABLE_STATS('CUSTOMER_APP', 'ORDERS');\n\n-- 2) Compare pending and published values:\nSELECT owner, table_name, partition_name, num_rows,\n       TO_CHAR(last_analyzed,'YYYY-MM-DD HH24:MI') AS last_analyzed\nFROM   dba_tab_pending_stats\nWHERE  owner = 'CUSTOMER_APP';\nSELECT num_rows, last_analyzed FROM dba_tables\nWHERE  owner = 'CUSTOMER_APP' AND table_name = 'ORDERS';\n\n-- 3) Try plans with pending statistics in this session only:\nALTER SESSION SET optimizer_use_pending_statistics = TRUE;\nEXPLAIN PLAN FOR SELECT COUNT(*) FROM customer_app.orders WHERE status = 7;\nSELECT * FROM TABLE(DBMS_XPLAN.DISPLAY(format => 'BASIC +ROWS'));\nALTER SESSION SET optimizer_use_pending_statistics = FALSE;\n\n-- 4a) If good, publish:\nEXEC DBMS_STATS.PUBLISH_PENDING_STATS('CUSTOMER_APP', 'ORDERS', no_invalidate => FALSE);\n-- 4b) If bad, discard:\n-- EXEC DBMS_STATS.DELETE_PENDING_STATS('CUSTOMER_APP', 'ORDERS');\n\n-- 5) Return to normal behavior:\nEXEC DBMS_STATS.SET_TABLE_PREFS('CUSTOMER_APP', 'ORDERS', 'PUBLISH', 'TRUE');","what":"使用 PUBLISH=FALSE 偏好时，收集的统计信息写入 pending 区域而不是数据字典，因此其他会话不受影响。在测试会话中通过 optimizer_use_pending_statistics 验证计划，然后用一条命令发布或丢弃。","when":"在生产环境中无风险地测试关键 OLTP 表刷新统计信息对计划的影响，以及大批量数据加载后的受控切换。","caution":"无需许可。只要 PUBLISH=FALSE 仍然有效，自动任务收集的统计信息也会堆积为 pending 且永远不会发布；记得将偏好改回 TRUE。EXPLAIN PLAN 只是估算；尽可能运行查询并检查 DISPLAY_CURSOR。","since":"Oracle 11g","tags":["pending stats","publish","publish_pending_stats","optimizer_use_pending_statistics","statistics testing","dbms_stats","dba_tab_pending_stats"]},{"id":"stats-incremental-partitioned","task":"为分区表配置增量统计信息","category":"统计信息","command":"-- 1) Enable incremental mode (global statistics are derived from partition synopses):\nEXEC DBMS_STATS.SET_TABLE_PREFS('CUSTOMER_APP', 'SALE_PART', 'INCREMENTAL', 'TRUE');\nEXEC DBMS_STATS.SET_TABLE_PREFS('CUSTOMER_APP', 'SALE_PART', 'INCREMENTAL_STALENESS', 'USE_STALE_PERCENT');\nEXEC DBMS_STATS.SET_TABLE_PREFS('CUSTOMER_APP', 'SALE_PART', 'GRANULARITY', 'AUTO');\n\n-- 2) The first gather scans everything and builds synopses; later ones read only changed partitions:\nEXEC DBMS_STATS.GATHER_TABLE_STATS(ownname => 'CUSTOMER_APP', tabname => 'SALE_PART');\n\n-- 3) Verify:\nSELECT DBMS_STATS.GET_PREFS('INCREMENTAL', 'CUSTOMER_APP', 'SALE_PART') AS incremental FROM dual;\nSELECT partition_name, num_rows,\n       TO_CHAR(last_analyzed,'YYYY-MM-DD HH24:MI:SS') AS last_analyzed, stale_stats\nFROM   dba_tab_statistics\nWHERE  owner = 'CUSTOMER_APP' AND table_name = 'SALE_PART'\nORDER  BY partition_position NULLS FIRST;\n\n-- 4) Pre-build synopses on a staging table to be loaded via exchange (12c+):\n-- EXEC DBMS_STATS.SET_TABLE_PREFS('CUSTOMER_APP', 'SALE_STAGE', 'INCREMENTAL', 'TRUE');\n-- EXEC DBMS_STATS.SET_TABLE_PREFS('CUSTOMER_APP', 'SALE_STAGE', 'INCREMENTAL_LEVEL', 'TABLE');\n-- EXEC DBMS_STATS.GATHER_TABLE_STATS('CUSTOMER_APP', 'SALE_STAGE');","what":"在增量模式下，每个分区存储一个 synopsis；全局（表级）统计信息，尤其是 NDV，通过合并 synopsis 计算，无需重新扫描整个表。只重新读取发生变化的分区。","when":"用于大型（TB 级）range/interval 分区表，新数据只落入最近分区，且全局统计信息收集超出维护窗口时。","caution":"本身无需许可，但前提是已需要 Partitioning 选件（EE 额外付费）。synopsis 会占用 SYSAUX 空间（12.2+ 的 HyperLogLog 格式小得多）。首次收集为全量扫描；请在维护窗口中开始。将 ESTIMATE_PERCENT 设为 AUTO_SAMPLE_SIZE 以外的任何值都会禁用增量模式。","since":"Oracle 11g","note":"INCREMENTAL_STALENESS 和 INCREMENTAL_LEVEL 偏好在 12c 中加入；在 12.2+ 中 synopsis 格式由 APPROXIMATE_NDV_ALGORITHM（HYPERLOGLOG）控制。","tags":["incremental stats","synopsis","partition statistics","global stats","incremental_staleness","dbms_stats","large table"]},{"id":"partition-split-merge-online","task":"在线拆分和合并分区","category":"分区","command":"-- SPLIT: split a range partition in two, keep indexes usable\nALTER TABLE customer_app.sale_part\n  SPLIT PARTITION p2026_h2 AT (DATE '2026-10-01')\n  INTO (PARTITION p2026_q3, PARTITION p2026_q4)\n  UPDATE INDEXES ONLINE;\n\n-- MERGE: merge two adjacent partitions\nALTER TABLE customer_app.sale_part\n  MERGE PARTITIONS p2026_q3, p2026_q4 INTO PARTITION p2026_h2\n  UPDATE INDEXES ONLINE;\n\n-- ADD: new partition at the end (when there is no MAXVALUE)\nALTER TABLE customer_app.sale_part\n  ADD PARTITION p2027 VALUES LESS THAN (DATE '2028-01-01');\n\n-- Check: partition bounds and UNUSABLE index partitions\nSELECT partition_name, partition_position, high_value, num_rows\nFROM   dba_tab_partitions\nWHERE  table_owner = 'CUSTOMER_APP' AND table_name = 'SALE_PART'\nORDER  BY partition_position;\n\nSELECT index_name, partition_name, status\nFROM   dba_ind_partitions\nWHERE  index_owner = 'CUSTOMER_APP' AND status <> 'USABLE';","what":"SPLIT 按边界值将一个分区拆分为两个（12c+：拆分为多个）；MERGE 合并相邻分区。使用 ONLINE 时，操作期间 DML 可继续进行；使用 UPDATE INDEXES 时，本地索引和全局索引不会变为 UNUSABLE。","when":"用于拆分已变大的 MAXVALUE/\"其他\"分区、在归档前合并较小的旧分区，或改变分区方案（按月改为按季度）。","caution":"需要 Partitioning 选件（EE 额外付费）；ONLINE 需要 Enterprise Edition。SPLIT/MERGE 会物理重写数据：在大分区上很慢，并产生 redo/undo 和临时空间消耗。不使用 ONLINE 时，操作期间表会被锁定禁止 DML；不使用 UPDATE INDEXES 时，全局索引会变为 UNUSABLE。拆分空分区很快（fast split）。","since":"Oracle 12.2","note":"ONLINE SPLIT 在 12.2 中引入，ONLINE MERGE 在 18c 中引入；两者在 19c 中均可用。在更早的版本中去掉 ONLINE 关键字，并在维护窗口中执行操作。","tags":["split partition","merge partition","add partition","split a partition","merge partitions","update indexes","online","partitioning"]},{"id":"partition-exchange","task":"使用 exchange partition 批量加载或归档数据","category":"分区","command":"-- 1) Staging table that exactly matches the target (12.2+):\nCREATE TABLE customer_app.sale_stage\n  FOR EXCHANGE WITH TABLE customer_app.sale_part;\n\n-- 2) Load the staging table and build indexes matching the local indexes:\nINSERT /*+ APPEND */ INTO customer_app.sale_stage\nSELECT * FROM customer_app.sale_load WHERE created_at >= DATE '2027-01-01';\nCOMMIT;\nCREATE INDEX customer_app.sale_stage_customer_ix ON customer_app.sale_stage (customer_id);\n\n-- 3) Swap (dictionary-only operation, no data movement):\nALTER TABLE customer_app.sale_part\n  EXCHANGE PARTITION p2027 WITH TABLE customer_app.sale_stage\n  INCLUDING INDEXES WITHOUT VALIDATION\n  UPDATE GLOBAL INDEXES;\n\n-- 4) Verify:\nSELECT COUNT(*) FROM customer_app.sale_part PARTITION (p2027);\nSELECT index_name, partition_name, status\nFROM   dba_ind_partitions\nWHERE  index_owner = 'CUSTOMER_APP' AND status <> 'USABLE';","what":"在数据字典中将一个分区的段与一个普通表的段互换；数百万行可在几秒内\"进入\"表，或\"离开\"表进行归档。FOR EXCHANGE WITH TABLE 会创建一个保证列和隐藏列兼容的暂存表。","when":"用于 DWH/ETL 批量加载、将旧分区移出到归档表，以及在加载期间不影响在线表的情况下准备索引和统计信息。","caution":"需要 Partitioning 选件（EE 额外付费）。WITHOUT VALIDATION 不检查暂存表中的行是否符合分区边界；错误的数据会留在错误的分区中，查询可能返回错误结果；不确定时请使用 WITH VALIDATION（全量扫描）。不使用 UPDATE GLOBAL INDEXES 时，全局索引会变为 UNUSABLE。两侧的唯一/PK 约束状态必须一致。","note":"CREATE TABLE ... FOR EXCHANGE WITH TABLE 在 12.2 中引入；在此之前，用 CREATE TABLE ... AS SELECT * FROM <table> WHERE 1=0 创建暂存表，并手动检查隐藏列/虚拟列的差异。","tags":["exchange partition","staging","for exchange with table","bulk load","archive","update global indexes","partitioning"]},{"id":"partition-drop-truncate-gidx","task":"截断/删除分区并保持全局索引可用","category":"分区","command":"-- Empty a partition / remove a partition (global indexes stay maintained):\nALTER TABLE customer_app.sale_part TRUNCATE PARTITION p2025 UPDATE INDEXES;\nALTER TABLE customer_app.sale_part DROP PARTITION p2025 UPDATE INDEXES;\n\n-- 12c+ asynchronous global index maintenance: index is VALID but carries orphaned entries\nSELECT index_name, status, orphaned_entries\nFROM   dba_indexes\nWHERE  owner = 'CUSTOMER_APP' AND table_name = 'SALE_PART';\n\n-- Clean orphaned entries now (normally SYS.PMO_DEFERRED_GIDX_MAINT_JOB does it at night):\nEXEC DBMS_PART.CLEANUP_GIDX(schema_name_in => 'CUSTOMER_APP', table_name_in => 'SALE_PART');\n-- or a single index: ALTER INDEX customer_app.sale_part_id_gidx COALESCE CLEANUP;\n\n-- Target the partition by value instead of name:\n-- ALTER TABLE customer_app.sale_part DROP PARTITION FOR (DATE '2025-06-15') UPDATE INDEXES;","what":"在分区级别立即删除旧数据，而不是逐行 DELETE。在 12c+ 中，UPDATE INDEXES 只在数据字典中标记全局索引（异步维护），而不是更新每个条目；索引立即保持可用，孤立条目稍后清理。","when":"按数据保留策略清除旧周期数据、ILM/清理作业中，以及避免在大表上执行 DELETE 带来的 undo/redo 负载时。","caution":"需要 Partitioning 选件（EE 额外付费）。DROP/TRUNCATE 不可逆（flashback table 无法找回分区）；请先备份或通过 exchange 归档。如果忘记 UPDATE INDEXES，全局索引会变为 UNUSABLE，查询可能遇到 ORA-01502。如果有子表引用（FK），操作会失败。在 interval 表上，最后一个 range（transition）分区无法删除。","since":"Oracle 12c","tags":["drop partition","truncate partition","update indexes","orphaned_entries","cleanup_gidx","global index","data purge","partitioning"]},{"id":"interval-partitioning","task":"创建 interval 分区并将现有 range 表转换为 interval","category":"分区","command":"-- New table: a partition is created automatically for each month\nCREATE TABLE customer_app.measurements (\n  ts        DATE   NOT NULL,\n  sensor_id NUMBER,\n  value     NUMBER)\nPARTITION BY RANGE (ts) INTERVAL (NUMTOYMINTERVAL(1, 'MONTH'))\n( PARTITION p_first VALUES LESS THAN (DATE '2026-01-01') );\n\n-- Convert an existing range table to interval (there must be no MAXVALUE partition):\nALTER TABLE customer_app.sale_part SET INTERVAL (NUMTOYMINTERVAL(1, 'MONTH'));\n-- Back to range: ALTER TABLE customer_app.sale_part SET INTERVAL ();\n\n-- Rename a system-named partition to a meaningful name:\nALTER TABLE customer_app.measurements RENAME PARTITION FOR (DATE '2026-03-15') TO p2026_03;\n\n-- Check:\nSELECT partition_name, partition_position, interval, high_value\nFROM   dba_tab_partitions\nWHERE  table_owner = 'CUSTOMER_APP' AND table_name = 'MEASUREMENTS'\nORDER  BY partition_position;\n\nSELECT table_name, partitioning_type, interval\nFROM   dba_part_tables\nWHERE  owner = 'CUSTOMER_APP';","what":"interval 分区在超出最后边界的第一行到达时自动创建新分区，消除因忘记创建下个月分区而导致的 ORA-14400 错误。SET INTERVAL 可在不移动数据的情况下转换现有 range 表。","when":"用于按日期增长的表，例如时间序列、日志和订单，并减少分区维护脚本。","caution":"需要 Partitioning 选件（EE 额外付费）。分区键必须是单个 NUMBER/DATE/TIMESTAMP 列；必须先删除 MAXVALUE 分区。错误或极端的日期（例如 9999 年）会打开一个很远的分区并扰乱分区编号；间隔较窄时可能接近 1,048,575 个分区的上限（ORA-14300），因此请在应用中校验日期。自动创建的分区会进入表的默认表空间（用 STORE IN 指定）。","since":"Oracle 11g","tags":["interval partitioning","interval","automatic partition","numtoyminterval","ora-14400","set interval","range partition","partitioning"]},{"id":"partition-wise-join-check","task":"验证分区键和 partition-wise join 的使用","category":"分区","command":"-- 1) Do both tables have the same partitioning type, key and count?\nSELECT p.owner, p.table_name, p.partitioning_type, k.column_name,\n       p.partition_count, p.subpartitioning_type\nFROM   dba_part_tables p\n  JOIN dba_part_key_columns k\n    ON k.owner = p.owner AND k.name = p.table_name AND k.object_type = 'TABLE'\nWHERE  p.owner = 'CUSTOMER_APP'\n  AND  p.table_name IN ('CUSTOMER_H', 'ORDERS_H');\n\n-- 2) Explain the join and inspect the plan shape:\nEXPLAIN PLAN FOR\nSELECT /*+ PARALLEL(4) */ m.city, SUM(s.amount)\nFROM   customer_app.customer_h m\n  JOIN customer_app.orders_h s ON s.customer_id = m.id\nGROUP  BY m.city;\n\nSELECT * FROM TABLE(DBMS_XPLAN.DISPLAY(format => 'BASIC +PARALLEL +PARTITION'));\n-- Full PWJ: HASH JOIN sits BELOW the PARTITION HASH ALL line (PX PARTITION HASH ALL in parallel)\n-- Partial PWJ: PX SEND PARTITION (KEY) on one side only\n-- No PWJ: PX SEND HASH / BROADCAST on both sides","what":"两个按连接键等分区的表可以逐对分区进行连接（full partition-wise join），从而大幅减少并行查询中的重分发流量和内存。计划形态会显示是否使用了 PWJ。","when":"在 DWH 中设计大型事实表与事实表或事实表与维度表的连接、并行查询出现大量 PX 重分发（PX Deq Credit: send blkd），以及验证分区策略时。","caution":"许可：Partitioning 选件（EE 额外付费）；并行查询需要 Enterprise Edition。EXPLAIN PLAN 只是估算；请用 DISPLAY_CURSOR/SQL Monitor 查看实际计划。Full PWJ 需要相同的分区数（hash 分区建议为 2 的幂）以及一致的键数据类型。PWJ 也可能出现在串行计划中，但真正的收益在并行执行中。","tags":["partition-wise join","pwj","equi-partitioning","hash partition","parallel query","dba_part_key_columns","partitioning"]},{"id":"online-redefinition-flow","task":"使用 DBMS_REDEFINITION 在线重定义表（完整流程）","category":"在线操作","command":"-- 1) Is it eligible? (use CONS_USE_ROWID if there is no PK)\nEXEC DBMS_REDEFINITION.CAN_REDEF_TABLE('CUSTOMER_APP', 'CUSTOMER', DBMS_REDEFINITION.CONS_USE_PK);\n\n-- 2) Interim table with the target structure: hash partitioned in this example\nCREATE TABLE customer_app.customer_int (\n  id NUMBER, name VARCHAR2(100), city VARCHAR2(30))\nPARTITION BY HASH (id) PARTITIONS 8;\n\n-- 3) Start: initial copy + change tracking through an MV log\nBEGIN\n  DBMS_REDEFINITION.START_REDEF_TABLE(\n    uname        => 'CUSTOMER_APP',\n    orig_table   => 'CUSTOMER',\n    int_table    => 'CUSTOMER_INT',\n    col_mapping  => NULL,\n    options_flag => DBMS_REDEFINITION.CONS_USE_PK);\nEND;\n/\n\n-- 4) Copy indexes, constraints, triggers, grants and statistics:\nSET SERVEROUTPUT ON\nDECLARE\n  l_errors PLS_INTEGER;\nBEGIN\n  DBMS_REDEFINITION.COPY_TABLE_DEPENDENTS(\n    uname            => 'CUSTOMER_APP',\n    orig_table       => 'CUSTOMER',\n    int_table        => 'CUSTOMER_INT',\n    copy_indexes     => DBMS_REDEFINITION.CONS_ORIG_PARAMS,\n    copy_triggers    => TRUE,\n    copy_constraints => TRUE,\n    copy_privileges  => TRUE,\n    ignore_errors    => FALSE,\n    num_errors       => l_errors,\n    copy_statistics  => TRUE);\n  DBMS_OUTPUT.PUT_LINE('Error count: ' || l_errors);\nEND;\n/\nSELECT object_name, base_table_name, ddl_txt FROM dba_redefinition_errors;\n\n-- 5) Close the gap before finishing (shortens finish; can be run several times):\nEXEC DBMS_REDEFINITION.SYNC_INTERIM_TABLE('CUSTOMER_APP', 'CUSTOMER', 'CUSTOMER_INT');\n\n-- 6) Swap under a short lock:\nEXEC DBMS_REDEFINITION.FINISH_REDEF_TABLE('CUSTOMER_APP', 'CUSTOMER', 'CUSTOMER_INT', dml_lock_timeout => 60);\n\n-- 7) Drop the interim table, which now holds the old structure:\nDROP TABLE customer_app.customer_int PURGE;\n\n-- On problems: EXEC DBMS_REDEFINITION.ABORT_REDEF_TABLE('CUSTOMER_APP', 'CUSTOMER', 'CUSTOMER_INT');\n-- Status: SELECT * FROM dba_redefinition_status WHERE base_table_owner = 'CUSTOMER_APP';","what":"在应用持续运行的同时更改表结构（分区、列类型/顺序、表空间、压缩、LOB 设置）：数据复制到中间表，变更通过 MV log 同步，最后在极短的锁下交换名称。","when":"用于 ALTER TABLE 无法在线完成的变更，例如在不停机的情况下对大型非分区表进行分区、更改列类型，或将 BasicFile LOB 转换为 SecureFiles。","caution":"需要 Enterprise Edition（如果目标是分区表，还需要 Partitioning 选件）。该过程需要两倍于表的空间，并增加 redo 和 MV log 开销。FINISH 会短暂获取排他锁；长时间运行的事务会使其等待（dml_lock_timeout）。务必在 DBA_REDEFINITION_ERRORS 中检查 COPY_TABLE_DEPENDENTS 的错误；FK 可能以 NOVALIDATE 方式复制。请先在测试环境中演练。","note":"dml_lock_timeout 参数在 12c 中加入。在 12.2+ 中可在 V$ONLINE_REDEF 中监控进度。对于简单的迁移/压缩变更，使用 ALTER TABLE ... MOVE ONLINE 即可，无需 DBMS_REDEFINITION。","tags":["dbms_redefinition","online redefinition","redefinition","zero downtime","convert to partitioned","can_redef_table","finish_redef_table","enterprise edition"]},{"id":"table-move-online","task":"在线移动表或分区（ALTER TABLE MOVE ONLINE）","category":"在线操作","command":"-- Move the table to another tablespace / reorganize it (DML continues):\nALTER TABLE customer_app.orders MOVE ONLINE TABLESPACE customer_data;\n\n-- Compress while moving (BASIC; for OLTP compression use ROW STORE COMPRESS ADVANCED):\nALTER TABLE customer_app.orders MOVE ONLINE COMPRESS;\n\n-- A single partition:\nALTER TABLE customer_app.sale_part\n  MOVE PARTITION p2026_h1 ONLINE TABLESPACE customer_archive UPDATE INDEXES;\n\n-- Verify: indexes must stay VALID\nSELECT index_name, status FROM dba_indexes\nWHERE  owner = 'CUSTOMER_APP' AND table_name = 'ORDERS';\nSELECT table_name, tablespace_name, compression, compress_for\nFROM   dba_tables\nWHERE  owner = 'CUSTOMER_APP' AND table_name = 'ORDERS';","what":"重写表段：减少碎片并降低高水位线、更换表空间、应用压缩。使用 ONLINE 时，操作期间 DML 不被阻塞，索引自动维护并保持 VALID。","when":"用于大批量删除后回收空间、将表迁移到新的表空间/存储，以及将旧分区压缩到归档表空间。","caution":"需要 Enterprise Edition；ROW STORE COMPRESS ADVANCED 需要 Advanced Compression 选件（额外付费）。该操作需要与表大小相等的额外空间，并产生大量 redo。开始和结束时会获取短暂的锁；长时间未结束的事务会使其等待。不带 ONLINE 或 UPDATE INDEXES 的 MOVE 会使所有索引变为 UNUSABLE。","since":"Oracle 12.2","note":"表级 MOVE ONLINE 在 12.2 中引入；分区级 MOVE PARTITION ONLINE 自 12.1 起即已存在。在 12.1 及更早版本中，表请使用 DBMS_REDEFINITION。","tags":["move online","alter table move","move table","reorganization","high water mark","compression","enterprise edition"]},{"id":"tde-keystore-setup","task":"创建、打开 TDE keystore，设置其主密钥并备份（19c+）","category":"安全","command":"-- 1) Keystore location (static parameter, restart required), then keystore type:\nALTER SYSTEM SET WALLET_ROOT = '/u01/app/oracle/admin/ORCL/wallet' SCOPE = SPFILE;\n-- SHUTDOWN IMMEDIATE;  STARTUP;\nALTER SYSTEM SET TDE_CONFIGURATION = 'KEYSTORE_CONFIGURATION=FILE' SCOPE = BOTH;\n\n-- 2) In CDB root: create a software keystore (under WALLET_ROOT/tde), open it, set the master key\nADMINISTER KEY MANAGEMENT CREATE KEYSTORE IDENTIFIED BY \"<keystore_pwd>\";\nADMINISTER KEY MANAGEMENT SET KEYSTORE OPEN\n  IDENTIFIED BY \"<keystore_pwd>\" CONTAINER = ALL;\nADMINISTER KEY MANAGEMENT SET KEY USING TAG 'mk_2026_10'\n  IDENTIFIED BY \"<keystore_pwd>\" WITH BACKUP USING 'mk_before' CONTAINER = ALL;\n\n-- 3) Open automatically after restart (auto-login, cwallet.sso):\nADMINISTER KEY MANAGEMENT CREATE AUTO_LOGIN KEYSTORE\n  FROM KEYSTORE IDENTIFIED BY \"<keystore_pwd>\";\n\n-- 4) Backup (repeat after every SET KEY / rekey, store SEPARATELY from database backups):\nADMINISTER KEY MANAGEMENT BACKUP KEYSTORE USING 'daily'\n  FORCE KEYSTORE IDENTIFIED BY \"<keystore_pwd>\" TO '/backup/tde/';\n\n-- 5) Status:\nSELECT con_id, wrl_type, wrl_parameter, status, wallet_type, keystore_mode\nFROM   v$encryption_wallet;\nSELECT con_id, key_id, tag, creation_time, activation_time\nFROM   v$encryption_keys ORDER BY creation_time;","what":"以 united 模式（所有 PDB 共享 CDB keystore）为 Transparent Data Encryption 建立软件 keystore，创建主加密密钥并启用 auto-login，使其在重启后自动打开。在 19c+ 中推荐使用 WALLET_ROOT/TDE_CONFIGURATION，而不是 sqlnet.ora 的 ENCRYPTION_WALLET_LOCATION。","when":"在要求静态数据加密的合规项目（GDPR、PCI-DSS、ISO 27001）中，作为表空间/列加密之前的第一步。","caution":"本地部署需要 Advanced Security 选件（额外付费）；Oracle Cloud（OCI/Autonomous）许可中已包含。WALLET_ROOT 是静态参数，需要重启实例（停机）。如果 keystore 及其密码丢失，加密数据将永久无法读取；请将 keystore 备份安全存放，并与数据库备份分开。任何持有 auto-login 文件（cwallet.sso）的人都能打开数据库；请限制文件权限，并在适当时使用 LOCAL AUTO_LOGIN。使用 Data Guard 时，也要将 keystore 复制到备库。","since":"Oracle 18c","note":"WALLET_ROOT 和 TDE_CONFIGURATION 在 18c 中引入，在 19c 中成为标准；12c-18c 的 sqlnet.ora ENCRYPTION_WALLET_LOCATION 方法在 19c 中已弃用，在 23ai 中不再支持。在 23ai 中默认算法为 AES256；GOST 和 SEED 加密不再支持，使用它们解密已弃用。WALLET_ROOT 是静态参数，需要重启实例。","tags":["tde","keystore","wallet","master key","encryption","advanced security","wallet_root","administer key management"],"untested":true},{"id":"tde-tablespace-encryption","task":"使用 TDE 加密表空间（新建和现有，在线/离线）","category":"安全","command":"-- Prerequisite: keystore OPEN and master key set (v$encryption_wallet.status = 'OPEN')\n\n-- 1) New encrypted tablespace:\nCREATE TABLESPACE customer_enc\n  DATAFILE '+DATA' SIZE 1G AUTOEXTEND ON NEXT 256M MAXSIZE 20G\n  ENCRYPTION USING 'AES256' ENCRYPT;\n\n-- 2a) Encrypt an existing tablespace ONLINE (12.2+, needs extra space temporarily):\nALTER TABLESPACE customer_data ENCRYPTION ONLINE USING 'AES256' ENCRYPT;\n-- If not OMF: ... ENCRYPT FILE_NAME_CONVERT = ('customer_data01.dbf', 'customer_data01_enc.dbf');\n\n-- 2b) OFFLINE conversion (no extra space, tablespace unavailable meanwhile):\nALTER TABLESPACE customer_data OFFLINE NORMAL;\nALTER TABLESPACE customer_data ENCRYPTION OFFLINE ENCRYPT;\nALTER TABLESPACE customer_data ONLINE;\n\n-- 3) Progress and status:\nSELECT t.name AS tablespace_name, e.encryptionalg, e.status, e.con_id\nFROM   v$encrypted_tablespaces e\n  JOIN v$tablespace t ON t.ts# = e.ts# AND t.con_id = e.con_id;\nSELECT tablespace_name, encrypted FROM dba_tablespaces ORDER BY tablespace_name;","what":"表空间加密会加密写入数据文件的每个块；数据在 buffer cache 中是明文，因此无需修改应用或 SQL。在线转换会构建数据文件的加密副本并在 DML 继续进行时切换过去；离线转换则原地加密块。","when":"用于对保存敏感数据的应用表空间进行静态加密、将数据迁入新的加密表空间，或关闭审计发现项。","caution":"本地部署需要 Advanced Security 选件（额外付费）。在线转换需要与被加密数据文件同等大小的额外空间，并产生大量 I/O 和 redo；Data Guard 备库也会执行相同的工作。离线转换必须在 Data Guard 备库上单独运行。SYSTEM/SYSAUX/UNDO 在 12.2+ 中可在线加密，但需格外小心。没有 keystore 无法还原加密表空间的备份。","since":"Oracle 12.2","note":"在线/离线表空间转换在 12.2 中引入。要自动加密新表空间，19c 使用 TABLESPACE_ENCRYPTION=AUTO_ENABLE（19.16+ RU）或 ENCRYPT_NEW_TABLESPACES=ALWAYS。keystore 必须已打开并已先设置主密钥（参见 tde-keystore-setup）。","tags":["tde","tablespace encryption","encryption online","aes256","v$encrypted_tablespaces","advanced security","at-rest"],"untested":true},{"id":"unified-audit-policy-lifecycle","task":"创建 Unified Auditing 策略、按条件启用、查看记录并删除","category":"安全","command":"-- 1) Object + condition based policy (excluding the batch module):\nCREATE AUDIT POLICY pol_customer_dml\n  ACTIONS SELECT ON customer_app.customer,\n          UPDATE ON customer_app.customer,\n          DELETE ON customer_app.customer\n  WHEN 'SYS_CONTEXT(''USERENV'',''MODULE'') NOT LIKE ''batch%'''\n  EVALUATE PER SESSION;\n\n-- 2) Privileged operations policy:\nCREATE AUDIT POLICY pol_ddl_priv\n  PRIVILEGES DROP ANY TABLE, ALTER SYSTEM\n  ACTIONS DROP USER, ALTER USER, CREATE USER;\n\n-- 3) Enable:\nAUDIT POLICY pol_customer_dml EXCEPT customer_app;   -- everyone except the schema owner\nAUDIT POLICY pol_ddl_priv WHENEVER SUCCESSFUL;\nAUDIT POLICY ORA_LOGON_FAILURES WHENEVER NOT SUCCESSFUL;\n\n-- 4) Which policy is enabled for whom?\nSELECT policy_name, enabled_option, entity_name, success, failure\nFROM   audit_unified_enabled_policies ORDER BY policy_name;\nSELECT policy_name, audit_option, object_schema, object_name, audit_condition\nFROM   audit_unified_policies WHERE policy_name LIKE 'POL_%';\n\n-- 5) Records:\nSELECT event_timestamp, dbusername, action_name, object_schema, object_name,\n       return_code, unified_audit_policies, os_username, userhost\nFROM   unified_audit_trail\nWHERE  event_timestamp > SYSTIMESTAMP - INTERVAL '1' DAY\n  AND  unified_audit_policies LIKE '%POL_%'\nORDER  BY event_timestamp DESC;\n-- 19c (queued-write): EXEC DBMS_AUDIT_MGMT.FLUSH_UNIFIED_AUDIT_TRAIL;  (not in 23ai)\n\n-- 6) Disable and remove:\nNOAUDIT POLICY pol_customer_dml;\nNOAUDIT POLICY pol_ddl_priv;\nDROP AUDIT POLICY pol_customer_dml;\nDROP AUDIT POLICY pol_ddl_priv;","what":"Unified Auditing 将所有审计记录收集到单一审计踪迹（AUDSYS schema，UNIFIED_AUDIT_TRAIL）中。策略按对象操作、系统权限或操作定义；WHEN 条件和 BY/EXCEPT 用户列表可减少噪音。","when":"用于记录对敏感表的访问、监控特权 DDL 和权限变更、查看登录失败尝试，以及满足审计要求。","caution":"无需额外许可。对繁忙表审计 SELECT 会产生大量记录并使 SYSAUX 增长；请用条件和 EXCEPT 缩小范围，并用 DBMS_AUDIT_MGMT（CLEAN_AUDIT_TRAIL、SET_LAST_ARCHIVE_TIMESTAMP）安排归档/清理。已启用的策略无法删除（ORA-46361）；请先执行 NOAUDIT。策略变更在新会话中生效。19c 默认以混合模式运行，因此也可能写入传统 AUDIT_TRAIL 记录。","since":"Oracle 12c","note":"在 23ai 中传统审计不再支持；只存在统一审计，且由于记录直接写入表，FLUSH_UNIFIED_AUDIT_TRAIL 已被移除。在 19c 中，切换到纯统一审计模式需要用 uniaud_on 重新链接 oracle 二进制文件。","tags":["unified auditing","audit policy","auditing","unified_audit_trail","create audit policy","noaudit","ora_logon_failures","security"]},{"id":"vpd-row-level-policy","task":"使用 VPD（DBMS_RLS）添加行级访问策略","category":"安全","command":"-- 1) Policy function returning the predicate:\nCREATE OR REPLACE FUNCTION customer_app.customer_vpd_fn (\n  p_schema IN VARCHAR2,\n  p_object IN VARCHAR2\n) RETURN VARCHAR2\nAS\nBEGIN\n  -- The schema owner sees all rows (NULL = no restriction)\n  IF SYS_CONTEXT('USERENV', 'SESSION_USER') = 'CUSTOMER_APP' THEN\n    RETURN NULL;\n  END IF;\n  -- Other users see only their own city\n  RETURN 'city = SYS_CONTEXT(''USERENV'', ''CLIENT_IDENTIFIER'')';\nEND;\n/\n\n-- 2) Attach the policy to the table:\nBEGIN\n  DBMS_RLS.ADD_POLICY(\n    object_schema   => 'CUSTOMER_APP',\n    object_name     => 'CUSTOMER',\n    policy_name     => 'CUSTOMER_CITY_VPD',\n    function_schema => 'CUSTOMER_APP',\n    policy_function => 'CUSTOMER_VPD_FN',\n    statement_types => 'SELECT, UPDATE, DELETE',\n    update_check    => TRUE,\n    policy_type     => DBMS_RLS.CONTEXT_SENSITIVE);\nEND;\n/\n\n-- 3) Check:\nSELECT object_owner, object_name, policy_name, function,\n       sel, upd, del, chk_option, enable, policy_type\nFROM   dba_policies WHERE object_owner = 'CUSTOMER_APP';\n-- Test (as the application user): EXEC DBMS_SESSION.SET_IDENTIFIER('Ankara');\n\n-- 4) Temporarily disable / drop:\nEXEC DBMS_RLS.ENABLE_POLICY('CUSTOMER_APP', 'CUSTOMER', 'CUSTOMER_CITY_VPD', FALSE);\nEXEC DBMS_RLS.DROP_POLICY('CUSTOMER_APP', 'CUSTOMER', 'CUSTOMER_CITY_VPD');","what":"Virtual Private Database 会自动将策略函数返回的 WHERE 谓词附加到针对该表的每条 SQL 上，使同一张表的用户只能查看/修改其有权访问的行。update_check=>TRUE 可防止用户将某行更新为其自身无法看到的值。","when":"用于多租户应用、基于区域/分支机构的数据隔离，以及在不修改应用代码的情况下增加行级安全。","caution":"包含在 Enterprise Edition 中，无需额外许可。CLIENT_IDENTIFIER 可由客户端随意设置；在生产环境中请使用由受信任程序包设置的应用上下文（CREATE CONTEXT ... USING）。SYS 和拥有 EXEMPT ACCESS POLICY 的用户会绕过策略；没有该权限的 Data Pump 导出会静默地只导出部分数据。函数中的错误会以 ORA-28112 阻止所有访问。错误的 policy_type（例如 STATIC）会缓存谓词并返回错误结果。","tags":["vpd","virtual private database","dbms_rls","row security","row level security","add_policy","policy function","security"]},{"id":"password-profile-hardening","task":"创建密码策略 profile 并分配给用户","category":"安全","command":"-- Hardened profile for application/human users:\nCREATE PROFILE app_user_prf LIMIT\n  FAILED_LOGIN_ATTEMPTS    5\n  PASSWORD_LOCK_TIME       1/24          -- 1 hour lock\n  PASSWORD_LIFE_TIME       90\n  PASSWORD_GRACE_TIME      7\n  PASSWORD_REUSE_TIME      365\n  PASSWORD_REUSE_MAX       10\n  INACTIVE_ACCOUNT_TIME    60            -- accounts unused for 60 days are locked\n  PASSWORD_VERIFY_FUNCTION ora12c_strong_verify_function;\n\n-- Service accounts: password never expires, gradual password rollover enabled:\nCREATE PROFILE service_account_prf LIMIT\n  FAILED_LOGIN_ATTEMPTS    UNLIMITED\n  PASSWORD_LIFE_TIME       UNLIMITED\n  PASSWORD_ROLLOVER_TIME   1;            -- old and new password both valid for 1 day\n\nALTER USER customer_app PROFILE app_user_prf;\n\n-- Check:\nSELECT username, profile, account_status,\n       TO_CHAR(expiry_date,'YYYY-MM-DD') AS expiry_date, lock_date\nFROM   dba_users\nWHERE  profile IN ('APP_USER_PRF', 'SERVICE_ACCOUNT_PRF');\n\nSELECT profile, resource_name, limit\nFROM   dba_profiles\nWHERE  profile = 'APP_USER_PRF' AND resource_type = 'PASSWORD'\nORDER  BY resource_name;","what":"profile 将登录失败锁定、密码有效期、重用、复杂度（verify 函数）和非活动账户锁定规则应用于一组用户。PASSWORD_ROLLOVER_TIME 使应用在服务账户密码变更期间可以无中断切换。","when":"用于安全审计/CIS 基准要求、创建新的应用用户时，以及按用户组分离 DEFAULT profile 的宽松取值。","caution":"无需额外许可。服务账户上的 FAILED_LOGIN_ATTEMPTS 限制会让一台配置了错误密码的应用服务器锁定该账户并导致生产中断。应用账户上的 PASSWORD_LIFE_TIME 可能导致意外的 EXPIRED 状态。verify 函数必须存在于 SYS 下（否则执行 @?/rdbms/admin/utlpwdmg.sql；该脚本也会修改 DEFAULT profile）。更改 profile 不会重新验证现有密码。","note":"INACTIVE_ACCOUNT_TIME 在 12.2 中加入；PASSWORD_ROLLOVER_TIME 在 21c 中引入，并通过 19.12 RU 向后移植到 19c。在 rollover 期间使用旧密码登录的会话可通过审计记录追踪。","tags":["profile","password policy","password_verify_function","failed_login_attempts","inactive_account_time","password_rollover_time","account lock","security"]},{"id":"privilege-analysis-capture","task":"通过权限分析查找未使用的权限","category":"安全","command":"-- 1) Capture privilege usage for a specific user:\nBEGIN\n  DBMS_PRIVILEGE_CAPTURE.CREATE_CAPTURE(\n    name        => 'CAP_APP_USER',\n    description => 'Actual privilege usage of the application user',\n    type        => DBMS_PRIVILEGE_CAPTURE.G_CONTEXT,\n    condition   => 'SYS_CONTEXT(''USERENV'',''SESSION_USER'') = ''CUSTOMER_APP''');\n  DBMS_PRIVILEGE_CAPTURE.ENABLE_CAPTURE(name => 'CAP_APP_USER', run_name => 'RUN_2026_10');\nEND;\n/\n-- Other types: G_DATABASE (whole DB), G_ROLE (roles => role_name_list('DBA')), G_ROLE_AND_CONTEXT\n\n-- 2) After a representative workload (including month end), stop and generate results:\nBEGIN\n  DBMS_PRIVILEGE_CAPTURE.DISABLE_CAPTURE(name => 'CAP_APP_USER');\n  DBMS_PRIVILEGE_CAPTURE.GENERATE_RESULT(name => 'CAP_APP_USER', run_name => 'RUN_2026_10');\nEND;\n/\n\n-- 3) Used and never-used privileges:\nSELECT username, sys_priv, obj_priv, object_owner, object_name, used_role\nFROM   dba_used_privs   WHERE capture = 'CAP_APP_USER';\nSELECT username, sys_priv, obj_priv, object_owner, object_name\nFROM   dba_unused_privs WHERE capture = 'CAP_APP_USER';\n\n-- 4) Through which role chain was the privilege used?\nSELECT username, sys_priv, used_role, path\nFROM   dba_used_sysprivs_path WHERE capture = 'CAP_APP_USER';\n\n-- 5) Cleanup:\nEXEC DBMS_PRIVILEGE_CAPTURE.DROP_CAPTURE(name => 'CAP_APP_USER');","what":"权限分析在运行时记录所选范围（整个数据库、角色、条件）内实际使用了哪些系统/对象权限；结果列出未使用的权限，以便按最小权限原则规划 REVOKE。","when":"用于清理拥有 DBA/ANY 权限的应用账户、审计前的权限审查，以及弄清套装应用真正需要什么。","caution":"自 2018 年起包含在 Enterprise Edition 中（之前需要 Database Vault 许可）；无需额外许可。捕获期间有少量开销。结果只反映捕获窗口内的负载：如果月末、年末或应急作业没有运行，显示为\"未使用\"的权限可能仍然需要，因此请逐步回收。同一时间只能启用一个捕获；唯一的例外是 G_DATABASE 捕获可以与一个角色或上下文捕获同时启用。","since":"Oracle 12c","tags":["privilege analysis","dbms_privilege_capture","unused privileges","least privilege","dba_unused_privs","privilege cleanup","security"]},{"id":"opatch-inventory-prereq","task":"使用 OPatch 检查清单并执行补丁前检查","category":"补丁与生命周期","command":"# OPatch version (must meet the minimum in the patch README; update with patch 6880880 if needed)\n$ORACLE_HOME/OPatch/opatch version\n\n# Installed patches: short list / detailed inventory\n$ORACLE_HOME/OPatch/opatch lspatches\n$ORACLE_HOME/OPatch/opatch lsinventory -detail -oh $ORACLE_HOME\n\n# Pre-apply checks (in the unzipped patch directory):\ncd /u01/stage/<patch_id>\n$ORACLE_HOME/OPatch/opatch prereq CheckConflictAgainstOHWithDetail -ph ./\n$ORACLE_HOME/OPatch/opatch prereq CheckSystemSpace -ph ./\n\n# For Grid Infrastructure / RAC homes (as root), analyze mode:\n# opatchauto apply /u01/stage/<patch_id> -analyze","what":"列出已应用到 Oracle Home 的二进制补丁（RU、one-off），并在应用新补丁前检查其是否与现有补丁冲突以及磁盘空间是否充足。","when":"季度 RU/安全补丁之前、支持人员在 SR 中要求提供清单输出时，以及比较两个环境的补丁级别时。","caution":"以拥有 Oracle Home 的操作系统用户（oracle）运行命令；opatchauto 需要 root。prereq 检查是只读的，不影响数据库，但如果报告冲突，在从 Oracle Support 获得合并/overlay 补丁之前不要应用。应用前备份 Oracle Home（tar）和清单（inventory）。","note":"要从 SQL 查看清单，可以使用 DBMS_QOPATCH.GET_OPATCH_LSINVENTORY（适用于有 OPatch 清单的安装）。","tags":["opatch","lsinventory","lspatches","prereq","patch","patch conflict","opatchauto","release update"],"untested":true},{"id":"datapatch-apply-verify","task":"二进制补丁后使用 datapatch -verbose 更新 SQL 层","category":"补丁与生命周期","command":"# 1) After the binary patch (opatch apply), start the DB; all PDBs must be open:\nsqlplus / as sysdba <<EOF\nSTARTUP;\nALTER PLUGGABLE DATABASE ALL OPEN;\nEXIT;\nEOF\n\n# 2) Check only first, then apply:\ncd $ORACLE_HOME/OPatch\n./datapatch -prereq\n./datapatch -verbose\n# For specific PDBs: ./datapatch -verbose -pdbs PDB1,PDB2\n\n# 3) Recompile invalid objects:\nsqlplus / as sysdba @?/rdbms/admin/utlrp.sql\n\n# Logs: $ORACLE_BASE/cfgtoollogs/sqlpatch/","what":"datapatch 将 Oracle Home 中二进制补丁的 SQL/PL-SQL 组件（数据字典、程序包）应用到 CDB root、PDB$SEED 和所有已打开的 PDB，并将结果记录在 DBA_REGISTRY_SQLPATCH 中。它也会回退已回滚补丁的 SQL 部分。","when":"每次 RU/RUR/one-off 二进制补丁或回滚之后；对于迁移到或 unplug/plug 到新打补丁 home 的 PDB；以及当 DBA_REGISTRY_SQLPATCH 显示 WITH ERRORS 时重新运行。","caution":"已关闭或 MOUNTED 的 PDB 不会被打补丁，并可能以 RESTRICTED 模式打开；请先打开所有 PDB。不要在高负载下运行（数据字典锁、程序包失效）。在 RAC 上只需从一个节点运行一次。使用 Data Guard 时只在主库上运行 datapatch；变更通过 redo 传到备库（先为备库二进制文件打补丁）。","note":"较新的 19c RU 附带的 datapatch 提供 -sanity_checks，可在打补丁前验证数据字典和环境的健康状况（无效对象、PDB 违规、表空间/TEMP 状态）。","tags":["datapatch","sql patch","release update","dba_registry_sqlpatch","post-patch","utlrp","pdb","patching"],"untested":true},{"id":"sqlpatch-registry-check","task":"验证 SQL 补丁历史、组件状态和无效对象","category":"补丁与生命周期","command":"-- 1) datapatch history (most recent at the bottom):\nSELECT patch_id, patch_type, action, status, source_version, target_version,\n       TO_CHAR(action_time,'YYYY-MM-DD HH24:MI') AS action_time, description\nFROM   dba_registry_sqlpatch\nORDER  BY action_time;\n\n-- Failed entries and their log files:\nSELECT install_id, patch_id, action, status, logfile\nFROM   dba_registry_sqlpatch\nWHERE  status <> 'SUCCESS';\n\n-- In a CDB, for all PDBs (from root):\nSELECT con_id, patch_id, action, status\nFROM   cdb_registry_sqlpatch ORDER BY con_id, action_time;\n\n-- 2) Components must be VALID:\nSELECT comp_id, comp_name, version_full, status\nFROM   dba_registry ORDER BY comp_id;\n\n-- 3) Invalid object summary (compare before/after utlrp):\nSELECT owner, object_type, COUNT(*) AS invalid_cnt\nFROM   dba_objects\nWHERE  status = 'INVALID'\nGROUP  BY owner, object_type\nORDER  BY invalid_cnt DESC;","what":"一目了然地显示打补丁后 SQL 层是否真正更新（数据字典与二进制文件一致）、哪个补丁在哪个 PDB 中失败、数据字典组件是否为 VALID，以及无效对象的数量。","when":"作为每次 datapatch 后的验证步骤、在打补丁或升级前捕获基线，以及插入 PDB 后怀疑存在不一致时。","caution":"只读；无需许可。SOURCE_VERSION/TARGET_VERSION 在 18c+ RU 模型中填充。如果 STATUS='WITH ERRORS'，请修复 LOGFILE 中的错误并重新运行 datapatch。部分组件（例如未使用的 XDB/JAVA）可能显示 INVALID/REMOVED；请与补丁前的状态对比。","note":"要从 SQL 查看二进制清单，请使用 SELECT XMLTRANSFORM(DBMS_QOPATCH.GET_OPATCH_LIST, DBMS_QOPATCH.GET_OPATCH_XSLT) FROM dual；在没有 OPatch 清单的安装上会报 ORA-20013。","tags":["dba_registry_sqlpatch","patch history","datapatch","dba_registry","invalid objects","invalid object","patch verification"]},{"id":"scheduler-job-lifecycle","task":"创建、手动运行、禁用和删除调度程序作业","category":"调度程序","command":"-- 1) Create disabled, set attributes, then enable:\nBEGIN\n  DBMS_SCHEDULER.CREATE_JOB(\n    job_name        => 'CUSTOMER_APP.NIGHTLY_STATS',\n    job_type        => 'PLSQL_BLOCK',\n    job_action      => q'[BEGIN DBMS_STATS.GATHER_SCHEMA_STATS(ownname => 'CUSTOMER_APP', options => 'GATHER STALE'); END;]',\n    start_date      => SYSTIMESTAMP,\n    repeat_interval => 'FREQ=DAILY; BYHOUR=2; BYMINUTE=30; BYSECOND=0',\n    enabled         => FALSE,\n    auto_drop       => FALSE,\n    comments        => 'Nightly stale statistics gathering');\n  DBMS_SCHEDULER.SET_ATTRIBUTE('CUSTOMER_APP.NIGHTLY_STATS', 'max_run_duration', INTERVAL '2' HOUR);\n  DBMS_SCHEDULER.SET_ATTRIBUTE('CUSTOMER_APP.NIGHTLY_STATS', 'logging_level', DBMS_SCHEDULER.LOGGING_FULL);\n  DBMS_SCHEDULER.ENABLE('CUSTOMER_APP.NIGHTLY_STATS');\nEND;\n/\n\n-- 2) Validate the calendar expression (next run time):\nSET SERVEROUTPUT ON\nDECLARE\n  l_next TIMESTAMP WITH TIME ZONE;\nBEGIN\n  DBMS_SCHEDULER.EVALUATE_CALENDAR_STRING(\n    calendar_string   => 'FREQ=DAILY; BYHOUR=2; BYMINUTE=30; BYSECOND=0',\n    start_date        => SYSTIMESTAMP,\n    return_date_after => SYSTIMESTAMP,\n    next_run_date     => l_next);\n  DBMS_OUTPUT.PUT_LINE('Next run: ' || l_next);\nEND;\n/\n\n-- 3) Run now in the background (does not affect the schedule):\nEXEC DBMS_SCHEDULER.RUN_JOB('CUSTOMER_APP.NIGHTLY_STATS', use_current_session => FALSE);\n\n-- 4) Status:\nSELECT owner, job_name, enabled, state, run_count, failure_count,\n       last_start_date, next_run_date\nFROM   dba_scheduler_jobs WHERE owner = 'CUSTOMER_APP';\n\n-- 5) Stop (if running) / disable / drop:\n-- EXEC DBMS_SCHEDULER.STOP_JOB('CUSTOMER_APP.NIGHTLY_STATS', force => FALSE);\nEXEC DBMS_SCHEDULER.DISABLE('CUSTOMER_APP.NIGHTLY_STATS', force => TRUE);\nEXEC DBMS_SCHEDULER.DROP_JOB('CUSTOMER_APP.NIGHTLY_STATS', force => TRUE);","what":"使用 DBMS_SCHEDULER 定义一个定时 PL/SQL 作业；设置 max_run_duration（超过时触发事件）和详细日志级别，预先验证日历表达式，并允许在不打乱调度的情况下手动运行。","when":"用于在数据库内部而不是 cron 中运行维护、ETL 和清理等周期性数据库工作，并感知 RAC/Data Guard。","caution":"无需额外许可。start_date 的时区很重要：SYSTIMESTAMP 使用数据库服务器的 TZ；请使用明确的区域（例如 'Europe/Istanbul'）以避免夏令时切换时出现偏移。如果 JOB_QUEUE_PROCESSES=0，则不会运行任何作业。使用 force=>TRUE 的 DISABLE 不会停止正在运行的实例，只会阻止以后的运行；要中断请使用 STOP_JOB。在其他 schema 中创建作业需要 CREATE ANY JOB。","tags":["dbms_scheduler","job","scheduled job","create_job","run_job","repeat_interval","disable job","scheduler"]},{"id":"scheduler-job-history","task":"监控调度程序作业运行历史和失败","category":"调度程序","command":"-- 1) Recent runs: duration, CPU, error code and message\nSELECT owner, job_name, status, error#,\n       TO_CHAR(actual_start_date,'YYYY-MM-DD HH24:MI:SS') AS started,\n       run_duration, cpu_used,\n       SUBSTR(additional_info, 1, 200) AS info\nFROM   dba_scheduler_job_run_details\nWHERE  owner = 'CUSTOMER_APP'\nORDER  BY actual_start_date DESC\nFETCH FIRST 20 ROWS ONLY;\n\n-- 2) Failures in the last 24 hours (whole DB):\nSELECT owner, job_name, error#, actual_start_date, SUBSTR(errors, 1, 300) AS errors\nFROM   dba_scheduler_job_run_details\nWHERE  status <> 'SUCCEEDED'\n  AND  log_date > SYSTIMESTAMP - INTERVAL '1' DAY\nORDER  BY log_date DESC;\n\n-- 3) Currently running:\nSELECT owner, job_name, session_id, running_instance, elapsed_time, cpu_used\nFROM   dba_scheduler_running_jobs;\n\n-- 4) Lifecycle events (ENABLE/DISABLE/RUN/STOP...):\nSELECT log_date, owner, job_name, operation, status\nFROM   dba_scheduler_job_log\nWHERE  owner = 'CUSTOMER_APP'\nORDER  BY log_date DESC\nFETCH FIRST 20 ROWS ONLY;\n\n-- Log retention (days): EXEC DBMS_SCHEDULER.SET_SCHEDULER_ATTRIBUTE('log_history', '30');","what":"从调度程序日志视图中列出每次运行的开始时间、持续时间、CPU 使用、状态和错误消息，以及正在运行的作业和作业生命周期事件。","when":"当夜间作业没有运行或运行时间过长时、用于作业 SLA 跟踪，以及在监控系统（OEM/Zabbix/Prometheus）中定义失败规则时。","caution":"只读；无需许可。默认日志保留期为 30 天，logging_level 为 OFF 的作业不产生记录。ERRORS 列在 12c+ 中存在；在更早的版本中请检查 ADDITIONAL_INFO。在 RAC 上，RUNNING_INSTANCE 显示作业在哪个节点上运行。","tags":["scheduler","job history","dba_scheduler_job_run_details","failed job","dba_scheduler_running_jobs","job log","monitoring"]},{"id":"resource-plan-basic","task":"创建并激活一个简单的 Resource Manager 计划","category":"调度程序","command":"-- 1) Define groups, plan, directives and mapping inside a pending area (PDB or non-CDB):\nBEGIN\n  DBMS_RESOURCE_MANAGER.CLEAR_PENDING_AREA;\n  DBMS_RESOURCE_MANAGER.CREATE_PENDING_AREA;\n\n  DBMS_RESOURCE_MANAGER.CREATE_CONSUMER_GROUP('OLTP_GRP',  'Online transactions');\n  DBMS_RESOURCE_MANAGER.CREATE_CONSUMER_GROUP('REPORT_GRP', 'Reporting / batch');\n  DBMS_RESOURCE_MANAGER.CREATE_PLAN('DAYTIME_PLAN', 'Business hours plan');\n\n  DBMS_RESOURCE_MANAGER.CREATE_PLAN_DIRECTIVE(\n    plan => 'DAYTIME_PLAN', group_or_subplan => 'OLTP_GRP',\n    comment => 'OLTP first', mgmt_p1 => 70);\n  DBMS_RESOURCE_MANAGER.CREATE_PLAN_DIRECTIVE(\n    plan => 'DAYTIME_PLAN', group_or_subplan => 'REPORT_GRP',\n    comment => 'Reports limited', mgmt_p1 => 20,\n    parallel_degree_limit_p1 => 4,\n    switch_group => 'CANCEL_SQL', switch_time => 1800, switch_for_call => TRUE);\n  DBMS_RESOURCE_MANAGER.CREATE_PLAN_DIRECTIVE(\n    plan => 'DAYTIME_PLAN', group_or_subplan => 'OTHER_GROUPS',\n    comment => 'Everyone else (mandatory)', mgmt_p1 => 10);\n\n  DBMS_RESOURCE_MANAGER.SET_CONSUMER_GROUP_MAPPING(\n    attribute      => DBMS_RESOURCE_MANAGER.ORACLE_USER,\n    value          => 'REPORT_USER',\n    consumer_group => 'REPORT_GRP');\n\n  DBMS_RESOURCE_MANAGER.VALIDATE_PENDING_AREA;\n  DBMS_RESOURCE_MANAGER.SUBMIT_PENDING_AREA;\nEND;\n/\n-- Switch privilege required for the mapping to take effect:\nEXEC DBMS_RESOURCE_MANAGER_PRIVS.GRANT_SWITCH_CONSUMER_GROUP('REPORT_USER', 'REPORT_GRP', FALSE);\n\n-- 2) Activate (FORCE: prevents maintenance windows from switching the plan):\nALTER SYSTEM SET resource_manager_plan = 'FORCE:DAYTIME_PLAN' SCOPE = BOTH;\n\n-- 3) Monitor:\nSELECT sid, username, resource_consumer_group FROM v$session WHERE username IS NOT NULL;\nSELECT name, active_sessions, execution_waiters, consumed_cpu_time, cpu_wait_time\nFROM   v$rsrc_consumer_group;\n\n-- 4) Roll back: deactivate the plan, then delete it\n-- ALTER SYSTEM SET resource_manager_plan = '' SCOPE = BOTH;\n-- BEGIN DBMS_RESOURCE_MANAGER.CREATE_PENDING_AREA;\n--       DBMS_RESOURCE_MANAGER.DELETE_PLAN_CASCADE('DAYTIME_PLAN');\n--       DBMS_RESOURCE_MANAGER.SUBMIT_PENDING_AREA; END;\n-- /","what":"决定 CPU 争用时哪类负载优先：为 OLTP 组分配 70% CPU、报表组分配 20%，将报表组的并行度限制为 4，并取消超过 30 分钟的调用（CANCEL_SQL）。用户自动映射到消费者组。","when":"用于白天报表拖慢 OLTP 的混合负载、限制不受控的并行查询，以及自动取消失控查询。","caution":"Resource Manager 需要 Enterprise Edition（在 SE2 上非常有限）；无需额外许可。mgmt_p1 百分比只在 CPU 争用时生效，同一级别的总和不能超过 100%。CANCEL_SQL 会以 ORA-00040 终止正在运行的语句；错误的映射可能会终止关键工作，因此请仔细测试阈值。计划激活期间，CPU 限流表现为 'resmgr:cpu quantum' 等待事件。如需在 CDB 级别于 PDB 之间分配资源，请使用 CREATE_CDB_PLAN。","tags":["resource manager","dbms_resource_manager","resource plan","consumer group","cpu priority","cancel_sql","resmgr:cpu quantum","resource management"]}]},"recipes/postgres.json":{"engineId":"postgres","engine":"PostgreSQL","order":2,"recipes":[{"id":"wal-switch","task":"切换 WAL 段","category":"WAL 与 Checkpoint","command":"SELECT pg_switch_wal();","what":"关闭当前 WAL 段并切换到新段；触发归档。","when":"用于生成归档文件或在备份后强制输出 WAL。","caution":"PostgreSQL 10 之前为 pg_switch_xlog()。","since":"PostgreSQL 10","note":"该函数在 PostgreSQL 10 中更名为 pg_switch_wal()；在此之前为 pg_switch_xlog()（WAL 过去称为 'xlog'）。","tags":["wal","switch","checkpoint","archive"]},{"id":"manual-checkpoint","task":"手动执行 CHECKPOINT","category":"WAL 与 Checkpoint","command":"CHECKPOINT;","what":"将脏缓冲区写入磁盘并创建 checkpoint 记录；推进恢复起点。","when":"备份前、维护窗口中，或在停止服务器前同步到磁盘。","caution":"可能产生大量 I/O；不要在生产高峰时段运行。只有超级用户或（PG15+）pg_checkpoint 角色可以运行。","tags":["checkpoint","wal","io","flush"]},{"id":"wal-status-size","task":"显示 WAL 大小和当前 LSN","category":"WAL 与 Checkpoint","command":"SELECT pg_current_wal_lsn() AS current_lsn, pg_walfile_name(pg_current_wal_lsn()) AS current_wal_file, pg_size_pretty(sum(size)) AS wal_dir_size FROM pg_ls_waldir();","what":"返回当前 WAL LSN、正在写入的 WAL 文件名以及 pg_wal 目录的总大小。","when":"用于监控 WAL 堆积、磁盘使用和 checkpoint 间隔。","caution":"pg_ls_waldir 和 pg_walfile_name 在 PostgreSQL 10+ 中可用；访问可能需要 pg_monitor 角色。","tags":["wal","lsn","size","monitor"]},{"id":"archive-lag","task":"显示归档延迟（尚未归档的 WAL）","category":"WAL 与 Checkpoint","command":"SELECT last_archived_wal, last_failed_wal, archived_count, failed_count, pg_walfile_name(pg_current_wal_lsn()) AS current_wal FROM pg_stat_archiver;","what":"通过比较最后归档的 WAL 文件、当前正在写入的 WAL 以及归档失败计数器来显示归档延迟。","when":"用于监控 archive_command 是否落后，并及时发现归档失败。","caution":"如果 failed_count 持续增加，说明 archive_command 正在失败，pg_wal 可能被填满；请立即处理以防磁盘溢出。","tags":["archive","wal","lag","monitor"]},{"id":"create-tablespace","task":"添加新表空间","category":"存储","command":"CREATE TABLESPACE ssd_ts OWNER postgres LOCATION '/mnt/ssd/pgdata';","what":"在指定目录中定义新表空间；表和索引可以放置在该磁盘上。","when":"用于将数据分布到不同磁盘（例如快速 SSD）并分离 I/O。","caution":"该目录必须已存在、为空，且属主为 postgres 用户。不要用符号链接将数据分散到其他磁盘；请使用 PostgreSQL 自身的表空间机制。","tags":["tablespace","storage","disk","create"]},{"id":"move-table-tablespace","task":"将表移动到另一个表空间","category":"存储","command":"ALTER TABLE customer SET TABLESPACE ssd_ts;","what":"将表的所有数据物理移动到新的表空间（磁盘）。","when":"用于将表移动到更快/独立的磁盘。","caution":"移动期间会对表加 ACCESS EXCLUSIVE 锁；在移动完成前表无法访问。对于大表请在维护窗口中进行。","tags":["tablespace","table","move","storage"]},{"id":"move-index-tablespace","task":"将索引移动到另一个表空间","category":"存储","command":"ALTER INDEX customer_email_idx SET TABLESPACE ssd_ts;","what":"将索引移动到指定表空间。","when":"用于将索引放在与数据不同的磁盘上并分散 I/O。","caution":"移动期间会加 ACCESS EXCLUSIVE 锁；索引无法使用。ALTER TABLE ... SET TABLESPACE 不包括表的索引，因此无法一次移动所有索引；请分别移动每个索引。","tags":["tablespace","index","move","storage"]},{"id":"db-table-size","task":"查询数据库和表的大小","category":"存储","command":"SELECT relname AS table_name, pg_size_pretty(pg_total_relation_size(relid)) AS total_size, pg_size_pretty(pg_relation_size(relid)) AS data_only FROM pg_catalog.pg_statio_user_tables ORDER BY pg_total_relation_size(relid) DESC LIMIT 20;","what":"列出包含索引和 TOAST 在内总大小最大的 20 个表。","when":"用于分析磁盘使用情况并找出哪些表占用空间。","caution":"整个数据库的大小：SELECT pg_size_pretty(pg_database_size(current_database()));","tags":["size","table","database","storage"]},{"id":"table-bloat","task":"估算表膨胀","category":"存储","command":"SELECT schemaname, relname AS table_name, n_dead_tup AS dead_rows, n_live_tup AS live_rows, round(n_dead_tup::numeric / NULLIF(n_live_tup + n_dead_tup, 0) * 100, 1) AS dead_pct, last_autovacuum FROM pg_stat_user_tables WHERE n_dead_tup > 0 ORDER BY n_dead_tup DESC LIMIT 20;","what":"根据死元组数量和最近一次 autovacuum 时间显示膨胀比例。","when":"用于判断是否需要 VACUUM 以及 autovacuum 是否跟得上。","caution":"n_dead_tup 是根据 DML 计数器持续更新的估算值（VACUUM 后下降；ANALYZE 只修正估算）；如需精确的物理膨胀，pgstattuple 扩展（pgstattuple('customer')）更准确。","tags":["bloat","dead-tuple","vacuum","storage"]},{"id":"vacuum-analyze-table","task":"对指定表执行 VACUUM ANALYZE","category":"维护","command":"VACUUM (ANALYZE, VERBOSE) customer;","what":"清理表中的死元组，使空间可重用，并更新规划器统计信息。","when":"大批量 DELETE/UPDATE 之后，或查询计划变差时。","caution":"VACUUM 不会将磁盘空间归还给操作系统；回收空间需要 VACUUM FULL，它会加 ACCESS EXCLUSIVE 锁。普通 VACUUM 不会阻塞表。","tags":["vacuum","analyze","maintenance","cleanup"]},{"id":"vacuum-freeze","task":"执行激进的 VACUUM FREEZE","category":"维护","command":"VACUUM (FREEZE, VERBOSE, ANALYZE) customer;","what":"立即将表中所有符合条件的元组标记为 'frozen'；降低事务 ID 回卷风险。","when":"出现回卷警告时，或预防性地用于将要归档/变为只读的大表。","caution":"由于扫描整个表，会产生大量 I/O。在回卷紧急情况下，数据库实际上可能变为只读；请提前监控 datfrozenxid。","tags":["vacuum","freeze","wraparound","maintenance"]},{"id":"reindex-concurrently","task":"不加锁重建索引（REINDEX CONCURRENTLY）","category":"维护","command":"REINDEX INDEX CONCURRENTLY customer_email_idx;","what":"在不阻塞写入的情况下重建索引；消除索引膨胀。","when":"当索引膨胀/损坏，且是没有维护窗口的在线系统时。","caution":"需要 PostgreSQL 12+。如果失败，可能会留下带 _ccnew 后缀的 INVALID 索引；请检查 pg_index.indisvalid，必要时用 DROP INDEX 清理。重建整个表：REINDEX TABLE CONCURRENTLY customer;","since":"PostgreSQL 12","note":"REINDEX ... CONCURRENTLY 在 PostgreSQL 12 中引入；在此之前，REINDEX 会以 ACCESS EXCLUSIVE 锁锁定索引（无锁重建需要 CREATE INDEX CONCURRENTLY + DROP）。","tags":["reindex","concurrently","index","maintenance"]},{"id":"autovacuum-per-table","task":"为特定表调整 autovacuum","category":"维护","command":"ALTER TABLE customer SET (autovacuum_vacuum_scale_factor = 0.02, autovacuum_vacuum_threshold = 5000, autovacuum_analyze_scale_factor = 0.01);","what":"仅降低该表的 autovacuum 触发阈值，使大表/繁忙表更频繁地被清理。","when":"当 autovacuum 在有大量 UPDATE/DELETE 活动的大表上跟不上时。","caution":"过于激进的设置可能导致 autovacuum 持续运行并带来 I/O 负载。恢复设置：ALTER TABLE customer RESET (autovacuum_vacuum_scale_factor);","tags":["autovacuum","tuning","table","maintenance"]},{"id":"statistics-target","task":"提高列的统计信息目标","category":"维护","command":"ALTER TABLE customer ALTER COLUMN city SET STATISTICS 500; ANALYZE customer;","what":"提高为特定列收集的直方图/样本精细度；改善规划器的估算。","when":"当分布倾斜的列上出现糟糕的查询计划时。","caution":"较高的值（默认 100，最大 10000）会增加 ANALYZE 时间和规划时间。更改只有在 ANALYZE 之后才生效。","tags":["statistics","planner","analyze","tuning"]},{"id":"alter-system-reload","task":"修改参数并重新加载（ALTER SYSTEM）","category":"参数","command":"ALTER SYSTEM SET work_mem = '64MB'; SELECT pg_reload_conf();","what":"将参数写入 postgresql.auto.conf 并重新加载配置（对于无需重启的参数立即生效）。","when":"用于持久修改可通过 reload 生效的参数，例如 work_mem 和日志设置。","caution":"shared_buffers 和 max_connections 等部分参数只有重启才生效，reload 无效；pg_settings.context = 'postmaster' 的参数需要重启。","tags":["alter-system","reload","parameter","config"]},{"id":"session-set","task":"为会话修改参数（SET）","category":"参数","command":"SET work_mem = '256MB';","what":"仅为当前连接/会话修改参数；会话结束时恢复。","when":"当需要为单个重型查询或批处理作业临时调高某项设置时。","caution":"使用 SET LOCAL 将其限制在单个事务内。全局更改需要 ALTER SYSTEM。","tags":["set","session","parameter","work-mem"]},{"id":"show-setting","task":"显示参数的当前值","category":"参数","command":"SELECT name, setting, unit, context, source FROM pg_settings WHERE name = 'shared_buffers';","what":"显示参数的生效值、单位、生效方式（context）及其来源。","when":"用于查明参数的运行值，以及它需要重启还是 reload。","caution":"快捷方式：SHOW shared_buffers; 或 SELECT current_setting('shared_buffers'); 如果 context 列为 'postmaster'，则需要重启。","tags":["show","pg_settings","parameter","current-setting"]},{"id":"physical-replica-basebackup","task":"构建物理副本（pg_basebackup）","category":"复制","command":"pg_basebackup -h primary_host -U replicator -D /var/lib/postgresql/16/main -Fp -Xs -P -R --slot=replica_slot_1 -C","what":"从主库获取完整物理备份，流式传输 WAL（-Xs），写入 standby.signal 和 primary_conninfo（-R），并创建复制槽（-C）。","when":"用于构建新的流复制备库（只读副本或故障转移目标）。","caution":"目标目录（-D）必须为空。在主库上，pg_hba.conf 必须为 replicator 用户授予 'replication' 访问权限，并且必须有足够的 wal_keep_size / 复制槽。在 PostgreSQL 12+ 中，-R 生成 standby.signal 而不是 recovery.conf。","tags":["replication","basebackup","standby","physical"]},{"id":"promote-replica","task":"将副本提升为主库","category":"复制","command":"SELECT pg_promote(wait := true, wait_seconds := 60);","what":"使备库服务器退出恢复模式，成为可写的主库。","when":"在计划内或紧急故障转移中将备库变为新主库。","caution":"提升无法撤销；该服务器将无法再跟随旧主库。请确认旧主库确实已宕机，以防脑裂。替代方法：pg_ctl promote。","since":"PostgreSQL 12","note":"pg_promote() SQL 函数在 PostgreSQL 12 中引入；在此之前，使用触发文件（promote_trigger_file）或 pg_ctl promote 来提升备库。","tags":["replication","promote","failover","standby"]},{"id":"replication-lag","task":"显示复制延迟","category":"复制","command":"SELECT client_addr, state, sent_lsn, replay_lsn, pg_wal_lsn_diff(pg_current_wal_lsn(), replay_lsn) AS replay_lag_bytes, write_lag, flush_lag, replay_lag FROM pg_stat_replication;","what":"在主库上显示每个备库相对于主库当前 WAL 位置的回放差距（字节），以及 write/flush/replay 延迟。","when":"用于监控流复制落后多少（在主库上运行）。","caution":"在备库上以秒计的延迟：SELECT now() - pg_last_xact_replay_timestamp(); 当主库没有写入时该值也会增长，因此请结合 LSN 差距来解读。结果为空表示没有备库连接。","tags":["replication","lag","lsn","monitor"]},{"id":"replication-slot","task":"创建和删除复制槽","category":"复制","command":"SELECT pg_create_physical_replication_slot('replica_slot_1'); -- To drop: SELECT pg_drop_replication_slot('replica_slot_1');","what":"创建一个物理复制槽，保证主库在备库确认之前不会删除所需的 WAL；第二条命令删除该复制槽。","when":"用于确保备库在短时间中断期间不会丢失 WAL。","caution":"未使用/被遗弃的复制槽会导致 WAL 堆积和磁盘写满。监控复制槽状态：SELECT slot_name, active, pg_size_pretty(pg_wal_lsn_diff(pg_current_wal_lsn(), restart_lsn)) AS retained FROM pg_replication_slots; 活动的复制槽无法删除。","tags":["replication","slot","wal","cleanup"]},{"id":"logical-replication","task":"建立逻辑复制的发布和订阅","category":"复制","command":"-- Source (publisher): CREATE PUBLICATION pub_customer FOR TABLE customer; -- Target (subscriber): CREATE SUBSCRIPTION sub_customer CONNECTION 'host=primary_host dbname=appdb user=replicator password=secret' PUBLICATION pub_customer;","what":"在源端为特定表创建发布（publication），在目标端创建连接到它的订阅（subscription），从而启动表级逻辑复制。","when":"用于版本升级、将选定的表复制到另一台服务器，或单向数据分发。","caution":"需要 PostgreSQL 10+。目标表的结构必须已存在；订阅会自动执行初始 COPY。wal_level 必须为 logical（需要重启）。DDL 变更和序列值不会被复制；请在两侧分别应用结构变更，并在切换时用 setval 同步序列。","since":"PostgreSQL 10","note":"内置逻辑复制（CREATE PUBLICATION / CREATE SUBSCRIPTION）在 PostgreSQL 10 中引入；在此之前，同样的工作需要 pglogical 等外部扩展。","tags":["logical","replication","publication","subscription"]},{"id":"terminate-backend","task":"终止后端进程（pg_terminate_backend）","category":"会话","command":"SELECT pg_terminate_backend(12345);","what":"强制终止指定 PID 的后端进程；断开连接并中止正在运行的事务。","when":"用于关闭卡住/挂起的连接或持有锁的会话。","caution":"正在运行的事务会被回滚；未提交的工作会丢失。请先尝试更温和的 pg_cancel_backend。在 pg_stat_activity 中核实 PID。","tags":["terminate","backend","session","kill"]},{"id":"cancel-backend","task":"取消正在运行的查询（pg_cancel_backend）","category":"会话","command":"SELECT pg_cancel_backend(12345);","what":"取消指定 PID 上当前正在运行的查询；连接保持打开。","when":"用于在不断开连接的情况下停止长时间运行的查询。","caution":"它只取消活动查询；不会关闭处于 idle in transaction 状态的会话，后者需要 pg_terminate_backend。","tags":["cancel","backend","query","session"]},{"id":"active-blocking-queries","task":"显示活动查询和阻塞查询","category":"会话","command":"SELECT pid, usename, state, wait_event_type, wait_event, pg_blocking_pids(pid) AS blocking_pids, now() - query_start AS duration, left(query, 100) AS query FROM pg_stat_activity WHERE state <> 'idle' AND pid <> pg_backend_pid() ORDER BY duration DESC;","what":"列出所有非 idle 的后端进程、哪些 PID 阻塞了它们以及它们已运行多久。","when":"用于诊断锁/阻塞问题和长时间运行的查询。","caution":"pg_blocking_pids 在 PostgreSQL 9.6+ 中可用。如果 blocking_pids 数组非空，说明这些 PID 持有锁；必要时取消/终止该 PID。","since":"PostgreSQL 9.6","note":"pg_blocking_pids() 函数在 PostgreSQL 9.6 中引入；在此之前，查找阻塞进程需要手动关联 pg_locks。","tags":["activity","blocking","lock","session"]},{"id":"create-extension","task":"添加扩展","category":"扩展","command":"CREATE EXTENSION IF NOT EXISTS pg_stat_statements;","what":"在当前数据库中安装指定扩展（添加函数、视图、类型等）。","when":"当需要 pg_stat_statements、pgcrypto 或 postgis 等附加功能时。","caution":"对于需要 shared_preload_libraries 的扩展（如 pg_stat_statements），请先将其添加到 postgresql.conf 并重启服务器。扩展文件（contrib 包）必须已安装在服务器上。","tags":["extension","create","pg_stat_statements","plugin"]},{"id":"list-extensions","task":"列出已安装的扩展","category":"扩展","command":"SELECT extname AS extension, extversion AS version FROM pg_extension ORDER BY extname;","what":"列出当前数据库中安装的所有扩展及其版本。","when":"用于查看安装了哪些扩展以及是否需要更新。","caution":"查看可安装（但尚未安装）的扩展：SELECT * FROM pg_available_extensions; 升级：ALTER EXTENSION pg_stat_statements UPDATE;","tags":["extension","list","pg_extension","plugin"]}]},"recipes/postgres-ha.json":{"engineId":"postgres","engine":"PostgreSQL","order":1,"recipes":[{"id":"patroni-cluster-status","task":"显示 Patroni 集群状态、拓扑和历史","category":"Patroni","command":"# Members, roles, state, timeline and lag (MB):\npatronictl -c /etc/patroni/patroni.yml list <cluster>\n# Extended info (pending restart, tags) + timestamp, refresh every 5 s:\npatronictl -c /etc/patroni/patroni.yml list <cluster> -e -t -w 5\n# Leader -> replica tree (including cascading replicas):\npatronictl -c /etc/patroni/patroni.yml topology <cluster>\n# Past failovers/switchovers (timeline, LSN, reason, time):\npatronictl -c /etc/patroni/patroni.yml history <cluster>","what":"显示 Patroni 从 DCS 获取的集群视图：leader/replica 角色、成员状态（running/streaming）、timeline、复制延迟和待处理的重启；history 列出 timeline 变化及其发生时的 LSN。","when":"每次主备切换/故障转移/维护前后，以及告警触发时的首个诊断步骤。","caution":"输出反映的是 DCS 中存储的内容；如果 DCS 不可达，命令会失败或可能显示过时数据。在确认所有成员处于同一 timeline 且 Lag in MB 低于 maximum_lag_on_failover 之前，不要开始主备切换。","untested":true,"tags":["patroni","cluster status","topology","history","lag"]},{"id":"patroni-switchover","task":"使用 Patroni 执行计划内主备切换","category":"Patroni","command":"# Immediate switchover without confirmation:\npatronictl -c /etc/patroni/patroni.yml switchover <cluster> --leader <leader_member> --candidate <member> --force\n# Scheduled switchover (ISO 8601 timestamp with time zone):\npatronictl -c /etc/patroni/patroni.yml switchover <cluster> --leader <leader_member> --candidate <member> --scheduled '2026-10-10T02:00:00+03:00' --force\n# Cancel a pending scheduled switchover:\npatronictl -c /etc/patroni/patroni.yml flush <cluster> switchover","what":"有控制地降级健康的 leader 并提升所选副本（--candidate）；原 leader 会自动作为新 leader 的副本重新加入。--scheduled 将操作推迟到指定时间。","when":"用于在维护前（操作系统补丁、硬件更换、小版本升级）将主库迁移到另一节点，或在故障转移后恢复原始角色。","caution":"leader 变更时所有连接都会断开；应用必须能够重连。如果候选者的延迟超过 maximum_lag_on_failover，Patroni 会拒绝主备切换。启用 synchronous_mode 时，候选者必须是同步备库。","note":"在较早的 Patroni 版本中，--leader 选项名为 --master；Patroni 4 移除了 --master。--primary 等同于 --leader。","untested":true,"tags":["patroni","switchover","planned switchover","maintenance","leader"]},{"id":"patroni-failover","task":"使用 Patroni 执行手动故障转移","category":"Patroni","command":"# Manual failover while the leader is unhealthy (candidate is mandatory):\npatronictl -c /etc/patroni/patroni.yml failover <cluster> --candidate <member> --force\n# Verify the result and the new timeline:\npatronictl -c /etc/patroni/patroni.yml list <cluster>\npatronictl -c /etc/patroni/patroni.yml history <cluster>","what":"无论 leader 状态如何，都会提升指定的副本；用于自动故障转移未发生或选错节点的情况。","when":"当 leader 不可达/不健康而自动故障转移没有启动（例如处于 pause 模式），或必须强制迁移到特定节点时。","caution":"使用异步复制时，候选者尚未接收的事务会丢失（RPO > 0）。如果 leader 健康，请使用 switchover 而不是 failover。旧 leader 恢复后可能需要 pg_rewind（如果启用了 use_pg_rewind）或 reinit。","note":"在 Patroni 3.2+ 中，failover 必须指定 --candidate；failover 的 --leader/--primary 选项在 3.2 中已弃用，并在 Patroni 4.0 中移除。如需指定 leader，请使用 switchover。","untested":true,"tags":["patroni","failover","manual failover","promote","rpo"]},{"id":"patroni-reinit-replica","task":"使用 Patroni 重建损坏的副本（reinit）","category":"Patroni","command":"# Wipe the replica's PGDATA and rebuild it (create_replica_methods: pgbackrest/basebackup):\npatronictl -c /etc/patroni/patroni.yml reinit <cluster> <member> --wait --force\n# Take the basebackup directly from the leader instead of the backup repository:\npatronictl -c /etc/patroni/patroni.yml reinit <cluster> <member> --from-leader --wait --force","what":"删除指定副本的数据目录，并按照 Patroni 的 create_replica_methods（pgBackRest restore 或 pg_basebackup）从头重建。","when":"当副本因 timeline 不匹配、pg_rewind 失败、WAL 缺失或数据损坏而无法重新加入复制时。","caution":"该成员上的所有本地数据都会被删除；请再次确认命令针对的是正确的成员（Patroni 拒绝在 leader 上执行 reinit）。在大型数据库上这可能耗时数小时，并给 leader 增加 I/O/网络负载；期间集群会少一个副本。--from-leader 仅存在于较新的 Patroni 4.x 版本中（用 patronictl reinit --help 检查）；它会跳过 create_replica_methods，直接从 leader 执行 pg_basebackup。","untested":true,"tags":["patroni","reinit","rebuild replica","rebuild","basebackup"]},{"id":"patroni-edit-config","task":"在 Patroni 中修改集群范围参数（edit-config）并应用待处理的重启","category":"Patroni","command":"# Show the current dynamic configuration stored in the DCS:\npatronictl -c /etc/patroni/patroni.yml show-config <cluster>\n# Cluster-wide PostgreSQL parameter (distributed to all members):\npatronictl -c /etc/patroni/patroni.yml edit-config <cluster> -p 'work_mem=64MB' -p 'max_connections=500' --force\n# Patroni behavior parameter:\npatronictl -c /etc/patroni/patroni.yml edit-config <cluster> -s 'maximum_lag_on_failover=1048576' --force\n# Parameters requiring a restart are flagged as 'Pending restart':\npatronictl -c /etc/patroni/patroni.yml list <cluster> -e\n# Restart replicas first, then the leader:\npatronictl -c /etc/patroni/patroni.yml restart <cluster> --role replica --pending --force\npatronictl -c /etc/patroni/patroni.yml restart <cluster> --role leader --pending --force","what":"将 PostgreSQL 和 Patroni 参数写入 DCS 中的动态配置；Patroni 将变更应用到所有成员，立即激活可 reload 的参数，并将需要重启的参数标记为 'Pending restart'。","when":"用于在 Patroni 管理的集群的每个节点上一致地修改 max_connections、shared_buffers 或 work_mem 等参数。","caution":"在 Patroni 下，不要手动编辑 postgresql.conf 或使用 ALTER SYSTEM：Patroni 会重写 postgresql.conf，而 postgresql.auto.conf 会覆盖 Patroni 的设置，导致节点之间出现配置漂移。备库上的 max_connections、max_worker_processes、max_wal_senders、max_prepared_transactions 和 max_locks_per_transaction 不能低于主库；请据此规划重启顺序。","untested":true,"tags":["patroni","edit-config","parameter","pending restart","dcs"]},{"id":"patroni-pause-resume","task":"将 Patroni 置于维护模式并恢复（pause/resume）","category":"Patroni","command":"# Maintenance mode: automatic failover and Patroni's control over PostgreSQL stop\npatronictl -c /etc/patroni/patroni.yml pause <cluster> --wait\npatronictl -c /etc/patroni/patroni.yml list <cluster>          # should show 'Maintenance mode: on'\n# ... maintenance work (e.g. manual PostgreSQL restart, DCS maintenance) ...\npatronictl -c /etc/patroni/patroni.yml resume <cluster> --wait","what":"将集群置于维护模式：Patroni 不执行自动故障转移，也不会重启已停止的 PostgreSQL；resume 恢复正常管理。","when":"在 DCS（etcd/Consul）维护、Patroni 升级期间，或必须在 Patroni 不干预的情况下手动管理 PostgreSQL 时。","caution":"如果暂停期间主库崩溃，不会自动故障转移，中断将持续。维护后不要忘记 resume；在告警中加入 'Maintenance mode' 检查。暂停期间手动将 PostgreSQL 作为主库启动可能导致脑裂。","untested":true,"tags":["patroni","pause","resume","maintenance mode","maintenance"]},{"id":"patroni-dcs-health","task":"检查 Patroni DCS（etcd/Consul）和 REST API 健康状况","category":"Patroni","command":"# etcd v3 (default API in etcd 3.4+): member health, leader, raft term and DB size\netcdctl --endpoints=https://<etcd1>:2379,https://<etcd2>:2379,https://<etcd3>:2379 --cacert=/etc/etcd/ca.crt endpoint health --cluster -w table\netcdctl --endpoints=https://<etcd1>:2379 --cacert=/etc/etcd/ca.crt endpoint status --cluster -w table\netcdctl --endpoints=https://<etcd1>:2379 --cacert=/etc/etcd/ca.crt alarm list\n# Consul:\nconsul members\nconsul operator raft list-peers\n# Patroni REST API (on each member):\ncurl -s http://<member>:8008/cluster | python3 -m json.tool\ncurl -s -o /dev/null -w '%{http_code}\\n' http://<member>:8008/primary   # 200 = leader, 503 = not leader","what":"检查 Patroni 持有 leader 锁的 DCS 的 quorum、leader 和容量，并通过 Patroni REST API 查看每个成员对自身角色的认知。/primary 端点是负载均衡器（HAProxy）健康检查的基础。","when":"当 Patroni 日志出现 'DCS is not accessible'、发生意外的降级/故障转移后，以及作为例行健康检查。","caution":"如果 DCS 失去 quorum，Patroni 无法续约 leader 锁，会将主库降级为只读（除非启用了 failsafe_mode，Patroni 3.0+）；因此 etcd 中断就等于数据库写入中断。当 etcd 数据库大小达到配额（默认 2 GB）时，NOSPACE 告警会停止写入。DCS 至少应在 3 个节点上运行，并使用独立的磁盘。","untested":true,"tags":["patroni","etcd","consul","dcs health","rest api"]},{"id":"pgbackrest-archive-config","task":"为 pgBackRest 配置 WAL 归档（archive-push/archive-get）","category":"备份与 PITR","command":"# postgresql.conf (changing archive_mode requires a restart):\nwal_level = replica\narchive_mode = on\narchive_command = 'pgbackrest --stanza=<stanza> archive-push %p'\n# On the restore/standby side (in postgresql.conf on PG12+):\nrestore_command = 'pgbackrest --stanza=<stanza> archive-get %f \"%p\"'\n\n# /etc/pgbackrest/pgbackrest.conf (database host, local repo example):\n[global]\nrepo1-path=/var/lib/pgbackrest\nrepo1-retention-full=2\narchive-async=y\nspool-path=/var/spool/pgbackrest\nprocess-max=4\ncompress-type=zst\nstart-fast=y\n\n[<stanza>]\npg1-path=/var/lib/pgsql/16/data","what":"让 PostgreSQL 将每个已完成的 WAL 段发送到 pgBackRest 存储库（archive-push），并在恢复期间从那里获取（archive-get）；archive-async 通过 spool 目录使推送并行且异步。","when":"作为使用 pgBackRest 建立物理备份和 PITR 的第一步，在 stanza-create 之前。","caution":"只要 archive_command 失败，WAL 就会在 pg_wal 中堆积，磁盘写满时主库会停止；请监控 pg_stat_archiver.failed_count。使用 Patroni 时，请通过 patroni.yml / edit-config 而不是 postgresql.conf 设置这些参数。从两个不同的集群归档到同一个 stanza 会损坏存储库。","untested":true,"tags":["pgbackrest","archiving","archive_command","wal archive","configuration"]},{"id":"pgbackrest-stanza-create-check","task":"创建 pgBackRest stanza 并验证归档（stanza-create/check）","category":"备份与 PITR","command":"# Create the stanza in the repository (once):\nsudo -u postgres pgbackrest --stanza=<stanza> --log-level-console=info stanza-create\n# Verify configuration and archiving end to end (switches WAL and waits for the segment to reach the repo):\nsudo -u postgres pgbackrest --stanza=<stanza> --log-level-console=info check","what":"stanza-create 在存储库中创建集群的元数据（system identifier、版本）；check 通过一次实际的 WAL 切换测试 PostgreSQL 连接、archive_command 以及 WAL 是否到达存储库。","when":"在初始安装时、修改 archive_command 或存储库后，以及作为监控/cron 健康检查。","caution":"请以 postgres（或存储库所有者）身份运行命令；以 root 运行会破坏文件属主。大版本升级后需要 stanza-upgrade，而不是 stanza-create。如果 check 失败，即使备份完成，PITR 也可能无法进行。","untested":true,"tags":["pgbackrest","stanza","stanza-create","check","archive verification"]},{"id":"pgbackrest-backup-full-diff-incr","task":"使用 pgBackRest 进行全量、差异和增量备份","category":"备份与 PITR","command":"sudo -u postgres pgbackrest --stanza=<stanza> --type=full backup\nsudo -u postgres pgbackrest --stanza=<stanza> --type=diff backup   # changes since the last full\nsudo -u postgres pgbackrest --stanza=<stanza> --type=incr backup   # changes since the last backup of any type\n# cron example (postgres user): full on Sunday, diff on other days\n# 30 1 * * 0   pgbackrest --stanza=<stanza> --type=full backup\n# 30 1 * * 1-6 pgbackrest --stanza=<stanza> --type=diff backup","what":"全量备份复制整个集群，diff 复制自上次全量以来变化的文件，incr 复制自上次任意类型备份以来变化的文件；结束时会验证所需的 WAL 已归档，并按保留策略自动运行 expire。","when":"在常规备份计划中：通常每周全量 + 每日 diff/incr，大型集群可更频繁地做 incr。","caution":"如果之前没有全量备份，diff/incr 请求会自动升级为全量。incr 链越长，还原时间和依赖性越大；链中丢失一个备份会使之后的备份不可用。为减轻主库负担，可用 backup-standby=y 从备库备份（仍需要主库进行协调）。","untested":true,"tags":["pgbackrest","backup","full","differential","incremental"]},{"id":"pgbackrest-info","task":"显示 pgBackRest 备份目录和 WAL 归档范围（info）","category":"备份与 PITR","command":"sudo -u postgres pgbackrest --stanza=<stanza> info\n# JSON for monitoring/automation:\nsudo -u postgres pgbackrest --stanza=<stanza> --output=json info\n# Details of a single backup (databases, LSN range):\nsudo -u postgres pgbackrest --stanza=<stanza> --set=<backup_label> info","what":"列出 stanza 状态、每个备份的标签、类型、开始/结束时间、大小、所依赖的备份，以及存储库中 WAL 归档的最小/最大范围。","when":"在还原/PITR 之前确认目标时间已被覆盖、监控备份是否定期运行，以及检查保留策略。","caution":"info 只说明备份存在，并不说明可还原；请通过定期测试还原和 pgbackrest verify 进行验证。如果 WAL 最小/最大范围中存在间隙，PITR 无法跨越它。","untested":true,"tags":["pgbackrest","info","backup list","catalog","monitoring"]},{"id":"pgbackrest-restore-latest","task":"使用 pgBackRest 还原最新备份","category":"备份与 PITR","command":"# PostgreSQL must be stopped and PGDATA empty (otherwise use --delta)\nsudo systemctl stop postgresql-16\nsudo -u postgres pgbackrest --stanza=<stanza> --log-level-console=info restore\n# From a specific backup: --set=<backup_label>\n# When restoring to another server for testing, disable archiving:\n# sudo -u postgres pgbackrest --stanza=<stanza> --archive-mode=off restore\nsudo systemctl start postgresql-16","what":"还原最新备份并写入 recovery.signal 和 restore_command；启动时 PostgreSQL 回放所有已归档的 WAL 到最近的一致点，并在新的 timeline 上提升。","when":"当主库的数据目录丢失/损坏时，或定期测试备份可还原性时。","caution":"如果还原出的集群和旧集群同时归档到同一个 stanza，存储库会损坏；测试还原请使用 --archive-mode=off。在 Patroni 下，请先 pause 集群或停止 Patroni，否则 Patroni 会干预。如果存在 PGDATA 之外的表空间或路径，请检查 --tablespace-map 和 link 选项。","untested":true,"tags":["pgbackrest","restore","restore","recovery","disaster recovery"]},{"id":"pgbackrest-pitr-restore","task":"使用 pgBackRest 还原到某个时间点（PITR，--type=time）","category":"备份与 PITR","command":"sudo systemctl stop postgresql-16\nsudo -u postgres pgbackrest --stanza=<stanza> --delta --type=time \"--target=2026-10-08 14:30:00+03\" --target-action=pause restore\nsudo systemctl start postgresql-16\n# Recovery stops at the target (read-only); validate the data:\npsql -X -c \"SELECT pg_is_in_recovery(), pg_last_xact_replay_timestamp();\"\n# If correct, end recovery and open read-write on a new timeline:\npsql -X -c \"SELECT pg_wal_replay_resume();\"","what":"还原一个在目标时间之前完成的备份，并只回放 WAL 到目标点；target-action=pause 会停在目标点以便验证数据，pg_wal_replay_resume() 执行提升。","when":"用于回到误操作 DELETE/DROP/UPDATE 等逻辑错误发生之前的那一刻。","caution":"该集群上目标点之后的所有变更都会丢失；尽可能在单独的服务器上运行 PITR，并用 pg_dump 只将受影响的数据迁回。在目标中明确写出时区。pgBackRest 2.24+ 会自动选择目标点之前的合适备份；在更早的版本中，请用 --set 指定一个在目标点之前完成的备份。如果目标早于备份的结束时间，还原无法达到一致状态。","untested":true,"tags":["pgbackrest","pitr","point in time recovery","point in time","recovery"]},{"id":"pgbackrest-delta-restore-standby","task":"使用 pgBackRest delta 还原快速重建备库","category":"备份与 PITR","command":"sudo systemctl stop postgresql-16\n# Keeps the existing PGDATA and fetches only differing files; writes standby.signal:\nsudo -u postgres pgbackrest --stanza=<stanza> --delta --type=standby \"--recovery-option=primary_conninfo=host=<primary_host> port=5432 user=replicator application_name=<standby_name>\" restore\nsudo systemctl start postgresql-16\npsql -X -c \"SELECT status, sender_host FROM pg_stat_wal_receiver;\"","what":"--delta 将现有 PGDATA 中的文件与备份进行比较（大小/时间戳和校验和），只复制不同的文件；--type=standby 写入 standby.signal，recovery-option 添加 primary_conninfo。备库先从归档追赶，然后切换到流复制。","when":"用于在不完整复制的情况下，将落后或 timeline 分叉的备库，或 pg_rewind 失败的旧主库重新变为副本。","caution":"delta 会用备份中的文件替换/删除目标目录中不同或多余的文件；在错误的服务器上运行会破坏该服务器的数据。pgBackRest 拒绝在运行中的 PostgreSQL 上还原，但请自行确认服务已停止。将密码保存在 .pgpass 中，而不是 primary_conninfo 中。","untested":true,"tags":["pgbackrest","delta restore","rebuild standby","rebuild","replica"]},{"id":"pgbackrest-retention-expire","task":"配置 pgBackRest 保留策略并运行 expire","category":"备份与 PITR","command":"# /etc/pgbackrest/pgbackrest.conf [global] - count based:\nrepo1-retention-full-type=count\nrepo1-retention-full=2\nrepo1-retention-diff=6\n# Time-based alternative (at least a 14-day PITR window):\n# repo1-retention-full-type=time\n# repo1-retention-full=14\n\n# Expire runs automatically after each backup; to trigger it manually:\nsudo -u postgres pgbackrest --stanza=<stanza> --log-level-console=info expire","what":"定义保留多少个全量/差异备份（count）或保留多少天的全量备份（time）；expire 会删除策略之外的备份、依赖它们的 diff/incr 备份，以及不再需要的 WAL 归档。","when":"用于控制存储库大小，并保证 RPO/PITR 窗口要求（例如能够回退 14 天）。","caution":"PITR 窗口受最旧的保留全量备份限制；每周全量且 retention-full=2 时，最多只能回退约 2 周。repo1-retention-archive 默认跟随全量保留；设置得更低会使较旧的备份只能恢复到其一致点。expire 无法撤销。","untested":true,"tags":["pgbackrest","retention","retention period","expire","backup cleanup"]},{"id":"standby-manual-config","task":"手动配置备库（standby.signal + primary_conninfo）","category":"复制","command":"# On the standby, with PostgreSQL stopped (PGDATA came from a backup/copy):\ntouch $PGDATA/standby.signal\ncat >> $PGDATA/postgresql.auto.conf <<'EOF'\nprimary_conninfo = 'host=<primary_host> port=5432 user=replicator application_name=<standby_name>'\nprimary_slot_name = '<slot>'\nEOF\npg_ctl -D $PGDATA start\n# PG13+: change the connection on a running standby without restart (e.g. point it to a new primary):\npsql -X -c \"ALTER SYSTEM SET primary_conninfo = 'host=<new_primary_host> port=5432 user=replicator application_name=<standby_name>'\" -c \"SELECT pg_reload_conf();\"","what":"standby.signal 文件使服务器以备库身份启动；primary_conninfo 定义流复制连接，primary_slot_name 指定要使用的复制槽。在 PG13+ 中，primary_conninfo 和 primary_slot_name 可通过 reload 修改。","when":"用于将未使用 pg_basebackup -R 准备的数据目录（pgBackRest、快照、rsync）变为备库，或将现有备库指向新主库。","caution":"复制槽必须事先在主库上创建（pg_create_physical_replication_slot），否则 walreceiver 会持续失败。请将密码放在 ~/.pgpass 中而不是 conninfo 中。application_name 必须与 synchronous_standby_names 中的名称完全一致。如果忘记 standby.signal，服务器会以主库身份启动并转到单独的 timeline。","since":"PostgreSQL 12","note":"PostgreSQL 12 之前这些设置位于 recovery.conf 中（standby_mode = on）；PG12 将其移到 standby.signal 和 postgresql.conf。PG13 之前，修改 primary_conninfo 需要重启。","tags":["standby","standby.signal","primary_conninfo","replica setup","streaming"]},{"id":"standby-wal-receiver-lag","task":"在备库上监控 WAL 接收和回放延迟（pg_stat_wal_receiver）","category":"复制","command":"-- Run on the standby:\nSELECT pg_is_in_recovery() AS in_recovery,\n       pg_last_wal_receive_lsn() AS receive_lsn,\n       pg_last_wal_replay_lsn() AS replay_lsn,\n       pg_size_pretty(pg_wal_lsn_diff(pg_last_wal_receive_lsn(), pg_last_wal_replay_lsn())) AS replay_backlog,\n       now() - pg_last_xact_replay_timestamp() AS last_replay_age,\n       pg_get_wal_replay_pause_state() AS pause_state;\nSELECT pid, status, sender_host, sender_port, slot_name, received_tli, flushed_lsn, latest_end_lsn, now() - last_msg_receipt_time AS since_last_msg FROM pg_stat_wal_receiver;","what":"显示备库已接收多少 WAL、已应用多少（回放积压）、最后回放事务的时长、回放是否暂停，以及 walreceiver 连接状态（哪个主库、复制槽、timeline、最后消息时间）。","when":"用于在主库不可达或级联备库上诊断延迟；出现 'standby behind' 告警时，区分网络（接收）问题和回放（I/O、冲突）问题。","caution":"如果 pg_stat_wal_receiver 不返回任何行，说明备库没有进行流复制（从归档获取或已断开）。即使主库没有写入，last_replay_age 也会增长；请结合 receive_lsn/replay_lsn 的差值来解读。如果接收在推进而回放积压在增长，说明备库上有长查询（max_standby_streaming_delay）或存在 I/O 瓶颈。","since":"PostgreSQL 14","note":"pg_get_wal_replay_pause_state() 在 PostgreSQL 14 中加入；在此之前只有 pg_is_wal_replay_paused()。查询的其余部分在 PG10+ 上可用。","tags":["standby","replication lag","wal receiver","replay","lag"]},{"id":"pg-rewind-old-primary","task":"故障转移后使用 pg_rewind 将旧主库重新加入为副本","category":"复制","command":"# Prerequisite (must be set BEFORE the incident): wal_log_hints = on OR data checksums; full_page_writes = on\npg_controldata $PGDATA | grep -E 'wal_log_hints|checksum'\n# 1) The old primary must be stopped:\npg_ctl -D $PGDATA stop -m fast\n# 2) Dry run first:\npg_rewind --target-pgdata=$PGDATA --source-server='host=<new_primary_host> port=5432 user=postgres dbname=postgres' --progress --dry-run\n# 3) Actual rewind + standby configuration (standby.signal + primary_conninfo):\npg_rewind --target-pgdata=$PGDATA --source-server='host=<new_primary_host> port=5432 user=postgres dbname=postgres' --progress --write-recovery-conf\n# 4) Review postgresql.auto.conf copied from the source (port, primary_slot_name), then start:\npg_ctl -D $PGDATA start","what":"通过从新主库复制 timeline 分叉点之后变化的块，使旧主库的数据目录与新主库一致；无需完整 basebackup，几分钟内即可将其变为副本。","when":"当旧主库在故障转移/提升后恢复，并且落后于新 timeline、存在分叉的变更时。","caution":"故障转移后留在旧主库上、从未到达新主库的事务会被永久删除。pg_rewind 还会从源端复制配置文件（postgresql.conf、postgresql.auto.conf、pg_hba.conf）；启动前请修正端口和复制槽设置。如果分叉点以来所需的 WAL 已不在源端，请用 -c（--restore-target-wal）从归档获取。源连接需要超级用户或拥有文档中所列函数权限的角色。在 Patroni 中，通过 use_pg_rewind: true 自动完成。","since":"PostgreSQL 13","note":"-R/--write-recovery-conf 和 -c/--restore-target-wal 在 PostgreSQL 13 中加入；在 PG13+ 中，pg_rewind 会在未正常停止的目标上自行执行崩溃恢复（用 --no-ensure-shutdown 禁用）。pg_rewind 自 9.5 起即已存在。","tags":["pg_rewind","after failover","old primary","timeline","rejoin"]},{"id":"sync-standby-quorum","task":"配置基于 quorum（ANY）或优先级（FIRST）的同步复制","category":"复制","command":"-- Quorum: commit completes once any 1 standby in the list confirms\nALTER SYSTEM SET synchronous_standby_names = 'ANY 1 (stby_a, stby_b)';\n-- Priority based: the first connected standby in the list is sync, the others 'potential'\n-- ALTER SYSTEM SET synchronous_standby_names = 'FIRST 1 (stby_a, stby_b)';\nSELECT pg_reload_conf();\nSELECT application_name, state, sync_state, sync_priority FROM pg_stat_replication ORDER BY application_name;","what":"定义提交完成前必须确认的备库：ANY N 基于 quorum（sync_state = quorum），FIRST N 使列表中按顺序最先连接的 N 个备库为同步（sync），其余为等待中的备库（potential）。确认级别由 synchronous_commit 设置（remote_write、on、remote_apply）。","when":"用于要求故障转移时零数据丢失（RPO = 0）的系统；有多个备库时首选 ANY，这样单个备库故障不会停止写入。","caution":"如果所需数量的同步备库没有连接，提交会无报错地挂起，应用会卡住。列表中的名称必须与备库的 application_name 一致。紧急情况下，单个会话可以用 SET synchronous_commit = local 绕过。在 Patroni 下，请用 synchronous_mode 管理，而不是手动设置。","since":"PostgreSQL 10","note":"FIRST 语法在 PostgreSQL 9.6 中加入，ANY（quorum commit）在 PostgreSQL 10 中加入；不带关键字的 'N (a, b)' 形式表示 FIRST。","tags":["synchronous replication","synchronous_standby_names","quorum","rpo","sync"]},{"id":"slot-retained-wal-monitor","task":"监控复制槽保留的 WAL 并安全删除被遗弃的复制槽","category":"复制","command":"SELECT slot_name, slot_type, database, active, active_pid, wal_status,\n       pg_size_pretty(pg_wal_lsn_diff(pg_current_wal_lsn(), restart_lsn)) AS retained_wal,\n       pg_size_pretty(safe_wal_size) AS safe_wal_size,\n       xmin, catalog_xmin\nFROM pg_replication_slots\nORDER BY pg_wal_lsn_diff(pg_current_wal_lsn(), restart_lsn) DESC NULLS LAST;\n-- Safe drop: drops only if not active (returns 0 rows if active)\nSELECT pg_drop_replication_slot(slot_name) FROM pg_replication_slots WHERE slot_name = '<slot>' AND NOT active;","what":"显示每个复制槽在主库上保留了多少 WAL、wal_status（reserved/extended/unreserved/lost）、max_slot_wal_keep_size 下剩余的安全余量（safe_wal_size），以及阻碍 VACUUM 的 xmin/catalog_xmin 值；第二个查询只在复制槽未被使用时删除它。","when":"当 pg_wal 目录增长、出现磁盘告警以及例行监控时；在备库或订阅者被永久移除后清理复制槽。","caution":"非活动的复制槽会无限制地保留 WAL，可能填满磁盘并使主库停止；持有 xmin/catalog_xmin 的复制槽还会阻止死元组清理，造成膨胀/回卷风险。删除复制槽后，其备库/订阅者将无法从中断处继续（需要重建）；删除前请核实其所有者。请在主库上运行（pg_current_wal_lsn() 在备库上会失败）。","since":"PostgreSQL 13","note":"wal_status 和 safe_wal_size 在 PostgreSQL 13 中加入。PG16 为逻辑复制槽增加了 conflicting，PG17 增加了 inactive_since；inactive_since 是判断复制槽被遗弃的最直接指标。","tags":["replication slot","wal accumulation","disk full","drop slot","retained wal"]},{"id":"slot-max-wal-keep-size","task":"限制复制槽可保留的 WAL（max_slot_wal_keep_size）","category":"复制","command":"ALTER SYSTEM SET max_slot_wal_keep_size = '100GB';\nSELECT pg_reload_conf();\nSELECT slot_name, active, wal_status, pg_size_pretty(safe_wal_size) AS safe_wal_size FROM pg_replication_slots;","what":"为复制槽在 pg_wal 中可保留的 WAL 设置上限（默认 -1 = 无限制）；超过时 checkpoint 会删除旧 WAL，复制槽变为 wal_status = 'lost'。该参数通过 reload 生效。","when":"作为每个使用复制槽的集群的安全措施，防止断开的备库或订阅者填满主库磁盘并使整个系统停止。","caution":"该限制以牺牲复制槽所有者为代价保住主库：复制槽为 'lost' 的备库必须重建，逻辑订阅必须重新创建。请选择一个低于 pg_wal 磁盘可用空间、但大于预计最长中断期间所产生 WAL 的值。如果 wal_status 为 'unreserved'，复制槽即将丢失；请发出告警。","since":"PostgreSQL 13","note":"max_slot_wal_keep_size 在 PostgreSQL 13 中加入；在此之前无法限制复制槽保留的 WAL，唯一的保护是监控加手动删除复制槽。","tags":["max_slot_wal_keep_size","slot limit","wal accumulation","disk protection","replication slot"]},{"id":"logical-add-table-refresh","task":"将表添加到发布并刷新订阅（REFRESH PUBLICATION）","category":"逻辑复制","command":"-- Publisher:\nALTER PUBLICATION pub_app ADD TABLE orders;\n-- PG15+: all tables in a schema: ALTER PUBLICATION pub_app ADD TABLES IN SCHEMA app_schema;\nSELECT pubname, schemaname, tablename FROM pg_publication_tables WHERE pubname = 'pub_app';\n-- Subscriber (the table structure must already exist):\nALTER SUBSCRIPTION sub_app REFRESH PUBLICATION WITH (copy_data = true);\nSELECT srrelid::regclass AS table_name, srsubstate FROM pg_subscription_rel ORDER BY 1;","what":"在发布端将新表添加到发布；在订阅端，REFRESH PUBLICATION 将新表纳入订阅，并以 copy_data = true 启动初始复制（tablesync）。srsubstate：i = init，d = data copy，f = copy finished，s = synchronized，r = ready。","when":"当向正在运行的逻辑复制中添加新表，或从发布中移除表时。","caution":"即使是 FOR ALL TABLES 或 TABLES IN SCHEMA 发布，订阅端在运行 REFRESH 之前也不会接收新表。如果订阅端不存在该表，REFRESH 会失败；DDL 不会被复制。如果被发布的表没有 PRIMARY KEY 或 REPLICA IDENTITY，UPDATE/DELETE 在发布端会失败。大表的初始复制会在发布端长时间占用一个复制槽和快照。","since":"PostgreSQL 10","note":"ADD TABLES IN SCHEMA 在 PostgreSQL 15 中加入（PG15 还增加了基于 WHERE 的行过滤和列列表）。","tags":["logical replication","publication","add table","refresh publication","subscription"]},{"id":"logical-subscription-monitor","task":"监控逻辑复制（pg_stat_subscription、应用错误、复制槽延迟）","category":"逻辑复制","command":"-- Subscriber:\nSELECT subname, pid, leader_pid, relid::regclass AS syncing_table, received_lsn, latest_end_lsn, now() - last_msg_receipt_time AS since_last_msg, latest_end_time FROM pg_stat_subscription;\nSELECT subname, apply_error_count, sync_error_count, stats_reset FROM pg_stat_subscription_stats;\n-- Publisher:\nSELECT slot_name, plugin, database, active, wal_status,\n       pg_size_pretty(pg_wal_lsn_diff(pg_current_wal_lsn(), confirmed_flush_lsn)) AS confirmed_lag,\n       pg_size_pretty(pg_wal_lsn_diff(pg_current_wal_lsn(), restart_lsn)) AS retained_wal\nFROM pg_replication_slots WHERE slot_type = 'logical';","what":"在订阅端显示 apply/tablesync worker、已接收和已确认的 LSN、最后消息时间以及累计的 apply/sync 错误数；在发布端显示逻辑复制槽未确认（confirmed_flush）的 WAL 及其保留的 WAL 总量。","when":"在建立逻辑复制期间、在升级/迁移切换时确认延迟接近零，以及用于错误告警。","caution":"如果 pid 为 NULL，说明 apply worker 没有运行（订阅被禁用，或反复失败并重启）；请检查服务器日志。apply_error_count 上升表示存在冲突。停滞的订阅会在发布端堆积 WAL 和 catalog_xmin；请监控发布端磁盘和系统目录膨胀。","since":"PostgreSQL 15","note":"pg_stat_subscription_stats 在 PostgreSQL 15 中加入，pg_stat_subscription.leader_pid 在 PG16 中加入（并行应用）；PG17 增加了 worker_type 列。pg_stat_subscription 的基本列自 PG10 起即已存在。","tags":["logical replication","subscription monitoring","pg_stat_subscription","apply error","lag"]},{"id":"logical-conflict-skip","task":"解决逻辑复制冲突（disable_on_error、SKIP）","category":"逻辑复制","command":"-- 1) Prevent a failing apply worker from looping forever:\nALTER SUBSCRIPTION sub_app SET (disable_on_error = true);\n-- 2) Find the finish LSN of the conflicting transaction in the subscriber log:\n--    CONTEXT: ... in transaction 749, finished at 0/5035378\n-- 3a) Preferred: fix/delete the conflicting row on the subscriber, then re-enable:\nALTER SUBSCRIPTION sub_app ENABLE;\n-- 3b) Or skip the whole remote transaction (NONE of its changes are applied on the subscriber):\nALTER SUBSCRIPTION sub_app SKIP (lsn = '0/5035378');\nALTER SUBSCRIPTION sub_app ENABLE;\nSELECT subname, subenabled, subskiplsn FROM pg_subscription;","what":"当重复键、行缺失或权限问题等错误使订阅端的应用停止时：disable_on_error 会自动禁用订阅；可以修复冲突数据后 ENABLE，或者 SKIP 由日志中 'finished at' LSN 标识的整个远程事务。","when":"当 apply worker 因订阅端的直接写入、不完整的初始复制或双向写入而失败并不断重启时。","caution":"SKIP 还会丢弃该事务中不冲突的变更，两侧将永久不一致；优先修复数据，并在 SKIP 后比对该表。在冲突解决之前，发布端的复制槽会持续堆积 WAL。请通过权限阻止应用写入订阅端的表。","since":"PostgreSQL 15","note":"SKIP 和 disable_on_error 在 PostgreSQL 15 中加入；在此之前，同样的操作通过在订阅端用 pg_replication_origin_advance() 推进 origin 来完成（风险更高）。","tags":["logical replication","conflict","conflict","skip","duplicate key"]},{"id":"logical-sync-sequences","task":"在逻辑复制中将序列值同步到订阅端","category":"逻辑复制","command":"# Generate the setval script on the publisher and apply it on the subscriber (at cutover, after writes are stopped):\npsql -h <publisher_host> -d <db> -XAtc \"SELECT format('SELECT setval(%L, %s, %s);', quote_ident(schemaname) || '.' || quote_ident(sequencename), COALESCE(last_value, start_value), (last_value IS NOT NULL)::text) FROM pg_sequences;\" | psql -h <subscriber_host> -d <db> -X","what":"由于逻辑复制不传递序列（包括 identity）值，此方法读取发布端每个序列的最后值，生成并直接执行在订阅端设置相同值的 setval 命令。","when":"在通过逻辑复制进行升级/迁移的切换时，就在将应用指向订阅端之前。","caution":"如果在发布端仍有写入时运行，这些值会立即过时，切换后会出现重复键错误；请在停止写入后运行，或增加安全余量。DDL 同样不会被复制：请将结构变更（ALTER TABLE、新索引）分别应用到两侧，先应用到订阅端。对于无权读取的序列，pg_sequences 返回的 last_value 为 NULL；请以所有者或超级用户身份运行命令。","since":"PostgreSQL 10","tags":["logical replication","sequence","setval","ddl not replicated","cutover"]},{"id":"logical-orphan-slot-cleanup","task":"删除订阅并清理发布端的孤立逻辑复制槽","category":"逻辑复制","command":"-- Subscriber: while the publisher is unreachable DROP SUBSCRIPTION cannot drop the remote slot and fails; detach the slot first:\nALTER SUBSCRIPTION sub_app DISABLE;\nALTER SUBSCRIPTION sub_app SET (slot_name = NONE);\nDROP SUBSCRIPTION sub_app;\n-- Publisher: find and drop orphaned logical slots (including tablesync slots pg_<suboid>_sync_<relid>_<sysid>):\nSELECT slot_name, active, pg_size_pretty(pg_wal_lsn_diff(pg_current_wal_lsn(), restart_lsn)) AS retained_wal FROM pg_replication_slots WHERE slot_type = 'logical' AND NOT active;\nSELECT pg_drop_replication_slot('sub_app');","what":"在不触及远程复制槽的情况下删除订阅（slot_name = NONE）；然后查找并删除发布端已没有消费者的逻辑复制槽以及残留的 tablesync 复制槽。","when":"当发布端不可达时必须删除订阅、迁移完成后，或者 active = false 的逻辑复制槽在发布端堆积 WAL 时。","caution":"孤立的逻辑复制槽会同时保留 WAL 和 catalog_xmin：磁盘被填满，系统目录膨胀。但删除复制槽后，该订阅将无法继续；删除前请确认没有任何订阅者（包括其他服务器上的）使用此复制槽。活动的复制槽无法删除；必须先终止已连接的 walsender。","since":"PostgreSQL 10","tags":["logical replication","slot cleanup","drop subscription","orphaned slot","catalog_xmin"]},{"id":"pg-upgrade-link-runbook","task":"使用 pg_upgrade --link 进行大版本升级（端到端操作手册）","category":"升级","command":"# As the postgres user, in a writable directory; example 16 -> 17\nexport OLD_BIN=/usr/pgsql-16/bin NEW_BIN=/usr/pgsql-17/bin\nexport OLD_DATA=/var/lib/pgsql/16/data NEW_DATA=/var/lib/pgsql/17/data\n# 0) initdb the new cluster with the same encoding/locale and checksum setting as the old one (-k if the old cluster has checksums)\n# 1) Compatibility check (can run while the old cluster is up, changes no data):\n$NEW_BIN/pg_upgrade --old-bindir=$OLD_BIN --new-bindir=$NEW_BIN --old-datadir=$OLD_DATA --new-datadir=$NEW_DATA --check\n# 2) Stop the old cluster and upgrade with hard links:\n$OLD_BIN/pg_ctl -D $OLD_DATA stop -m fast\n$NEW_BIN/pg_upgrade --old-bindir=$OLD_BIN --new-bindir=$NEW_BIN --old-datadir=$OLD_DATA --new-datadir=$NEW_DATA --link --jobs=4\n# 3) Carry postgresql.conf/pg_hba.conf customizations over to the new cluster, start it:\n$NEW_BIN/pg_ctl -D $NEW_DATA -l /tmp/pg17_start.log start\n# 4) Gather statistics, run generated scripts if any:\n$NEW_BIN/vacuumdb --all --analyze-in-stages\n# psql -X -f update_extensions.sql   (if pg_upgrade generated it)\n# 5) After validation and a fresh backup, delete the old cluster:\n./delete_old_cluster.sh","what":"先用 --check 验证兼容性，然后通过将旧集群的数据文件硬链接到新版本而不是复制，在几分钟内完成大版本升级，与数据量无关；之后分阶段收集统计信息并删除旧集群。","when":"用于以可接受的短暂中断（数分钟）将大型集群原地迁移到新的大版本。","caution":"使用 --link 时，一旦新集群启动，旧集群就无法再安全使用；唯一的回退方式是升级前所做的备份。新旧数据目录必须位于同一文件系统上。在 analyze 完成之前，新集群上的计划可能较差。如果使用 pgBackRest，请运行 stanza-upgrade 并立即进行新的全量备份；旧版本的备份无法还原到新集群中。","note":"物理备库不通过 pg_upgrade 升级：主库升级后，请使用文档中的 rsync --hard-links --size-only 方法更新它们或重建。PG17 之前逻辑复制槽不会迁移（旧集群为 PG17+ 时会迁移）；PG18 之前规划器统计信息不会迁移。","tags":["pg_upgrade","major upgrade","version upgrade","link","check"]},{"id":"logical-upgrade-near-zero-downtime","task":"使用逻辑复制实现接近零停机的大版本升级","category":"升级","command":"# 0) On the old version wal_level = logical (restart); every table needs a PRIMARY KEY or REPLICA IDENTITY\n# 1) Move roles and schema to the new version (a 'role postgres already exists' error is expected):\npg_dumpall -h <old_host> --globals-only | psql -h <new_host> -X -d postgres\npg_dump -h <old_host> --schema-only --create -d <db> | psql -h <new_host> -X -d postgres\n# 2) Publication on the old version, subscription on the new version (initial copy + continuous stream):\npsql -h <old_host> -d <db> -X -c \"CREATE PUBLICATION upg_pub FOR ALL TABLES;\"\npsql -h <new_host> -d <db> -X -c \"CREATE SUBSCRIPTION upg_sub CONNECTION 'host=<old_host> dbname=<db> user=<repl_user>' PUBLICATION upg_pub;\"\n# 3) Wait until all tables are 'r' (ready) and lag is ~0:\npsql -h <new_host> -d <db> -X -c \"SELECT srsubstate, count(*) FROM pg_subscription_rel GROUP BY 1;\"\npsql -h <old_host> -d <db> -X -c \"SELECT slot_name, pg_size_pretty(pg_wal_lsn_diff(pg_current_wal_lsn(), confirmed_flush_lsn)) AS lag FROM pg_replication_slots WHERE slot_name = 'upg_sub';\"\n# 4) Cutover: stop application writes, confirm lag = 0, synchronize sequences (logical-sync-sequences)\n# 5) Drop the subscription (the remote slot is dropped too), point the application to the new server:\npsql -h <new_host> -d <db> -X -c \"DROP SUBSCRIPTION upg_sub;\"","what":"在新版本上构建一个空集群，迁移结构，并通过逻辑复制持续从旧版本传输数据；只有最后的切换步骤（停止写入、同步序列、重定向）会造成短暂中断。反方向的第二条复制可以保留回退路径。","when":"当 pg_upgrade 所需的停机不可接受、同时迁移到新硬件/操作系统，或跨越多个大版本时。","caution":"DDL、序列、大对象和物化视图内容不会被复制；迁移期间请冻结结构。在没有主键/replica identity 的表上，UPDATE/DELETE 在发布端会失败。在大型数据库上，初始复制耗时较长，并会在旧服务器上占用 WAL 和 catalog_xmin。每个数据库都需要自己的发布/订阅。切换前比对行数/校验和。","since":"PostgreSQL 10","note":"PostgreSQL 17 中加入的 pg_createsubscriber 可在无需初始复制的情况下将物理备库转换为逻辑订阅者，消除大型数据库的初始复制时间。","tags":["upgrade","logical replication","zero downtime migration","major upgrade","cutover"]},{"id":"pgbouncer-show-pools-stats","task":"显示 PgBouncer 连接池、统计信息和连接（SHOW POOLS/STATS/CLIENTS/SERVERS）","category":"PgBouncer","command":"# Connect to the PgBouncer admin console (user must be in admin_users/stats_users):\npsql -h 127.0.0.1 -p 6432 -U pgbouncer -d pgbouncer -X\nSHOW POOLS;    -- if cl_waiting > 0 and maxwait grows, the pool is saturated\nSHOW STATS;    -- per-database total/average query, transaction and wait times\nSHOW CLIENTS;  -- client connections (state, source address, linked server connection)\nSHOW SERVERS;  -- server connections opened to PostgreSQL\nSHOW CONFIG;","what":"针对每个数据库/用户连接池，显示活动和等待中的客户端（cl_active、cl_waiting）、活动/空闲的服务端连接（sv_active、sv_idle）、最长等待时间（maxwait）以及查询/事务计数器。","when":"当应用报告连接失败/超时、规划连接池大小，以及在维护前查看活动连接时。","caution":"管理控制台不接受 SQL，只运行 PgBouncer 命令。如果 cl_waiting 持续偏高，请先检查服务端是否有长事务或锁；盲目调大 default_pool_size 会把 PostgreSQL 推向 max_connections 上限并带来内存压力。SHOW STATS 的值是累计值；平均值指 stats_period 区间。","untested":true,"tags":["pgbouncer","show pools","connection pooling","statistics","connection pool"]},{"id":"pgbouncer-pause-resume-reload","task":"维护期间用 PgBouncer 挂起连接（PAUSE/RESUME）并重新加载配置（RELOAD）","category":"PgBouncer","command":"psql -h 127.0.0.1 -p 6432 -U pgbouncer -d pgbouncer -X\nRELOAD;            -- apply pgbouncer.ini and auth_file changes without dropping connections\nPAUSE <db>;        -- closes server connections once running queries/transactions finish; new requests are queued\n-- ... maintenance: switchover, PostgreSQL restart, etc. ...\nRESUME <db>;\n-- If the host in [databases] was changed (after RELOAD), gradually refresh server connections:\nRECONNECT <db>;","what":"PAUSE 在不断开客户端连接的情况下排空 PostgreSQL 端的连接，并将新查询排队；RESUME 让队列重新流动。这样，短暂的主库重启或主备切换对应用来说只是短暂的延迟，而不是错误。","when":"在短时维护窗口（主备切换、小版本重启、参数重启）期间，以及应用 pgbouncer.ini/userlist 变更时。","caution":"PAUSE 只有在所有活动事务结束后才返回；长时间运行的查询会拖住 PAUSE（必要时在 PostgreSQL 中终止该会话）。query_wait_timeout（默认 120 s）到期时，等待中的客户端会收到错误；请在此时间内完成维护。PAUSE 后忘记 RESUME 就是完全中断。部分设置（listen_addr、listen_port 等）不会随 RELOAD 改变，需要重启。","untested":true,"tags":["pgbouncer","pause","resume","reload","maintenance"]},{"id":"pgbouncer-pool-mode","task":"PgBouncer pool_mode 选择和基本容量规划","category":"PgBouncer","command":"; /etc/pgbouncer/pgbouncer.ini\n[databases]\nappdb = host=<primary_host> port=5432 dbname=appdb pool_mode=transaction\nreportdb = host=<primary_host> port=5432 dbname=appdb pool_mode=session\n\n[pgbouncer]\nlisten_addr = 0.0.0.0\nlisten_port = 6432\nauth_type = scram-sha-256\nauth_file = /etc/pgbouncer/userlist.txt\npool_mode = transaction\nmax_client_conn = 5000\ndefault_pool_size = 20\nreserve_pool_size = 5\nmax_prepared_statements = 200\nquery_wait_timeout = 120","what":"session 模式在客户端连接的整个生命周期内将其绑定到一个服务端连接；transaction 模式只在事务期间分配服务端连接，用几十个 PostgreSQL 连接服务数千个客户端；statement 模式在每条查询后归还连接，不允许多语句事务。pool_mode 也可以按数据库覆盖。","when":"transaction 模式适用于打开大量短连接的 OLTP/微服务应用；session 模式适用于依赖会话状态的工作（报表、迁移、使用 LISTEN 的服务）。","caution":"在 transaction 模式下，事务之间不保留会话状态：SET（SET LOCAL 除外）、会话级 advisory 锁、LISTEN、WITH HOLD 游标、跨事务的临时表以及 SQL 级 PREPARE 都不可靠。协议级 prepared statement 仅在 PgBouncer 1.21+ 且 max_prepared_statements > 0 时受支持（1.24 起默认 200，之前为 0）。default_pool_size 乘以（数据库，用户）对的数量不得超过 PostgreSQL 的 max_connections（减去 superuser_reserved_connections 余量）。","untested":true,"tags":["pgbouncer","pool_mode","transaction mode","connection pooling","sizing"]}]},"recipes/postgres-ops.json":{"engineId":"postgres","engine":"PostgreSQL","order":2,"recipes":[{"id":"pgss-top-queries","task":"查找开销最大的查询（pg_stat_statements：总时间和平均时间）","category":"性能","command":"-- By total time: queries consuming the most server time\nSELECT queryid,\n       calls,\n       round(total_exec_time::numeric, 1) AS total_ms,\n       round(mean_exec_time::numeric, 2) AS mean_ms,\n       round(stddev_exec_time::numeric, 2) AS stddev_ms,\n       round((100 * total_exec_time / sum(total_exec_time) OVER ())::numeric, 1) AS pct_total,\n       rows / NULLIF(calls, 0) AS rows_per_call,\n       left(regexp_replace(query, '\\s+', ' ', 'g'), 120) AS query\nFROM pg_stat_statements\nWHERE dbid = (SELECT oid FROM pg_database WHERE datname = current_database())\nORDER BY total_exec_time DESC\nLIMIT 20;\n\n-- By mean time per call: individually slow queries\nSELECT queryid,\n       calls,\n       round(mean_exec_time::numeric, 2) AS mean_ms,\n       round(max_exec_time::numeric, 2) AS max_ms,\n       round(mean_plan_time::numeric, 2) AS mean_plan_ms,\n       left(regexp_replace(query, '\\s+', ' ', 'g'), 120) AS query\nFROM pg_stat_statements\nWHERE calls >= 50\nORDER BY mean_exec_time DESC\nLIMIT 20;","what":"列出当前数据库中消耗服务器时间最多的规范化查询及其在总量中的占比；第二个查询列出单次调用最慢的查询。","when":"CPU/IO 负载上升或用户反映\"数据库很慢\"时的第一站；用于决定从哪里开始调优。","caution":"在将 pg_stat_statements 加入 shared_preload_libraries、重启服务器并在数据库中运行 CREATE EXTENSION 之前，它不会收集任何数据。数值是自上次重置以来的累计值。stddev_ms 相对 mean_ms 较高，表示计划或数据分布在各次调用之间存在差异。mean_plan_time 只有在 pg_stat_statements.track_planning = on 时才会填充（默认 off；启用会增加开销）。查看其他用户的查询文本需要超级用户或 pg_read_all_stats 角色。","since":"PostgreSQL 13","note":"在 PostgreSQL 13 中，total_time/mean_time 列更名为 total_exec_time/mean_exec_time，规划时间（total_plan_time）有了单独的列；在 PG12 及更早版本中请使用旧名称。","tags":["pg_stat_statements","slow query","most expensive query","top sql","performance","query tuning"]},{"id":"pgss-io-temp-heavy","task":"查找造成磁盘 I/O、临时文件和 WAL 的查询（pg_stat_statements）","category":"性能","command":"SELECT queryid,\n       calls,\n       shared_blks_hit,\n       shared_blks_read,\n       round(100.0 * shared_blks_hit / NULLIF(shared_blks_hit + shared_blks_read, 0), 1) AS hit_pct,\n       pg_size_pretty(temp_blks_written * current_setting('block_size')::bigint) AS temp_written,\n       pg_size_pretty(wal_bytes) AS wal,\n       left(regexp_replace(query, '\\s+', ' ', 'g'), 120) AS query\nFROM pg_stat_statements\nORDER BY shared_blks_read + temp_blks_read + temp_blks_written DESC\nLIMIT 20;","what":"显示从 shared buffers 之外读取块最多、写入临时文件最多（无法放入 work_mem 的排序/哈希）以及生成 WAL 最多的查询，并给出其缓存命中率。","when":"当磁盘读延迟上升、出现临时文件告警（log_temp_files）或 WAL 量突然增长时，用于找出责任查询。","caution":"shared_blks_read 也会计入由操作系统页缓存提供的读取；真实的磁盘时间需要 track_io_timing = on（先用 pg_test_timing 测量其开销）。如果 temp_written 很高，通常的解决方法是按角色/会话调高 work_mem 或修复查询/索引；全局调高 work_mem 会增加内存风险。","since":"PostgreSQL 13","note":"wal_bytes/wal_records 列在 PG13 中引入。在 PG17 中，blk_read_time/blk_write_time 更名为 shared_blk_read_time/shared_blk_write_time，并增加了 local_blk_*_time。","tags":["pg_stat_statements","io","temp files","temp_blks_written","work_mem","wal"]},{"id":"pgss-reset","task":"重置 pg_stat_statements 统计信息（全部或单个查询）","category":"性能","command":"-- Last reset time and number of entries evicted because the table was full (dealloc)\nSELECT dealloc, stats_reset FROM pg_stat_statements_info;\n\n-- Reset a single query only (userid, dbid, queryid; 0 = all)\nSELECT pg_stat_statements_reset(0, 0, 5927525816675996975);\n\n-- Reset all statistics\nSELECT pg_stat_statements_reset();","what":"完全重置或按用户/数据库/queryid 重置 pg_stat_statements 计数器；pg_stat_statements_info 显示上次重置时间以及因空间不足被淘汰的条目数。","when":"在调优变更（索引、参数、发布）之前开始一次干净的测量，或单独测量某个查询的新计划。","caution":"重置无法撤销；依赖累计计数器的监控/APM 工具会出现断档，因此请先做快照（参见计划回归操作指南）。如果 dealloc 持续增长，说明 pg_stat_statements.max 太小（修改需要重启）。默认只有超级用户可以运行；可用 GRANT EXECUTE ON FUNCTION 授权。","since":"PostgreSQL 14","note":"带参数的重置（userid、dbid、queryid）在 PG12 中引入，pg_stat_statements_info 视图在 PG14 中引入。PG17 增加了第四个参数 minmax_only：pg_stat_statements_reset(0, 0, 0, true) 只重置最小/最大时间；stats_since 和 minmax_stats_since 列也是 PG17 引入的。","tags":["pg_stat_statements","reset","clear statistics","queryid","statistics","baseline"]},{"id":"auto-explain-setup","task":"使用 auto_explain 记录慢查询的执行计划","category":"性能","command":"-- 1) Per session: effective immediately, no restart (superuser)\nLOAD 'auto_explain';\nSET auto_explain.log_min_duration = '500ms';\nSET auto_explain.log_analyze = on;\nSET auto_explain.log_buffers = on;\nSET auto_explain.log_timing = off;\nSET auto_explain.log_nested_statements = on;\n\n-- 2) Persistent: loaded by new sessions, a reload is enough.\n--    The module must be LOADed in this session before ALTER SYSTEM accepts auto_explain.* parameters.\nLOAD 'auto_explain';\nALTER SYSTEM SET session_preload_libraries = 'auto_explain';\nALTER SYSTEM SET auto_explain.log_min_duration = '2s';\nALTER SYSTEM SET auto_explain.log_analyze = on;\nALTER SYSTEM SET auto_explain.log_buffers = on;\nALTER SYSTEM SET auto_explain.log_timing = off;\nALTER SYSTEM SET auto_explain.sample_rate = 0.1;\nSELECT pg_reload_conf();","what":"将超过阈值的查询执行计划写入服务器日志（log_analyze 开启时附带实际行数和缓冲区数据）；捕获以应用真实参数生成的计划。","when":"当查询只在应用中或使用特定参数时才变慢，且无法用手动 EXPLAIN 重现时；用于捕获间歇性的计划变化。","caution":"log_analyze = on 会给每个被采样的查询增加插桩开销，包括低于阈值的查询；log_timing = off 和 sample_rate < 1 可降低开销。在生产环境中 log_min_duration = 0 会刷爆日志。ALTER SYSTEM SET session_preload_libraries 会覆盖现有值；请先检查 SHOW session_preload_libraries。session_preload_libraries 不影响现有会话；要在每个进程中加载，需要 shared_preload_libraries 加重启。","note":"auto_explain 是 contrib 模块，不需要 CREATE EXTENSION。还可使用 log_wal（PG13+）和 log_settings（PG12+）选项。","tags":["auto_explain","execution plan","slow query","log","plan capture","explain"]},{"id":"explain-analyze-buffers-wal","task":"使用 EXPLAIN (ANALYZE, BUFFERS, SETTINGS, WAL) 深入阅读计划","category":"性能","command":"-- ANALYZE really executes the statement: run DML inside a transaction and roll it back\nBEGIN;\nEXPLAIN (ANALYZE, BUFFERS, SETTINGS, WAL, VERBOSE)\nUPDATE customer SET balance = balance + 1 WHERE city = 'Ankara';\nROLLBACK;\n\n-- For SELECT\nEXPLAIN (ANALYZE, BUFFERS, SETTINGS)\nSELECT name FROM customer ORDER BY name DESC LIMIT 10 OFFSET 1000;","what":"返回带真实执行统计信息的计划：BUFFERS 显示每个节点的 shared hit/read/dirtied 和临时块，SETTINGS 显示影响规划的非默认参数，WAL 显示生成的 WAL 记录数/字节数。","when":"用于分析估算行数与实际行数之间的差距、溢出到磁盘的排序/哈希（Sort Method: external merge、temp read/written）以及 DML 的 WAL 成本。","caution":"ANALYZE 会真正执行语句；DML 务必在 BEGIN/ROLLBACK 中运行（触发器和序列递增仍会发生）。阅读提示：估算 'rows' 与 'actual rows' 相差 10 倍或以上，说明存在统计信息问题；对于循环内的节点，时间和行数是每次循环的值，乘以 loops 才是总量；Index Only Scan 上 'Heap Fetches' 较高表示 visibility map 过时，'Workers Planned' > 'Workers Launched' 表示 worker 池已耗尽。计时开销较高时使用 TIMING OFF；COSTS OFF 还会隐藏 JIT 部分。","since":"PostgreSQL 13","note":"SETTINGS 选项在 PG12 中引入，WAL 在 PG13 中引入。PG16 增加了 GENERIC_PLAN，PG17 增加了 SERIALIZE 和 MEMORY；在 PG18 中，使用 ANALYZE 时 BUFFERS 默认开启。","tags":["explain analyze","buffers","execution plan","reading plans","wal","performance"]},{"id":"plan-regression-queryid","task":"按 queryid 捕获计划回归（pg_stat_statements 快照对比）","category":"性能","command":"-- 1) Take a reference snapshot (before deploy/upgrade/ANALYZE)\nCREATE TABLE IF NOT EXISTS pgss_snapshot AS\n  SELECT now() AS snap_time, userid, dbid, queryid, calls, total_exec_time,\n         shared_blks_hit, shared_blks_read, left(query, 200) AS query\n  FROM pg_stat_statements WITH NO DATA;\nINSERT INTO pgss_snapshot\n  SELECT now(), userid, dbid, queryid, calls, total_exec_time,\n         shared_blks_hit, shared_blks_read, left(query, 200)\n  FROM pg_stat_statements;\n\n-- 2) Later: queries whose time per call grew the most since the snapshot\nWITH base AS (\n  SELECT DISTINCT ON (userid, dbid, queryid) *\n  FROM pgss_snapshot\n  ORDER BY userid, dbid, queryid, snap_time DESC\n)\nSELECT s.queryid,\n       s.calls - b.calls AS new_calls,\n       round((b.total_exec_time / b.calls)::numeric, 2) AS old_mean_ms,\n       round(((s.total_exec_time - b.total_exec_time) / (s.calls - b.calls))::numeric, 2) AS new_mean_ms,\n       round((b.shared_blks_hit + b.shared_blks_read)::numeric / b.calls, 0) AS old_blks_per_call,\n       round((s.shared_blks_hit + s.shared_blks_read - b.shared_blks_hit - b.shared_blks_read)::numeric\n             / (s.calls - b.calls), 0) AS new_blks_per_call,\n       left(regexp_replace(s.query, '\\s+', ' ', 'g'), 100) AS query\nFROM pg_stat_statements s\nJOIN base b USING (userid, dbid, queryid)\nWHERE s.calls - b.calls >= 10\n  AND b.calls > 0\nORDER BY ((s.total_exec_time - b.total_exec_time) / (s.calls - b.calls))\n         / NULLIF(b.total_exec_time / b.calls, 0) DESC NULLS LAST\nLIMIT 20;\n\n-- 3) Is the suspicious queryid running right now?\nSELECT pid, state, now() - query_start AS runtime, left(query, 100) AS query\nFROM pg_stat_activity\nWHERE query_id = -4094682686355101727;","what":"将 pg_stat_statements 的累计计数器保存到一张表中，并按 queryid 与之后的状态进行比较；将快照之后调用的每次调用平均时间和缓冲区与旧值并列显示。","when":"用于找出在发布、大版本升级、统计信息刷新或参数变更后计划发生变化的查询（\"昨天还很快，今天变慢\"）。","caution":"如果在快照和比较之间运行了 pg_stat_statements_reset()，或条目因空间不足被淘汰（dealloc），则差值没有意义；请检查 pg_stat_statements_info.stats_reset。每次调用的缓冲区增加比时间增加更能可靠地说明计划发生了变化，因为它与缓存无关。要确认计划，请用真实参数 EXPLAIN 该查询或使用 auto_explain；PostgreSQL 没有内置的计划固定功能（需要 pg_hint_plan 扩展）。完成后删除快照表。","since":"PostgreSQL 14","note":"pg_stat_activity.query_id 和 compute_query_id 参数在 PG14 中引入（默认 auto：加载 pg_stat_statements 时计算）。EXPLAIN VERBOSE 输出中的 'Query Identifier' 行显示相同的值。步骤 1 和 2 在 PG13 上也可用。","tags":["plan regression","queryid","pg_stat_statements","snapshot","plan change","performance"]},{"id":"parallel-query-usage","task":"检查并行查询容量和当前 worker 使用情况","category":"性能","command":"-- Parallel worker pool settings\nSELECT name, current_setting(name) AS value\nFROM pg_settings\nWHERE name IN ('max_worker_processes', 'max_parallel_workers', 'max_parallel_workers_per_gather',\n               'max_parallel_maintenance_workers', 'parallel_setup_cost', 'parallel_tuple_cost',\n               'min_parallel_table_scan_size', 'min_parallel_index_scan_size')\nORDER BY name;\n\n-- Queries currently running in parallel and their worker counts\nSELECT a.pid AS leader_pid,\n       count(w.pid) AS workers_running,\n       current_setting('max_parallel_workers')::int AS max_parallel_workers,\n       now() - a.query_start AS runtime,\n       left(a.query, 80) AS query\nFROM pg_stat_activity a\nJOIN pg_stat_activity w ON w.leader_pid = a.pid AND w.pid <> a.pid\nGROUP BY a.pid, a.query_start, a.query\nORDER BY workers_running DESC;\n\n-- Pin / reset the planned worker count for a single table\nALTER TABLE orders SET (parallel_workers = 4);\nALTER TABLE orders RESET (parallel_workers);","what":"显示并行查询的限制以及每个 leader 进程当前正在使用多少个并行 worker；演示按表设置 worker 数量。","when":"当分析型查询比预期慢、计划显示 'Workers Planned' > 'Workers Launched'，或 CPU 核心空闲时。","caution":"max_parallel_workers 受 max_worker_processes 限制（需要重启），且该池与逻辑复制 worker 及其他后台 worker 共享。在 OLTP 中，较高的 max_parallel_workers_per_gather 会在并发下使 CPU 饱和；每个 worker 还会使用自己的 work_mem。写入数据或锁定行的查询（CREATE TABLE AS / SELECT INTO 除外）、游标以及调用 PARALLEL UNSAFE 函数的查询不会得到并行计划。ALTER TABLE ... SET (parallel_workers) 会短暂获取 ShareUpdateExclusiveLock。","since":"PostgreSQL 13","note":"pg_stat_activity.leader_pid 在 PG13 中引入；在此之前，并行 worker 只显示为 backend_type = 'parallel worker'，无法与其 leader 关联。","tags":["parallel query","parallel worker","leader_pid","max_parallel_workers","gather","cpu"]},{"id":"jit-impact-check","task":"测量 JIT 编译对查询时间的影响","category":"性能","command":"-- Share of JIT time in total execution time (PG15+)\nSELECT queryid,\n       calls,\n       jit_functions,\n       round((jit_generation_time + jit_inlining_time + jit_optimization_time + jit_emission_time)::numeric, 1) AS jit_ms,\n       round(total_exec_time::numeric, 1) AS total_ms,\n       round((100 * (jit_generation_time + jit_inlining_time + jit_optimization_time + jit_emission_time)\n             / NULLIF(total_exec_time, 0))::numeric, 1) AS jit_pct,\n       left(regexp_replace(query, '\\s+', ' ', 'g'), 100) AS query\nFROM pg_stat_statements\nWHERE jit_functions > 0\nORDER BY jit_pct DESC NULLS LAST\nLIMIT 20;\n\n-- Compare on a single query: check the \"JIT:\" section at the end of the output and Execution Time\nSET jit = on;\nEXPLAIN (ANALYZE) SELECT city, sum(balance) FROM customer GROUP BY city;\nSET jit = off;\nEXPLAIN (ANALYZE) SELECT city, sum(balance) FROM customer GROUP BY city;\nRESET jit;","what":"按查询显示 JIT 编译时间（generation、inlining、optimization、emission）在执行时间中的占比；通过在 JIT 开启和关闭的情况下运行同一查询来测量净影响。","when":"当短的 OLTP 或报表查询出现数十到数百毫秒的不明延迟时；尤其是在分区很多的表上，被夸大的成本估算会触发 JIT。","caution":"只有当计划成本超过 jit_above_cost（默认 100000）时 JIT 才会启动；在成本估算被夸大时，短查询的编译时间可能比执行时间还长。与其全局禁用，不如先考虑为受影响的角色/数据库执行 ALTER ROLE ... SET jit = off，或调高 jit_above_cost。EXPLAIN (ANALYZE, COSTS OFF) 不会输出 JIT 部分。如果 PostgreSQL 编译时没有 LLVM 支持，则永远不会使用 JIT；用 SELECT pg_jit_available(); 检查。","since":"PostgreSQL 15","note":"jit_* 列在 PG15 中加入 pg_stat_statements；PG17 增加了 jit_deform_count/jit_deform_time。JIT 在 PG11 中引入，自 PG12 起默认开启。","tags":["jit","llvm","jit_above_cost","pg_stat_statements","compilation time","performance"]},{"id":"blocking-tree","task":"构建阻塞树（找出根阻塞者）","category":"会话","command":"WITH RECURSIVE tree AS (\n  SELECT a.pid, a.pid AS root_pid, 0 AS level, ARRAY[a.pid] AS path\n  FROM pg_stat_activity a\n  WHERE cardinality(pg_blocking_pids(a.pid)) = 0\n    AND EXISTS (SELECT 1 FROM pg_stat_activity w WHERE a.pid = ANY (pg_blocking_pids(w.pid)))\n  UNION ALL\n  SELECT w.pid, t.root_pid, t.level + 1, t.path || w.pid\n  FROM tree t\n  JOIN pg_stat_activity w ON t.pid = ANY (pg_blocking_pids(w.pid))\n  WHERE w.pid <> ALL (t.path)\n), node AS (\n  SELECT DISTINCT ON (pid) pid, root_pid, level\n  FROM tree\n  ORDER BY pid, level DESC\n)\nSELECT repeat('  ', n.level) || n.pid AS pid_tree,\n       n.root_pid,\n       pg_blocking_pids(n.pid) AS blocked_by,\n       a.usename,\n       a.state,\n       a.wait_event_type || ':' || a.wait_event AS wait,\n       now() - a.xact_start AS xact_age,\n       left(regexp_replace(a.query, '\\s+', ' ', 'g'), 80) AS query\nFROM node n\nJOIN pg_stat_activity a USING (pid)\nORDER BY n.root_pid, n.level, n.pid;","what":"使用 pg_blocking_pids() 递归追踪等待链；从根阻塞者（不等待任何人）开始，以缩进树的形式显示每个等待中的会话、直接阻塞它的 PID 以及其事务时长。","when":"当应用出现大量超时或队列堆积时；用于在几十个等待者中找出需要干预的那一个根会话。","caution":"根往往是一个被遗忘的、持有锁的 'idle in transaction' 会话；query 列显示的是该会话的最后一条语句，而不是获取锁的那条。等待 ACCESS EXCLUSIVE 的 DDL 也会阻塞排在它后面的简单 SELECT，并显示为中间节点。pg_blocking_pids() 会短暂锁定锁管理器；不要在紧密循环中运行此查询。可通过多个分支到达的会话只在其最深层级显示一次。","since":"PostgreSQL 9.6","tags":["blocking","lock tree","lock chain","pg_blocking_pids","blocking session","lock"]},{"id":"lock-wait-detail","task":"按对象和锁模式详细检查锁等待（pg_locks）","category":"会话","command":"SELECT DISTINCT\n       w.pid AS waiting_pid,\n       w.locktype,\n       coalesce(w.relation::regclass::text, 'xid ' || w.transactionid, w.virtualxid, w.objid::text) AS target,\n       w.mode AS wanted_mode,\n       b.pid AS blocker_pid,\n       b.mode AS blocker_mode,\n       b.granted AS blocker_holds,\n       now() - wa.state_change AS waiting_for,\n       left(wa.query, 60) AS waiting_query,\n       left(ba.query, 60) AS blocker_query\nFROM pg_locks w\nJOIN pg_locks b\n  ON b.pid = ANY (pg_blocking_pids(w.pid))\n AND b.locktype = w.locktype\n AND b.database IS NOT DISTINCT FROM w.database\n AND b.relation IS NOT DISTINCT FROM w.relation\n AND b.page IS NOT DISTINCT FROM w.page\n AND b.tuple IS NOT DISTINCT FROM w.tuple\n AND b.virtualxid IS NOT DISTINCT FROM w.virtualxid\n AND b.transactionid IS NOT DISTINCT FROM w.transactionid\n AND b.classid IS NOT DISTINCT FROM w.classid\n AND b.objid IS NOT DISTINCT FROM w.objid\n AND b.objsubid IS NOT DISTINCT FROM w.objsubid\nJOIN pg_stat_activity wa ON wa.pid = w.pid\nJOIN pg_stat_activity ba ON ba.pid = b.pid\nWHERE NOT w.granted\nORDER BY waiting_for DESC, waiting_pid;","what":"对每个未授予的锁请求（granted = false），显示目标对象（表、事务 ID 等）、请求的模式、阻塞者 PID 及其模式、阻塞者是否实际持有该锁，以及大致的等待时间。","when":"用于了解是哪个表和哪种锁模式冲突导致了阻塞；例如等待 AccessExclusiveLock 的 DDL，或表现为 transactionid 等待的行锁。","caution":"由于匹配使用 pg_blocking_pids()，只会列出真正冲突的请求。blocker_holds = false 表示阻塞者尚未持有该锁，但以冲突模式排在前面（锁队列）。locktype = 'transactionid' 是行级冲突：等待者会一直等到修改该行的事务结束；行本身在 pg_locks 中不可见。在大型系统上 pg_locks 可能包含数千行；不要频繁运行。waiting_for 是基于 state_change 的近似值。","since":"PostgreSQL 9.6","tags":["pg_locks","lock wait","lock waiting","accessexclusivelock","transactionid","lock"]},{"id":"long-idle-transactions","task":"查找长时间运行和 'idle in transaction' 的事务","category":"会话","command":"SELECT pid,\n       usename,\n       datname,\n       application_name,\n       client_addr,\n       state,\n       now() - xact_start AS xact_age,\n       now() - state_change AS in_state_for,\n       backend_xid,\n       age(backend_xmin) AS xmin_age,\n       left(regexp_replace(query, '\\s+', ' ', 'g'), 100) AS last_query\nFROM pg_stat_activity\nWHERE xact_start IS NOT NULL\n  AND backend_type = 'client backend'\n  AND pid <> pg_backend_pid()\n  AND (now() - xact_start > interval '5 minutes' OR state LIKE 'idle in transaction%')\nORDER BY xact_start;","what":"显示打开的事务超过 5 分钟或在事务中处于空闲状态的会话；包括事务时长、当前状态持续时间、已分配的 XID 以及其持有的 xmin 的年龄。","when":"当膨胀增长、VACUUM 报告 'dead row versions cannot be removed yet'、出现锁等待，或怀疑连接池泄漏时。","caution":"长事务（尤其是 REPEATABLE READ/SERIALIZABLE 或持有打开游标的事务）会通过 backend_xmin 阻碍 VACUUM 的清理边界（xmin horizon），在整个数据库中造成膨胀和 XID 老化。'idle in transaction (aborted)' 是遇到错误并等待 ROLLBACK 的会话。永久解决方法：按角色设置 idle_in_transaction_session_timeout，并在应用中正确设定事务边界。","tags":["long transaction","idle in transaction","xmin","bloat","session","long running"]},{"id":"terminate-idle-in-transaction","task":"有控制地批量终止长时间 'idle in transaction' 的会话","category":"会话","command":"-- 1) Dry run first: who will be affected?\nSELECT pid, usename, application_name, client_addr,\n       now() - state_change AS idle_for, left(query, 80) AS last_query\nFROM pg_stat_activity\nWHERE state IN ('idle in transaction', 'idle in transaction (aborted)')\n  AND now() - state_change > interval '15 minutes'\n  AND backend_type = 'client backend'\n  AND pid <> pg_backend_pid();\n\n-- 2) Terminate; wait up to 5 s for each process to exit\nSELECT pid, usename, now() - state_change AS idle_for,\n       pg_terminate_backend(pid, 5000) AS terminated\nFROM pg_stat_activity\nWHERE state IN ('idle in transaction', 'idle in transaction (aborted)')\n  AND now() - state_change > interval '15 minutes'\n  AND backend_type = 'client backend'\n  AND pid <> pg_backend_pid();","what":"先列出事务保持打开并空闲超过指定时间的会话，然后终止它们；timeout 参数会一直等到进程实际退出（或超时到期）。","when":"用于紧急干预：被遗忘的、持有锁的会话造成阻塞，或阻碍 xmin horizon 使 VACUUM 停滞时。","caution":"pg_cancel_backend 对 'idle in transaction' 会话无效（没有正在运行的查询可取消）；需要 pg_terminate_backend，且所有未提交的变更都会被回滚。用 usename/application_name 缩小过滤范围；排除复制、备份和监控会话。连接池会重新打开被终止的连接。如果超时到期，函数会发出警告并返回 false。作为永久解决方法，请按角色设置 idle_in_transaction_session_timeout。","since":"PostgreSQL 14","note":"双参数的 pg_terminate_backend(pid, timeout) 在 PG14 中引入；在此之前只有 pg_terminate_backend(pid)，它在发送信号后立即返回。","tags":["pg_terminate_backend","idle in transaction","terminate session","kill session","bulk","lock"]},{"id":"role-timeouts","task":"按角色和数据库设置 statement_timeout / lock_timeout","category":"会话","command":"-- Application role: short queries, short lock waits, a limit on forgotten transactions\nALTER ROLE web_app SET statement_timeout = '30s';\nALTER ROLE web_app SET lock_timeout = '5s';\nALTER ROLE web_app SET idle_in_transaction_session_timeout = '10min';\n\n-- Let the reporting role run longer, only in this database\nALTER ROLE report_app IN DATABASE mydb SET statement_timeout = '15min';\n\n-- Show configured role/database settings\nSELECT coalesce(r.rolname, '(all)') AS role,\n       coalesce(d.datname, '(all)') AS database,\n       s.setconfig\nFROM pg_db_role_setting s\nLEFT JOIN pg_roles r ON r.oid = s.setrole\nLEFT JOIN pg_database d ON d.oid = s.setdatabase\nORDER BY 1, 2;\n\n-- Revert\nALTER ROLE web_app RESET lock_timeout;","what":"在角色（以及可选的数据库）级别持久设置超时参数，使每类负载在不改动全局设置的情况下以自己的限制运行。","when":"用于防止失控查询耗尽资源、DDL/锁队列使应用卡死，以及被遗忘的事务造成膨胀。","caution":"设置只对新会话生效；请回收连接池中的现有连接。用户可以在其会话中用 SET 覆盖该值；这不是安全边界。不要在 postgresql.conf 中全局设置 statement_timeout：pg_dump、VACUUM 和 CREATE INDEX 等维护作业也会被取消。优先级：角色+数据库 > 角色 > 数据库 > postgresql.conf。","note":"idle_in_transaction_session_timeout 在 PG9.6 中引入，idle_session_timeout 在 PG14 中引入，transaction_timeout 在 PG17 中引入。","tags":["statement_timeout","lock_timeout","idle_in_transaction_session_timeout","alter role","query timeout","timeout"]},{"id":"ddl-lock-timeout-retry","task":"使用 lock_timeout 和重试安全地执行 DDL（避免锁队列）","category":"会话","command":"-- Single attempt: give up if the lock is not acquired within 3 s instead of queueing and blocking traffic\nSET lock_timeout = '3s';\nALTER TABLE customer ADD COLUMN IF NOT EXISTS segment text;\nRESET lock_timeout;\n\n-- Automatic retry: up to 5 attempts, 3 s apart\nDO $$\nDECLARE\n  attempt int := 0;\nBEGIN\n  LOOP\n    attempt := attempt + 1;\n    BEGIN\n      SET LOCAL lock_timeout = '2s';\n      ALTER TABLE customer ADD COLUMN IF NOT EXISTS segment text;\n      RAISE NOTICE 'OK, attempt %', attempt;\n      EXIT;\n    EXCEPTION WHEN lock_not_available THEN\n      RAISE NOTICE 'lock not acquired, attempt %', attempt;\n      IF attempt >= 5 THEN RAISE; END IF;\n      PERFORM pg_sleep(3);\n    END;\n  END LOOP;\nEND $$;","what":"防止需要 ACCESS EXCLUSIVE 的 DDL 在拿不到锁时无限等待，并阻塞排在它后面的所有查询（锁队列）；以短间隔重试。","when":"在生产环境繁忙的表上执行 ALTER TABLE、CREATE/DROP TRIGGER、ATTACH PARTITION 或 RENAME 等快速但锁定很重的变更时。","caution":"没有 lock_timeout 时，排在单个长 SELECT 后面的 ALTER TABLE 也会使之后的所有读写等待，几分钟内就可能耗尽连接池。lock_timeout 只限制锁等待；它不限制重写表的 DDL（例如修改列类型）的运行时间。DO 块是单个事务，会在多次尝试之间持有快照；对于长时间重试的场景，优先使用外部 shell 循环。","tags":["lock_timeout","ddl","alter table","lock queue","retry","lock"]},{"id":"connection-usage","task":"测量连接使用情况相对于 max_connections 的比例","category":"会话","command":"-- Capacity: used slots and slots left for regular users\nSELECT max_conn,\n       reserved,\n       used,\n       max_conn - reserved - used AS free_for_normal_users,\n       round(100.0 * used / max_conn, 1) AS used_pct\nFROM (SELECT current_setting('max_connections')::int AS max_conn,\n             current_setting('superuser_reserved_connections')::int\n             + coalesce(current_setting('reserved_connections', true)::int, 0) AS reserved,\n             (SELECT count(*) FROM pg_stat_activity WHERE backend_type = 'client backend') AS used) x;\n\n-- Breakdown by database, user, application and state\nSELECT datname, usename, application_name, state,\n       count(*) AS sessions,\n       max(now() - state_change) AS longest_in_state\nFROM pg_stat_activity\nWHERE backend_type = 'client backend'\nGROUP BY datname, usename, application_name, state\nORDER BY sessions DESC;\n\n-- Role and database connection limits\nSELECT rolname, rolconnlimit FROM pg_roles WHERE rolconnlimit <> -1;\nSELECT datname, datconnlimit FROM pg_database WHERE datconnlimit <> -1;","what":"显示相对于 max_connections 已使用的连接槽和留给普通用户的剩余槽，按数据库/用户/应用/状态的细分，以及已配置的 CONNECTION LIMIT。","when":"出现 'too many clients already' / 'remaining connection slots are reserved' 错误时、容量规划期间，或评估是否需要连接池时。","caution":"max_connections 只限制客户端后端进程；autovacuum worker、后台 worker 和 walsender 使用单独的槽。最后的 superuser_reserved_connections 个槽（PG16+ 还有 reserved_connections）不对普通用户开放。与其把 max_connections 调到数百甚至数千，不如使用 PgBouncer 等连接池：每个连接都是一个独立进程，内存和快照成本随连接数增长。大量 'idle' 会话说明应用连接池过大。用 ALTER ROLE ... CONNECTION LIMIT 防止失控的应用占用所有槽；修改 max_connections 需要重启。","note":"reserved_connections 参数和 pg_use_reserved_connections 角色在 PG16 中引入；该查询在更早的版本上也可运行（参数不存在时按 0 计算）。","tags":["max_connections","connection count","connection","pooler","superuser_reserved_connections","session"]},{"id":"autovacuum-progress","task":"监控正在运行的 autovacuum/VACUUM 操作进度","category":"维护","command":"SELECT p.pid,\n       a.backend_type,\n       p.datname,\n       p.relid::regclass AS table_name,\n       p.phase,\n       pg_size_pretty(p.heap_blks_total * current_setting('block_size')::bigint) AS table_size,\n       round(100.0 * p.heap_blks_scanned / NULLIF(p.heap_blks_total, 0), 1) AS scanned_pct,\n       round(100.0 * p.heap_blks_vacuumed / NULLIF(p.heap_blks_total, 0), 1) AS vacuumed_pct,\n       p.index_vacuum_count,\n       now() - a.xact_start AS running_for,\n       a.wait_event_type || ':' || a.wait_event AS wait,\n       left(a.query, 80) AS query\nFROM pg_stat_progress_vacuum p\nJOIN pg_stat_activity a USING (pid)\nORDER BY running_for DESC;\n\n-- Busy autovacuum workers / upper limit\nSELECT count(*) FILTER (WHERE backend_type = 'autovacuum worker') AS av_workers_busy,\n       current_setting('autovacuum_max_workers') AS av_max_workers\nFROM pg_stat_activity;","what":"对每个正在运行的 VACUUM（autovacuum worker 或手动），显示表、阶段、已扫描/已清理的堆百分比、索引清理轮数以及等待事件；以及忙碌的 autovacuum worker 数量。","when":"用于估算大表上的 VACUUM 何时完成、是否所有 autovacuum worker 都在忙（有表在队列中等待），以及 vacuum 是否因限流而等待。","caution":"index_vacuum_count > 1 表示死元组列表无法放入内存，索引被扫描了多次；请调高 maintenance_work_mem/autovacuum_work_mem（PG17 之前此用途最多使用 1GB）。wait = Timeout:VacuumDelay 是基于成本的限流（autovacuum_vacuum_cost_delay/cost_limit）。不要终止查询文本中带有 'to prevent wraparound' 的 autovacuum；它会立即重新启动。VACUUM FULL 和 CLUSTER 显示在 pg_stat_progress_cluster 中，而不是此视图中。","since":"PostgreSQL 9.6","note":"在 PG17 中，max_dead_tuples/num_dead_tuples 列改为 max_dead_tuple_bytes/num_dead_item_ids，并增加了 indexes_total/indexes_processed；因此该查询使用与版本无关的列。","tags":["autovacuum","vacuum progress","pg_stat_progress_vacuum","vacuum","maintenance","progress"]},{"id":"xid-wraparound-risk","task":"测量事务 ID 回卷风险（按数据库和表的 XID 年龄）","category":"维护","command":"-- Per database: distance to autovacuum_freeze_max_age and the ~2^31 limit\nSELECT datname,\n       age(datfrozenxid) AS xid_age,\n       mxid_age(datminmxid) AS mxid_age,\n       round(100.0 * age(datfrozenxid) / current_setting('autovacuum_freeze_max_age')::int, 1) AS pct_of_freeze_max_age,\n       round(100.0 * age(datfrozenxid) / 2147483647, 1) AS pct_of_wraparound\nFROM pg_database\nORDER BY age(datfrozenxid) DESC;\n\n-- Oldest tables in the current database (including TOAST)\nSELECT c.oid::regclass AS table_name,\n       greatest(age(c.relfrozenxid), age(t.relfrozenxid)) AS xid_age,\n       mxid_age(c.relminmxid) AS mxid_age,\n       pg_size_pretty(pg_total_relation_size(c.oid)) AS total_size,\n       s.last_autovacuum,\n       s.last_vacuum\nFROM pg_class c\nLEFT JOIN pg_class t ON t.oid = c.reltoastrelid\nLEFT JOIN pg_stat_all_tables s ON s.relid = c.oid\nWHERE c.relkind IN ('r', 'm')\nORDER BY 2 DESC\nLIMIT 20;","what":"显示每个数据库最旧的未冻结 XID 和 multixact 年龄相对于 autovacuum_freeze_max_age 和回卷上限的比例；在当前数据库中，列出最旧的表（包括 TOAST）及其大小和最近一次 vacuum 时间。","when":"用于例行监控（典型阈值：pct_of_wraparound 50% 警告 / 75% 严重）、日志显示 'must be vacuumed within N transactions' 时，以及确定 freeze 工作的优先级。","caution":"对于 pct_of_freeze_max_age 超过 100 的表，autovacuum 会启动强制（防回卷）vacuum；它 I/O 很重，即使被终止也会重新启动。如果年龄不下降，原因几乎总是有东西阻碍了 xmin horizon（长事务、prepared transaction、复制槽、hot_standby_feedback）；请先解决它，否则即使 VACUUM FREEZE 也无法降低年龄。接近上限时，服务器先发出警告，当只剩几百万时会拒绝分配新的 XID（写入）。针对性 freeze：VACUUM (FREEZE, VERBOSE) <table>;","note":"PG14 增加了 vacuum_failsafe_age / vacuum_multixact_failsafe_age（默认 16 亿）：当表达到该年龄时，VACUUM 会切换到跳过成本延迟和索引清理的紧急模式。","tags":["wraparound","xid age","datfrozenxid","relfrozenxid","freeze","multixact"]},{"id":"xmin-horizon-holders","task":"找出阻止 VACUUM 清除死元组的 xmin horizon 持有者","category":"维护","command":"SELECT 'session' AS source, pid::text AS id, usename::text AS owner, state AS detail,\n       age(backend_xmin) AS xmin_age\nFROM pg_stat_activity\nWHERE backend_xmin IS NOT NULL AND pid <> pg_backend_pid()\nUNION ALL\nSELECT 'prepared_xact', gid, owner::text, prepared::text, age(transaction)\nFROM pg_prepared_xacts\nUNION ALL\nSELECT 'replication_slot', slot_name::text, slot_type, active::text,\n       greatest(age(xmin), age(catalog_xmin))\nFROM pg_replication_slots\nWHERE xmin IS NOT NULL OR catalog_xmin IS NOT NULL\nUNION ALL\nSELECT 'standby_feedback', application_name, usename::text, state, age(backend_xmin)\nFROM pg_stat_replication\nWHERE backend_xmin IS NOT NULL\nORDER BY xmin_age DESC NULLS LAST;","what":"按 xmin 年龄排序，列出可能阻碍死元组清理的四类来源：持有快照的会话、prepared transaction、复制槽，以及发送 hot_standby_feedback 的备库。","when":"当 VACUUM VERBOSE 报告 'dead row versions cannot be removed yet'、膨胀持续增长且 XID 年龄不下降时。","caution":"最旧的 xmin 决定清理边界；复制槽和备库反馈会影响所有数据库。prepared transaction 在重启后依然存在；请核实所有者，并用 COMMIT PREPARED / ROLLBACK PREPARED '<gid>' 关闭。删除未使用的复制槽会破坏其下游（备库、CDC）；请先核实所有者。在备库上运行时，只显示该备库的会话。","tags":["xmin horizon","vacuum blocker","dead tuple","prepared transaction","replication slot","bloat"]},{"id":"pgstattuple-bloat","task":"使用 pgstattuple 测量表和索引膨胀","category":"维护","command":"CREATE EXTENSION IF NOT EXISTS pgstattuple;\n\n-- Fast estimate: skips pages marked all-visible in the visibility map\nSELECT pg_size_pretty(table_len) AS table_size,\n       round(approx_tuple_percent::numeric, 1) AS live_pct,\n       round(dead_tuple_percent::numeric, 1) AS dead_pct,\n       round(approx_free_percent::numeric, 1) AS free_pct\nFROM pgstattuple_approx('orders');\n\n-- Exact measurement: reads the whole table\nSELECT pg_size_pretty(table_len) AS table_size,\n       round(dead_tuple_percent::numeric, 1) AS dead_pct,\n       round(free_percent::numeric, 1) AS free_pct,\n       pg_size_pretty(dead_tuple_len + free_space) AS reclaimable\nFROM pgstattuple('orders');\n\n-- B-tree indexes: leaf density (~90 when freshly built; below 50 is bloated)\nWITH idx AS MATERIALIZED (\n  SELECT i.indexrelid\n  FROM pg_index i\n  JOIN pg_class c ON c.oid = i.indexrelid\n  JOIN pg_am am ON am.oid = c.relam\n  WHERE am.amname = 'btree' AND i.indrelid = 'orders'::regclass\n)\nSELECT idx.indexrelid::regclass AS index_name,\n       pg_size_pretty(s.index_size) AS index_size,\n       s.avg_leaf_density,\n       s.leaf_fragmentation\nFROM idx\nCROSS JOIN LATERAL pgstatindex(idx.indexrelid) s;","what":"显示表的活元组/死元组和空闲空间百分比（快速估算和精确值）以及可回收空间；对于 B-tree 索引，显示叶子密度和碎片。","when":"当 pg_stat_user_tables 中的 n_dead_tup 估算值不够用时；用数据支撑 pg_repack、VACUUM FULL 或 REINDEX 的决策。","caution":"pgstattuple 和 pgstatindex 会读取整个关系，在大表上产生大量 I/O（只获取 AccessShareLock）。请先使用 pgstattuple_approx。默认需要超级用户或 pg_stat_scan_tables 角色。一定的空闲空间是正常的（fillfactor、为 HOT 更新预留的空间）；VACUUM 使其可重用但不会缩小文件；回收它需要 pg_repack 或 VACUUM FULL（ACCESS EXCLUSIVE 锁！）。对于 avg_leaf_density 较低的索引，请用 REINDEX CONCURRENTLY 重建。","since":"PostgreSQL 12","note":"pgstattuple 是 contrib 扩展；pgstattuple_approx 在 PG9.5 中引入。CTE 中的 MATERIALIZED（PG12+）可防止在非 B-tree 索引上调用 pgstatindex 而失败。","tags":["bloat","pgstattuple","pgstatindex","index bloat","dead tuple","free space"]},{"id":"pg-repack-online","task":"在不长时间加锁的情况下重组膨胀的表（pg_repack）","category":"维护","command":"-- Once per target database (the pg_repack package must be installed on the server)\nCREATE EXTENSION pg_repack;\n\n# Dry run: show what would be done\npg_repack -h localhost -U postgres -d mydb --table=public.orders --dry-run\n\n# Rewrite the table and its indexes: 2 parallel index builds, wait at most 60 s for locks\npg_repack -h localhost -U postgres -d mydb --table=public.orders --jobs=2 --wait-timeout=60 --no-kill-backend\n\n# Rebuild indexes only\npg_repack -h localhost -U postgres -d mydb --table=public.orders --only-indexes","what":"在后台构建表的紧凑副本，通过触发器将并发变更记录到日志表中，最后在短暂的 ACCESS EXCLUSIVE 锁下交换表；与 VACUUM FULL 不同，它不会在整个过程中阻塞读写。","when":"当必须在在线系统上回收 VACUUM 无法归还的严重膨胀（已用 pgstattuple 验证）时。","caution":"表必须有 PRIMARY KEY 或在 NOT NULL 列上的 UNIQUE 索引。它临时需要与表及其索引大小相等的额外磁盘空间，并产生大量 WAL（副本延迟、归档量）。开始和结束时需要 ACCESS EXCLUSIVE 锁；--wait-timeout 到期时，默认会取消冲突的查询，必要时终止其会话，而 --no-kill-backend 则会跳过该表。运行期间不要在目标表上执行 DDL。客户端和扩展版本必须一致。如果被中断，请用 DROP EXTENSION pg_repack CASCADE; CREATE EXTENSION pg_repack; 清理残留。","note":"pg_repack 不属于 PostgreSQL 核心（单独的软件包，例如 postgresql-17-repack）。","tags":["pg_repack","bloat","online","vacuum full alternative","table reorganization","repack"]},{"id":"invalid-indexes","task":"查找并清理 INVALID 索引","category":"维护","command":"SELECT n.nspname AS schema_name,\n       c.relname AS index_name,\n       t.relname AS table_name,\n       pg_size_pretty(pg_relation_size(c.oid)) AS index_size,\n       i.indisvalid,\n       i.indisready,\n       format('DROP INDEX CONCURRENTLY %I.%I;', n.nspname, c.relname) AS fix\nFROM pg_index i\nJOIN pg_class c ON c.oid = i.indexrelid\nJOIN pg_class t ON t.oid = i.indrelid\nJOIN pg_namespace n ON n.oid = c.relnamespace\nWHERE NOT i.indisvalid OR NOT i.indisready;\n\n-- Before dropping: is an index build still in progress?\nSELECT pid, relid::regclass AS table_name, index_relid::regclass AS index_name, command, phase\nFROM pg_stat_progress_create_index;","what":"列出由失败的 CREATE INDEX CONCURRENTLY 或 REINDEX CONCURRENTLY 遗留的无效索引及其大小，并生成删除命令。","when":"在线索引操作失败或被取消后、例行健康检查中，以及大版本升级之前。","caution":"正在进行的 CREATE INDEX CONCURRENTLY / REINDEX CONCURRENTLY 也会显示为 INVALID；删除前请检查 pg_stat_progress_create_index。INVALID 索引不会被查询使用，但如果 indisready = true，它在每次 INSERT/UPDATE 时仍会被维护，即纯粹的开销。可以用 REINDEX INDEX CONCURRENTLY 重建，但除非修复了原因（例如唯一性冲突），否则会再次失败。REINDEX CONCURRENTLY 的残留带有 _ccnew/_ccold 后缀。DROP INDEX CONCURRENTLY 不能在事务块中运行。","tags":["invalid index","indisvalid","create index concurrently","broken index","_ccnew","index"]},{"id":"extended-statistics","task":"为相关列创建扩展统计信息（CREATE STATISTICS）","category":"维护","command":"-- city and district are correlated: the planner assumes independence and underestimates rows\nCREATE STATISTICS customer_city_district_stx (dependencies, ndistinct, mcv) ON city, district FROM customer;\nANALYZE customer;\n\n-- Collected statistics\nSELECT statistics_name, attnames, kinds, n_distinct, dependencies\nFROM pg_stats_ext\nWHERE tablename = 'customer';\n\n-- Verify: estimated rows should now be close to actual rows\nEXPLAIN (ANALYZE, TIMING OFF)\nSELECT * FROM customer WHERE city = 'Ankara' AND district = 'Cankaya';","what":"让规划器了解列之间的相关性（函数依赖、组合 distinct 计数、联合最常见值）；修正多列 WHERE 和 GROUP BY 条件的行数估算。","when":"当 EXPLAIN ANALYZE 显示多列过滤的估算行数远低于实际（例如 12 对 570），并因此选择了错误的连接方法/索引时。","caution":"只有在 ANALYZE 之后才生效。当列组合数量远高于统计信息目标（默认 100）时，mcv 列表无法覆盖其中的大部分，此时 mcv 可能无法修正 dependencies 能够修正的估算。请用 EXPLAIN 验证；必要时从定义中去掉 mcv，或用 ALTER STATISTICS customer_city_district_stx SET STATISTICS 1000; 调高目标。dependencies 只用于等值/IN 条件，ndistinct 用于 GROUP BY 估算。每个定义都会延长 ANALYZE；只为已证实存在估算错误的列组添加。","since":"PostgreSQL 12","note":"CREATE STATISTICS 在 PG10 中引入（ndistinct、dependencies），mcv 类型在 PG12 中引入，ALTER STATISTICS ... SET STATISTICS 在 PG13 中引入，表达式统计信息在 PG14 中引入。","tags":["extended statistics","create statistics","correlation","row estimate","cardinality","analyze"]},{"id":"analyze-staleness","task":"查找统计信息过期的表（ANALYZE 策略）","category":"维护","command":"SELECT schemaname,\n       relname AS table_name,\n       n_live_tup,\n       n_mod_since_analyze,\n       round(100.0 * n_mod_since_analyze / NULLIF(n_live_tup, 0), 1) AS mod_pct,\n       greatest(last_analyze, last_autoanalyze) AS last_analyzed,\n       round(current_setting('autovacuum_analyze_threshold')::int\n             + current_setting('autovacuum_analyze_scale_factor')::numeric * n_live_tup) AS autoanalyze_at\nFROM pg_stat_user_tables\nWHERE n_mod_since_analyze > 0\nORDER BY mod_pct DESC NULLS LAST\nLIMIT 20;\n\n-- After a bulk load, without waiting for autovacuum\nANALYZE (VERBOSE) orders;","what":"显示自上次 ANALYZE 以来修改的行数、其与活行数的比例，以及 autovacuum 触发 ANALYZE 的大致修改阈值。","when":"大批量加载/删除之后、计划显示错误的行数估算时，或怀疑 autoanalyze 在大表上滞后时。","caution":"计算出的阈值使用全局设置；如果存在表级 autovacuum_analyze_* reloptions（pg_class.reloptions），实际阈值会不同。在大表上默认的 scale_factor（0.1）触发得太晚；请按表调低。autovacuum 不会对分区表的父表执行 ANALYZE；父表级统计信息需要手动 ANALYZE。autovacuum 永远看不到临时表。ANALYZE 读取样本并获取 ShareUpdateExclusiveLock（不阻塞 DML）。","tags":["analyze","stale statistics","n_mod_since_analyze","autoanalyze","statistics","planner"]},{"id":"visibility-map-index-only","task":"检查 visibility map 覆盖率和 Index Only Scan 效率","category":"维护","command":"-- Tables with a low all-visible page ratio: Index Only Scans must visit the heap\nSELECT c.oid::regclass AS table_name,\n       c.relpages,\n       c.relallvisible,\n       round(100.0 * c.relallvisible / NULLIF(c.relpages, 0), 1) AS all_visible_pct,\n       pg_size_pretty(pg_relation_size(c.oid)) AS heap_size,\n       s.n_ins_since_vacuum,\n       s.last_autovacuum\nFROM pg_class c\nJOIN pg_stat_user_tables s ON s.relid = c.oid\nWHERE c.relpages > 1000\nORDER BY all_visible_pct NULLS FIRST\nLIMIT 20;\n\n-- If \"Heap Fetches\" in the plan is close to the returned rows, the visibility map is stale\nEXPLAIN (ANALYZE, BUFFERS) SELECT id FROM customer WHERE id BETWEEN 1 AND 50000;\n\n-- Exact count (pg_visibility extension)\nCREATE EXTENSION IF NOT EXISTS pg_visibility;\nSELECT * FROM pg_visibility_map_summary('customer');","what":"显示表的 all-visible 页面比例、自上次 vacuum 以来插入的行数，以及 Index Only Scan 访问堆（Heap Fetches）的频率。","when":"当 Index Only Scan 表现不佳时；用于插入密集/仅追加的表，以及验证覆盖索引（INCLUDE）设计时。","caution":"relpages/relallvisible 是最近一次 VACUUM/ANALYZE 记录的估算值。只有 VACUUM 会设置 visibility map 位；每次 UPDATE/DELETE 都会清除受影响页面的位。对于频繁更新的表比例偏低时，请按表调低 autovacuum_vacuum_scale_factor。pg_visibility_map_summary 读取的是很小的 VM fork，而不是堆。","since":"PostgreSQL 13","note":"n_ins_since_vacuum 和由插入触发的 autovacuum（autovacuum_vacuum_insert_threshold/scale_factor）在 PG13 中引入；在此之前，仅追加的表可能直到防回卷 vacuum 才会被 vacuum，因此 visibility map 不会被设置。","tags":["visibility map","index only scan","heap fetches","relallvisible","pg_visibility","vacuum"]},{"id":"range-partition-monthly","task":"创建按月 range 分区的表（在循环中生成分区）","category":"分区","command":"CREATE TABLE sales (\n  id         bigint GENERATED ALWAYS AS IDENTITY,\n  created_at      date   NOT NULL,\n  customer_id bigint NOT NULL,\n  amount      numeric(12,2),\n  PRIMARY KEY (id, created_at)\n) PARTITION BY RANGE (created_at);\n\n-- An index defined on the parent propagates to all partitions automatically\nCREATE INDEX sales_customer_id_idx ON sales (customer_id);\n\n-- 12 monthly partitions for 2026\nDO $$\nDECLARE\n  m date;\nBEGIN\n  FOR m IN SELECT generate_series(date '2026-01-01', date '2026-12-01', interval '1 month')::date LOOP\n    EXECUTE format(\n      'CREATE TABLE IF NOT EXISTS %I PARTITION OF sales FOR VALUES FROM (%L) TO (%L)',\n      'sales_' || to_char(m, 'YYYY_MM'), m, (m + interval '1 month')::date);\n  END LOOP;\nEND $$;\n\n-- Hierarchy, bounds and sizes\nSELECT t.relid::regclass AS partition_name,\n       t.parentrelid::regclass AS parent,\n       t.isleaf,\n       t.level,\n       pg_get_expr(c.relpartbound, c.oid) AS bounds,\n       pg_size_pretty(pg_total_relation_size(t.relid)) AS total_size\nFROM pg_partition_tree('sales') t\nJOIN pg_class c ON c.oid = t.relid\nORDER BY t.level, bounds;","what":"在日期列上创建 RANGE 分区的父表、一个会传播到每个分区的索引，并在循环中创建 12 个按月分区；然后列出层次结构及其边界和大小。","when":"用于不断增长、定期删除旧数据（DETACH/DROP）或查询大多按日期范围过滤的大表（订单、日志、时间序列）。","caution":"在分区表上，PRIMARY KEY/UNIQUE 约束必须包含分区键（不存在全局唯一索引）。下界包含，上界不包含（FROM 包含，TO 不包含）。如果没有预先创建未来分区的作业（pg_cron、pg_partman），超出范围的 INSERT 会失败。数千个分区会增加规划时间和锁开销。父表不支持 CREATE INDEX CONCURRENTLY：在父表上使用 CREATE INDEX ... ON ONLY，在每个分区上使用 CREATE INDEX CONCURRENTLY，然后执行 ALTER INDEX ... ATTACH PARTITION。","since":"PostgreSQL 12","note":"声明式分区在 PG10 中引入，父表上的索引/PRIMARY KEY 和默认分区在 PG11 中引入，pg_partition_tree() 在 PG12 中引入。","tags":["partition","range partition","declarative partitioning","monthly partition","pg_partition_tree","partitioning"]},{"id":"attach-partition-safely","task":"以短暂的锁将准备好的表挂载为分区（ATTACH PARTITION）","category":"分区","command":"-- 1) Prepare the data in a separate table without touching the live table\nCREATE TABLE sales_2027_01 (LIKE sales INCLUDING DEFAULTS INCLUDING CONSTRAINTS);\n-- ... bulk load (COPY / INSERT) ...\nALTER TABLE sales_2027_01 ADD PRIMARY KEY (id, created_at);\nCREATE INDEX CONCURRENTLY sales_2027_01_customer_id_idx ON sales_2027_01 (customer_id);\n\n-- 2) A CHECK that exactly matches the bounds: avoids a full scan during ATTACH\nALTER TABLE sales_2027_01 ADD CONSTRAINT sales_2027_01_bound_chk\n  CHECK (created_at IS NOT NULL AND created_at >= DATE '2027-01-01' AND created_at < DATE '2027-02-01') NOT VALID;\nALTER TABLE sales_2027_01 VALIDATE CONSTRAINT sales_2027_01_bound_chk;\n\n-- 3) Attach (SHARE UPDATE EXCLUSIVE on the parent; reads/writes continue)\nSET lock_timeout = '5s';\nALTER TABLE sales ATTACH PARTITION sales_2027_01\n  FOR VALUES FROM ('2027-01-01') TO ('2027-02-01');\nRESET lock_timeout;\n\n-- 4) Drop the now redundant CHECK\nALTER TABLE sales_2027_01 DROP CONSTRAINT sales_2027_01_bound_chk;","what":"利用与边界匹配的预先验证的 CHECK 约束和预先建好的索引，在不进行全量扫描、不长时间加锁的情况下，将加载到单独表中的数据变为一个分区。","when":"用于批量加载（ETL）或从归档还原：在不触及在线表的情况下准备数据，然后一步上线。","caution":"ATTACH 在父表上获取 SHARE UPDATE EXCLUSIVE，在被挂载的表上获取 ACCESS EXCLUSIVE。如果没有匹配的有效 CHECK，会在该锁下全量扫描表以验证边界；如果缺少与父表匹配的索引，ATTACH 会在锁下创建它们，因此请提前准备好两者。如果存在默认分区，也会扫描它以证明其中没有新范围内的行（参见默认分区操作指南）。列名和类型必须与父表完全一致；用 LIKE ... INCLUDING 创建表是最安全的方式。","since":"PostgreSQL 12","note":"ATTACH PARTITION 在父表上只获取 SHARE UPDATE EXCLUSIVE 是 PG12 引入的；在 PG11 及更早版本中，父表会被 ACCESS EXCLUSIVE 锁定。","tags":["attach partition","add partition","check constraint","not valid","etl","partitioning"]},{"id":"detach-partition-concurrently","task":"在不阻塞在线流量的情况下卸载旧分区（DETACH CONCURRENTLY）","category":"分区","command":"-- Must run outside a transaction block\nALTER TABLE sales DETACH PARTITION sales_2026_01 CONCURRENTLY;\n\n-- If interrupted (cancel, lost connection) the partition stays 'detach pending'; complete it:\nALTER TABLE sales DETACH PARTITION sales_2026_01 FINALIZE;\n\n-- Any pending detach?\nSELECT inhrelid::regclass AS partition_name, inhparent::regclass AS parent\nFROM pg_inherits\nWHERE inhdetachpending;\n\n-- The detached table is now standalone: archive or drop it\nDROP TABLE sales_2026_01;","what":"分两个事务卸载分区；在父表上只获取 SHARE UPDATE EXCLUSIVE，因此不会阻塞正在运行的查询，并为卸载出的表添加与分区边界等价的 CHECK 约束。","when":"在在线系统上为保留/归档而卸载旧周期分区时（之后执行 DROP、迁移到其他表空间或 pg_dump 归档）。","caution":"不能在 BEGIN ... COMMIT 块中运行，父表有默认分区时也不能使用。它会等待所有使用父表且在其之前开始的事务；长事务会延长它。如果被中断，分区对查询不可见但尚未卸载（'detach pending'），必须用 FINALIZE 完成。不带 CONCURRENTLY 的普通 DETACH 更快，但需要父表上的 ACCESS EXCLUSIVE；请配合 lock_timeout 尝试。","since":"PostgreSQL 14","tags":["detach partition","concurrently","finalize","retention","archiving","partitioning"]},{"id":"default-partition-pitfall","task":"默认分区陷阱：新分区因 'would be violated by some row' 而失败","category":"分区","command":"-- Which periods have rows in the default partition?\nSELECT date_trunc('month', created_at)::date AS month, count(*) AS row_count\nFROM sales_default\nGROUP BY 1\nORDER BY 1;\n\n-- Move the rows into a new partition and attach it in one transaction\nBEGIN;\nSET LOCAL lock_timeout = '5s';\nLOCK TABLE sales_default IN ACCESS EXCLUSIVE MODE;\nCREATE TABLE sales_2027_02 (LIKE sales INCLUDING DEFAULTS INCLUDING CONSTRAINTS);\nWITH moved AS (\n  DELETE FROM sales_default\n  WHERE created_at >= DATE '2027-02-01' AND created_at < DATE '2027-03-01'\n  RETURNING *\n)\nINSERT INTO sales_2027_02 SELECT * FROM moved;\nALTER TABLE sales ATTACH PARTITION sales_2027_02\n  FOR VALUES FROM ('2027-02-01') TO ('2027-03-01');\nCOMMIT;","what":"按周期统计落入默认分区的行数；在一个事务中将属于新分区范围的行移动到新表并将其挂载。","when":"当 CREATE TABLE ... PARTITION OF 或 ATTACH 因 'updated partition constraint for default partition would be violated by some row' 而失败，或发现默认分区在增长时。","caution":"存在默认分区时，每个新分区都会全量扫描默认分区，并在此期间对其持有 ACCESS EXCLUSIVE；大的默认分区意味着每次维护都会中断。移动行期间默认分区不可写入；请在低流量时进行。永久解决方法：预先创建分区，并通过监控保持默认分区为空（行数 > 0 时告警），或者根本不使用默认分区，让超出范围的 INSERT 失败，使问题尽早暴露。默认分区还会阻止 DETACH ... CONCURRENTLY。","since":"PostgreSQL 11","tags":["default partition","partition error","violated by some row","partition constraint","pitfall","partitioning"]},{"id":"partition-pruning-check","task":"验证分区裁剪是否生效（规划时和运行时）","category":"分区","command":"SHOW enable_partition_pruning;\n\n-- Plan-time pruning: only the relevant partition should appear in the plan\nEXPLAIN (COSTS OFF)\nSELECT count(*) FROM sales WHERE created_at >= DATE '2026-03-01' AND created_at < DATE '2026-04-01';\n\n-- Run-time pruning: generic plan / parameterized query, \"Subplans Removed: N\"\nPREPARE q_sales(date) AS SELECT count(*) FROM sales WHERE created_at = $1;\nSET plan_cache_mode = force_generic_plan;\nEXPLAIN (ANALYZE, COSTS OFF, TIMING OFF) EXECUTE q_sales('2026-03-15');\nRESET plan_cache_mode;\nDEALLOCATE q_sales;\n\n-- Pruning killer: a function on the key column scans every partition\nEXPLAIN (COSTS OFF)\nSELECT count(*) FROM sales WHERE date_trunc('month', created_at) = DATE '2026-03-01';","what":"比较规划时裁剪、使用通用计划时的运行时裁剪（'Subplans Removed: N'），以及在分区键上应用函数时裁剪如何失效。","when":"当分区表上的查询比预期慢、计划扫描了每个分区，或应用使用 ORM/prepared statement 时。","caution":"对分区键应用函数或类型转换（date_trunc(created_at)、created_at::text）会禁用裁剪；请将条件写成键上的范围。使用通用计划时，裁剪发生在执行器启动阶段，只显示为 'Subplans Removed'；对于 now() 等 STABLE 表达式，在不带 ANALYZE 的 EXPLAIN 中也能看到。enable_partition_pruning = off 时完全不进行裁剪。分区很多时，也要测量 Planning Time。","since":"PostgreSQL 12","note":"运行时裁剪在 PG11 中引入；plan_cache_mode 在 PG12 中引入，PG12 还大幅提升了多分区时的规划性能。","tags":["partition pruning","subplans removed","generic plan","plan_cache_mode","explain","partitioning"]},{"id":"pg-partman-setup","task":"使用 pg_partman 自动化分区创建和保留","category":"分区","command":"CREATE SCHEMA partman;\nCREATE EXTENSION pg_partman SCHEMA partman;\n\nCREATE TABLE public.measurements (\n  id    bigint GENERATED ALWAYS AS IDENTITY,\n  ts    timestamptz NOT NULL,\n  value numeric,\n  PRIMARY KEY (id, ts)\n) PARTITION BY RANGE (ts);\n\n-- Daily partitions, pre-create 7 days ahead (pg_partman 5.x syntax)\nSELECT partman.create_parent(\n  p_parent_table := 'public.measurements',\n  p_control      := 'ts',\n  p_interval     := '1 day',\n  p_premake      := 7\n);\n\n-- Drop partitions older than 30 days\nUPDATE partman.part_config\n   SET retention = '30 days',\n       retention_keep_table = false\n WHERE parent_table = 'public.measurements';\n\n-- Maintenance: run periodically (schedule with pg_cron or pg_partman_bgw)\nCALL partman.run_maintenance_proc();\n\n-- Any rows in the default partition?\nSELECT * FROM partman.check_default(p_exact_count := true);","what":"将父表注册到 pg_partman；预先创建未来分区，删除超过保留期的分区，并报告落入默认分区的行。","when":"当需要标准化、可观测的自动化，而不是手工或用自定义脚本管理按日/按月分区时（日志、指标、事件表）。","caution":"pg_partman 4.x 与 5.x 之间 API 有变化：在 5.x 中 p_type 只接受 'range'/'list'（默认 'range'），4.x 的 p_type := 'native' 无效；请用 SELECT extversion FROM pg_extension WHERE extname = 'pg_partman'; 检查已安装版本。create_parent 默认还会创建一个默认分区。retention_keep_table = false 会永久 DROP 分区；如需归档，请保持为 true 并使用 retention_schema。如果没有调度 run_maintenance_proc()，则不会创建新分区，数据会落入默认分区。pg_partman_bgw 需要 shared_preload_libraries 加重启。","note":"pg_partman 不属于 PostgreSQL 核心（单独的软件包，例如 postgresql-17-partman）。命令使用 pg_partman 5.x 语法。","tags":["pg_partman","partition maintenance","retention","create_parent","automatic partitioning","partitioning"]},{"id":"least-privilege-roles","task":"构建最小权限角色层次（GRANT + ALTER DEFAULT PRIVILEGES）","category":"安全","command":"-- Group roles (NOLOGIN): object owner, read-only, read-write\nCREATE ROLE app_owner NOLOGIN;\nCREATE ROLE app_readonly NOLOGIN;\nCREATE ROLE app_readwrite NOLOGIN;\nGRANT app_readonly TO app_readwrite;\nCREATE SCHEMA app AUTHORIZATION app_owner;\n\n-- Restrict PUBLIC's default privileges\nREVOKE ALL ON DATABASE mydb FROM PUBLIC;\nREVOKE CREATE ON SCHEMA public FROM PUBLIC;\n\n-- Existing objects\nGRANT CONNECT ON DATABASE mydb TO app_readonly;\nGRANT USAGE ON SCHEMA app TO app_readonly;\nGRANT SELECT ON ALL TABLES IN SCHEMA app TO app_readonly;\nGRANT INSERT, UPDATE, DELETE ON ALL TABLES IN SCHEMA app TO app_readwrite;\nGRANT USAGE, SELECT ON ALL SEQUENCES IN SCHEMA app TO app_readwrite;\n\n-- Objects app_owner will create IN THE FUTURE\nALTER DEFAULT PRIVILEGES FOR ROLE app_owner IN SCHEMA app GRANT SELECT ON TABLES TO app_readonly;\nALTER DEFAULT PRIVILEGES FOR ROLE app_owner IN SCHEMA app GRANT INSERT, UPDATE, DELETE ON TABLES TO app_readwrite;\nALTER DEFAULT PRIVILEGES FOR ROLE app_owner IN SCHEMA app GRANT USAGE, SELECT ON SEQUENCES TO app_readwrite;\n\n-- Login users get privileges only through group membership\nCREATE ROLE web_app LOGIN PASSWORD 'Change#2026' IN ROLE app_readwrite;\nCREATE ROLE report_app LOGIN PASSWORD 'Change#2026' IN ROLE app_readonly;\n\n-- Configured default privileges (psql: \\ddp)\nSELECT pg_get_userbyid(d.defaclrole) AS owner_role,\n       n.nspname AS schema_name,\n       d.defaclobjtype AS object_type,\n       d.defaclacl\nFROM pg_default_acl d\nLEFT JOIN pg_namespace n ON n.oid = d.defaclnamespace\nORDER BY 1, 2, 3;","what":"构建 owner、只读和读写组角色的层次结构；对现有和未来的表/序列授予正确的权限，并只通过组成员关系为应用用户授权。","when":"在建立新的应用 schema、修复以超级用户或表所有者身份连接的应用，以及在审计前简化权限时。","caution":"ALTER DEFAULT PRIVILEGES 只适用于 FOR ROLE 中指定的角色之后创建的对象；如果迁移工具以其他用户身份建表，权限不会被应用，因此请用 SET ROLE app_owner 执行 DDL。GRANT ... ON ALL TABLES 只覆盖当时已存在的对象。在 PG15+ 上创建的数据库默认已不再向 PUBLIC 授予 public schema 的 CREATE 权限；从旧版本升级的数据库需要执行 REVOKE。REVOKE ALL ON DATABASE ... FROM PUBLIC 也会移除 CONNECT 和 TEMP；请单独授予需要它们的其他角色。请使用 psql \\password，而不是在命令行中写密码（这样密码不会进入日志和历史记录）。","note":"pg_read_all_data / pg_write_all_data 角色在 PG14 中引入，角色成员关系的 INHERIT/SET 选项（GRANT ... WITH INHERIT FALSE）在 PG16 中引入。","tags":["least privilege","role hierarchy","alter default privileges","grant","privilege","security"]},{"id":"row-level-security","task":"使用行级安全实现租户隔离","category":"安全","command":"ALTER TABLE app.orders ENABLE ROW LEVEL SECURITY;\n-- Make the table owner subject to the policy too\nALTER TABLE app.orders FORCE ROW LEVEL SECURITY;\n\nCREATE POLICY orders_tenant_isolation ON app.orders\n  FOR ALL\n  TO app_readwrite\n  USING (tenant_id = NULLIF(current_setting('app.tenant_id', true), '')::int)\n  WITH CHECK (tenant_id = NULLIF(current_setting('app.tenant_id', true), '')::int);\n\n-- The application sets the tenant at the start of every transaction (pooler-friendly)\nBEGIN;\nSET LOCAL app.tenant_id = '42';\nSELECT * FROM app.orders;\nCOMMIT;\n\n-- Configured policies\nSELECT schemaname, tablename, policyname, roles, cmd, qual, with_check\nFROM pg_policies\nWHERE tablename = 'orders';","what":"在表上启用 RLS，并定义一个策略：按会话变量中的租户 id 过滤行（USING），并阻止写入其他租户的数据（WITH CHECK）。","when":"用于在数据库中为多租户应用强制实施数据隔离，而不是依赖应用代码。","caution":"超级用户和具有 BYPASSRLS 属性的角色总是绕过策略；表所有者只有在 FORCE ROW LEVEL SECURITY 时才受其约束。启用 RLS 但没有匹配的策略时，结果为空（不报错）。变量未定义时 current_setting(..., true) 返回 NULL；NULLIF 可防止 RESET 后留下的空字符串因 ''::int 而失败。用户可以用 SET 修改 app.tenant_id；只能由受信任的应用层设置，终端用户不得获得 SQL 访问权限。使用 transaction 模式的连接池时，请用 SET LOCAL 代替 SET。为策略中使用的 tenant_id 列建立索引。以受 RLS 约束的用户运行 pg_dump 会失败；请用 BYPASSRLS 角色进行备份。","since":"PostgreSQL 9.5","tags":["row level security","rls","create policy","multi-tenant","tenant isolation","security"]},{"id":"pg-hba-validate-reload","task":"验证 pg_hba.conf 规则顺序并安全地重新加载","category":"安全","command":"-- File location\nSHOW hba_file;\n\n# Rules are read top to bottom and the FIRST matching rule wins;\n# specific rules must come BEFORE general ones:\n#   hostssl  appdb  web_app  10.10.0.0/16  scram-sha-256\n#   host     all    all      10.10.0.0/16  reject\n#   host     all    all      0.0.0.0/0     scram-sha-256\n\n-- Validate BEFORE reloading: pg_hba_file_rules reads the file on disk\nSELECT line_number, type, database, user_name, address, netmask, auth_method, error\nFROM pg_hba_file_rules\nORDER BY line_number;\n\n-- Apply if the error column is empty\nSELECT pg_reload_conf();","what":"在 reload 之前显示 pg_hba.conf 的位置、PostgreSQL 如何解释它（行、类型、数据库、用户、地址、方法）以及任何语法错误；然后重新加载配置。","when":"在添加新的客户端网络/应用、从 md5 迁移到 scram-sha-256，或排查 'no pg_hba.conf entry for host' 错误时。","caution":"pg_hba_file_rules 显示的是文件当前的内容，而不是生效中的规则。如果任何一行的 error 非空，reload 根本不会加载 pg_hba.conf，旧规则继续生效（日志显示 'pg_hba.conf was not reloaded'）。变更只影响新连接。放在前面的宽泛规则（例如 host all all 0.0.0.0/0 trust）会使其后的所有限制失效。SSL 关闭时，hostssl 行无法匹配，并在 error 列中显示消息。默认只有超级用户可以读取该视图。确保以 root 编辑文件的工具不会改变其属主；如果 postgres 用户无法读取，reload 会失败。","since":"PostgreSQL 10","note":"pg_hba_file_rules 在 PG10 中引入；较新版本还有 rule_number 和 file_name 列。PG16 支持 pg_hba.conf 中的 include 指令以及 user/database 字段中的正则匹配。","tags":["pg_hba.conf","authentication","rule order","reload","pg_hba_file_rules","security"]},{"id":"ssl-enable-verify","task":"启用 SSL/TLS 并验证加密连接（pg_stat_ssl）","category":"安全","command":"# Server certificate (self-signed for testing; use a corporate CA in production)\nopenssl req -new -x509 -days 365 -nodes -subj \"/CN=db.example.com\" -keyout server.key -out server.crt\nchmod 600 server.key    # in the data directory, owned by postgres\n\n-- Enable SSL (a reload is enough)\nALTER SYSTEM SET ssl = on;\nALTER SYSTEM SET ssl_cert_file = 'server.crt';\nALTER SYSTEM SET ssl_key_file = 'server.key';\nALTER SYSTEM SET ssl_min_protocol_version = 'TLSv1.2';\nSELECT pg_reload_conf();\n\n-- Which connections are encrypted?\nSELECT a.pid, a.usename, a.client_addr, a.application_name,\n       s.ssl, s.version, s.cipher, s.bits\nFROM pg_stat_ssl s\nJOIN pg_stat_activity a USING (pid)\nWHERE a.backend_type = 'client backend'\nORDER BY s.ssl, a.usename;\n\n# On the client, verify the server identity as well\npsql \"host=db.example.com dbname=postgres user=postgres sslmode=verify-full sslrootcert=root.crt\"","what":"无需重启即可使用服务器证书启用 TLS，设置最低协议版本，并列出当前连接是否已加密以及 TLS 版本和加密套件。","when":"用于安全审计/合规要求（PCI、GDPR），保护网络传输中的密码和数据。","caution":"仅 ssl = on 并不强制加密；客户端仍可用 sslmode=prefer/disable 进行未加密连接。要强制加密，请在 pg_hba.conf 中用 hostssl 代替 host（并添加 hostnossl ... reject）。sslmode=require 不验证服务器身份（易受 MITM 攻击）；证书验证请使用 verify-full。密钥文件的属主必须是 postgres 用户且权限为 0600（或属主为 root 且权限为 0640）；否则 SSL 不会加载，因此 reload 后请检查日志和 SHOW ssl。证书续期也可通过 reload 生效。","since":"PostgreSQL 10","note":"用 reload 而不是重启修改 SSL 设置是 PG10 引入的，ssl_min_protocol_version 在 PG12 中引入；自 PG13 起其默认值为 TLSv1.2。","tags":["ssl","tls","pg_stat_ssl","hostssl","certificate","encryption"]},{"id":"scram-migration","task":"从 md5 密码迁移到 scram-sha-256","category":"安全","command":"-- 1) Store new passwords as SCRAM\nALTER SYSTEM SET password_encryption = 'scram-sha-256';\nSELECT pg_reload_conf();\n\n-- 2) Find roles still carrying md5 password hashes (requires superuser)\nSELECT rolname,\n       CASE\n         WHEN rolpassword LIKE 'SCRAM-SHA-256$%' THEN 'scram-sha-256'\n         WHEN rolpassword LIKE 'md5%' THEN 'md5'\n         WHEN rolpassword IS NULL THEN 'no password'\n         ELSE 'other'\n       END AS password_type,\n       rolvaliduntil\nFROM pg_authid\nWHERE rolcanlogin\nORDER BY password_type, rolname;\n\n-- 3) Reset the password of each md5 role (preferably in psql: \\password legacy_app)\nALTER ROLE legacy_app PASSWORD 'New#Password2';\n\n# 4) Once all roles use SCRAM, change md5 -> scram-sha-256 in pg_hba.conf and reload","what":"让新密码以 SCRAM-SHA-256 存储，找出仍带有 md5 哈希的登录角色，并给出在密码更新后将 pg_hba.conf 切换到 scram-sha-256 的步骤。","when":"当安全审计标记出 md5、升级到 PG14+ 之后，或为 md5 被弃用的版本做准备时。","caution":"SCRAM 无法从现有的 md5 哈希推导出来；每个密码都必须以明文重新设置（需要与应用团队协调）。pg_hba.conf 中的 'md5' 方法会对密码为 SCRAM 的角色自动执行 SCRAM 认证，因此迁移期间保留 md5 是安全的过渡步骤，但一旦方法改为 'scram-sha-256'，使用 md5 密码的角色将无法再连接。旧的客户端驱动（libpq < 10、旧版 JDBC/.NET 驱动）可能不支持 SCRAM；请先升级驱动。ALTER ROLE ... PASSWORD 可能通过 log_statement 将密码写入日志；psql \\password 会在客户端先哈希再发送。","since":"PostgreSQL 10","note":"SCRAM-SHA-256 在 PG10 中引入，password_encryption 的默认值在 PG14 中变为 scram-sha-256。PG18 弃用 md5 密码并发出警告。","tags":["scram-sha-256","md5","password_encryption","password","authentication","security"]},{"id":"pgaudit-object-audit","task":"使用 pgaudit 保留角色级和对象级审计日志","category":"安全","command":"-- Prerequisite: shared_preload_libraries = 'pgaudit' + restart, then:\nCREATE EXTENSION pgaudit;\n\n-- Session auditing: server-wide, only DDL and role/privilege changes\nALTER SYSTEM SET pgaudit.log = 'ddl, role';\nALTER SYSTEM SET pgaudit.log_relation = on;\nALTER SYSTEM SET pgaudit.log_parameter = off;\nSELECT pg_reload_conf();\n\n-- All reads/writes of a specific application role\nALTER ROLE web_app SET pgaudit.log = 'read, write';\n\n-- Object auditing: privileges granted to the auditor role = operations to log\nCREATE ROLE auditor NOLOGIN;\nALTER SYSTEM SET pgaudit.role = 'auditor';\nSELECT pg_reload_conf();\nGRANT SELECT, DELETE ON customer TO auditor;\nGRANT UPDATE (balance) ON customer TO auditor;","what":"记录服务器范围的 DDL 和权限变更、特定角色的所有读写，以及对授予 auditor 角色的敏感表/列的访问，输出为以 'AUDIT:' 为前缀的结构化日志行。","when":"用于在 GDPR、PCI-DSS 或 SOX 等合规审计中证明谁访问了敏感数据。","caution":"在服务器范围启用 pgaudit.log = 'all' 或 'read' 会大幅增加日志量和 I/O；请用角色审计或对象审计缩小范围。超级用户可以关闭审计（ALTER SYSTEM/ALTER ROLE）；请将记录发送到集中日志系统（syslog/SIEM），并独立于数据库服务器存储。log_parameter = on 也会记录绑定参数（个人数据）。角色设置在新会话中生效。pgaudit 的大版本必须与 PostgreSQL 的大版本一致（pgaudit 17.x = PG17）。","note":"pgaudit 不属于 PostgreSQL 核心（单独的软件包，例如 postgresql-17-pgaudit）。","tags":["pgaudit","audit","auditing","object audit","log","security"]},{"id":"memory-settings-check","task":"检查内存设置（shared_buffers、work_mem、maintenance_work_mem）","category":"参数","command":"SELECT name,\n       current_setting(name) AS value,\n       source,\n       context\nFROM pg_settings\nWHERE name IN ('shared_buffers', 'effective_cache_size', 'work_mem', 'hash_mem_multiplier',\n               'maintenance_work_mem', 'autovacuum_work_mem', 'max_connections',\n               'max_parallel_workers_per_gather', 'huge_pages', 'temp_buffers', 'wal_buffers')\nORDER BY name;\n\n-- Rough ceilings: work_mem x max_connections and the autovacuum memory ceiling\nSELECT pg_size_pretty(pg_size_bytes(current_setting('work_mem'))\n                      * current_setting('max_connections')::int) AS work_mem_x_max_connections,\n       pg_size_pretty(pg_size_bytes(current_setting('maintenance_work_mem'))\n                      * current_setting('autovacuum_max_workers')::int) AS autovacuum_mem_ceiling;\n\n-- Trace of operations that did not fit in work_mem: temp files per database\nSELECT datname, temp_files, pg_size_pretty(temp_bytes) AS temp_size, stats_reset\nFROM pg_stat_database\nWHERE datname IS NOT NULL\nORDER BY temp_bytes DESC;","what":"显示与内存相关的参数及其值、来源（默认/配置文件）和变更上下文（postmaster = 重启）；work_mem 和 autovacuum 的粗略内存上限；以及每个数据库的临时文件量。","when":"在搭建新服务器、增加内存后、发生 OOM killer 事件后，或发现大量使用临时文件时。","caution":"work_mem 不是按连接分配，而是按每个排序/哈希节点分配（哈希还要乘以 hash_mem_multiplier），并且每个并行 worker 单独分配；一个复杂查询可能使用数倍的 work_mem，因此计算出的乘积只是粗略警告，而不是上限。典型起始值：shared_buffers 约为内存的 25%（需要重启），effective_cache_size 约 50-75%（仅为规划器提示，不分配任何内存），maintenance_work_mem 512MB-2GB。autovacuum_work_mem = -1 时，每个 autovacuum worker 可使用 maintenance_work_mem。如果 temp_bytes 很高，请按角色或查询调高 work_mem，而不是全局调高。在 Linux 上使用较大的 shared_buffers 时，请考虑 huge_pages。","note":"hash_mem_multiplier 在 PG13 中引入（自 PG15 起默认 2.0）。","tags":["shared_buffers","work_mem","maintenance_work_mem","memory","memory tuning","temp_bytes"]},{"id":"pending-restart-config-errors","task":"显示待重启的参数和配置文件错误","category":"参数","command":"-- Parameters changed but not effective until a restart\nSELECT name, setting, unit, context, source, sourcefile, pending_restart\nFROM pg_settings\nWHERE pending_restart;\n\n-- Lines in configuration files that were not applied or have errors\nSELECT sourcefile, sourceline, seqno, name, setting, applied, error\nFROM pg_file_settings\nWHERE NOT applied OR error IS NOT NULL\nORDER BY sourcefile, sourceline;\n\n-- Where does a parameter's effective value come from?\nSELECT name, setting, source, sourcefile, sourceline\nFROM pg_settings\nWHERE name = 'work_mem';","what":"显示已在文件中修改但等待重启的参数、未生效或有错误的配置文件行，以及参数的生效值来自哪个文件/行。","when":"用于在 ALTER SYSTEM 或修改 postgresql.conf 后验证 reload 是否达到预期效果，以及在计划内重启前列出待生效的变更。","caution":"pg_file_settings 读取的是文件当前的内容；如果一个参数在多处定义，只有最后一处的 applied = true（postgresql.auto.conf 最后读取，因此 ALTER SYSTEM 总是优先）。applied = false 且 error 为空的行被另一个定义覆盖；这不是错误。需要重启的参数会显示 error 'setting could not be applied'。有错误的文件在 reload 时可能被部分应用；修改后请运行 pg_reload_conf()，然后检查这些查询和服务器日志。不要手工编辑 postgresql.auto.conf；请使用 ALTER SYSTEM RESET。","since":"PostgreSQL 9.5","tags":["pending_restart","pg_file_settings","restart","configuration error","alter system","config"]},{"id":"checkpoint-stats","task":"检查 checkpoint 行为（PG17+ 为 pg_stat_checkpointer，PG16 及更早为 pg_stat_bgwriter）","category":"参数","command":"-- PG17+\nSELECT num_timed,\n       num_requested,\n       round(100.0 * num_requested / NULLIF(num_timed + num_requested, 0), 1) AS req_pct,\n       restartpoints_timed,\n       restartpoints_req,\n       round((write_time / 1000)::numeric / NULLIF(num_timed + num_requested, 0), 1) AS avg_write_s,\n       round((sync_time / 1000)::numeric / NULLIF(num_timed + num_requested, 0), 1) AS avg_sync_s,\n       pg_size_pretty(buffers_written * current_setting('block_size')::bigint) AS written_by_checkpoint,\n       stats_reset\nFROM pg_stat_checkpointer;\n\n-- PG16 and earlier\nSELECT checkpoints_timed,\n       checkpoints_req,\n       round(100.0 * checkpoints_req / NULLIF(checkpoints_timed + checkpoints_req, 0), 1) AS req_pct,\n       round((checkpoint_write_time / 1000)::numeric / NULLIF(checkpoints_timed + checkpoints_req, 0), 1) AS avg_write_s,\n       round((checkpoint_sync_time / 1000)::numeric / NULLIF(checkpoints_timed + checkpoints_req, 0), 1) AS avg_sync_s,\n       pg_size_pretty(buffers_checkpoint * current_setting('block_size')::bigint) AS written_by_checkpoint,\n       pg_size_pretty(buffers_clean * current_setting('block_size')::bigint) AS written_by_bgwriter,\n       pg_size_pretty(buffers_backend * current_setting('block_size')::bigint) AS written_by_backends,\n       maxwritten_clean,\n       stats_reset\nFROM pg_stat_bgwriter;\n\n-- Related settings\nSELECT name, current_setting(name) AS value\nFROM pg_settings\nWHERE name IN ('checkpoint_timeout', 'max_wal_size', 'min_wal_size',\n               'checkpoint_completion_target', 'log_checkpoints')\nORDER BY name;","what":"显示计划 checkpoint 与因 WAL 量而强制触发的 checkpoint 的比例、每次 checkpoint 的平均写入/同步时间以及写出的缓冲区量（在 PG16 及更早版本中，还包括 bgwriter 和后端进程的写入）。","when":"当写入密集时段出现 I/O 尖峰、WAL 生成量高，或日志警告 'checkpoints are occurring too frequently' 时。","caution":"较高的 req_pct（通常高于 10-20%）表示由于 max_wal_size 被填满而强制触发 checkpoint；请调大 max_wal_size，并在 log_checkpoints = on 时关注 'checkpoint starting: wal' 行。较长的 checkpoint_timeout 会分散 I/O，但会延长崩溃恢复时间。在 PG16 及更早版本中，written_by_backends 较高表示后端进程必须自己写出脏页（shared_buffers/bgwriter 不足）；在 PG17 中此信息位于 pg_stat_io。平均值也计入了因无工作而跳过的计划 checkpoint，因此只能作为粗略数字。计数器是自 stats_reset 以来的累计值。","since":"PostgreSQL 17","note":"在 PG17 中，checkpointer 统计信息从 pg_stat_bgwriter 移到了新的 pg_stat_checkpointer 视图（checkpoints_timed → num_timed，checkpoints_req → num_requested，buffers_checkpoint → buffers_written）；buffers_backend 和 buffers_backend_fsync 被移除。PG18 增加了 num_done 和 slru_written 列。","tags":["checkpoint","pg_stat_checkpointer","pg_stat_bgwriter","max_wal_size","checkpoint_timeout","wal"]},{"id":"relation-size-breakdown","task":"列出按 heap / index / TOAST 细分的表大小以及最大的索引","category":"存储","command":"SELECT n.nspname AS schema_name,\n       c.relname AS table_name,\n       pg_size_pretty(pg_total_relation_size(c.oid)) AS total,\n       pg_size_pretty(pg_relation_size(c.oid)) AS heap,\n       pg_size_pretty(pg_indexes_size(c.oid)) AS indexes,\n       pg_size_pretty(pg_total_relation_size(c.oid) - pg_relation_size(c.oid) - pg_indexes_size(c.oid)) AS toast_fsm_vm,\n       round(100.0 * pg_total_relation_size(c.oid) / NULLIF(pg_database_size(current_database()), 0), 1) AS pct_of_db\nFROM pg_class c\nJOIN pg_namespace n ON n.oid = c.relnamespace\nWHERE c.relkind IN ('r', 'm')\n  AND n.nspname NOT IN ('pg_catalog', 'information_schema')\nORDER BY pg_total_relation_size(c.oid) DESC\nLIMIT 20;\n\n-- Largest indexes and their usage counts\nSELECT s.schemaname,\n       s.relname AS table_name,\n       s.indexrelname AS index_name,\n       pg_size_pretty(pg_relation_size(s.indexrelid)) AS index_size,\n       s.idx_scan,\n       i.indisunique,\n       i.indisprimary\nFROM pg_stat_user_indexes s\nJOIN pg_index i ON i.indexrelid = s.indexrelid\nORDER BY pg_relation_size(s.indexrelid) DESC\nLIMIT 20;","what":"显示最大的表，按总量、heap、索引和 TOAST（+FSM/VM）细分，并给出其在数据库中的占比；以及最大的索引及其扫描次数和 unique/primary 标志。","when":"用于在磁盘使用上升时找出增长来自哪里（数据、索引还是 TOAST），并发现未使用的大索引。","caution":"大小函数会为每个关系访问文件系统，在有数万张表的数据库中可能很慢。分区父表（relkind 'p'）本身为 0 字节，已被排除；计算整个层次结构的总量：SELECT pg_size_pretty(sum(pg_total_relation_size(relid))) FROM pg_partition_tree('sales'); idx_scan = 0 的大索引是删除候选，但计数器只覆盖自上次统计信息重置以来的时间，且只针对本服务器（副本上的使用不可见）；unique/primary 索引用于强制约束，不能删除。","tags":["table size","index size","toast","disk usage","pg_total_relation_size","storage"]},{"id":"extension-update-check","task":"查找有待更新的扩展并进行更新","category":"扩展","command":"SELECT e.extname,\n       e.extversion AS installed,\n       a.default_version AS available,\n       n.nspname AS schema_name,\n       format('ALTER EXTENSION %I UPDATE;', e.extname) AS fix\nFROM pg_extension e\nJOIN pg_available_extensions a ON a.name = e.extname\nJOIN pg_namespace n ON n.oid = e.extnamespace\nWHERE e.extversion IS DISTINCT FROM a.default_version\nORDER BY e.extname;\n\n-- Is there an update path?\nSELECT * FROM pg_extension_update_paths('pgstattuple') WHERE path IS NOT NULL;\n\nALTER EXTENSION pgstattuple UPDATE;","what":"比较数据库中每个扩展的已安装版本与服务器上软件包的默认版本，并为有待更新的扩展生成 ALTER EXTENSION 命令。","when":"在 PostgreSQL 小版本/大版本升级和扩展软件包更新之后，以及定期维护期间。","caution":"升级操作系统软件包不会自动更新 SQL 侧；必须在每个数据库中分别运行 ALTER EXTENSION ... UPDATE（在 pg_upgrade 之后尤为重要）。更新脚本可能会加锁并修改对象（例如 PostGIS）；请先测试并阅读发行说明。迁移到特定版本：ALTER EXTENSION pgstattuple UPDATE TO '1.5'; 部分扩展（例如 pg_stat_statements）还需要重启以重新加载共享库。","tags":["alter extension update","extension version","extension","pg_available_extensions","upgrade","update"]},{"id":"pg-stat-io-analysis","task":"使用 pg_stat_io 分析 I/O 来源（后端类型、对象、上下文）","category":"扩展","command":"SELECT backend_type,\n       object,\n       context,\n       reads,\n       pg_size_pretty(reads * op_bytes) AS read_size,\n       writes,\n       pg_size_pretty(writes * op_bytes) AS write_size,\n       extends,\n       hits,\n       round(100.0 * hits / NULLIF(hits + reads, 0), 1) AS hit_pct,\n       evictions,\n       reuses,\n       fsyncs,\n       round(read_time::numeric, 1) AS read_ms,\n       round(write_time::numeric, 1) AS write_ms\nFROM pg_stat_io\nWHERE coalesce(reads, 0) + coalesce(writes, 0) + coalesce(extends, 0) + coalesce(evictions, 0) > 0\nORDER BY coalesce(reads, 0) + coalesce(writes, 0) DESC;\n\n-- Who writes? Ideally most writes come from the checkpointer and background writer\nSELECT backend_type,\n       pg_size_pretty(sum(writes * op_bytes)) AS written,\n       round(100.0 * sum(writes) / NULLIF(sum(sum(writes)) OVER (), 0), 1) AS pct_of_writes,\n       sum(fsyncs) AS fsyncs\nFROM pg_stat_io\nWHERE object = 'relation' AND writes > 0\nGROUP BY backend_type\nORDER BY sum(writes) DESC;","what":"按后端类型（client backend、autovacuum、checkpointer...）、对象（relation/temp relation）和上下文（normal、vacuum、bulkread、bulkwrite）显示读取、写入、扩展、驱逐和 fsync；以及写入来自哪些进程类型。","when":"在 I/O 瓶颈期间，用于判断负载来自查询、autovacuum 还是 checkpoint，并验证 shared_buffers/bgwriter 设置。","caution":"pg_stat_io 是集群范围的，自上次重置以来累计；请取两次采样的差值来观察变化（重置：SELECT pg_stat_reset_shared('io');）。read_time/write_time 只有在 track_io_timing = on 时才会填充。reads 也包括由操作系统页缓存提供的读取。client backend 的写入或淘汰较高，表示后端进程自己写出缓冲区以腾出空间；请检查 shared_buffers、bgwriter_lru_maxpages 和 checkpoint 设置。context = 'vacuum'/'bulkread' 的行反映的是环形缓冲区的使用；其中的 reuses 属于正常现象。","since":"PostgreSQL 16","note":"pg_stat_io 在 PG16 中引入。PG18 移除了 op_bytes 列，增加了 read_bytes/write_bytes/extend_bytes，并在此视图中也报告 WAL I/O；在 PG18 上请使用 read_bytes 代替 reads * op_bytes。","tags":["pg_stat_io","io statistics","evictions","fsync","shared_buffers","io"]}]},"recipes/mysql.json":{"engineId":"mysql","engine":"MySQL / MariaDB","order":3,"recipes":[{"id":"rotate-binlog","task":"轮换 binary log","category":"Binlog 与日志","command":"FLUSH BINARY LOGS;","what":"关闭当前 binary log 文件并切换到新文件。","when":"用于在备份前创建干净的 binlog 起点，或限制文件大小。","caution":"频繁轮换会产生许多小的 binlog 文件。同样的命令在 MariaDB 中也可用；FLUSH LOGS 会轮换所有日志。","tags":["binlog","rotate","flush","log"]},{"id":"purge-binlog","task":"清除旧的 binary log（PURGE）","category":"Binlog 与日志","command":"PURGE BINARY LOGS BEFORE NOW() - INTERVAL 7 DAY;","what":"从磁盘删除早于指定日期的 binary log 文件。","when":"当 binlog 填满磁盘时，用于清理不再需要的旧日志。","caution":"如果副本仍需要正在被删除的 binlog，复制会中断；请先用 SHOW REPLICA STATUS 确认所有副本都已越过它。删除到指定文件为止：PURGE BINARY LOGS TO 'mysql-bin.000123'; 自动清理请使用 binlog_expire_logs_seconds 设置。","tags":["binlog","purge","cleanup","log"]},{"id":"show-binlogs","task":"查看 binary log 状态","category":"Binlog 与日志","command":"SHOW BINARY LOGS;","what":"列出服务器上的所有 binary log 文件及其大小。","when":"用于查看 binlog 磁盘使用情况以及存在哪些文件。","caution":"当前正在写入的 binlog 文件及其位置：SHOW BINARY LOG STATUS;（MySQL 8.2+）。旧版本使用 SHOW MASTER STATUS（在 8.2–8.3 中弃用，在 8.4 中移除）。","tags":["binlog","status","show","log"]},{"id":"enable-general-slow-log","task":"启用通用查询日志和慢查询日志","category":"Binlog 与日志","command":"SET GLOBAL slow_query_log = 'ON'; SET GLOBAL long_query_time = 1; SET GLOBAL general_log = 'ON';","what":"在运行时启用慢查询日志（超过 1 秒的查询）和记录每条查询的通用日志。","when":"用于临时分析慢查询或全部查询流量。","caution":"general_log 会带来显著的性能和磁盘开销；诊断完成后务必关闭（SET GLOBAL general_log='OFF'）。这些设置在重启后会恢复；如需永久生效，请使用 SET PERSIST。","tags":["slow-log","general-log","log","diagnostics"]},{"id":"innodb-redo-capacity","task":"修改 InnoDB redo log 大小","category":"存储","command":"SET GLOBAL innodb_redo_log_capacity = 2147483648;","what":"在运行时动态设置 InnoDB redo log 的总容量（以字节为单位，此处为 2 GB）。","when":"用于在写入密集的负载下降低 checkpoint 频率并提升性能。","caution":"innodb_redo_log_capacity 在 MySQL 8.0.30 中引入，取代旧的 innodb_log_file_size + innodb_log_files_in_group 设置。在旧版本/MariaDB 上，innodb_log_file_size 需在 my.cnf 中修改并重启。永久生效：SET PERSIST innodb_redo_log_capacity = 2147483648;","since":"MySQL 8.0.30","note":"innodb_redo_log_capacity 在 MySQL 8.0.30 中引入，使 redo log 可以动态（无需重启）调整大小；此前必须设置 innodb_log_file_size + innodb_log_files_in_group 并重启服务器。","tags":["innodb","redo-log","capacity","storage"]},{"id":"innodb-undo-tablespace","task":"添加/管理 InnoDB undo 表空间","category":"存储","command":"CREATE UNDO TABLESPACE undo_003 ADD DATAFILE 'undo_003.ibu';","what":"创建新的 InnoDB undo 表空间；undo log 与数据文件分开管理，并可被截断。","when":"当长时间运行的事务使 undo log 膨胀时用于管理空间，或将 undo 移到单独的磁盘。","caution":"文件名必须以 .ibu 结尾。undo 表空间必须先设为非活动才能删除：ALTER UNDO TABLESPACE undo_003 SET INACTIVE; 然后 DROP UNDO TABLESPACE undo_003; 这是 MySQL 8.0+ 的功能。","since":"MySQL 8.0","note":"用 SQL 创建/删除 undo 表空间（CREATE/DROP UNDO TABLESPACE）在 MySQL 8.0 中引入；此前 undo 表空间的数量由 innodb_undo_tablespaces 固定，无法在运行时管理。","tags":["innodb","undo-log","tablespace","storage"]},{"id":"create-tablespace","task":"添加通用表空间","category":"存储","command":"CREATE TABLESPACE ssd_ts ADD DATAFILE 'ssd_ts.ibd' ENGINE=InnoDB;","what":"创建一个可供多个表共享的 InnoDB 通用表空间。","when":"用于将表归组到特定文件/磁盘，或减少文件数量。","caution":"如果 DATAFILE 路径是相对路径，会在 datadir 下创建；放在其他磁盘上需要绝对路径和相应权限。将表移到这里：ALTER TABLE customer TABLESPACE ssd_ts; 表空间只有为空时才能删除。","tags":["tablespace","innodb","storage","create"]},{"id":"optimize-table","task":"OPTIMIZE 表","category":"存储","command":"OPTIMIZE TABLE customer;","what":"重建表以消除碎片、回收未使用的空间并更新索引统计信息。","when":"用于回收因大量 DELETE/UPDATE 而膨胀的 InnoDB 表的空间。","caution":"在 InnoDB 上，OPTIMIZE 实际上以重建表的在线 ALTER 方式运行，但仍需要 I/O 和临时磁盘空间；大表需要与表大小相等的可用磁盘空间。请在低流量时运行以减少锁定。","tags":["optimize","table","defragment","storage"]},{"id":"table-schema-size","task":"查询表和 schema 的大小","category":"存储","command":"SELECT table_schema AS schema_name, table_name AS table_name, ROUND((data_length + index_length) / 1024 / 1024, 1) AS size_mb FROM information_schema.tables WHERE table_schema NOT IN ('mysql','information_schema','performance_schema','sys') ORDER BY (data_length + index_length) DESC LIMIT 20;","what":"按数据和索引总大小（MB）列出最大的 20 个表。","when":"用于分析磁盘使用情况并找出哪些表占用空间。","caution":"对于 InnoDB，information_schema 中的大小是近似值。如需按 schema 汇总，请只 GROUP BY table_schema，而不是 table_name。","tags":["size","table","schema","information-schema"]},{"id":"analyze-table","task":"更新表统计信息（ANALYZE TABLE）","category":"维护","command":"ANALYZE TABLE customer;","what":"重新计算表的索引/键分布统计信息，使优化器能够选择更好的计划。","when":"当大批量数据变更后出现糟糕的查询计划时。","caution":"运行期间会在表上短暂获取元数据锁。在 InnoDB 上基于采样；如需更高精度，请调大 innodb_stats_persistent_sample_pages。","tags":["analyze","statistics","optimizer","maintenance"]},{"id":"rebuild-table","task":"重建表/索引","category":"维护","command":"ALTER TABLE customer ENGINE=InnoDB;","what":"使用相同引擎重建表；重新创建索引和数据页以消除碎片和索引膨胀。","when":"用于通过空 ALTER 刷新表，或修复损坏的页面布局。","caution":"大表需要与表大小相等的临时磁盘空间。为减少锁定：ALTER TABLE customer ENGINE=InnoDB, ALGORITHM=INPLACE, LOCK=NONE;","tags":["rebuild","alter","innodb","maintenance"]},{"id":"check-table","task":"检查表完整性（CHECK TABLE）","category":"维护","command":"CHECK TABLE customer;","what":"检查表及其索引是否损坏并返回状态。","when":"出现意外错误或怀疑有损坏时。","caution":"检查期间会在表上获取读锁。如果发现损坏，InnoDB 通常通过 dump/restore 修复；REPAIR TABLE 只适用于 MyISAM/Archive 等引擎。","tags":["check","integrity","corruption","maintenance"]},{"id":"set-global-persist","task":"修改全局变量并持久化","category":"参数","command":"SET PERSIST innodb_buffer_pool_size = 4294967296;","what":"同时在运行中的服务器上设置变量，并使其在重启后持久生效（mysqld-auto.cnf）。","when":"用于在不重启的情况下永久修改服务器变量。","caution":"SET PERSIST 是 MySQL 8.0+ 的功能；MariaDB 不支持（在 MariaDB 中请编辑 my.cnf 并重启）。仅运行时修改请使用 SET GLOBAL。只能重启生效的变量需要 SET PERSIST_ONLY。","since":"MySQL 8.0","note":"SET PERSIST 在 MySQL 8.0 中引入，通过写入 mysqld-auto.cnf 使变量持久化；此前（以及在 MariaDB 中）持久化需要手动编辑 my.cnf 并重启。","tags":["set-persist","global","variable","parameter"]},{"id":"show-variables","task":"查看变量的当前值","category":"参数","command":"SHOW VARIABLES LIKE 'innodb_buffer_pool_size';","what":"显示指定服务器变量（或匹配模式的变量）的生效值。","when":"用于检查参数的运行值。","caution":"要查看全局值而不是会话值，请使用 SHOW GLOBAL VARIABLES。查看值的来源（文件、命令行、persist）：SELECT * FROM performance_schema.variables_info WHERE variable_name = 'innodb_buffer_pool_size';","tags":["show-variables","parameter","current-value","config"]},{"id":"flush-privileges","task":"重新加载授权表（FLUSH PRIVILEGES）","category":"参数","command":"FLUSH PRIVILEGES;","what":"在直接修改 mysql.user 等授权表后，将其重新加载到内存中。","when":"用于使通过手动 INSERT/UPDATE 对授权表所做的更改生效。","caution":"GRANT/REVOKE/CREATE USER 已会立即应用权限；之后执行 FLUSH PRIVILEGES 是不必要的。只有在直接修改授权表时才需要。","tags":["flush-privileges","grants","privileges","reload"]},{"id":"setup-replica","task":"建立副本（CHANGE REPLICATION SOURCE）","category":"复制","command":"CHANGE REPLICATION SOURCE TO SOURCE_HOST='source_host', SOURCE_PORT=3306, SOURCE_USER='replicator', SOURCE_PASSWORD='secret', SOURCE_AUTO_POSITION=1;","what":"将此服务器配置为副本，使用基于 GTID 的自动定位跟随指定的源。","when":"用于建立新副本或更改复制源。","caution":"这是 MySQL 8.0.23+ 的术语；旧版本使用 CHANGE MASTER TO ... MASTER_HOST（在 8.0.23–8.3 中为别名，在 8.4 中移除）。SOURCE_AUTO_POSITION=1 要求启用 GTID（gtid_mode=ON）；否则必须指定 SOURCE_LOG_FILE 和 SOURCE_LOG_POS。运行命令前必须先停止复制。如果复制用户是用 8.0+ 默认的 caching_sha2_password 创建的，连接需要 TLS：添加 SOURCE_SSL=1，或在不使用 TLS 时设置 GET_SOURCE_PUBLIC_KEY=1；否则 I/O 线程会因错误 2061 而无法连接。","since":"MySQL 8.0.23","note":"CHANGE REPLICATION SOURCE TO（SOURCE_HOST 等）在 MySQL 8.0.23 中引入；此前使用 CHANGE MASTER TO ... MASTER_HOST / MASTER_AUTO_POSITION。","tags":["replication","change-source","replica","setup"]},{"id":"start-stop-replica","task":"启动/停止副本","category":"复制","command":"STOP REPLICA; START REPLICA;","what":"停止并重新启动复制 I/O 线程和 SQL 线程。","when":"用于维护、配置变更或修复复制错误。","caution":"这是 MySQL 8.0.22+ 的术语；旧版本使用 STOP SLAVE / START SLAVE。仅针对 SQL 线程：STOP REPLICA SQL_THREAD; 执行 STOP REPLICA 后可以运行 CHANGE REPLICATION SOURCE。","since":"MySQL 8.0.22","note":"START REPLICA / STOP REPLICA 在 MySQL 8.0.22 中引入；此前使用 START SLAVE / STOP SLAVE（旧语法在 8.0.22–8.3 中作为别名可用，在 8.4 中移除）。","tags":["replication","start","stop","replica"]},{"id":"replica-status","task":"查看副本状态（SHOW REPLICA STATUS）","category":"复制","command":"SHOW REPLICA STATUS\\G","what":"详细显示副本与源的连接、线程状态、延迟以及任何错误。","when":"用于检查复制是否健康以及落后多少。","caution":"这是 MySQL 8.0.22+ 的术语；旧版本使用 SHOW SLAVE STATUS（在 8.0.22–8.3 中为别名，在 8.4 中移除）。关键字段：Replica_IO_Running 和 Replica_SQL_Running 必须为 'Yes'，Seconds_Behind_Source 显示延迟，Last_Error 显示错误。","since":"MySQL 8.0.22","note":"SHOW REPLICA STATUS 在 MySQL 8.0.22 中引入；此前使用 SHOW SLAVE STATUS，字段名为 Slave_IO_Running / Seconds_Behind_Master 之类。","tags":["replication","status","replica","lag"]},{"id":"skip-gtid-transaction","task":"跳过出错的事务（GTID）","category":"复制","command":"STOP REPLICA; SET GTID_NEXT='aaaaaaaa-bbbb-cccc-dddd-eeeeeeeeeeee:15'; BEGIN; COMMIT; SET GTID_NEXT='AUTOMATIC'; START REPLICA;","what":"为指定的 GTID 提交一个空事务，使该事务被视为'已应用'，从而让复制继续进行。","when":"当副本上重复/无法应用的事件使复制停止时，用于跳过该事件。","caution":"跳过事务会造成副本与源之间的数据不一致；只有在清楚被跳过事件的影响时才使用。从 SHOW REPLICA STATUS 的 Last_Error / Retrieved_Gtid_Set 中获取 GTID 值。如果 GTID 关闭，请使用 SET GLOBAL sql_replica_skip_counter = 1;（8.0.26+；此前为 sql_slave_skip_counter，在 8.0.26+ 中为已弃用的别名）。","tags":["replication","gtid","skip","recovery"]},{"id":"kill-query-connection","task":"终止查询或连接（KILL）","category":"会话","command":"KILL QUERY 4567;","what":"终止指定连接 ID 上当前正在运行的查询，但不关闭连接。","when":"用于停止长时间运行/卡住的查询。","caution":"关闭整个连接（会话）：KILL CONNECTION 4567;（或直接 KILL 4567）。如果终止的是正在运行的写事务，它会被回滚，在大表上可能需要很长时间。","tags":["kill","query","connection","session"]},{"id":"process-list","task":"查看进程列表","category":"会话","command":"SELECT id, user, host, db, command, time, state, LEFT(info, 100) AS query FROM performance_schema.processlist WHERE command <> 'Sleep' ORDER BY time DESC;","what":"列出所有非空闲连接、它们正在运行的查询以及已耗时间。","when":"用于诊断活动会话和长时间运行的查询。","caution":"快捷方式：SHOW FULL PROCESSLIST; performance_schema.processlist 提供所有列，并可用 WHERE 过滤。time 列以秒为单位。","tags":["processlist","session","activity","performance-schema"]},{"id":"blocking-locks","task":"查看阻塞锁（data_lock_waits）","category":"会话","command":"SELECT r.trx_id AS waiting_trx, r.trx_mysql_thread_id AS waiting_thread, LEFT(r.trx_query,80) AS waiting_query, b.trx_id AS blocking_trx, b.trx_mysql_thread_id AS blocking_thread, LEFT(b.trx_query,80) AS blocking_query FROM performance_schema.data_lock_waits w JOIN information_schema.innodb_trx r ON r.trx_id = w.requesting_engine_transaction_id JOIN information_schema.innodb_trx b ON b.trx_id = w.blocking_engine_transaction_id;","what":"显示哪个事务被哪个事务阻塞，以及相关的查询和线程 ID。","when":"用于诊断锁等待以及由死锁引起的性能下降。","caution":"performance_schema.data_lock_waits 在 MySQL 8.0 中引入；旧版本使用 information_schema.innodb_lock_waits。要停止阻塞线程，请使用 KILL blocking_thread;。","since":"MySQL 8.0","note":"performance_schema.data_lock_waits 在 MySQL 8.0 中引入；在 MySQL 5.7 及更早版本中，使用 information_schema.innodb_lock_waits 获取相同信息。","tags":["blocking","lock","data-lock-waits","deadlock"]}]},"recipes/mysql-ext.json":{"engineId":"mysql","engine":"MySQL","order":2,"recipes":[{"id":"replica-applier-health","task":"监控副本延迟和 applier worker 错误（performance_schema）","category":"复制","command":"-- Receiver (I/O) thread: connection state and last error\nSELECT CHANNEL_NAME, SERVICE_STATE, LAST_ERROR_NUMBER,\n       LEFT(LAST_ERROR_MESSAGE, 100) AS last_error,\n       LAST_QUEUED_TRANSACTION, LAST_HEARTBEAT_TIMESTAMP\nFROM performance_schema.replication_connection_status;\n\n-- Applier workers: error, applied GTID and real lag\nSELECT CHANNEL_NAME, WORKER_ID, SERVICE_STATE, LAST_ERROR_NUMBER,\n       LEFT(LAST_ERROR_MESSAGE, 120) AS last_error,\n       LAST_APPLIED_TRANSACTION,\n       APPLYING_TRANSACTION,\n       IF(APPLYING_TRANSACTION = '', 0,\n          TIMESTAMPDIFF(SECOND, APPLYING_TRANSACTION_ORIGINAL_COMMIT_TIMESTAMP, NOW(6))) AS current_lag_sec,\n       TIMESTAMPDIFF(MICROSECOND, LAST_APPLIED_TRANSACTION_ORIGINAL_COMMIT_TIMESTAMP,\n                     LAST_APPLIED_TRANSACTION_END_APPLY_TIMESTAMP) / 1000000 AS last_trx_lag_sec\nFROM performance_schema.replication_applier_status_by_worker\nORDER BY CHANNEL_NAME, WORKER_ID;","what":"将 SHOW REPLICA STATUS 汇总在一行中的信息按通道和 worker 细分：receiver 线程状态、每个 worker 的最后错误、已应用/正在应用的 GTID，以及根据原始提交时间戳计算的真实延迟。","when":"当 Seconds_Behind_Source 看起来不可靠（在 0 和很大的值之间跳动、为 NULL）时，用于找出多线程副本上哪个 worker 卡住，以及为监控提供准确的延迟指标。","caution":"current_lag_sec 只有在正在应用事务时才有意义；空闲副本返回 0。要查看 receiver 是否也落后，请将 LAST_QUEUED_TRANSACTION 与源的 gtid_executed 进行比较。原始提交时间戳来自源的时钟；服务器之间的时钟偏差（NTP）会扭曲结果。在大事务开始时 Seconds_Behind_Source 保持接近零，结束时才跳升；长事务会使该指标产生误导。","note":"ORIGINAL_COMMIT_TIMESTAMP / END_APPLY_TIMESTAMP 列在 8.0.2 中引入，覆盖整个链路（源 → 中间副本 → 副本）；5.7 只有 Seconds_Behind_Master。","since":"MySQL 8.0","tags":["replication","lag","applier","worker","performance_schema","delay"]},{"id":"replica-parallel-applier","task":"调优多线程副本（replica_parallel_workers）","category":"复制","command":"-- Replica: stop the applier, set worker count and commit order\nSTOP REPLICA SQL_THREAD;\nSET PERSIST replica_parallel_workers = 8;\nSET PERSIST replica_preserve_commit_order = ON;\nSTART REPLICA SQL_THREAD;\nSELECT COUNT(*) AS workers FROM performance_schema.replication_applier_status_by_worker;\n\n-- Source (8.0 only): switch dependency tracking to WRITESET (default in 8.4, variable removed)\nSET PERSIST binlog_transaction_dependency_tracking = WRITESET;","what":"设置副本上并行应用事务的 worker 数量；replica_preserve_commit_order 使提交顺序与源保持一致。源上的 WRITESET 依赖跟踪使涉及不同行的事务即使在源上是串行提交的，也能并行应用。","when":"当副本跟不上源的写入速度，而 applier 实际上是单线程，导致延迟不断累积时。","caution":"新的 worker 数量在 applier 重启时生效。运行的 worker 数多于 CPU 核心数会增加协调开销；从 8–16 的范围开始，并观察延迟和 worker 错误。单个超大事务或在没有 PK 的表上的大量 DML 无法并行化，这通常是持续延迟的真正原因。replica_preserve_commit_order=OFF 可提供更高的并行度，但允许副本上出现短暂的乱序提交；在提供读服务的副本上请保持 ON。","note":"自 8.0.27 起默认值为 replica_parallel_workers=4、replica_parallel_type=LOGICAL_CLOCK 和 replica_preserve_commit_order=ON。8.0.26 之前这些变量名为 slave_parallel_workers / slave_preserve_commit_order。binlog_transaction_dependency_tracking 在 8.0.35 中弃用，在 8.4 中移除，8.4 中 WRITESET 行为为默认。","since":"MySQL 8.0.26","tags":["replication","parallel","replica_parallel_workers","writeset","lag","mta"]},{"id":"replica-fix-row-conflict","task":"修复出错的副本：1062 / 1032 行冲突","category":"复制","command":"-- 1) Failing worker, error and GTID\nSELECT WORKER_ID, LAST_ERROR_NUMBER, LEFT(LAST_ERROR_MESSAGE, 250) AS last_error, APPLYING_TRANSACTION\nFROM performance_schema.replication_applier_status_by_worker\nWHERE LAST_ERROR_NUMBER <> 0;\n\n-- 2) Shell: decode the row images of that transaction from the relay log\nmysqlbinlog --base64-output=DECODE-ROWS -vv --include-gtids='af6bc361-c353-11f1-8f12-f69e63ad6c0e:11' /var/lib/mysql/db2-relay-bin.0* | grep '^###'\n\n-- 3a) 1062 Duplicate entry: delete the extra row on the replica without binlogging\nSET SESSION sql_log_bin = 0;\nDELETE FROM shop.orders WHERE id = 20001;\n-- 3b) 1032 Can't find record: insert the missing row from the WHERE (before image) values\nINSERT INTO shop.orders (id, cust, amount, note, created) VALUES (6, 6, 9.00, 'n6', '2026-10-08 20:09:44');\nSET SESSION sql_log_bin = 1;\n\n-- 4) Start the applier and verify\nSTART REPLICA SQL_THREAD;\nSELECT SERVICE_STATE, LAST_ERROR_NUMBER FROM performance_schema.replication_applier_status_by_coordinator;","what":"不跳过事务，而是将副本上的行恢复到源所期望的状态，以修复两种最常见的基于行的复制中断（1062 duplicate key、1032 row not found）；数据一致性得以保持。","when":"当 Replica_SQL_Running = No 且错误为 1062 / 1032 时；作为正确的修复方式，代替用空事务跳过 GTID。","caution":"在副本上的修复必须在 sql_log_bin=0 下运行；否则会产生 errant GTID，在故障转移时造成问题。如果 super_read_only=ON，请临时禁用并在之后重新启用。before-image 的值假定 binlog_row_image=FULL；使用 MINIMAL 时只记录 PK，行的其余部分必须从源读取。一张表中出现一次冲突通常意味着还有更多；修复后请用 pt-table-checksum 将该表与源进行比对。relay log 路径：SELECT @@relay_log_basename;","note":"replica_exec_mode=IDEMPOTENT 会静默吞掉这些错误；它是为 NDB 和特殊情况设计的，不得长期开启。如果确实必须跳过事务，请参见 skip-gtid-transaction 操作指南。","since":"MySQL 8.0.22","tags":["replication","1062","1032","mysqlbinlog","relay log","broken replica"]},{"id":"gtid-errant-transactions","task":"查找并消除 errant GTID 事务","category":"复制","command":"-- 1) On the source: get gtid_executed\nSELECT @@GLOBAL.gtid_executed;\n\n-- 2) On the replica: GTIDs missing on the source (empty result = clean)\nSELECT GTID_SUBTRACT(@@GLOBAL.gtid_executed,\n       'af6bc361-c353-11f1-8f12-f69e63ad6c0e:1-13') AS errant_gtids;\n\n-- 3) If the content is harmless: commit an empty transaction per errant GTID on the source\nSET GTID_NEXT = 'b030e5a6-c353-11f1-a887-523817ec642a:1'; BEGIN; COMMIT;\nSET GTID_NEXT = 'b030e5a6-c353-11f1-a887-523817ec642a:2'; BEGIN; COMMIT;\nSET GTID_NEXT = 'AUTOMATIC';","what":"通过 GTID 集合差集检测直接在副本上执行、在源上没有对应项的事务（errant），并通过在源上用空事务标记它们，使整个拓扑中的 GTID 集合保持一致。","when":"作为故障转移/主备切换前的例行检查；当 Orchestrator 或 MySQL Shell 因 errant GTID 拒绝提升副本，或副本在连接新源时因 1236 失败时。","caution":"先通过副本的 binlog（mysqlbinlog --include-gtids=...）查明 errant 事务做了什么。如果它修改了数据，空事务只是掩盖了 GTID，数据差异依然存在；请重建副本（CLONE）或手动对齐数据。在源上提交的空事务会传播到每个副本；这是预期行为。在副本上保持 super_read_only=ON 以防再次发生。","note":"副本一旦被提升，其他副本就会向它请求这些 GTID；如果该 binlog 已被清除，复制会以错误 1236 中断。这就是为什么 errant GTID 是一种在故障转移之前一直不可见的风险。","since":"MySQL 5.6","tags":["gtid","errant","gtid_subtract","failover","replication","empty transaction"]},{"id":"replica-rebuild-clone","task":"使用 CLONE 插件从头重建副本","category":"复制","command":"-- Donor (healthy replica or source)\nINSTALL PLUGIN clone SONAME 'mysql_clone.so';\nCREATE USER 'clone_donor'@'%' IDENTIFIED BY 'Cl0ne#Donor';\nGRANT BACKUP_ADMIN ON *.* TO 'clone_donor'@'%';\n\n-- Recipient (replica to rebuild; its data is replaced)\nINSTALL PLUGIN clone SONAME 'mysql_clone.so';\nSET GLOBAL clone_valid_donor_list = 'db1:3306';\nCLONE INSTANCE FROM 'clone_donor'@'db1':3306 IDENTIFIED BY 'Cl0ne#Donor';\n\n-- After the restart: verify and start replication\nSELECT STATE, ERROR_NO, BINLOG_FILE, BINLOG_POSITION, GTID_EXECUTED FROM performance_schema.clone_status;\nSELECT STAGE, STATE, ROUND(DATA / 1024 / 1024) AS data_mb FROM performance_schema.clone_progress;\nCHANGE REPLICATION SOURCE TO SOURCE_HOST = 'db1', SOURCE_PORT = 3306, SOURCE_USER = 'repl', SOURCE_PASSWORD = 'Repl#2024', SOURCE_AUTO_POSITION = 1, SOURCE_SSL = 1;\nSTART REPLICA;","what":"通过网络物理复制 donor 的 InnoDB 数据，重启 recipient 并带过 GTID 状态；无需单独的备份、传输和还原步骤即可生成一致的副本。","when":"用于重建损坏的副本、存在 errant GTID 的副本或落后太多以至于其 binlog 已被清除的副本，或添加新副本。","caution":"recipient 上的所有数据（包括用户）都会被 donor 的副本替换；root 密码变为 donor 的密码。donor 和 recipient 需要相同的版本系列、平台和插件集（8.0.37 之前要求完全相同的小版本）。在 recipient 上运行命令的用户需要 CLONE_ADMIN。克隆完成后 mysqld 会自行重启；如果没有 systemd / mysqld_safe 之类的监管进程，会返回 3707 'Restart server failed (mysqld is not managed by supervisor process)'。这不是失败：克隆已完成，请手动启动服务器。donor 会承受较重的 IO / 网络负载；可用 clone_max_concurrency 和 clone_max_data_bandwidth 限制。GTID 关闭时，请将 clone_status 中的 BINLOG_FILE / BINLOG_POSITION 用作 SOURCE_LOG_FILE / SOURCE_LOG_POS。","note":"只克隆 InnoDB 数据；MyISAM 和 CSV 表到达时为空。在 InnoDB Cluster 中，addInstance(..., {recoveryMethod: 'clone'}) 使用相同的机制。","since":"MySQL 8.0.17","tags":["clone","rebuild","replica","provisioning","gtid","reseed"]},{"id":"delayed-replica-recover","task":"使用延迟副本从错误的 DROP / DELETE 中恢复（SOURCE_DELAY + UNTIL）","category":"复制","command":"-- Setup: the replica applies events one hour behind\nSTOP REPLICA;\nCHANGE REPLICATION SOURCE TO SOURCE_DELAY = 3600;\nSTART REPLICA;\n\n-- During the incident: stop the applier (the receiver keeps fetching binlogs)\nSTOP REPLICA SQL_THREAD;\n\n-- Find the bad transaction's GTID in the source binlog, apply up to just before it\nCHANGE REPLICATION SOURCE TO SOURCE_DELAY = 0;\nSTART REPLICA SQL_THREAD UNTIL SQL_BEFORE_GTIDS = 'af6bc361-c353-11f1-8f12-f69e63ad6c0e:17';\nSELECT SERVICE_STATE FROM performance_schema.replication_applier_status_by_coordinator;","what":"让副本以固定的时间延迟应用事件；当错误的 DROP / DELETE 发生在源上时，将副本前滚到该事务之前，并从中恢复丢失的数据。","when":"在从备份还原需要数小时的大型数据库上，作为应对人为错误的快速恢复层。","caution":"当 coordinator 为 OFF 时，数据处于错误操作之前的状态；用 mysqldump 导出受影响的表并加载到源中。之后将副本重新置于延迟模式；如果它也应用了错误的事务，这里的数据也会丢失。延迟副本不得作为故障转移候选（在 Orchestrator / Router / ProxySQL 中将其排除）。relay log 会在整个延迟期间累积；请相应规划磁盘大小。","note":"8.0.23 之前请使用 CHANGE MASTER TO MASTER_DELAY。延迟从直接源上的提交开始计算，因此链路中带延迟的中间副本会叠加。SQL_BEFORE_GTIDS 在指定 GTID 之前停止，SQL_AFTER_GTIDS 会把它也应用；在 8.2 之前（包括 8.0），在多线程 applier 上使用 SQL_AFTER_GTIDS 会发出警告，副本会切换到单线程模式。","since":"MySQL 8.0.23","tags":["delayed replica","source_delay","until","sql_before_gtids","recovery","drop table"]},{"id":"mysqldump-consistent","task":"使用 mysqldump 获取带 GTID 信息的一致性逻辑备份","category":"备份与 PITR","command":"mysqldump -h db1 -u backup -p \\\n  --single-transaction --source-data=2 --set-gtid-purged=ON \\\n  --routines --events --triggers --hex-blob \\\n  --all-databases | gzip > /backup/full_$(date +%F_%H%M).sql.gz\n\n# Restore: GTID sets must not overlap on the target\nzcat /backup/full_2026-10-08_0200.sql.gz | mysql -h db2 -u root -p","what":"在单个一致性快照中导出 InnoDB 表，并在开头写入 binlog 文件/位置（作为注释）以及 SET @@GLOBAL.GTID_PURGED 语句，使该导出可用于初始化副本或作为基于 binlog 的 PITR 起点。","when":"用于数十 GB 以内的数据库、迁移 schema/表，以及版本之间的逻辑迁移。","caution":"--single-transaction 只对 InnoDB 保证一致；MyISAM 表的导出是不一致的。导出期间对表执行 DDL（ALTER / RENAME / TRUNCATE）会使导出失败或遗漏该表。--source-data / --set-gtid-purged 在开始时会短暂获取全局读锁（需要 RELOAD 权限）；如果此时有长查询在运行，锁等待期间所有写入都会停滞。部分导出（--databases）仍会写入所有 GTID；将子集迁移到另一台服务器时请使用 --set-gtid-purged=OFF。导出是单线程的；对于大数据量，优先使用 MySQL Shell dump 或 XtraBackup。","note":"--source-data 在 8.0.26 中取代了 --master-data。mysqlpump 在 8.0.34 中弃用，在 8.4 中移除；并行逻辑备份请使用 MySQL Shell 的 util.dumpInstance。","since":"MySQL 8.0.26","tags":["mysqldump","backup","single-transaction","gtid","logical backup","dump"]},{"id":"mysqlsh-dump-load","task":"使用 MySQL Shell 并行导出/加载（util.dumpInstance、loadDump）","category":"备份与 PITR","command":"# Parallel, zstd-compressed, consistent instance dump\nmysqlsh backup@db1:3306 -- util dump-instance /backup/dump_20261008 --threads=8 --compression=zstd --consistent=true\n# Single schema\nmysqlsh backup@db1:3306 -- util dump-schemas shop --outputUrl=/backup/shop_20261008 --threads=8\n\n# Load: local_infile must be temporarily ON on the target\nmysql -h db2 -u root -p -e \"SET GLOBAL local_infile = ON\"\nmysqlsh root@db2:3306 -- util load-dump /backup/dump_20261008 --threads=8 --skipBinlog=false\nmysql -h db2 -u root -p -e \"SET GLOBAL local_infile = OFF\"","what":"将表拆分为多个块并用多线程导出/加载；导出和还原都比 mysqldump 快很多倍。导出目录在 @.json 中保存 GTID 和 binlog 坐标。","when":"用于数百 GB 的逻辑备份、升级期间的逻辑迁移，以及本地 → 云（RDS、HeatWave）迁移。","caution":"--consistent=true 会短暂获取全局读锁/备份锁；如果有长查询在运行，写入会等待。loadDump 使用 LOAD DATA LOCAL，因此目标需要 local_infile=ON，之后应将其关闭。中断的加载会从停止处继续（进度文件）；使用 --resetProgress 重新开始。默认不加载用户账户（--loadUsers=true）。--skipBinlog=true 在加载期间禁用 binlog；如果目标有副本，请保持为 false。","note":"这不是物理备份；PITR 请从导出的 @.json 中的 gtidExecuted 值开始应用 binlog。ocimds / compatibility 选项可生成符合 HeatWave / RDS 限制的导出。","since":"MySQL Shell 8.0.21","tags":["mysqlsh","dumpinstance","loaddump","parallel","logical backup","migration"]},{"id":"xtrabackup-full-incremental","task":"使用 XtraBackup 进行全量 + 增量物理备份和还原","category":"备份与 PITR","command":"# Full backup\nxtrabackup --backup --host=127.0.0.1 --user=bkp --password='Bkp#2024' --target-dir=/backup/full --parallel=4\n# Incremental (based on the previous backup)\nxtrabackup --backup --host=127.0.0.1 --user=bkp --password='Bkp#2024' --target-dir=/backup/inc1 --incremental-basedir=/backup/full\n\n# Prepare: full and intermediate incrementals with --apply-log-only, final step without it\nxtrabackup --prepare --apply-log-only --target-dir=/backup/full\nxtrabackup --prepare --apply-log-only --target-dir=/backup/full --incremental-dir=/backup/inc1\nxtrabackup --prepare --target-dir=/backup/full\n\n# Restore: mysqld stopped and datadir empty\nsystemctl stop mysqld\nxtrabackup --copy-back --target-dir=/backup/full --datadir=/var/lib/mysql\nchown -R mysql:mysql /var/lib/mysql\nsystemctl start mysqld\ncat /backup/full/xtrabackup_binlog_info","what":"在服务器运行时通过复制 InnoDB 文件和 redo log 进行热物理备份；增量备份只包含自上一次备份以来变化的页面。prepare 步骤会应用 redo 并使备份一致。","when":"用于需要快速还原（比逻辑还原快很多倍）的大型数据库、初始化副本，以及每日全量 + 每小时增量的策略。","caution":"XtraBackup 版本必须与服务器版本系列匹配（8.0 服务器 → XtraBackup 8.0，8.4 → 8.4）；如果服务器使用的 redo / 数据字典格式比 XtraBackup 构建更新，备份会被拒绝。未经 prepare 的备份是不一致的。在中间步骤忘记 --apply-log-only 会使之后的增量备份不可用。还原时 datadir 必须为空；--move-back 可节省磁盘但会消耗掉备份。备份用户：BACKUP_ADMIN、PROCESS、RELOAD、LOCK TABLES、REPLICATION CLIENT，以及 performance_schema.log_status、keyring_component_status、replication_group_members 上的 SELECT。","note":"xtrabackup_binlog_info 保存备份的 binlog 文件/位置和 GTID 集合；PITR 从该点开始回放 binlog。要流式传输到另一台主机，请使用 --stream=xbstream | ssh host 'xbstream -x -C /backup'。","since":"XtraBackup 8.0","tags":["xtrabackup","physical backup","incremental","prepare","restore","percona"]},{"id":"pitr-binlog-replay","task":"使用 binlog 进行时间点恢复：找到并跳过错误的事务","category":"备份与 PITR","command":"# 1) Find the bad event's GTID and position (narrow by time range)\nmysqlbinlog --base64-output=DECODE-ROWS -vv --start-datetime='2026-10-08 20:00:00' --stop-datetime='2026-10-08 20:30:00' /var/lib/mysql/binlog.000003 | grep -B12 -iE '^(DROP|TRUNCATE)' | grep -E 'GTID_NEXT|^# at|^#[0-9]{6}|^DROP|^TRUNCATE'\n\n# 2) Restore the backup on a separate server (the dump sets GTID_PURGED)\nzcat /backup/full_2026-10-08_0200.sql.gz | mysql -h restore-db -u root -p\n\n# 3a) Replay all binlogs skipping only the bad transaction\nmysqlbinlog --exclude-gtids='af6bc361-c353-11f1-8f12-f69e63ad6c0e:23' /binlogs/binlog.000003 /binlogs/binlog.000004 | mysql -h restore-db -u root -p\n# 3b) Or stop right before the bad event's GTID event\nmysqlbinlog --stop-position=537205 /binlogs/binlog.000003 | mysql -h restore-db -u root -p","what":"还原备份后，按顺序回放 binlog，将数据库恢复到所需时间点。启用 GTID 时，已包含在备份中的事务会被自动跳过；--exclude-gtids 只去除错误的操作，并保留其后的所有变更。","when":"在错误的 DROP / DELETE / UPDATE 之后，用于在不丢失备份与事故之间所做变更的情况下进行恢复。","caution":"在单独的服务器上还原，而不是在生产环境；验证后只迁移受影响的表。--start/--stop-datetime 按运行 mysqlbinlog 的机器所在时区解释。--stop-position 只适用于给出的最后一个文件；对于多个文件，请用单独的命令完整回放前面的文件。--exclude-gtids 只对独立的操作是安全的；如果之后的事务写入了被删除的表，它们也会失败。所需的 binlog 不得在源上被清除；binlog 保留期至少要长于一个备份间隔。","note":"在一个管道中运行 mysqlbinlog 和 mysql，并在一条命令中包含所有文件；逐个回放文件会破坏临时表以及跨文件的事务。对于非常大的 binlog 集合，另一种方法是将 binlog 作为 relay log 放到还原服务器上，并用（并行）SQL 线程应用。","since":"MySQL 8.0","tags":["pitr","mysqlbinlog","binlog","point in time","exclude-gtids","recovery"]},{"id":"innodb-cluster-status","task":"检查 InnoDB Cluster / Group Replication 健康状况","category":"InnoDB Cluster","command":"# Shell: topology, primary, fault tolerance, lag\nmysqlsh icadmin@db1:3306 -- cluster status --extended=1\nmysqlsh icadmin@db1:3306 -- cluster list-routers\n\n-- SQL: group members and queues from any member\nSELECT m.MEMBER_HOST, m.MEMBER_PORT, m.MEMBER_STATE, m.MEMBER_ROLE, m.MEMBER_VERSION,\n       s.COUNT_TRANSACTIONS_IN_QUEUE AS certify_queue,\n       s.COUNT_TRANSACTIONS_REMOTE_IN_APPLIER_QUEUE AS apply_queue,\n       s.COUNT_CONFLICTS_DETECTED AS conflicts\nFROM performance_schema.replication_group_members m\nJOIN performance_schema.replication_group_member_stats s ON s.MEMBER_ID = m.MEMBER_ID\nORDER BY m.MEMBER_ROLE, m.MEMBER_HOST;","what":"显示集群整体状态（OK / OK_NO_TOLERANCE / NO_QUORUM）、主节点、每个成员的状态以及 certification / applier 队列；队列可揭示哪个成员落后以及 flow control 风险。","when":"每次干预前后；出现告警、写入变慢，或成员显示 RECOVERING / ERROR / UNREACHABLE 时。","caution":"statusText 应显示 'can tolerate up to ONE failure'（3 个成员）。NO_QUORUM 时写入会停止；只有在确定另一个分区已宕机时才使用 forceQuorumUsingPartitionOf，否则会出现脑裂。apply_queue 持续增长的成员会触发 flow control，使主节点上的写入变慢。长时间 RECOVERING 表示正在进行 clone / 增量恢复；出现 ERROR 时请检查错误日志并尝试 cluster.rejoinInstance()。密码中的特殊字符（#、@）在 URI 中必须进行 URL 编码（# → %23）。","note":"MEMBER_COMMUNICATION_STACK 在 8.0.27 中引入；MYSQL 栈通过 3306 端口以常规 TLS 和用户认证传输 GR 流量（XCOM 使用单独的端口，通常为 33061）。在 8.4 中 group_replication_consistency 默认为 BEFORE_ON_PRIMARY_FAILOVER，exit_state_action 默认为 OFFLINE_MODE。","since":"MySQL 8.0","tags":["innodb cluster","group replication","mysqlsh","status","flow control","quorum"]},{"id":"innodb-cluster-switch-primary","task":"InnoDB Cluster 中计划内的主节点切换","category":"InnoDB Cluster","command":"# Shell: planned switchover\nmysqlsh icadmin@db1:3306 -- cluster set-primary-instance db2:3306\n\n-- SQL alternative: target member's server_uuid, 30 s timeout for open transactions\nSELECT MEMBER_HOST, MEMBER_ID, MEMBER_ROLE FROM performance_schema.replication_group_members;\nSELECT group_replication_set_as_primary('3f06e651-c356-11f1-8aac-927a9034ef99', 30);","what":"在 single-primary 模式下有控制地将主节点角色迁移到另一个成员；旧主节点变为 super_read_only，新主节点应用其积压后开放写入。MySQL Router 会自动获知新的主节点。","when":"在维护、操作系统打补丁或升级主节点之前，以及硬件迁移时。","caution":"超时到期时，旧主节点上未结束的事务会被终止；不设置超时时，切换会等待它们，耗时更长。如果应用不通过 Router 连接，必须自行重定向。使用 BEFORE_ON_PRIMARY_FAILOVER 时，写入会等待新主节点清空其积压；请先在集群状态中确认 apply_queue 为空。在 InnoDB Cluster 中优先使用 Shell 命令；它与元数据保持一致。","note":"group_replication_set_as_primary 在 8.0.13 中引入；timeout 参数在 8.0.29 中加入。在计划外故障转移时，GR 会自行选举新的主节点（group_replication_member_weight 设置优先级）。","since":"MySQL 8.0.13","tags":["innodb cluster","switchover","primary","group replication","set_as_primary","maintenance"]},{"id":"innodb-cluster-reboot-outage","task":"在完全中断后恢复集群（rebootClusterFromCompleteOutage）","category":"InnoDB Cluster","command":"# Are all members OFFLINE? (on every node)\nmysql -h db1 -u icadmin -p -e \"SELECT MEMBER_HOST, MEMBER_STATE FROM performance_schema.replication_group_members\"\n# Dry run first: shows the most up-to-date member and the planned steps\nmysqlsh icadmin@db1:3306 --js -e \"dba.rebootClusterFromCompleteOutage('prodCluster', {dryRun: true})\"\nmysqlsh icadmin@db1:3306 --js -e \"dba.rebootClusterFromCompleteOutage('prodCluster')\"\nmysqlsh icadmin@db1:3306 -- cluster status","what":"在断电或所有节点同时重启后、没有 quorum 时重新引导组；选择 GTID 集合最高的成员，并让其他成员重新加入。","when":"当集群状态报告 'Cluster is not reachable' 且所有成员都显示 OFFLINE 时。","caution":"只有在所有成员都为 OFFLINE 时才使用；如果仍有一个成员为 ONLINE，说明组仍然存活，其他成员用 rejoinInstance 加入即可。运行前先启动每个节点：如果某个不可达的成员数据更新，其事务会丢失。force: true 会忽略缺失的成员；这是最后的手段。primary: 'host:port' 选项强制指定某个成员为主节点。","note":"dryRun、primary 和 force 选项在 Shell 8.0.31 中引入；更早的版本通过交互式提示操作。即使 group_replication_start_on_boot=ON，完全中断后组也不会自行形成；必须有意识地从一个地方进行引导。","since":"MySQL Shell 8.0.31","tags":["innodb cluster","outage","reboot","bootstrap","mysqlsh","disaster"]},{"id":"mysql-router-bootstrap","task":"针对 InnoDB Cluster 引导 MySQL Router","category":"Router 与代理","command":"# Generates config from metadata and creates the router account; --user is required when run as root\nmysqlrouter --bootstrap icadmin@db1:3306 --directory /etc/mysqlrouter/prod --account routeruser --account-create if-not-exists --name router01 --conf-bind-address 0.0.0.0 --user mysqlrouter\n/etc/mysqlrouter/prod/start.sh\n# Ports: 6446 RW, 6447 RO, 6450 RW split (classic); 6448 / 6449 X protocol\nmysql -h app-host -P 6446 -u app -p -e \"SELECT @@hostname\"\nmysql -h app-host -P 6447 -u app -p -e \"SELECT @@hostname\"\nmysqlsh icadmin@db1:3306 -- cluster list-routers","what":"将 Router 注册到集群元数据中并生成账户和配置；应用通过 RW 端口访问当前主节点，通过 RO 端口以轮询方式访问从节点。","when":"在 InnoDB Cluster / ClusterSet / ReplicaSet 前面放置透明的故障转移层时；通常每台应用服务器一个 Router。","caution":"Router 是无状态的；在应用旁边运行可避免单点故障，而单个集中式 Router 则不能。引导账户只在引导期间使用；Router 使用自己的账户（routeruser）读取元数据。它通过元数据缓存获知拓扑变化，无需重新引导。RW 分离端口（6450）将事务内的读取发送到主节点，其他读取发送到从节点；对期望 read-your-writes 的应用要小心。Router 版本不得低于集群中的 MySQL 服务器版本。","note":"读写分离端口（6450）在 8.2 中引入。故障转移时现有连接会断开；应用必须具备重连（重试）逻辑。","since":"MySQL Router 8.0","tags":["mysql router","bootstrap","innodb cluster","failover","read write split","6446"]},{"id":"proxysql-rw-split","task":"使用 ProxySQL 实现读写分离和副本健康检查","category":"Router 与代理","command":"-- On MySQL (source): monitor user\nCREATE USER 'proxysql_mon'@'%' IDENTIFIED BY 'M0n#itor2024';\nGRANT USAGE, REPLICATION CLIENT ON *.* TO 'proxysql_mon'@'%';\n\n-- ProxySQL admin: mysql -u admin -p -h 127.0.0.1 -P 6032\nUPDATE global_variables SET variable_value = 'proxysql_mon' WHERE variable_name = 'mysql-monitor_username';\nUPDATE global_variables SET variable_value = 'M0n#itor2024' WHERE variable_name = 'mysql-monitor_password';\nLOAD MYSQL VARIABLES TO RUNTIME; SAVE MYSQL VARIABLES TO DISK;\nINSERT INTO mysql_replication_hostgroups (writer_hostgroup, reader_hostgroup, check_type, comment) VALUES (10, 20, 'read_only', 'shop');\nINSERT INTO mysql_servers (hostgroup_id, hostname, port, max_replication_lag) VALUES (10, 'db1', 3306, 0), (20, 'db2', 3306, 30), (20, 'db3', 3306, 30);\nLOAD MYSQL SERVERS TO RUNTIME; SAVE MYSQL SERVERS TO DISK;\nINSERT INTO mysql_users (username, password, default_hostgroup, transaction_persistent) VALUES ('app', 'App#Pass2024', 10, 1);\nLOAD MYSQL USERS TO RUNTIME; SAVE MYSQL USERS TO DISK;\nINSERT INTO mysql_query_rules (rule_id, active, match_digest, destination_hostgroup, apply) VALUES (100, 1, '^SELECT .* FOR (UPDATE|SHARE)', 10, 1), (200, 1, '^SELECT ', 20, 1);\nLOAD MYSQL QUERY RULES TO RUNTIME; SAVE MYSQL QUERY RULES TO DISK;\n\n-- Verify\nSELECT hostgroup_id, hostname, status FROM runtime_mysql_servers;\nSELECT hostgroup, srv_host, status, ConnUsed, Queries, Latency_us FROM stats_mysql_connection_pool;\nSELECT hostgroup, substr(digest_text, 1, 60) AS digest, count_star, sum_time / 1000 AS sum_ms FROM stats_mysql_query_digest ORDER BY sum_time DESC LIMIT 10;","what":"根据 read_only 标志自动管理 writer（10）和 reader（20）主机组；将 SELECT 路由到副本，将加锁读和写入路由到源；临时移除超过 max_replication_lag 的副本（SHUNNED）。","when":"用于在不修改应用的情况下分散异步复制拓扑中的读负载；用于连接池以及按查询的路由/缓存/防火墙。","caution":"配置分为 3 层（MEMORY → RUNTIME → DISK）：不执行 LOAD ... TO RUNTIME 则什么都不会改变，不执行 SAVE ... TO DISK 则重启后会丢失。read_only 检查要求副本上 super_read_only=ON；否则副本会被移入 writer 主机组。将每个 SELECT 都发送到副本会因延迟导致写后读错误；基于 stats_mysql_query_digest 只为重型/报表查询编写规则更安全。管理接口是 SQLite：不存在 LEFT() 等 MySQL 函数，请使用 substr()。请修改默认的 admin:admin 账户（admin-admin_credentials）。","note":"max_replication_lag 检查要求 monitor 用户拥有 REPLICATION CLIENT。对于 Group Replication / InnoDB Cluster，请使用 mysql_group_replication_hostgroups 表。","since":"ProxySQL 2.0","tags":["proxysql","read write split","hostgroup","query rules","connection pool","replication lag"]},{"id":"online-ddl-instant-inplace","task":"Online DDL：ALGORITHM=INSTANT / INPLACE、LOCK=NONE 和进度跟踪","category":"在线操作","command":"-- Always state ALGORITHM/LOCK: unsupported operations fail immediately instead of silently falling back to COPY\nALTER TABLE shop.orders ADD COLUMN status TINYINT NOT NULL DEFAULT 0, ALGORITHM=INSTANT;\nALTER TABLE shop.orders RENAME COLUMN note TO remark, ALGORITHM=INSTANT;\nALTER TABLE shop.orders ADD INDEX ix_status (status), ALGORITHM=INPLACE, LOCK=NONE;\nALTER TABLE shop.orders MODIFY remark VARCHAR(200), ALGORITHM=INPLACE, LOCK=NONE;\n\n-- INSTANT row version counter (INSTANT is refused at 64, a rebuild resets it)\nSELECT NAME, TOTAL_ROW_VERSIONS FROM information_schema.INNODB_TABLES WHERE NAME = 'shop/orders';\n\n-- Progress of a long ALTER (enable the instruments once)\nUPDATE performance_schema.setup_instruments SET ENABLED = 'YES', TIMED = 'YES' WHERE NAME LIKE 'stage/innodb/alter%';\nUPDATE performance_schema.setup_consumers SET ENABLED = 'YES' WHERE NAME LIKE 'events_stages_%';\nSELECT t.PROCESSLIST_ID, s.EVENT_NAME, s.WORK_COMPLETED, s.WORK_ESTIMATED,\n       ROUND(100 * s.WORK_COMPLETED / NULLIF(s.WORK_ESTIMATED, 0), 1) AS pct_done\nFROM performance_schema.events_stages_current s\nJOIN performance_schema.threads t ON t.THREAD_ID = s.THREAD_ID\nWHERE s.EVENT_NAME LIKE 'stage/innodb/alter%';","what":"INSTANT 只修改数据字典，与表大小无关，立即完成。INPLACE + LOCK=NONE 原地处理表，同时允许并发读写。最后一个查询显示正在运行的 ALTER 处于哪个阶段及其百分比。","when":"在对生产表进行结构变更之前；用于了解哪些操作可以无停机运行，以及估算长时间 ALTER 何时完成。","caution":"INSTANT 和 INPLACE 在开始和结束时仍会短暂获取排他元数据锁；如果有长事务持有该表，ALTER 会等待，其后的每个查询都会排队（参见元数据锁诊断）。在会话中调低 lock_wait_timeout（SET SESSION lock_wait_timeout = 5）。INPLACE 期间到达的 DML 会记录到 innodb_online_alter_log_max_size 为止；超过时 ALTER 会在结束时回滚。列类型变更、字符集转换和 PK 变更需要 COPY 并会阻塞写入；在大表上请使用 gh-ost / pt-online-schema-change。在副本上 ALTER 以单线程运行相同的时长；大型 INPLACE ALTER 会造成副本延迟，INSTANT 不会。","note":"INSTANT ADD COLUMN 在 8.0.12 中引入，仅限最后一列；自 8.0.29 起可在任意位置添加列，并支持 INSTANT DROP COLUMN。当长度字节数不变（不跨越 255 字节边界）时，扩展 VARCHAR 只修改元数据；它通过 INPLACE 而不是 INSTANT 立即完成。带索引的列无法用 INSTANT 删除；请先删除索引。","since":"MySQL 8.0.29","tags":["online ddl","instant","inplace","alter table","lock=none","progress"]},{"id":"pt-online-schema-change","task":"使用 pt-online-schema-change 对大表执行非阻塞 ALTER","category":"在线操作","command":"# First --dry-run, then the same command with --execute; throttles on replica lag\npt-online-schema-change --alter \"ADD COLUMN region CHAR(2) NULL, ADD INDEX ix_region (region)\" \\\n  D=shop,t=orders,h=db1,u=osc,p='Osc#2024',s=1 \\\n  --max-lag=5 --check-replica-lag h=db2,u=osc,p='Osc#2024',s=1 \\\n  --max-load Threads_running=50 --critical-load Threads_running=200 \\\n  --set-vars lock_wait_timeout=5 --noversion-check --dry-run","what":"创建一个新结构的空副本，分块复制行，在复制期间用触发器同步变更，最后用原子 RENAME 交换表。当负载或副本延迟超过阈值时，它会自行减速/暂停。","when":"用于需要 COPY 的 ALTER（列类型、字符集、PK 变更），或会造成长时间副本延迟的 ALTER。","caution":"表需要有 PK 或唯一键。迁移期间触发器会给每次写入增加开销；在写入非常密集的表上，gh-ost 的影响更小。如果子表通过外键引用此表，需要 --alter-foreign-keys-method=auto。对于 caching_sha2_password 账户，请在 DSN 中添加 s=1（TLS）；否则副本连接会因 'Authentication requires secure connection' 失败。--check-replica-lag 的 DSN 不会继承主 DSN 的 user / password；请单独指定。交换时会短暂获取元数据锁；请保持较低的 lock_wait_timeout，无法获取锁时它会重试。","note":"默认通过 processlist 按 IP 发现副本；如果这些 IP 不可达，请配合 --recursion-method=dsn=D=percona,t=dsns 使用 DSN 表。在 3.7 中 --check-slave-lag 更名为 --check-replica-lag（旧名称已弃用）。","since":"Percona Toolkit 3.7","tags":["pt-online-schema-change","pt-osc","percona toolkit","alter","trigger","online schema change"]},{"id":"gh-ost-migration","task":"使用 gh-ost 进行无触发器的在线结构变更","category":"在线操作","command":"# Connects to a replica and reads its binlog; postpones cut-over until the flag file is removed\ngh-ost --host=db2 --port=3306 --user=ghost --password='Gh0st#2024' \\\n  --database=shop --table=orders \\\n  --alter=\"ADD COLUMN channel VARCHAR(20) NULL, ADD INDEX ix_channel (channel)\" \\\n  --max-load=Threads_running=50 --critical-load=Threads_running=200 --max-lag-millis=1500 \\\n  --exact-rowcount --concurrent-rowcount --default-retries=120 \\\n  --postpone-cut-over-flag-file=/tmp/ghost.postpone --panic-flag-file=/tmp/ghost.panic \\\n  --serve-socket-file=/tmp/gh-ost.shop.orders.sock --ssl --ssl-allow-insecure --execute\n# While running: status / throttle / cut-over\necho status | nc -U /tmp/gh-ost.shop.orders.sock\necho throttle | nc -U /tmp/gh-ost.shop.orders.sock\nrm /tmp/ghost.postpone","what":"构建 ghost 表并复制行；通过读取 binlog 而不是触发器来应用并发变更。限流、暂停和切换时机可在运行时通过 socket 或标志文件控制。","when":"用于大型写入密集的表，触发器开销不可接受，且希望自行选择切换时机（例如低流量窗口）时。","caution":"需要 binlog_format=ROW 和 binlog_row_image=FULL；所连接的副本需要 log_replica_updates=ON。用户需要 SUPER 或 REPLICATION CLIENT / REPLICATION SLAVE，以及该表上的 ALL 权限。不支持有外键（父表或子表）的表。切换后旧表保留为 _<table>_del；验证后请手动删除（--ok-to-drop-table 会自动删除）。1.1.8 之前的 gh-ost 版本使用 SHOW SLAVE STATUS，在 MySQL 8.4 上会以错误 1064 停止；8.4 支持在 1.1.8 中加入。gh-ost 会自行创建 --postpone-cut-over-flag-file 中指定的文件；在删除该文件之前不会进行切换。","note":"--test-on-replica 和 --migrate-on-replica 选项可先仅在副本上试运行并验证变更。限流原因和预计完成时间显示在 'status' 命令的输出中。","tags":["gh-ost","online schema change","binlog","cut-over","alter","triggerless"]},{"id":"ps-top-digests","task":"找出开销最大的查询（performance_schema digest）","category":"性能","command":"SELECT SCHEMA_NAME AS db,\n       LEFT(DIGEST_TEXT, 80) AS query,\n       COUNT_STAR AS calls,\n       ROUND(SUM_TIMER_WAIT / 1e12, 2) AS total_sec,\n       ROUND(AVG_TIMER_WAIT / 1e9, 2) AS avg_ms,\n       ROUND(QUANTILE_95 / 1e9, 2) AS p95_ms,\n       ROUND(100 * SUM_TIMER_WAIT / SUM(SUM_TIMER_WAIT) OVER (), 1) AS pct_total,\n       ROUND(SUM_ROWS_EXAMINED / NULLIF(COUNT_STAR, 0)) AS rows_exam_per_call,\n       ROUND(SUM_ROWS_SENT / NULLIF(COUNT_STAR, 0)) AS rows_sent_per_call,\n       SUM_NO_INDEX_USED AS no_index,\n       SUM_CREATED_TMP_DISK_TABLES AS tmp_disk,\n       LAST_SEEN\nFROM performance_schema.events_statements_summary_by_digest\nWHERE SCHEMA_NAME IS NOT NULL\nORDER BY SUM_TIMER_WAIT DESC\nLIMIT 20;\n\n-- Server-wide top wait events\nSELECT events, total, total_latency, avg_latency FROM sys.waits_global_by_latency LIMIT 10;","what":"按总耗时对规范化查询排序，并给出调用次数、平均和 p95 延迟、占总耗时的比例、每次调用的扫描行数/返回行数、未使用索引的执行次数以及溢出到磁盘的临时表。第二个查询显示时间消耗在哪些等待上（IO、锁、redo）。","when":"CPU/IO 负载升高或用户反映“数据库很慢”时的第一站；即使慢查询日志关闭也可使用。","caution":"数值是自上次重启或 TRUNCATE TABLE performance_schema.events_statements_summary_by_digest 以来的累计值；采集两次快照并求差值才能看到当前活动。该表最多 performance_schema_digests_size 行；写满后，新查询会汇总到一行 DIGEST 为 NULL 的记录中。DIGEST_TEXT 截断在 performance_schema_max_digest_length（1024）字节。时间单位为皮秒。rows_exam_per_call / rows_sent_per_call 比值高是缺少索引最明显的信号。","note":"QUANTILE_95 / QUANTILE_99 和 QUERY_SAMPLE_TEXT 列在 8.0 引入。以可读单位呈现的相同数据可在 sys.statement_analysis 和 sys.statements_with_full_table_scans 视图中查看。","since":"MySQL 8.0","tags":["performance_schema","digest","top sql","slow query","sys","wait"]},{"id":"sys-session-activity","task":"当前正在运行什么：活动会话、延迟、锁和事务（sys.session）","category":"性能","command":"SELECT conn_id, user, db, command, state, time,\n       LEFT(current_statement, 100) AS current_statement,\n       statement_latency, progress, lock_latency,\n       rows_examined, full_scan, tmp_disk_tables,\n       trx_state, trx_latency, last_wait\nFROM sys.session\nWHERE command <> 'Sleep' OR trx_state = 'ACTIVE'\nORDER BY time DESC\nLIMIT 25;","what":"用 performance_schema 数据扩充 SHOW PROCESSLIST：在一行中给出正在执行语句的延迟、进度百分比、锁等待时间、扫描行数、全表扫描/磁盘临时表信息以及打开的事务状态。还会列出处于 Sleep 但仍持有打开事务的会话。","when":"故障发生的前 30 秒内：哪个查询已运行多久、谁在等待锁、哪个空闲会话持有打开的事务。","caution":"sys.session 在 performance_schema 上执行多次连接；连接数达到数千时查询本身可能耗时数秒，频繁监控时请使用 x$ 视图并加 LIMIT。progress 仅在启用 stage instrument 且操作会报告进度（如 ALTER）时才有值。处于 Sleep 且 trx_state=ACTIVE 的会话是使用 autocommit=0 的应用/连接池遗忘的事务，这是元数据锁和 purge 问题最常见的原因。","note":"sys.processlist 返回包含后台线程在内的相同数据。终止方式：KILL QUERY conn_id（仅终止语句）或 KILL conn_id（终止连接，事务回滚）。","since":"MySQL 8.0","tags":["sys.session","processlist","active sessions","incident","trx","session"]},{"id":"metadata-lock-diagnose","task":"诊断 'Waiting for table metadata lock' 并找到真正的阻塞者","category":"性能","command":"-- Waiter -> blocker pairs; filter out the ALTER's own SHARED_UPGRADABLE entry\nSELECT object_schema, object_name, waiting_pid, LEFT(waiting_query, 60) AS waiting_query,\n       waiting_lock_type, blocking_pid, blocking_lock_type, sql_kill_blocking_connection\nFROM sys.schema_table_lock_waits\nWHERE blocking_lock_type <> 'SHARED_UPGRADABLE';\n\n-- All MDLs on the table, owner age and open transaction\nSELECT ml.OBJECT_SCHEMA, ml.OBJECT_NAME, ml.LOCK_TYPE, ml.LOCK_DURATION, ml.LOCK_STATUS,\n       t.PROCESSLIST_ID AS conn_id, t.PROCESSLIST_COMMAND AS command, t.PROCESSLIST_TIME AS sec,\n       LEFT(t.PROCESSLIST_INFO, 60) AS query, trx.trx_started\nFROM performance_schema.metadata_locks ml\nJOIN performance_schema.threads t ON t.THREAD_ID = ml.OWNER_THREAD_ID\nLEFT JOIN information_schema.INNODB_TRX trx ON trx.trx_mysql_thread_id = t.PROCESSLIST_ID\nWHERE ml.OBJECT_TYPE = 'TABLE' AND ml.OBJECT_SCHEMA NOT IN ('performance_schema', 'mysql')\nORDER BY ml.OBJECT_NAME, ml.LOCK_STATUS DESC;","what":"显示 ALTER / TRUNCATE / RENAME 等 DDL 正在等待哪个会话的元数据锁，以及排在该 DDL 之后的查询。阻塞者通常是读取过该表却从未关闭事务的 Sleep 会话。","when":"ALTER 启动后，该表上的所有查询都堆积在 'Waiting for table metadata lock' 状态时。","caution":"等待中的 DDL 也会阻塞其后到达的 SELECT；故障期间要快速决断：KILL 阻塞会话或取消 ALTER（KILL QUERY）。阻塞者的 PROCESSLIST_INFO 可能为 NULL（空闲）；使用 performance_schema.events_statements_history 或 sys.session.last_statement 查看其最后一条语句。预防：先执行 SET SESSION lock_wait_timeout = 5 再运行 DDL，使 ALTER 快速放弃，并避免应用中出现长时间打开的事务。","note":"wait/lock/metadata/sql/mdl instrument 在 8.0 中默认启用；在 5.7 中须在 setup_instruments 中启用。sys.schema_table_lock_waits 也会把 ALTER 自身的 SHARED_UPGRADABLE 锁列为阻塞者；真正的元凶是持有 GRANTED 状态 SHARED_READ / SHARED_WRITE 的会话。","since":"MySQL 8.0","tags":["metadata lock","mdl","alter","waiting for table metadata lock","lock","blocking"]},{"id":"innodb-deadlock-analysis","task":"分析 InnoDB 死锁并记录所有死锁","category":"性能","command":"-- Latest deadlock: LATEST DETECTED DEADLOCK section (only the most recent is kept)\nSHOW ENGINE INNODB STATUS\\G\n-- Total deadlock counter\nSELECT NAME, COUNT FROM information_schema.INNODB_METRICS WHERE NAME = 'lock_deadlocks';\n-- Write every deadlock to the error log (logged at Note level, needs verbosity 3)\nSET PERSIST innodb_print_all_deadlocks = ON;\nSET PERSIST log_error_verbosity = 3;","what":"显示死锁中的两个事务、各自持有和等待的锁（索引、lock_mode、记录）以及 InnoDB 回滚了哪一个；计数器跟踪发生频率，错误日志保留历史死锁。","when":"应用收到错误 1213 'Deadlock found when trying to get lock' 或死锁告警增多时。","caution":"LATEST DETECTED DEADLOCK 只保留最后一次事件，重启后丢失。'HOLDS THE LOCK(S)' / 'WAITING FOR THIS LOCK TO BE GRANTED' 行中的索引名和 lock_mode（X、S、gap、insert intention）说明了原因；'WE ROLL BACK TRANSACTION (n)' 显示牺牲者。遇到 1213 时应用必须重试整个事务。根本解决办法：以一致的顺序访问行、保持事务简短、使用合适的索引减少间隙锁，并在合适场景使用 READ COMMITTED。log_error_verbosity=3 也会启用所有 Note 消息；注意错误日志的增长。MySQL Community 中不存在 Innodb_deadlocks 状态变量；计数器请使用 INNODB_METRICS。","note":"在极高并发下，死锁检测本身可能成为 CPU 瓶颈；仅在 innodb_lock_wait_timeout 较低时才考虑 innodb_deadlock_detect=OFF。锁等待超时（1205）不是死锁；使用 blocking-locks 操作指南查找阻塞者。","since":"MySQL 8.0","tags":["deadlock","innodb","1213","innodb status","lock","error log"]},{"id":"long-trx-history-list","task":"长事务与 history list length（purge 滞后）","category":"性能","command":"-- Undo records waiting for purge (millions = trouble)\nSELECT NAME, COUNT AS history_list_length\nFROM information_schema.INNODB_METRICS WHERE NAME = 'trx_rseg_history_len';\n\n-- Oldest transactions, their owner and last SQL\nSELECT trx.trx_id, trx.trx_mysql_thread_id AS conn_id, trx.trx_state,\n       TIMESTAMPDIFF(SECOND, trx.trx_started, NOW()) AS age_sec,\n       trx.trx_rows_modified, trx.trx_rows_locked, trx.trx_isolation_level,\n       p.USER, p.HOST, p.COMMAND, p.TIME AS state_sec,\n       LEFT(COALESCE(trx.trx_query, esc.SQL_TEXT), 80) AS current_or_last_sql\nFROM information_schema.INNODB_TRX trx\nJOIN performance_schema.processlist p ON p.ID = trx.trx_mysql_thread_id\nLEFT JOIN performance_schema.threads th ON th.PROCESSLIST_ID = trx.trx_mysql_thread_id\nLEFT JOIN performance_schema.events_statements_current esc ON esc.THREAD_ID = th.THREAD_ID\nORDER BY trx.trx_started\nLIMIT 10;","what":"显示 purge 落后多少（history list length）以及拖住它的最老事务：所有者、持续时间、修改行数和最后执行的 SQL。","when":"undo 表空间增长、相同查询随时间变慢（扫描旧行版本）或出现磁盘空间告警时。","caution":"最常见的原因是持有打开事务的 Sleep 会话，或在 REPEATABLE READ 下被遗忘的一致性快照（如被丢弃的 mysqldump/报表）。KILL 之后，回滚耗时与该事务修改的行数相当；终止 trx_rows_modified 很高的事务意味着漫长且 IO 密集的回滚，重启也无法缩短。purge 追上后 undo 会自动截断（innodb_undo_log_truncate=ON）。","note":"performance_schema.processlist 在 8.0.22 引入，取代已弃用的 information_schema.PROCESSLIST。SHOW ENGINE INNODB STATUS 中的 'History list length' 给出相同的值。","since":"MySQL 8.0.22","tags":["history list length","purge","undo","long transaction","innodb_trx","idle in transaction"]},{"id":"slow-log-pt-query-digest","task":"短时抓取完整慢查询日志并用 pt-query-digest 分析","category":"性能","command":"-- Temporary full capture (e.g. 10 minutes); only new connections are affected\nSET GLOBAL slow_query_log_file = '/var/lib/mysql/slow.log';\nSET GLOBAL log_slow_extra = ON;\nSET GLOBAL long_query_time = 0;\nSET GLOBAL slow_query_log = ON;\n-- ... then return to the normal threshold\nSET GLOBAL long_query_time = 1;\n\n# Shell: top 10 queries by total time\npt-query-digest --limit=10 --order-by=Query_time:sum --noversion-check /var/lib/mysql/slow.log > /tmp/digest.txt\npt-query-digest --since='2026-10-08 09:00:00' --until='2026-10-08 10:00:00' --filter '$event->{db} && $event->{db} eq \"shop\"' --report-format=profile --noversion-check /var/lib/mysql/slow.log","what":"在短时间窗口内以 long_query_time=0 抓取所有查询；pt-query-digest 按指纹分组，生成按排名排列的报告，包含总耗时/平均/p95 时间、扫描行数和示例查询。","when":"performance_schema digest 不够用时：需要完整语句文本和真实参数的分析、部署前后对比、在其他主机上离线审查。","caution":"long_query_time 在会话启动时从全局值复制；连接池中的现有连接在重连前看不到新阈值。在繁忙的服务器上 long_query_time=0 每秒写入数千条记录；注意磁盘空间和 IO，保持窗口简短并务必还原设置。分析前将文件从生产环境拷出。运行 pt-query-digest 的用户需要对该文件有读权限（模式 640，mysql:mysql）。","note":"log_slow_extra（8.0.14）会写入 Bytes_sent、Read_first / Read_next 和 Created_tmp_disk_tables 等额外字段；pt-query-digest 会在报告中显示它们。永久启用/禁用请参见 enable-general-slow-log 操作指南。","since":"MySQL 8.0.14","tags":["slow log","pt-query-digest","long_query_time","percona toolkit","query analysis","slow query"]},{"id":"explain-analyze-optimizer-trace","task":"用 EXPLAIN ANALYZE 和优化器跟踪调试糟糕的执行计划","category":"性能","command":"-- Plan + actual time and rows (the query actually runs)\nEXPLAIN ANALYZE\nSELECT region, COUNT(*) FROM shop.big WHERE id < 20000 GROUP BY region;\n\n-- Why the optimizer chose this plan: optimizer trace\nSET SESSION optimizer_trace = 'enabled=on', SESSION optimizer_trace_max_mem_size = 1048576;\nSELECT * FROM shop.big WHERE region = 'TR' AND id > 100 LIMIT 5;\nSELECT JSON_PRETTY(JSON_EXTRACT(TRACE, '$**.considered_execution_plans')) AS plans,\n       MISSING_BYTES_BEYOND_MAX_MEM_SIZE\nFROM information_schema.OPTIMIZER_TRACE;\nSET SESSION optimizer_trace = 'enabled=off';","what":"EXPLAIN ANALYZE 针对每个计划步骤并列显示估算值（cost、rows）和实际值（actual time、rows、loops）；优化器跟踪显示考虑过的访问路径、其成本以及每条路径被选中或被拒绝的原因。","when":"查询突然变慢（执行计划变化）、优化器选错索引，或要查找估算行数与实际行数不符的根源时。","caution":"EXPLAIN ANALYZE 会真正执行查询；在生产环境中对重型查询要谨慎。它适用于 SELECT、TABLE 以及多表 UPDATE / DELETE。估算行数与实际行数相差 10 倍以上说明统计信息/直方图有问题（ANALYZE TABLE、直方图）。在嵌套循环的内侧，actual rows 是每次循环的值；总数 = rows × loops。若 MISSING_BYTES_BEYOND_MAX_MEM_SIZE > 0，跟踪被截断；请调大 optimizer_trace_max_mem_size。跟踪只保存同一会话的最后一条语句。","note":"EXPLAIN FORMAT=TREE 在 8.0.16 引入，EXPLAIN ANALYZE 在 8.0.18 引入。要固定执行计划，优化器提示（/*+ INDEX(t ix) */、JOIN_ORDER）比 FORCE INDEX 控制更精细。","since":"MySQL 8.0.18","tags":["explain analyze","optimizer trace","execution plan","query tuning","index","plan"]},{"id":"histogram-statistics","task":"为非索引列创建和管理直方图","category":"索引与统计信息","command":"ANALYZE TABLE shop.big UPDATE HISTOGRAM ON status WITH 64 BUCKETS;\n-- 8.4+: histogram refreshed automatically as data changes\nANALYZE TABLE shop.big UPDATE HISTOGRAM ON status WITH 64 BUCKETS AUTO UPDATE;\n\nSELECT SCHEMA_NAME, TABLE_NAME, COLUMN_NAME,\n       HISTOGRAM->>'$.\"histogram-type\"' AS type,\n       JSON_LENGTH(HISTOGRAM->'$.buckets') AS buckets,\n       HISTOGRAM->>'$.\"last-updated\"' AS last_updated,\n       HISTOGRAM->>'$.\"sampling-rate\"' AS sampling_rate\nFROM information_schema.COLUMN_STATISTICS;\n\nANALYZE TABLE shop.big DROP HISTOGRAM ON status;","what":"在数据字典中存储列的值分布（singleton 或 equi-height）；优化器能正确估算非索引列上过滤条件的选择性，从而改善连接顺序和执行计划质量。","when":"EXPLAIN ANALYZE 在数据倾斜的列（如 status、type）上显示估算与实际行数差距很大，而又不宜添加索引时。","caution":"在索引列上优化器使用 index dive，因此直方图通常不起作用（除非超过 eq_range_index_dive_limit）。在默认的 MANUAL 模式下，直方图不会随数据变化而刷新；请定期调度 ANALYZE ... UPDATE HISTOGRAM。大表按 histogram_generation_max_mem_size 进行采样（sampling-rate < 1）。ANALYZE TABLE 会短暂持有元数据锁；更新直方图不会改变表统计信息（索引基数）。","note":"直方图在 8.0 引入；AUTO UPDATE 选项在 8.4 新增，会随自动统计信息重算一同刷新。最多 1024 个桶；若不同值数量少于桶数，则创建 singleton 直方图。","since":"MySQL 8.0","tags":["histogram","statistics","optimizer","analyze table","selectivity","cardinality"]},{"id":"invisible-redundant-index","task":"查找未使用/冗余索引，设为不可见，再安全删除","category":"索引与统计信息","command":"-- Candidate 1: indexes unused since the last restart\nSELECT * FROM sys.schema_unused_indexes WHERE object_schema = 'shop';\n-- Candidate 2: redundant indexes that are a left prefix of another\nSELECT table_name, redundant_index_name, redundant_index_columns, dominant_index_name, sql_drop_index\nFROM sys.schema_redundant_indexes WHERE table_schema = 'shop';\n\n-- Make it invisible before dropping; revert instantly if anything regresses\nALTER TABLE shop.big ALTER INDEX ix_region INVISIBLE;\nSELECT INDEX_NAME, IS_VISIBLE FROM information_schema.STATISTICS WHERE TABLE_SCHEMA = 'shop' AND TABLE_NAME = 'big' AND SEQ_IN_INDEX = 1;\n-- Try the invisible index for a single query\nEXPLAIN SELECT /*+ SET_VAR(optimizer_switch = 'use_invisible_indexes=on') */ COUNT(*) FROM shop.big WHERE region = 'TR';\nALTER TABLE shop.big ALTER INDEX ix_region VISIBLE;\n-- After a few business cycles (incl. month-end) without issues\nALTER TABLE shop.big DROP INDEX ix_region, ALGORITHM=INPLACE, LOCK=NONE;","what":"列出未使用的索引以及被其他索引覆盖的索引；删除前先将索引设为不可见，可在没有该索引的情况下测试生产行为，必要时立即恢复（无需重建）。","when":"为降低写入成本、buffer pool 压力和磁盘占用而清理索引时。","caution":"schema_unused_indexes 仅依赖自上次重启以来收集的统计；月度报表等低频查询可能不会出现。若各副本承担不同负载，请逐台服务器检查。不可见索引在 DML 时仍会被维护（写入成本仍在），但优化器会忽略它；唯一索引在不可见时仍强制唯一性。PRIMARY KEY 不能设为不可见。在 FORCE INDEX / USE INDEX 中指定该索引的查询在其不可见后会失败。","note":"切换索引可见性只修改元数据，立即完成。冗余列表基于列顺序；索引 (a) 被 (a,b) 覆盖，但 (b) 不被覆盖。","since":"MySQL 8.0","tags":["invisible index","unused index","redundant index","sys","drop index","index cleanup"]},{"id":"buffer-pool-sizing","task":"buffer pool 是否够大：命中率、wait_free 与在线调整大小","category":"内存","command":"SELECT ROUND(@@innodb_buffer_pool_size / 1024 / 1024) AS bp_mb,\n       @@innodb_buffer_pool_chunk_size / 1024 / 1024 AS chunk_mb,\n       @@innodb_buffer_pool_instances AS instances;\nSELECT\n  ROUND(100 * (1 - (SELECT VARIABLE_VALUE FROM performance_schema.global_status WHERE VARIABLE_NAME = 'Innodb_buffer_pool_reads')\n                 / NULLIF((SELECT VARIABLE_VALUE FROM performance_schema.global_status WHERE VARIABLE_NAME = 'Innodb_buffer_pool_read_requests'), 0)), 3) AS hit_pct,\n  (SELECT VARIABLE_VALUE FROM performance_schema.global_status WHERE VARIABLE_NAME = 'Innodb_buffer_pool_pages_free') AS pages_free,\n  (SELECT VARIABLE_VALUE FROM performance_schema.global_status WHERE VARIABLE_NAME = 'Innodb_buffer_pool_pages_dirty') AS pages_dirty,\n  (SELECT VARIABLE_VALUE FROM performance_schema.global_status WHERE VARIABLE_NAME = 'Innodb_buffer_pool_wait_free') AS wait_free;\n\n-- Online resize and status tracking\nSET PERSIST innodb_buffer_pool_size = 8589934592;\nSELECT VARIABLE_NAME, VARIABLE_VALUE FROM performance_schema.global_status WHERE VARIABLE_NAME LIKE 'Innodb_buffer_pool_resize_status%';","what":"显示 buffer pool 大小、缓存命中率、空闲页/脏页数量以及空闲页等待（wait_free）计数器；无需重启即可调整其大小并跟踪进度。","when":"读 IO 很高、增减内存后或做容量规划时。","caution":"单看命中率会产生误导；即使 99% 也可能意味着每秒数千次磁盘读。应关注 Innodb_buffer_pool_reads 每秒的增量；wait_free > 0 表示 buffer pool 太小或 page cleaner 跟不上。新大小会向上取整为 innodb_buffer_pool_chunk_size × 实例数的倍数。调整大小期间可能出现短暂停顿，缩小时更明显；避开高峰时段。在专用服务器上从内存的 60–75% 起步；为每连接内存、操作系统缓存和其他进程留出空间，因为发生 swap 总比 buffer pool 小一些更糟。","note":"Innodb_buffer_pool_resize_status_code / _progress 在 8.0.31 引入。innodb_dedicated_server=ON 会根据内存自动设置 buffer pool 大小和 redo 容量。sys.innodb_buffer_stats_by_table 显示每张表的 buffer pool 使用情况；它会扫描 INNODB_BUFFER_PAGE，在大 buffer pool 上开销很大。","since":"MySQL 8.0","tags":["buffer pool","innodb","memory","hit ratio","resize","sizing"]},{"id":"partition-maintenance","task":"RANGE 分区维护：添加未来分区，归档/删除旧分区","category":"分区","command":"-- Current partitions\nSELECT PARTITION_NAME, PARTITION_DESCRIPTION, TABLE_ROWS\nFROM information_schema.PARTITIONS\nWHERE TABLE_SCHEMA = 'shop' AND TABLE_NAME = 'events_log' ORDER BY PARTITION_ORDINAL_POSITION;\n\n-- Add a new month by splitting the empty pmax\nALTER TABLE shop.events_log REORGANIZE PARTITION pmax INTO (\n  PARTITION p2026_11 VALUES LESS THAN ('2026-12-01'),\n  PARTITION pmax VALUES LESS THAN (MAXVALUE));\n\n-- Move the old month into an archive table (instant), then drop the empty partition\nCREATE TABLE shop.events_log_2026_08 LIKE shop.events_log;\nALTER TABLE shop.events_log_2026_08 REMOVE PARTITIONING;\nALTER TABLE shop.events_log EXCHANGE PARTITION p2026_08 WITH TABLE shop.events_log_2026_08;\nALTER TABLE shop.events_log DROP PARTITION p2026_08;\n\n-- Pruning check: the partitions column should list a single partition\nEXPLAIN SELECT * FROM shop.events_log WHERE created >= '2026-10-01' AND created < '2026-11-01';","what":"在按日期进行 RANGE 分区的表上，添加下一周期的分区，通过 EXCHANGE 在毫秒级内将旧周期移到单独的表中，并以远低于 DELETE 的代价删除分区。","when":"用于日志、事件、审计记录等随时间增长的表的按月保留和归档。","caution":"分区列必须包含在每个唯一键中（包括 PK）。REORGANIZE PARTITION pmax 会重写 pmax 中的数据；保持 pmax 为空并提前创建分区（如 3 个月）。DROP PARTITION 不可撤销，且不按行记录日志；在副本上以 DDL 形式执行。EXCHANGE 的目标表必须结构相同且未分区；超出范围的行会报错（WITHOUT VALIDATION 更快，但可能放入错误数据）。分区 DDL 会持有表级元数据锁。要实现分区裁剪，在 WHERE 中使用分区列时不要用函数包裹。","note":"在 8.0 中只有 InnoDB（和 NDB）支持分区。添加未来分区/删除旧分区通常用 Event Scheduler 或 cron 自动化；ALTER TABLE ... TRUNCATE PARTITION 删除数据但保留分区。","since":"MySQL 8.0","tags":["partition","range","reorganize","exchange partition","retention","archive"]},{"id":"roles-least-privilege","task":"用角色构建最小权限模型（CREATE ROLE、DEFAULT ROLE）","category":"安全","command":"CREATE ROLE IF NOT EXISTS 'app_read', 'app_write';\nGRANT SELECT ON shop.* TO 'app_read';\nGRANT INSERT, UPDATE, DELETE ON shop.* TO 'app_write';\n\n-- Application account: network range, TLS required, roles active by default\nCREATE USER 'svc_app'@'10.0.%' IDENTIFIED BY 'S3cure#App2024' DEFAULT ROLE 'app_read', 'app_write' REQUIRE SSL;\nCREATE USER 'ana'@'%' IDENTIFIED BY 'An4#Report2024';\nGRANT 'app_read' TO 'ana'@'%';\nSET DEFAULT ROLE ALL TO 'ana'@'%';\n\n-- Show grants with roles expanded\nSHOW GRANTS FOR 'svc_app'@'10.0.%' USING 'app_read', 'app_write';\nSELECT FROM_USER, FROM_HOST, TO_USER, TO_HOST FROM mysql.role_edges;","what":"把权限授予角色而不是单个用户；账户通过角色成员关系获得权限。变更集中在一处，审计和离职回收更容易。","when":"创建新的应用/团队账户，以及清理权限混乱的遗留服务器时。","caution":"不是默认角色的角色在会话中不会激活（CURRENT_ROLE() 为 NONE），用户看起来缺少权限；请使用 SET DEFAULT ROLE ALL 或 activate_all_roles_on_login=ON。角色也是一个账户（已锁定、无密码），会出现在 mysql.user 中。应用账户使用网段而非 '%'。切勿向应用账户授予 ALL、SUPER、FILE、PROCESS 或 CONNECTION_ADMIN。从角色撤销权限也会影响活动会话。","note":"mandatory_roles 会自动为每个账户分配角色。CREATE USER ... DEFAULT ROLE 同时授予角色并将其设为默认角色。","since":"MySQL 8.0","tags":["role","grant","least privilege","default role","security","privilege"]},{"id":"password-policy-lockout","task":"密码策略、登录失败锁定与零停机密码轮换","category":"安全","command":"-- Password strength policy (component)\nINSTALL COMPONENT 'file://component_validate_password';\nSET PERSIST validate_password.policy = 'MEDIUM';\nSET PERSIST validate_password.length = 12;\nSELECT VALIDATE_PASSWORD_STRENGTH('Summer2024') AS score;\n\n-- Failed-login lockout, password lifetime, history\nALTER USER 'ana'@'%' FAILED_LOGIN_ATTEMPTS 5 PASSWORD_LOCK_TIME 1\n  PASSWORD EXPIRE INTERVAL 90 DAY PASSWORD HISTORY 5 PASSWORD REUSE INTERVAL 365 DAY;\n\n-- Zero-downtime rotation: dual password\nALTER USER 'svc_app'@'10.0.%' IDENTIFIED BY 'N3w#AppSecret2025' RETAIN CURRENT PASSWORD;\n-- Once every application uses the new password\nALTER USER 'svc_app'@'10.0.%' DISCARD OLD PASSWORD;","what":"启用拒绝弱密码的策略，在连续登录失败后临时锁定账户，设置密码有效期和重用规则；借助双密码，可在逐个更新应用的同时无停机地更换服务账户密码。","when":"用于安全审计/合规（PCI、ISO 27001）和服务账户密码轮换。","caution":"策略只作用于新设置/修改的密码；不会检测已有的弱密码。在服务账户上，FAILED_LOGIN_ATTEMPTS 可能让单个配置错误的客户端锁定账户并造成故障；请用于人工账户。PASSWORD_LOCK_TIME 单位为天（UNBOUNDED：直到手动解锁）；使用 ALTER USER ... ACCOUNT UNLOCK 解锁。PASSWORD EXPIRE 会在服务账户上造成意外故障；那里优先使用 PASSWORD EXPIRE NEVER 加双密码轮换。","note":"双密码在 8.0.14 引入，FAILED_LOGIN_ATTEMPTS / PASSWORD_LOCK_TIME 在 8.0.19 引入。5.7 的 validate_password 插件在 8.0 中被组件取代；变量名使用点号（validate_password.policy）。","since":"MySQL 8.0.19","tags":["password policy","validate_password","failed_login_attempts","dual password","security","password"]},{"id":"require-tls-transport","task":"强制 TLS、查找非 TLS 连接、无需重启更新证书","category":"安全","command":"SELECT @@require_secure_transport, @@tls_version, @@ssl_ca, @@ssl_cert;\nSELECT * FROM performance_schema.tls_channel_status\nWHERE PROPERTY IN ('Enabled', 'Current_tls_version', 'Current_tls_cert', 'Ssl_server_not_after');\n\n-- Active connections and their TLS versions (empty = no TLS)\nSELECT t.PROCESSLIST_USER AS user, t.PROCESSLIST_HOST AS host, t.CONNECTION_TYPE, sv.VARIABLE_VALUE AS tls_version\nFROM performance_schema.threads t\nLEFT JOIN performance_schema.status_by_thread sv ON sv.THREAD_ID = t.THREAD_ID AND sv.VARIABLE_NAME = 'Ssl_version'\nWHERE t.TYPE = 'FOREGROUND' AND t.PROCESSLIST_USER IS NOT NULL;\n\nSET PERSIST require_secure_transport = ON;\n-- After copying the new certificate files\nALTER INSTANCE RELOAD TLS;","what":"显示服务器 TLS 配置、证书到期日期以及每个连接是否使用 TLS；完全禁止未加密的 TCP 连接，并在不停止服务器的情况下更新证书。","when":"用于安全审计/合规、证书到期之前，以及追查未加密连接的遗留客户端时。","caution":"require_secure_transport=ON 不影响 Unix socket 和共享内存连接；这些被视为安全连接。启用前先找出不使用 TLS 连接的客户端，否则它们会以错误 3159 失败。ALTER INSTANCE RELOAD TLS 不影响现有连接，只影响新连接；若证书文件无效会报错并保留旧证书。自动生成的自签名证书无法被使用 VERIFY_CA / VERIFY_IDENTITY 的客户端验证；生产环境请使用由你的 CA 签发的证书。","note":"ALTER INSTANCE RELOAD TLS 在 8.0.16 引入，performance_schema.tls_channel_status 在 8.0.21 引入。按账户强制请使用 ALTER USER ... REQUIRE SSL（或 REQUIRE X509）。","since":"MySQL 8.0.21","tags":["tls","ssl","require_secure_transport","certificate","encryption","cert"]},{"id":"risky-accounts-audit","task":"审计高风险账户：空密码、'%' 主机、SUPER、mysql_native_password","category":"安全","command":"SELECT user, host, plugin,\n       CONCAT_WS(', ',\n         IF(authentication_string = '' AND account_locked = 'N', 'EMPTY PASSWORD', NULL),\n         IF(host = '%', 'ANY HOST', NULL),\n         IF(plugin = 'mysql_native_password', 'NATIVE AUTH', NULL),\n         IF(Super_priv = 'Y', 'SUPER', NULL),\n         IF(Grant_priv = 'Y', 'GRANT OPTION', NULL),\n         IF(File_priv = 'Y', 'FILE', NULL),\n         IF(ssl_type = '', 'NO TLS REQUIRED', NULL),\n         IF(password_expired = 'Y', 'EXPIRED', NULL),\n         IF(account_locked = 'Y', 'LOCKED', NULL)) AS findings,\n       password_last_changed\nFROM mysql.user\nWHERE user NOT IN ('mysql.sys', 'mysql.session', 'mysql.infoschema')\nORDER BY (Super_priv = 'Y') DESC, user;\n\n-- Accounts holding powerful dynamic privileges\nSELECT GRANTEE, PRIVILEGE_TYPE FROM information_schema.USER_PRIVILEGES\nWHERE PRIVILEGE_TYPE IN ('SUPER', 'SYSTEM_VARIABLES_ADMIN', 'CONNECTION_ADMIN', 'BINLOG_ADMIN', 'REPLICATION_SLAVE_ADMIN')\nORDER BY GRANTEE;","what":"按账户在一行中列出安全发现：无密码、可从任意位置连接、遗留认证插件、SUPER / GRANT / FILE 权限以及未要求 TLS；第二个查询显示危险的动态权限。","when":"定期安全审计、新接手服务器时以及升级到 8.4 之前。","caution":"该查询直接读取 mysql.user，需要对其有 SELECT 权限。角色账户已锁定且无密码，因此只对未锁定账户报告 EMPTY PASSWORD。Super_priv 是静态 SUPER 权限；由于 8.0 将其拆分为动态权限，请同时检查第二个查询。标记为 NATIVE AUTH 的账户须在 8.4 之前迁移到 caching_sha2_password：ALTER USER 'u'@'h' IDENTIFIED WITH caching_sha2_password BY '...'；旧客户端/连接器可能不支持 caching_sha2_password，请先测试。","note":"mysql_native_password 在 8.0.34 被弃用，在 8.4 中默认禁用（可通过 mysql_native_password=ON 启用），并在 9.0 中移除。","since":"MySQL 8.0","tags":["security audit","mysql.user","mysql_native_password","super","privileges","audit"]},{"id":"upgrade-checker-84","task":"8.0 → 8.4 LTS 升级预检查（util.checkForServerUpgrade）","category":"升级","command":"# State the target version explicitly; the Shell must be the same version as the target or newer\nmysqlsh root@db1:3306 -- util check-for-server-upgrade --target-version=8.4.10 --output-format=TEXT\n# JSON output for automation, including a my.cnf check\nmysqlsh root@db1:3306 -- util check-for-server-upgrade --target-version=8.4.10 --output-format=JSON --config-path=/etc/my.cnf > /tmp/upgrade_check.json","what":"对照目标版本检查当前服务器：已移除的变量和权限、默认值改变的参数、遗留认证方式、无效的列定义、外键和分区问题。Error 级别的发现会阻止升级。","when":"每次大版本/LTS 升级前；修复发现的问题后重新运行。","caution":"也要查看警告：在 8.4 中 innodb_io_capacity（10000）、innodb_log_buffer_size（64 MB）、innodb_adaptive_hash_index（OFF）、innodb_change_buffering（none）、innodb_buffer_pool_instances 和 group_replication_consistency 等默认值发生变化，可能影响性能；在 my.cnf 中固定你想保持当前行为的值。SET_USER_ID 等已移除的权限会在升级时被删除。先升级一个副本，在负载下观察，然后再切换；不支持从 8.4 二进制降级到 8.0，因此回退手段是逻辑备份或保留在旧版本上的副本。","note":"8.4 中已移除：START / STOP SLAVE、SHOW SLAVE STATUS 和 CHANGE MASTER TO 等旧复制语法，default_authentication_plugin（由 authentication_policy 取代），binlog_transaction_dependency_tracking 和 mysqlpump。mysql_native_password 默认禁用。","since":"MySQL Shell 8.0","tags":["upgrade","8.4","checkforserverupgrade","mysqlsh","lts","version upgrade"]},{"id":"charset-utf8mb4-convert","task":"将 latin1 / utf8mb3 表转换为 utf8mb4","category":"维护","command":"-- Columns that are not utf8mb4\nSELECT TABLE_SCHEMA, TABLE_NAME, COLUMN_NAME, CHARACTER_SET_NAME, COLLATION_NAME, COLUMN_TYPE\nFROM information_schema.COLUMNS\nWHERE CHARACTER_SET_NAME IS NOT NULL AND CHARACTER_SET_NAME <> 'utf8mb4'\n  AND TABLE_SCHEMA NOT IN ('mysql', 'sys', 'information_schema', 'performance_schema')\nORDER BY TABLE_SCHEMA, TABLE_NAME;\n\n-- Generate conversion DDL for tables whose default charset is not utf8mb4\nSELECT CONCAT('ALTER TABLE `', t.TABLE_SCHEMA, '`.`', t.TABLE_NAME,\n              '` CONVERT TO CHARACTER SET utf8mb4 COLLATE utf8mb4_0900_ai_ci, ALGORITHM=COPY, LOCK=SHARED;') AS ddl\nFROM information_schema.TABLES t\nJOIN information_schema.COLLATIONS c ON c.COLLATION_NAME = t.TABLE_COLLATION\nWHERE c.CHARACTER_SET_NAME <> 'utf8mb4' AND t.TABLE_TYPE = 'BASE TABLE'\n  AND t.TABLE_SCHEMA NOT IN ('mysql', 'sys', 'information_schema', 'performance_schema');\n\n-- Schema default and a single table\nALTER DATABASE legacy CHARACTER SET utf8mb4 COLLATE utf8mb4_0900_ai_ci;\nALTER TABLE legacy.cust CONVERT TO CHARACTER SET utf8mb4 COLLATE utf8mb4_0900_ai_ci, ALGORITHM=COPY, LOCK=SHARED;","what":"找出非 utf8mb4 的列和表并生成转换 DDL；CONVERT TO 同时修改表默认字符集和所有字符列，从而支持 emoji/4 字节字符。","when":"用于从 5.x 迁移过来的 schema、出现 'Incorrect string value'（1366）错误，以及不同排序规则的列之间的连接无法使用索引时。","caution":"CONVERT TO 以 COPY 方式重写表，期间阻塞写入（不支持 INPLACE）；大表请通过 pt-online-schema-change / gh-ost 执行相同的 --alter。utf8mb4 每字符占 4 字节：带索引的 VARCHAR(255) 变为 1020 字节；DYNAMIC / COMPRESSED 行格式的上限为 3072 字节，COMPACT / REDUNDANT 为 767 字节，超出会得到错误 1071。必要时 VARCHAR / TEXT 列会提升为更大的类型（TEXT → MEDIUMTEXT），以保持相同的字符容量。排序规则变化会影响排序和比较：utf8mb4_0900_ai_ci 不区分重音和大小写，原本不同的值可能在唯一索引上冲突（1062）。相互连接的表要一起转换。客户端连接也必须使用 utf8mb4。","note":"utf8（utf8mb3）在 8.0 中已弃用；自 8.0.30 起在 SHOW CREATE TABLE 中显示为 utf8mb3。土耳其语排序规则（带点和不带点的 I）请使用 utf8mb4_tr_0900_ai_ci。8.0 的默认值为 utf8mb4 / utf8mb4_0900_ai_ci。","since":"MySQL 8.0","tags":["utf8mb4","character set","collation","convert","emoji","charset"]}]},"recipes/mssql.json":{"engineId":"mssql","engine":"SQL Server","order":4,"recipes":[{"id":"backup-log","task":"执行事务日志备份","category":"备份与日志","command":"BACKUP LOG [Customer]\nTO DISK = N'D:\\Backup\\Customer_log_20260706_1200.trn'\nWITH COMPRESSION, CHECKSUM, STATS = 10;","what":"执行事务日志备份，并（在 FULL/BULK_LOGGED 恢复模式下）截断日志中已使用的部分以便重用。","when":"在 FULL 恢复模式下定期执行（如每 15 分钟），用于时间点恢复链，以及日志文件增长时。","caution":"仅在 FULL/BULK_LOGGED 恢复模式下有意义（SIMPLE 模式下日志自动截断）。必须先有至少一个 FULL 备份，日志链才不会断。为备份文件设置正确的删除/覆盖策略。","tags":["backup log","log backup","transaction log","trn","recovery","backup"]},{"id":"backup-full-diff","task":"执行 FULL 和 DIFFERENTIAL 备份","category":"备份与日志","command":"-- Full backup:\nBACKUP DATABASE [Customer]\nTO DISK = N'D:\\Backup\\Customer_full_20260706.bak'\nWITH COMPRESSION, CHECKSUM, INIT, STATS = 10;\n\n-- Differential backup:\nBACKUP DATABASE [Customer]\nTO DISK = N'D:\\Backup\\Customer_diff_20260706.bak'\nWITH DIFFERENTIAL, COMPRESSION, CHECKSUM, INIT, STATS = 10;","what":"FULL 备份整个数据库；DIFFERENTIAL 备份自上次 FULL 以来更改过的区。","when":"FULL：每日/每周的基础备份。DIFF：FULL 之间的快速中间备份。","caution":"还原 DIFF 需要其基础 FULL 备份。INIT 会覆盖现有文件；文件名写错会丢失一份备份。CHECKSUM 能及早发现损坏。","tags":["backup","full","differential","differential backup","bak","backup"]},{"id":"log-reuse-wait","task":"日志文件为何增长/为何无法截断","category":"备份与日志","command":"SELECT name,\n       recovery_model_desc,\n       log_reuse_wait_desc\nFROM   sys.databases\nWHERE  name = N'Customer';\n\n-- Log space usage for all databases:\nSELECT DB_NAME(database_id) AS db, name, type_desc,\n       size*8/1024        AS size_mb,\n       CAST(FILEPROPERTY(name,'SpaceUsed') AS BIGINT)*8/1024 AS used_mb\nFROM   sys.master_files\nWHERE  type_desc = 'LOG';","what":"log_reuse_wait_desc 列说明事务日志无法截断的原因（如 LOG_BACKUP、ACTIVE_TRANSACTION、REPLICATION、AVAILABILITY_REPLICA）。","when":"日志文件持续增长或日志已满（错误 9002）时查找根本原因。","caution":"若显示 LOG_BACKUP，解决办法是执行日志备份，而不是收缩文件。仅查询；无风险。","tags":["log_reuse_wait_desc","log growth","sys.databases","log backup","recovery","diagnostics"]},{"id":"shrink-log","task":"收缩日志文件","category":"备份与日志","command":"-- First take a log backup so the log can be truncated (FULL model):\nBACKUP LOG [Customer] TO DISK = N'D:\\Backup\\Customer_log_pre_shrink.trn' WITH COMPRESSION;\n\n-- Find the logical name of the log file:\nSELECT name, type_desc FROM sys.database_files WHERE type_desc = 'LOG';\n\n-- Shrink (target 2048 MB):\nUSE [Customer];\nDBCC SHRINKFILE (N'Customer_log', 2048);","what":"减小事务日志文件的物理大小，并将空间归还给操作系统。","when":"日志在一次性异常增长（大事务/索引重建）后一直保持膨胀时。","caution":"危险/注意：例行收缩是不良做法；日志再次增长期间性能下降，且 VLF 碎片增加。先执行日志备份，否则大小不会下降。只收缩不必要增长的日志文件，而不是数据文件。之后一次性将其增长到合适的大小。","tags":["shrink","dbcc shrinkfile","shrink log","log","vlf","caution"]},{"id":"vlf-count","task":"查看 VLF（虚拟日志文件）数量","category":"备份与日志","command":"-- SQL Server 2016 SP2 / 2017 and later:\nSELECT DB_NAME(database_id) AS db,\n       COUNT(*)             AS vlf_count,\n       SUM(CASE WHEN vlf_active = 1 THEN 1 ELSE 0 END) AS active_vlf\nFROM   sys.dm_db_log_info(DB_ID(N'Customer'))\nGROUP BY database_id;\n\n-- For older versions: DBCC LOGINFO('Customer'); (row count = VLF count)","what":"返回事务日志中虚拟日志文件（VLF）的数量。VLF 过多会降低启动/恢复/日志备份性能。","when":"日志缓慢或数据库启动耗时较长时；检查日志增长设置时。","caution":"数百/数千个 VLF 是警告信号；解决办法是收缩日志，再以适当的大增量（如每次 8GB）重新增长。仅查询；无风险。","since":"SQL Server 2016 SP2","note":"sys.dm_db_log_info DMV 随 SQL Server 2016 SP2 和 2017 引入；此前通过未公开的 DBCC LOGINFO 输出的行数统计 VLF。","tags":["vlf","dm_db_log_info","dbcc loginfo","log","virtual log file","performance"]},{"id":"open-transaction","task":"查找最老的打开事务（ACTIVE_TRANSACTION）","category":"备份与日志","command":"USE [Customer];\nDBCC OPENTRAN;\n\n-- Detailed: show long-running open transactions and their owners:\nSELECT s.session_id, s.login_name, s.host_name, s.program_name,\n       t.transaction_id,\n       DATEDIFF(SECOND, t.transaction_begin_time, GETDATE()) AS duration_sec,\n       t.transaction_begin_time\nFROM   sys.dm_tran_active_transactions t\n  JOIN sys.dm_tran_session_transactions st ON st.transaction_id = t.transaction_id\n  JOIN sys.dm_exec_sessions s ON s.session_id = st.session_id\nORDER BY t.transaction_begin_time;","what":"显示最老的打开（未提交/未回滚）事务及持有它的会话。打开的事务会阻止日志截断（LOG_REUSE_WAIT = ACTIVE_TRANSACTION）。","when":"执行了日志备份但日志仍不缩小且 log_reuse_wait_desc = ACTIVE_TRANSACTION 时查找根本原因。","caution":"KILL 持有长时间打开事务的会话前，请与业务负责人确认；回滚可能耗时很长。仅查询；无风险。","tags":["dbcc opentran","open transaction","active_transaction","dm_tran_active_transactions","log","diagnostics"]},{"id":"add-data-file","task":"向数据库添加数据文件","category":"存储","command":"ALTER DATABASE [Customer]\nADD FILE (\n  NAME = N'Customer_data2',\n  FILENAME = N'E:\\Data\\Customer_data2.ndf',\n  SIZE = 4GB,\n  FILEGROWTH = 512MB,\n  MAXSIZE = 100GB\n) TO FILEGROUP [PRIMARY];","what":"向现有文件组添加新的数据文件（.ndf），以增加容量并分散 I/O。","when":"磁盘已满，或希望将 I/O 分散到多块磁盘时。","caution":"写入新文件时按比例填充进行均衡；最空的文件最先被填充。FILEGROWTH 应指定为固定 MB，而不是百分比。设置 MAXSIZE 可防止失控增长。","tags":["add file","add data file","ndf","alter database","filegroup","storage"]},{"id":"add-filegroup","task":"添加文件组","category":"存储","command":"-- 1) Create the filegroup:\nALTER DATABASE [Customer] ADD FILEGROUP [FG_Archive];\n\n-- 2) Add a file to it:\nALTER DATABASE [Customer]\nADD FILE (\n  NAME = N'Customer_archive1',\n  FILENAME = N'F:\\Data\\Customer_archive1.ndf',\n  SIZE = 8GB, FILEGROWTH = 1GB\n) TO FILEGROUP [FG_Archive];\n\n-- (Optional) make it the default filegroup:\n-- ALTER DATABASE [Customer] MODIFY FILEGROUP [FG_Archive] DEFAULT;","what":"创建一个包含文件的新文件组；可将表/索引放到单独的磁盘/文件组上。","when":"用于分离归档/冷数据、文件组级别的备份/还原以及 I/O 分离。","caution":"空文件组无法备份/使用；至少向其中添加一个文件。修改默认文件组会影响新对象的存放位置。","tags":["filegroup","filegroup","add filegroup","alter database","ndf","storage"]},{"id":"modify-file-growth","task":"更改文件大小/自动增长","category":"存储","command":"-- Grow (SIZE only grows; shrinking requires shrink, not a resize):\nALTER DATABASE [Customer]\nMODIFY FILE (NAME = N'Customer_data', SIZE = 20GB);\n\n-- Set autogrowth (fixed MB recommended):\nALTER DATABASE [Customer]\nMODIFY FILE (NAME = N'Customer_data', FILEGROWTH = 1GB, MAXSIZE = 200GB);\n\n-- Disable autogrowth:\nALTER DATABASE [Customer]\nMODIFY FILE (NAME = N'Customer_log', FILEGROWTH = 0);","what":"预先增长数据/日志文件，或设置其自动增长增量/上限。","when":"用于修正失控的小增长增量、预分配空间或设置 MAXSIZE。","caution":"基于百分比的自动增长在大文件上会产生非常大的增量；请使用固定 MB。未启用即时文件初始化时，数据文件增长会造成阻塞。SIZE 不能缩小文件（需要收缩）。","tags":["modify file","autogrowth","filegrowth","size","alter database","storage"]},{"id":"move-file","task":"将数据/日志文件移动到其他位置","category":"存储","command":"-- 1) Tell SQL Server the new path (nothing is moved yet):\nALTER DATABASE [Customer]\nMODIFY FILE (NAME = N'Customer_data',\n             FILENAME = N'G:\\Data\\Customer_data.mdf');\n\n-- 2) Take the database offline:\nALTER DATABASE [Customer] SET OFFLINE WITH ROLLBACK IMMEDIATE;\n\n-- 3) Physically copy/move the file to the new path AT THE OPERATING SYSTEM level (G:\\Data\\...).\n\n-- 4) Bring it online:\nALTER DATABASE [Customer] SET ONLINE;","what":"更改数据库文件的物理位置：在元数据中定义新路径，在数据库脱机时物理移动文件，然后使数据库联机。","when":"更换磁盘或迁移到其他驱动器以获得空间时。","caution":"OFFLINE + ROLLBACK IMMEDIATE 会断开所有活动连接；请规划停机时间。在操作系统中把文件实际复制到新路径之前，不要让数据库 ONLINE，否则数据库会变为 SUSPECT。tempdb 需要不同的方法（MODIFY FILE + 重启 SQL Server 服务）。","tags":["move file","move file","modify file","offline","online","mdf","ldf","storage"]},{"id":"add-tempdb-file","task":"向 tempdb 添加数据文件","category":"存储","command":"-- Show current tempdb files:\nSELECT name, physical_name, size*8/1024 AS size_mb, growth\nFROM   sys.master_files WHERE database_id = DB_ID(N'tempdb');\n\n-- Add a new file (up to the number of CPU cores, equally sized):\nALTER DATABASE [tempdb]\nADD FILE (\n  NAME = N'tempdev2',\n  FILENAME = N'T:\\tempdb\\tempdb_mssql_2.ndf',\n  SIZE = 8GB, FILEGROWTH = 1GB\n);","what":"向 tempdb 添加额外的数据文件，以减少分配（PFS/GAM/SGAM）争用并分散 I/O。","when":"用于存在 tempdb 争用（PAGELATCH_UP 等待）或 tempdb 使用繁重的系统。","caution":"所有 tempdb 数据文件必须具有相同的大小和增长增量（以实现按比例填充）。新增文件无需重启即可立即使用；若现有文件大小不一，请先将它们全部调为一致，否则新的（空）文件会承担不成比例的负载。文件数通常与核心数匹配（最多 8 个）；SQL Server 2016+ 安装程序默认如此配置。","tags":["tempdb","add file","tempdb file","ndf","contention","pagelatch","storage"]},{"id":"index-rebuild-reorg","task":"索引重建/重组","category":"维护","command":"-- Rebuild (high fragmentation > 30%). ONLINE requires Enterprise:\nALTER INDEX IX_Customer_Email ON dbo.Customer\n  REBUILD WITH (ONLINE = ON, MAXDOP = 4, SORT_IN_TEMPDB = ON);\n\n-- All indexes of a table:\nALTER INDEX ALL ON dbo.Customer REBUILD WITH (FILLFACTOR = 90);\n\n-- Reorganize (moderate fragmentation 5-30%, always online, can be stopped):\nALTER INDEX IX_Customer_Email ON dbo.Customer REORGANIZE;","what":"REBUILD 从头重新创建索引（彻底消除碎片，刷新统计信息）；REORGANIZE 就地压缩叶级（轻量、不阻塞）。","when":"碎片率 5-30% 时用 REORGANIZE，超过 30% 时用 REBUILD。大表在维护窗口中进行。","caution":"ONLINE = ON 仅在 Enterprise/Developer 中可用；Standard 中使用 OFF（表会被锁定）。REBUILD 会生成大量日志并需要空间。MAXDOP 和 SORT_IN_TEMPDB 影响资源使用。","tags":["index","rebuild","reorganize","alter index","fragmentation","maintenance","online"]},{"id":"update-statistics","task":"更新统计信息","category":"维护","command":"-- Single table, full scan:\nUPDATE STATISTICS dbo.Customer WITH FULLSCAN;\n\n-- Specific statistic, with sampling:\nUPDATE STATISTICS dbo.Customer IX_Customer_Email WITH SAMPLE 30 PERCENT;\n\n-- Bulk for the whole database (user tables), sampled:\nEXEC sys.sp_updatestats;","what":"更新优化器使用的列/索引统计信息；确保执行计划和基数估算正确。","when":"大量数据变更/加载后、出现执行计划回归时；索引重建已经完整刷新统计信息（无需单独更新）。","caution":"FULLSCAN 准确但开销大；在大表上会产生 I/O 负载。sp_updatestats 只更新发生变化的统计信息，并使用默认采样。索引 REBUILD 之后再对同一索引执行 UPDATE STATISTICS 是不必要的。","tags":["update statistics","statistics","sp_updatestats","fullscan","optimizer","maintenance"]},{"id":"dbcc-checkdb","task":"完整性检查（DBCC CHECKDB）","category":"维护","command":"-- Full integrity check:\nDBCC CHECKDB (N'Customer') WITH NO_INFOMSGS, ALL_ERRORMSGS;\n\n-- Lighter on large databases (physical consistency):\nDBCC CHECKDB (N'Customer') WITH PHYSICAL_ONLY, NO_INFOMSGS;","what":"检查数据库的物理和逻辑完整性（页结构、分配、索引一致性）并报告损坏。","when":"用于定期维护（每周）、怀疑硬件/IO 有问题时、出现 823/824 错误之后。","caution":"会消耗大量 I/O 和 tempdb；请在维护窗口中运行。若发现错误，REPAIR_ALLOW_DATA_LOSS 必须是最后手段，且会导致数据丢失；先考虑从完好的备份还原。PHYSICAL_ONLY 更快，但会跳过逻辑检查。","tags":["dbcc checkdb","integrity","corruption","corruption","integrity","maintenance"]},{"id":"index-fragmentation","task":"查看索引碎片","category":"维护","command":"SELECT OBJECT_SCHEMA_NAME(ips.object_id) AS schema_name,\n       OBJECT_NAME(ips.object_id)        AS table_name,\n       i.name                            AS index_name,\n       ips.index_type_desc,\n       ROUND(ips.avg_fragmentation_in_percent,1) AS frag_pct,\n       ips.page_count\nFROM   sys.dm_db_index_physical_stats(DB_ID(N'Customer'), NULL, NULL, NULL, 'LIMITED') ips\n  JOIN sys.indexes i\n    ON i.object_id = ips.object_id AND i.index_id = ips.index_id\nWHERE  ips.avg_fragmentation_in_percent > 10\n  AND  ips.page_count > 1000\nORDER BY ips.avg_fragmentation_in_percent DESC;","what":"列出索引的碎片百分比和页数；是做出重建/重组决定的依据。","when":"维护前，用于决定处理哪些索引。","caution":"小索引（< 1000 页）的碎片无关紧要；已被过滤掉。'DETAILED' 模式更准确但开销很大；'LIMITED' 通常就足够。仅查询；无风险。","tags":["fragmentation","dm_db_index_physical_stats","index","frag","maintenance","diagnostics"]},{"id":"kill-spid","task":"终止会话（SPID）","category":"会话","command":"-- First find the offending SPID:\nSELECT s.session_id, s.login_name, s.host_name, s.program_name,\n       r.status, r.command, r.wait_type, r.blocking_session_id,\n       DB_NAME(r.database_id) AS db\nFROM   sys.dm_exec_sessions s\n  LEFT JOIN sys.dm_exec_requests r ON r.session_id = s.session_id\nWHERE  s.is_user_process = 1;\n\n-- Kill (example SPID 63):\nKILL 63;\n\n-- Show rollback progress:\nKILL 63 WITH STATUSONLY;","what":"终止指定的 session_id（SPID）；其打开的事务会被回滚。","when":"用于停止持有锁、运行失控/长时间查询或不释放连接的会话。","caution":"对于大型打开事务，回滚可能耗时很长且无法取消；使用 KILL WITH STATUSONLY 监视进度。不要终止系统 SPID（is_user_process=0）。确认 SPID 正确。","tags":["kill","spid","kill session","dm_exec_sessions","dm_exec_requests","session"]},{"id":"blocking-chain","task":"查找阻塞（锁）链","category":"会话","command":"SELECT r.session_id            AS waiting_spid,\n       r.blocking_session_id  AS blocking_spid,\n       r.wait_type,\n       r.wait_time / 1000.0   AS wait_sec,\n       r.status, r.command,\n       DB_NAME(r.database_id) AS db,\n       t.text                 AS waiting_query\nFROM   sys.dm_exec_requests r\n  CROSS APPLY sys.dm_exec_sql_text(r.sql_handle) t\nWHERE  r.blocking_session_id <> 0\nORDER BY r.wait_time DESC;\n\n-- To find the head (root) blocker of the chain, look for the SPID whose\n-- own blocking_session_id is not set (it is not blocked itself).","what":"列出等待中的会话及阻塞它们的会话，以及等待时间和正在运行的查询；使锁链一目了然。","when":"在出现“应用挂起/超时”投诉时查找锁争用的根源。","caution":"终止根阻塞者（未被任何会话阻塞但阻塞其他会话的 SPID）之前，请与业务负责人确认。仅查询；无风险。","tags":["blocking","lock","lock","dm_exec_requests","blocking_session_id","session","chain"]},{"id":"active-queries","task":"查看活动查询及其开销","category":"会话","command":"SELECT r.session_id,\n       r.status,\n       r.command,\n       DB_NAME(r.database_id) AS db,\n       r.cpu_time,\n       r.total_elapsed_time / 1000 AS elapsed_sec,\n       r.reads, r.writes, r.logical_reads,\n       r.wait_type,\n       SUBSTRING(t.text,\n         (r.statement_start_offset/2)+1,\n         ((CASE r.statement_end_offset WHEN -1 THEN DATALENGTH(t.text)\n            ELSE r.statement_end_offset END - r.statement_start_offset)/2)+1) AS running_statement\nFROM   sys.dm_exec_requests r\n  JOIN sys.dm_exec_sessions s ON s.session_id = r.session_id\n  CROSS APPLY sys.dm_exec_sql_text(r.sql_handle) t\nWHERE  s.is_user_process = 1 AND r.session_id <> @@SPID\nORDER BY r.total_elapsed_time DESC;","what":"显示当前正在运行的用户查询及其 CPU、已用时间、读/写次数和准确的语句文本。","when":"在实时变慢期间查找哪个查询在消耗资源。","caution":"statement_start/end_offset 提取正在执行的准确语句（而不是整个批处理）。仅查询；无风险。","tags":["active queries","active query","dm_exec_requests","dm_exec_sql_text","cpu","session"]},{"id":"sp-configure-memory","task":"用 sp_configure 配置（max server memory）","category":"配置","command":"-- Make advanced options visible:\nEXEC sys.sp_configure 'show advanced options', 1;\nRECONFIGURE;\n\n-- Set max server memory (MB). Example: allow 24 GB:\nEXEC sys.sp_configure 'max server memory (MB)', 24576;\nRECONFIGURE;\n\n-- Verify:\nEXEC sys.sp_configure 'max server memory (MB)';","what":"限制 SQL Server 可用于 buffer pool 的最大内存；为操作系统和其他组件留出内存。","when":"服务器上的操作系统/其他服务内存不足，或新增了内存时。","caution":"为操作系统、SSAS/SSIS、CLR 和线程栈留出足够的内存（通常操作系统至少 4-6 GB）。设置过低会严重削弱 SQL Server 性能。不执行 RECONFIGURE 变更不会生效；此设置是动态的（无需重启）。","tags":["sp_configure","max server memory","reconfigure","memory","configuration","memory"]},{"id":"reconfigure-general","task":"通用 sp_configure 设置与 RECONFIGURE","category":"配置","command":"EXEC sys.sp_configure 'show advanced options', 1;\nRECONFIGURE;\n\n-- Example: cost threshold for parallelism and MAXDOP:\nEXEC sys.sp_configure 'cost threshold for parallelism', 50;\nEXEC sys.sp_configure 'max degree of parallelism', 4;\nRECONFIGURE;\n\n-- Show all configured/running values:\nSELECT name, value AS configured, value_in_use AS running, is_dynamic, is_advanced\nFROM   sys.configurations\nORDER BY name;","what":"设置服务器级配置值；RECONFIGURE 将其应用到运行值。","when":"更改 MAXDOP、并行度阈值或 optimize for ad hoc workloads 等服务器设置时。","caution":"is_dynamic = 0 的设置需要重启 SQL Server。RECONFIGURE WITH OVERRIDE 会跳过某些设置的验证；请谨慎使用。更改前记下 value_in_use。","tags":["sp_configure","reconfigure","maxdop","cost threshold","sys.configurations","configuration"]},{"id":"ag-failover","task":"可用性组故障转移","category":"配置","command":"-- Run on the TARGET (secondary) REPLICA - planned, no data loss (synchronous):\nALTER AVAILABILITY GROUP [AG_Customer] FAILOVER;\n\n-- Forced (asynchronous replica / primary unreachable, DATA LOSS risk):\n-- ALTER AVAILABILITY GROUP [AG_Customer] FORCE_FAILOVER_ALLOW_DATA_LOSS;\n\n-- Check status:\nSELECT ag.name, ar.replica_server_name,\n       rs.role_desc, rs.synchronization_health_desc, ar.availability_mode_desc\nFROM   sys.availability_groups ag\n  JOIN sys.availability_replicas ar ON ar.group_id = ag.group_id\n  JOIN sys.dm_hadr_availability_replica_states rs ON rs.replica_id = ar.replica_id;","what":"将可用性组的主角色转移到另一个副本；向同步副本执行的计划内故障转移不会丢失数据。","when":"维护/打补丁时的角色转移，主节点故障时的故障转移。","caution":"普通 FAILOVER 命令在将要接管的辅助副本上运行。FORCE_FAILOVER_ALLOW_DATA_LOSS 仅在主副本不可达或在异步副本上使用，可能导致数据丢失；同步状态不健康时务必格外小心。","tags":["availability group","ag","failover","hadr","failover","alwayson","configuration"]},{"id":"change-recovery-model","task":"更改数据库恢复模式","category":"配置","command":"-- Show the current model:\nSELECT name, recovery_model_desc FROM sys.databases WHERE name = N'Customer';\n\n-- FULL recovery (for point-in-time recovery):\nALTER DATABASE [Customer] SET RECOVERY FULL;\n\n-- SIMPLE recovery (log truncated automatically, no log backups needed):\n-- ALTER DATABASE [Customer] SET RECOVERY SIMPLE;\n\n-- BULK_LOGGED (temporary, for large bulk operations):\n-- ALTER DATABASE [Customer] SET RECOVERY BULK_LOGGED;","what":"更改数据库恢复模式；决定日志的管理方式和可用的恢复选项。","when":"FULL：需要时间点恢复/日志备份/AG 时。SIMPLE：不需要恢复点的系统（开发/测试、数据仓库）。","caution":"切换到 FULL 后，执行一次 FULL 备份以启动日志链，并设置定期日志备份，否则日志会持续增长。从 FULL 切换到 SIMPLE 会断开日志链；将失去时间点恢复能力。AG 成员必须处于 FULL 恢复模式。","tags":["recovery model","recovery full","simple","bulk_logged","alter database","configuration"]}]},"recipes/mssql-ext.json":{"engineId":"mssql","engine":"SQL Server","order":2,"recipes":[{"id":"ag-health-lag","task":"监控可用性组健康状况与延迟（log send / redo 队列、RPO）","category":"Always On 与高可用","command":"-- Replica health (run on the primary to see all replicas):\nSELECT ag.name AS ag_name, ar.replica_server_name, ars.role_desc,\n       ar.availability_mode_desc, ar.failover_mode_desc,\n       ars.connected_state_desc, ars.synchronization_health_desc,\n       ars.operational_state_desc, ars.last_connect_error_description\nFROM   sys.availability_groups ag\n  JOIN sys.availability_replicas ar                ON ar.group_id = ag.group_id\n  JOIN sys.dm_hadr_availability_replica_states ars ON ars.replica_id = ar.replica_id\nORDER BY ag.name, ars.role_desc;\n\n-- Per-database lag: log send queue (RPO), redo queue (RTO):\nSELECT ag.name AS ag_name, ar.replica_server_name, DB_NAME(drs.database_id) AS db,\n       drs.is_primary_replica, drs.synchronization_state_desc, drs.synchronization_health_desc,\n       drs.is_suspended, drs.suspend_reason_desc,\n       drs.log_send_queue_size  AS log_send_queue_kb,\n       drs.log_send_rate        AS log_send_kb_s,\n       drs.redo_queue_size      AS redo_queue_kb,\n       drs.redo_rate            AS redo_kb_s,\n       CAST(drs.redo_queue_size * 1.0 / NULLIF(drs.redo_rate, 0) AS DECIMAL(18,1)) AS est_redo_catchup_s,\n       drs.last_commit_time,\n       DATEDIFF(SECOND, drs.last_commit_time, pri.last_commit_time) AS data_loss_window_s,\n       drs.secondary_lag_seconds\nFROM   sys.dm_hadr_database_replica_states drs\n  JOIN sys.availability_replicas ar ON ar.replica_id = drs.replica_id\n  JOIN sys.availability_groups ag   ON ag.group_id = drs.group_id\n  LEFT JOIN sys.dm_hadr_database_replica_states pri\n         ON pri.group_database_id = drs.group_database_id AND pri.is_primary_replica = 1\nORDER BY ag.name, db, drs.is_primary_replica DESC;\n\n-- WSFC quorum state:\nSELECT cluster_name, quorum_type_desc, quorum_state_desc FROM sys.dm_hadr_cluster;\nSELECT member_name, member_type_desc, member_state_desc, number_of_quorum_votes\nFROM   sys.dm_hadr_cluster_members;","what":"显示副本连接和同步健康状况；按数据库给出 log send 队列、redo 队列、发送/redo 速率、预计 redo 追平时间以及与主副本的最后提交时间差（潜在数据丢失窗口）；还返回 WSFC 仲裁状态。","when":"AG 仪表板出现 NOT_HEALTHY / NOT SYNCHRONIZING 告警、计划内故障转移前检查 RPO/RTO，或用户反映可读辅助副本上数据过时时。","caution":"不断增长的 log_send_queue 是主副本故障时会丢失的日志量（RPO）；不断增长的 redo_queue 决定故障转移后数据库联机所需的时间（RTO）。在辅助副本上只能看到本地副本；在主副本上运行可看到全貌。data_loss_window_s 仅在有写入流量时有意义。只读查询；安全。","since":"2016","note":"secondary_lag_seconds 是 2016+ 的列。若 redo 队列持续增长，检查辅助副本上的 redo 线程是否被阻塞（长时间读查询、DDL），并查看 REDO_THREAD_PENDING_WORK / PARALLEL_REDO_* 等待。","tags":["availability group","ag","log send queue","redo queue","synchronization","rpo"],"untested":true},{"id":"ag-forced-failover-dr","task":"强制故障转移（FORCE_FAILOVER_ALLOW_DATA_LOSS）及故障转移后的恢复","category":"Always On 与高可用","command":"-- 1) On each candidate secondary: pick the most current one (highest end_of_log_lsn)\nSELECT ag.name AS ag_name, DB_NAME(drs.database_id) AS db,\n       drs.synchronization_state_desc, drs.end_of_log_lsn, drs.last_commit_time\nFROM   sys.dm_hadr_database_replica_states drs\n  JOIN sys.availability_groups ag ON ag.group_id = drs.group_id\nWHERE  drs.is_local = 1;\n\n-- 2) On the chosen replica (while the primary is unreachable):\nALTER AVAILABILITY GROUP [AG_Musteri] FORCE_FAILOVER_ALLOW_DATA_LOSS;\n\n-- 3) On the other secondaries, resume the suspended data movement:\nALTER DATABASE [Musteri] SET HADR RESUME;\n\n-- 4) When the old primary returns (now a SUSPENDED secondary):\n--    take a snapshot first to inspect the lost transactions, then resume\nCREATE DATABASE [Musteri_lost_snap]\n  ON (NAME = N'Musteri', FILENAME = N'D:\\Data\\Musteri_lost_snap.ss')\n  AS SNAPSHOT OF [Musteri];\nALTER DATABASE [Musteri] SET HADR RESUME;","what":"主副本完全丢失时，接受数据丢失将 DR 副本提升为主副本；随后恢复其他副本上挂起的数据移动，并在旧主副本恢复后创建快照，以检查未到达新主副本的事务。","when":"主数据中心宕机、只剩异步副本，或同步副本不是 SYNCHRONIZED 状态但仍必须故障转移时。","caution":"数据丢失：最后一个已固化 LSN 之后的事务会丢失。先获得业务方对 RPO 的认可。强制故障转移后所有辅助数据库都处于 SUSPENDED 状态；在 HADR RESUME 之前日志不会截断并持续增长。若 WSFC 仲裁丢失，先以强制仲裁启动群集（PowerShell：Start-ClusterNode -FixQuorum），否则命令会失败。在旧主副本上执行 HADR RESUME 会永久丢弃丢失的事务；未创建快照前不要恢复。","note":"无数据丢失的计划内故障转移请使用 ag-failover 操作指南中的 FAILOVER 命令。CLUSTER_TYPE = NONE / EXTERNAL 的 AG 也使用 FORCE_FAILOVER_ALLOW_DATA_LOSS；在 EXTERNAL（Pacemaker）上故障转移由集群管理器驱动。快照需要源数据库的每个逻辑数据文件；对于多文件数据库，每个数据文件添加一条 ON 条目。","tags":["force failover","allow data loss","dr","availability group","hadr resume","data loss"],"untested":true},{"id":"ag-add-db-autoseed","task":"将数据库加入 AG（自动种子设定）并监控种子设定进度","category":"Always On 与高可用","command":"-- Prerequisite (primary): FULL recovery and at least one FULL backup\nSELECT name, recovery_model_desc FROM sys.databases WHERE name = N'Musteri';\nBACKUP DATABASE [Musteri] TO DISK = N'D:\\Backup\\Musteri_pre_ag.bak' WITH COMPRESSION, CHECKSUM, INIT;\n\n-- 1) Primary: switch the replica to automatic seeding\nALTER AVAILABILITY GROUP [AG_Musteri]\n  MODIFY REPLICA ON N'SQL02' WITH (SEEDING_MODE = AUTOMATIC);\n\n-- 2) On each secondary: allow the AG to create databases\nALTER AVAILABILITY GROUP [AG_Musteri] GRANT CREATE ANY DATABASE;\n\n-- 3) Primary: add the database\nALTER AVAILABILITY GROUP [AG_Musteri] ADD DATABASE [Musteri];\n\n-- 4) Progress (primary or secondary):\nSELECT local_database_name, role_desc, internal_state_desc,\n       transferred_size_bytes / 1048576         AS transferred_mb,\n       database_size_bytes / 1048576            AS db_mb,\n       transfer_rate_bytes_per_second / 1048576 AS mb_s,\n       estimate_time_complete_utc, failure_message\nFROM   sys.dm_hadr_physical_seeding_stats;\n\n-- 5) Seeding history and failure reason:\nSELECT s.start_time, s.completion_time, adc.database_name, ar.replica_server_name,\n       s.current_state, s.failure_state_desc, s.error_code, s.number_of_attempts\nFROM   sys.dm_hadr_automatic_seeding s\n  JOIN sys.availability_databases_cluster adc ON adc.group_database_id = s.ag_db_id\n  JOIN sys.availability_replicas ar           ON ar.replica_id = s.ag_remote_replica_id\nORDER BY s.start_time DESC;","what":"通过终结点（VDI）将数据库流式传输到辅助副本，从而将其加入 AG，无需手动备份/还原；显示种子设定吞吐量、预计完成时间和失败原因。","when":"向 AG 添加新数据库、添加新副本或重建损坏的辅助数据库时。","caution":"种子设定会占满终结点链路，且默认不压缩；对于超大数据库或较弱的 WAN，手动备份/还原 + JOIN 更可预测。种子设定期间主副本日志不会截断（log_reuse_wait_desc = AVAILABILITY_REPLICA）。在 2016 上辅助副本需要与主副本相同的数据/日志文件夹路径；在 2017+ 上，若主副本文件位于实例默认路径，辅助副本使用其自身的默认路径。对于 TDE 数据库，必须先在每个辅助副本上还原证书。若辅助副本上未执行 GRANT CREATE ANY DATABASE，种子设定会一直挂起。","since":"2016","note":"种子设定压缩通过跟踪标志 9567 启用（消耗 CPU）。种子设定的开始、完成和失败会写入 SQL Server 错误日志；如需详细跟踪，用 hadr_automatic_seeding_* 和 hadr_physical_seeding_* 事件创建单独的扩展事件会话。使用 SEEDING_MODE = MANUAL 切换回来。","tags":["automatic seeding","availability group","add database","seeding_mode","dm_hadr_physical_seeding_stats","ag"],"untested":true},{"id":"ag-listener-routing","task":"检查侦听器、其 IP 和只读路由配置","category":"Always On 与高可用","command":"-- Listener, port and IP states:\nSELECT ag.name AS ag_name, l.dns_name, l.port, l.is_conformant,\n       ip.ip_address, ip.ip_subnet_mask, ip.state_desc\nFROM   sys.availability_group_listeners l\n  JOIN sys.availability_groups ag ON ag.group_id = l.group_id\n  LEFT JOIN sys.availability_group_listener_ip_addresses ip ON ip.listener_id = l.listener_id;\n\n-- Read-only routing list: where read intent goes when X is primary:\nSELECT ag.name AS ag_name, pr.replica_server_name AS when_primary_is,\n       rl.routing_priority, sr.replica_server_name AS route_to,\n       sr.read_only_routing_url, sr.secondary_role_allow_connections_desc\nFROM   sys.availability_read_only_routing_lists rl\n  JOIN sys.availability_replicas pr ON pr.replica_id = rl.replica_id\n  JOIN sys.availability_replicas sr ON sr.replica_id = rl.read_only_replica_id\n  JOIN sys.availability_groups ag   ON ag.group_id = pr.group_id\nORDER BY ag.name, when_primary_is, rl.routing_priority;\n\n-- Replica endpoint and connection settings:\nSELECT replica_server_name, endpoint_url, read_only_routing_url,\n       secondary_role_allow_connections_desc, seeding_mode_desc, session_timeout\nFROM   sys.availability_replicas;\n\n-- Routing test from the command line (-d is required with -K ReadOnly):\n-- sqlcmd -S tcp:AGL-Musteri,1433 -d Musteri -E -M -K ReadOnly -Q \"SELECT @@SERVERNAME\"","what":"显示侦听器 DNS 名称、端口以及每个子网 IP 的群集状态；针对只读路由列表中的每种主副本场景，显示读意向连接被路由到哪个副本和 URL。","when":"故障转移后应用无法连接、ApplicationIntent=ReadOnly 连接落到主副本上，或添加了新子网/副本时。","caution":"只读路由仅对通过侦听器、带 ApplicationIntent=ReadOnly 且显式指定数据库的连接生效；直接连接实例名的连接不会被路由。read_only_routing_url 为空的副本不能作为路由目标。在多子网侦听器上，未设置 MultiSubnetFailover=True 的客户端在故障转移后会遇到 20-30 秒的连接超时。只读查询；安全。","note":"对于 2016+ 的负载均衡，路由列表使用嵌套括号：READ_ONLY_ROUTING_LIST = (('SQL02','SQL03'),'SQL01')。若侦听器 IP 显示 OFFLINE，请检查群集端的资源依赖关系（Failover Cluster Manager / Get-ClusterResource）。","tags":["listener","read-only routing","applicationintent","multisubnetfailover","availability group","connection"],"untested":true},{"id":"logshipping-monitor","task":"监控日志传送状态与延迟","category":"Always On 与高可用","command":"-- Summary (on the primary, secondary or monitor server):\nEXEC master.sys.sp_help_log_shipping_monitor;\n\n-- PRIMARY: when the last log backup was taken:\nSELECT p.primary_server, p.primary_database,\n       p.last_backup_date, p.last_backup_file,\n       DATEDIFF(MINUTE, p.last_backup_date, GETDATE()) AS backup_age_min,\n       p.backup_threshold                              AS threshold_min\nFROM   msdb.dbo.log_shipping_monitor_primary p;\n\n-- SECONDARY: copy and restore latency:\nSELECT s.secondary_server, s.secondary_database, s.primary_server,\n       s.last_copied_date, s.last_copied_file,\n       s.last_restored_date, s.last_restored_file,\n       s.last_restored_latency                           AS restore_latency_min,\n       DATEDIFF(MINUTE, s.last_restored_date, GETDATE()) AS since_last_restore_min,\n       s.restore_threshold                               AS threshold_min\nFROM   msdb.dbo.log_shipping_monitor_secondary s;\n\n-- Errors in the last 24 hours:\nSELECT e.database_name, e.agent_type, e.log_time, e.message\nFROM   msdb.dbo.log_shipping_monitor_error_detail e\nWHERE  e.log_time >= DATEADD(HOUR, -24, GETDATE())\nORDER BY e.log_time DESC;\n\n-- Secondary DB mode (RESTORING = NORECOVERY, is_in_standby = STANDBY):\nSELECT name, state_desc, is_in_standby FROM sys.databases WHERE name = N'Musteri';","what":"显示主服务器上最后一次日志备份的时长、辅助服务器上最后复制/还原的文件和还原延迟及其阈值，以及过去 24 小时内备份/复制/还原作业的错误。","when":"日志传送警报作业触发（错误 14420/14421）、DR 演练之前，或报告辅助服务器落后多少时。","caution":"监视表只包含服务器能看到的一侧：主表在主服务器/监视服务器上填充，辅助表在辅助服务器/监视服务器上填充。手动（非日志传送）日志备份会断开链，还原作业会持续失败；临时日志备份请使用 COPY_ONLY。在 STANDBY 模式下，每次还原前会断开用户连接。只读查询；安全。","note":"日期列为服务器本地时间；比较不同时区的服务器时请使用 *_utc 列。若还原作业记录“skipping log backup file”，说明该文件已应用过；要查找真正的缺口，请在辅助服务器上用 RESTORE HEADERONLY 检查 FirstLSN/LastLSN 序列。","tags":["log shipping","sp_help_log_shipping_monitor","restore latency","dr","secondary","latency"],"untested":true},{"id":"backup-copy-only","task":"不断开链的临时备份（COPY_ONLY、条带化、吞吐量）","category":"备份与日志","command":"-- Ad-hoc FULL that does not reset the differential base:\nBACKUP DATABASE [Musteri]\nTO DISK = N'D:\\Backup\\Musteri_copyonly_20261008.bak'\nWITH COPY_ONLY, COMPRESSION, CHECKSUM, INIT, STATS = 10;\n\n-- Ad-hoc log backup that does not truncate the log:\nBACKUP LOG [Musteri]\nTO DISK = N'D:\\Backup\\Musteri_copyonly_20261008.trn'\nWITH COPY_ONLY, COMPRESSION, CHECKSUM, INIT;\n\n-- Large DB: stripe across files and tune buffers:\nBACKUP DATABASE [Musteri]\nTO DISK = N'D:\\Backup\\Musteri_s1.bak', DISK = N'E:\\Backup\\Musteri_s2.bak'\nWITH COPY_ONLY, COMPRESSION, CHECKSUM, INIT,\n     MAXTRANSFERSIZE = 4194304, BUFFERCOUNT = 64, STATS = 5;","what":"执行一个不影响常规备份链（差异基准和日志链）的独立 FULL 或日志备份；对于大型数据库，跨多个文件条带化和更大的传输大小可缩短备份时间。","when":"向测试/开发交付副本、进行高风险变更之前，或审计要求提供备份时；凡是需要备份又不想触动计划内备份策略的场合。","caution":"不带 COPY_ONLY 的临时 FULL 会成为后续 DIFF 备份的基准；若该文件被删除，DIFF 将无用。COPY_ONLY 与 DIFFERENTIAL 一起使用时无效。还原条带化备份需要所有文件；丢失一个就会使整个备份不可用。BUFFERCOUNT x MAXTRANSFERSIZE 的内存在 buffer pool 之外分配（示例中为 256 MB）；在内存压力大的服务器上请调低。","note":"对于 TDE 数据库，备份压缩要求 MAXTRANSFERSIZE 大于 65536（2016+）。服务器默认值：sp_configure 'backup compression default' 和 'backup checksum default'。在配备 Intel QAT 硬件的 2022 上可使用 WITH COMPRESSION (ALGORITHM = QAT_DEFLATE)。","tags":["copy_only","ad-hoc backup","stripe","maxtransfersize","buffercount","backup"]},{"id":"backup-to-url","task":"备份到 URL（Azure Blob SAS / S3 兼容存储）","category":"备份与日志","command":"-- A) Azure Blob: SAS credential (named after the container URL, SECRET without a leading '?')\nCREATE CREDENTIAL [https://dbabackup.blob.core.windows.net/sql]\nWITH IDENTITY = N'SHARED ACCESS SIGNATURE',\n     SECRET   = N'sv=2022-11-02&ss=b&srt=co&sp=rwdlac&se=2027-01-01T00:00:00Z&sig=XXXX';\n\nBACKUP DATABASE [Musteri]\nTO URL = N'https://dbabackup.blob.core.windows.net/sql/Musteri_full_1.bak',\n   URL = N'https://dbabackup.blob.core.windows.net/sql/Musteri_full_2.bak'\nWITH COMPRESSION, CHECKSUM, MAXTRANSFERSIZE = 4194304, BLOCKSIZE = 65536, STATS = 10;\n\n-- B) S3-compatible storage (2022+):\nCREATE CREDENTIAL [s3://s3.eu-central-1.amazonaws.com/dba-backup/sql]\nWITH IDENTITY = N'S3 Access Key',\n     SECRET   = N'<AccessKeyID>:<SecretKeyID>';\n\nBACKUP DATABASE [Musteri]\nTO URL = N's3://s3.eu-central-1.amazonaws.com/dba-backup/sql/Musteri_full.bak'\nWITH COMPRESSION, CHECKSUM, FORMAT, STATS = 10;\n\n-- Verify (uses the same credential):\nRESTORE VERIFYONLY\nFROM URL = N's3://s3.eu-central-1.amazonaws.com/dba-backup/sql/Musteri_full.bak'\nWITH CHECKSUM;","what":"将备份直接写入对象存储：使用 SAS 凭据以块 blob 形式写入 Azure Blob，或在 2022 上使用访问密钥凭据写入兼容 S3 API 的存储（AWS S3、MinIO 等）。通过匹配 URL 前缀自动选择凭据。","when":"用于异地备份要求、云迁移/混合 DR 场景，或本地备份存储不足时。","caution":"在 Azure 上单个块 blob 最多 50,000 个块；MAXTRANSFERSIZE 为 4 MB 时每个文件约 195 GB，因此大型数据库应条带化到多个 URL。在 S3 上一个文件最多 10,000 个分段；对于大型数据库，调大 MAXTRANSFERSIZE（最高 20 MB，需要 COMPRESSION）并条带化到多个 URL（最多 64 个）。S3 不支持 INIT/追加；FORMAT 会覆盖同名的现有备份。SAS 过期后备份作业会报错失败；把到期日期记入日历。凭据密钥存储在 master 中；拥有 CONTROL SERVER 的主体可以使用它。","since":"2022","note":"使用 SAS 的块 blob 备份需要 2016+，S3 支持需要 2022+。旧的存储账户密钥方式（IDENTITY = 账户名）生成页 blob，不推荐。对于 S3，服务器必须信任终结点的 TLS 证书；对于 MinIO 等本地部署方案，端口写在 URL 中（s3://minio.local:9000/bucket/...）。","tags":["backup to url","azure blob","s3","credential","sas","cloud backup"],"untested":true},{"id":"backup-history-msdb","task":"从 msdb 查看备份历史并检查缺失的备份","category":"备份与日志","command":"-- Last FULL / DIFF / LOG per database (never backed up first):\nSELECT d.name AS db,\n       d.recovery_model_desc,\n       MAX(CASE WHEN b.type = 'D' THEN b.backup_finish_date END) AS last_full,\n       MAX(CASE WHEN b.type = 'I' THEN b.backup_finish_date END) AS last_diff,\n       MAX(CASE WHEN b.type = 'L' THEN b.backup_finish_date END) AS last_log,\n       DATEDIFF(MINUTE, MAX(CASE WHEN b.type = 'L' THEN b.backup_finish_date END), GETDATE()) AS log_age_min\nFROM   sys.databases d\n  LEFT JOIN msdb.dbo.backupset b\n         ON b.database_name = d.name AND b.is_copy_only = 0\nWHERE  d.name <> N'tempdb'\nGROUP BY d.name, d.recovery_model_desc\nORDER BY last_full;\n\n-- Chain for one DB over the last 7 days (LSN, size, file):\nSELECT b.backup_start_date, b.backup_finish_date, b.type, b.is_copy_only,\n       b.first_lsn, b.last_lsn, b.database_backup_lsn,\n       CAST(b.backup_size / 1048576.0 AS DECIMAL(12,1))            AS size_mb,\n       CAST(b.compressed_backup_size / 1048576.0 AS DECIMAL(12,1)) AS compressed_mb,\n       b.has_backup_checksums, mf.physical_device_name\nFROM   msdb.dbo.backupset b\n  JOIN msdb.dbo.backupmediafamily mf ON mf.media_set_id = b.media_set_id\nWHERE  b.database_name = N'Musteri'\n  AND  b.backup_start_date >= DATEADD(DAY, -7, GETDATE())\nORDER BY b.backup_start_date;","what":"列出每个数据库最后一次 FULL/DIFF/LOG 备份的时间和日志备份时长；针对单个数据库，列出备份链及其 LSN 序列、压缩后大小、校验和标志和文件路径。","when":"用于晨检、查找还原所需的文件、在审计中回答“最后一次备份是什么时候”，或在日志已满告警期间确认日志备份是否在运行。","caution":"msdb 只知道本实例执行的备份；在其他服务器或其他 AG 副本上执行的备份不可见（在 AG 中要检查每个副本）。日志备份的 first_lsn 必须等于前一个的 last_lsn；出现缺口说明链已断。在 FULL 恢复模式下 last_log 为 NULL 表示日志从未备份，会持续增长。只读查询；安全。","note":"msdb 历史会无限增长并拖慢备份作业；请定期用 EXEC msdb.dbo.sp_delete_backuphistory @oldest_date = '2026-01-01'; 清理。type 值：D = FULL，I = DIFF，L = LOG，F = 文件/文件组。","tags":["backupset","msdb","backup history","last backup","lsn","backup chain"]},{"id":"backup-restore-progress","task":"查看备份/还原/CHECKDB 进度和预计完成时间","category":"备份与日志","command":"SELECT r.session_id, r.command, DB_NAME(r.database_id) AS db,\n       CAST(r.percent_complete AS DECIMAL(5,1))                     AS pct,\n       DATEADD(SECOND, r.estimated_completion_time / 1000, GETDATE()) AS eta,\n       r.total_elapsed_time / 60000                                 AS elapsed_min,\n       r.wait_type, r.wait_time, r.blocking_session_id\nFROM   sys.dm_exec_requests r\nWHERE  r.percent_complete > 0;\n\n-- Restore phases and throughput are in the error log:\nEXEC sys.xp_readerrorlog 0, 1, N'Restore';","what":"对报告 percent_complete 的长时间操作（BACKUP、RESTORE、DBCC CHECKDB/SHRINK、ALTER INDEX REORGANIZE、KILL 后的回滚）显示百分比进度、预计完成时间和当前等待。","when":"夜间备份延续到业务时间、大型还原期间回答“什么时候完成”，或跟踪被终止会话的回滚时。","caution":"估算假定进度是线性的；在还原的 redo/undo 阶段和日志零初始化期间，百分比可能长时间停滞。日志文件不能使用即时文件初始化（2022 上 64 MB 以内的增长除外）；日志较大的还原会在第一阶段耗费很长时间。只读查询；安全。","note":"若 wait_type 主要是 BACKUPIO / ASYNC_IO_COMPLETION，瓶颈在目标/源磁盘；BACKUPTHREAD 或 CPU 较高则说明是压缩开销。要在错误日志中详细查看还原的长时间清零阶段，请使用跟踪标志 3004 + 3605。","tags":["percent_complete","backup progress","restore progress","eta","dm_exec_requests","progress"]},{"id":"tail-log-backup","task":"还原前执行结尾日志备份（NORECOVERY / NO_TRUNCATE）","category":"还原与恢复","command":"-- A) Healthy DB: back up the tail and put the DB into RESTORING (no new writes):\nBACKUP LOG [Musteri]\nTO DISK = N'D:\\Backup\\Musteri_tail.trn'\nWITH NORECOVERY, CHECKSUM, INIT;\n\n-- B) Damaged DB / data file lost, log file intact:\n-- BACKUP LOG [Musteri]\n-- TO DISK = N'D:\\Backup\\Musteri_tail_damaged.trn'\n-- WITH NO_TRUNCATE, CONTINUE_AFTER_ERROR, INIT;\n\nSELECT name, state_desc FROM sys.databases WHERE name = N'Musteri';","what":"备份自上次日志备份以来写入的日志部分（结尾），使还原链到达故障时刻，最后几分钟的事务不会丢失。","when":"在同一数据库上覆盖还原之前、数据文件磁盘故障时，或迁移最终切换时。","caution":"NORECOVERY 会使数据库进入 RESTORING 状态并终止用户访问；不要在错误的数据库上运行。未执行结尾日志备份就 WITH REPLACE 还原，会永久丢弃上次日志备份之后的事务。用 NO_TRUNCATE 执行的备份可能包含不完整的元数据；还原后运行 DBCC CHECKDB。SIMPLE 恢复模式下无法执行结尾日志备份。","note":"要在不还原的情况下让数据库脱离 RESTORING：RESTORE DATABASE [Musteri] WITH RECOVERY;。NO_TRUNCATE 等同于 COPY_ONLY + CONTINUE_AFTER_ERROR，即使数据库无法打开也会尝试读取日志。","tags":["tail-log","backup log","norecovery","no_truncate","restore","recovery"]},{"id":"restore-pitr-stopat","task":"还原到某个时间点（时间点还原，STOPAT）","category":"还原与恢复","command":"-- Restore under a side name, keep the original intact:\nRESTORE DATABASE [Musteri_PITR]\nFROM DISK = N'D:\\Backup\\Musteri_full_20261008.bak'\nWITH MOVE N'Musteri'     TO N'D:\\Data\\Musteri_PITR.mdf',\n     MOVE N'Musteri_log' TO N'D:\\Log\\Musteri_PITR_log.ldf',\n     NORECOVERY, STATS = 10;\n\nRESTORE DATABASE [Musteri_PITR]\nFROM DISK = N'D:\\Backup\\Musteri_diff_20261008.bak' WITH NORECOVERY;\n\n-- All log backups in order, each with the same STOPAT:\nRESTORE LOG [Musteri_PITR] FROM DISK = N'D:\\Backup\\Musteri_log_1200.trn'\nWITH NORECOVERY, STOPAT = '2026-10-08T14:29:00';\nRESTORE LOG [Musteri_PITR] FROM DISK = N'D:\\Backup\\Musteri_log_1500.trn'\nWITH NORECOVERY, STOPAT = '2026-10-08T14:29:00';\n\nRESTORE DATABASE [Musteri_PITR] WITH RECOVERY;\n\n-- Alternative: step forward with STANDBY and read data at each point:\n-- RESTORE LOG [Musteri_PITR] FROM DISK = N'...' WITH STANDBY = N'D:\\Backup\\Musteri_PITR_undo.dat', STOPAT = '...';","what":"以 WITH NORECOVERY 应用 FULL +（可选）DIFF + 日志备份，并在指定时间停止日志重放；将数据库恢复到错误操作（错误的 DELETE/UPDATE、DROP）之前的时刻。","when":"用于恢复被误删/误改的数据、检查某一时刻的数据或审计。","caution":"以另一个名称还原，而不是覆盖原库，然后把需要的行复制回去；若必须覆盖还原，先执行结尾日志备份。STOPAT 为服务器本地时间。恢复模式必须是 FULL；若包含 STOPAT 时间的日志备份中含有 BULK_LOGGED 下的最小日志记录操作，则无法在该备份内部停止。RESTORE ... WITH RECOVERY 之后不能再应用日志。","note":"推荐在每次日志还原时都指定 STOPAT；较早文件上出现的信息性消息“contains records that were logged before the designated point in time”是正常的。要停在命名标记处（BEGIN TRAN ... WITH MARK），请使用 STOPATMARK / STOPBEFOREMARK。使用 fn_dump_dblog 或应用日志查找确切时间。","tags":["stopat","point-in-time","restore log","norecovery","standby","rollback to time"]},{"id":"restore-verify-header","task":"检查并验证备份文件（VERIFYONLY、HEADERONLY、FILELISTONLY）","category":"还原与恢复","command":"-- Readability and page checksum verification:\nRESTORE VERIFYONLY FROM DISK = N'D:\\Backup\\Musteri_full_20261008.bak' WITH CHECKSUM;\n\n-- Backup sets in the file (type, LSN, date, version, compression):\nRESTORE HEADERONLY FROM DISK = N'D:\\Backup\\Musteri_full_20261008.bak';\n\n-- Logical file names and sizes (needed for MOVE):\nRESTORE FILELISTONLY FROM DISK = N'D:\\Backup\\Musteri_full_20261008.bak';\n\n-- Media info (stripe count: FamilyCount):\nRESTORE LABELONLY FROM DISK = N'D:\\Backup\\Musteri_full_20261008.bak';","what":"在不还原的情况下验证备份可读且页校验和有效；显示其中的备份集（Position、BackupType、FirstLSN/LastLSN、DatabaseVersion）、逻辑文件名和条带信息。","when":"还原从其他团队收到的备份之前、为 MOVE 获取文件名、备份作业后快速检查，或一个文件包含多个备份集时选择正确的 FILE = n。","caution":"VERIFYONLY 不保证数据库可还原且一致；它只检查备份结构和（若以 WITH CHECKSUM 执行）页校验和。要真正放心，请定期执行测试还原 + DBCC CHECKDB。较新版本的备份无法在旧版本上还原（检查 DatabaseVersion）。读操作；安全。","note":"若 HEADERONLY 显示 HasBackupChecksums = 0，说明备份未使用 CHECKSUM，VERIFYONLY WITH CHECKSUM 无法验证页。未使用 INIT 写入的文件会累积多个备份集；还原时指定 WITH FILE = <Position>。","tags":["restore verifyonly","headeronly","filelistonly","labelonly","backup verification","checksum"]},{"id":"page-restore-suspect","task":"查找损坏页（suspect_pages）并还原单个页","category":"还原与恢复","command":"-- Pages that hit 823/824 or checksum errors:\nSELECT DB_NAME(sp.database_id) AS db, sp.file_id, sp.page_id,\n       CASE sp.event_type WHEN 1 THEN '823/824 error' WHEN 2 THEN 'bad checksum'\n            WHEN 3 THEN 'torn page'     WHEN 4 THEN 'restored'\n            WHEN 5 THEN 'repaired'      WHEN 7 THEN 'deallocated by DBCC' END AS event_desc,\n       sp.error_count, sp.last_update_date\nFROM   msdb.dbo.suspect_pages sp\nORDER BY sp.last_update_date DESC;\n\n-- Page restore (example 1:336): FULL + later logs + a new log backup\nRESTORE DATABASE [Musteri] PAGE = '1:336'\nFROM DISK = N'D:\\Backup\\Musteri_full_20261008.bak' WITH NORECOVERY;\nRESTORE LOG [Musteri] FROM DISK = N'D:\\Backup\\Musteri_log_1200.trn' WITH NORECOVERY;\nBACKUP LOG [Musteri] TO DISK = N'D:\\Backup\\Musteri_log_pagetail.trn' WITH INIT;\nRESTORE LOG [Musteri] FROM DISK = N'D:\\Backup\\Musteri_log_pagetail.trn' WITH RECOVERY;\n\n-- Verify:\nDBCC CHECKDB (N'Musteri') WITH NO_INFOMSGS, ALL_ERRORMSGS;","what":"从 msdb.dbo.suspect_pages 列出遇到 I/O（823/824）、校验和或页撕裂错误的页；从 FULL 备份中取出各个损坏页并用日志前滚进行修复，无需还原整个数据库。","when":"错误日志中出现 823/824 错误、CHECKDB 中的一致性错误仅限于少数几页，或用户查询因“incorrect checksum”失败时。","caution":"需要 FULL 或 BULK_LOGGED 恢复模式以及完整的日志链。联机页还原仅限 Enterprise/Developer；其他版本在还原期间数据库处于脱机状态。分配页（GAM/SGAM/PFS）、文件头和引导页（1:9）无法用此方式还原。823/824 通常意味着存储问题；未检查磁盘/控制器/SAN 就不要结案，否则会再次发生。坏页很多时，完整还原数据库更快。","note":"suspect_pages 最多保存 1,000 行；写满后不再记录新错误，因此请删除旧行（event_type 4/5/7）。多个页以逗号分隔：PAGE = '1:336, 1:337'。在 AG 中，主副本上的损坏页会通过自动页修复（sys.dm_hadr_auto_page_repair）从辅助副本自动修复。","tags":["suspect_pages","page restore","823","824","corruption","bad page"]},{"id":"top-queries-cpu","task":"在计划缓存中查找开销最大的查询（CPU、读取、持续时间）","category":"性能","command":"SELECT TOP (20)\n       qs.query_hash,\n       SUM(qs.execution_count)                                       AS execs,\n       SUM(qs.total_worker_time) / 1000                              AS total_cpu_ms,\n       SUM(qs.total_worker_time) / SUM(qs.execution_count) / 1000    AS avg_cpu_ms,\n       SUM(qs.total_elapsed_time) / SUM(qs.execution_count) / 1000   AS avg_ms,\n       SUM(qs.total_logical_reads) / SUM(qs.execution_count)         AS avg_reads,\n       SUM(qs.total_grant_kb) / SUM(qs.execution_count)              AS avg_grant_kb,\n       COUNT(DISTINCT qs.query_plan_hash)                            AS plan_count,\n       MAX(qs.last_execution_time)                                   AS last_exec,\n       MIN(SUBSTRING(st.text, qs.statement_start_offset / 2 + 1,\n           ((CASE qs.statement_end_offset WHEN -1 THEN DATALENGTH(st.text)\n             ELSE qs.statement_end_offset END - qs.statement_start_offset) / 2) + 1)) AS statement_text,\n       MIN(DB_NAME(pa.dbid))                                         AS db\nFROM   sys.dm_exec_query_stats qs\n  CROSS APPLY sys.dm_exec_sql_text(qs.sql_handle) st\n  OUTER APPLY (SELECT CONVERT(int, value) AS dbid FROM sys.dm_exec_plan_attributes(qs.plan_handle)\n               WHERE attribute = N'dbid') pa\nGROUP BY qs.query_hash\nORDER BY total_cpu_ms DESC;\n\n-- To rank by reads: ORDER BY SUM(qs.total_logical_reads) DESC\n-- Plan: SELECT query_plan FROM sys.dm_exec_query_plan(<plan_handle>);","what":"按 query_hash 对计划缓存中的语句分组（仅字面值不同的即席查询合并为一行），返回总的和平均的 CPU、持续时间、逻辑读、内存授予以及计划数。","when":"CPU 占满、出现“服务器很慢”投诉、数据库未启用 Query Store，或需要跨所有数据库的服务器级视图时。","caution":"只能看到仍在缓存中的计划：计划因重启、DBCC FREEPROCCACHE、内存压力或 RECOMPILE 被逐出的查询不会出现。持久化、按时间窗口的分析请使用 Query Store。plan_count > 1 暗示参数敏感或 SET 选项不同。计划缓存非常大时查询可能需要几秒；安全。","since":"2016","note":"total_grant_kb 是 2016+ 的列。按存储过程汇总请使用 sys.dm_exec_procedure_stats。","tags":["dm_exec_query_stats","top queries","cpu","query_hash","plan cache","expensive query"]},{"id":"wait-stats-top","task":"等待统计：服务器在等待什么","category":"性能","command":"WITH w AS (\n    SELECT wait_type, waiting_tasks_count,\n           wait_time_ms / 1000.0                         AS wait_s,\n           (wait_time_ms - signal_wait_time_ms) / 1000.0 AS resource_s,\n           signal_wait_time_ms / 1000.0                  AS signal_s\n    FROM   sys.dm_os_wait_stats\n    WHERE  waiting_tasks_count > 0\n      AND  wait_type NOT LIKE N'SLEEP[_]%'\n      AND  wait_type NOT IN (\n           N'BROKER_EVENTHANDLER', N'BROKER_RECEIVE_WAITFOR', N'BROKER_TASK_STOP', N'BROKER_TO_FLUSH',\n           N'BROKER_TRANSMITTER', N'CHECKPOINT_QUEUE', N'CLR_AUTO_EVENT', N'CLR_MANUAL_EVENT',\n           N'DIRTY_PAGE_POLL', N'DISPATCHER_QUEUE_SEMAPHORE', N'FT_IFTS_SCHEDULER_IDLE_WAIT',\n           N'HADR_FILESTREAM_IOMGR_IOCOMPLETION', N'HADR_WORK_QUEUE', N'LAZYWRITER_SLEEP',\n           N'LOGMGR_QUEUE', N'ONDEMAND_TASK_QUEUE', N'PVS_PREALLOCATE', N'REQUEST_FOR_DEADLOCK_SEARCH',\n           N'SNI_HTTP_ACCEPT', N'SOS_WORK_DISPATCHER', N'SP_SERVER_DIAGNOSTICS_SLEEP',\n           N'SQLTRACE_BUFFER_FLUSH', N'SQLTRACE_INCREMENTAL_FLUSH_SLEEP', N'WAITFOR',\n           N'XE_DISPATCHER_WAIT', N'XE_TIMER_EVENT', N'XE_LIVE_TARGET_TVF', N'PREEMPTIVE_XE_DISPATCHER',\n           N'QDS_PERSIST_TASK_MAIN_LOOP_SLEEP', N'QDS_ASYNC_QUEUE', N'QDS_SHUTDOWN_QUEUE',\n           N'QDS_CLEANUP_STALE_QUERIES_TASK_MAIN_LOOP_SLEEP', N'PWAIT_ALL_COMPONENTS_INITIALIZED',\n           N'PWAIT_EXTENSIBILITY_CLEANUP_TASK', N'STARTUP_DEPENDENCY_MANAGER', N'WAIT_XTP_CKPT_CLOSE')\n)\nSELECT TOP (20) wait_type, waiting_tasks_count,\n       CAST(wait_s AS DECIMAL(18,1))                                AS wait_s,\n       CAST(100.0 * wait_s / SUM(wait_s) OVER () AS DECIMAL(5,1))   AS pct,\n       CAST(resource_s AS DECIMAL(18,1))                            AS resource_s,\n       CAST(signal_s AS DECIMAL(18,1))                              AS signal_s,\n       CAST(1000.0 * wait_s / waiting_tasks_count AS DECIMAL(18,2)) AS avg_wait_ms\nFROM   w\nORDER BY wait_s DESC;\n\n-- Values accumulate since:\nSELECT sqlserver_start_time FROM sys.dm_os_sys_info;\n\n-- User requests waiting right now:\nSELECT wt.session_id, wt.wait_type, wt.wait_duration_ms, wt.resource_description, wt.blocking_session_id\nFROM   sys.dm_os_waiting_tasks wt\n  JOIN sys.dm_exec_sessions s ON s.session_id = wt.session_id\nWHERE  s.is_user_process = 1;","what":"过滤掉无害的后台等待，按等待类型分解服务器总等待时间，给出百分比以及资源等待与信号等待（CPU 队列）的占比；还显示此刻正在等待的用户请求。","when":"作为性能排查的第一步，用于判断瓶颈类别（I/O、CPU、锁、日志、内存、并行）；用于变更前后的基线对比。","caution":"数值是自重启以来的累计值；排查当前问题时采集两次快照并比较差值。DBCC SQLPERF('sys.dm_os_wait_stats', CLEAR) 会重置整个服务器的计数器并干扰监控工具；避免使用。解读：PAGEIOLATCH_* = 磁盘读，WRITELOG = 日志磁盘，LCK_M_* = 阻塞，CXPACKET/CXCONSUMER = 并行，RESOURCE_SEMAPHORE = 内存授予，SOS_SCHEDULER_YIELD + 高信号等待 = CPU 压力。只读查询；安全。","note":"按会话查看等待请使用 sys.dm_exec_session_wait_stats（2016+）；在 Query Store 中按查询查看等待类别请使用 sys.query_store_wait_stats（2017+）。","tags":["wait stats","dm_os_wait_stats","waits","pageiolatch","writelog","performance"]},{"id":"deadlock-system-health","task":"从 system_health 会话读取历史死锁（死锁图）","category":"性能","command":"SET QUOTED_IDENTIFIER ON;  -- required for XML methods (sqlcmd/Agent default is OFF)\nWITH dl AS (\n    SELECT CAST(event_data AS XML) AS x\n    FROM   sys.fn_xe_file_target_read_file(N'system_health*.xel', NULL, NULL, NULL)\n    WHERE  object_name = N'xml_deadlock_report'\n)\nSELECT TOP (20)\n       x.value('(event/@timestamp)[1]', 'datetime2')                                          AS utc_time,\n       x.value('(event/data/value/deadlock/victim-list/victimProcess/@id)[1]', 'varchar(50)')  AS victim,\n       x.value('count(event/data/value/deadlock/process-list/process)', 'int')                 AS process_count,\n       x.value('(event/data/value/deadlock/process-list/process/@currentdbname)[1]', 'sysname') AS db,\n       x.query('event/data/value/deadlock')                                                    AS deadlock_graph\nFROM   dl\nORDER BY utc_time DESC;","what":"从 system_health 扩展事件会话（默认开启）的 event_file 目标读取 xml_deadlock_report 事件；对每个死锁返回时间（UTC）、牺牲进程、参与者数量、数据库以及完整的死锁图 XML。","when":"应用报告错误 1205 时、分析死锁频率及涉及的对象时；适用于未启用跟踪标志 1222 的服务器。","caution":"system_health 文件会滚动覆盖；在繁忙的服务器上较早的死锁可能已不存在，如需永久归档请建立专用的 XE 会话。读取大量文件会消耗 CPU；业务时间内保留 TOP。事件写入有延迟，新死锁几秒后才会出现。只读；安全。","note":"点击 deadlock_graph 值并另存为 .xdl，即可在 SSMS 中以图形方式打开。在 2016 SP2 CU12 / 2017 CU20 / 2019 CU3 及以后版本（Standard/Enterprise）上，system_health 的 event_file 默认为 10 x 100 MB；在较旧的版本和其他版本上为 4 x 5 MB。死锁图中 inputbuf、waitresource（KEY/PAGE/OBJECT）和 lockMode 是关键字段；大多数死锁通过补充缺失的索引或统一访问顺序即可解决。","tags":["deadlock","system_health","xml_deadlock_report","extended events","1205","deadlock graph"]},{"id":"xe-long-query-session","task":"创建扩展事件会话：将慢查询捕获到文件并读取","category":"性能","command":"SET QUOTED_IDENTIFIER ON;\nCREATE EVENT SESSION [dba_long_queries] ON SERVER\nADD EVENT sqlserver.sql_statement_completed (\n    ACTION (sqlserver.client_app_name, sqlserver.client_hostname, sqlserver.database_name,\n            sqlserver.session_id, sqlserver.sql_text, sqlserver.username, sqlserver.query_hash)\n    WHERE (duration >= 1000000 AND sqlserver.is_system = 0)),   -- 1 s (microseconds)\nADD EVENT sqlserver.rpc_completed (\n    ACTION (sqlserver.client_app_name, sqlserver.client_hostname, sqlserver.database_name,\n            sqlserver.session_id, sqlserver.sql_text, sqlserver.username, sqlserver.query_hash)\n    WHERE (duration >= 1000000 AND sqlserver.is_system = 0))\nADD TARGET package0.event_file (SET filename = N'D:\\XE\\dba_long_queries.xel',\n                                    max_file_size = 256, max_rollover_files = 4)\nWITH (MAX_MEMORY = 8 MB, EVENT_RETENTION_MODE = ALLOW_SINGLE_EVENT_LOSS,\n      MAX_DISPATCH_LATENCY = 15 SECONDS, STARTUP_STATE = OFF);\n\nALTER EVENT SESSION [dba_long_queries] ON SERVER STATE = START;\n\n-- Read:\nSELECT x.value('(event/@name)[1]', 'sysname')                                AS event_name,\n       x.value('(event/@timestamp)[1]', 'datetime2')                         AS utc_time,\n       x.value('(event/data[@name=\"duration\"]/value)[1]', 'bigint') / 1000   AS duration_ms,\n       x.value('(event/data[@name=\"cpu_time\"]/value)[1]', 'bigint') / 1000   AS cpu_ms,\n       x.value('(event/data[@name=\"logical_reads\"]/value)[1]', 'bigint')     AS logical_reads,\n       x.value('(event/action[@name=\"database_name\"]/value)[1]', 'sysname')  AS db,\n       x.value('(event/action[@name=\"client_app_name\"]/value)[1]', 'nvarchar(256)') AS app,\n       x.value('(event/action[@name=\"sql_text\"]/value)[1]', 'nvarchar(max)') AS sql_text\nFROM  (SELECT CAST(event_data AS XML) AS x\n       FROM sys.fn_xe_file_target_read_file(N'D:\\XE\\dba_long_queries*.xel', NULL, NULL, NULL)) t\nORDER BY duration_ms DESC;\n\n-- When done:\n-- ALTER EVENT SESSION [dba_long_queries] ON SERVER STATE = STOP;\n-- DROP EVENT SESSION [dba_long_queries] ON SERVER;","what":"创建一个轻量级扩展事件会话，把超过 1 秒的语句和 RPC 调用连同应用、主机、用户、数据库和 SQL 文本写入磁盘；用 T-SQL 将 .xel 文件转换为行。","when":"用于间歇性变慢的投诉、需要 Query Store 不显示的客户端信息（应用/主机）时，以及替代 SQL Trace/Profiler。","caution":"阈值过低（如 0）会很快写满文件，并在繁忙服务器上增加开销；在 WHERE 谓词中过滤，而不是事后过滤。大量数据时 sql_text action 开销较大。SQL Server 服务账户必须对该文件夹有写权限。duration 和 cpu_time 以微秒为单位。用完后停止/删除会话；STARTUP_STATE = OFF 时重启后不会自动启动。","note":"要限定到某个数据库，在 WHERE 子句中加上 sqlserver.database_name = N'Musteri'。快速查看可使用 SSMS > Management > Extended Events > Watch Live Data。","tags":["extended events","xe","event session","slow query","sql_statement_completed","event_file"]},{"id":"tempdb-contention","task":"诊断 tempdb 争用和空间使用","category":"性能","command":"-- 1) Current tempdb page latch waits (2:1:1 PFS, 2:1:2 GAM, 2:1:3 SGAM):\nSELECT wt.session_id, wt.wait_type, wt.wait_duration_ms, wt.resource_description\nFROM   sys.dm_os_waiting_tasks wt\nWHERE  wt.wait_type LIKE N'PAGELATCH[_]%'\n  AND  wt.resource_description LIKE N'2:%';\n\n-- 2) Sessions using the most tempdb (MB):\nSELECT TOP (20) su.session_id, s.login_name, s.program_name,\n       (su.user_objects_alloc_page_count - su.user_objects_dealloc_page_count) * 8 / 1024         AS user_obj_mb,\n       (su.internal_objects_alloc_page_count - su.internal_objects_dealloc_page_count) * 8 / 1024 AS internal_obj_mb\nFROM   sys.dm_db_session_space_usage su\n  JOIN sys.dm_exec_sessions s ON s.session_id = su.session_id\nWHERE  s.is_user_process = 1\nORDER BY su.user_objects_alloc_page_count + su.internal_objects_alloc_page_count\n       - su.user_objects_dealloc_page_count - su.internal_objects_dealloc_page_count DESC;\n\n-- 3) Overall breakdown and version store:\nSELECT SUM(unallocated_extent_page_count) * 8 / 1024       AS free_mb,\n       SUM(user_object_reserved_page_count) * 8 / 1024     AS user_obj_mb,\n       SUM(internal_object_reserved_page_count) * 8 / 1024 AS internal_obj_mb,\n       SUM(version_store_reserved_page_count) * 8 / 1024   AS version_store_mb\nFROM   tempdb.sys.dm_db_file_space_usage;\n\nSELECT DB_NAME(database_id) AS db, reserved_page_count * 8 / 1024 AS version_store_mb\nFROM   sys.dm_tran_version_store_space_usage\nORDER BY reserved_page_count DESC;\n\n-- 4) File count / equal sizing and memory-optimized metadata:\nSELECT name, type_desc, size * 8 / 1024 AS size_mb, growth * 8 / 1024 AS growth_mb, is_percent_growth\nFROM   tempdb.sys.database_files;\nSELECT SERVERPROPERTY('IsTempdbMetadataMemoryOptimized') AS tempdb_metadata_memopt;","what":"显示 tempdb 分配页（PFS/GAM/SGAM）和系统表上的 PAGELATCH 等待、使用 tempdb 最多的会话（临时表与排序/哈希溢出）、哪个数据库在撑大版本存储，以及文件配置。","when":"大量临时表负载下 PAGELATCH_UP/EX 等待上升、tempdb 写满（1105/9002）或版本存储增长时。","caution":"页 2:1:1、2:1:2、2:1:3 以及位于 8088 倍数处的 PFS 页表示分配争用：解决办法是使用多个大小相同、增长相同的数据文件（每核一个，最多 8 个，之后每次加 4 个）。对于系统表页上的争用，2019+ 提供内存优化的 tempdb 元数据（需要重启）。若版本存储增长，找出最老的打开快照事务；收缩 tempdb 不是解决办法。只读查询；安全。","note":"内存优化元数据：ALTER SERVER CONFIGURATION SET MEMORY_OPTIMIZED TEMPDB_METADATA = ON; + 重启（2019+）。2022 新增了无闩锁的 GAM/SGAM 更新，大幅减少争用。持有版本存储的事务：sys.dm_tran_active_snapshot_database_transactions ORDER BY elapsed_time_seconds DESC。","tags":["tempdb","pagelatch","pfs","gam","version store","contention"]},{"id":"qs-enable-config","task":"启用 Query Store、应用推荐设置并检查其状态","category":"Query Store","command":"ALTER DATABASE [Musteri] SET QUERY_STORE = ON;\nALTER DATABASE [Musteri] SET QUERY_STORE (\n    OPERATION_MODE = READ_WRITE,\n    QUERY_CAPTURE_MODE = AUTO,\n    MAX_STORAGE_SIZE_MB = 2048,\n    INTERVAL_LENGTH_MINUTES = 15,\n    DATA_FLUSH_INTERVAL_SECONDS = 900,\n    CLEANUP_POLICY = (STALE_QUERY_THRESHOLD_DAYS = 30),\n    SIZE_BASED_CLEANUP_MODE = AUTO,\n    MAX_PLANS_PER_QUERY = 200,\n    WAIT_STATS_CAPTURE_MODE = ON);\n\n-- State: if actual_state dropped to READ_ONLY, check readonly_reason\nUSE [Musteri];\nSELECT actual_state_desc, desired_state_desc, readonly_reason,\n       current_storage_size_mb, max_storage_size_mb,\n       query_capture_mode_desc, size_based_cleanup_mode_desc, wait_stats_capture_mode_desc\nFROM   sys.database_query_store_options;","what":"在数据库中启用 Query Store；开始按时间间隔持久化查询文本、执行计划、运行时和等待统计。选项决定存储上限、清理和捕获模式。","when":"新数据库上线时、版本/兼容级别升级前（用作基线），或怀疑执行计划回归时。","caution":"达到 MAX_STORAGE_SIZE_MB 时，Query Store 会静默切换为 READ_ONLY（readonly_reason = 65536）并停止收集；请监控 current_storage_size_mb。QUERY_CAPTURE_MODE = ALL 在大量即席查询负载下会很快占满存储并增加开销。非常繁忙的 OLTP 系统可能出现 QDS_* 等待。设为 OFF 会保留已收集的数据；CLEAR 会删除数据。","since":"2016","note":"在 2022 上，新建数据库默认以 READ_WRITE 开启 Query Store。2019+ 的 QUERY_CAPTURE_MODE = CUSTOM 可接受阈值（EXECUTION_COUNT、TOTAL_COMPILE_CPU_TIME_MS、TOTAL_EXECUTION_CPU_TIME_MS）。可读辅助副本的 Query Store（ALTER DATABASE ... FOR SECONDARY SET QUERY_STORE = ON）在 2022 上是有限预览，仅通过跟踪标志 12606 启用，不支持用于生产；2025 无需跟踪标志（仍为预览）。","tags":["query store","enable","max_storage_size_mb","query_capture_mode","read_only","qds"]},{"id":"qs-regressed-queries","task":"用 Query Store 查找执行计划回归（同一查询，不同计划）","category":"Query Store","command":"USE [Musteri];\n-- Queries with more than one plan in the last 24 hours, per-plan performance:\nWITH plan_stats AS (\n    SELECT p.query_id, p.plan_id, p.is_forced_plan,\n           SUM(rs.count_executions) AS execs,\n           SUM(rs.avg_duration * rs.count_executions) / SUM(rs.count_executions) / 1000.0     AS avg_ms,\n           SUM(rs.avg_cpu_time * rs.count_executions) / SUM(rs.count_executions) / 1000.0     AS avg_cpu_ms,\n           SUM(rs.avg_logical_io_reads * rs.count_executions) / SUM(rs.count_executions)      AS avg_reads,\n           MAX(rs.last_execution_time) AS last_exec\n    FROM   sys.query_store_plan p\n      JOIN sys.query_store_runtime_stats rs ON rs.plan_id = p.plan_id\n      JOIN sys.query_store_runtime_stats_interval i\n        ON i.runtime_stats_interval_id = rs.runtime_stats_interval_id\n    WHERE  i.start_time >= DATEADD(HOUR, -24, SYSUTCDATETIME())\n    GROUP BY p.query_id, p.plan_id, p.is_forced_plan\n)\nSELECT ps.query_id, ps.plan_id, ps.is_forced_plan, ps.execs,\n       CAST(ps.avg_ms AS DECIMAL(18,2))     AS avg_ms,\n       CAST(ps.avg_cpu_ms AS DECIMAL(18,2)) AS avg_cpu_ms,\n       CAST(ps.avg_reads AS BIGINT)         AS avg_reads,\n       CAST(ps.avg_ms / NULLIF(MIN(ps.avg_ms) OVER (PARTITION BY ps.query_id), 0) AS DECIMAL(18,1)) AS x_slower_than_best,\n       ps.last_exec,\n       OBJECT_NAME(q.object_id)     AS module,\n       LEFT(qt.query_sql_text, 120) AS query_text\nFROM   plan_stats ps\n  JOIN sys.query_store_query q       ON q.query_id = ps.query_id\n  JOIN sys.query_store_query_text qt ON qt.query_text_id = q.query_text_id\nWHERE  ps.query_id IN (SELECT query_id FROM plan_stats GROUP BY query_id HAVING COUNT(*) > 1)\nORDER BY ps.query_id, ps.avg_ms;","what":"找出过去 24 小时内以多个执行计划运行过的查询；并列显示每个计划的执行次数、加权平均持续时间/CPU/读取量，以及比最佳计划慢多少倍。","when":"查询“昨天还快、今天变慢”时，以及统计信息更新、索引变更、兼容级别升级或打补丁之后。","caution":"平均持续时间也取决于数据分布；比较以不同参数值运行的计划时，也要看 avg_reads（参数嗅探）。时间窗口为 UTC。执行次数少的计划结果在统计上不可靠。只读查询；安全。","since":"2016","note":"SSMS > Query Store > Regressed Queries 以图形方式实现相同功能。在兼容级别 160 的 2022 上，参数敏感计划（PSP）优化会为一条语句创建多个变体 query_id；用 sys.query_store_query_variant（parent_query_id）将其映射到父查询。2017+ 的自动优化（FORCE_LAST_GOOD_PLAN）可自动修复此类回退。","tags":["query store","plan regression","regressed queries","parameter sniffing","query_store_runtime_stats","plan"]},{"id":"qs-force-plan-hints","task":"用 Query Store 强制良好计划或附加提示","category":"Query Store","command":"USE [Musteri];\n-- Force the good plan (query_id / plan_id from the regression query):\nEXEC sys.sp_query_store_force_plan @query_id = 42, @plan_id = 7;\n\n-- Forced plans and failure reason:\nSELECT p.query_id, p.plan_id, p.is_forced_plan, p.plan_forcing_type_desc,\n       p.force_failure_count, p.last_force_failure_reason_desc\nFROM   sys.query_store_plan p\nWHERE  p.is_forced_plan = 1;\n\n-- Undo:\nEXEC sys.sp_query_store_unforce_plan @query_id = 42, @plan_id = 7;\n\n-- 2022+: attach / remove a hint without changing code:\nEXEC sys.sp_query_store_set_hints @query_id = 42, @query_hints = N'OPTION (RECOMPILE)';\nSELECT query_id, query_hint_text, last_query_hint_failure_reason_desc\nFROM   sys.query_store_query_hints;\nEXEC sys.sp_query_store_clear_hints @query_id = 42;","what":"为所选查询强制使用特定的 Query Store 执行计划；优化器每次编译都使用该计划。在 2022 上也可以不修改应用代码而附加 RECOMPILE、MAXDOP 或 OPTIMIZE FOR 等提示。","when":"执行计划回归需要在生产环境中紧急修复，且永久修复（索引、查询改写、统计信息）尚未就绪时。","caution":"强制计划可能随数据增长而变差，并不是永久修复；请定期复查。若计划使用的索引被删除/重命名，强制会失败（last_force_failure_reason_desc = NO_INDEX 等），查询将正常编译。强制后，“道义上等价”的计划可能以新的 plan_id 出现；这是预期行为。Query Store 处于 READ_ONLY 时强制仍然有效，但在还原或 AG 故障转移后请验证。","since":"2016","note":"Query Store 提示需要 2022+（Azure SQL 中更早提供）。提示取代计划指南；若提示无法应用，查询将在不带提示的情况下运行，原因显示在 last_query_hint_failure_reason_desc 中。在可读辅助副本上强制计划（@replica_group_id）需要辅助副本的 Query Store，在 2022 上这是通过跟踪标志 12606 启用的有限预览。","tags":["force plan","sp_query_store_force_plan","query store hints","plan forcing","regression","recompile"]},{"id":"missing-index-dmv","task":"从 DMV 读取缺失索引建议（附注意事项）","category":"维护","command":"USE [Musteri];\nSELECT TOP (25)\n       mid.statement                                   AS table_name,\n       mid.equality_columns, mid.inequality_columns, mid.included_columns,\n       migs.user_seeks, migs.user_scans,\n       CAST(migs.avg_total_user_cost AS DECIMAL(18,2)) AS avg_query_cost,\n       migs.avg_user_impact                            AS est_impact_pct,\n       CAST(migs.user_seeks * migs.avg_total_user_cost * migs.avg_user_impact / 100.0 AS DECIMAL(18,0)) AS score,\n       migs.last_user_seek,\n       N'CREATE INDEX [IX_' + OBJECT_NAME(mid.object_id, mid.database_id) + N'_missing_'\n         + CAST(mid.index_handle AS NVARCHAR(10)) + N'] ON ' + mid.statement + N' ('\n         + ISNULL(mid.equality_columns, N'')\n         + CASE WHEN mid.equality_columns IS NOT NULL AND mid.inequality_columns IS NOT NULL THEN N', ' ELSE N'' END\n         + ISNULL(mid.inequality_columns, N'') + N')'\n         + ISNULL(N' INCLUDE (' + mid.included_columns + N')', N'') AS create_stmt_draft\nFROM   sys.dm_db_missing_index_details mid\n  JOIN sys.dm_db_missing_index_groups mig       ON mig.index_handle = mid.index_handle\n  JOIN sys.dm_db_missing_index_group_stats migs ON migs.group_handle = mig.index_group_handle\nWHERE  mid.database_id = DB_ID()\nORDER BY score DESC;\n\n-- 2019+: the query text behind each suggestion:\nSELECT TOP (25) mid.statement AS table_name, misq.group_handle, misq.user_seeks,\n       SUBSTRING(st.text, misq.last_statement_start_offset / 2 + 1, 300) AS statement_text\nFROM   sys.dm_db_missing_index_group_stats_query misq\n  JOIN sys.dm_db_missing_index_groups mig  ON mig.index_group_handle = misq.group_handle\n  JOIN sys.dm_db_missing_index_details mid ON mid.index_handle = mig.index_handle\n  CROSS APPLY sys.dm_exec_sql_text(misq.last_sql_handle) st\nWHERE  mid.database_id = DB_ID()\nORDER BY misq.user_seeks DESC;","what":"根据使用情况、估算成本和影响对优化器在编译时记录的缺失索引建议打分，并生成 CREATE INDEX 语句草稿；在 2019+ 上还显示每条建议背后的查询。","when":"新版本应用发布后、快速扫描读密集查询的索引需求，或作为索引设计评审的初始清单。","caution":"不要盲目采纳建议：优化器会忽略现有索引，经常给出近乎重复（重叠）的建议和过宽的 INCLUDE 列表，并按相等/不等而非选择性排列列。每个新索引都有写入和存储成本；先考虑扩展现有索引。重启、数据库脱机以及表上的索引变更都会重置这些 DMV。只读查询；安全。","note":"创建建议的索引前，先与表上现有索引比较（sp_helpindex 或 sys.index_columns），并结合未使用索引操作指南一起评审。计划 XML 中的 MissingIndex 元素按查询给出相同的信息。","tags":["missing index","missing index dmv","dm_db_missing_index_details","index suggestion","create index","tuning"]},{"id":"unused-indexes","task":"查找未使用/只写索引","category":"维护","command":"USE [Musteri];\nSELECT OBJECT_SCHEMA_NAME(i.object_id) AS schema_name,\n       OBJECT_NAME(i.object_id)        AS table_name,\n       i.name                          AS index_name,\n       ISNULL(us.user_seeks, 0) + ISNULL(us.user_scans, 0) + ISNULL(us.user_lookups, 0) AS reads,\n       ISNULL(us.user_updates, 0)      AS writes,\n       ps.used_mb,\n       us.last_user_seek, us.last_user_scan,\n       N'DROP INDEX ' + QUOTENAME(i.name) + N' ON ' + QUOTENAME(OBJECT_SCHEMA_NAME(i.object_id))\n         + N'.' + QUOTENAME(OBJECT_NAME(i.object_id)) + N';' AS drop_stmt_draft\nFROM   sys.indexes i\n  LEFT JOIN sys.dm_db_index_usage_stats us\n         ON us.database_id = DB_ID() AND us.object_id = i.object_id AND us.index_id = i.index_id\n  CROSS APPLY (SELECT CAST(SUM(p.used_page_count) * 8 / 1024.0 AS DECIMAL(18,1)) AS used_mb\n               FROM sys.dm_db_partition_stats p\n               WHERE p.object_id = i.object_id AND p.index_id = i.index_id) ps\nWHERE  OBJECTPROPERTY(i.object_id, 'IsUserTable') = 1\n  AND  i.type_desc = N'NONCLUSTERED'\n  AND  i.is_primary_key = 0 AND i.is_unique_constraint = 0 AND i.is_unique = 0\n  AND  ISNULL(us.user_seeks, 0) + ISNULL(us.user_scans, 0) + ISNULL(us.user_lookups, 0) = 0\nORDER BY writes DESC, ps.used_mb DESC;\n\n-- Usage counters since:\nSELECT sqlserver_start_time FROM sys.dm_os_sys_info;","what":"列出自重启以来从未被读取（seek/scan/lookup = 0）但仍产生写入成本的非聚集索引及其大小，并生成 DROP INDEX 语句草稿。","when":"写密集表上的 INSERT/UPDATE 变慢、要减少存储和维护（重建、CHECKDB）时间，或做索引合并之前。","caution":"计数器会在重启、数据库脱机/分离以及（在部分较旧的 2012/2014 版本上）索引重建时重置；只被月末/年末报表使用的索引在短时间窗口内看起来“未使用”。删除前至少观察一个完整业务周期；在 AG 中要分别检查可读辅助副本上的使用情况（计数器按副本统计）。唯一索引即使未被读取也在强制约束；已排除在外。只读查询；安全。","note":"删除前先用 ALTER INDEX ... DISABLE 禁用，回退很容易（定义保留；REBUILD 即可恢复）。在 Query Store 或计划缓存中搜索索引名，可确认它是否被提示引用。","tags":["unused index","write-only index","dm_db_index_usage_stats","drop index","write overhead","index"]},{"id":"stats-health","task":"统计信息健康状况：何时更新、变更了多少行","category":"维护","command":"USE [Musteri];\nSELECT OBJECT_SCHEMA_NAME(s.object_id) AS schema_name,\n       OBJECT_NAME(s.object_id)        AS table_name,\n       s.name                          AS stats_name,\n       s.auto_created, s.has_filter, s.no_recompute,\n       sp.last_updated,\n       sp.rows, sp.rows_sampled,\n       CAST(100.0 * sp.rows_sampled / NULLIF(sp.rows, 0) AS DECIMAL(5,1))         AS sample_pct,\n       sp.modification_counter,\n       CAST(100.0 * sp.modification_counter / NULLIF(sp.rows, 0) AS DECIMAL(9,1)) AS modified_pct,\n       CAST(CASE WHEN 500 + 0.20 * sp.rows < SQRT(1000.0 * sp.rows) THEN 500 + 0.20 * sp.rows\n                 ELSE SQRT(1000.0 * sp.rows) END AS BIGINT)                       AS auto_update_threshold,\n       sp.persisted_sample_percent\nFROM   sys.stats s\n  CROSS APPLY sys.dm_db_stats_properties(s.object_id, s.stats_id) sp\nWHERE  OBJECTPROPERTY(s.object_id, 'IsUserTable') = 1\nORDER BY sp.modification_counter DESC;\n\nSELECT name, is_auto_create_stats_on, is_auto_update_stats_on, is_auto_update_stats_async_on\nFROM   sys.databases\nWHERE  name = DB_NAME();","what":"为每个统计信息显示上次更新时间、采样率、自上次更新以来修改的行数，以及触发自动更新的阈值（兼容级别 130+ 为动态阈值）。","when":"用于估算行数与实际行数差距很大的执行计划、大量数据加载之后，或检查统计信息维护作业是否有效时。","caution":"该阈值公式适用于兼容级别 130+（MIN(500 + 20%, SQRT(1000 x rows))）；较低的兼容级别使用 20% + 500 规则。从未更新过的统计信息（空表）返回 NULL。sample_pct 较低的倾斜列可能需要 FULLSCAN 或 PERSIST_SAMPLE_PERCENT。只读查询；安全。","since":"2016","note":"persisted_sample_percent 反映 UPDATE STATISTICS ... WITH PERSIST_SAMPLE_PERCENT = ON（2016 SP1 CU4 / 2017 CU1+）。更新命令请参见 update-statistics 操作指南。","tags":["statistics","stats health","dm_db_stats_properties","modification_counter","auto update","cardinality"]},{"id":"checkdb-strategy","task":"DBCC CHECKDB 策略：上次成功检查、拆分工作、解读结果","category":"维护","command":"-- Last successful CHECKDB (1900-01-01 = never):\nSELECT name, DATABASEPROPERTYEX(name, 'LastGoodCheckDbTime') AS last_good_checkdb\nFROM   sys.databases\nWHERE  state_desc = N'ONLINE' AND name <> N'tempdb'\nORDER BY last_good_checkdb;\n\n-- Estimated tempdb requirement:\nDBCC CHECKDB (N'Musteri') WITH ESTIMATEONLY;\n\n-- Split a large DB across days: allocation + catalog + table by table CHECKTABLE\nUSE [Musteri];\nDBCC CHECKALLOC (N'Musteri') WITH NO_INFOMSGS;\nDBCC CHECKCATALOG (N'Musteri') WITH NO_INFOMSGS;\nDBCC CHECKTABLE (N'dbo.Siparis') WITH NO_INFOMSGS, ALL_ERRORMSGS;\n\n-- Get results as rows (error rows with object and page):\nDBCC CHECKDB (N'Musteri') WITH TABLERESULTS, NO_INFOMSGS, ALL_ERRORMSGS, MAXDOP = 4;","what":"显示每个数据库上次成功执行 CHECKDB 的时间，估算所需的 tempdb，允许将大型数据库的检查拆分为 CHECKALLOC + CHECKCATALOG + CHECKTABLE 几部分，并以行（对象、索引、页）形式返回结果。","when":"审计维护工作时、超大数据库无法在维护窗口内完成时，或在 823/824 之后确定损坏范围时。","caution":"结果中出现“repair_allow_data_loss is the minimum repair level”表示修复会删除数据；先考虑从完好的备份还原（或页还原）。若只有非聚集索引（index_id > 1）受影响，删除并重建该索引即可无数据丢失地修复。发现损坏后要调查磁盘子系统。PHYSICAL_ONLY 会跳过逻辑检查；仍需定期执行完整 CHECKDB。用 CHECKTABLE 拆分时，若不同时运行 CHECKALLOC/CHECKCATALOG，覆盖是不完整的。","note":"DATABASEPROPERTYEX(..., 'LastGoodCheckDbTime') 在 2016 SP2、2017 CU9 和 2019+ 上可用；在较旧版本上从 DBCC DBINFO WITH TABLERESULTS 读取 dbi_dbccLastKnownGood。为分担负载，在从备份还原的副本上（如测试服务器上）运行 CHECKDB 同样可以验证生产数据库。MAXDOP 选项需要 2014 SP2 / 2016+。","tags":["dbcc checkdb","lastgoodcheckdbtime","checktable","corruption","repair_allow_data_loss","integrity"]},{"id":"resource-governor","task":"用 Resource Governor 限制报表负载（资源池、工作负载组、分类器）","category":"配置","command":"USE master;\n-- 1) Pool and workload group:\nCREATE RESOURCE POOL [pool_report]\nWITH (MIN_CPU_PERCENT = 0, MAX_CPU_PERCENT = 30, CAP_CPU_PERCENT = 40,\n      MIN_MEMORY_PERCENT = 0, MAX_MEMORY_PERCENT = 25);\nCREATE WORKLOAD GROUP [wg_report]\nWITH (IMPORTANCE = LOW, REQUEST_MAX_MEMORY_GRANT_PERCENT = 15,\n      MAX_DOP = 2, REQUEST_MAX_CPU_TIME_SEC = 600)\nUSING [pool_report];\nGO\n-- 2) Classifier function (in master, SCHEMABINDING required):\nCREATE FUNCTION dbo.fn_rg_classifier()\nRETURNS sysname\nWITH SCHEMABINDING\nAS\nBEGIN\n    DECLARE @grp sysname = N'default';\n    IF SUSER_SNAME() = N'rapor_user' OR APP_NAME() LIKE N'Power BI%'\n        SET @grp = N'wg_report';\n    RETURN @grp;\nEND;\nGO\n-- 3) Enable:\nALTER RESOURCE GOVERNOR WITH (CLASSIFIER_FUNCTION = dbo.fn_rg_classifier);\nALTER RESOURCE GOVERNOR RECONFIGURE;\n\n-- 4) Which session landed in which group:\nSELECT s.session_id, s.login_name, s.program_name, wg.name AS workload_group, rp.name AS pool\nFROM   sys.dm_exec_sessions s\n  JOIN sys.dm_resource_governor_workload_groups wg ON wg.group_id = s.group_id\n  JOIN sys.dm_resource_governor_resource_pools rp  ON rp.pool_id = wg.pool_id\nWHERE  s.is_user_process = 1;\n\n-- Rollback:\n-- ALTER RESOURCE GOVERNOR WITH (CLASSIFIER_FUNCTION = NULL); ALTER RESOURCE GOVERNOR DISABLE;","what":"在连接时将来自特定登录名或应用的会话路由到单独的工作负载组；通过限制该组的 CPU、内存授予、DOP 和每请求 CPU 时间来保护 OLTP 负载。","when":"报表/BI 查询挤占 OLTP、ETL 与业务时间负载重叠，或多租户服务器需要资源共享时。","caution":"分类器函数在每个新连接上运行；函数缓慢或出错会拖慢或中断每次登录（函数中不要有查询或表访问）。若出现异常，通过 DAC 连接并设置 CLASSIFIER_FUNCTION = NULL。分类只在连接时进行；现有会话不受影响。CAP_CPU_PERCENT 是硬上限，即使 CPU 空闲也会生效。MAX_CPU_PERCENT 仅在 CPU 争用时生效。Resource Governor 是 Enterprise/Developer 功能。","note":"超过 REQUEST_MAX_CPU_TIME_SEC 默认只会触发一个事件（cpu_threshold_exceeded）；启用跟踪标志 2422（2016 SP2 / 2017 CU3+）后请求会被中止。I/O 限制请在资源池上使用 MAX_IOPS_PER_VOLUME（2014+）。统计信息：sys.dm_resource_governor_resource_pools / _workload_groups。","tags":["resource governor","resource pool","workload group","classifier","cpu limit","resource management"]},{"id":"tde-enable","task":"配置 TDE：主密钥、证书、证书备份和加密进度","category":"安全","command":"USE master;\nIF NOT EXISTS (SELECT 1 FROM sys.symmetric_keys WHERE name = N'##MS_DatabaseMasterKey##')\n    CREATE MASTER KEY ENCRYPTION BY PASSWORD = N'<GucluParola_1>';\nCREATE CERTIFICATE [TDE_Cert_2026]\n    WITH SUBJECT = N'TDE certificate', EXPIRY_DATE = '20361231';\n\n-- Back it up NOW: without cert + private key the backups cannot be restored anywhere\nBACKUP CERTIFICATE [TDE_Cert_2026]\n    TO FILE = N'D:\\Keys\\TDE_Cert_2026.cer'\n    WITH PRIVATE KEY (FILE = N'D:\\Keys\\TDE_Cert_2026.pvk',\n                      ENCRYPTION BY PASSWORD = N'<GucluParola_2>');\n\nUSE [Musteri];\nCREATE DATABASE ENCRYPTION KEY\n    WITH ALGORITHM = AES_256\n    ENCRYPTION BY SERVER CERTIFICATE [TDE_Cert_2026];\nALTER DATABASE [Musteri] SET ENCRYPTION ON;\n\n-- Progress and certificate backup date:\nSELECT DB_NAME(dek.database_id) AS db, dek.encryption_state_desc, dek.percent_complete,\n       dek.encryption_scan_state_desc, c.name AS cert_name, c.expiry_date, c.pvt_key_last_backup_date\nFROM   sys.dm_database_encryption_keys dek\n  LEFT JOIN master.sys.certificates c ON c.thumbprint = dek.encryptor_thumbprint;\n\n-- On the target server (before restore / AG secondary):\n-- CREATE CERTIFICATE [TDE_Cert_2026] FROM FILE = N'D:\\Keys\\TDE_Cert_2026.cer'\n--   WITH PRIVATE KEY (FILE = N'D:\\Keys\\TDE_Cert_2026.pvk', DECRYPTION BY PASSWORD = N'<GucluParola_2>');","what":"在 master 中创建数据库主密钥和证书，连同私钥备份证书，创建 AES-256 数据库加密密钥，并对静态数据文件和日志文件加密；显示后台加密扫描的进度。","when":"用于 GDPR/PCI 等静态数据加密要求，以及防范备份文件和 MDF/LDF 文件被盗。","caution":"若证书 + 私钥备份或其密码丢失，TDE 数据库及其所有备份将永久无法读取；把备份存放在远离服务器的安全保管库中。在任何数据库上启用 TDE 都会同时加密 tempdb（全服务器范围的少量 CPU 开销）。加密扫描 I/O 很重；在 2019+ 上可用 ALTER DATABASE ... SET ENCRYPTION SUSPEND/RESUME 暂停。对于 AG/日志传送，必须先在每台目标服务器上安装证书。TDE 不加密内存中或网络上的数据。","note":"证书过期不会停止现有 TDE，但轮换时应创建新证书并用 ALTER DATABASE ENCRYPTION KEY ENCRYPTION BY SERVER CERTIFICATE [new] 重新加密（只重新加密 DEK，不重新加密数据）。encryption_state_desc 和 encryption_scan_state_desc 是 2019+ 的列；在较旧版本上使用 encryption_state（3 = 已加密，2 = 进行中）。自 2019 起 Standard 版本也支持 TDE。","tags":["tde","transparent data encryption","certificate","backup certificate","encryption","dek"]},{"id":"sql-server-audit","task":"配置 SQL Server Audit：登录、权限变更和表访问","category":"安全","command":"USE master;\nCREATE SERVER AUDIT [Audit_DBA]\nTO FILE (FILEPATH = N'D:\\Audit\\', MAXSIZE = 256 MB, MAX_ROLLOVER_FILES = 20, RESERVE_DISK_SPACE = OFF)\nWITH (QUEUE_DELAY = 1000, ON_FAILURE = CONTINUE);\nALTER SERVER AUDIT [Audit_DBA] WITH (STATE = ON);\n\n-- Server-wide (covers all databases):\nCREATE SERVER AUDIT SPECIFICATION [AuditSpec_Server]\nFOR SERVER AUDIT [Audit_DBA]\n    ADD (FAILED_LOGIN_GROUP),\n    ADD (SERVER_ROLE_MEMBER_CHANGE_GROUP),\n    ADD (DATABASE_ROLE_MEMBER_CHANGE_GROUP),\n    ADD (SERVER_PRINCIPAL_CHANGE_GROUP),\n    ADD (DATABASE_PRINCIPAL_CHANGE_GROUP),\n    ADD (SERVER_PERMISSION_CHANGE_GROUP),\n    ADD (DATABASE_PERMISSION_CHANGE_GROUP),\n    ADD (AUDIT_CHANGE_GROUP)\nWITH (STATE = ON);\n\n-- Database: access to a sensitive table:\nUSE [Musteri];\nCREATE DATABASE AUDIT SPECIFICATION [AuditSpec_Musteri]\nFOR SERVER AUDIT [Audit_DBA]\n    ADD (SELECT, INSERT, UPDATE, DELETE ON OBJECT::[dbo].[Siparis] BY [public])\nWITH (STATE = ON);\n\n-- Read:\nSELECT event_time, action_id, succeeded, server_principal_name, database_name,\n       schema_name, object_name, LEFT(statement, 200) AS stmt, client_ip, application_name\nFROM   sys.fn_get_audit_file(N'D:\\Audit\\Audit_DBA_*.sqlaudit', DEFAULT, DEFAULT)\nWHERE  event_time >= DATEADD(HOUR, -24, SYSUTCDATETIME())\nORDER BY event_time DESC;","what":"创建写入文件的服务器审核；在服务器范围记录登录失败、角色成员身份、主体和权限变更，并在数据库级别记录对所选表的 SELECT/DML 访问，再用 T-SQL 读取记录。","when":"用于审计/合规要求（GDPR、PCI、SOX）、“谁读取/修改了这张表”之类的问题，或怀疑权限提升时。","caution":"对繁忙表做 SELECT 审核会极快地产生记录并影响性能；按对象和主体缩小范围。ON_FAILURE = SHUTDOWN 会在审核无法写入时停止服务器；仅在强制要求时使用。审核文件夹必须可被 SQL Server 服务账户写入，且非 DBA 无法删除。event_time 为 UTC。数据库审核规范会随数据库一起还原，但若服务器审核 GUID 不匹配就会成为孤立状态。","note":"自 2016 SP1 起 Standard 版本也支持数据库审核规范。务必包含 AUDIT_CHANGE_GROUP，以便记录对审核本身的更改（包括关闭审核）。","tags":["sql server audit","server audit","audit specification","fn_get_audit_file","auditing","compliance"]},{"id":"orphaned-users-fix","task":"查找孤立用户并重新映射到登录名（还原/AG 之后）","category":"安全","command":"USE [Musteri];\n-- Database users without a matching login:\nSELECT dp.name AS user_name, dp.type_desc, dp.authentication_type_desc, dp.sid, dp.create_date\nFROM   sys.database_principals dp\n  LEFT JOIN sys.server_principals sp ON sp.sid = dp.sid\nWHERE  sp.sid IS NULL\n  AND  dp.type IN ('S', 'U', 'G')\n  AND  dp.authentication_type_desc IN (N'INSTANCE', N'WINDOWS')\n  AND  dp.principal_id > 4;\n\n-- If a login with the same name exists, remap the SID:\nALTER USER [app_user] WITH LOGIN = [app_user];\n\n-- Permanent fix: generate CREATE LOGIN with SID + password hash on the source, run it on the target:\nSELECT N'CREATE LOGIN ' + QUOTENAME(sp.name)\n     + N' WITH PASSWORD = ' + CONVERT(NVARCHAR(MAX), LOGINPROPERTY(sp.name, 'PasswordHash'), 1)\n     + N' HASHED, SID = ' + CONVERT(NVARCHAR(MAX), sp.sid, 1)\n     + N', CHECK_POLICY = OFF;' AS create_login_stmt\nFROM   sys.server_principals sp\nWHERE  sp.type = 'S' AND sp.name = N'app_user';","what":"找出 SID 与任何服务器登录名都不匹配的数据库用户（孤立用户）；将用户重新映射到现有登录名，并生成脚本以相同 SID 和密码哈希在另一台服务器上重建登录名。","when":"在另一台服务器上还原/附加数据库后、AG/日志传送故障转移后应用收到“Login failed for user”时，或服务器迁移期间。","caution":"ALTER USER ... WITH LOGIN 会把用户的 SID 改为登录名的 SID；在 AG 中此更改从主副本流向辅助副本，但若辅助服务器上登录名的 SID 不同，下次故障转移时问题会重现。永久解决办法是在每个副本上用相同 SID 创建登录名。密码哈希是敏感信息；请安全保存脚本。包含数据库用户（authentication_type = DATABASE）不需要登录名，不会被列出。","note":"sp_change_users_login 已弃用；请使用 ALTER USER WITH LOGIN。对于孤立的 dbo（数据库所有者登录名缺失），使用 ALTER AUTHORIZATION ON DATABASE::[Musteri] TO [sa];。批量迁移登录名请使用 PowerShell dbatools：Copy-DbaLogin -Source SQL01 -Destination SQL02。","tags":["orphaned user","alter user with login","sid","login failed","after restore","sp_change_users_login"]},{"id":"permission-audit","task":"登录名与权限审计：sysadmin 成员、高风险权限、弱密码","category":"安全","command":"-- 1) Server role memberships:\nSELECT r.name AS server_role, m.name AS member, m.type_desc, m.is_disabled, m.create_date\nFROM   sys.server_role_members rm\n  JOIN sys.server_principals r ON r.principal_id = rm.role_principal_id\n  JOIN sys.server_principals m ON m.principal_id = rm.member_principal_id\nORDER BY r.name, m.name;\n\n-- 2) Risky server permissions:\nSELECT pr.name AS grantee, pr.type_desc, pe.permission_name, pe.state_desc\nFROM   sys.server_permissions pe\n  JOIN sys.server_principals pr ON pr.principal_id = pe.grantee_principal_id\nWHERE  pe.permission_name IN (N'CONTROL SERVER', N'IMPERSONATE ANY LOGIN', N'ALTER ANY LOGIN',\n                              N'ALTER ANY SERVER ROLE', N'ALTER SETTINGS', N'IMPERSONATE')\n   OR  pe.state_desc = N'GRANT_WITH_GRANT_OPTION';\n\n-- 3) SQL login health (policy, lockout, password = login name):\nSELECT sl.name, sl.is_disabled, sl.is_policy_checked, sl.is_expiration_checked,\n       LOGINPROPERTY(sl.name, 'PasswordLastSetTime') AS pwd_last_set,\n       LOGINPROPERTY(sl.name, 'IsLocked')            AS is_locked,\n       LOGINPROPERTY(sl.name, 'BadPasswordCount')    AS bad_pwd_count,\n       PWDCOMPARE(sl.name, sl.password_hash)         AS pwd_equals_name,\n       PWDCOMPARE(N'', sl.password_hash)             AS pwd_empty\nFROM   sys.sql_logins sl;\n\n-- 4) Database role memberships and explicit object permissions:\nUSE [Musteri];\nSELECT r.name AS db_role, m.name AS member, m.type_desc\nFROM   sys.database_role_members rm\n  JOIN sys.database_principals r ON r.principal_id = rm.role_principal_id\n  JOIN sys.database_principals m ON m.principal_id = rm.member_principal_id\nWHERE  m.name <> N'dbo'\nORDER BY r.name, m.name;\n\nSELECT pr.name AS grantee, pe.class_desc, pe.permission_name, pe.state_desc,\n       OBJECT_SCHEMA_NAME(pe.major_id) AS schema_name,\n       CASE pe.class WHEN 1 THEN OBJECT_NAME(pe.major_id) WHEN 3 THEN SCHEMA_NAME(pe.major_id) END AS object_name\nFROM   sys.database_permissions pe\n  JOIN sys.database_principals pr ON pr.principal_id = pe.grantee_principal_id\nWHERE  (pr.name NOT IN (N'public', N'dbo') AND NOT (pe.class = 0 AND pe.permission_name = N'CONNECT'))\n   OR  (pr.name = N'public' AND OBJECT_SCHEMA_NAME(pe.major_id) <> N'sys')\nORDER BY pr.name, pe.class_desc;","what":"列出服务器角色成员身份、CONTROL SERVER/IMPERSONATE 等高风险权限，以及关闭密码策略、已锁定、密码与登录名相同或为空的 SQL 登录名；在数据库中列出角色成员身份和显式的非系统对象权限。","when":"用于定期访问评审、审计之前、有人离开团队之后，或怀疑发生安全事件时。","caution":"通过 Windows 组授予的权限显示在组名下；要查看具体人员，使用 EXEC xp_logininfo N'DOMAIN\\Group', 'members';。撤销任何权限前先确认应用依赖。PWDCOMPARE 仅适用于 SQL 登录名，且需要 CONTROL SERVER。只读查询；安全。","note":"查看主体的有效权限：EXECUTE AS USER = N'app_user'; SELECT * FROM fn_my_permissions(NULL, 'DATABASE'); REVERT;。在 2022 上，新的细粒度角色（##MS_ServerStateReader##、##MS_DefinitionReader## 等）无需 sysadmin 即可授予监控权限。","tags":["permission audit","sysadmin","server_role_members","database_permissions","pwdcompare","security"]},{"id":"ledger-tables","task":"创建账本表、读取其变更历史并验证完整性","category":"安全","command":"USE [Musteri];\n-- Ledger verification requires snapshot isolation:\nALTER DATABASE [Musteri] SET ALLOW_SNAPSHOT_ISOLATION ON;\n\n-- Updatable ledger table (history table maintained automatically):\nCREATE TABLE dbo.Hesap (\n    HesapId int           NOT NULL PRIMARY KEY,\n    Bakiye  decimal(18,2) NOT NULL\n)\nWITH (SYSTEM_VERSIONING = ON (HISTORY_TABLE = dbo.Hesap_History), LEDGER = ON);\n\n-- Append-only ledger (UPDATE/DELETE are blocked):\nCREATE TABLE dbo.IslemLog (Id bigint IDENTITY, Mesaj nvarchar(400))\nWITH (LEDGER = ON (APPEND_ONLY = ON));\n\n-- Who changed what, and when:\nSELECT t.commit_time, t.principal_name, l.HesapId, l.Bakiye, l.ledger_operation_type_desc\nFROM   dbo.Hesap_Ledger l\n  JOIN sys.database_ledger_transactions t ON t.transaction_id = l.ledger_transaction_id\nORDER BY t.commit_time DESC;\n\n-- Take a digest (store it somewhere trusted outside) and verify:\nEXEC sys.sp_generate_database_ledger_digest;\nEXEC sys.sp_verify_database_ledger\n     N'{\"database_name\":\"Musteri\",\"block_id\":0,\"hash\":\"0x...\",\"last_transaction_commit_time\":\"...\",\"digest_time\":\"...\"}';","what":"创建一张账本表，以加密哈希链记录行变更；_Ledger 视图显示每次 INSERT/UPDATE/DELETE 由谁在何时执行，对照已存储的摘要进行验证可证明数据事后未被篡改（包括被 DBA 篡改）。","when":"用于不可篡改性必须可证明的数据：财务记录、审计跟踪、合同/审批记录。","caution":"表创建后无法关闭账本；账本表不能转换为普通表，被删除的账本表会作为已删除的账本表保留。可更新账本表每次变更都会写一行历史，存储会增长。除非摘要存放在数据库之外的不可变存储（WORM、Azure 不可变 blob）中，否则验证不具备证据价值。未启用快照隔离时，sp_verify_database_ledger 会以错误 37498 失败。","since":"2022","note":"sp_generate_database_ledger_digest 返回 JSON；sp_verify_database_ledger 接受单个摘要或 JSON 数组，并可通过 @table_name 验证单张表。自动存储摘要：ALTER DATABASE SCOPED CONFIGURATION SET LEDGER_DIGEST_STORAGE_ENDPOINT = '<blob url>'。CREATE DATABASE ... WITH LEDGER = ON 会使每张表默认成为账本表。","tags":["ledger","append-only","sp_verify_database_ledger","digest","immutable","tamper-evident"]},{"id":"agent-job-failures","task":"SQL Agent 作业失败历史和上次运行状态","category":"SQL Agent","command":"-- Job steps that failed / were cancelled in the last 24 hours:\nSELECT j.name AS job_name, h.step_id, h.step_name,\n       msdb.dbo.agent_datetime(h.run_date, h.run_time) AS run_at,\n       STUFF(STUFF(RIGHT('000000' + CAST(h.run_duration AS VARCHAR(6)), 6), 3, 0, ':'), 6, 0, ':') AS duration_hhmmss,\n       CASE h.run_status WHEN 0 THEN 'Failed' WHEN 1 THEN 'Succeeded' WHEN 2 THEN 'Retry'\n                         WHEN 3 THEN 'Canceled' WHEN 4 THEN 'In progress' END AS status,\n       h.sql_message_id, h.sql_severity,\n       LEFT(h.message, 500) AS message\nFROM   msdb.dbo.sysjobhistory h\n  JOIN msdb.dbo.sysjobs j ON j.job_id = h.job_id\nWHERE  h.run_status IN (0, 3)\n  AND  msdb.dbo.agent_datetime(h.run_date, h.run_time) >= DATEADD(HOUR, -24, GETDATE())\nORDER BY run_at DESC;\n\n-- Each job's last outcome, running now, next run:\nSELECT j.name, j.enabled,\n       CASE lr.run_status WHEN 0 THEN 'Failed' WHEN 1 THEN 'Succeeded' WHEN 3 THEN 'Canceled' END AS last_outcome,\n       lr.run_at AS last_run_at,\n       CASE WHEN a.start_execution_date IS NOT NULL AND a.stop_execution_date IS NULL THEN 'RUNNING' END AS running_now,\n       a.next_scheduled_run_date\nFROM   msdb.dbo.sysjobs j\n  LEFT JOIN msdb.dbo.sysjobactivity a\n         ON a.job_id = j.job_id AND a.session_id = (SELECT MAX(session_id) FROM msdb.dbo.syssessions)\n  OUTER APPLY (SELECT TOP (1) h.run_status, msdb.dbo.agent_datetime(h.run_date, h.run_time) AS run_at\n               FROM msdb.dbo.sysjobhistory h\n               WHERE h.job_id = j.job_id AND h.step_id = 0\n               ORDER BY h.instance_id DESC) lr\nORDER BY j.name;\n\n-- Is the Agent service running:\nSELECT servicename, status_desc, startup_type_desc FROM sys.dm_server_services;","what":"显示过去 24 小时内失败或被取消的作业步骤及其错误消息；每个作业的上次结果、当前是否在运行及下次计划运行时间；以及 Agent 服务状态。","when":"用于晨检、备份/维护作业告警、确认“夜间作业是否运行了”，或故障转移后验证作业在正确的副本上运行。","caution":"msdb 中的作业历史有上限（默认总共 1,000 行，每个作业 100 行）；频繁运行的作业会很快挤掉旧的失败记录，请在 Agent 属性中调高上限。message 列截断在 4,000 个字符；输出较长时请为作业步骤定义输出文件。若 Agent 服务停止，计划作业都不会运行，也不会有任何作业报错。只读查询；安全。","note":"msdb.dbo.agent_datetime 未公开文档但被广泛使用。在 AG 中，作业必须存在于每个副本上，并在第一步用 sys.fn_hadr_is_primary_replica(N'Musteri') 检查角色。dbatools：Get-DbaAgentJobHistory -SqlInstance SQL01 -StartDate (Get-Date).AddDays(-1) -OutcomeType Failed。","tags":["sql agent","job history","sysjobhistory","job failure","msdb","scheduled job"]}]},"recipes/mongodb.json":{"engineId":"mongodb","engine":"MongoDB","order":5,"recipes":[{"id":"rs-add-member","task":"向副本集添加成员","category":"副本集","command":"rs.add({ host: \"mongo2:27017\", priority: 1, votes: 1 })","what":"向现有副本集添加新成员（secondary）并开始初始同步。","when":"添加新节点以提升读取能力或增加冗余时。","caution":"初始同步期间新成员从 primary 拉取数据；数据集较大时会产生网络和磁盘负载。","tags":["replica","rs.add","member","member","add"]},{"id":"rs-remove-member","task":"从副本集移除成员","category":"副本集","command":"rs.remove(\"mongo2:27017\")","what":"从副本集配置中移除指定主机。","when":"节点退役或更换硬件时。","caution":"不要移除 primary；以破坏多数派的方式移除成员会导致副本集无法读写。","tags":["replica","rs.remove","member","member","remove"]},{"id":"rs-step-down","task":"让 primary 降级（stepDown）","category":"副本集","command":"rs.stepDown(60)","what":"将当前 primary 降级为 secondary 并持续指定的秒数，同时触发新的选举。","when":"计划内维护前把 primary 角色交给另一节点。","caution":"stepDown 期间会有短暂的写入中断；应用必须重新连接。","tags":["replica","stepdown","primary","failover","step down"]},{"id":"rs-set-priority","task":"更改成员优先级","category":"副本集","command":"cfg = rs.conf(); cfg.members[1].priority = 2; rs.reconfig(cfg)","what":"更改指定成员在选举中成为 primary 的优先级。","when":"希望某个特定节点成为首选 primary 时。","caution":"reconfig 可能触发选举，导致短暂的 primary 变更/写入中断。","tags":["replica","priority","reconfig","priority","election"]},{"id":"rs-status","task":"查看副本集状态","category":"副本集","command":"rs.status()","what":"显示副本集成员的状态、角色、复制延迟和健康状况。","when":"检查复制健康状况和延迟时。","caution":"只读命令；无风险。","tags":["replica","status","status","health","rs.status"]},{"id":"rs-hidden-member","task":"配置隐藏/无投票权成员","category":"副本集","command":"cfg = rs.conf(); cfg.members[2].hidden = true; cfg.members[2].priority = 0; cfg.members[2].votes = 0; rs.reconfig(cfg)","what":"将成员设为隐藏（对应用不可见）、无投票权且优先级为 0，仅用于备份/分析。","when":"希望隔离备份或分析查询时。","caution":"隐藏成员的优先级必须为 0；否则 reconfig 会失败。","tags":["replica","hidden","votes","hidden","non-voting"]},{"id":"rs-resync","task":"从头重新同步成员","category":"副本集","command":"// On the SECONDARY to be resynced (NEVER on the PRIMARY):\ndb.getSiblingDB(\"admin\").shutdownServer()\n// At the OS level: empty the dbPath contents (or move them elsewhere as a backup)\n// and restart mongod with the same configuration; the member starts an initial sync automatically.\n// Monitor progress (stateStr: STARTUP2 -> SECONDARY):\nrs.status()","what":"MongoDB 没有单独的 resync 命令；停止 secondary、清空其数据目录并重启，即可从 primary（或同步源）开始新的初始同步。","when":"成员落后过多以至超出 oplog 窗口（RECOVERING / too stale）或其数据不一致时。","caution":"该成员上的所有本地数据都会被删除并重新拉取；只在 secondary 上执行，并确保多数派保持在线。同步时间以及网络/磁盘负载都很高；对于超大数据集，从健康成员的文件系统快照做种更快。","tags":["replica","resync","initialsync","sync","resync"]},{"id":"sh-add-shard","task":"向集群添加分片","category":"分片","command":"sh.addShard(\"shardRS/mongoShard1:27018\")","what":"向分片集群添加新分片（副本集）。","when":"横向扩展存储或吞吐能力时。","caution":"添加分片后，balancer 开始迁移 chunk；这会产生额外的 IO 负载。","tags":["sharding","addshard","shard","add","cluster"]},{"id":"sh-shard-collection","task":"对集合分片","category":"分片","command":"sh.shardCollection(\"shop.customer\", { customerId: \"hashed\" })","what":"按给定的分片键对指定集合分片。","when":"希望把不断增长的集合分布到多个分片上时。","caution":"在 5.0+ 中可用 reshardCollection 更改分片键（在 4.4+ 中可用 refineCollectionShardKey 扩展），但由于要重新分布全部数据，开销很大；分片键选择不当会导致分布不均。","tags":["sharding","shardcollection","key","customer","shard key"]},{"id":"sh-start-balancer","task":"启动 balancer","category":"分片","command":"sh.startBalancer()","what":"启用负责分布 chunk 的 balancer 进程。","when":"维护结束后希望重新启用均衡时。","caution":"balancer 迁移 chunk 时会产生 IO 和网络负载；请在高峰时段之外定义均衡窗口。","tags":["sharding","balancer","start","start","rebalance"]},{"id":"sh-stop-balancer","task":"停止 balancer","category":"分片","command":"sh.stopBalancer()","what":"禁用执行 chunk 迁移的 balancer。","when":"在备份、维护或批量数据加载前停止迁移。","caution":"长时间关闭会导致分片不均衡；事后别忘了重新启用。","tags":["sharding","balancer","stop","stop","maintenance"]},{"id":"sh-status","task":"查看分片状态","category":"分片","command":"sh.status()","what":"汇总分片、数据库、已分片集合和 chunk 分布。","when":"检查集群的分布状态和 balancer 健康状况时。","caution":"只读命令；无风险。","tags":["sharding","status","status","chunk","sh.status"]},{"id":"sh-add-zone","task":"添加 zone（标签）并分配范围","category":"分片","command":"sh.addShardToZone(\"shard0000\", \"TR\"); sh.updateZoneKeyRange(\"shop.customer\", { region: \"TR\" }, { region: \"TR~\" }, \"TR\")","what":"将分片分配到一个 zone，并把特定键范围绑定到该 zone（zone 分片）。","when":"希望按地理位置或硬件把数据固定在特定分片上时。","caution":"zone 范围必须与分片键匹配；范围错误会导致 chunk 无法迁移。","tags":["sharding","zone","tag","region","range"]},{"id":"sh-move-chunk","task":"手动移动 chunk","category":"分片","command":"sh.moveChunk(\"shop.customer\", { customerId: 1000 }, \"shard0001\")","what":"把包含指定键的 chunk 手动移动到目标分片。","when":"用于纠正分布不均或安置特定数据。","caution":"手动移动可能与 balancer 冲突；大 chunk 的迁移会产生 IO 负载和短暂锁定。","tags":["sharding","movechunk","chunk","move","manual"]},{"id":"compact-collection","task":"压缩集合（compact）","category":"维护","command":"db.runCommand({ compact: \"customer\" })","what":"在磁盘上重写集合及其索引，回收空闲空间。","when":"大量删除/更新后希望回收磁盘空间时。","caution":"在 4.4+ 中 compact 不阻塞读写；只阻塞同一集合上的某些 DDL 操作（drop、createIndex、dropIndex 等）；它仍会产生大量 IO，且不一定把空间归还给操作系统。尽可能在 secondary 上、在维护窗口中运行。","tags":["compact","storage","maintenance","disk","storage"]},{"id":"create-index-background","task":"创建索引（非阻塞）","category":"维护","command":"db.customer.createIndex({ email: 1 }, { name: \"idx_email\" })","what":"在指定字段上创建索引；自 MongoDB 4.2 起，索引构建默认不阻塞写入。","when":"为提升查询性能添加新索引时。","caution":"在大集合上，索引构建会消耗 CPU 和 IO；primary 上的负载可能增加。","since":"MongoDB 4.2","note":"MongoDB 4.2 引入了新的索引构建流程，创建索引默认不再阻塞写入；因此旧的 { background: true } 选项已无必要，自 4.2 起会被忽略。","tags":["index","createindex","background","create","performance"]},{"id":"collection-stats","task":"查看索引和集合大小","category":"维护","command":"db.customer.stats({ indexDetails: true })","what":"报告集合大小、存储大小以及每个索引的大小。","when":"分析存储使用情况和索引成本时。","caution":"只读命令；无风险。","tags":["stats","index","size","collection","storage"]},{"id":"log-rotate","task":"轮换日志文件","category":"维护","command":"db.adminCommand({ logRotate: 1 })","what":"关闭当前日志文件并打开新文件（日志轮换）。","when":"日志文件变大时或归档前。","caution":"若与操作系统的日志轮换同时使用，请检查配置以免冲突。","tags":["log","logrotate","rotate","maintenance","archive"]},{"id":"current-op-long","task":"查找长时间运行的操作","category":"维护","command":"db.currentOp({ \"active\": true, \"secs_running\": { $gt: 5 } })","what":"列出已运行超过 5 秒的活动操作。","when":"在变慢或卡死期间识别问题查询时。","caution":"只读命令；无风险。","tags":["currentop","long","long-running","operation","diagnostics"]},{"id":"kill-op","task":"终止操作","category":"维护","command":"db.killOp(12345)","what":"终止指定 opid 的正在运行的操作。","when":"查询卡住系统或运行时间过长时。","caution":"终止写操作可能留下部分变更；避免终止系统/复制操作。","tags":["killop","kill","operation","kill","diagnostics"]},{"id":"set-parameter","task":"更改运行时参数","category":"参数","command":"db.adminCommand({ setParameter: 1, cursorTimeoutMillis: 600000 })","what":"无需重启服务器即可更改运行时参数。","when":"希望临时调整某项服务器行为时。","caution":"该更改不持久；重启后恢复为配置文件中的值。错误的值可能降低性能。","tags":["setparameter","parameter","runtime","setting","admin"]},{"id":"set-profiling","task":"设置 profiling 级别","category":"参数","command":"db.setProfilingLevel(1, { slowms: 100 })","what":"将慢查询（超过 100 ms）记录到 system.profile 集合中（级别 1）。","when":"希望诊断慢查询时。","caution":"级别 2 会记录所有操作，开销显著；profiling 数据会占用磁盘空间。","tags":["profiling","slowms","profiler","slow","diagnostics"]}]},"recipes/mongodb-ext.json":{"engineId":"mongodb","engine":"MongoDB","order":2,"recipes":[{"id":"rs-oplog-window","task":"测量 oplog 窗口和复制延迟","category":"副本集","command":"// Oplog size and the time span it covers (oplog window):\nrs.printReplicationInfo()\n// Lag of each secondary relative to the primary:\nrs.printSecondaryReplicationInfo()\n// Lag in seconds and sync source, for monitoring/scripts:\nconst s = rs.status(); const p = s.members.find(m => m.stateStr === \"PRIMARY\");\ns.members.filter(m => m.stateStr === \"SECONDARY\").map(m => ({ host: m.name, lagSec: (p.optimeDate - m.optimeDate) / 1000, syncSource: m.syncSourceHost }))\n// Window (hours):\ndb.getReplicationInfo().timeDiffHours","what":"显示 oplog 能容纳多少小时的写入（oplog 窗口），以及每个 secondary 落后 primary 多少秒及其同步源。","when":"出现复制延迟告警时、计划内维护前计算成员可下线多久，以及批量写入作业之后。","caution":"若成员下线时间超过 oplog 窗口，就会变成 'too stale'，需要初始同步。批量更新/删除作业可能在数小时内使窗口缩小；应以高峰负载时的窗口值为准。延迟计算基于 optimeDate 差值；若 primary 没有写入，延迟看起来不高，但窗口也不会缩小。","since":"MongoDB 4.4","note":"rs.printSlaveReplicationInfo() 在 4.4 中更名为 rs.printSecondaryReplicationInfo()。若窗口太小，用 replSetResizeOplog 扩大 oplog（参见 rs-oplog-resize）。延迟成员会显示落后 secondaryDelaySecs（4.4 中为 slaveDelay）；请将其排除在延迟告警之外。","tags":["oplog","oplog window","replication lag","delay","secondary","printReplicationInfo"],"untested":true},{"id":"rs-oplog-resize","task":"在线调整 oplog 大小（replSetResizeOplog）","category":"副本集","command":"// Current oplog size (MB):\ndb.getSiblingDB(\"local\").oplog.rs.stats().maxSize / 1024 / 1024\n// Resize the oplog to 50 GB (in MB; run on each member separately, secondaries first):\ndb.adminCommand({ replSetResizeOplog: 1, size: 51200 })\n// Minimum retention period (hours), independent of size:\ndb.adminCommand({ replSetResizeOplog: 1, minRetentionHours: 48 })\n// Verify the result:\nrs.printReplicationInfo()","what":"无需重启即可扩大或缩小运行中成员的 oplog；minRetentionHours 使 oplog 条目即使超过大小上限也至少保留一段时间。","when":"oplog 窗口无法覆盖维护或初始同步所需时长，或批量写入作业使窗口缩小时。","caution":"该命令只影响执行它的成员，不会被复制；需在每个成员上执行。充当同步源的成员上窗口也必须足够。缩小不会立即把磁盘空间归还给操作系统；为此需要对 oplog.rs 执行 compact。设置 minRetentionHours 后，oplog 可能随写入量超出 size；请相应规划磁盘。","since":"MongoDB 4.4","note":"新大小在重启后保留；命令设置的 minRetentionHours 不保留，因此还要设置 storage.oplogMinRetentionHours 以使其持久。size 至少为 990 MB。在配置中更改 replication.oplogSizeMB 仅在首次创建 oplog 时生效。","tags":["oplog","resize","replSetResizeOplog","minRetentionHours","size","oplog window"],"untested":true},{"id":"rs-force-reconfig","task":"失去多数派后强制重新配置副本集（force reconfig）","category":"副本集","command":"// On a surviving member with the most recent optime (runs as a secondary):\nrs.status().members.map(m => ({ host: m.name, state: m.stateStr, health: m.health, optime: m.optimeDate }))\ncfg = rs.conf()\ncfg.members.map((m, i) => ({ i: i, host: m.host }))\n// Keep only the reachable members (example: members 0 and 2):\ncfg.members = [cfg.members[0], cfg.members[2]]\nrs.reconfig(cfg, { force: true })\n// Verify the new primary election:\nrs.status().members.map(m => ({ host: m.name, state: m.stateStr }))","what":"当多数有投票权的成员永久丢失时（如整个数据中心损毁），用幸存成员构建新配置，使副本集能够再次选出 primary。","when":"仅用于灾难场景：多数派无法恢复，副本集卡在只读状态。","caution":"这是最后手段。若丢失的成员之后带着旧配置回来，可能出现脑裂和回滚；将其与网络隔离，带回时清空其数据并通过初始同步加入。选择 optime 最新的成员；否则其他成员上未经多数派提交的写入会进入回滚。不要在常规成员变更中使用 force。","since":"MongoDB 4.4","note":"force reconfig 会将配置版本加上一个很大的随机增量，它是唯一能在 secondary 上运行的 reconfig 形式。在 4.4+ 中常规 reconfig 每次最多增删一个有投票权的成员；force 不受此限制。","tags":["force reconfig","majority","disaster","recovery","split-brain","rs.reconfig"],"untested":true},{"id":"rs-rolling-maintenance","task":"副本集滚动维护（重启、操作系统补丁、小版本升级）","category":"副本集","command":"// 1) Before starting: all members healthy, low lag, and an oplog window longer than the maintenance\nrs.printSecondaryReplicationInfo()\n// 2) Clean shutdown on the SECONDARY under maintenance (then OS/binary work, restart with the same config):\ndb.getSiblingDB(\"admin\").shutdownServer({ timeoutSecs: 60 })\n// 3) Wait until the member is SECONDARY and lag has closed, then move to the next secondary:\nrs.status().members.map(m => ({ host: m.name, state: m.stateStr, health: m.health }))\n// 4) When all secondaries are done, on the PRIMARY: hand over to a secondary that catches up within 30 s\nrs.stepDown(120, 30)\n// 5) The old primary is now a SECONDARY: apply steps 2 and 3 to it","what":"给出逐个成员进入维护的顺序：先 secondary，最后 primary，从而在不让副本集停止服务的情况下完成重启、操作系统补丁或二进制更新。","when":"计划内的操作系统/内核更新、小版本升级、mongod 参数变更或磁盘/硬件维护期间。","caution":"切勿同时关闭超过一个成员；在 3 成员副本集中失去两个成员就会失去多数派。每处理完一个成员，确认其为 SECONDARY 状态且延迟已追平。stepDown 会造成短暂的写入中断；确保驱动通过 retryable writes 重新连接。若有 arbiter，按相同顺序处理。","since":"MongoDB 4.4","note":"rs.stepDown(stepDownSecs, secondaryCatchUpPeriodSecs)：第二个参数是 primary 等待 secondary 追上的时长；若没有成员追上，stepDown 失败，primary 保持不变。要在维护期间阻止某个成员成为 primary，在其上运行 rs.freeze(seconds)。","tags":["rolling","maintenance","restart","stepDown","upgrade","patching"],"untested":true},{"id":"sh-balancer-window","task":"balancer 状态、活动窗口与按集合均衡","category":"分片","command":"// Is the balancer enabled, is it running a round right now:\nsh.getBalancerState()\ndb.adminCommand({ balancerStatus: 1 })\n// Run the balancer only between 01:00 and 05:00 (on mongos):\ndb.getSiblingDB(\"config\").settings.updateOne({ _id: \"balancer\" }, { $set: { activeWindow: { start: \"01:00\", stop: \"05:00\" } } }, { upsert: true })\n// Remove the window:\ndb.getSiblingDB(\"config\").settings.updateOne({ _id: \"balancer\" }, { $unset: { activeWindow: true } })\n// Disable / enable balancing for a single collection:\nsh.disableBalancing(\"shop.musteri\")\nsh.enableBalancing(\"shop.musteri\")\n// Whether the collection is balanced (4.4+):\ndb.adminCommand({ balancerCollectionStatus: \"shop.musteri\" })","what":"显示 balancer 是否启用以及是否正在执行一轮均衡，将 chunk 迁移限制在某个时间窗口内，并为单个集合禁用或启用均衡。","when":"chunk 迁移影响白天性能时、备份窗口期间，或在单个集合上隔离迁移问题时。","caution":"窗口太窄会跟不上数据增长，导致分片不均衡；定期用 balancerCollectionStatus 检查均衡状态。在窗口关闭前开始的迁移会一直运行到完成。若某集合的均衡一直被禁用，该集合会持续在单个分片上增长。","since":"MongoDB 6.0","note":"activeWindow 时间按 config server 副本集 primary 的本地时区计算。在 6.0.3+ 中 balancer 根据分片间的数据量差异而非 chunk 数量做决定；数据已均衡时 sh.status() 中的 chunk 数量也可能看起来不均。","tags":["balancer","activeWindow","window","migration","sharding","balancerCollectionStatus"],"untested":true},{"id":"sh-jumbo-chunks","task":"查找 jumbo chunk、拆分并清除标记","category":"分片","command":"// Chunks flagged as jumbo (5.0+ config.chunks links to the collection by uuid):\nconst uuid = db.getSiblingDB(\"config\").collections.findOne({ _id: \"shop.musteri\" }).uuid;\ndb.getSiblingDB(\"config\").chunks.find({ uuid: uuid, jumbo: true }, { min: 1, max: 1, shard: 1 })\n// If the chunk can be split, split it at the median:\nsh.splitFind(\"shop.musteri\", { musteriId: 1000 })\n// Last resort for an indivisible chunk, clear the flag manually (a successful split already clears it):\ndb.adminCommand({ clearJumboFlag: \"shop.musteri\", find: { musteriId: 1000 } })\n// If a single key value is too large, refine the key (4.4+):\ndb.adminCommand({ refineCollectionShardKey: \"shop.musteri\", key: { musteriId: 1, siparisId: 1 } })","what":"列出因 balancer 无法移动而被标记为 jumbo 的 chunk，拆分可拆分的 chunk 并清除 jumbo 标记；说明如何为巨大的单值 chunk 细化分片键。","when":"某个分片明显大于其他分片、sh.status() 输出中出现 jumbo，或 balancer 报告迁移错误时。","caution":"所有文档共享相同分片键值的 chunk 无法拆分；唯一持久的解决办法是用 refineCollectionShardKey 或 reshardCollection 更改分片键。refineCollectionShardKey 要求事先存在支持新键的索引。不拆分就清除 jumbo 标记，会使 balancer 重试同一 chunk 并再次失败。","since":"MongoDB 5.0","note":"低基数分片键或集中在某个值上的键（如 status、country）是 jumbo chunk 最常见的原因。在 5.0 之前，config.chunks 文档按 ns 字段查询。clearJumboFlag 在 4.2.3/4.0.15 引入。","tags":["jumbo","chunk","split","clearJumboFlag","shard key","refineCollectionShardKey"],"untested":true},{"id":"sh-shard-key-check","task":"检查分片键选择和数据分布（getShardDistribution、analyzeShardKey）","category":"分片","command":"// Data and document distribution of the collection across shards:\ndb.getSiblingDB(\"shop\").musteri.getShardDistribution()\n// Cardinality, frequency and monotonicity analysis of the current or candidate shard key:\ndb.adminCommand({ analyzeShardKey: \"shop.musteri\", key: { musteriId: 1 }, keyCharacteristics: true, readWriteDistribution: false })\n// For read/write distribution, enable query sampling first (10 samples per second):\ndb.adminCommand({ configureQueryAnalyzer: \"shop.musteri\", mode: \"full\", samplesPerSecond: 10 })\ndb.adminCommand({ analyzeShardKey: \"shop.musteri\", key: { musteriId: 1 }, keyCharacteristics: false, readWriteDistribution: true })\n// Turn sampling off when done:\ndb.adminCommand({ configureQueryAnalyzer: \"shop.musteri\", mode: \"off\" })","what":"显示集合数据在每个分片上的百分比；analyzeShardKey 报告当前或候选分片键的基数、最常见值和单调性，以及采样查询是定向到单个分片还是所有分片（scatter-gather）。","when":"对新集合分片之前、发现热点分片或增长不均时，以及决定是否执行 reshardCollection 之前。","caution":"analyzeShardKey 需要支持该键的索引，并在大集合上对文档采样；高峰时段会增加读取负载。查询采样开启期间，样本会写入 config.sampledQueries；分析完成后将其关闭。单调递增的键（ObjectId、时间戳）会把所有插入堆到一个分片上。","since":"MongoDB 7.0","note":"analyzeShardKey 和 configureQueryAnalyzer 在 7.0 引入，也适用于副本集。好的分片键基数高、频率低、非单调，并出现在频繁查询的过滤条件中。哈希键能改善分布，但会把范围查询变成 scatter-gather。","tags":["shard key","analyzeShardKey","getShardDistribution","cardinality","distribution","hot shard"],"untested":true},{"id":"explain-execstats","task":"用真实执行统计检查查询计划（explain executionStats）","category":"性能","command":"db.musteri.find({ sehir: \"Ankara\", durum: \"aktif\" }).sort({ kayitTarihi: -1 }).explain(\"executionStats\")\n// Summary: winning plan, returned documents, examined keys/documents and time\nconst e = db.musteri.find({ sehir: \"Ankara\", durum: \"aktif\" }).sort({ kayitTarihi: -1 }).explain(\"executionStats\");\n({ plan: e.queryPlanner.winningPlan, n: e.executionStats.nReturned, keys: e.executionStats.totalKeysExamined, docs: e.executionStats.totalDocsExamined, ms: e.executionStats.executionTimeMillis })\n// For an aggregation:\ndb.musteri.explain(\"executionStats\").aggregate([{ $match: { sehir: \"Ankara\" } }, { $group: { _id: \"$durum\", n: { $sum: 1 } } }])","what":"执行查询并显示胜出的计划（IXSCAN/COLLSCAN、SORT 阶段）、返回的文档数、检查的索引键和文档数以及耗时。","when":"分析在 profiler 或日志中显示为慢的查询，以及验证新索引是否真正被使用时。","caution":"executionStats 会真正执行查询；在高峰时段 explain 重型查询也会增加负载。对于写命令（update/delete/findAndModify），explain 不修改数据，但会执行计划。totalDocsExamined/nReturned 比值高或计划中出现内存 SORT 阶段，说明索引不合适。","since":"MongoDB 4.4","note":"设计复合索引时遵循 ESR 规则：先 Equality（相等）字段，再 Sort（排序），最后 Range（范围）。使用 explain(\"allPlansExecution\") 也可看到被拒绝的计划。在 7.0+ 中，查询在 Slot-Based Engine 上运行时，winningPlan 包含 queryPlan 和 slotBasedPlan 字段。","tags":["explain","executionStats","IXSCAN","COLLSCAN","plan","index"]},{"id":"profiler-analyze","task":"从 profiler 数据中提取最慢的查询和查询形态","category":"性能","command":"// Recent slow operations (in the db where profiling is on; see set-profiling):\ndb.system.profile.find({ millis: { $gte: 100 } }, { ts: 1, ns: 1, op: 1, millis: 1, planSummary: 1, keysExamined: 1, docsExamined: 1, nreturned: 1, appName: 1 }).sort({ ts: -1 }).limit(20)\n// Total time per query shape (those consuming the most time):\ndb.system.profile.aggregate([\n  { $group: { _id: { ns: \"$ns\", plan: \"$planSummary\", shape: \"$queryHash\" }, n: { $sum: 1 }, totalMs: { $sum: \"$millis\" }, maxMs: { $max: \"$millis\" }, avgDocs: { $avg: \"$docsExamined\" } } },\n  { $sort: { totalMs: -1 } },\n  { $limit: 10 }\n])\n// Queries not using an index (COLLSCAN):\ndb.system.profile.find({ planSummary: \"COLLSCAN\" }, { ns: 1, millis: 1, command: 1 }).sort({ millis: -1 }).limit(10)","what":"根据 system.profile 集合中的记录，列出最近的慢操作、每种查询形态（queryHash）的总耗时，以及未使用索引的 COLLSCAN 查询。","when":"出现变慢投诉时，用于找出哪个查询消耗了服务器时间并确定索引需求。","caution":"system.profile 默认是 1 MB 的 capped 集合，且按数据库区分；在繁忙系统上记录几分钟内就会被覆盖，因此要在 profiling 开启期间或刚结束后分析。profiling 级别 2 或 slowms 0 会写入每个操作，开销显著。记录只保存在执行操作的成员上，不会被复制。","since":"MongoDB 4.4","note":"即使关闭 profiling，超过 slowms（默认 100 ms）的操作也会以 'Slow query' 写入 mongod 日志；日志中的 attr.durationMillis 和 attr.planSummary 可给出相同的诊断。在 8.0+ 中还会为查询形态写入 planCacheShapeHash 字段。","tags":["profiler","system.profile","slow query","top queries","COLLSCAN","queryHash"]},{"id":"index-usage-hide","task":"查找未使用的索引并在删除前隐藏（hideIndex）","category":"性能","command":"// Usage counter per index (on this member, since the last restart):\ndb.musteri.aggregate([{ $indexStats: {} }, { $project: { name: 1, ops: \"$accesses.ops\", since: \"$accesses.since\" } }, { $sort: { ops: 1 } }])\n// Hide it from the planner before dropping (the index keeps being maintained):\ndb.musteri.hideIndex(\"idx_email\")\n// Revert immediately if problems appear:\ndb.musteri.unhideIndex(\"idx_email\")\n// If the observation period passes without issues:\ndb.musteri.dropIndex(\"idx_email\")","what":"$indexStats 显示每个索引被使用的次数；hideIndex 在不删除索引的情况下将其对查询规划器隐藏，从而可逆地测试移除它的影响。","when":"清理增加写入开销和存储占用的多余索引时。","caution":"计数器按成员统计，重启后重置；若读取发往 secondary，请检查所有成员并观察足够长的时间（包括月末报表）。ops 为 0 的唯一索引或 TTL 索引可能仍然必要：唯一约束和 TTL 删除不会体现在计数器中。隐藏的索引在写入时仍会被维护，隐藏的唯一索引仍会强制约束。","since":"MongoDB 4.4","note":"hideIndex 要求 featureCompatibilityVersion 4.4 或更高。_id 索引不能隐藏。用 unhideIndex 取消隐藏无需重建，立即生效。","tags":["indexStats","hideIndex","unused index","index cleanup","dropIndex","index"]},{"id":"connections-by-app","task":"按应用和客户端统计连接（连接风暴诊断）","category":"性能","command":"// Total, available and rejected connection counts:\ndb.serverStatus().connections\n// Count connections by application name and client IP:\ndb.getSiblingDB(\"admin\").aggregate([\n  { $currentOp: { allUsers: true, idleConnections: true } },\n  { $group: { _id: { app: \"$appName\", client: { $arrayElemAt: [{ $split: [\"$client\", \":\"] }, 0] } }, conns: { $sum: 1 }, active: { $sum: { $cond: [\"$active\", 1, 0] } } } },\n  { $sort: { conns: -1 } },\n  { $limit: 20 }\n])","what":"显示服务器上的总连接数和可用连接数，然后按 appName 和客户端 IP 对打开的连接分组，并给出其中活动连接的数量。","when":"连接数突然飙升、出现 'connection refused' 或连接池超时错误时，用于找出哪台应用服务器泄漏连接。","caution":"idleConnections: true 还会返回所有空闲连接，在有数千个连接的系统上输出很大；因此用 $group 汇总很重要。client 为 null 的行是内部线程。每个连接约消耗 1 MB 内存；连接风暴还会使 WiredTiger 缓存之外的内存增长。","since":"MongoDB 4.4","note":"持久的解决办法通常在驱动侧：maxPoolSize、maxIdleTimeMS 和应用实例数量。若应用未设置 appName，就很难区分它们；要求在连接字符串中加入 appName=<service>。net.maxIncomingConnections 和操作系统的 ulimit -n 值决定硬性上限。","tags":["connections","connection count","currentOp","appName","connection storm","pool"]},{"id":"index-build-monitor","task":"监控并中止进行中的索引构建","category":"维护","command":"// Index builds in progress and their completion percentage:\ndb.getSiblingDB(\"admin\").aggregate([\n  { $currentOp: { allUsers: true } },\n  { $match: { $or: [{ \"command.createIndexes\": { $exists: true } }, { desc: /^IndexBuildsCoordinator/ }] } },\n  { $project: { opid: 1, desc: 1, ns: 1, secs_running: 1, msg: 1, progress: 1, \"command.indexes.name\": 1 } }\n])\n// Abort the build (on the primary; in a replica set it stops on all members):\ndb.musteri.dropIndex(\"idx_email\")","what":"列出正在运行的索引构建及已扫描的文档数和百分比进度（progress.done / progress.total）；必要时用 dropIndex 中止构建。","when":"用于估算在大集合上启动的 createIndex 何时完成，或停止在高峰时段造成负载的构建。","caution":"用 dropIndex/dropIndexes 而不是 killOp 中止索引构建；在副本集中请在 primary 上执行，使中止操作传播到所有成员。构建期间集合上的某些 DDL 操作会等待。构建结束时会短暂持有排他锁；在写入极其繁重时该时刻可能造成延迟。","since":"MongoDB 4.4","note":"在 4.4+ 副本集中，索引构建在所有承载数据的成员上同时运行，并在满足 commitQuorum（默认 votingMembers）后提交；若某个 secondary 滞后，primary 上的构建也不会完成。在 5.0+ 中，被正常关闭中断的构建在重启后从中断处继续。","tags":["index build","createIndex","progress","monitor","dropIndex","commitQuorum"]},{"id":"rolling-index-build","task":"副本集上的滚动索引构建（逐个成员）","category":"维护","command":"// 1) Shut down a SECONDARY; comment out replication.replSetName and start it standalone on another port:\n//    mongod --config /etc/mongod.conf --port 27217 --setParameter disableLogicalSessionCacheRefresh=true\n// 2) Build the index on the standalone member:\ndb.getSiblingDB(\"shop\").musteri.createIndex({ email: 1 }, { name: \"idx_email\" })\n// 3) Restart with the normal configuration; wait until it is SECONDARY and lag has closed:\nrs.printSecondaryReplicationInfo()\n// 4) When all secondaries are done, step down the primary and apply steps 1-3 to the old primary:\nrs.stepDown(120, 30)","what":"通过临时让成员脱离副本集以 standalone 方式运行，在每个成员上分别构建索引；primary 从不承担构建负载。","when":"对于超大集合，默认分布式构建对 primary 和复制延迟的影响不可接受时。","caution":"每个成员的维护时间必须短于 oplog 窗口；否则成员回归时已经 too stale。以滚动方式构建唯一索引时，在整个构建期间停止对该集合的写入；否则某些成员上可能出现违反约束的文档。在不同端口上启动 standalone 成员，以免应用连接到它。","since":"MongoDB 4.4","note":"在 4.4+ 中默认构建已经是非阻塞的，并在所有成员上同时运行；只有在该负载不可接受时才需要滚动方式。在分片集群中，需事先停止 balancer，再对每个分片副本集分别执行该流程。","tags":["rolling index","index build","standalone","replica","createIndex","maintenance"],"untested":true},{"id":"ttl-index","task":"创建 TTL 索引、更改其过期时间并监控 TTL 删除","category":"维护","command":"// Automatically delete documents lastSeen + 3600 s later:\ndb.oturum.createIndex({ lastSeen: 1 }, { expireAfterSeconds: 3600, name: \"ttl_lastSeen\" })\n// Change the expiry without rebuilding the index:\ndb.runCommand({ collMod: \"oturum\", index: { name: \"ttl_lastSeen\", expireAfterSeconds: 7200 } })\n// TTL indexes on the collection and their expiry:\ndb.oturum.getIndexes().filter(i => i.expireAfterSeconds !== undefined)\n// TTL monitor statistics (deleted documents, passes):\ndb.serverStatus().metrics.ttl","what":"创建 TTL 索引，在日期字段之后的设定时间自动删除文档；用 collMod 更改过期时间而无需重建；并显示 TTL monitor 已删除的文档数。","when":"用于清理会话、日志或临时令牌等有时效的数据，而无需在应用中编写删除作业，以及排查不断增长的集合为何没有缩小。","caution":"TTL monitor 默认每 60 秒运行一次；删除不是即时的。该字段必须是 Date 类型（或 Date 数组）；字符串日期或缺少该字段的文档永远不会被删除。缩短过期时间会导致大量文档被一次性删除，产生写入负载和复制延迟。删除只在 primary 上发生，并写入 oplog。","since":"MongoDB 5.1","note":"TTL 索引必须是单字段索引（不支持复合 TTL）。在 5.1+ 中可以用 collMod 为现有的非 TTL 单字段索引添加 expireAfterSeconds；更改现有 TTL 过期时间在较旧版本上也可用。要在特定时间删除，使用 expireAfterSeconds: 0 并在该字段中存储删除时间。","tags":["ttl","expireAfterSeconds","collMod","auto delete","index","metrics.ttl"]},{"id":"wt-cache-pressure","task":"测量 WiredTiger 缓存压力（填充率、脏数据比例、应用线程驱逐）","category":"性能","command":"const c = db.serverStatus().wiredTiger.cache;\nconst max = c[\"maximum bytes configured\"];\n({\n  maxGB: (max / 1024 ** 3).toFixed(1),\n  usedPct: (100 * c[\"bytes currently in the cache\"] / max).toFixed(1),\n  dirtyPct: (100 * c[\"tracked dirty bytes in the cache\"] / max).toFixed(1),\n  appThreadEvictedPages: c[\"pages evicted by application threads\"],\n  pagesReadIntoCache: c[\"pages read into cache\"]\n})\n// Read/write ticket usage and queue:\ndb.serverStatus().wiredTiger.concurrentTransactions","what":"显示 WiredTiger 缓存大小、填充和脏数据百分比、应用线程被迫驱逐的页数，以及读/写 ticket 状态。","when":"磁盘读取增加、延迟突然飙升或写入吞吐量下降时；确定缓存大小时。","caution":"填充率超过 80%（eviction_target）时后台驱逐开始；达到 95%（eviction_trigger）时应用线程也会参与驱逐，查询变慢。脏数据的目标是 5%，触发点是 20%；长期接近 20% 说明磁盘写入能力不足。pages evicted by application threads 计数器不断增长，就是用户直接感受到的延迟。这些值是累计计数器；请比较两次采样的差值。","since":"MongoDB 7.0","note":"默认缓存大小取 (RAM - 1 GB) / 2 与 256 MB 中较大者；在容器中根据 cgroup 限制计算。增大 storage.wiredTiger.engineConfig.cacheSizeGB 会减少操作系统页缓存；超过内存的一半很少有帮助。在 7.0 中 ticket 数量动态调整；在 8.0+ 中 ticket 指标也出现在 serverStatus().queues.execution 下。","tags":["wiredtiger","cache","eviction","dirty","tickets","memory"]},{"id":"dump-restore-archive","task":"用 mongodump/mongorestore 进行归档备份并还原单个集合","category":"备份","command":"# Single database, one compressed archive file (password is prompted):\nmongodump --uri=\"mongodb://admin@mongo1:27017/?authSource=admin\" --db=shop --gzip --archive=/backup/shop_$(date +%F).archive.gz\n# Dry run before restoring the archive under a different name:\nmongorestore --uri=\"mongodb://admin@mongo1:27017/?authSource=admin\" --gzip --archive=/backup/shop_2026-10-08.archive.gz --nsFrom=\"shop.*\" --nsTo=\"shop_restore.*\" --dryRun\n# Restore a single collection from the archive under a new name:\nmongorestore --uri=\"mongodb://admin@mongo1:27017/?authSource=admin\" --gzip --archive=/backup/shop_2026-10-08.archive.gz --nsInclude=\"shop.musteri\" --nsFrom=\"shop.*\" --nsTo=\"shop_restore.*\" --numInsertionWorkersPerCollection=4\n# Restore under the same name, dropping the existing collection:\nmongorestore --uri=\"mongodb://admin@mongo1:27017/?authSource=admin\" --gzip --archive=/backup/shop_2026-10-08.archive.gz --nsInclude=\"shop.musteri\" --drop","what":"将数据库备份到单个 gzip 归档文件中；从归档中还原整个数据库或单个集合，并可选择使用不同的名称（nsFrom/nsTo）。","when":"用于逻辑备份、在环境之间迁移数据，或恢复被误删或损坏的单个集合。","caution":"mongodump 是逻辑备份：在大数据集上很慢，还原时要重建索引。在运行中的系统上，不带 --oplog 的转储不具备时间点一致性。--drop 会在还原开始前删除目标集合；先用 nsTo 以不同名称还原并验证。还原写入 primary，可能导致复制延迟。","since":"MongoDB 4.4","note":"自 4.4 起，数据库工具（mongodump/mongorestore）与服务器分开发布版本（100.x）。在 URI 中加入 readPreference=secondary，可在备份期间减轻 primary 的读取负载。若要同时迁移用户和角色，请使用带 --restoreDbUsersAndRoles 的完整实例转储，或单独备份 admin 数据库。","tags":["mongodump","mongorestore","backup","archive","nsFrom","restore"]},{"id":"pitr-oplog-replay","task":"通过重放 oplog 进行时间点还原（回到错误 drop/delete 之前）","category":"备份","command":"# 1) Full backup + oplog written during the backup (whole instance, from a replica set member):\nmongodump --uri=\"mongodb://admin@mongo2:27017/?authSource=admin&readPreference=secondary\" --oplog --gzip --out=/backup/full\n# 2) Find the time of the bad operation (mongosh): db.getSiblingDB(\"local\").oplog.rs.find({ \"o.drop\": \"musteri\" }).sort({ $natural: -1 }).limit(1)\n# 3) Dump the oplog after the backup (t: start time of the full backup, epoch seconds):\nmongodump --uri=\"mongodb://admin@mongo2:27017/?authSource=admin\" --db=local --collection=oplog.rs --query='{\"ts\":{\"$gte\":{\"$timestamp\":{\"t\":1791400000,\"i\":1}}}}' --out=/backup/oplog\n# 4) Restore the full backup together with its own oplog:\nmongorestore --uri=\"mongodb://admin@target:27017/?authSource=admin\" --gzip --oplogReplay /backup/full\n# 5) Replay the oplog up to the bad operation (oplogLimit is exclusive):\nmkdir /backup/pitr && mv /backup/oplog/local/oplog.rs.bson /backup/pitr/oplog.bson\nmongorestore --uri=\"mongodb://admin@target:27017/?authSource=admin\" --oplogReplay --oplogLimit=1791403600:1 /backup/pitr","what":"用 --oplog 使完整备份保持一致，然后重放备份后累积的 oplog，直到错误操作（drop、批量删除）的时间戳，将数据恢复到该时刻。","when":"集合被误删，或执行了错误的批量更新/删除，需要恢复到损坏前的最后状态时。","caution":"还原到单独的目标，而不是生产系统，验证后再迁移所需数据。从备份开始到错误操作之间的 oplog 必须仍在 oplog 窗口内；已滑出窗口的操作无法恢复。--oplog 仅适用于完整实例转储（不能与 --db 同时使用）。在分片集群上无法用 mongodump 实现一致的 PITR；需要文件系统快照或备份产品。","since":"MongoDB 4.4","note":"--oplogLimit=<seconds>:<ordinal> 不会应用该时间戳及之后的条目；直接传入错误操作的 ts 即可将其排除。ts 值在 oplog 中显示为 Timestamp(t, i)。在错误操作发生后、窗口关闭前立即转储 oplog。","tags":["pitr","point in time","oplogReplay","oplogLimit","rollback","mongorestore"],"untested":true},{"id":"user-role-admin","task":"定义自定义角色、创建用户、授予/撤销角色并修改密码","category":"安全","command":"use admin\ndb.createRole({\n  role: \"opsRead\",\n  privileges: [\n    { resource: { db: \"shop\", collection: \"\" }, actions: [\"find\", \"listCollections\", \"listIndexes\", \"collStats\", \"dbStats\"] },\n    { resource: { cluster: true }, actions: [\"serverStatus\", \"inprog\"] }\n  ],\n  roles: []\n})\ndb.createUser({ user: \"shop_app\", pwd: passwordPrompt(), roles: [{ role: \"readWrite\", db: \"shop\" }, { role: \"opsRead\", db: \"admin\" }] })\ndb.grantRolesToUser(\"shop_app\", [{ role: \"clusterMonitor\", db: \"admin\" }])\ndb.revokeRolesFromUser(\"shop_app\", [{ role: \"clusterMonitor\", db: \"admin\" }])\ndb.changeUserPassword(\"shop_app\", passwordPrompt())\ndb.getUser(\"shop_app\", { showPrivileges: true })\ndb.runCommand({ usersInfo: { forAllDBs: true } })","what":"定义包含特定数据库和集群操作的自定义角色，创建拥有该角色和内置角色的用户，授予和撤销角色，修改密码，并列出所有数据库中的用户。","when":"按最小权限创建应用和监控账户时、访问评审或密码轮换期间。","caution":"定义用户的数据库（此处为 admin）用作 authSource；它必须与应用的连接字符串一致。不要向应用账户授予 root/dbOwner 等宽泛角色。角色变更立即作用于现有会话；修改密码不会断开已打开的连接，只作用于新连接。","since":"MongoDB 4.4","note":"passwordPrompt() 会提示输入密码，而不会将其留在 shell 历史或进程列表中。在副本集和分片集群中，用户通过 primary/mongos 管理并被复制。在 getUser 中使用 showPrivileges: true 可查看用户的有效权限。","tags":["createUser","createRole","grantRolesToUser","privileges","rbac","password"]},{"id":"fcv-upgrade","task":"检查 featureCompatibilityVersion（fCV）和大版本升级顺序","category":"升级","command":"// 1) Current fCV (must be the same on all members; targetVersion appears if a transition is incomplete):\ndb.adminCommand({ getParameter: 1, featureCompatibilityVersion: 1 })\n// 2) Upgrade binaries in a rolling fashion (secondaries -> stepDown -> old primary); fCV stays old\ndb.version()\n// 3) After the observation period, raise fCV on the primary (on mongos in a sharded cluster):\ndb.adminCommand({ setFeatureCompatibilityVersion: \"7.0\", confirm: true })\n// 4) If a rollback is needed, fCV is lowered BEFORE the binaries:\ndb.adminCommand({ setFeatureCompatibilityVersion: \"6.0\", confirm: true })","what":"显示当前的 fCV；给出二进制升级和 fCV 升级这两个独立步骤的执行顺序。","when":"规划大版本升级（如 6.0 -> 7.0）、升级后无法使用新功能，或升级到下一个大版本之前。","caution":"大版本不能跳过：从 5.0 -> 7.0 需先升级到 6.0 并将 fCV 设为 6.0。提高 fCV 后数据文件可能切换为新格式，二进制降级会更困难；仅在新版本平稳运行几天后再提高 fCV。7.0+ Community 不正式支持二进制降级；回退方案应基于备份。升级前检查驱动兼容性。","since":"MongoDB 7.0","note":"自 7.0 起，setFeatureCompatibilityVersion 需要 confirm: true。若 fCV 转换失败，getParameter 输出中会出现 targetVersion/previousVersion；重新运行相同命令即可完成。fCV 值会在副本集内复制。","tags":["fcv","featureCompatibilityVersion","upgrade","major upgrade","major","downgrade"]}]},"recipes/redis.json":{"engineId":"redis","engine":"Redis","order":6,"recipes":[{"id":"bgrewriteaof","task":"重写（压缩）AOF 文件","category":"持久化","command":"BGREWRITEAOF","what":"在后台以更紧凑的形式重写 AOF 文件。","when":"AOF 文件膨胀时，用于缩短重启时间。","caution":"fork 期间内存和 IO 使用会增加；高峰时段要谨慎。","tags":["aof","rewrite","persistence","persistence"]},{"id":"bgsave","task":"在后台生成 RDB 快照","category":"持久化","command":"BGSAVE","what":"使用 fork 出的子进程在后台将 RDB 快照写入磁盘。","when":"需要备份或创建快照时。","caution":"fork 时由于写时复制，内存使用可能暂时上升。","tags":["rdb","bgsave","snapshot","persistence","backup"]},{"id":"save","task":"同步生成 RDB 快照","category":"持久化","command":"SAVE","what":"在主线程上同步将 RDB 快照写入磁盘。","when":"仅在维护/关机前、没有为客户端提供服务时使用。","caution":"SAVE 会阻塞整个服务器，完成前不处理任何命令；生产环境优先使用 BGSAVE。","tags":["rdb","save","snapshot","blocking","persistence"]},{"id":"lastsave","task":"查看上次成功快照的时间","category":"持久化","command":"LASTSAVE","what":"返回上次成功保存 RDB 的 Unix 时间戳。","when":"用于检查 BGSAVE 是否已完成。","caution":"只读命令；无风险。","tags":["lastsave","rdb","timestamp","check","persistence"]},{"id":"config-rewrite","task":"将运行时配置写入磁盘","category":"持久化","command":"CONFIG REWRITE","what":"将运行时用 CONFIG SET 所做的更改持久化到 redis.conf 文件。","when":"希望运行时更改在重启后保留时。","caution":"会覆盖现有的 redis.conf 文件；该文件必须已定义且可写。","tags":["config","rewrite","persistent","conf","setting"]},{"id":"config-appendonly","task":"启用或禁用 AOF","category":"持久化","command":"CONFIG SET appendonly yes","what":"在运行时启用（yes）或禁用（no）AOF 持久化。","when":"希望在不重启的情况下开启或关闭 AOF 持久化时。","caution":"禁用 AOF 会削弱持久性保证；启用时，初始 AOF 重写会产生 IO 负载。","tags":["aof","appendonly","config","persistence","setting"]},{"id":"config-save-rdb","task":"设置 RDB 快照规则","category":"持久化","command":"CONFIG SET save \"900 1 300 10 60 10000\"","what":"设置触发自动 RDB 快照的规则（秒数/变更次数）。","when":"希望更改自动快照频率时。","caution":"若传入空值（\"\"），自动 RDB 保存会被完全禁用，可能失去持久化。","tags":["rdb","save","config","snapshot","setting"]},{"id":"replicaof","task":"将节点设为副本","category":"复制","command":"REPLICAOF redis-master 6379","what":"将此节点设为指定主机和端口上主节点的副本，并开始同步。","when":"添加新的只读副本或分配角色时。","caution":"成为副本的节点上的现有数据会被丢弃，并从主节点拉取全量同步。","since":"Redis 5.0","note":"Redis 5.0 中 REPLICAOF 命令取代了 SLAVEOF；旧的 SLAVEOF 命令仍可作为别名使用。","tags":["replicaof","replica","replication","master","sync"]},{"id":"replicaof-no-one","task":"将副本提升为主节点","category":"复制","command":"REPLICAOF NO ONE","what":"断开节点的副本链路，使其成为独立的主节点。","when":"在故障转移场景中需要提升副本时。","caution":"提升后要谨慎管理拓扑，避免与旧主节点出现数据分歧（脑裂）。","since":"Redis 5.0","note":"Redis 5.0 中 REPLICAOF NO ONE 取代了 SLAVEOF NO ONE；旧的 SLAVEOF NO ONE 仍可作为别名使用。","tags":["replicaof","promote","master","failover","replication"]},{"id":"cluster-failover","task":"执行手动故障转移","category":"集群","command":"CLUSTER FAILOVER","what":"在副本上运行时，通过将该副本提升为主节点来启动受控的故障转移。","when":"希望在维护前把主节点角色交给副本时。","caution":"此命令必须在目标副本上运行，而不是在主节点本身上。","tags":["cluster","failover","promote","maintenance","replication"]},{"id":"cluster-meet","task":"向集群添加节点","category":"集群","command":"CLUSTER MEET 10.0.0.12 6379","what":"将指定 IP 和端口上的节点加入集群 gossip 网络。","when":"向集群添加新节点（扩容）时。","caution":"添加节点后若未进行槽分配（ADDSLOTS）或 reshard，它不会接收任何数据。","tags":["cluster","meet","node","add","node"]},{"id":"cluster-addslots","task":"分配集群槽","category":"集群","command":"CLUSTER ADDSLOTS 0 1 2 3 4 5","what":"将指定的哈希槽分配给此主节点。","when":"手动配置新主节点或修正槽分布时。","caution":"同一个槽不能分配给多个节点；若 16384 个槽未全部分配，集群会进入 down 状态。","tags":["cluster","addslots","slot","assign","node"]},{"id":"info-replication","task":"查看复制状态","category":"集群","command":"INFO replication","what":"显示角色（master/replica）、已连接的副本以及复制偏移量信息。","when":"检查复制延迟和链路状态时。","caution":"只读命令；无风险。","tags":["info","replication","status","replication","lag"]},{"id":"cluster-info","task":"查看集群状态","category":"集群","command":"CLUSTER INFO","what":"显示集群整体状态（ok/fail）、已分配的槽数以及已知节点数。","when":"验证集群健康状况和槽完整性时。","caution":"只读命令；无风险。","tags":["cluster","info","status","slot","health"]},{"id":"config-maxmemory","task":"设置 maxmemory 及策略","category":"内存","command":"CONFIG SET maxmemory 4gb maxmemory-policy allkeys-lru","what":"设置内存上限以及超出上限时采用的淘汰策略。Redis 7.0+ 允许在一条 CONFIG SET 中设置多个参数；在较旧版本上请分别运行两条 CONFIG SET 命令。","when":"希望限制内存使用并定义驱逐行为时。","caution":"使用 noeviction 策略时，达到上限后写入会失败；上限设置错误可能导致数据丢失/停机。","tags":["maxmemory","policy","memory","eviction","config"]},{"id":"memory-doctor","task":"分析内存","category":"内存","command":"MEMORY DOCTOR","what":"分析内存使用情况，并就可能存在的问题生成易读的报告。","when":"怀疑内存使用过高或存在碎片时用于诊断。","caution":"只读分析；如需详情也可使用 MEMORY STATS 命令。","tags":["memory","doctor","memory","analysis","diagnostics"]},{"id":"slowlog-get","task":"查看慢命令日志","category":"维护","command":"SLOWLOG GET 10","what":"从 slowlog 返回最近 10 条慢命令记录（耗时、时间、参数）。","when":"识别慢命令和排查性能瓶颈时。","caution":"只读命令；使用 SLOWLOG RESET 清除记录。","tags":["slowlog","get","slow","diagnostics","performance"]},{"id":"slowlog-config","task":"设置慢命令阈值","category":"维护","command":"CONFIG SET slowlog-log-slower-than 10000","what":"设置命令被记入 slowlog 的耗时阈值，单位为微秒（10000 = 10 ms）。","when":"希望调整 slowlog 灵敏度时。","caution":"设为 0 会记录每条命令并增加开销；-1 会禁用 slowlog。","tags":["slowlog","threshold","threshold","config","slow"]},{"id":"slowlog-reset","task":"重置慢命令日志","category":"维护","command":"SLOWLOG RESET","what":"清除 slowlog 中的所有记录。","when":"清除记录以开始新的观察窗口时。","caution":"现有的慢命令历史会被永久删除。","tags":["slowlog","reset","reset","clear","maintenance"]},{"id":"client-kill","task":"终止客户端连接","category":"维护","command":"CLIENT KILL ADDR 10.0.0.5:53210","what":"终止指定地址的客户端连接。","when":"希望终止卡住或行为异常的客户端时。","caution":"连接会立即断开；进行中的操作可能无法完成。确保选择了正确的地址。","tags":["client","kill","client","kill","maintenance"]},{"id":"client-list","task":"列出已连接的客户端","category":"维护","command":"CLIENT LIST","what":"列出所有已连接的客户端及其地址、空闲时间和最后一条命令等详情。","when":"检查连接或查找有问题的客户端时。","caution":"只读命令；无风险。","tags":["client","list","client","list","connection"]},{"id":"config-get","task":"查看参数值","category":"运行时","command":"CONFIG GET maxmemory-policy","what":"返回一个或多个配置参数的当前值（支持 glob 模式）。","when":"验证某项设置的运行时值时。","caution":"只读命令；无风险。","tags":["config","get","parameter","view","runtime"]},{"id":"config-set","task":"更改参数值","category":"运行时","command":"CONFIG SET appendfsync everysec","what":"无需重启即可在运行时更改配置参数。","when":"希望立即调整某项服务器行为时。","caution":"该更改是临时的；运行 CONFIG REWRITE 使其持久化。错误的值可能降低性能/持久性。","tags":["config","set","parameter","runtime","setting"]}]},"recipes/redis-ext.json":{"engineId":"redis","engine":"Redis","order":2,"recipes":[{"id":"mem-fragmentation","task":"分析内存使用和碎片，启用主动碎片整理","category":"内存","command":"INFO memory\n# Fields to check: used_memory_human, used_memory_rss_human, mem_fragmentation_ratio, mem_fragmentation_bytes, allocator_frag_ratio\nMEMORY STATS\n# If fragmentation is high and persistent, enable active defrag (requires jemalloc):\nCONFIG SET activedefrag yes\nCONFIG SET active-defrag-ignore-bytes 100mb\nCONFIG SET active-defrag-threshold-lower 10\n# Ask the allocator to return free pages to the OS:\nMEMORY PURGE","what":"显示 Redis 为数据分配的内存（used_memory）与操作系统看到的 RSS 之间的差异、碎片率和内存明细；必要时在运行时启用主动碎片整理（active defrag）。","when":"RSS 明显大于数据集、批量删除/过期后内存不下降，或出现 OOM 风险时。","caution":"在小数据集上 mem_fragmentation_ratio 具有误导性（如 2 MB 数据显示 4.0）；绝对值请看 mem_fragmentation_bytes。比值低于 1 表示发生了 swap 而非碎片，会造成延迟。主动碎片整理消耗 CPU；用 active-defrag-cycle-max 加以限制。需要 CONFIG REWRITE 或 redis.conf 才能持久化。","since":"Redis 4.0","note":"主动碎片整理仅适用于使用 Redis 自带 jemalloc 编译的版本。allocator_frag_ratio 显示碎片整理可以修复的内部碎片；allocator_rss_ratio 显示分配器尚未归还给操作系统的空间。在频繁更新大小不一的值的负载下，碎片增长更快。","tags":["memory","ram","fragmentation","activedefrag","rss","info memory"]},{"id":"bigkeys-memkeys","task":"查找大 key（--bigkeys、--memkeys、MEMORY USAGE）","category":"内存","command":"# Largest keys per type by element count:\nredis-cli -h redis1 -p 6379 --bigkeys -i 0.01\n# By memory size:\nredis-cli -h redis1 -p 6379 --memkeys -i 0.01\n# Size and length together, top 20 keys (redis-cli 7.4+):\nredis-cli -h redis1 -p 6379 --keystats --top 20 -i 0.01\n# Memory usage (SAMPLES 0: all elements) and encoding of a single suspicious key:\nredis-cli -h redis1 -p 6379 MEMORY USAGE kullanici:1001:sepet SAMPLES 0\nredis-cli -h redis1 -p 6379 OBJECT ENCODING kullanici:1001:sepet","what":"用 SCAN 扫描键空间，按类型报告元素最多和占用内存最多的 key；显示可疑 key 的实际内存消耗和编码。","when":"内存意外增长、单条命令（HGETALL、SMEMBERS、DEL）造成延迟，或集群中某个节点比其他节点占用更多内存时。","caution":"扫描使用 SCAN，不会阻塞服务器，但会遍历整个键空间；-i 0.01 每 100 次 SCAN 调用休眠一次以降低负载。用 DEL 删除大 key 会阻塞主线程；请使用 UNLINK。在大型集合上，SAMPLES 0 的 MEMORY USAGE 为 O(N)。在副本上运行不会给主节点增加负载。","since":"Redis 6.0","note":"--bigkeys 看元素数量，--memkeys 看 MEMORY USAGE；对于 list/hash 等类型，两者可能指向不同的 key。小型 hash/zset/list 使用 listpack 编码，占用内存少得多；超过 hash-max-listpack-entries 等阈值后会转换为 hashtable。在集群中需在每个主节点上分别运行。","tags":["bigkeys","memkeys","big key","memory usage","keystats","large key"]},{"id":"hotkeys-lfu","task":"查找热 key（--hotkeys、OBJECT FREQ）","category":"性能","command":"# Requires an LFU-based policy; note the current policy:\nCONFIG GET maxmemory-policy\nCONFIG SET maxmemory-policy allkeys-lfu\n# Hottest keys by access frequency:\nredis-cli -h redis1 -p 6379 --hotkeys -i 0.01\n# LFU counter of a single key:\nOBJECT FREQ urun:populer:liste\n# Revert to the previous policy when the analysis is done:\nCONFIG SET maxmemory-policy noeviction","what":"使用 LFU 计数器列出访问最频繁的 key；显示单个 key 的访问频率计数器。","when":"某个节点 CPU 接近 100%、集群中某个槽/节点的流量远超其他节点，或网络带宽被单个 key 占满时。","caution":"--hotkeys 仅在 maxmemory-policy 为 *-lfu 时有效。更改策略也会改变淘汰行为：若设置了 maxmemory 且原策略为 volatile-* 或 noeviction，allkeys-lfu 也可能淘汰没有 TTL 的 key。保持分析时间简短并还原策略。LFU 计数器是对数式的且会随时间衰减；它们不是绝对访问次数。","since":"Redis 4.0","note":"更改策略后需要一定流量，LFU 计数器才有意义。若无法更改策略，可短时用 MONITOR 采样作为替代，但在繁忙系统上 MONITOR 会严重降低吞吐量。对于热读 key，持久的解决办法是客户端缓存（CLIENT TRACKING）或拆分 key。","tags":["hotkeys","hot keys","lfu","object freq","hot key","cpu"]},{"id":"scan-unlink-pattern","task":"不用 KEYS 查找匹配模式的 key 并安全删除（SCAN + UNLINK）","category":"维护","command":"# Cursor-based scan instead of KEYS (call again with the returned cursor until it returns 0):\nSCAN 0 MATCH oturum:* COUNT 1000 TYPE string\n# Number of keys matching the pattern:\nredis-cli -h redis1 -p 6379 --scan --pattern 'oturum:*' --count 1000 | wc -l\n# Bulk delete in batches of 500, freeing memory in the background:\nredis-cli -h redis1 -p 6379 --scan --pattern 'oturum:*' --count 1000 | xargs -r -n 500 redis-cli -h redis1 -p 6379 UNLINK","what":"在不阻塞服务器的情况下用游标扫描键空间，统计匹配模式和类型的 key，并用 UNLINK 批量删除，在后台释放内存。","when":"清理旧版本应用遗留的 key、查明某个前缀下有多少 key，或原本需要在生产中使用 KEYS 时。","caution":"KEYS 一次性返回整个键空间，在大数据量下会阻塞服务器数秒；不要在生产中使用。对于扫描期间新增/删除的 key，SCAN 不提供完全保证，且可能多次返回同一个 key。包含空格或引号的 key 名在 xargs 中会出错。删除前检查数量和几个样本；无法撤销。","since":"Redis 4.0","note":"SCAN 的 TYPE 选项在 6.0 引入。COUNT 只是提示，不保证返回的元素数量。在集群中，--scan 只扫描所连接的节点；需在每个主节点上运行。UNLINK 通过 lazyfree 在单独线程中释放大值；对于小 key，其开销与 DEL 相同。","tags":["scan","unlink","keys","bulk delete","pattern","match"]},{"id":"latency-diagnosis","task":"诊断延迟来源（LATENCY DOCTOR、直方图、commandstats）","category":"性能","command":"# Enable the latency monitor (record events above 100 ms):\nCONFIG SET latency-monitor-threshold 100\nLATENCY LATEST\nLATENCY HISTORY command\nLATENCY DOCTOR\n# Per-command latency distribution and percentiles:\nLATENCY HISTOGRAM GET SET HGETALL\nINFO latencystats\n# Most expensive commands by total CPU time:\nredis-cli -h redis1 -p 6379 INFO commandstats | sort -t= -k3 -rn | head\n# On the server host, baseline latency outside Redis (VM/steal detection, 30 s):\nredis-cli --intrinsic-latency 30\nLATENCY RESET","what":"延迟监视器记录 fork、AOF fsync、过期周期和慢命令等事件引起的延迟尖峰；显示按命令统计的延迟百分位以及按总 CPU 时间排序的最昂贵命令；在主机层面测量基线延迟。","when":"应用出现 p99 延迟尖峰，且 SLOWLOG 干净但变慢投诉仍在持续时。","caution":"latency-monitor-threshold 默认为 0（关闭）；诊断后恢复原值。SLOWLOG 只测量命令执行时间，看不到网络或排队等待；LATENCY 事件还能捕获 fork 和 fsync 等服务器内部停顿。--intrinsic-latency 会消耗 CPU，必须在运行 Redis 服务器的机器上执行。","since":"Redis 7.0","note":"LATENCY HISTOGRAM 和 INFO latencystats 在 7.0 引入（latency-tracking 默认开启）。若 LATENCY HISTORY 中以 fork 事件为主，请检查数据集大小和 THP（透明大页）；若 aof-fsync-always/aof-write 事件较多，请检查磁盘延迟。commandstats 中 usec_per_call 较高的命令说明存在 O(N) 用法。","tags":["latency","slowness","latency doctor","histogram","commandstats","intrinsic-latency"]},{"id":"eviction-oom-diag","task":"诊断驱逐、过期和 OOM 错误","category":"内存","command":"INFO stats\n# Fields to check: evicted_keys, expired_keys, expired_stale_perc, keyspace_hits, keyspace_misses, total_eviction_exceeded_time (7.0+)\nINFO keyspace\nCONFIG GET maxmemory\nCONFIG GET maxmemory-policy\nINFO memory\n# The value that triggers eviction: used_memory - mem_not_counted_for_evict, compared with maxmemory","what":"显示被淘汰和已过期的 key 数量、缓存命中率、带 TTL 的 key 的占比，以及触发淘汰的内存值相对于 maxmemory 的情况。","when":"应用收到 'OOM command not allowed when used memory > maxmemory'、缓存命中率下降，或反映 key 意外消失时。","caution":"使用 noeviction 策略时，达到 maxmemory 后写命令会收到 OOM 错误，而读取仍可正常进行。使用 volatile-* 策略时，没有 TTL 的 key 永远不会被淘汰；若不存在带 TTL 的 key，其行为与 noeviction 相同（检查 INFO keyspace 中的 expires）。不要把 maxmemory 设得太接近物理内存：要为复制缓冲区和 fork 期间的写时复制留出空间。","since":"Redis 6.0","note":"命中率 = keyspace_hits / (keyspace_hits + keyspace_misses)。若 evicted_keys 持续增长而命中率下降，说明内存不足。expired_stale_perc 较高表示已过期但尚未删除的 key 占比较高；可通过 active-expire-effort 加快过期周期。副本缓冲区和 AOF 缓冲区计入 mem_not_counted_for_evict。","tags":["eviction","oom","maxmemory","expire","hit ratio","evicted_keys"]},{"id":"client-memory","task":"查找撑大内存的连接（输出缓冲区、查询缓冲区、maxmemory-clients）","category":"内存","command":"INFO clients\n# Top 10 connections by tot-mem (tot-mem, omem, qbuf, id, addr, user, cmd, age):\nredis-cli -h redis1 -p 6379 CLIENT LIST | awk '{for(i=1;i<=NF;i++){split($i,a,\"=\"); f[a[1]]=a[2]} print f[\"tot-mem\"], f[\"omem\"], f[\"qbuf\"], f[\"id\"], f[\"addr\"], f[\"user\"], f[\"cmd\"], f[\"age\"]}' | sort -rn | head -10\nCLIENT LIST TYPE pubsub\nCONFIG GET client-output-buffer-limit\n# Upper limit for the total memory of all clients (largest clients are closed when exceeded):\nCONFIG SET maxmemory-clients 5%","what":"按总内存（tot-mem）、输出缓冲区（omem）和查询缓冲区（qbuf）对连接排序；显示 pub/sub 连接和缓冲区限制；为客户端内存设置总体上限。","when":"used_memory 增长而数据没有增长、mem_clients_normal 偏高，或消费缓慢的 pub/sub/MONITOR 客户端占满内存时。","caution":"omem 很大通常说明某个客户端读取结果很慢（大型 KEYS/HGETALL 回复、缓慢的 pub/sub 订阅者、未关闭的 MONITOR）。超过 maxmemory-clients 时，占用内存最多的客户端会被无预警关闭；副本和主节点链路不受影响，但应用连接可能被断开。对关键连接使用 CLIENT NO-EVICT on。","since":"Redis 7.0","note":"maxmemory-clients 在 7.0 引入，默认为 0（无限制）。client-output-buffer-limit 对 pubsub 和 replica 类别施加硬/软限制；普通客户端默认无限制。用 CLIENT KILL ID <id> 关闭有问题的连接（参见 client-kill）。","tags":["client list","omem","output buffer","maxmemory-clients","connections","pubsub"]},{"id":"persistence-health","task":"诊断 RDB/AOF 错误状态、fork 时间及 MISCONF 的原因","category":"持久化","command":"INFO persistence\n# Critical fields: rdb_last_bgsave_status, aof_last_bgrewrite_status, aof_last_write_status, rdb_changes_since_last_save, aof_delayed_fsync\nINFO stats\n# latest_fork_usec: duration of the last fork (microseconds), total_forks\nCONFIG GET stop-writes-on-bgsave-error\nCONFIG GET dir\nCONFIG GET appendfsync","what":"显示上次 RDB 快照和 AOF 重写/写入是否成功、自上次保存以来变更的 key 数量、延迟 fsync 次数以及 fork 耗时。","when":"应用收到 'MISCONF Redis is configured to save RDB snapshots, but it's currently unable to persist to disk'、BGSAVE/BGREWRITEAOF 失败，或出现与 fork 相关的延迟时。","caution":"在 stop-writes-on-bgsave-error yes（默认）下，BGSAVE 失败后所有写入都会被拒绝。将其设为 'no' 来消除错误会掩盖数据丢失风险；先修复原因：磁盘已满、对 dir 无写权限，或 fork 无法获得内存（vm.overcommit_memory=0）。在大数据集上 fork 时间可达数百毫秒，其间 Redis 不响应。","since":"Redis 7.0","note":"Linux 上设置 vm.overcommit_memory=1 并禁用透明大页是 Redis 官方建议。aof_delayed_fsync 不断增长说明磁盘跟不上 fsync（appendfsync everysec）。错误详情见 Redis 日志（'Background saving error'、'Can't save in background: fork'）。","tags":["misconf","bgsave","aof","fork","persistence","durability"]},{"id":"aof-repair","task":"修复因 AOF 损坏而无法启动的 Redis（redis-check-aof、redis-check-rdb）","category":"持久化","command":"# With Redis stopped, copy the files first:\ncp -a /var/lib/redis/appendonlydir /var/lib/redis/appendonlydir.bak\n# Check (7.0+ multi-part AOF: pass the manifest file):\nredis-check-aof /var/lib/redis/appendonlydir/appendonly.aof.manifest\n# Repair by truncating everything after the first corrupt point (asks for confirmation):\nredis-check-aof --fix /var/lib/redis/appendonlydir/appendonly.aof.manifest\n# Verify the RDB file:\nredis-check-rdb /var/lib/redis/dump.rdb","what":"验证 AOF 文件（base + incr + manifest）和 RDB 文件；截断 AOF 不完整或损坏的尾部，使 Redis 能够再次启动。","when":"断电、磁盘写满或突然关机后，Redis 因 'Bad file format reading the append only file' 或 'Unexpected EOF' 无法启动时。","caution":"--fix 会删除从第一个损坏点到文件末尾的所有内容；若损坏位于文件中间，之后的所有写入都会丢失。确认前在输出中检查将被截断的字节数（ok_up_to、diff）。不完整的最后一条命令（如大型 HSET）会被整体丢弃。务必先做一份拷贝。","since":"Redis 7.0","note":"在 aof-load-truncated yes（默认）下，仅在末尾被截断的 AOF 会在启动时自动修剪；通常只有损坏位于文件中间时才需要 redis-check-aof。在 7.0+ 的多部分 AOF 中，文件位于 appenddirname 目录，base 文件可以是 RDB 格式。对于损坏的 RDB，redis-check-rdb 只做诊断，不做修复。","tags":["aof","redis-check-aof","corrupted","corrupt","repair","redis-check-rdb"]},{"id":"replication-backlog","task":"修复复制中断和无休止的全量重同步循环（backlog、副本缓冲区）","category":"复制","command":"INFO replication\n# Master: connected_slaves, slaveN:...,state=online,offset=,lag=  Replica: master_link_status, master_last_io_seconds_ago, master_sync_in_progress\nINFO stats\n# sync_full / sync_partial_ok / sync_partial_err: repeated full syncs = backlog too small\nCONFIG GET repl-backlog-size\nCONFIG SET repl-backlog-size 256mb\n# If the replica buffer exceeds its limit during a full sync, the link drops and sync starts over:\nCONFIG GET client-output-buffer-limit\nCONFIG SET client-output-buffer-limit \"replica 1gb 512mb 120\"","what":"显示主节点和副本上的链路状态、偏移量差异以及部分/全量同步计数器；增大复制 backlog，使短暂断连不会变成全量同步，并增大副本输出缓冲区限制，使全量同步不会中途被切断。","when":"副本不断循环出现 'MASTER <-> REPLICA sync started'、sync_full 计数器增长，或主节点日志显示 'scheduled to be closed ASAP for overcoming of output buffer limits' 时。","caution":"每次全量同步都意味着主节点上的一次 fork + RDB 生成；循环会给主节点的内存和磁盘带来压力。repl-backlog-size 从主节点内存中分配。提高副本缓冲区限制意味着全量同步期间主节点可能额外使用这么多内存；请将其计入 maxmemory 计算。需要 CONFIG REWRITE 或 redis.conf 才能持久化更改。","since":"Redis 4.0","note":"backlog 大小的粗略估算：主节点每秒写入量（master_repl_offset 的增长）x 需要容忍的断连时长。自 5.0 起 client-output-buffer-limit 中可以用 'replica' 类别名代替 'slave'；CONFIG GET 输出可能仍显示 slave。若磁盘较慢，repl-diskless-sync yes（7.0+ 默认）会通过 socket 流式传输 RDB 而不写入磁盘。","tags":["replication","backlog","full sync","psync","replica","output buffer"]},{"id":"sentinel-status","task":"检查 Sentinel 状态、当前主节点和法定人数","category":"Sentinel","command":"redis-cli -p 26379 SENTINEL MASTERS\nredis-cli -p 26379 SENTINEL GET-MASTER-ADDR-BY-NAME mymaster\nredis-cli -p 26379 SENTINEL REPLICAS mymaster\nredis-cli -p 26379 SENTINEL SENTINELS mymaster\n# Are there enough sentinels (quorum and majority) for a failover:\nredis-cli -p 26379 SENTINEL CKQUORUM mymaster\nredis-cli -p 26379 INFO sentinel","what":"列出 Sentinel 监控的主节点、当前主节点地址、已知的副本和其他 sentinel；验证故障转移所需的法定人数（quorum）和多数派是否可用。","when":"应用连接到错误节点时、故障转移后验证拓扑时，以及每次维护之前。","caution":"每个 sentinel 报告的是其自身视角；GET-MASTER-ADDR-BY-NAME 必须在所有 sentinel 上返回相同的地址，否则说明存在网络分区或配置不一致。flags 字段中的 s_down/o_down 或副本上的 disconnected 需要紧急排查。Sentinel 端口（26379）在防火墙中应只对 sentinel 之间以及客户端开放。","since":"Redis 5.0","note":"5.0 中 SENTINEL REPLICAS 取代了 SENTINEL SLAVES（旧名称仍可使用）。sentinel 数量应为奇数且至少 3 个，分布在不同的故障域（主机/机架/可用区）。若 Redis 使用 ACL/密码，必须在 sentinel.conf 中设置 sentinel auth-user/auth-pass。","tags":["sentinel","master","quorum","ckquorum","topology","failover"],"untested":true},{"id":"sentinel-failover","task":"用 Sentinel 执行手动故障转移并清理拓扑","category":"Sentinel","command":"# First check quorum and an eligible replica:\nredis-cli -p 26379 SENTINEL CKQUORUM mymaster\nredis-cli -p 26379 SENTINEL REPLICAS mymaster\n# Manual failover without waiting for agreement from other sentinels:\nredis-cli -p 26379 SENTINEL FAILOVER mymaster\n# Verify the new master (must be the same on all sentinels):\nredis-cli -p 26379 SENTINEL GET-MASTER-ADDR-BY-NAME mymaster\n# If old/removed replicas remain listed, reset sentinels one at a time, 30 s apart:\nredis-cli -p 26379 SENTINEL RESET mymaster","what":"即使主节点健康，也将主节点角色交给最合适的副本；然后验证新主节点，并从 sentinel 的记忆中清除已不存在的副本。","when":"主节点所在主机进行计划内维护之前、故障转移演练期间，或将主节点迁移到另一个数据中心时。","caution":"故障转移期间会有几秒的写入中断，已写入主节点但尚未复制的数据可能丢失。replica-priority 为 0 的副本永远不会被选中；若没有合格的副本，命令会返回错误。若在所有 sentinel 上同时运行 SENTINEL RESET，拓扑信息会暂时丢失；请在每个 sentinel 上分别、错开时间运行。确保客户端使用会向 Sentinel 查询主节点地址的驱动。","since":"Redis 5.0","note":"SENTINEL FAILOVER 不需要其他 sentinel 同意，但会传播新的 config epoch。选择顺序：replica-priority（越小越优先）、复制偏移量（越大越优先）、run ID。故障转移后旧主节点恢复时，Sentinel 会自动将其重新配置为新主节点的副本。","tags":["sentinel","failover","manual failover","master","reset","switchover"],"untested":true},{"id":"cluster-check-fix","task":"检查集群健康状况并修复槽不一致（--cluster check/fix）","category":"集群","command":"redis-cli -h node1 -p 6379 CLUSTER INFO\nredis-cli -h node1 -p 6379 CLUSTER NODES\nredis-cli -h node1 -p 6379 CLUSTER SHARDS\n# Slot coverage, open (migrating/importing) slots and node consistency:\nredis-cli --cluster check node1:6379\n# Repair if there are open or unowned slots:\nredis-cli --cluster fix node1:6379\n# Which slot and node a key lives on (-c: follow redirects):\nredis-cli -h node1 -p 6379 CLUSTER KEYSLOT kullanici:1001\nredis-cli -c -h node1 -p 6379 GET kullanici:1001","what":"检查所有节点是否看到相同的槽映射、16384 个槽是否全部分配，以及是否存在未完成（migrating/importing）的槽；修复发现的问题，并显示某个 key 位于哪个槽/节点。","when":"出现 cluster_state:fail、客户端收到 CLUSTERDOWN 或持续的 MOVED/ASK 错误，以及 reshard 被中断之后。","caution":"--cluster fix 可能把无主槽分配给任意节点，并移动处于打开状态的槽中的 key；请先查看 check 的输出。若某个主节点及其所有副本都丢失，这些槽中的数据就没了；fix 只会把该槽以空槽形式分配给另一个节点。在 cluster-require-full-coverage yes（默认）下，只要有一个无主槽就会停止整个集群的写入。","since":"Redis 7.0","note":"CLUSTER SHARDS 在 7.0 引入，取代 CLUSTER SLOTS。若启用了认证，请在 --cluster 命令中加上 -a <password> 或 --user/--pass。CLUSTER NODES 输出中的 fail/pfail 标记表示其他节点认为该节点不可达。","tags":["cluster","check","fix","slot","clusterdown","cluster nodes"],"untested":true},{"id":"cluster-reshard","task":"在集群中添加节点、移动槽和移除节点（add-node、reshard、rebalance）","category":"集群","command":"# Add the new master to the cluster (joins empty, with no slots):\nredis-cli --cluster add-node node7:6379 node1:6379\n# Attach a new replica to a specific master:\nredis-cli --cluster add-node node8:6379 node1:6379 --cluster-slave --cluster-master-id <master-node-id>\n# Move 1000 slots from all masters to the new node:\nredis-cli --cluster reshard node1:6379 --cluster-from all --cluster-to <node7-id> --cluster-slots 1000 --cluster-yes\n# Or rebalance automatically (give slots to empty masters too):\nredis-cli --cluster rebalance node1:6379 --cluster-use-empty-masters\n# Remove a node whose slots have been emptied:\nredis-cli --cluster del-node node1:6379 <node-id>","what":"向集群添加新的主节点或副本，在线在节点之间移动槽及其 key，自动重新平衡槽分布，并将已清空的节点从集群中移除。","when":"横向扩展内存/吞吐量、分散热点节点负载，或下线节点时。","caution":"槽迁移通过 MIGRATE 逐个传输 key；迁移大 key 时，两个节点都可能短暂阻塞。迁移期间客户端会收到 ASK 重定向；需要支持集群的客户端。若 reshard 被中断，槽会停留在 migrating/importing 状态；用 --cluster fix 完成。不要用 del-node 移除仍拥有槽的主节点；先移走它的槽。","since":"Redis 5.0","note":"节点 ID 来自 CLUSTER NODES 或 CLUSTER MYID。--cluster-pipeline 设置每次 MIGRATE 调用移动多少个 key（默认 10）。--cluster rebalance 配合 --cluster-weight 可以为节点设置不同权重。5.0 中 redis-cli --cluster 命令取代了 redis-trib.rb。","tags":["cluster","reshard","rebalance","add-node","slot","del-node"],"untested":true},{"id":"acl-users","task":"创建 ACL 用户、测试权限并使其持久化","category":"安全","command":"# Application user: only app:* keys and channels, dangerous commands excluded:\nACL SETUSER app_rw on >S3cret-Change ~app:* resetchannels &app:* +@read +@write -@dangerous\n# Monitoring user: no key access, only specific commands:\nACL SETUSER monitor on >M0n-Secret resetkeys resetchannels -@all +info +ping +client|list +slowlog|get +latency +config|get\n# Test permissions without running the command:\nACL DRYRUN app_rw GET app:1001\nACL GETUSER app_rw\nACL LIST\n# Make it persistent: ACL SAVE if aclfile is set, otherwise CONFIG REWRITE into redis.conf\nACL SAVE","what":"创建按 key 前缀、pub/sub 频道和命令类别授权的应用和监控用户；用 DRYRUN 在不执行命令的情况下测试命令是否被允许，并将 ACL 写入磁盘。","when":"从单一共享密码（requirepass）迁移到按用户访问、隔离各应用的 key，或为监控工具创建只读账户时。","caution":"规则顺序很重要：resetchannels 会删除在它之前给出的所有 &channel 规则。-@dangerous 也会阻止 KEYS、FLUSHALL、CONFIG 和 DEBUG 等命令；用 DRYRUN 验证应用使用的命令。若 default 用户以 nopass 和 +@all 保持启用，ACL 就起不到保护作用；创建管理员用户后运行 ACL SETUSER default off。在 redis-cli 中，若使用 --user/--pass 的 AUTH 失败，连接会以 default 用户继续，命令照样执行；因此权限测试请使用 DRYRUN。","since":"Redis 7.0","note":"ACL 在 6.0 引入；ACL DRYRUN、选择器和子命令规则（client|list）在 7.0 新增。aclfile 和 redis.conf 中的 user 行不能同时使用。密码以 SHA-256 哈希存储；ACL GETUSER 不显示明文密码。","tags":["acl","setuser","users","permissions","dryrun","security"]},{"id":"acl-log-denied","task":"排查 NOPERM 和 WRONGPASS 错误（ACL LOG）","category":"安全","command":"# Last 10 permission denials and failed logins (reason: auth/command/key/channel):\nACL LOG 10\n# User of this connection:\nACL WHOAMI\n# Cumulative denial counters:\nINFO stats\n# acl_access_denied_auth, acl_access_denied_cmd, acl_access_denied_key, acl_access_denied_channel\nACL LOG RESET","what":"列出被拒绝的命令、key 和频道访问以及失败的 AUTH 尝试，包含用户名、对象、重复次数和客户端信息（IP、最后一条命令）；显示累计拒绝计数器。","when":"部署后应用收到 NOPERM 或 WRONGPASS 时、查找被收紧的 ACL 规则破坏的命令，或检测密码猜测时。","caution":"ACL LOG 存在于内存中，重启后清空，且最多保存 acllog-max-len（默认 128）条记录；同一错误重复出现时会增加 count 而不是新增一行。client-info 字段包含客户端 IP 和所用用户；重置前记录所需信息。","since":"Redis 7.0","note":"acl_access_denied_* 计数器在 7.2 中加入 INFO stats；将其接入监控，在增长时告警。entry-id 和 timestamp-created/last-updated 字段在 7.2 引入。密码错误的尝试显示为 reason=auth，缺少命令权限显示为 reason=command。","tags":["acl log","noperm","wrongpass","access denied","auth","security"]},{"id":"keyspace-notifications","task":"用 keyspace 通知监控 key 删除和过期事件","category":"维护","command":"CONFIG GET notify-keyspace-events\n# E: keyevent channel, x: expire events, g: generic commands (DEL, EXPIRE, RENAME):\nCONFIG SET notify-keyspace-events Exg\n# Listen to expire and del events in db 0:\nredis-cli -h redis1 -p 6379 --csv PSUBSCRIBE '__keyevent@0__:expired' '__keyevent@0__:del'\n# Turn off:\nCONFIG SET notify-keyspace-events \"\"","what":"把 key 上的事件（expire、del、set 等）发布到 pub/sub 频道，从而实时观察哪个 key 在何时被删除或过期。","when":"用于证明 key“自行消失”时是哪条命令或过期操作删除了它们，或为依赖过期事件的工作流（会话关闭）提供输入。","caution":"事件会增加 CPU 和网络负载；在繁忙系统上 'A'（所有类别）尤其昂贵，因此只启用需要的类别，用完后关闭。Pub/sub 是发后即忘的：未连接的订阅者会错过事件，且不会持久化任何内容。expired 事件是在 key 被实际删除时（访问时或由后台过期周期删除）产生的，而不是在 TTL 到期的那一刻。","since":"Redis 2.8","note":"对于被淘汰删除的 key，添加 e（evicted）类别。在集群中，事件只在持有该 key 的节点上发布；需分别订阅每个主节点。如需持久设置，请在 redis.conf 中设置 notify-keyspace-events。","tags":["keyspace notifications","notify-keyspace-events","expired","pubsub","events","psubscribe"]}]},"recipes/elasticsearch.json":{"engineId":"elasticsearch","engine":"Elasticsearch","order":7,"recipes":[{"id":"flush","task":"刷新索引（flush）","category":"索引生命周期","command":"POST /customer/_flush","what":"执行 Lucene commit：将内存中的索引数据以段的形式持久写入磁盘，并修剪不再需要的 translog 条目。","when":"在维护或重启前缩小 translog。","caution":"通常自动完成；手动 flush 会使 IO 略有增加。","tags":["flush","translog","index","customer","maintenance"]},{"id":"force-merge","task":"合并索引段（force merge）","category":"索引生命周期","command":"POST /customer/_forcemerge?max_num_segments=1","what":"合并索引段以减少段数并提升读取性能。","when":"仅用于不再写入（只读）的索引，以提升查询性能。","caution":"消耗大量 IO 和 CPU；切勿在正在写入的索引上运行。可能耗时很长且无法取消。","tags":["forcemerge","segment","merge","merge segments","performance"]},{"id":"rollover","task":"通过别名进行 rollover","category":"索引生命周期","command":"POST /customer-alias/_rollover\n{\n  \"conditions\": {\n    \"max_age\": \"7d\",\n    \"max_docs\": 100000000,\n    \"max_primary_shard_size\": \"50gb\"\n  }\n}","what":"当别名指向的写入索引满足条件时，创建新索引并将别名指向它。","when":"用于管理时序数据的索引大小（ILM 的替代或补充）。","caution":"别名必须通过 is_write_index 拥有唯一的写入索引；配置错误会阻止 rollover。","tags":["rollover","alias","index","time series","ilm"]},{"id":"set-refresh-interval","task":"更改刷新间隔","category":"索引生命周期","command":"PUT /customer/_settings\n{\n  \"index\": { \"refresh_interval\": \"30s\" }\n}","what":"设置索引使新文档可被搜索的频率（refresh）。","when":"批量加载期间增大间隔以加快索引速度时。","caution":"-1 会完全禁用 refresh；文档将无法被搜索。加载完成后将其恢复为合理的值。","tags":["refresh","interval","settings","bulk","refresh interval"]},{"id":"set-replicas","task":"更改副本数","category":"索引生命周期","command":"PUT /customer/_settings\n{\n  \"index\": { \"number_of_replicas\": 2 }\n}","what":"动态更改索引的副本分片数量。","when":"用于增加/减少冗余或读取能力。","caution":"增加副本会产生额外的磁盘占用和网络流量（恢复）；降为 0 会失去冗余。","tags":["replica","settings","number_of_replicas","redundancy","capacity"]},{"id":"open-close-index","task":"打开或关闭索引","category":"索引生命周期","command":"POST /customer/_close\nPOST /customer/_open","what":"关闭索引的读写（释放资源）或重新打开索引。","when":"用于降低很少使用的索引的资源占用，或更改某些设置。","caution":"已关闭的索引无法查询或写入；关闭期间除通过快照外无法访问。","tags":["open","close","index","open index","close index"]},{"id":"node-join-note","task":"向集群添加节点","category":"集群/节点","command":"GET /_cat/nodes?v","what":"在 elasticsearch.yml 中使用相同的 cluster.name 和正确的 discovery.seed_hosts 启动新节点即可；此命令用于验证节点是否已加入。","when":"向集群添加新节点时验证成员身份。","caution":"7.0+ 中没有 minimum_master_nodes；投票配置自动管理。每次只添加或移除一个具备主节点资格的节点，绝不要一次移除一半或更多；永久移除多个具备主节点资格的节点时，先使用 POST _cluster/voting_config_exclusions?node_names=....","tags":["node","join","cluster","cat","add node"]},{"id":"node-decommission","task":"排空节点（下线）","category":"集群/节点","command":"PUT /_cluster/settings\n{\n  \"persistent\": {\n    \"cluster.routing.allocation.exclude._name\": \"node-3\"\n  }\n}","what":"将指定节点上的分片迁移到其他节点，从而安全地排空该节点。","when":"在节点退役前把其数据迁走。","caution":"分片重新分配会产生大量网络/IO 负载；确保其余节点有足够的磁盘和容量。节点移除后用 null 删除该设置。transient 设置自 7.16 起已弃用；请使用 persistent。","tags":["decommission","exclude","allocation","drain","node"]},{"id":"reroute-shard","task":"手动移动分片（reroute）","category":"集群/节点","command":"POST /_cluster/reroute\n{\n  \"commands\": [\n    {\n      \"move\": {\n        \"index\": \"customer\",\n        \"shard\": 0,\n        \"from_node\": \"node-1\",\n        \"to_node\": \"node-2\"\n      }\n    }\n  ]\n}","what":"手动将特定分片从一个节点移动到另一个节点。","when":"用于修正不均衡的分片分布或排空特定节点。","caution":"可能与自动再平衡冲突；移动大分片会产生网络和 IO 负载。","tags":["reroute","shard","move","relocate","cluster"]},{"id":"allocation-enable","task":"启用或禁用分片分配","category":"集群/节点","command":"PUT /_cluster/settings\n{\n  \"persistent\": {\n    \"cluster.routing.allocation.enable\": \"primaries\"\n  }\n}","what":"在集群范围内限制分片分配（primaries：只分配主分片；none：不分配任何分片），或恢复为默认值（all / null）。","when":"在滚动重启之前，用于防止不必要的副本重新分配（官方推荐值为 \"primaries\"）。","caution":"事后记得用 null（或 \"all\"）还原该设置；若一直受限，副本将保持未分配状态。transient 设置已弃用；请使用 persistent。","tags":["allocation","enable","restart","shard","cluster"]},{"id":"cluster-settings","task":"更改集群设置","category":"集群/节点","command":"PUT /_cluster/settings\n{\n  \"persistent\": {\n    \"indices.recovery.max_bytes_per_sec\": \"100mb\"\n  }\n}","what":"更改集群范围的 persistent 或 transient 设置。","when":"调整恢复速度等集群行为时。","caution":"persistent 设置在重启后保留；transient 设置会丢失，且自 7.16 起已弃用，请勿使用。要将设置恢复为默认值，将其值设为 null。错误的值会影响集群稳定性。","tags":["cluster","settings","persistent","transient","setting"]},{"id":"snapshot-repo","task":"注册快照仓库","category":"快照","command":"PUT /_snapshot/backup_repo\n{\n  \"type\": \"fs\",\n  \"settings\": {\n    \"location\": \"/mnt/es_backups\"\n  }\n}","what":"注册用于存储快照的仓库。","when":"首次搭建备份基础设施时。","caution":"对于 fs 仓库，路径必须在 path.repo 中定义，并且所有主节点和数据节点都能访问。","tags":["snapshot","repository","repo","backup","register"]},{"id":"snapshot-create","task":"创建快照","category":"快照","command":"PUT /_snapshot/backup_repo/snap1?wait_for_completion=false\n{\n  \"indices\": \"customer\",\n  \"include_global_state\": false\n}","what":"将指定索引的时间点备份（快照）写入仓库。","when":"用于定期备份，或在重大变更前做安全备份。","caution":"在大索引上 wait_for_completion=true 会让 HTTP 连接长时间等待；大型快照请使用 false。","tags":["snapshot","create","backup","backup snapshot","take"]},{"id":"snapshot-restore","task":"从快照还原","category":"快照","command":"POST /_snapshot/backup_repo/snap1/_restore\n{\n  \"indices\": \"customer\",\n  \"rename_pattern\": \"customer\",\n  \"rename_replacement\": \"customer_restored\"\n}","what":"将快照中的索引还原到集群（可选择重命名）。","when":"用于数据丢失后的恢复，或将备份迁移到测试环境。","caution":"要以相同名称还原，目标索引必须已关闭或删除；不能覆盖打开状态的索引进行还原。","tags":["snapshot","restore","restore snapshot","recovery","backup"]},{"id":"cache-clear","task":"清除缓存","category":"维护","command":"POST /customer/_cache/clear","what":"清除索引的 query、request 和 fielddata 缓存。","when":"用于降低内存压力或解决 fielddata 问题。","caution":"清除缓存后，在缓存重新预热之前，最初的查询会变慢。","tags":["cache","clear","clear cache","fielddata","maintenance"]},{"id":"pending-tasks","task":"查看待处理的集群任务","category":"维护","command":"GET /_cluster/pending_tasks","what":"列出在主节点上等待处理的集群级任务。","when":"集群变慢或更新卡住时诊断主节点负载。","caution":"只读命令；无风险。","tags":["pending","tasks","master","diagnostics","pending tasks"]},{"id":"hot-threads","task":"获取 hot threads 分析","category":"维护","command":"GET /_nodes/hot_threads","what":"显示节点上消耗 CPU 最多的线程的堆栈跟踪。","when":"诊断 CPU 使用率高的来源时。","caution":"只读命令；输出可能很长，可用 thread_type 参数缩小范围。","tags":["hotthreads","cpu","diagnostics","thread","performance"]},{"id":"cat-shards","task":"查看分片分布","category":"维护","command":"GET /_cat/shards?v&s=state","what":"列出所有分片及其索引、类型（primary/replica）、状态和所在节点。","when":"检查分片放置情况和未分配分片时。","caution":"只读命令；无风险。","tags":["cat","shards","distribution","state","shard"]},{"id":"allocation-explain","task":"查找分片未分配的原因","category":"维护","command":"GET /_cluster/allocation/explain\n{\n  \"index\": \"customer\",\n  \"shard\": 0,\n  \"primary\": true\n}","what":"详细说明特定分片无法分配（unassigned）的原因。","when":"在 yellow/red 集群中查找未分配分片的原因时。","caution":"只读诊断命令；无风险。","tags":["allocation","explain","unassigned","diagnostics","shard"]},{"id":"cluster-health","task":"查看集群健康状况","category":"维护","command":"GET /_cluster/health","what":"显示集群整体健康状况（green/yellow/red）以及活动和未分配分片的数量。","when":"快速检查集群状态时。","caution":"只读命令；无风险。","tags":["cluster","health","health check","status","green"]},{"id":"refresh-index","task":"手动刷新索引","category":"索引生命周期","command":"POST /customer/_refresh","what":"使索引中待处理的变更立即可被搜索。","when":"希望批量加载后数据立即可查询时。","caution":"频繁手动 refresh 会产生大量小段并降低性能。","tags":["refresh","index","search","segment","customer"]}]},"recipes/elasticsearch-ext.json":{"engineId":"elasticsearch","engine":"Elasticsearch","order":2,"recipes":[{"id":"red-yellow-triage","task":"red/yellow 集群排查流程（哪个索引、哪个分片、为什么）","category":"集群/节点","command":"GET _cluster/health?filter_path=status,unassigned_shards,initializing_shards,delayed_unassigned_shards,number_of_pending_tasks\nGET _health_report?verbose=false\nGET _cat/indices?v&health=red&h=health,index,pri,rep,docs.count,store.size\nGET _cat/indices?v&health=yellow&h=health,index,pri,rep\nGET _cat/shards?v&h=index,shard,prirep,state,unassigned.reason,unassigned.for,node&s=state\n# Without a body, the call explains the first unassigned shard:\nGET _cluster/allocation/explain","what":"给出有序的排查流程：从集群状态到问题索引，再到未分配分片及其未被分配的原因（unassigned.reason、allocation explain）；_health_report 汇总哪个指标（磁盘、分片可用性、主节点）不健康。","when":"作为集群变为 red 或 yellow 时的第一响应。","caution":"red：至少有一个主分片未分配；对该索引的写入以及命中该分片的搜索会失败。yellow：所有主分片都在，但缺少副本；数据可访问但没有冗余。在单节点集群上，replicas > 0 的索引会一直保持 yellow（same_shard 决策）。若有节点刚离开，delayed_unassigned_shards 会被有意保留，直到 index.unassigned.node_left.delayed_timeout（默认 1m）到期。","since":"Elasticsearch 8.7","note":"_health_report 在 8.7 引入；在较旧版本上使用前四步。unassigned.reason 取值：NODE_LEFT（节点离开）、ALLOCATION_FAILED（达到重试上限，参见 retry-failed-allocation）、INDEX_CREATED、CLUSTER_RECOVERED。针对特定分片，请使用带 index/shard/primary 请求体的 allocation-explain 操作指南。","tags":["red","yellow","cluster health","unassigned","health report","triage"]},{"id":"retry-failed-allocation","task":"重新分配达到重试上限的分片（reroute retry_failed）","category":"集群/节点","command":"# Attempt count and last failure details:\nGET _cluster/allocation/explain?filter_path=index,shard,unassigned_info.reason,unassigned_info.failed_allocation_attempts,unassigned_info.details,allocate_explanation\n# After fixing the cause, retry shards that hit the limit:\nPOST _cluster/reroute?retry_failed=true&metric=none\n# Recovery progress:\nGET _cat/recovery?v&active_only=true&h=index,shard,stage,source_node,target_node,bytes_percent,translog_ops_percent","what":"显示因连续失败而停止分配（ALLOCATION_FAILED、max_retry）的分片的失败详情，并再次触发分配；监控恢复进度。","when":"allocation explain 显示 'shard has exceeded the maximum number of retries' 时；在磁盘已满、translog 损坏或临时网络问题解决之后。","caution":"不修复原因就运行 retry_failed，分片会再尝试 5 次并再次失败；先解决 unassigned_info.details 中的错误。若大量分片同时进入恢复，网络和磁盘负载会上升；受 cluster.routing.allocation.node_concurrent_recoveries 和 indices.recovery.max_bytes_per_sec 限制。","since":"Elasticsearch 7.0","note":"重试上限为 index.allocation.max_retries（默认 5）。Elasticsearch 也会定期重试，但 retry_failed 免去了等待。metric=none 可避免在响应中返回庞大的集群状态。","tags":["reroute","retry_failed","allocation failed","max_retries","unassigned","recovery"]},{"id":"allocate-stale-primary","task":"作为最后手段分配丢失的主分片（allocate_stale_primary）","category":"集群/节点","command":"# Which nodes hold copies of the primary (allocation id, store errors):\nGET _shard_stores/musteri?status=red\n# Last resort: promote a stale copy to primary; data loss is accepted:\nPOST _cluster/reroute\n{\n  \"commands\": [\n    {\n      \"allocate_stale_primary\": {\n        \"index\": \"musteri\",\n        \"shard\": 0,\n        \"node\": \"node-2\",\n        \"accept_data_loss\": true\n      }\n    }\n  ]\n}","what":"当持有最新主分片副本的节点永久丢失时，将另一节点上较旧的（非 in-sync）副本指定为主分片，使索引脱离 red。","when":"仅在承载主分片的节点无法恢复，且无法从快照还原或还原耗时更长时。","caution":"陈旧副本不包含最新的写入；该副本之后的所有写入都会永久丢失。若丢失的节点之后回归，其上较新的副本会被删除。先考虑恢复节点和从快照还原。allocate_empty_primary 会创建完全空的分片并删除该分片中的所有数据；仅在没有任何副本留存时使用。","since":"Elasticsearch 7.0","note":"在 _shard_stores 输出中，分配类型显示为 'primary'、'replica' 或 'unused'；不要选择带有 store_exception 的副本。可先以 ?dry_run=true&explain=true 运行命令查看决策。操作完成后，从应用侧重新索引以弥补数据缺口。","tags":["allocate_stale_primary","red","primary","data loss","shard_stores","reroute"],"untested":true},{"id":"disk-watermark","task":"管理磁盘水位线并解除 flood-stage 写入阻塞","category":"集群/节点","command":"GET _cat/allocation?v&h=node,shards,disk.indices,disk.used,disk.avail,disk.percent&s=disk.percent:desc\nGET _cluster/settings?include_defaults=true&filter_path=*.cluster.routing.allocation.disk\n# Adjust thresholds temporarily (null returns to the default):\nPUT _cluster/settings\n{\n  \"persistent\": {\n    \"cluster.routing.allocation.disk.watermark.low\": \"85%\",\n    \"cluster.routing.allocation.disk.watermark.high\": \"90%\",\n    \"cluster.routing.allocation.disk.watermark.flood_stage\": \"95%\"\n  }\n}\n# After freeing space, remove the flood-stage write block:\nPUT _all/_settings\n{\n  \"index.blocks.read_only_allow_delete\": null\n}","what":"显示每个节点的磁盘使用情况和实际生效的水位线值；调整 low/high/flood_stage 阈值，并解除在 flood stage 时施加到索引上的 read_only_allow_delete 阻塞。","when":"写入被拒绝并报 'TOO_MANY_REQUESTS/12/disk usage exceeded flood-stage watermark, index has read-only-allow-delete block'、新分片未被分配，或分片开始从已满节点迁出时。","caution":"超过 low 时不再向该节点分配新分片；超过 high 时分片会从该节点迁出；超过 flood_stage 时，在该节点上有分片的索引会变为只读。提高阈值只能争取时间；持久的解决办法是删除旧索引、减少副本或增加磁盘/节点。若磁盘完全写满，分片可能损坏。","since":"Elasticsearch 8.5","note":"在 7.4+ 中，磁盘使用率降到 high 水位线以下后阻塞会自动解除；手动解除可缩短等待。在 8.5+ 中，大磁盘使用 max_headroom（low 200GB、high 150GB、flood 100GB）作为绝对空闲空间，而不是百分比；这些默认 headroom 仅在水位线保持默认值时生效，因此像上面那样显式设置百分比会使其失效。水位线也适用于单数据节点集群。","tags":["disk watermark","flood_stage","read_only_allow_delete","disk full","allocation","cat allocation"]},{"id":"rolling-restart","task":"滚动重启/滚动升级（逐个节点，避免不必要的分片移动）","category":"集群/节点","command":"# 1) Disable replica allocation (only primaries are allocated):\nPUT _cluster/settings\n{\n  \"persistent\": { \"cluster.routing.allocation.enable\": \"primaries\" }\n}\n# 2) Optional: stop indexing and flush to speed up recovery:\nPOST _flush\n# 3) Stop one node, do the maintenance, start it; verify it joined:\nGET _cat/nodes?v&h=name,version,node.role,master,heap.percent\n# 4) Re-enable allocation:\nPUT _cluster/settings\n{\n  \"persistent\": { \"cluster.routing.allocation.enable\": null }\n}\n# 5) Wait for green before moving to the next node:\nGET _cluster/health?wait_for_status=green&timeout=10m","what":"给出逐个重启或升级节点的顺序，同时在节点停机时防止副本被重新复制到其他节点；节点回归后，分片可从本地副本快速恢复。","when":"因操作系统补丁、JVM/heap 变更、elasticsearch.yml 变更或小/大版本升级而依次重启节点时。","caution":"不等到 green 就不要处理下一个节点；同时有两个节点停机时，某个分片的所有副本可能都不存在了。具备主节点资格的节点最后重启，且每次一个；若失去多数派，集群将无法选出主节点。若忘记重新启用 allocation.enable，副本会一直未分配。升级时先升级数据节点（先 frozen/cold，最后 hot），再升级主节点。","since":"Elasticsearch 7.16","note":"若有 ML 作业，开始时运行 POST _ml/set_upgrade_mode?enabled=true，结束时运行 enabled=false。Synced flush 在 8.0 中已移除；普通 _flush 即可。由于 transient 设置自 7.16 起已弃用，请使用 persistent。对于短时重启，也可以调高 index.unassigned.node_left.delayed_timeout，而不是禁用分配。","tags":["rolling restart","rolling upgrade","allocation","primaries","maintenance","upgrade"],"untested":true},{"id":"shard-sizing","task":"审查分片大小和分片数（过度分片/超大分片检测）","category":"维护","command":"# Largest shards (GB):\nGET _cat/shards?v&h=index,shard,prirep,state,docs,store,node&s=store:desc&bytes=gb\n# Indices by primary size:\nGET _cat/indices?v&h=index,pri,rep,docs.count,pri.store.size,store.size&s=pri.store.size:desc&bytes=gb\n# Shard count and disk per node:\nGET _cat/allocation?v&h=node,shards,disk.indices,disk.percent&s=shards:desc\nGET _cluster/stats?filter_path=indices.shards.total,indices.shards.primaries,nodes.count.data\nGET _cluster/settings?include_defaults=true&filter_path=*.cluster.max_shards_per_node","what":"列出最大的分片、索引的主分片大小、每个节点的分片数、分片总数以及 cluster.max_shards_per_node 限制。","when":"容量规划期间、恢复时间变长或 heap 压力上升时，以及出现错误 'this action would add [x] shards, but this cluster currently has [y]/[z] maximum normal shards open' 时。","caution":"大量很小的分片（过度分片）会给主节点和 heap 带来压力；很大的分片会使恢复和再平衡耗时数小时。索引创建后主分片数无法更改；需要 shrink、split 或 reindex 来修正。提高 max_shards_per_node 只是去掉限制，并不能解决问题。","since":"Elasticsearch 8.3","note":"Elastic 的一般建议：每个分片 10-50 GB，最多约 2 亿个文档。在 8.3+ 中，旧的“每 GB heap 20 个分片”规则被基于映射字段数量的 heap 估算取代。对于时序数据，在 rollover 中用 max_primary_shard_size 固定分片大小。","tags":["shard size","oversharding","cat shards","max_shards_per_node","capacity","sizing"]},{"id":"shrink-index","task":"收缩分片过多的索引（shrink）","category":"索引生命周期","command":"# 1) Block writes on the source and gather all shard copies on one node:\nPUT log-2026.09/_settings\n{\n  \"index.number_of_replicas\": 0,\n  \"index.routing.allocation.require._name\": \"node-1\",\n  \"index.blocks.write\": true\n}\nGET _cluster/health/log-2026.09?wait_for_no_relocating_shards=true&timeout=30m\n# 2) Create a new index with fewer shards (the target must be a factor of the source):\nPOST log-2026.09/_shrink/log-2026.09-shrunk\n{\n  \"settings\": {\n    \"index.number_of_shards\": 1,\n    \"index.number_of_replicas\": 1,\n    \"index.routing.allocation.require._name\": null,\n    \"index.blocks.write\": null,\n    \"index.codec\": \"best_compression\"\n  }\n}","what":"通过将段硬链接到新索引，减少已不再写入的索引的主分片数；降低 heap 和分片数开销。","when":"每日索引创建时分片数多于实际需要、接近分片数上限，或在将旧索引移到 warm 层之前。","caution":"shrink 期间源索引禁止写入；仅对不再写入的索引执行。目标分片数必须是源分片数的因数（8 -> 4、2 或 1）。汇集所有副本的节点需要与源索引同等大小的空闲磁盘。replicas 为 0 时源分片只有一个副本；在操作完成且新索引变为 green 之前，不要删除源索引。","since":"Elasticsearch 7.0","note":"shrink 完成后，删除源索引或将别名切换到新索引。由于同一节点可以在同一文件系统上硬链接文件，操作通常很快；否则会复制文件。要增加分片数请使用 _split；要用 ILM 自动化，请在 warm 阶段使用 shrink action。","tags":["shrink","shard count","oversharding","index","blocks.write","warm"]},{"id":"ilm-policy-bootstrap","task":"定义 ILM 策略、通过模板关联并引导 rollover 链","category":"索引生命周期","command":"PUT _ilm/policy/log-policy\n{\n  \"policy\": {\n    \"phases\": {\n      \"hot\": { \"actions\": { \"rollover\": { \"max_primary_shard_size\": \"50gb\", \"max_age\": \"1d\" }, \"set_priority\": { \"priority\": 100 } } },\n      \"warm\": { \"min_age\": \"7d\", \"actions\": { \"shrink\": { \"number_of_shards\": 1 }, \"forcemerge\": { \"max_num_segments\": 1 }, \"set_priority\": { \"priority\": 50 } } },\n      \"delete\": { \"min_age\": \"30d\", \"actions\": { \"delete\": {} } }\n    }\n  }\n}\nPUT _index_template/log\n{\n  \"index_patterns\": [\"log-*\"],\n  \"priority\": 500,\n  \"template\": {\n    \"settings\": {\n      \"number_of_shards\": 1,\n      \"number_of_replicas\": 1,\n      \"index.lifecycle.name\": \"log-policy\",\n      \"index.lifecycle.rollover_alias\": \"log\"\n    }\n  }\n}\n# Create the first index with the write alias (its name must end with a number such as -000001):\nPUT log-000001\n{\n  \"aliases\": { \"log\": { \"is_write_index\": true } }\n}","what":"创建包含 hot（rollover）、warm（shrink + force merge）和 delete 阶段的 ILM 策略，通过模板将策略和 rollover 别名应用到新索引，并创建带写入别名的链中第一个索引。","when":"为日志或指标等时序数据自动管理索引大小、保留和删除时。","caution":"warm 和 delete 阶段的 min_age 从 rollover 时间开始计算，而不是从索引创建时间。策略变更只有在现有索引的当前阶段完成后才对其生效。warm 阶段的 shrink 和 forcemerge 会产生大量 IO；在 force merge 完成前不得写入该索引。ILM 默认每 10 分钟运行一次，因此条件不会被即时评估。","since":"Elasticsearch 7.8","note":"对于新项目，data stream 能更简单地实现同样效果：在索引模板中加入 \"data_stream\": {}，无需 rollover 别名或引导索引。rollover 条件基于已刷新的统计信息评估。可组合索引模板（_index_template）在 7.8 引入；旧版 _template API 已弃用。","tags":["ilm","lifecycle","rollover","policy","index template","retention"]},{"id":"ilm-explain-retry","task":"诊断卡住的 ILM 步骤并重试（_ilm/explain、_ilm/retry）","category":"索引生命周期","command":"# Indices waiting in an error step:\nGET log-*/_ilm/explain?only_errors=true\n# Phase/action/step state and failure reason for one index:\nGET log-000003/_ilm/explain?filter_path=indices.*.phase,indices.*.action,indices.*.step,indices.*.failed_step,indices.*.step_info,indices.*.is_auto_retryable_error\n# After fixing the cause (example: missing rollover alias setting):\nPUT log-000003/_settings\n{\n  \"index.lifecycle.rollover_alias\": \"log\"\n}\nPOST log-000003/_ilm/retry\n# Is ILM running (if STOPPED, POST _ilm/start):\nGET _ilm/status","what":"显示停在 ERROR 步骤的索引、卡在哪个 phase/action/step 以及失败原因（step_info）；原因修复后重新执行失败的步骤。","when":"rollover 没有发生、旧索引未被删除，或磁盘被本应由 ILM 删除的索引占满时。","caution":"_ilm/retry 仅对处于 ERROR 步骤的索引有效；否则返回 'has not encountered an error'。常见原因：缺少 rollover_alias 设置或该别名的 is_write_index 位于另一个索引上、单个节点上没有足够磁盘执行 shrink、分配规则（_tier_preference）无法满足。删除策略不会解除索引的 ILM 管理；先从索引上移除策略设置。","since":"Elasticsearch 7.0","note":"is_auto_retryable_error 为 true 的错误由 ILM 自行重试（failed_step_retry_count 递增）。ILM 检查间隔由 indices.lifecycle.poll_interval 设置（默认 10m）。使用 POST <index>/_ilm/remove 将索引移出 ILM。","tags":["ilm","explain","retry","error step","rollover","lifecycle"]},{"id":"index-template-component","task":"用组件模板构建索引模板并模拟结果","category":"索引生命周期","command":"PUT _component_template/app-mappings\n{\n  \"template\": {\n    \"mappings\": {\n      \"dynamic\": \"strict\",\n      \"properties\": {\n        \"@timestamp\": { \"type\": \"date\" },\n        \"level\": { \"type\": \"keyword\" },\n        \"msg\": { \"type\": \"text\" }\n      }\n    }\n  }\n}\nPUT _index_template/app-events\n{\n  \"index_patterns\": [\"app-events-*\"],\n  \"priority\": 500,\n  \"composed_of\": [\"app-mappings\"],\n  \"template\": { \"settings\": { \"number_of_shards\": 1, \"refresh_interval\": \"5s\" } }\n}\n# See the final settings and mapping before the index is created:\nPOST _index_template/_simulate_index/app-events-000001\nGET _cat/templates?v&s=name","what":"定义可复用的 mapping 组件模板，将其组合到索引模板中，并在不创建索引的情况下显示将应用于给定索引名的最终 settings 和 mapping。","when":"新索引以错误的 mapping 或分片数创建时（动态 mapping 爆炸、用 text 代替 keyword），以及推出模板变更之前。","caution":"模板只作用于新创建的索引；现有索引的 mapping 不会改变（参见 reindex-alias-swap）。若两个模板匹配同一模式，只使用优先级最高的那个；它们不会合并。dynamic: strict 会拒绝包含未定义字段的文档；若应用发送新字段，索引会失败。","since":"Elasticsearch 7.8","note":"_simulate_index 输出中的 overlapping 字段显示匹配同一模式但被覆盖的其他模板。将 priority 保持在 200 以上，以免与内置模板（logs-*-*、metrics-*-*）冲突。可组合模板优先于旧版 _template API。","tags":["index template","component template","simulate","mapping","priority","dynamic strict"]},{"id":"reindex-alias-swap","task":"零停机更改 mapping（reindex + 原子别名切换）","category":"索引生命周期","command":"# 1) Target index with the new mapping (refresh off and replicas 0 for loading):\nPUT musteri-v2\n{\n  \"settings\": { \"number_of_shards\": 1, \"number_of_replicas\": 0, \"refresh_interval\": \"-1\" },\n  \"mappings\": { \"properties\": { \"kod\": { \"type\": \"keyword\" }, \"adet\": { \"type\": \"long\" } } }\n}\n# 2) Reindex in the background (tracked with the returned task id):\nPOST _reindex?wait_for_completion=false&slices=auto\n{\n  \"source\": { \"index\": \"musteri-v1\", \"size\": 1000 },\n  \"dest\": { \"index\": \"musteri-v2\", \"op_type\": \"create\" },\n  \"conflicts\": \"proceed\"\n}\nGET _tasks/<task_id>\n# 3) Restore settings and compare document counts:\nPUT musteri-v2/_settings\n{\n  \"index\": { \"refresh_interval\": null, \"number_of_replicas\": 1 }\n}\nPOST musteri-v2/_refresh\nGET _cat/indices/musteri-v1,musteri-v2?v&h=index,docs.count,pri.store.size\n# 4) Switch the alias to the new index in one atomic operation:\nPOST _aliases\n{\n  \"actions\": [\n    { \"remove\": { \"index\": \"musteri-v1\", \"alias\": \"musteri\" } },\n    { \"add\": { \"index\": \"musteri-v2\", \"alias\": \"musteri\", \"is_write_index\": true } }\n  ]\n}","what":"创建使用新 mapping 的索引，在后台用 reindex 复制数据，比较文档数，并在一次原子操作中将应用使用的别名切换到新索引。","when":"需要更改现有字段的类型（text -> keyword、integer -> long）、更改 analyzer 或重新定义分片数时；这些无法在现有索引上原地完成。","caution":"应用必须连接别名而不是索引名；否则切换需要一次部署。reindex 期间写入源索引的新数据不会到达目标：要么停止写入，要么在切换前以 op_type create + conflicts proceed 重新运行 reindex 来弥补缺口（更新和删除不会以这种方式带过去）。reindex 会给源索引带来读负载、给目标索引带来写负载；用 requests_per_second 限流。","since":"Elasticsearch 7.0","note":"refresh_interval 为 -1 时加载的文档在 refresh 之前不会出现在计数中；比较前先运行 _refresh。若 _cat/count 收到多个逗号分隔的索引，会返回它们的总数；按索引比较请使用 _cat/indices。运行中的 reindex 速度可用 POST _reindex/<task_id>/_rethrottle?requests_per_second=500 更改。wait_for_completion=false 会将结果写入 .tasks 索引；用 GET _tasks/<task_id> 检查 response.failures。","tags":["reindex","alias","mapping change","zero downtime","_aliases","no downtime"]},{"id":"tasks-cancel","task":"查找、取消或限流长时间运行的搜索和 reindex 任务","category":"维护","command":"# Longest running tasks:\nGET _cat/tasks?v&detailed&h=action,task_id,parent_task_id,running_time,node,description&s=running_time:desc\n# Search, reindex and by-query tasks, grouped by parent:\nGET _tasks?actions=*search*,*reindex*,*byquery*&detailed=true&group_by=parents\n# Cancel a single task:\nPOST _tasks/<task_id>/_cancel\n# Cancel all search tasks (with care):\nPOST _tasks/_cancel?actions=*search*\n# Slow down a running reindex/update_by_query:\nPOST _reindex/<task_id>/_rethrottle?requests_per_second=500","what":"按持续时间对集群中运行的任务排序，显示搜索和批量操作及其查询描述；取消选定的任务或所有搜索，并对 reindex/by-query 作业限流。","when":"search 线程池占满、CPU 突然上升、某个仪表板以繁重的聚合拖累集群，或 update_by_query 影响生产时。","caution":"取消是协作式的：任务在下一个检查点停止；某些阶段（如 force merge）无法取消。取消所有搜索会给用户返回错误。被取消的 reindex/by-query 不会回滚已做的更改；目标中会包含部分数据。父任务被取消时，其子任务也会被取消。","since":"Elasticsearch 7.0","note":"detailed 参数会把搜索任务的查询体写入 description 字段。要获得持久保护，请使用 search.default_search_timeout、搜索请求中的 timeout，以及针对大型聚合的 search.max_buckets。在 7.x+ 中，HTTP 连接关闭的搜索会自动取消。","tags":["tasks","cancel","kill query","rethrottle","long query","search"]},{"id":"search-slowlog","task":"启用搜索和索引慢日志（按索引设置阈值）","category":"性能","command":"PUT musteri/_settings\n{\n  \"index.search.slowlog.threshold.query.warn\": \"2s\",\n  \"index.search.slowlog.threshold.query.info\": \"500ms\",\n  \"index.search.slowlog.threshold.fetch.warn\": \"1s\",\n  \"index.indexing.slowlog.threshold.index.warn\": \"1s\",\n  \"index.indexing.slowlog.source\": \"200\"\n}\nGET musteri/_settings/index.*slowlog*?flat_settings=true\n# Set thresholds to null to turn it off:\nPUT musteri/_settings\n{\n  \"index.search.slowlog.threshold.query.warn\": null,\n  \"index.search.slowlog.threshold.query.info\": null,\n  \"index.search.slowlog.threshold.fetch.warn\": null,\n  \"index.indexing.slowlog.threshold.index.warn\": null,\n  \"index.indexing.slowlog.source\": null\n}","what":"针对给定索引，将 query 和 fetch 阶段以及索引操作中超过阈值的请求连同查询体写入慢日志。","when":"用于找出导致搜索变慢的查询，或捕获来自特定仪表板或客户端的重型查询。","caution":"阈值按分片计量：在分片很多的索引上，每个分片都可能写自己的一行。阈值过低（0ms）会显著增加日志量和 IO；诊断后用 null 关闭。indexing.slowlog.source 会把文档 source 写入日志；若包含敏感数据请设为 false。该设置只作用于给定索引；新索引请将其加入模板。","since":"Elasticsearch 8.0","note":"慢日志文件以 <cluster>_index_search_slowlog.json 和 <cluster>_index_indexing_slowlog.json 写入 logs 目录；若 log4j2 被路由到控制台，则输出到 stdout。index.search.slowlog.level 设置在 8.0 中已移除；级别由阈值决定（warn/info/debug/trace）。在 8.14+ 中，index.search.slowlog.include.user: true 还会把发起请求的用户写入日志。要详细检查单个查询，在搜索请求中加入 \"profile\": true。","tags":["slowlog","slow log","slow search","search","indexing","threshold"]},{"id":"rejections-heap-breaker","task":"诊断 429 拒绝、heap 和断路器压力","category":"性能","command":"# Thread pool queue and rejection counts (source of 429):\nGET _cat/thread_pool/write,search,get?v&h=node_name,name,active,queue,rejected,completed,size,queue_size&s=rejected:desc\nGET _cat/nodes?v&h=name,node.role,heap.percent,ram.percent,cpu,load_1m,disk.used_percent,master&s=heap.percent:desc\n# Circuit breaker trips and parent breaker usage:\nGET _nodes/stats/breaker?filter_path=nodes.*.name,nodes.*.breakers.*.tripped,nodes.*.breakers.parent\nGET _nodes/stats/jvm?filter_path=nodes.*.name,nodes.*.jvm.mem.heap_used_percent,nodes.*.jvm.gc.collectors.old\n# Fielddata memory usage (per field):\nGET _nodes/stats/indices/fielddata?fields=*&filter_path=nodes.*.indices.fielddata","what":"显示因 write/search 线程池队列已满而被拒绝的请求、每个节点的 heap/CPU 状态、断路器触发、old GC 次数以及 fielddata 内存使用。","when":"客户端收到 429 Too Many Requests 或 es_rejected_execution_exception、出现 CircuitBreakingException（[parent] Data too large），或节点出现长时间 GC 停顿时。","caution":"rejected 计数器是自节点启动以来的累计值；请看两次采样之间的增量。提高 queue_size 会掩盖拒绝，但会增加 heap 和延迟；真正的解决办法是减小 bulk 大小/并发或增加容量。若 heap 持续高于 85% 且 old GC 频繁运行，节点正在接近 OOM。在 text 字段上启用 fielddata 会快速消耗 heap。","since":"Elasticsearch 7.0","note":"parent 断路器默认为 heap 的 95%（real memory circuit breaker，7.0+）。写入拒绝通常集中在单个热点节点上：检查分片分布和路由（_routing）。要临时释放 fielddata，使用 POST _cache/clear?fielddata=true（参见 cache-clear）。","tags":["429","rejected","thread pool","circuit breaker","heap","fielddata"]},{"id":"slm-snapshot-monitor","task":"用 SLM 自动化快照和保留，监控快照进度","category":"快照","command":"# Take a snapshot every night at 01:30, keep for 30 days (at least 7, at most 60 snapshots):\nPUT _slm/policy/gece-yedek\n{\n  \"schedule\": \"0 30 1 * * ?\",\n  \"name\": \"<gece-{now/d}>\",\n  \"repository\": \"yedek_repo\",\n  \"config\": { \"indices\": [\"*\"], \"include_global_state\": true },\n  \"retention\": { \"expire_after\": \"30d\", \"min_count\": 7, \"max_count\": 60 }\n}\n# Run the policy now and see success/failure statistics:\nPOST _slm/policy/gece-yedek/_execute\nGET _slm/stats\n# Snapshot in progress and per-shard progress:\nGET _snapshot/yedek_repo/_current\nGET _snapshot/yedek_repo/_status\nGET _cat/snapshots/yedek_repo?v&s=end_epoch:desc\n# Verify that the repository is writable from all nodes:\nPOST _snapshot/yedek_repo/_verify","what":"定义按 cron 计划创建快照并按保留规则删除旧快照的 SLM 策略；显示运行中快照的按分片进度、历史快照以及仓库可达性。","when":"建立定期备份时、出现夜间快照失败告警时，或估算长时间运行的快照何时完成时。","caution":"计划按 UTC 评估。若多个集群写入同一仓库，或该仓库在另一集群上注册为可写，仓库可能损坏；在其他集群上使用 readonly: true。保留规则只删除由 SLM 创建的快照。include_global_state true 在还原时也可能还原模板和 persistent 集群设置。","since":"Elasticsearch 7.5","note":"快照是增量的：只复制上一次快照中缺少的段，因此频繁快照不会在仓库中占用成比例的空间。SLM 在 7.4 引入，保留功能在 7.5 引入。不指定仓库名时，_status 返回集群中所有运行中的快照；指定历史快照名称时，它会从仓库读取，可能较慢。","tags":["slm","snapshot","retention","backup","schedule","snapshot status"],"untested":true},{"id":"security-user-role","task":"创建角色和 native 用户、修改密码、签发 API key","category":"安全","command":"POST _security/role/log_okuma\n{\n  \"cluster\": [\"monitor\"],\n  \"indices\": [\n    { \"names\": [\"log-*\"], \"privileges\": [\"read\", \"view_index_metadata\"] }\n  ]\n}\nPOST _security/user/rapor_kullanici\n{\n  \"password\": \"Degistir-123!\",\n  \"roles\": [\"log_okuma\"],\n  \"full_name\": \"Rapor Servisi\"\n}\nGET _security/user/rapor_kullanici\n# User making this request and its roles:\nGET _security/_authenticate\nPOST _security/user/rapor_kullanici/_password\n{\n  \"password\": \"Yeni-Sifre-456!\"\n}\n# Time-limited API key with restricted privileges:\nPOST _security/api_key\n{\n  \"name\": \"rapor-key\",\n  \"expiration\": \"30d\",\n  \"role_descriptors\": { \"log_okuma\": { \"indices\": [ { \"names\": [\"log-*\"], \"privileges\": [\"read\"] } ] } }\n}\n# If the elastic user's password is lost (shell on a node):\n# bin/elasticsearch-reset-password -u elastic -i","what":"创建对某个索引模式具有读权限的角色和持有该角色的 native realm 用户，修改密码，验证调用者身份，并签发权限收窄、有时限的 API key。","when":"按最小权限创建报表、应用或监控账户时、密码轮换期间，以及将服务从用户迁移到 API key 时。","caution":"不要在应用中使用 elastic 超级用户。API key 的权限不能超过创建它的用户；role_descriptors 只能收窄权限。API key 受限于其所有者在创建时刻的权限快照；之后的角色变更不影响现有 key。修改密码后，使用旧密码的服务会收到 401；请规划切换。API key 响应中的 encoded 值只返回一次；请妥善保存。","since":"Elasticsearch 8.0","note":"8.0+ 中默认启用安全功能，elastic 密码在首次启动时生成。elasticsearch-reset-password 工具在 8.0 引入。用 GET _security/api_key?name=rapor-key 列出 API key，用 DELETE _security/api_key 撤销。LDAP/AD/SAML realm 中用户的角色映射通过 _security/role_mapping 完成。","tags":["security","users","roles","api key","password","rbac"],"untested":true}]},"solver/slow-query.json":{"id":"slow-query","title":"慢查询：深入诊断","icon":"🐢","category":"性能","intro":"某个查询或整个系统很慢。逐步缩小范围：在每个节点执行检查，并根据结果选择分支。最终得到根本原因 + 修复方案。","start":"scope","nodes":{"scope":{"kind":"step","title":"什么慢？","detail":"是一个或少数几个特定查询慢，还是整个系统慢？据此将排查分为两条路径之一：“以查询为中心”或“以资源/瓶颈为中心”。","choices":[{"label":"一个或少数几个特定查询慢","next":"running"},{"label":"整个系统都慢","next":"system"}]},"running":{"kind":"step","title":"查询此刻正在运行吗？","detail":"是此刻正在运行/卡住的查询，还是“一直都慢”的查询？查看活动会话。","command":{"oracle":"SELECT sid, serial#, sql_id, last_call_et AS running_sec, event, blocking_session\nFROM v$session WHERE status='ACTIVE' AND type='USER' ORDER BY last_call_et DESC;","postgres":"SELECT pid, now()-query_start AS dur, state, wait_event, query\nFROM pg_stat_activity WHERE state='active' ORDER BY query_start;","mysql":"SELECT id, time, state, info FROM information_schema.processlist WHERE command='Query' ORDER BY time DESC;","mssql":"SELECT session_id, total_elapsed_time/1000 AS sec, status, wait_type, blocking_session_id\nFROM sys.dm_exec_requests WHERE session_id IN (SELECT session_id FROM sys.dm_exec_sessions WHERE is_user_process = 1) ORDER BY total_elapsed_time DESC;"},"choices":[{"label":"此刻正在运行/卡住","next":"blocked"},{"label":"一直都慢；我想看它的执行计划","next":"plan"}]},"blocked":{"kind":"step","title":"查询是否在等待什么？","detail":"如果它处于活动状态却没有进展，很可能是在等待。blocking_session / wait_event 是否有值？","command":{"oracle":"SELECT sid, blocking_session, event, seconds_in_wait\nFROM v$session WHERE sid=&target_sid;","postgres":"SELECT pid, pg_blocking_pids(pid) AS blocked_by, wait_event_type, wait_event\nFROM pg_stat_activity WHERE pid=&target_pid;","mssql":"SELECT session_id, blocking_session_id, wait_type, wait_resource\nFROM sys.dm_exec_requests WHERE session_id=&target;"},"choices":[{"label":"被另一个会话阻塞","next":"sol_blocking"},{"label":"在等待资源（IO / 日志 / latch）","next":"wait"},{"label":"没有等待；正在大量消耗 CPU","next":"plan"}]},"wait":{"kind":"step","title":"哪种等待占主导？","detail":"占主导的等待事件说明了瓶颈的类型。","command":{"oracle":"SELECT event, total_waits, time_waited FROM v$session_event WHERE sid=&target_sid ORDER BY time_waited DESC FETCH FIRST 5 ROWS ONLY;","postgres":"SELECT wait_event_type, wait_event, count(*) FROM pg_stat_activity WHERE state='active' GROUP BY 1,2 ORDER BY 3 DESC;","mssql":"SELECT TOP 5 wait_type, waiting_tasks_count, wait_time_ms FROM sys.dm_os_wait_stats ORDER BY wait_time_ms DESC;"},"choices":[{"label":"IO 等待（db file read / PAGEIOLATCH）","next":"sol_io"},{"label":"日志/提交等待（log file sync / WRITELOG）","next":"sol_log"},{"label":"锁/队列（enq: TX / LCK）","next":"sol_blocking"}]},"plan":{"kind":"step","title":"获取执行计划（EXPLAIN）","detail":"取出执行计划。是否有全表扫描、意外的连接顺序，或估算行数与实际行数之间的差距？","command":{"oracle":"SELECT * FROM TABLE(DBMS_XPLAN.DISPLAY_CURSOR('&sql_id', NULL, 'ALLSTATS LAST'));","postgres":"EXPLAIN (ANALYZE, BUFFERS) <query>;","mysql":"EXPLAIN ANALYZE <query>;","mssql":"SET STATISTICS PROFILE ON; <query>; SET STATISTICS PROFILE OFF;","mongodb":"db.collection.find({...}).explain('executionStats')  // COLLSCAN or IXSCAN?"},"choices":[{"label":"全表扫描/缺少索引","next":"sol_index"},{"label":"昨天还很快；执行计划突然变了","next":"sol_planchange"},{"label":"处理的行太多 / N+1","next":"sol_rewrite"},{"label":"执行计划看起来不错但仍然慢","next":"stats"}]},"stats":{"kind":"step","title":"统计信息是否最新？","detail":"估算行数与实际行数（E-rows 与 A-rows）差距很大，是统计信息过时的迹象。","command":{"oracle":"SELECT table_name, num_rows, last_analyzed FROM dba_tables WHERE table_name='&TABLE_NAME';","postgres":"SELECT relname, last_analyze, last_autoanalyze, n_live_tup FROM pg_stat_user_tables WHERE relname='&table_name';","mysql":"SELECT table_name, table_rows, update_time FROM information_schema.tables WHERE table_name='&table_name';","mssql":"SELECT name, STATS_DATE(object_id, stats_id) AS last_updated FROM sys.stats WHERE object_id=OBJECT_ID('&table_name');"},"choices":[{"label":"统计信息过时/陈旧","next":"sol_stats"},{"label":"统计信息是最新的但仍然慢","next":"sol_deep"}]},"system":{"kind":"step","title":"系统级：瓶颈在哪里？","detail":"如果是所有操作都慢而不是单个查询，就要查找资源瓶颈：CPU、IO、内存，还是大范围阻塞？","command":{"oracle":"SELECT metric_name, value FROM v$sysmetric WHERE metric_name IN ('Host CPU Utilization (%)','Average Active Sessions') AND intsize_csec=(SELECT MAX(intsize_csec) FROM v$sysmetric);","postgres":"SELECT state, wait_event_type, count(*) FROM pg_stat_activity GROUP BY 1,2 ORDER BY 3 DESC;","mssql":"SELECT TOP 5 wait_type, wait_time_ms FROM sys.dm_os_wait_stats ORDER BY wait_time_ms DESC;"},"choices":[{"label":"CPU 接近 100%","next":"sol_cpu"},{"label":"IO 等待高","next":"sol_io"},{"label":"内存压力/缓存命中率低","next":"sol_mem"},{"label":"大量阻塞","next":"sol_blocking"}]},"sol_blocking":{"kind":"solution","title":"根本原因：阻塞","detail":"查询在等待另一个会话持有的锁。找到链条最前端的会话（根阻塞者）并加以处理。","steps":["找到阻塞链的最前端（自身不在等待却让其他会话等待的会话）。","弄清根阻塞者在做什么：一个长时间未提交的事务，还是 'idle in transaction'？","若应用可以 commit/rollback，就触发它；若不能且业务已停滞，就终止根阻塞者。","修复根本原因：缺少索引可能导致大范围加锁；缩短事务并检查隔离级别。"],"related":["blocking-sessions","kill-session"],"playbook":"blocking"},"sol_io":{"kind":"solution","title":"根本原因：IO 瓶颈","detail":"查询从磁盘读取大量数据。通常源于全表扫描（缺少索引）或内存（buffer cache）不足。","steps":["为导致全表扫描的过滤条件添加合适的索引（执行计划应切换为索引扫描，IXSCAN）。","检查 buffer cache / buffer pool 命中率；偏低时增加内存。","减少不必要的列/行读取（只取需要的列而不是 SELECT *，分页）。","若存储确实很慢（IO 延迟高），改善磁盘层。"],"related":["cache-hit-ratio","create-index"]},"sol_log":{"kind":"solution","title":"根本原因：提交/日志瓶颈","detail":"若 'log file sync' / 'WRITELOG' 占主导，说明提交在等待 redo/WAL 写入。","steps":["将频繁的单行提交改为批量提交；减少不必要的提交。","加快 redo/WAL 磁盘（独立的快速磁盘）；检查日志文件大小/数量。","结合负载以及持久性的取舍评估 synchronous_commit / commit_write 设置。"],"related":["show-config-param"]},"sol_index":{"kind":"solution","title":"根本原因：缺少索引","detail":"执行计划在做全表扫描。在过滤/连接列上建立合适的索引会大幅提速。","steps":["在 WHERE/JOIN 列上在线添加合适的索引（必要时使用复合索引）。","添加索引后再次获取执行计划，验证其已切换为索引扫描（IXSCAN）。","若写入负载很重，要权衡索引成本；删除不必要的索引。","在选择性低的列上索引没有帮助；检查选择性。"],"related":["create-index","explain-plan"]},"sol_planchange":{"kind":"solution","title":"根本原因：执行计划回归","detail":"查询昨天还快，今天却不快了；执行计划变了。通常是统计信息过时或参数变更所致。","steps":["先收集最新的统计信息（统计信息过时是最常见的原因）。","固定良好的执行计划：Oracle SQL Plan Baseline / hint、MSSQL Query Store 'force plan'、PG 设置。","若近期有升级/补丁/参数变更，请复查（优化器行为）。"],"related":["update-statistics"]},"sol_rewrite":{"kind":"solution","title":"根本原因：查询设计","detail":"执行计划合理，但查询本身做了太多工作（行太多、N+1、不必要的连接）。","steps":["缩小结果集：分页（LIMIT/OFFSET 或 keyset）、尽早过滤。","将 N+1 模式改为单个批量查询；简化不必要的连接/子查询。","对于反复执行的重型报表，考虑使用物化视图/汇总表。"],"related":["explain-plan","materialized-view"]},"sol_stats":{"kind":"solution","title":"根本原因：统计信息过时","detail":"由于统计信息不是最新的，优化器估算错误。","steps":["为受影响的表手动收集统计信息。","确认自动收集作业（autovacuum/auto stats/gather job）在运行。","在突发的大量数据加载之后，将统计信息收集加入工作流程。"],"related":["update-statistics"]},"sol_deep":{"kind":"solution","title":"深入分析","detail":"若执行计划和统计信息都没问题但仍然慢，就要深入挖掘。","steps":["启用会话级跟踪：Oracle 10046、PG auto_explain、MySQL profiling、MSSQL Extended Events。","检查并行度、内存授予以及排序/哈希溢出（tempdb/temp）。","排除硬件/操作系统层面的问题（CPU steal、IO 延迟、网络）。","评估连接数和并发导致的争用。"],"related":["session-trace","top-wait-events"]},"sol_cpu":{"kind":"solution","title":"根本原因：CPU 瓶颈","detail":"整个系统受 CPU 限制。通常是少数几个执行计划糟糕的查询在大量消耗 CPU。","steps":["找出消耗 CPU 最多的前 10 个查询并修正其执行计划（索引/统计信息）。","限制失控查询（Resource Manager / Resource Governor）。","若使用率持续很高，进行扩容（增加 CPU，将读取分散到只读副本）。"],"related":["top-slow-queries"],"playbook":"high-cpu"},"sol_mem":{"kind":"solution","title":"根本原因：内存压力","detail":"缓存命中率低和/或发生 swap：内存不足或分配不当。","steps":["正确设置 buffer pool / SGA 和会话内存（work_mem/PGA）的大小（工具 > 内存计算器）。","找到并修复消耗内存的失控查询。","若系统正在 swap，首要任务是摆脱 swap；必要时增加内存。"],"related":["cache-hit-ratio","show-config-param"],"playbook":"memory-pressure"}}},"solver/deadlock.json":{"id":"deadlock","title":"死锁/锁冲突（ORA-00060、1213、40P01）","icon":"♻️","category":"锁","intro":"遇到了死锁（Oracle ORA-00060、MySQL 1213、PG 40P01、MSSQL 1205）。捕获死锁图，弄清模式，防止再次发生。死锁通过设计来解决——目标是降低其发生频率。","start":"capture","nodes":{"capture":{"kind":"step","title":"先捕获死锁图","detail":"是哪两个（或更多）会话、哪些资源、哪些锁模式发生了冲突？没看到这些之前不要盲目修复。","command":{"oracle":"-- Check the trace file referenced in the alert log (deadlock graph). For history:\nSELECT * FROM dba_hist_active_sess_history WHERE event LIKE 'enq: TX%' ORDER BY sample_time DESC FETCH FIRST 20 ROWS ONLY;","mysql":"SHOW ENGINE INNODB STATUS\\G  -- 'LATEST DETECTED DEADLOCK' section","postgres":"-- Deadlock errors are always written to postgresql.log as ERROR (log_lock_waits not required); log_lock_waits=on also logs lock waits longer than deadlock_timeout.","mssql":"SELECT XEvent.query('.') FROM (SELECT CAST(target_data AS XML) td FROM sys.dm_xe_session_targets t JOIN sys.dm_xe_sessions s ON s.address=t.event_session_address WHERE s.name='system_health') x CROSS APPLY td.nodes('//event[@name=\"xml_deadlock_report\"]') AS q(XEvent);"},"choices":[{"label":"已拿到死锁图——两个事务以相反顺序锁定资源","next":"sol_order"},{"label":"大量事务堆积在单行/单表上（热点）","next":"sol_hotspot"},{"label":"存在大范围锁（许多行被锁定）","next":"index_check"}]},"index_check":{"kind":"step","title":"加锁的查询是否使用了索引？","detail":"没有索引时，UPDATE/DELETE 为满足 WHERE 子句会锁定大量行（甚至整张表）→ 这是死锁的来源之一。检查它的执行计划。","command":{"mysql":"EXPLAIN <the UPDATE/DELETE involved in the deadlock>;  -- type=ALL means a full scan, no index","postgres":"EXPLAIN <query>;  -- Seq Scan or Index Scan?","oracle":"SELECT * FROM TABLE(DBMS_XPLAN.DISPLAY_CURSOR('&sql_id'));"},"choices":[{"label":"没有索引/在做全表扫描","next":"sol_index"},{"label":"有索引，但仍然冲突","next":"sol_order"}]},"sol_order":{"kind":"solution","title":"根本原因：加锁顺序相反","detail":"死锁最常见的原因：两个事务以不同顺序访问资源。A→B 和 B→A 同时发生时，就会死锁。","steps":["在每条代码路径上以相同顺序访问资源（如总是从最小 id → 最大 id）。","保持事务简短；将用户交互/耗时工作移到事务之外。","必要时按确定的顺序提前加锁（有序的 SELECT ... FOR UPDATE）。","在应用中为死锁牺牲者加入自动重试（重试 + 退避）——这是必需的。"],"related":["blocking-sessions","open-transactions"]},"sol_index":{"kind":"solution","title":"根本原因：缺少索引 → 大范围锁","detail":"没有索引的 UPDATE/DELETE 锁定的行多于必要，大大增加死锁的可能性。","steps":["在 WHERE 子句中的列上添加索引；锁就会限制在受影响的行上。","再次获取执行计划，验证全表扫描（Seq Scan / type=ALL）已变为索引访问。","以小批量（分块）执行批量更新。"],"related":["create-index","list-indexes"]},"sol_hotspot":{"kind":"solution","title":"根本原因：热点","detail":"大量事务同时堆积在计数器行/键这类单一资源上。","steps":["分散热点行：使用 sequence/identity，拆分计数器（分片计数器）。","缩短事务持续时间，使锁的持有时间更短。","若可能，使用乐观锁（version/timestamp）或队列模式。"],"related":["sequence","open-transactions"]}}},"solver/disk-full.json":{"id":"disk-full","title":"磁盘/表空间已满","icon":"💽","category":"容量","intro":"出现了 'unable to extend'、'disk full'、'could not extend' 或 'log full'。不要慌：先找出是什么满了，安全地回收空间，再做永久修复。","start":"what","nodes":{"what":{"kind":"step","title":"什么满了？","detail":"清理错了地方无济于事。先测量使用情况：是数据文件、事务日志、归档/redo、temp，还是操作系统磁盘？","command":{"oracle":"SELECT d.tablespace_name, ROUND((1-(d.alloc-NVL(f.free,0))/d.maxb)*100,1) AS pct_free\nFROM (SELECT tablespace_name, SUM(bytes) alloc, SUM(GREATEST(bytes,maxbytes)) maxb FROM dba_data_files GROUP BY tablespace_name) d LEFT JOIN (SELECT tablespace_name, SUM(bytes) free FROM dba_free_space GROUP BY tablespace_name) f ON f.tablespace_name = d.tablespace_name ORDER BY pct_free;","postgres":"SELECT datname, pg_size_pretty(pg_database_size(datname)) FROM pg_database ORDER BY 2 DESC;  -- OS disk: df -h","mssql":"SELECT name, size/128.0 AS size_mb, size/128.0 - CAST(FILEPROPERTY(name,'SpaceUsed') AS int)/128.0 AS free_mb, type_desc\nFROM sys.database_files;","mysql":"SELECT table_schema, ROUND(SUM(data_length+index_length)/1048576,1) AS mb FROM information_schema.tables GROUP BY table_schema ORDER BY mb DESC;"},"choices":[{"label":"数据文件/表空间已满","next":"data"},{"label":"事务日志/归档-redo 已满","next":"log"},{"label":"temp/undo 已满","next":"sol_temp"},{"label":"操作系统磁盘已满（不是数据库）","next":"sol_os"}]},"data":{"kind":"step","title":"最大的占用者是什么？","detail":"大多数情况下罪魁祸首是某一个失控的表/段。找出最大的那些。","command":{"oracle":"SELECT segment_name, segment_type, ROUND(bytes/1048576,1) AS mb FROM dba_segments ORDER BY bytes DESC FETCH FIRST 10 ROWS ONLY;","postgres":"SELECT relname, pg_size_pretty(pg_total_relation_size(relid)) FROM pg_statio_user_tables ORDER BY pg_total_relation_size(relid) DESC LIMIT 10;","mssql":"SELECT TOP 10 OBJECT_NAME(object_id) AS table_name, SUM(reserved_page_count)*8/1024 AS mb FROM sys.dm_db_partition_stats GROUP BY object_id ORDER BY mb DESC;"},"choices":[{"label":"有一个意外巨大/失控的表","next":"sol_runaway"},{"label":"正常增长，空间确实用完了","next":"sol_extend"},{"label":"死行过多/膨胀（bloat）","next":"sol_reclaim"}]},"log":{"kind":"step","title":"日志为什么在增长？","detail":"在最老的活动事务/备份完成之前，日志/归档无法清理。它为什么会累积？","command":{"mssql":"SELECT name, log_reuse_wait_desc FROM sys.databases WHERE name=DB_NAME();  -- why can it not be truncated?","postgres":"SELECT slot_name, active, pg_size_pretty(pg_wal_lsn_diff(pg_current_wal_lsn(), restart_lsn)) AS retained FROM pg_replication_slots ORDER BY 3 DESC;","oracle":"SELECT * FROM v$flash_recovery_area_usage;  -- FRA (archive) usage"},"choices":[{"label":"MSSQL：没有执行日志备份（LOG_BACKUP）","next":"sol_logbackup"},{"label":"PG：停滞的复制槽保留着 WAL","next":"sol_slot"},{"label":"Oracle：归档日志区（FRA）已满","next":"sol_archive"},{"label":"长时间打开的事务占住了日志","next":"sol_longtxn"}]},"sol_runaway":{"kind":"solution","title":"根本原因：失控的表/段","detail":"某个对象意外增长（不受控的插入、从未清理的队列/日志表）。","steps":["确认该对象是否真正需要；若是日志/队列表，应用保留策略。","用分区删除/TRUNCATE 清理不需要的旧数据（在备份/审批之后）。","在应用中阻止不受控的增长；设置增长告警。"],"related":["largest-tables","create-partition"]},"sol_extend":{"kind":"solution","title":"根本原因：确实需要扩容","detail":"正常增长；必须增加空间。","steps":["为数据文件增加空间或启用 AUTOEXTEND（Oracle：ALTER DATABASE DATAFILE ... AUTOEXTEND ON MAXSIZE）。","必要时添加新的数据文件/表空间或扩容磁盘。","设置 80%/90% 使用率告警，避免再出现意外。"],"related":["free-space"]},"sol_reclaim":{"kind":"solution","title":"根本原因：膨胀/死行","detail":"已删除/更新的行所占空间未被回收。","steps":["回收空间：PG VACUUM（必要时用 pg_repack）、Oracle SHRINK SPACE、MySQL OPTIMIZE TABLE、Mongo compact。","让 autovacuum/自动维护设置更积极。","VACUUM FULL / OPTIMIZE 会锁表——请在高峰时段之外运行。"],"related":["reclaim-space","free-space"]},"sol_temp":{"kind":"solution","title":"根本原因：temp/undo 已满","detail":"大型排序/哈希或长时间打开的事务占满了临时空间。","steps":["找出撑爆 temp 的查询（大型排序/哈希溢出）并优化它。","找出长时间打开的事务；正是它占住了 undo（Oracle）。","必要时扩大 temp/undo 空间；检查保留设置。"],"related":["open-transactions","reclaim-space"]},"sol_os":{"kind":"solution","title":"根本原因：操作系统磁盘已满","detail":"问题出在操作系统磁盘上，而不是数据库。日志、core dump、旧备份或跟踪文件可能占用了空间。","steps":["用 df -h 找出已满的文件系统；扫描最大的目录（du -sh *）。","清理旧的日志/跟踪/core dump/备份文件（需经审批）。","将数据库诊断目录（Oracle ADR、PG 日志）纳入轮换。"]},"sol_logbackup":{"kind":"solution","title":"根本原因：未执行日志备份（SQL Server）","detail":"在 FULL 恢复模式下，若不执行日志备份，日志会无限增长。","steps":["执行日志备份（BACKUP LOG）——这样日志才能被重用。","若恢复模式确实应为 SIMPLE，就进行更改。","仅在必要时收缩日志文件（DBCC SHRINKFILE），并设置定期日志备份作业。"],"related":["last-backup-status"],"playbook":"log-growth"},"sol_slot":{"kind":"solution","title":"根本原因：被遗弃的复制槽（PG）","detail":"停滞（非活动）的复制槽会阻止 WAL 被删除，并撑大 pg_wal。","steps":["确认该复制槽是否确实在使用。","若不需要就删除：SELECT pg_drop_replication_slot('slot_name');","修复活动副本，使复制槽向前推进；为复制槽延迟设置告警。"],"related":["free-space"],"playbook":"log-growth"},"sol_archive":{"kind":"solution","title":"根本原因：归档日志区（FRA）已满（Oracle）","detail":"归档日志累积导致 FRA 已满；归档进程一旦停滞，数据库可能停止接受写入。","steps":["使用 RMAN 删除已备份过的旧归档日志（切勿在没有备份的情况下删除）。","必要时增大 FRA 大小（db_recovery_file_dest_size）。","设置归档日志清理/备份作业定期运行。"],"related":["last-backup-status"]},"sol_longtxn":{"kind":"solution","title":"根本原因：长时间打开的事务","detail":"最老的活动事务阻止了日志/undo 被回收。","steps":["找到最老的打开事务并使其提交/回滚（或与其负责人沟通）。","修复让会话处于 'idle in transaction' 的应用行为；设置超时。","将长时间运行的报表移到单独的副本/时间窗口。"],"related":["open-transactions"],"playbook":"log-growth"}}},"solver/connection-storm.json":{"id":"connection-storm","title":"连接风暴（连接过多）","icon":"🌊","category":"容量","intro":"出现了 'too many connections'、'remaining connection slots reserved' 或 ORA-00018；新连接被拒绝。找到来源，立即缓解压力，并彻底修复。","start":"measure","nodes":{"measure":{"kind":"step","title":"评估现状：有多少连接，其中多少是活动的？","detail":"先看连接总数、上限以及活动/空闲的比例。","command":{"oracle":"SELECT status, COUNT(*) FROM v$session WHERE type='USER' GROUP BY status;\nSELECT name, value FROM v$parameter WHERE name IN ('processes','sessions');","postgres":"SELECT state, count(*) FROM pg_stat_activity GROUP BY state;\nSHOW max_connections;","mysql":"SHOW STATUS LIKE 'Threads_connected'; SHOW VARIABLES LIKE 'max_connections';","mssql":"SELECT status, COUNT(*) FROM sys.dm_exec_sessions WHERE is_user_process=1 GROUP BY status;","redis":"INFO clients"},"choices":[{"label":"大多数连接是 idle / idle in transaction","next":"sol_idle"},{"label":"大多数是活动连接；需要找出来源","next":"who"}]},"who":{"kind":"step","title":"是谁/来自哪里？按来源对连接分组","detail":"通常是某台应用服务器的连接池暴涨或存在连接泄漏。按主机/用户/应用查看分布。","command":{"postgres":"SELECT client_addr, usename, application_name, count(*) FROM pg_stat_activity GROUP BY 1,2,3 ORDER BY 4 DESC;","mysql":"SELECT host, user, count(*) FROM information_schema.processlist GROUP BY host, user ORDER BY 3 DESC;","mssql":"SELECT host_name, program_name, login_name, COUNT(*) FROM sys.dm_exec_sessions WHERE is_user_process=1 GROUP BY host_name, program_name, login_name ORDER BY 4 DESC;","oracle":"SELECT machine, program, COUNT(*) FROM v$session WHERE type='USER' GROUP BY machine, program ORDER BY 3 DESC;"},"choices":[{"label":"某个应用/主机让连接数暴涨（连接池）","next":"sol_pool"},{"label":"负载确实增加了；所有连接都是合法且活动的","next":"sol_scale"}]},"sol_idle":{"kind":"solution","title":"根本原因：空闲连接堆积/泄漏","detail":"连接被打开后从不关闭，或停留在 'idle in transaction'；白白占用连接槽。","steps":["清理长时间 idle / idle-in-transaction 的连接；使用为管理员保留的连接槽。","设置服务器端超时：PG idle_in_transaction_session_timeout、MySQL wait_timeout、Oracle profile idle_time。","确保应用关闭连接（和事务）；从源头修复泄漏。"],"related":["open-transactions","kill-session","connection-count"],"playbook":"connection-storm"},"sol_pool":{"kind":"solution","title":"根本原因：应用连接池暴涨","detail":"某台应用服务器的连接池没有上限/过大或存在泄漏。连接就是它产生的。","steps":["立即处理：终止相关应用的空闲连接并重启其连接池。","在应用侧设置合理的连接池大小（工具 > 连接池计算器）。","在服务器和数据库之间加入连接池中间件（PgBouncer / ProxySQL / Oracle DRCP / Connection Manager (CMAN)）。","不要盲目增大 max_connections；它会消耗内存，只是推迟问题。"],"related":["connection-count","kill-session"],"playbook":"connection-storm"},"sol_scale":{"kind":"solution","title":"根本原因：负载确实增加","detail":"连接是合法且活动的；容量确实不足。","steps":["通过连接池中间件复用连接，减少到达数据库的连接数。","将读取负载分散到只读副本。","必要时按内存预算有控制地增大 max_connections；同时扩大服务器内存。"],"related":["connection-count"],"playbook":"connection-storm"}}},"solver/replication-lag.json":{"id":"replication-lag","title":"复制延迟/停止","icon":"🔁","category":"复制","intro":"备库/副本落后于主库，或复制完全停止。此决策树逐步缩小延迟的类型（增长、恒定或停止），以找到根本原因，如应用错误、单线程瓶颈、恢复冲突或损坏。","start":"root","nodes":{"root":{"kind":"step","title":"延迟表现如何？","detail":"先评估现状。查看副本流和延迟指标：延迟是随时间增长、停在恒定水平，还是应用已完全停止？据此将排查分为三个主要分支。","command":{"postgres":"SELECT client_addr, state, write_lag, flush_lag, replay_lag,\n       pg_wal_lsn_diff(sent_lsn, replay_lsn) AS bytes_behind\nFROM pg_stat_replication;","mysql":"SHOW REPLICA STATUS\\G","oracle":"SELECT name, value, unit FROM v$dataguard_stats WHERE name IN ('apply lag','transport lag');","mssql":"SELECT database_id, is_local, synchronization_state_desc, redo_queue_size, log_send_queue_size\nFROM sys.dm_hadr_database_replica_states;","redis":"INFO replication","elasticsearch":"GET _cluster/health"},"choices":[{"label":"应用已完全停止/存在错误","next":"stopped"},{"label":"延迟持续增长（追不上）","next":"growing"},{"label":"延迟恒定，但怀疑有长查询","next":"standby_query"},{"label":"落后很多/看起来不一致","next":"sol_rebuild"}]},"stopped":{"kind":"step","title":"应用错误说的是什么？","detail":"若复制已停止，通常是正在应用的某个事件出错。查看错误消息：是目标上缺少行/表、主键冲突，还是连接/网络中断？","command":{"mysql":"SHOW REPLICA STATUS\\G  -- Last_Error, Last_SQL_Error, Replica_IO_Running, Replica_SQL_Running","postgres":"SELECT status, last_msg_receipt_time, latest_end_lsn, slot_name\nFROM pg_stat_wal_receiver;","oracle":"SELECT process, status, sequence#, block# FROM v$managed_standby WHERE process LIKE 'MRP%';","elasticsearch":"GET _cat/recovery?v&active_only=true"},"choices":[{"label":"行或表冲突/缺失错误","next":"sol_apply_error"},{"label":"网络/连接中断，IO 线程已停止","next":"sol_connection"}]},"growing":{"kind":"step","title":"为什么追不上？","detail":"若延迟持续增长，说明副本跟不上主库的写入速率。瓶颈是单线程应用，还是副本的磁盘/IO 慢？查看应用工作线程状态和 IO 等待。","command":{"mysql":"SHOW REPLICA STATUS\\G  -- Seconds_Behind_Source and apply thread state\nSELECT * FROM performance_schema.replication_applier_status_by_worker;","postgres":"SELECT pid, wait_event_type, wait_event, state\nFROM pg_stat_activity WHERE backend_type='startup';","mssql":"SELECT database_id, redo_queue_size, redo_rate, log_send_queue_size\nFROM sys.dm_hadr_database_replica_states;"},"choices":[{"label":"单线程应用 + 繁重的写入负载","next":"sol_parallel"},{"label":"副本的磁盘/IO 慢","next":"sol_io"}]},"standby_query":{"kind":"step","title":"备库上是否有长查询？","detail":"在只读备库上运行的长查询可能阻塞应用（恢复）。在 PostgreSQL 中，恢复冲突就是这样产生的。查看备库上的活动查询和冲突计数器。","command":{"postgres":"SELECT now()-query_start AS dur, pid, state, query\nFROM pg_stat_activity WHERE state='active' ORDER BY query_start;\nSELECT confl_snapshot, confl_lock, confl_bufferpin FROM pg_stat_database_conflicts WHERE datname=current_database();"},"choices":[{"label":"是的，有长查询阻塞了应用","next":"sol_recovery_conflict"},{"label":"没有查询，但仍然落后","next":"sol_parallel"}]},"sol_apply_error":{"kind":"solution","title":"根本原因：应用错误（数据冲突）","detail":"副本卡在一个无法应用的事件上：目标上缺少行、主键冲突或缺少表。修复不一致的原因，而不是盲目跳过。","steps":["检查错误和卡住的事件：哪张表、哪种操作（delete/update/insert）失败了？","找到根本原因：手动写入目标的数据、缺失的 DDL（表/列），或损坏的 binlog/WAL 条目。","对齐 schema：添加缺失的表/列；将冲突行与主库比较并修正。","若必须跳过该事件（如 MySQL 中的单个事件），请记录在案，并在事后验证副本的数据一致性。","防止再次发生：禁止直接写入目标，DDL 通过主库执行。"],"related":["replication-status"],"playbook":"replication-lag"},"sol_connection":{"kind":"solution","title":"根本原因：连接/网络中断","detail":"接收（IO/receiver）线程无法连接主库。问题可能在于网络、认证，或已累积/被清除的日志段。","steps":["验证主库与副本之间的网络访问以及复制用户的权限。","检查所需的 WAL/binlog 段在主库上是否仍然存在（保留/过期）。","在 PostgreSQL 中，确认复制槽存在且处于活动状态；没有复制槽时，WAL 可能已被提前删除。","重新建立连接；日志重新开始传输后，延迟应会追平。"],"related":["replication-status","list-replicas"],"playbook":"replication-lag"},"sol_parallel":{"kind":"solution","title":"根本原因：单线程应用瓶颈","detail":"主库并行写入，而副本按单一顺序应用变更，跟不上。将应用并行化。","steps":["启用并行应用：在 MySQL 中设置 replica_parallel_workers + replica_parallel_type=LOGICAL_CLOCK。","在 PostgreSQL 物理复制中，应用是单个进程；通过拆分大事务来降低主库上的生成速率。","将繁重的批处理作业移到低流量时段或将其拆分。","变更后监控延迟指标；它应下降到零。"],"related":["replication-status"],"playbook":"replication-lag"},"sol_io":{"kind":"solution","title":"根本原因：副本 IO 瓶颈","detail":"应用线程在等待磁盘而不是 CPU。副本的存储跟不上主库的写入速率。","steps":["测量副本磁盘的 IO 延迟/饱和度；与主库比较。","若副本硬件较弱（磁盘慢、内存少），使其与主库一致；硬件不对称是常见原因。","加快用于 redo/WAL 应用的磁盘；为缓冲区/缓存设置足够的大小。","若没有改善，降低写入负载或将读取流量分配到另一个副本。"],"related":["replication-status"],"playbook":"replication-lag"},"sol_recovery_conflict":{"kind":"solution","title":"根本原因：备库恢复冲突","detail":"备库上长时间运行的只读查询阻塞了来自主库的变更应用（vacuum/锁冲突）。PostgreSQL 通过延迟应用或取消查询来解决。","steps":["找出造成阻塞的长查询；若确实需要，等待其完成，否则终止它。","设置 hot_standby_feedback=on，让主库照顾备库上的查询（代价是主库膨胀）。","或者，根据负载调整 max_standby_streaming_delay（在延迟容忍度与查询取消之间权衡）。","将长时间运行的报表移到不会阻塞应用的单独副本上，或换个时间运行。"],"related":["replication-status","open-transactions"],"playbook":"replication-lag"},"sol_rebuild":{"kind":"solution","title":"根本原因：副本落后太多/已损坏——重建","detail":"副本落后太多无法恢复、所需日志已被删除，或数据不一致。最安全的做法是从新的备份重建。","steps":["从主库获取当前的基础备份：PG pg_basebackup、MySQL clone/xtrabackup、Oracle RMAN duplicate。","将备份还原到副本，并重新建立复制配置（复制槽/用户/位置）。","启动复制并监控初始同步（追赶）进度。","延迟降到零后，验证数据一致性和读取流量，再投入使用。"],"related":["replication-status","list-replicas","last-backup-status"],"playbook":"replication-lag"}}},"solver/ora-01555.json":{"id":"ora-01555","title":"ORA-01555: Snapshot too old（Oracle）","icon":"⏳","category":"Oracle","intro":"Oracle 的 'ORA-01555: snapshot too old' 错误表示查询进行一致性读所需的旧数据（undo）已被覆盖。此决策树缩小错误来源的范围（长查询 + UNDO 不足、undo 周转过快或 LOB），并引导你找到正确的修复方法。","start":"root","nodes":{"root":{"kind":"step","title":"错误发生在哪里？","detail":"先确定错误发生的上下文。经典的 ORA-01555 是在长时间运行的 SELECT/报表期间出现，还是在访问 LOB（CLOB/BLOB）列时出现？与 LOB 相关的 ORA-01555 是另一种机制（PCTVERSION/RETENTION），需单独解决。","command":{"oracle":"SELECT sql_id, elapsed_time/1e6 AS sec, sql_text\nFROM v$sql ORDER BY elapsed_time DESC FETCH FIRST 10 ROWS ONLY;"},"choices":[{"label":"在长时间运行的查询/报表期间","next":"undo_size"},{"label":"在访问 LOB 列时","next":"sol_lob"}]},"undo_size":{"kind":"step","title":"UNDO 是否足够，保留时间是否合适？","detail":"长查询从 UNDO 中读取自其开始以来被修改过的块的旧映像。若 UNDO 表空间太小或 undo_retention 短于查询时长，旧 undo 就会被覆盖。测量 UNDO 使用情况和保留时间。","command":{"oracle":"SHOW PARAMETER undo_retention;\nSELECT tablespace_name, SUM(bytes)/1024/1024 AS mb, status\nFROM dba_undo_extents GROUP BY tablespace_name, status;\nSELECT MAX(maxquerylen) AS longest_query_sec, MAX(tuned_undoretention) AS tuned_retention\nFROM v$undostat;"},"choices":[{"label":"UNDO 太小/保留时间短于查询时长","next":"sol_undo"},{"label":"UNDO 足够，但提交非常频繁","next":"sol_commits"},{"label":"查询运行时间过长","next":"sol_query"}]},"sol_undo":{"kind":"solution","title":"根本原因：UNDO/保留时间不足","detail":"长查询所需的旧 undo 因保留期到期或表空间写满而被覆盖。扩大 UNDO，并将保留时间设置为能覆盖查询时长。","steps":["根据最长查询时长（v$undostat.maxquerylen），将 undo_retention 提高到其之上：ALTER SYSTEM SET undo_retention=10800;","将 UNDO 表空间扩大到足以容纳保留时间，或启用自动扩展：ALTER DATABASE DATAFILE '...' AUTOEXTEND ON MAXSIZE ...;","为关键的长查询提供保证：ALTER TABLESPACE undotbs1 RETENTION GUARANTEE;  （注意：若 UNDO 写满，DML 可能失败）。","变更后重新运行同一报表，确认 ORA-01555 已消失。"],"related":["show-config-param"],"playbook":"ora-01555"},"sol_commits":{"kind":"solution","title":"根本原因：undo 周转过快（频繁提交）","detail":"提交非常频繁的大量 DML 会快速循环使用 undo 块，抹掉长查询所需的旧映像。'commit cleanout' 效应会使情况更糟。","steps":["减少不必要的频繁提交：使用批量提交，而不是在循环中每行提交一次。","为减缓 undo 周转，不要把繁重的 DML 负载与长报表安排在同一时间。","增大 undo_retention，必要时用 RETENTION GUARANTEE 保护旧 undo。","将报表移出繁重写入的时间窗口，或将其固定到一致的读取点。"],"related":["open-transactions","show-config-param"],"playbook":"ora-01555"},"sol_query":{"kind":"solution","title":"根本原因：查询时长过长","detail":"查询运行时间太长，即使合理的 undo_retention 也不够。永久的解决办法是加快查询或分流其负载。","steps":["优化查询：修复缺失的索引、糟糕的执行计划或不必要的全表扫描；缩短其时长。","拆分大作业（按日期/范围），使每个分块都能落在 undo 窗口内。","将重型报表移到备库/副本或单独的读取时间——使其不与活动 DML 冲突。","时长可以接受后，相应地设置 undo_retention。"],"related":["explain-plan"],"playbook":"ora-01555"},"sol_lob":{"kind":"solution","title":"根本原因：LOB 保留 / PCTVERSION","detail":"读取 LOB 时出现 ORA-01555，说明保存旧 LOB 版本的空间（RETENTION 或 PCTVERSION）不足。LOB 有单独的版本机制。","steps":["查看 LOB 列的当前设置：SELECT retention, pctversion FROM dba_lobs WHERE table_name='...' AND column_name='...';","根据 AUTO/MANUAL undo 模式提高相应设置：ALTER TABLE t MODIFY LOB (my_column) (RETENTION);  或在 MANUAL 模式下增大 PCTVERSION。","使用 RETENTION 的 LOB 基于数据库的 undo_retention 值——也要保证它足够。","缩短访问 LOB 的长时间操作；变更后重新测试该错误。"],"related":["show-config-param"],"playbook":"ora-01555"}}},"solver/backup-restore.json":{"id":"backup-restore","title":"备份与还原决策","icon":"💾","category":"备份与恢复","intro":"一棵决策树，逐步引导你确定要对备份做什么（执行备份、还原、回到特定时间点 - PITR，或验证备份），并为每种数据库给出正确的命令和步骤。","start":"root","nodes":{"root":{"kind":"step","title":"你想做什么？","detail":"选择你的目标。你是要执行新的备份、还原现有备份、回到特定时间（故障发生前一刻），还是验证手头的备份是否完好？","choices":[{"label":"我想执行备份","next":"take"},{"label":"我想还原备份","next":"restore"},{"label":"回到特定时间点（PITR）","next":"pitr"},{"label":"我想验证备份","next":"verify"}]},"take":{"kind":"step","title":"哪种类型的备份？","detail":"逻辑备份（dump）可移植，适合选择性还原，但对大数据量较慢。物理备份（文件/块级）适合大型数据库和快速恢复。根据需要选择。","choices":[{"label":"逻辑备份（dump / export）","next":"sol_logical_backup"},{"label":"物理/完整备份","next":"sol_physical_backup"}]},"sol_logical_backup":{"kind":"solution","title":"执行逻辑备份（dump）","detail":"以 SQL/BSON 语句导出数据。非常适合跨版本/平台迁移以及还原单张表/单个集合。","steps":["PostgreSQL：pg_dump -Fc -d mydb -f backup.dump  （整个集群使用 pg_dumpall）。","Oracle：expdp app_user/password directory=DP_DIR dumpfile=backup.dmp schemas=APP_SCHEMA logfile=exp.log","MySQL/MariaDB：mysqldump --single-transaction --routines --triggers mydb > backup.sql","MongoDB：mongodump --db mydb --out /backup/dir","Redis：用 BGSAVE 在后台生成 RDB 快照（dump.rdb）。","Elasticsearch：先注册快照仓库，然后 PUT _snapshot/my_repo/snap1?wait_for_completion=true","将备份移到安全位置，并记录其大小/完成状态。"],"related":["logical-dump","last-backup-status"],"playbook":"backup-restore"},"sol_physical_backup":{"kind":"solution","title":"执行物理/完整备份","detail":"数据文件的块/文件级拷贝。对大型数据库速度快，是 PITR 和完整恢复的基础。","steps":["Oracle：RMAN> BACKUP DATABASE PLUS ARCHIVELOG;  （连同归档日志一起）。","PostgreSQL：pg_basebackup -D /backup/base -Fp -Xs -P  （需启用 WAL 归档）。","SQL Server：BACKUP DATABASE [mydb] TO DISK='D:\\backup\\db.bak' WITH CHECKSUM, COMPRESSION;","MySQL/MariaDB：xtrabackup --backup --target-dir=/backup/base  （一致的物理拷贝）。","MongoDB：创建文件系统快照，或从副本集成员获取一致性快照。","将备份复制到离线/远程位置，并记录最后一次备份的状态。"],"related":["last-backup-status"],"playbook":"backup-restore"},"restore":{"kind":"solution","title":"还原备份","detail":"将你手头最合适的备份还原到目标。先验证备份的完整性和时间戳，再用对应引擎的命令加载。","steps":["验证要还原的备份的日期和完好性（了解你的数据丢失窗口）。","PostgreSQL：pg_restore -d new_db backup.dump   （SQL dump：psql -d new_db -f backup.sql）。","Oracle：RMAN> RESTORE DATABASE; RECOVER DATABASE; ALTER DATABASE OPEN;  （逻辑导入使用 impdp）。","MySQL/MariaDB：mysql mydb < backup.sql   （物理：xtrabackup --copy-back）。","SQL Server：RESTORE DATABASE [mydb] FROM DISK='D:\\backup\\db.bak' WITH RECOVERY, REPLACE;","MongoDB：mongorestore --db mydb /backup/dir","还原后，用行数/应用检查验证数据。"],"related":["restore-dump","last-backup-status"],"playbook":"backup-restore"},"pitr":{"kind":"solution","title":"回到特定时间点（PITR）","detail":"你希望以秒级精度回到误删/损坏发生前的那一刻。需要完整备份 + 直到该时间点的事务日志（归档日志 / WAL / binlog）。","steps":["精确确定目标时间（事故发生前一刻），并确认所需日志可用。","Oracle：RMAN> RESTORE DATABASE; RECOVER DATABASE UNTIL TIME \"TO_DATE('2026-07-04 10:00:00','YYYY-MM-DD HH24:MI:SS')\"; ALTER DATABASE OPEN RESETLOGS;","PostgreSQL：还原基础备份，然后设置 recovery_target_time='2026-07-04 10:00:00'，并将 WAL 应用到该时间点。","SQL Server：RESTORE ... WITH NORECOVERY; 然后 RESTORE LOG ... WITH STOPAT='2026-07-04 10:00:00', RECOVERY;","MySQL/MariaDB：还原完整备份，然后用 mysqlbinlog --stop-datetime='2026-07-04 10:00:00' 将 binlog 应用到该时间点。","打开数据库后，验证数据处于预期的时间点。"],"related":["restore-dump","last-backup-status"],"playbook":"backup-restore"},"verify":{"kind":"solution","title":"验证备份","detail":"备份只有被证明可还原后才算真正的备份。定期测试完整性和实际可还原性。","steps":["Oracle：RMAN> RESTORE DATABASE VALIDATE;  块一致性检查使用 VALIDATE CHECK LOGICAL DATABASE;","SQL Server：以 WITH CHECKSUM 执行备份并运行 RESTORE VERIFYONLY ... WITH CHECKSUM（只检查备份文件）；要真正证明，请将备份还原到测试服务器并在那里运行 DBCC CHECKDB([mydb]) WITH NO_INFOMSGS;（CHECKDB 检查的是运行中/已还原的数据库，而不是备份文件）。","PostgreSQL：将备份实际还原到单独的/临时服务器上，并确认 pg_restore 无错误完成。","MySQL/MongoDB：将 dump 还原到测试环境，并比较行数/集合数量和样本数据。","定期安排“还原演练”——仅执行备份是不够的，还要证明能够还原。","记录结果和最后一次成功验证的日期。"],"related":["last-backup-status","restore-dump"],"playbook":"backup-restore"}}},"solver/upgrade-wizard.json":{"id":"upgrade-wizard","title":"版本升级向导","icon":"⬆️","category":"生命周期","intro":"补丁还是大版本，原地升级还是迁移？逐步规划升级；每个分支涵盖预检查、方法和升级后工作。在动生产环境之前先在这里演练。","start":"kind","nodes":{"kind":{"kind":"step","title":"您要进行哪种升级？","detail":"补丁/次版本升级（在同一主版本内，例如 16.2 -> 16.4，或 SQL Server CU）风险较低，通常就地进行。主版本升级（例如 15 -> 16）可能涉及数据格式变化，需要更周密的规划。","command":{"oracle":"SELECT * FROM v$version;\nSELECT patch_id, action, status FROM dba_registry_sqlpatch ORDER BY action_time DESC;","postgres":"SELECT version();","mysql":"SELECT VERSION();","mssql":"SELECT SERVERPROPERTY('ProductVersion') AS v, SERVERPROPERTY('ProductUpdateLevel') AS cu;","elasticsearch":"GET /"},"choices":[{"label":"次版本/补丁（同一主版本，CU）","next":"patch_prep"},{"label":"主版本（主版本号变化）","next":"major_method"},{"label":"我不清楚区别，请解释","next":"explain"}]},"explain":{"kind":"step","title":"补丁还是大版本？","detail":"补丁：大版本不变（16.2 -> 16.4）；数据文件格式不变，通常替换二进制文件 + 重启即可。大版本：大版本号改变（15 -> 16）；目录/格式可能改变，需要 pg_upgrade / AutoUpgrade / dump-restore，并且必须测试应用兼容性。","choices":[{"label":"我的是补丁（相同大版本）","next":"patch_prep"},{"label":"我的是大版本（大版本号改变）","next":"major_method"}]},"patch_prep":{"kind":"step","title":"备份和回退计划准备好了吗？","detail":"尽管补丁风险较低，但每次升级的黄金法则是：先做完整备份（可能的话再做快照）。出问题时需要一个可以回退的点。","choices":[{"label":"是的，备份 + 回退已就绪","next":"sol_patch"},{"label":"不，还没有","next":"sol_backupfirst"}]},"sol_backupfirst":{"kind":"solution","title":"先做备份 + 回退计划","detail":"开始升级前先建立安全网。","steps":["执行完整备份（可能的话再做存储快照/Oracle 担保还原点）。","写下回退步骤：如果出问题，如何回到旧版本？","敲定维护窗口和沟通计划，然后进入打补丁步骤。"],"related":["logical-dump","last-backup-status"]},"sol_patch":{"kind":"solution","title":"应用补丁","detail":"同一大版本内的补丁。在集群中以滚动方式应用（从副本开始）可减少停机时间。","steps":["Oracle：用 opatch 安装新的 RU，然后用 'datapatch -verbose' 应用 SQL 变更。","PostgreSQL：小版本升级 = 安装新的二进制文件并重启服务（无需 initdb/dump）。","MySQL：更新软件包；在 8.0 中服务器会在启动时自动执行升级。","SQL Server：运行相应的 CU/GDR 安装程序，然后重启。","MongoDB/Redis/Elasticsearch：滚动重启；先 secondary 节点，最后 primary。","然后验证版本和应用；若出现错误，执行回退计划。"],"related":["apply-patch","check-version"],"playbook":"upgrade-rollback"},"major_method":{"kind":"step","title":"大版本升级方法","detail":"两条主要路径：(1) 原地升级：在现有服务器上转换（pg_upgrade、Oracle AutoUpgrade/DBUA、MySQL in-place）。速度快但难以回退。(2) 迁移升级：在新版本上搭建全新服务器，迁移数据（通过 dump/restore 或复制），然后切换。更安全，停机时间最短。","choices":[{"label":"我想先做一次准备/兼容性检查","next":"precheck"},{"label":"原地升级","next":"sol_inplace"},{"label":"迁移到新服务器（停机时间最短）","next":"sol_migrate"}]},"precheck":{"kind":"step","title":"兼容性/就绪检查","detail":"升级前收集目标版本的警告：已移除（弃用）的功能、无效对象、兼容级别。","command":{"oracle":"-- AutoUpgrade analyze mode: java -jar autoupgrade.jar -config cfg -mode analyze","postgres":"-- pg_upgrade --check   (with the old and new binary paths)","mysql":"-- MySQL Shell: util.checkForServerUpgrade('root@localhost:3306', {targetVersion: '8.4.0'})","mssql":"-- Compatibility report with Data Migration Assistant (DMA)","elasticsearch":"GET _migration/deprecations"},"choices":[{"label":"输出干净，继续","next":"major_method"},{"label":"存在警告/不兼容项","next":"sol_fixwarn"}]},"sol_fixwarn":{"kind":"solution","title":"先修复警告","detail":"在升级之前解决报告中的每一条警告；否则升级可能中途失败或应用可能出错。","steps":["更新使用已移除（弃用）功能/语法的对象和查询。","编译/修复无效对象；解决缺失的依赖。","为目标版本规划兼容级别/功能版本的步骤。","警告清除后，重新运行预检查并返回方法选择。"],"related":["check-upgrade-readiness","set-compatibility"]},"sol_inplace":{"kind":"solution","title":"原地大版本升级","detail":"在现有服务器上转换。速度快，但回退通常意味着从备份还原，因此快照必不可少。","steps":["执行完整备份 + 快照/担保还原点（唯一的回退途径）。","Oracle：AutoUpgrade（deploy 模式）或 DBUA。PostgreSQL：pg_upgrade（--link 速度快但无法回退）。MySQL：新的二进制文件 + 自动原地升级。SQL Server：setup /ACTION=upgrade。","执行升级后任务（参见下方相关内容）。","在真实负载下验证应用；若出现执行计划回归，固定优化器行为。"],"related":["upgrade-major","post-upgrade-tasks"],"playbook":"upgrade-rollback"},"sol_migrate":{"kind":"solution","title":"迁移升级（停机时间最短）","detail":"在新版本上搭建全新服务器，迁移数据，然后通过短暂的切换完成转换。最安全的方法；旧系统保持完好。","steps":["在目标版本上搭建新服务器并准备 schema。","通过 dump/restore 或复制传输初始数据；然后通过持续复制（CDC / 逻辑复制）保持同步。","在短暂的维护窗口内：停止写入，等待剩余增量同步完成，并将应用指向新服务器（切换）。","验证迁移（行数/校验和）；若有问题，立即切回旧服务器。"],"related":["setup-cdc-replication","verify-migration","export-for-migration"]}}},"solver/migration-wizard.json":{"id":"migration-wizard","title":"迁移向导（数据/schema 迁移）","icon":"🚚","category":"生命周期","intro":"你正在迁移数据/schema。是同构还是异构迁移，停机容忍度是多少？逐步规划：评估 → 迁移 → 验证。先在这里演练切换。","start":"kind","nodes":{"kind":{"kind":"step","title":"您要从什么迁移到什么？","detail":"同构：相同的数据库引擎（例如 Oracle 19 → Oracle 23，或 PostgreSQL 迁移到另一台服务器）。异构：不同的引擎（例如 Oracle → PostgreSQL、MySQL → SQL Server）。异构迁移需要多得多的兼容性工作（数据类型、函数、PL/SQL）。","choices":[{"label":"相同引擎，新服务器/新版本（同构）","next":"downtime"},{"label":"迁移到不同的引擎（异构）","next":"hetero"},{"label":"我还在规划阶段，应该如何着手？","next":"downtime"}]},"downtime":{"kind":"step","title":"你的停机容忍度是多少？","detail":"这决定了迁移方法。若有维护窗口，最简单的途径是 dump/restore。若要求接近零停机，则先加载初始数据，通过持续复制（CDC）保持同步，再进行短暂切换。","choices":[{"label":"有维护窗口（可以接受停机）","next":"sol_offline"},{"label":"要求接近零停机","next":"sol_online"}]},"hetero":{"kind":"step","title":"是否已评估 schema 和类型兼容性？","detail":"在异构迁移中，真正的工作不是复制数据，而是解决差异：数据类型（VARCHAR2↔TEXT）、函数（NVL↔COALESCE）、自增值、PL/SQL ↔ PL/pgSQL、已移除/不同的功能。","choices":[{"label":"我已梳理了 schema/类型差异","next":"sol_hetero"},{"label":"我还没有评估","next":"sol_assess"}]},"sol_assess":{"kind":"solution","title":"先评估兼容性","detail":"迁移前梳理源与目标之间的差异；若跳过这一步，迁移会中途停滞。","steps":["生成 schema 差异：表、约束、索引、视图、存储过程。","映射数据类型和函数（DBAtlas > 类型与函数模块正是为此而建）。","决定如何在目标上处理引擎特有的功能（identity/sequence、分区、PL/SQL、触发器）。","选择转换工具（ora2pg、pgloader、AWS SCT/DMS），并用小样本演练。"],"related":["compare-schema","heterogeneous-migration"]},"sol_offline":{"kind":"solution","title":"离线迁移（dump / restore）","detail":"最简单可靠的同构方法：执行一致性备份，加载到目标，验证，切换。","steps":["从源端导出一致的数据（pg_dump -Fc、expdp FLASHBACK_SCN、mysqldump --single-transaction、BACKUP、mongodump --oplog）。","将其加载到目标（pg_restore -j、impdp、mysql <、RESTORE、mongorestore）。","验证迁移：源和目标之间的行数和关键校验和是否一致？","在维护窗口内将应用指向目标（切换）；若出问题，回退到源端。"],"related":["export-for-migration","import-for-migration","verify-migration"]},"sol_online":{"kind":"solution","title":"最短停机（CDC / 复制）","detail":"加载初始数据，然后通过持续复制使目标与源保持同步；停机只发生在短暂的切换期间。","steps":["用 dump/restore 或快照加载初始（基线）数据。","建立持续复制：PG 逻辑复制（PUBLICATION/SUBSCRIPTION）、MySQL binlog 复制、Oracle GoldenGate、MSSQL 事务复制/CDC、Mongo change streams/mongosync。","等待并监控，直到目标追上源端且延迟约为 0。","短暂切换：停止对源端的写入，等待剩余增量同步完成，将应用指向目标，验证。"],"related":["setup-cdc-replication","verify-migration"]},"sol_hetero":{"kind":"solution","title":"异构迁移（迁移到不同的引擎）","detail":"转换 schema、迁移数据、改写逻辑、验证。分阶段进行；不要一次迁移所有内容。","steps":["转换 schema：使用工具（ora2pg、pgloader、AWS SCT）或手工；使类型和约束适配目标。","迁移数据：批量加载或使用工具；对大表进行分区/分块。","改写服务器端逻辑：PL/SQL → PL/pgSQL，函数/触发器的等价实现。","彻底验证：行数、样本数据、关键查询的结果；在目标上测试应用。","若必须减少停机时间，转换后用 CDC 层保持目标同步，然后再切换。"],"related":["heterogeneous-migration","verify-migration","setup-cdc-replication"]}}},"solver/high-cpu.json":{"id":"high-cpu","title":"CPU 过高","icon":"🔥","category":"性能","intro":"服务器 CPU 已占满。先确认确实是数据库在消耗 CPU，然后沿各分支找到根本原因：执行计划糟糕的查询、执行计划回归、并发过高，或非数据库进程。","start":"root","nodes":{"root":{"kind":"step","title":"真的是数据库在消耗 CPU 吗？","detail":"在操作系统侧检查是哪个进程在消耗 CPU。大部分负载是在数据库进程中，还是在相邻进程中（备份、杀毒软件、其他应用）？据此将排查分为两路。","command":{"oracle":"-- OS side: top -o %CPU  (or: top -c)\n-- DB-side verification:\nSELECT ROUND(value,1) AS host_cpu_util_pct FROM v$sysmetric WHERE metric_name='Host CPU Utilization (%)' AND rownum=1;","postgres":"-- OS: top -c  /  ps -eo pcpu,pid,cmd --sort=-pcpu | head\n-- DB active session count:\nSELECT count(*) FROM pg_stat_activity WHERE state='active';","mysql":"-- OS: top -c  /  mysqladmin processlist\nSHOW GLOBAL STATUS LIKE 'Threads_running';","mssql":"-- OS: Task Manager / typeperf\nSELECT record_id, SQLProcessUtilization AS sql_cpu_pct, (100 - SystemIdle - SQLProcessUtilization) AS other_cpu_pct FROM (SELECT record.value('(./Record/@id)[1]','int') AS record_id, record.value('(./Record/SchedulerMonitorEvent/SystemHealth/SystemIdle)[1]','int') AS SystemIdle, record.value('(./Record/SchedulerMonitorEvent/SystemHealth/ProcessUtilization)[1]','int') AS SQLProcessUtilization FROM (SELECT CONVERT(xml, record) AS record FROM sys.dm_os_ring_buffers WHERE ring_buffer_type='RING_BUFFER_SCHEDULER_MONITOR') AS x) AS y ORDER BY record_id DESC;","mongodb":"// OS: top -c  |  db.serverStatus().metrics\ndb.serverStatus().opcounters","redis":"INFO cpu","elasticsearch":"GET _nodes/stats/os,process"},"choices":[{"label":"是的，是数据库进程在消耗 CPU","next":"db_confirm"},{"label":"不是，是相邻进程/备份作业在消耗","next":"sol_os"}]},"db_confirm":{"kind":"step","title":"负载是如何出现的？","detail":"若是数据库在消耗 CPU，识别其模式：是少数几个重型查询占主导、问题在某次变更后突然出现，还是存在高并发/连接争用？","choices":[{"label":"少数几个重型查询在大量消耗 CPU","next":"topcpu"},{"label":"突然出现；最近有部署/参数变更","next":"sol_regression"},{"label":"大量连接/并发争用","next":"sol_concurrency"}]},"topcpu":{"kind":"step","title":"找出消耗 CPU 最多的查询","detail":"按 CPU 总消耗取出排名靠前的查询。然后判断每个查询是单次调用开销大（执行计划糟糕），还是单次开销小但调用非常频繁。","command":{"oracle":"SELECT sql_id, executions, ROUND(cpu_time/1e6,1) AS cpu_sec, ROUND(cpu_time/GREATEST(executions,1)/1000,2) AS cpu_ms_per_exec, sql_text\nFROM v$sql ORDER BY cpu_time DESC FETCH FIRST 10 ROWS ONLY;","postgres":"SELECT queryid, calls, ROUND(total_exec_time::numeric,1) AS total_ms, ROUND(mean_exec_time::numeric,2) AS mean_ms, query\nFROM pg_stat_statements ORDER BY total_exec_time DESC LIMIT 10;","mysql":"SELECT DIGEST_TEXT, COUNT_STAR AS calls, ROUND(SUM_CPU_TIME/1e12,2) AS cpu_sec, ROUND(SUM_TIMER_WAIT/1e12,2) AS total_sec\nFROM performance_schema.events_statements_summary_by_digest ORDER BY SUM_TIMER_WAIT DESC LIMIT 10;","mssql":"SELECT TOP 10 qs.execution_count, qs.total_worker_time/1000 AS cpu_ms, qs.total_worker_time/qs.execution_count/1000 AS cpu_ms_per_exec, SUBSTRING(st.text,(qs.statement_start_offset/2)+1,50) AS stmt\nFROM sys.dm_exec_query_stats qs CROSS APPLY sys.dm_exec_sql_text(qs.sql_handle) st ORDER BY qs.total_worker_time DESC;","mongodb":"db.setProfilingLevel(1, { slowms: 100 });  // then: db.system.profile.find().sort({ millis: -1 }).limit(10)"},"choices":[{"label":"单次调用开销大；执行计划糟糕/全表扫描","next":"sol_query"},{"label":"单次调用开销小但调用非常频繁","next":"sol_frequency"}]},"sol_query":{"kind":"solution","title":"根本原因：执行计划糟糕的查询在大量消耗 CPU","detail":"少数几个查询由于全表扫描或糟糕的连接顺序，每次调用都消耗过多 CPU。修正其执行计划是核心解决办法。","steps":["获取消耗 CPU 最多的查询的执行计划（EXPLAIN）；查找全表扫描、糟糕的连接顺序或估算行数与实际行数之间的差距。","在过滤/连接列上添加合适的索引；验证执行计划已切换为索引扫描（IXSCAN）。","若优化器估算错误，收集最新的统计信息。","限制失控查询（Oracle Resource Manager / MSSQL Resource Governor），必要时改写查询。"],"related":["top-slow-queries","explain-plan","create-index","update-statistics"],"playbook":"high-cpu"},"sol_frequency":{"kind":"solution","title":"根本原因：调用频率过高","detail":"这些查询单个开销很小，但每秒被调用数千次；总的 CPU 开销就来源于此。通常是 N+1 模式或未缓存的热点路径。","steps":["识别调用最频繁的语句（calls / execution_count），并找到发起调用的应用路径。","将 N+1 模式改为批量查询；用应用/结果缓存减少重复读取。","使用预编译语句降低重复解析（硬解析）的开销。","对于频繁执行且结果不变的重型查询，考虑使用物化视图/汇总表。"],"related":["top-slow-queries","explain-plan"],"playbook":"high-cpu"},"sol_regression":{"kind":"solution","title":"根本原因：执行计划回归/统计信息过时","detail":"系统昨天还正常；在某次部署、参数变更或数据加载后 CPU 飙升。优化器可能切换到了更差的执行计划。","steps":["确定变更：最近的部署、升级/补丁、参数变更或大量数据加载。","为受影响的表收集最新的统计信息（统计信息过时是最常见的原因）。","固定良好的执行计划：Oracle SQL Plan Baseline / hint、MSSQL Query Store 'force plan'。","若变更来自某个参数，将其还原，并以受控方式重新评估。"],"related":["update-statistics","explain-plan","show-config-param"],"playbook":"high-cpu"},"sol_concurrency":{"kind":"solution","title":"根本原因：高并发/连接争用","detail":"单个查询都可以接受，但同时运行的会话太多；活动会话数超过 CPU 核心数会引起上下文切换和 latch/spin 争用。","steps":["比较活动会话数与 CPU 核心数；活动会话多于核心数时，会形成运行队列。","在应用与数据库之间加入连接池中间件（PgBouncer / ProxySQL），并设置合理的连接池大小。","若并行过度，限制并行度（Oracle parallel_degree_limit / parallel_max_servers、MSSQL MAXDOP）。","将读取负载分散到只读副本，减轻主节点的 CPU 压力。"],"related":["connection-count","active-sessions"],"playbook":"connection-storm"},"sol_os":{"kind":"solution","title":"根本原因：非数据库进程在消耗 CPU","detail":"消耗 CPU 的不是数据库，而是相邻进程：同一台机器上的备份/压缩作业、杀毒扫描、日志传送程序或其他应用。","steps":["用操作系统工具（top / 任务管理器）精确定位罪魁祸首进程。","若是备份、统计信息收集或维护作业，将其改到高峰时段之外执行。","将数据目录加入杀毒/扫描进程的排除列表。","若冲突长期存在，将数据库迁移到拥有独立资源的单独服务器上。"],"related":["os-resource"],"playbook":"high-cpu"}}},"solver/memory-pressure.json":{"id":"memory-pressure","title":"内存压力 / OOM","icon":"🧠","category":"性能","intro":"服务器内存即将耗尽、系统正在 swap，或 OOM killer 已触发。先确定是谁在消耗内存：已分配内存（buffer pool/SGA）、会话内存（work_mem/PGA）、整个操作系统，还是 Redis 特有的情况？","start":"root","nodes":{"root":{"kind":"step","title":"是谁在消耗内存？","detail":"按层次区分内存压力的来源。是已分配（固定）内存过大、每会话内存暴涨、非数据库进程在占用，还是 Redis 自身的内存管理在起作用？","command":{"oracle":"SELECT component, ROUND(current_size/1024/1024) AS mb FROM v$memory_dynamic_components WHERE current_size>0 ORDER BY current_size DESC;","postgres":"SHOW shared_buffers; SHOW work_mem; SHOW effective_cache_size;\nSELECT count(*) FROM pg_stat_activity WHERE state='active';","mysql":"SHOW VARIABLES LIKE 'innodb_buffer_pool_size';\nSHOW VARIABLES LIKE 'sort_buffer_size'; SHOW VARIABLES LIKE 'join_buffer_size';\nSHOW STATUS LIKE 'Threads_connected';","mssql":"SELECT physical_memory_in_use_kb/1024 AS used_mb, large_page_allocations_kb/1024 AS large_mb FROM sys.dm_os_process_memory;\nSELECT cntr_value/1024 AS target_mb FROM sys.dm_os_performance_counters WHERE counter_name='Target Server Memory (KB)';","mongodb":"db.serverStatus().wiredTiger.cache","redis":"INFO memory","elasticsearch":"GET _nodes/stats/jvm,os"},"choices":[{"label":"已分配内存（buffer pool / SGA）占主导","next":"allocated"},{"label":"会话内存（work_mem / PGA）暴涨","next":"sol_session"},{"label":"非数据库进程/整个操作系统","next":"sol_os"},{"label":"Redis 特有（maxmemory / 淘汰 / 碎片）","next":"sol_redis"}]},"allocated":{"kind":"step","title":"配置的内存总量是否超过物理内存？","detail":"若固定分配的内存很大，有两种可能：要么配置总量（buffer pool + 会话 + 操作系统）超过物理内存（过量分配），要么大小合理但缓存命中率仍然很低。把各项设置加起来与物理内存比较。","command":{"oracle":"SHOW PARAMETER sga_target;  SHOW PARAMETER pga_aggregate_target;\nSELECT ROUND(value/1024/1024/1024,1) AS host_ram_gb FROM v$osstat WHERE stat_name='PHYSICAL_MEMORY_BYTES';","postgres":"SELECT name, setting, unit FROM pg_settings WHERE name IN ('shared_buffers','work_mem','maintenance_work_mem','max_connections');","mysql":"SELECT ROUND(@@innodb_buffer_pool_size/1024/1024/1024,1) AS pool_gb, ROUND((@@sort_buffer_size+@@join_buffer_size)/1024/1024,1) AS per_conn_mb, @@max_connections AS max_conn;","mssql":"SELECT value_in_use AS max_server_mem_mb FROM sys.configurations WHERE name='max server memory (MB)';\nSELECT total_physical_memory_kb/1024 AS ram_mb FROM sys.dm_os_sys_memory;"},"choices":[{"label":"是的，配置总量超过了物理内存（过量分配）","next":"sol_overcommit"},{"label":"没有，大小合理但缓存命中率低","next":"sol_cache"}]},"sol_overcommit":{"kind":"solution","title":"根本原因：内存过量分配","detail":"buffer pool/SGA + 会话内存 + 操作系统需求之和超过了物理内存。系统正在 swap，或 OOM killer 正在终止进程。","steps":["重新制定内存预算：为操作系统预留一部分，然后正确设置已分配内存的大小（工具 > 内存计算器）。","降低 buffer pool/SGA 目标值和会话内存上限，使其落在物理内存限制之内。","以受控方式应用变更，并确认 swap 使用和 OOM 事件已停止。","若容量长期不足，增加内存或拆分负载。"],"related":["show-config-param","cache-hit-ratio"],"playbook":"memory-pressure"},"sol_cache":{"kind":"solution","title":"根本原因：缓存不足（缓存命中率低）","detail":"已分配内存未超过物理内存，但相对于工作集偏小；buffer cache 命中率低，数据库不断从磁盘读取。","steps":["测量 buffer cache / buffer pool 命中率；若持续偏低（如低于 90%），说明缓存太小。","若有可用内存，以受控方式扩大 buffer pool/SGA，并再次测量命中率。","若热数据确实很大，减少不必要的读取：添加索引、只选择需要的列而不是 SELECT *、分页。","若扩大缓存会给内存带来压力，重新平衡会话内存与已分配内存。"],"related":["cache-hit-ratio","show-config-param"],"playbook":"memory-pressure"},"sol_session":{"kind":"solution","title":"根本原因：会话内存暴涨","detail":"work_mem/PGA 等每会话内存乘以高并发，耗尽了物理内存。每个排序/哈希操作都可能单独分配这部分内存。","steps":["估算最坏情况：会话内存 × 并发会话数 × 操作数；与物理内存比较。","降低全局会话内存设置（work_mem / sort_buffer_size / PGA target）。","对于确实需要大型排序/哈希的重型查询，仅在会话级别提高该设置。","使用连接池限制并发，以降低总内存需求。"],"related":["show-config-param","connection-count"],"playbook":"memory-pressure"},"sol_redis":{"kind":"solution","title":"根本原因：Redis 内存管理","detail":"在 Redis 中，used_memory 已达到 maxmemory、淘汰正在进行，或碎片率过高（used_memory_rss >> used_memory）。需要处理淘汰策略和碎片问题。","steps":["使用 INFO memory 查看 used_memory、maxmemory 和 mem_fragmentation_ratio。","设置符合用途的淘汰策略：CONFIG SET maxmemory-policy allkeys-lru（缓存）或 noeviction（持久数据）。","使用 TTL 清理过期键，并缩减大键（BIG KEYS）。","如果碎片率过高，启用主动碎片整理（CONFIG SET activedefrag yes），或在合适的维护窗口重启。"],"related":["show-config-param"],"playbook":"memory-pressure"},"sol_os":{"kind":"solution","title":"根本原因：非数据库的内存消耗","detail":"内存不是被数据库消耗的，而是被整个操作系统或相邻进程消耗：其他应用、文件系统缓存压力或存在内存泄漏的辅助进程。","steps":["使用操作系统工具（top / free / Task Manager）找出内存消耗最多的进程。","如果罪魁祸首是非数据库进程，将其迁走、加以限制（cgroup / job object）或修复内存泄漏。","如果 OOM killer 正在杀掉数据库进程，检查 OOM 设置并为数据库保证内存。","如果冲突持续存在，将数据库迁移到拥有独立内存的单独服务器。"],"related":["os-resource"],"playbook":"memory-pressure"}}},"solver/install-setup.json":{"id":"install-setup","title":"新安装与安全基线","icon":"🛠️","category":"生命周期","intro":"将新安装的数据库投入生产时跳过的步骤，会导致代价最高的故障。选择你所处的阶段，获取初始配置、安全加固、持久性和监控的有序检查清单。","start":"root","nodes":{"root":{"kind":"step","title":"你处于哪个阶段？","detail":"将新安装投入生产分为四个阶段：初始配置、安全加固、持久性（备份/复制）和监控。你现在正在处理哪一个？建议按顺序逐一完成。","command":{"oracle":"SELECT banner_full FROM v$version; SHOW PARAMETER compatible;","postgres":"SELECT version(); SHOW server_encoding; SHOW data_directory;","mysql":"SELECT VERSION(); SHOW VARIABLES LIKE 'character_set_server'; SHOW VARIABLES LIKE 'datadir';","mssql":"SELECT @@VERSION; SELECT SERVERPROPERTY('Collation') AS collation;","mongodb":"db.version(); db.serverStatus().host","redis":"INFO server","elasticsearch":"GET /"},"choices":[{"label":"安装后的初始配置","next":"firstconfig"},{"label":"安全加固","next":"sol_security"},{"label":"备份与持久性设置","next":"durability"},{"label":"监控与告警设置","next":"sol_monitoring"}]},"firstconfig":{"kind":"step","title":"初始配置的优先事项是什么？","detail":"初始配置分为两大主题：资源/参数规划（内存、连接）和永久性基础（字符集/排序规则、数据目录/文件布局）。这些是之后最难更改的决定。","command":{"postgres":"SELECT name, setting, unit FROM pg_settings WHERE name IN ('shared_buffers','work_mem','max_connections','wal_level');","oracle":"SHOW PARAMETER sga_target; SHOW PARAMETER pga_aggregate_target; SHOW PARAMETER processes;","mysql":"SHOW VARIABLES LIKE 'innodb_buffer_pool_size'; SHOW VARIABLES LIKE 'max_connections';","mssql":"SELECT name, value_in_use FROM sys.configurations WHERE name IN ('max server memory (MB)','max degree of parallelism');"},"choices":[{"label":"内存 / 参数规划","next":"sol_sizing"},{"label":"字符集、排序规则和文件布局","next":"sol_layout"}]},"durability":{"kind":"step","title":"你正在设置持久性的哪一部分？","detail":"持久性依靠两大支柱：经过验证的备份策略（首次备份 + 恢复测试）和连续性（归档/日志模式、复制）。未经测试就说“我有备份”是最常见的误区。","command":{"postgres":"SHOW archive_mode; SHOW wal_level;","oracle":"ARCHIVE LOG LIST; SELECT log_mode FROM v$database;","mysql":"SHOW VARIABLES LIKE 'log_bin'; SHOW VARIABLES LIKE 'server_id';","mssql":"SELECT name, recovery_model_desc FROM sys.databases;"},"choices":[{"label":"备份策略 + 首次备份 + 恢复测试","next":"sol_backup"},{"label":"归档/日志模式 + 复制","next":"sol_replication"}]},"sol_sizing":{"kind":"solution","title":"初始配置：内存和参数规划","detail":"大多数引擎的默认值偏保守；应根据服务器的 RAM 和 CPU 进行规划，但避免过量分配（over-commit）。","steps":["划分总 RAM：为操作系统预留一部分，然后将 buffer pool/SGA 设为主缓存。","根据预期并发量保守地设置会话内存（work_mem/PGA）；不要超过总 RAM。","按实际需求设置 max_connections；高并发场景应规划连接池。","检查引擎特有的核心参数（WAL/redo、checkpoint、并行度），并记录所做的更改。"],"related":["show-config-param","connect-cli"]},"sol_layout":{"kind":"solution","title":"初始配置：字符集、排序规则和文件布局","detail":"字符集/排序规则和数据目录布局在安装时确定；之后更改通常需要重新安装或迁移。","steps":["在服务器级别选择 Unicode 字符集（例如 UTF-8 / AL32UTF8），并设置一致的排序规则。","将数据、日志/WAL 和备份目录放在不同的磁盘/卷上；减少 IO 争用和单点故障。","加固文件系统和权限：只有数据库用户才能读取数据目录。","记录布局和字符集决策，使之后添加的节点与之保持一致。"],"related":["show-config-param","connect-cli"]},"sol_security":{"kind":"solution","title":"安全加固","detail":"新安装带有默认账户、宽松的网络访问和开放的端口。投入生产前缩小攻击面是必须的。","steps":["删除或锁定默认/示例账户；禁用未使用的管理账户。","强制执行强密码策略，并修改所有默认密码。","最小权限：只授予每个应用所需的权限，避免使用宽泛的角色。","启用 TLS，将监听器/端口限制在受信任的网络，并启用审计日志。"],"related":["create-user-grant","change-password","enable-audit"]},"sol_backup":{"kind":"solution","title":"持久性：备份策略和恢复测试","detail":"备份的价值取决于它能否被恢复。进行首次备份，建立连续性，并始终执行恢复测试。","steps":["定义恢复目标（RPO/RTO），并建立全量 + 增量备份计划。","进行首次全量备份；将备份存放在与数据库服务器分离的位置。","将备份恢复到单独的环境中进行测试；完整性未经证明的备份不算数。","调度备份作业，将其输出接入监控，并定期检查最近一次备份的状态。"],"related":["logical-dump","restore-dump","last-backup-status"]},"sol_replication":{"kind":"solution","title":"持久性：归档/日志模式和复制","detail":"要实现时间点恢复和高可用，必须启用归档/日志模式，并且至少要有一个副本。","steps":["启用归档/日志模式（PG archive_mode/wal_level、Oracle ARCHIVELOG、MySQL binlog、MSSQL FULL recovery）。","至少建立一个副本（standby/replica），并完成初始同步。","监控复制延迟，并定义延迟告警。","通过受控的 failover/switchover 演练验证故障转移。"],"related":["replication-status","list-replicas"],"playbook":"replication-lag"},"sol_monitoring":{"kind":"solution","title":"监控与告警设置","detail":"投入生产前的最后一步：在用户之前发现问题。设置健康检查和阈值合理的告警。","steps":["收集基本健康检查指标：缓存命中率、活动/等待会话、最慢的查询。","定义容量告警：磁盘使用率、接近上限的连接数、复制延迟。","将告警发送到某个渠道（邮件/聊天），并根据实际负载校准阈值。","同时将备份成功情况和错误日志纳入监控，以捕获静默故障。"],"related":["cache-hit-ratio","connection-count","free-space"]}}},"solver/corruption-recovery.json":{"id":"corruption-recovery","title":"数据损坏恢复","icon":"🧯","category":"恢复","intro":"针对数据损坏（块/页校验和错误、无法读取的表、损坏的 RDB/AOF）的分步决策树：先确认损坏范围，然后在块级恢复、基于备份的完整恢复/PITR 和最后手段的修复工具之间做出正确选择。","start":"root","nodes":{"root":{"kind":"step","title":"损坏范围有多大？","detail":"先运行完整性检查，确定损坏扩散的程度。是只有少数块/页损坏，影响单个对象（表/索引），还是大范围损坏？使用下面的命令扫描以确定范围，然后选择相应的分支。","command":{"oracle":"RMAN> BACKUP VALIDATE CHECK LOGICAL DATABASE;  -- then: SELECT file#, block#, blocks, corruption_type FROM v$database_block_corruption;","postgres":"pg_amcheck --all --heapallindexed --progress   # single table: pg_amcheck -d mydb -t app_schema.my_table","mysql":"CHECK TABLE app_schema.my_table EXTENDED;   -- for InnoDB also: SHOW ENGINE INNODB STATUS\\G","mssql":"DBCC CHECKDB (N'mydb') WITH NO_INFOMSGS, ALL_ERRORMSGS;","mongodb":"db.my_table.validate({ full: true })","redis":"redis-check-rdb /var/lib/redis/dump.rdb   # AOF (7+, read-only check): redis-check-aof /var/lib/redis/appendonlydir/appendonly.aof.manifest  (--fix modifies the files; only after taking a copy)","elasticsearch":"GET _cluster/health?level=shards   # for a corrupted shard: GET _cat/shards?v&h=index,shard,state,unassigned.reason"},"choices":[{"label":"只有少数块 / 页损坏","next":"block_level"},{"label":"影响单个对象（表/索引）","next":"restore"},{"label":"大范围损坏 / 涉及多个对象","next":"restore"},{"label":"检查工具无法运行，数据库无法打开","next":"last_resort"},{"label":"损坏反复出现（怀疑存在根本原因）","next":"hardware"}]},"block_level":{"kind":"solution","title":"根本原因：有限的块/页损坏 → 定向恢复","detail":"如果只有少数块/页损坏，无需恢复整个数据库。利用引擎的块/页级恢复功能，仅从备份/日志修复损坏部分，将数据丢失降到最低。","steps":["Oracle：查看 v$database_block_corruption 中的损坏块列表后（如果列表已过时，先运行 RMAN> VALIDATE DATABASE;），使用 RMAN> RECOVER DATAFILE 5 BLOCK 1234; 恢复单个块，或使用 RMAN> RECOVER CORRUPTION LIST; 恢复所有列出的块。","SQL Server：单页恢复 — RESTORE DATABASE [db] PAGE='5:1234' FROM DISK='D:\\backup\\db.bak' WITH NORECOVERY;  然后以 WITH NORECOVERY 应用后续日志备份，最后一个使用 WITH RECOVERY。","PostgreSQL：没有自动的块级修复；从备份中部分恢复受影响的表，或者仅为提取数据而临时启用 zero_damaged_pages=on（损坏的行会丢失）。","MySQL/InnoDB：没有单页修复；从最近一次完好的备份恢复受影响的表，必要时通过 PITR 前滚。","修复后重新运行检查命令（VALIDATE / DBCC CHECKDB / pg_amcheck），确认不再存在损坏。","在应用端抽样验证已恢复块中的业务数据。"],"related":["restore-dump","last-backup-status"],"playbook":"corruption"},"restore":{"kind":"solution","title":"根本原因：对象级/大范围损坏 → 基于备份的完整恢复或 PITR","detail":"如果损坏已扩散到一个或多个对象，最安全的做法是从完好的备份恢复，并尽可能前滚到损坏发生的时刻（PITR）。这样可以避免修复工具造成的数据丢失。","steps":["确定损坏开始的大致时间（alert log / error log），以便将 PITR 目标设在此之前。","确认最近一次完好的全量备份，以及截至该时间点的事务日志（归档日志 / WAL / binlog）。","Oracle：RMAN> RESTORE DATABASE; RECOVER DATABASE UNTIL TIME \"TO_DATE('2026-07-04 09:59:00','YYYY-MM-DD HH24:MI:SS')\"; ALTER DATABASE OPEN RESETLOGS;","SQL Server：RESTORE DATABASE [db] FROM DISK='...' WITH NORECOVERY; RESTORE LOG [db] FROM DISK='...' WITH STOPAT='2026-07-04 09:59:00', RECOVERY;","PostgreSQL：恢复基础备份，设置 recovery_target_time='2026-07-04 09:59:00'，并应用截至该时间点的 WAL。","MySQL/MariaDB：恢复全量备份，然后使用 mysqlbinlog --stop-datetime='2026-07-04 09:59:00' 应用截至该时间点的 binlog。","MongoDB：使用 mongorestore 从完好的副本/快照恢复；Elasticsearch：使用 POST _snapshot/my_repo/snap1/_restore 恢复损坏的索引。","打开后重新运行完整性检查，并通过行数统计/应用检查验证数据。"],"related":["restore-dump","logical-dump","last-backup-status"],"playbook":"corruption"},"last_resort":{"kind":"step","title":"没有备份 / 数据库无法打开 — 要采取最后手段吗？","detail":"修复工具（REPAIR_ALLOW_DATA_LOSS、innodb_force_recovery、--fix）可能导致数据丢失，并可能破坏完整性约束。只有在没有可用备份且数据库无法打开时，它们才是最后手段。首先确认确实没有可用的备份。","choices":[{"label":"我找到了完好的备份，进行恢复","next":"restore"},{"label":"完全没有备份，只能使用修复工具","next":"repair"}]},"repair":{"kind":"solution","title":"最后手段：有数据丢失风险的修复工具","detail":"这些工具会丢弃损坏的行/页，使数据库能够打开。仅在没有备份且已先复制现有文件的情况下使用。目标：打开到足以导出数据的程度，然后将数据迁移到干净的副本。","steps":["首先对现有数据文件做原始复制（尝试操作可能使情况恶化）。","SQL Server：DBCC CHECKDB (N'db', REPAIR_ALLOW_DATA_LOSS) WITH ALL_ERRORMSGS;  （数据库必须处于 SINGLE_USER 模式；此命令可能导致数据丢失）。","MySQL/InnoDB：在 my.cnf 的 [mysqld] 下设置 innodb_force_recovery=1 后启动；如果无法打开，逐步增加到 2、3...（最多 6）；一旦打开，立即用 mysqldump 导出数据，然后导入干净的安装并移除该设置。","PostgreSQL：作为最后手段，仅在 WAL 损坏时使用 pg_resetwal（数据丢失风险高）；然后用 pg_dump 提取数据并加载到新集群。","Oracle：没有备份时，需与 Oracle Support 一起评估 DBMS_REPAIR / 基于 event 的恢复；可以用 'skip corrupt' 跳过块（会丢失数据）。","MongoDB：使用 mongod --repair 或 WiredTiger salvage 打开，然后将数据迁移到新的副本集。","修复后务必迁移到干净的安装，重新运行完整性检查，并报告丢失的数据。"],"related":["logical-dump","restore-dump"],"playbook":"corruption"},"hardware":{"kind":"solution","title":"根本原因：硬件/基础设施 → 修复根源并启用校验和","detail":"如果损坏反复出现，真正的原因很可能是磁盘、控制器、内存（RAM）或不稳定的存储层。仅恢复数据是不够的；不修复根源，损坏还会再次出现。同时启用校验和/完整性验证，以便尽早发现。","steps":["检查磁盘健康状况：SMART 值（smartctl -a /dev/sdX）、RAID 控制器日志和存储告警。","运行内存测试（memtest86+ 或 ECC 错误计数器）；单比特错误是导致损坏的典型原因。","检查操作系统/文件系统和硬件错误日志（dmesg、Event Viewer、存储阵列告警）。","PostgreSQL：使用 initdb --data-checksums（或 pg_checksums --enable）创建带校验和的集群，以便在写入磁盘时捕获损坏。","Oracle：通过 DB_BLOCK_CHECKSUM=FULL 和 DB_BLOCK_CHECKING=FULL 启用块校验；并添加 DB_LOST_WRITE_PROTECT=TYPICAL。","SQL Server：确保数据库使用 PAGE_VERIFY CHECKSUM（ALTER DATABASE [db] SET PAGE_VERIFY CHECKSUM）。","MySQL/InnoDB：使用 innodb_checksum_algorithm=crc32，必要时启用 innodb_doublewrite=ON，防止部分写入。","修复根源后，从最近一次完好的备份恢复到干净的副本，并加强监控。"],"related":["restore-dump","last-backup-status"],"playbook":"corruption"}}},"solver/failover-drill.json":{"id":"failover-drill","title":"故障转移演练 / 计划内角色切换","icon":"🔀","category":"复制","intro":"一棵决策树：识别角色变更的类型（计划内主备切换、紧急故障转移，或不涉及生产的演练），并为每种场景给出正确的命令顺序和验证步骤。目标：计划内切换零数据丢失，紧急情况下损失最小，演练时完全不停机。","start":"root","nodes":{"root":{"kind":"step","title":"你想做什么？","detail":"选择角色变更的场景。是在主库健康时进行计划内切换（维护/迁移），还是主库确实已宕机需要紧急切换，或是在不触及生产的情况下进行演练/就绪验证？","choices":[{"label":"计划内主备切换（switchover）（主库健康）","next":"planned"},{"label":"紧急故障转移（主库已宕机）","next":"emergency_check"},{"label":"演练 / 就绪测试（不触及生产）","next":"drill"}]},"planned":{"kind":"step","title":"计划内切换 — 先确认是否就绪？","detail":"计划内主备切换的目标是零数据丢失。为此，切换前必须确认副本已追上主库（延迟 ~0）且状态健康。使用下面的命令检查延迟；如果延迟接近零，继续执行切换步骤。","command":{"oracle":"SELECT thread#, MAX(sequence#) FROM v$archived_log GROUP BY thread#;   -- DG: SELECT name, value FROM v$dataguard_stats WHERE name LIKE '%lag%';","postgres":"SELECT client_addr, state, replay_lag, write_lag FROM pg_stat_replication;","mysql":"SHOW REPLICA STATUS\\G   -- check Seconds_Behind_Source and Replica_SQL_Running","mssql":"SELECT ag.name, drs.synchronization_state_desc, drs.log_send_queue_size, drs.redo_queue_size FROM sys.dm_hadr_database_replica_states drs JOIN sys.availability_groups ag ON ag.group_id = drs.group_id;","mongodb":"rs.printSecondaryReplicationInfo()","redis":"redis-cli INFO replication   # check the difference between master_repl_offset and the slave offset","elasticsearch":"GET _cat/recovery?v&active_only=true"},"choices":[{"label":"延迟接近零，可以切换","next":"sol_planned"},{"label":"延迟较高，先等待同步","next":"drill"}]},"sol_planned":{"kind":"solution","title":"计划内主备切换（无数据丢失的受控切换）","detail":"在主库和备库健康且同步时，以受控方式互换角色。顺序很重要：停止写入，确认剩余变更已到达备库，然后移交角色，并将应用重定向到新主库。","steps":["公布维护窗口，并以受控方式暂停应用的读写。","Oracle Data Guard（12c+）：SQL> ALTER DATABASE SWITCHOVER TO standby_db VERIFY; 然后执行 SQL> ALTER DATABASE SWITCHOVER TO standby_db;  （使用 Broker：DGMGRL> SWITCHOVER TO 'standby_db'；11g 及更早版本：COMMIT TO SWITCHOVER TO PHYSICAL STANDBY）。","PostgreSQL：干净地关闭主库（pg_ctl stop -m fast），用 SELECT pg_promote(); 提升备库，然后用 pg_rewind 将旧主库重新挂接为新的备库。","SQL Server AG：在同步副本上执行 ALTER AVAILABILITY GROUP [ag] FAILOVER;  （无数据丢失的计划内故障转移；需要 SYNCHRONOUS_COMMIT）。","MongoDB：使用 rs.stepDown(60) 强制当前主节点降级 60 s；数据最新的从节点会被自动选举为主节点。","Redis：在新 master 候选节点上执行 REPLICAOF NO ONE;，然后在其他节点上执行 REPLICAOF new_master port;（使用 Sentinel 时：SENTINEL FAILOVER master_name）。","将应用的连接字符串 / 路由指向新主库（VIP、服务名或连接字符串）。","验证：新主库可接受写入，旧主库作为备库正常流复制且延迟 ~0，应用无报错。"],"related":["promote-failover","replication-status","list-replicas"],"playbook":"replication-lag"},"emergency_check":{"kind":"step","title":"紧急切换 — 主库真的宕机了吗？","detail":"在强制故障转移之前，确认主库确实不可达，并评估脑裂风险。如果主库实际上仍存活（例如短暂的网络中断），强制切换会导致出现两个独立的可写副本（脑裂）。同时比较延迟，选择数据最新的备库。","command":{"oracle":"-- try to reach the primary: tnsping primary_db; and on the standby: SELECT thread#, MAX(sequence#) FROM v$archived_log GROUP BY thread#;","postgres":"SELECT pg_last_wal_receive_lsn(), pg_last_wal_replay_lsn();   -- choose the highest LSN among the standbys","mysql":"SHOW REPLICA STATUS\\G   -- choose the candidate with the largest Exec_Source_Log_Pos","mssql":"SELECT drs.replica_id, drs.last_hardened_lsn FROM sys.dm_hadr_database_replica_states drs;","mongodb":"rs.status()   -- the member with the highest optime is the primary candidate","redis":"redis-cli INFO replication   # choose the one with the highest slave_repl_offset","elasticsearch":"GET _cat/nodes?v   # verify whether the master node is reachable"},"choices":[{"label":"是的，主库已宕机 — 强制切换","next":"emergency"},{"label":"不确定 / 主库可能会恢复","next":"planned"}]},"emergency":{"kind":"solution","title":"紧急故障转移（主库已宕机，切换有数据丢失风险）","detail":"主库不可达，且无法等待其恢复。需要强制提升备库；在异步复制下，最后的部分事务可能会丢失。首要任务是恢复服务，然后确定丢失窗口，并安全地恢复旧主库。","steps":["确认主库确实已宕机（将旧主库从网络隔离 / 关闭，以防止脑裂 — fencing/STONITH）。","选择数据最新的备库：操作前比较延迟/偏移量，以提升数据丢失最少的那一个。","Oracle Data Guard：DGMGRL> FAILOVER TO 'standby_db';  （或 SQL> ALTER DATABASE ACTIVATE STANDBY DATABASE;）。","PostgreSQL：立即用 SELECT pg_promote();; 提升备库；旧主库恢复后，切勿在未执行 pg_rewind 的情况下将其作为主库打开（有脑裂风险）。","SQL Server AG：ALTER AVAILABILITY GROUP [ag] FORCE_FAILOVER_ALLOW_DATA_LOSS;  （使用异步副本时可能丢失数据）。","MongoDB：如果已不存在多数派，使用 rs.reconfig(cfg, {force:true}) 重新配置存活的成员。","Redis：没有 Sentinel 时，在新 master 候选节点上运行 REPLICAOF NO ONE；将其他节点挂接到它；绝不允许旧 master 自动重新加入。","将应用重定向到新主库；然后确定并报告丢失的数据窗口（丢失的事务），并将旧主库干净地重建为新的备库。"],"related":["promote-failover","replication-status","list-replicas"],"playbook":"db-down"},"drill":{"kind":"solution","title":"演练 / 就绪测试（在不触及生产的情况下验证）","detail":"在不进行真实切换的情况下，证明你已为故障转移做好准备。在不影响生产的前提下，验证副本健康状况、延迟是否可接受以及应用能否被重定向。可选：在隔离的副本上演练一次真实的主备切换。","steps":["测量延迟并确认低于阈值（PG：pg_stat_replication.replay_lag；MySQL：Seconds_Behind_Source；Oracle：v$dataguard_stats；Mongo：rs.printSecondaryReplicationInfo()）。","验证副本健康状况：所有备库都在流复制，且预期成员上同步模式已激活（SQL Server：synchronization_state_desc='SYNCHRONIZED'）。","读取/角色测试：在备库上运行只读查询，确认能无延迟地得到响应（PG hot standby、Oracle Active Data Guard、只读副本）。","应用路由演练：在测试环境中，将连接字符串/服务名指向新主库，并验证应用可正常连接（不要触及生产流量）。","在隔离的副本上端到端演练一次真实的主备切换，并测量切换时间（RTO）。","同时演练回切步骤：使用 pg_rewind / DG reinstate / AG resume 重建旧主库。","记录发现、耗时和差距；更新运行手册（runbook），并定期安排演练。"],"related":["replication-status","list-replicas","promote-failover"],"playbook":"replication-lag"}}},"solver/security-incident.json":{"id":"security-incident","title":"安全事件 / 可疑访问","icon":"🚨","category":"安全","intro":"针对可疑访问、意外的权限变更或可能的数据泄露的决策树：根据症状引导到正确的响应步骤（锁定账户、撤销多余权限、切断连接、确定范围），以及事后的永久加固。","start":"root","nodes":{"root":{"kind":"step","title":"你注意到了什么？","detail":"选择事件类型。症状是可疑登录或大量失败登录、意外的权限（GRANT/角色）变更、可能的数据泄露 / 异常导出，还是事件已结束、你正在进行永久加固？根据你的第一观察选择最合适的分支。首先记下事件发生的时间以及受影响的账户/资源。","choices":[{"label":"可疑登录 / 大量失败登录","next":"identity"},{"label":"意外的权限（GRANT/角色）变更","next":"privilege"},{"label":"数据泄露 / 异常导出","next":"data"},{"label":"事后永久措施（加固）","next":"harden"}]},"identity":{"kind":"step","title":"身份事件：哪种症状？","detail":"大量失败登录表明存在暴力破解 / 撞库（credential stuffing）攻击。相反，成功但意外的登录（来自异常 IP/在异常时间）表明账户可能已被入侵。两种情况下都应先收集证据，再进行响应。","command":{"oracle":"SELECT username, os_username, userhost, timestamp, returncode FROM dba_audit_session WHERE returncode <> 0 ORDER BY timestamp DESC;","postgres":"-- failed logins in the log file: grep 'password authentication failed' postgresql.log | awk '{print $NF}' | sort | uniq -c | sort -rn","mysql":"SELECT * FROM performance_schema.host_cache ORDER BY COUNT_AUTHENTICATION_ERRORS DESC;   -- also 'Access denied' in the error log","mssql":"-- failed logins in the error log: EXEC xp_readerrorlog 0,1,N'Login failed';  also SELECT login_name, COUNT(*) FROM sys.dm_exec_sessions GROUP BY login_name;","mongodb":"-- audit log: grep 'authenticate' audit.json | grep '\"result\":18'  (18 = AuthenticationFailed)","redis":"ACL LOG   # shows failed authentication/command attempts","elasticsearch":"GET .security-audit-*/_search { \"query\": { \"match\": { \"event.action\": \"authentication_failed\" } } }"},"choices":[{"label":"大量失败登录（暴力破解）","next":"id_bruteforce"},{"label":"成功但意外的登录（账户被入侵）","next":"id_compromised"}]},"id_bruteforce":{"kind":"solution","title":"暴力破解 → 封锁来源并保护账户","detail":"出现大量失败登录时，攻击者可能尚未获得访问权限；目标是封锁来源，通过锁定/延迟保护账户，并排除任何成功入侵。","steps":["识别攻击 IP（见上面的查询），并在网络层面封锁：firewall / pg_hba.conf reject / listener ACL / security group。","临时锁定被攻击的账户：Oracle：ALTER USER target_user ACCOUNT LOCK;  PostgreSQL：ALTER ROLE target_user NOLOGIN;  SQL Server：ALTER LOGIN [target_user] DISABLE;  MySQL：ALTER USER 'target_user'@'%' ACCOUNT LOCK;  Redis：ACL SETUSER target_user off。","应用登录尝试次数限制 / 锁定策略：Oracle：profile 中的 FAILED_LOGIN_ATTEMPTS；PostgreSQL：auth_delay 模块（shared_preload_libraries；它延迟失败的尝试而不是锁定）；MySQL：FAILED_LOGIN_ATTEMPTS ... PASSWORD_LOCK_TIME。","无论如何都将被攻击账户的密码重置为强密码（某次尝试可能已经成功）。","确认是否有尝试成功：检查是否存在来自同一 IP 的成功登录；如有，同时执行账户被入侵分支。","按时间线记录事件，并执行加固步骤以采取永久措施（从根节点进入“事后永久措施”）。"],"related":["change-password","list-users"],"playbook":"harden-sql"},"id_compromised":{"kind":"solution","title":"账户被入侵 → 停用、调查、轮换凭据","detail":"意外的成功登录表明账户已被入侵。先停用该账户及其会话，调查其所作所为，然后重置密码并收窄访问来源。","steps":["立即锁定账户（上面的 LOCK/DISABLE 命令）并终止其活动会话：Oracle：ALTER SYSTEM KILL SESSION 'sid,serial#' IMMEDIATE;  PostgreSQL：SELECT pg_terminate_backend(pid) FROM pg_stat_activity WHERE usename='app_user';  SQL Server：KILL <spid>;  MySQL：KILL <id>;  Redis：CLIENT KILL。","调查该账户在其会话中做了什么：在审计记录/日志中查看其执行的查询、访问的表以及任何 GRANT/CREATE 操作。","将密码重置为强密码，并轮换所有相关的服务/应用凭据。","查找攻击者可能创建的新账户或权限变更（同时执行权限分支）。","收窄访问来源：只允许该账户从已知 IP 连接（pg_hba.conf / listener ACL / firewall）。","执行加固步骤以采取永久措施（从根节点进入“事后永久措施”）。"],"related":["change-password","list-users","show-privileges"],"playbook":"harden-sql"},"privilege":{"kind":"solution","title":"权限提升 → 审计并撤销多余权限","detail":"意外的 GRANT、角色分配或新的高权限账户，可能是入侵的第二阶段（持久化/权限提升）。审计近期的权限变更，撤销不合法的权限，回归最小权限原则。","steps":["审计近期的权限变更：Oracle：SELECT * FROM dba_audit_trail WHERE action_name IN ('GRANT','REVOKE','CREATE USER') ORDER BY timestamp DESC;  SQL Server：default trace / Server Audit 中的 GRANT 事件，以及 SELECT * FROM sys.server_permissions;  MySQL：审计日志中的 GRANT 记录，以及 SELECT * FROM mysql.user;  MongoDB：db.getRoles({showPrivileges:true})。","列出当前权限：Oracle：SELECT grantee, privilege FROM dba_sys_privs WHERE grantee='APP_USER';  PostgreSQL：SELECT * FROM information_schema.role_table_grants WHERE grantee='app_user';  MySQL：SHOW GRANTS FOR 'app_user'@'%';","撤销不合法的多余权限：Oracle：REVOKE DBA FROM app_user;  PostgreSQL：REVOKE ALL ON ... FROM app_user; ALTER ROLE app_user NOSUPERUSER;  SQL Server：ALTER SERVER ROLE [sysadmin] DROP MEMBER [app_user];  MySQL：REVOKE ALL PRIVILEGES, GRANT OPTION FROM 'app_user'@'%';","查找并删除攻击者可能创建的隐藏/后门账户（意外的 CREATE USER 记录）。","检查并收窄授予 PUBLIC 或宽泛角色的过多权限。","确认这些变更与合法的变更申请相符；将所有不相符的变更视为事件的一部分，并执行加固步骤。"],"related":["show-privileges","list-users","create-user-grant"],"playbook":"harden-sql"},"data":{"kind":"solution","title":"数据泄露 → 切断访问并确定范围","detail":"异常大的查询/导出或意外的外部连接表明存在数据泄露。先切断当前危险的连接，然后通过审计记录/日志确定范围：哪些数据被访问、由谁访问、何时访问。","steps":["检查活动连接的来源：PostgreSQL：SELECT pid, usename, client_addr, state, query FROM pg_stat_activity WHERE client_addr IS NOT NULL;  Oracle：SELECT sid, serial#, username, machine, program FROM v$session;  SQL Server：SELECT * FROM sys.dm_exec_connections;  MongoDB：db.currentOp(true);  Redis：CLIENT LIST。","立即切断可疑/外部连接，并锁定该账户（KILL/LOCK 命令）。","在网络层面封锁来源：使用 firewall / pg_hba.conf / listener ACL 封禁可疑 IP 段。","检查审计日志：确定哪些表被 SELECT/EXPORT、行数以及时间窗口（Oracle unified_audit_trail；SQL Server sys.fn_get_audit_file；PostgreSQL pgaudit 日志；MongoDB auditLog）。","查找导出痕迹：大结果集、COPY / SELECT ... INTO OUTFILE、mongoexport、批量读取模式。","记录受影响的数据范围（哪些表、哪些个人/机密字段）以及可能的数据量；评估法律/通报义务，并执行加固步骤。"],"related":["show-privileges","list-users"],"playbook":"harden-sql"},"harden":{"kind":"solution","title":"事后永久加固","detail":"应急响应之后，实施永久控制以防止再次发生：启用审计、执行最小权限、限制网络访问，并完整记录事件。","steps":["启用审计：在审计记录中捕获关键事件（登录、GRANT/REVOKE、敏感表访问）；详情请参阅“审计设置”决策树。","最小权限：只授予每个账户所需的权限，分离共享/服务账户，禁用未使用的账户。","密码/身份策略：强密码、定期轮换、尽可能使用多因素认证，以及集中式身份管理（LDAP/Kerberos）。","网络限制：数据库只对应用服务器开放（firewall、security group、pg_hba.conf、listener ACL）；阻止直接的互联网访问。","加密：启用传输中的 TLS，并在需要时启用静态加密。","监控/告警：为失败登录激增、新的 GRANT 和异常数据量设置告警。","事件记录：报告时间线、受影响资产、采取的措施和经验教训；更新运行手册（runbook）。"],"related":["list-users","show-privileges","change-password"]}}},"solver/audit-setup.json":{"id":"audit-setup","title":"审计设置","icon":"🧾","category":"安全","intro":"一棵决策树：根据你要审计的内容（登录/会话、DDL/模式变更、数据访问或权限变更），在相应的数据库引擎上为该范围启用审计，然后给出读取记录以及保留/轮转的步骤。","start":"root","nodes":{"root":{"kind":"step","title":"你想审计什么？","detail":"选择审计范围。审计一切会产生噪音和成本；按需收窄范围。你想跟踪登录/会话事件、DDL/模式变更、数据访问（DML/SELECT）还是权限变更（GRANT/REVOKE）？","choices":[{"label":"登录 / 会话（login、connect）","next":"login"},{"label":"DDL / 模式变更（CREATE/ALTER/DROP）","next":"ddl"},{"label":"数据访问（DML / SELECT）","next":"dml"},{"label":"权限变更（GRANT / REVOKE）","next":"grants"},{"label":"读取审计记录及保留 / 轮转","next":"retention"}]},"login":{"kind":"solution","title":"登录 / 会话审计","detail":"记录成功和失败的登录：谁、从哪里、何时连接。这是检测未授权访问和暴力破解尝试的基础。按引擎启用相应的策略，然后读取并保留记录。","steps":["Oracle（Unified Auditing）：CREATE AUDIT POLICY login_pol ACTIONS LOGON, LOGOFF;  AUDIT POLICY login_pol;  （针对失败登录：AUDIT POLICY login_pol WHENEVER NOT SUCCESSFUL;）。","PostgreSQL：在 postgresql.conf 中设置 log_connections=on 和 log_disconnections=on 并重新加载；无需 pgaudit 即可记录连接。","MySQL/MariaDB：安装 audit_log 插件（INSTALL PLUGIN audit_log ...）并运行 SET GLOBAL audit_log_policy='LOGINS';（MariaDB：server_audit_events='CONNECT'）。","SQL Server：创建 Server Audit（CREATE SERVER AUDIT ...），将 FAILED_LOGIN_GROUP 和 SUCCESSFUL_LOGIN_GROUP 添加到 Server Audit Specification，并 ENABLE 两者。","MongoDB：使用 --auditDestination file --auditFilter '{ atype: { $in: [\"authenticate\", \"logout\"] } }' 启动（Enterprise/Percona）。","Redis：使用 ACL LOG 跟踪失败的认证/命令尝试（详情请使用 ACL 日志，而不是 'notify'）。","Elasticsearch：在 elasticsearch.yml 中设置 xpack.security.audit.enabled: true，并将 authentication_success、authentication_failed 添加到 include 列表。","要读取并保留/轮转记录，请从根节点进入“读取审计记录及保留 / 轮转”步骤。"],"related":["list-users","change-password"]},"ddl":{"kind":"solution","title":"DDL / 模式变更审计","detail":"记录 CREATE/ALTER/DROP 等模式变更。用于检测未授权的对象变更、隐藏表/后门以及模式漂移。","steps":["Oracle：CREATE AUDIT POLICY ddl_pol ACTIONS CREATE TABLE, ALTER TABLE, DROP TABLE, CREATE USER, ALTER USER;  AUDIT POLICY ddl_pol;","PostgreSQL（pgaudit）：通过 shared_preload_libraries='pgaudit' 加载，然后执行 ALTER SYSTEM SET pgaudit.log='ddl'; SELECT pg_reload_conf();","MySQL/MariaDB：使用 audit_log 时，执行 SET GLOBAL audit_log_policy='ALL';，然后在日志中筛选 DDL 语句（MariaDB：server_audit_events='QUERY_DDL'）。","SQL Server：将 SCHEMA_OBJECT_CHANGE_GROUP（以及 DATABASE_OBJECT_CHANGE_GROUP）添加到 Database Audit Specification 并 ENABLE。","MongoDB：--auditFilter '{ atype: { $in: [\"createCollection\",\"dropCollection\",\"createIndex\",\"dropIndex\",\"createDatabase\",\"dropDatabase\"] } }'。","Elasticsearch：将 index_creation/index_deletion 相关事件添加到审计 include 列表；对于模式（mapping）变更，将 _mapping 调用作为请求记录。","要读取并保留/轮转记录，请从根节点进入“读取审计记录及保留 / 轮转”步骤。"],"related":["show-privileges"]},"dml":{"kind":"solution","title":"数据访问（DML / SELECT）审计","detail":"记录对敏感表的读写访问（SELECT/INSERT/UPDATE/DELETE）。由于可能产生大量记录，应将审计限制在敏感对象上。它对泄露检测和合规（KVKK/GDPR）至关重要。","steps":["收窄范围：只审计敏感的表/列，而不是所有数据（以控制噪音和成本）。","Oracle：CREATE AUDIT POLICY dml_pol ACTIONS SELECT, INSERT, UPDATE, DELETE ON HR.SALARIES;  AUDIT POLICY dml_pol;","PostgreSQL（pgaudit）：广泛记录时使用 ALTER SYSTEM SET pgaudit.log='read,write';；如需定向的对象级审计，只在相关表上为审计角色授予权限并设置 pgaudit.role。","MySQL/MariaDB：使用 audit_log 过滤器只记录相关的表/用户（audit_log_filter / MariaDB server_audit_events='QUERY_DML'）。","SQL Server：将针对特定表和主体（例如 public）的 SELECT、INSERT、UPDATE、DELETE 操作添加到 Database Audit Specification 并 ENABLE。","MongoDB：--auditFilter '{ atype: \"authCheck\", \"param.command\": { $in: [\"find\",\"insert\",\"update\",\"delete\"] }, \"param.ns\": \"db.sensitive\" }'（注意数据量）。","Elasticsearch：将 access_granted/access_denied 添加到审计 include 列表，并使用 emit_request_body 查看查询针对的是哪个索引（记录敏感数据的请求体时需谨慎）。","要读取并保留/轮转记录，请从根节点进入“读取审计记录及保留 / 轮转”步骤。"],"related":["show-privileges","list-users"]},"grants":{"kind":"solution","title":"权限变更（GRANT / REVOKE）审计","detail":"记录权限和角色变更（GRANT/REVOKE、角色分配、用户创建）。这是检测权限提升和后门账户的基础；通常是数据量低但价值高的审计。","steps":["Oracle：CREATE AUDIT POLICY priv_pol ACTIONS GRANT, REVOKE, CREATE USER, ALTER USER, CREATE ROLE;  AUDIT POLICY priv_pol;","PostgreSQL（pgaudit）：ALTER SYSTEM SET pgaudit.log='role'; SELECT pg_reload_conf();  （捕获 GRANT/REVOKE/CREATE ROLE 事件）。","MySQL/MariaDB：使用 audit_log 记录 GRANT/REVOKE/CREATE USER 语句（策略 'ALL' 或 DDL），并在过滤器中保留 ACL 语句。","SQL Server：将 SERVER_PERMISSION_CHANGE_GROUP、SERVER_ROLE_MEMBER_CHANGE_GROUP 和 DATABASE_PERMISSION_CHANGE_GROUP 添加到 Server Audit Specification 并 ENABLE。","MongoDB：--auditFilter '{ atype: { $in: [\"grantRolesToUser\",\"revokeRolesFromUser\",\"createUser\",\"dropUser\",\"createRole\"] } }'。","Elasticsearch：对于角色/用户变更，在审计记录中记录与安全相关的请求事件（realm/role API 调用）。","要读取并保留/轮转记录，请从根节点进入“读取审计记录及保留 / 轮转”步骤。"],"related":["show-privileges","create-user-grant","list-users"]},"retention":{"kind":"solution","title":"读取审计记录及保留 / 轮转","detail":"只有在记录被读取和保留时，审计才有意义。在此定义如何查询记录、记录存放在哪里，以及控制其增长的保留/轮转策略。","steps":["读取记录：Oracle：SELECT event_timestamp, dbusername, action_name, object_name, sql_text FROM unified_audit_trail ORDER BY event_timestamp DESC;  PostgreSQL：pgaudit 输出写入服务器日志文件（log_directory）；用 grep 过滤 'AUDIT:' 行。","SQL Server：SELECT * FROM sys.fn_get_audit_file('D:\\audit\\*.sqlaudit', DEFAULT, DEFAULT);  MySQL：读取 audit_log 文件（JSON/XML）或 MariaDB 的 server_audit.log；MongoDB：解析 audit.json；Elasticsearch：查询 .security-audit-* 索引。","设置保留期限（根据合规要求，例如 90 天 / 1 年），并写入策略。","轮转/清理：Oracle：使用 DBMS_AUDIT_MGMT.SET_LAST_ARCHIVE_TIMESTAMP 和 CLEAN_AUDIT_TRAIL 归档/清除旧记录；PostgreSQL/MySQL/MongoDB 的文件日志使用 logrotate 按大小/时间轮转。","Elasticsearch：为审计索引定义 ILM 策略（rollover + delete）。","将审计记录发送到不可变（WORM）的位置，其访问权限与 DBA 分离（职责分离）；限制访问。","监控存储增长：定期检查审计不会占满磁盘或过度影响性能。"],"related":["list-users","show-privileges"]}}},"solver/decide-ha.json":{"id":"decide-ha","title":"决策：选择哪种 HA/DR 方案？","icon":"🧭","category":"决策指南","intro":"选择高可用和容灾方案，首先考虑的不是技术，而是业务需求（RTO/RPO）、地理分布需求与成本之间的平衡。这棵决策树从你对停机和数据丢失的容忍度出发，在同步副本、异步备库、自动故障转移集群和备份+PITR 中为你指出合适的方案。","start":"root","nodes":{"root":{"kind":"step","title":"你的数据丢失容忍度（RPO）是多少？","detail":"从最严格的约束开始：发生灾难时，你最多能承受丢失几秒的数据？如果要求零数据丢失（RPO=0），则必须使用同步复制；如果可以接受几秒/几分钟，则可以考虑异步方案和更低成本的方法。RPO 目标通常决定了其他所有决策。","choices":[{"label":"必须零数据丢失（RPO = 0）","next":"rpo_zero"},{"label":"可以接受几秒/几分钟的数据丢失","next":"rpo_soft"}]},"rpo_zero":{"kind":"step","title":"是否还要求自动、快速的接管（低 RTO）？","detail":"你要求零数据丢失；但主库宕机时，必须多快恢复服务？需要在几秒内自动故障转移，还是可以接受短暂的人工干预窗口？这决定了选择普通同步副本还是全自动集群。","choices":[{"label":"需要在几秒内自动故障转移","next":"sol_cluster"},{"label":"人工/计划内接管即可；零丢失优先","next":"sol_sync"}]},"rpo_soft":{"kind":"step","title":"是否需要不同的区域/地理位置（DR）？","detail":"如果可以接受一定的数据丢失，真正的问题是容灾范围：必须防范整个数据中心/区域的丢失，还是只需防范同一区域内单个节点的故障？如果需要地理分布，异步备库成为首选；否则评估成本优先的方案。","choices":[{"label":"是的，必须有独立区域以防范地理性灾难","next":"sol_async"},{"label":"不，预算和简单性优先","next":"budget"}]},"budget":{"kind":"step","title":"能否接受较长的恢复时间（高 RTO）？","detail":"你只使用一个区域，并能容忍一定的数据丢失。你追求最低成本，还是希望有一个在故障时快速接管的备用节点？如果恢复耗时数小时可以接受，仅用备份+PITR 是最经济的方案；否则需要一个温备库（warm standby）。","choices":[{"label":"是的，可以接受数小时的恢复时间（最便宜）","next":"sol_backup"},{"label":"不，需要能在几分钟内接管的温备库","next":"sol_async"}]},"sol_sync":{"kind":"solution","title":"建议：同步副本（零数据丢失）","detail":"在至少一个备库确认之前，主库不会完成提交；因此即使主库丢失，也不会丢失任何已确认的事务（RPO=0）。是单区域低延迟网络环境下的理想方案。","steps":["原因：已确认的事务写入备库之前提交不会完成，因此数据丢失在数学上为零。","适用场景：金融/关键 OLTP，同一区域（相同/相邻可用区），具备低 RTT 网络。","示例：PostgreSQL synchronous_commit + synchronous_standby_names、MySQL Group Replication / semisync、Oracle Data Guard SYNC（Maximum Protection/Availability）。","注意：同步确认会给每次提交增加网络延迟；如果备库变慢/断开且只有一个同步副本，主库也可能停止接受写入。应至少设计两个备库或合适的降级方案。","注意：不要向远程/高延迟区域进行同步复制，否则写入性能会急剧下降。地理容灾应使用异步复制。"],"related":["replication-status","promote-failover"]},"sol_async":{"kind":"solution","title":"建议：异步备库（地理容灾）","detail":"备库以流的方式应用变更，不会延迟主库的提交。这是在不影响写入性能的前提下，在另一个区域保留副本的标准方法；代价是接受一个小的数据丢失窗口（等于复制延迟）。","steps":["原因：主库不等待备库确认，因此向远程区域复制不会增加写入延迟。","适用场景：需要区域/数据中心级别的容灾，且可以接受秒级的数据丢失。","示例：PostgreSQL streaming replication（async）、MySQL 异步复制、Oracle Data Guard ASYNC（Maximum Performance）。","注意：故障转移时可能丢失与延迟相当的数据；应持续监控复制延迟并设置阈值告警。","注意：接管通常由人工或外部编排完成；清晰的故障转移流程和定期演练必不可少。"],"related":["replication-status","promote-failover"]},"sol_cluster":{"kind":"solution","title":"建议：自动故障转移集群","detail":"结合健康监控、领导者选举和自动接管的集群方案；同时追求低 RTO（几秒内自动故障转移）和借助同步副本实现的低/零 RPO。运维开销更高，但人工干预降到最低。","steps":["原因：集群管理器自动检测主库故障，提升健康的副本并重定向流量；无需半夜人工干预。","适用场景：低 RTO 和低 RPO 都必须满足，且运维团队能够管理额外的复杂性。","示例：PostgreSQL 的 Patroni/etcd、MySQL InnoDB Cluster（Group Replication + MySQL Router）、SQL Server Always On Availability Groups、Oracle RAC + 带 Fast-Start Failover 的 Data Guard（自动故障转移需要 FSFO 和 observer）。","注意：脑裂风险是真实存在的；没有合适的 fencing/quorum（奇数个节点、可靠的分布式存储）不要部署。","注意：集群本身是一个新的故障面；定期演练故障转移，并在真实条件下验证自动化。"],"related":["promote-failover","replication-status"]},"sol_backup":{"kind":"solution","title":"建议：备份 + PITR（最经济）","detail":"不运行备库，只保留定期备份以及基于 WAL/归档日志的时间点恢复（PITR）。这是成本最低的方案；代价是恢复可能需要数小时（高 RTO）。","steps":["原因：无需持续运行第二个节点的成本；只需保留备份存储和归档日志。","适用场景：停机容忍度高的非关键系统；开发/测试环境；预算和简单性优先时。","示例：使用物理备份 + 归档日志进行 PITR（PG base backup + WAL、Oracle RMAN + archivelog、MySQL xtrabackup + binlog）。","注意：RTO 较高 — 恢复 + 日志应用时间随数据量增长；应实际测量，确认符合你可接受的时间。","注意：未经测试的备份不算备份；定期进行恢复演练和备份验证。"],"related":["last-backup-status","logical-dump"]}}},"solver/decide-isolation.json":{"id":"decide-isolation","title":"决策：选择哪种隔离级别？","icon":"🧭","category":"决策指南","intro":"事务隔离级别决定了并发与一致性之间的平衡。隔离级别越高，异常越少，但锁/冲突和重试越多。这棵决策树根据你能容忍哪些读异常（脏读、不可重复读、幻读），帮助你在 Read Committed、Repeatable Read/Snapshot 和 Serializable 之间做出选择。","start":"root","nodes":{"root":{"kind":"step","title":"你能容忍脏读吗？","detail":"从最基本的异常开始：一个事务是否应该能够读取尚未提交（且可能被回滚）的数据？几乎没有正经的应用需要这样；大多数现代引擎的默认设置已经阻止了脏读。只有在近似/分析性的粗略计数和追求极致速度时才会放宽。","choices":[{"label":"不，我只能读取已提交的数据","next":"committed"},{"label":"是的，粗略/近似读取即可（少见）","next":"sol_read_uncommitted"}]},"committed":{"kind":"step","title":"在同一事务中再次读取时，必须看到相同的结果吗？","detail":"排除脏读之后，下一个问题是：如果在一个事务中两次读取同一行，因为有人在中间提交而看到不同的值（不可重复读），或出现新的行（幻读），对你来说是否有问题？需要报表/计算一致性的逻辑通常需要一致性快照。","choices":[{"label":"不，每条语句看到最新的已提交数据即可","next":"sol_read_committed"},{"label":"是的，整个事务需要一致性快照","next":"consistent"}]},"consistent":{"kind":"step","title":"并发事务在逻辑上必须完全有序吗？","detail":"你需要一致性读取；这样就够了吗，还是并发事务必须产生如同逐个顺序执行（可串行化）一样的结果？如果你能容忍写偏斜（write skew）这类微妙异常（例如两个事务读取不同的行，并以相互使对方失效的方式写入），Snapshot 就足够了；否则需要完全串行化。","choices":[{"label":"不，一致性快照即可","next":"sol_repeatable_read"},{"label":"是的，不能接受任何并发异常","next":"sol_serializable"}]},"sol_read_uncommitted":{"kind":"solution","title":"建议：Read Uncommitted（仅限特殊情况）","detail":"最低的隔离级别；允许脏读。读取时几乎无需等待锁，但可能看到会被回滚的数据。不建议用于通用应用。","steps":["原因：读取者不等待写入者的锁；并发最高，但没有一致性保证。","适用场景：仅限粗略指标/进度计数等准确性无关紧要的情况。","注意：在 PostgreSQL 中该级别的行为与 Read Committed 相同，而 Oracle 根本不提供 Read Uncommitted；真正的脏读主要出现在基于锁的 SQL Server（NOLOCK）中。","注意：“为了速度用 NOLOCK”不应成为长期习惯；它有读到错误/重复/缺失行的风险。"]},"sol_read_committed":{"kind":"solution","title":"建议：Read Committed（OLTP 默认）","detail":"每条语句看到该时刻最新提交的数据；可防止脏读。它是大多数引擎的默认级别，为典型 OLTP 负载提供最佳的并发/一致性平衡。","steps":["原因：防止脏读，又不强迫事务不必要地长时间持有锁；并发性高。","适用场景：简短、目标明确的 OLTP 事务；绝大多数 Web 应用的正确默认选择。","示例：Read Committed 是 PostgreSQL、SQL Server 和 Oracle 的默认级别（在 Oracle 中，一致性由语句级快照提供）。","注意：同一事务中的两次读取可能返回不同的结果（不可重复读/幻读）；不要将需要一致性报表的逻辑建立在它之上。","注意：MySQL/InnoDB 的默认级别是 Repeatable Read；迁移时注意引擎差异。"]},"sol_repeatable_read":{"kind":"solution","title":"建议：Repeatable Read / Snapshot（一致性读取）","detail":"事务在其开始时（或首次读取时）固定的一致性快照上工作；读取结果在事务期间不会改变。借助 MVCC，读不阻塞写，写也不阻塞读。","steps":["原因：为整个事务提供固定视图；防止不可重复读（在 MVCC 引擎中实际上也防止幻读），使读多的报表/计算逻辑保持一致。","适用场景：在一个事务中多次读取相同数据并需要一致结果的报表、对账和批量计算。","示例：MySQL/InnoDB 默认的 Repeatable Read；SQL Server SNAPSHOT（行版本控制）；PostgreSQL REPEATABLE READ；Oracle SERIALIZABLE 实际上是快照语义。","注意：它无法防止写偏斜等串行化异常；如果业务规则需要，升级到 Serializable。","注意：Snapshot/行版本控制可能导致 tempdb 或 undo/version store 增长；长事务会使旧版本膨胀。"]},"sol_serializable":{"kind":"solution","title":"建议：Serializable（完全隔离）","detail":"最高的隔离级别：并发事务产生的结果如同串行执行（逐个顺序执行）一样。它防止所有读异常和写偏斜；代价是更多的冲突和重试需求。","steps":["原因：保证结果始终等价于某个有效的串行执行；消除所有并发异常。","适用场景：承载正确性关键不变量的逻辑：余额/库存检查、绝不能出现重复预订的情况。","示例：PostgreSQL SSI（Serializable Snapshot Isolation）、SQL Server SERIALIZABLE（范围锁）；在 Oracle 中，真正的串行化需在应用层处理。","注意：串行化失败（例如 PG 中的 40001）是预期行为；应用中必须有自动重新执行事务的重试逻辑。","注意：在冲突严重的负载下吞吐量可能下降；不要处处使用，只用于正确性关键的事务。"]}}},"solver/decide-sqlnosql.json":{"id":"decide-sqlnosql","title":"决策：SQL 还是 NoSQL（哪个数据库家族）？","icon":"🧭","category":"决策指南","intro":"选择正确的数据库家族取决于数据模型的形态、一致性与扩展性的优先级，以及查询复杂度。“NoSQL 更现代”之类的概括具有误导性；每个家族都是为特定访问模式设计的。这棵决策树从数据结构出发，引导你在关系型、文档型、键值和搜索引擎中选择合适的工具，并在需要时提醒你采用多语言持久化（polyglot）方式。","start":"root","nodes":{"root":{"kind":"step","title":"数据的性质和主要访问模式是什么？","detail":"从最具决定性的问题开始：数据是具有强关联的结构化记录、具有灵活/可变模式的文档、简单的键值查找，还是文本搜索等特殊需求？数据的形态和查询方式在很大程度上决定了家族。","choices":[{"label":"关系型、结构化记录和复杂查询","next":"relational_check"},{"label":"具有灵活/可变模式的嵌套文档","next":"sol_document"},{"label":"简单键值、极低延迟/缓存","next":"sol_keyvalue"},{"label":"全文 / 分面搜索、日志分析","next":"sol_search"}]},"relational_check":{"kind":"step","title":"优先考虑强一致性（ACID）还是水平扩展？","detail":"数据看起来是关系型的；但你的优先级是什么？多表连接、事务完整性和强一致性（ACID）是必需的，还是要水平扩展单个节点容纳不下的海量数据？关系模型是大多数负载的正确默认选择；只有当规模确实超出单个节点时才会吃力。","choices":[{"label":"ACID、连接和复杂查询的完整性优先","next":"sol_relational"},{"label":"规模高于一切，关系/连接较弱","next":"sol_document"}]},"sol_relational":{"kind":"solution","title":"建议：关系型数据库（RDBMS）","detail":"对于数据结构化、关联性强且需要事务完整性的负载，这是正确的默认选择。ACID 保证、成熟的 SQL 查询引擎和连接能力，使其能够可靠地回答复杂问题。","steps":["原因：提供 ACID 事务、外键完整性和强一致性，以及能够优化多表连接复杂查询的成熟优化器。","适用场景：关系和一致性至关重要的 OLTP 和报表负载，例如金融、订单/库存、ERP/CRM。","示例：PostgreSQL、MySQL、Oracle、SQL Server；时序需求可使用 PostgreSQL + TimescaleDB 等扩展。","注意：模式变更（迁移）需要纪律；如果模式变更频繁且不可预测，评估文档模型。","注意：如果达到单节点的写入/数据上限，通过只读副本、分区，必要时通过分片进行扩展。"],"related":["create-partition"]},"sol_document":{"kind":"solution","title":"建议：文档数据库","detail":"以自然形式（类 JSON 文档）存储模式频繁变化的灵活嵌套数据。适合主要通过单个文档读写、连接需求少且需要水平扩展的应用。","steps":["原因：模式灵活；文档可以增删字段，一次读取即可获取嵌套结构，并且易于水平扩展（分片）。","适用场景：以文档为中心、结构多变、快速演进的数据模型，例如商品目录、用户档案、内容/CMS、事件记录。","示例：MongoDB、Couchbase；在关系型领域，JSONB（PostgreSQL）在一定程度上提供类似的灵活性。","注意：复杂的多文档连接和报表能力较弱；如果关系占主导，关系模型更合适。","注意：一致性和模式校验的责任在很大程度上转移到应用；需在代码中保障数据质量。"]},"sol_keyvalue":{"kind":"solution","title":"建议：键值存储 / 缓存","detail":"专为需要按键进行极快读写且无需复杂查询的负载而设计。通常运行在内存中，提供微秒/毫秒级延迟；非常适合缓存、会话和计数器等任务。","steps":["原因：简单键访问的延迟极低、吞吐量高；内存结构可以减轻关系型数据库的负载。","适用场景：缓存层、会话存储、限流计数器、排行榜、临时队列/pub-sub。","示例：Redis、Memcached；Redis 还提供 list/set/sorted set 等丰富结构以及持久化选项。","注意：它通常不是唯一的数据来源（single source of truth）；源数据应保存在持久化数据库中，这里存放派生/缓存数据。","注意：内存有限且昂贵；根据业务需求规划淘汰策略、TTL 和持久化/持久性设置。"]},"sol_search":{"kind":"solution","title":"建议：搜索 / 分析引擎","detail":"基于倒排索引的引擎，专门用于全文搜索、相关性排序、分面过滤和大规模日志/文本分析。它能提供关系型 LIKE 查询无法实现的搜索体验。","steps":["原因：借助倒排索引和评分，能够高性能地执行全文搜索、自动补全、分面和聚合。","适用场景：商品/站内搜索、日志和可观测性分析、需要按相关性排序搜索体验的应用。","示例：Elasticsearch/OpenSearch、Apache Solr；对于中等规模的全文搜索，PostgreSQL tsvector 可能就足够了。","注意：它通常不是主记录系统；主数据应保存在关系型/文档数据库中，并从那里同步到搜索索引（CDC/同步）。","注意：实时强一致性保证较弱（近实时索引）；设计时要考虑索引延迟。"],"related":["setup-cdc-replication"]}}},"solver/decide-shard-replica.json":{"id":"decide-shard-replica","title":"决策：分片还是复制（扩展）？","icon":"🧭","category":"决策指南","intro":"数据库扩展决策取决于瓶颈在读还是写，以及数据能否容纳在单个节点上。错误的策略意味着不必要的复杂性或瓶颈得不到解决。这棵决策树首先提醒你最便宜、最简单的选项（垂直扩展、缓存），然后帮助你在只读副本和分片之间做出正确选择。","start":"root","nodes":{"root":{"kind":"step","title":"你是否先尝试了最简单的路径：垂直扩展和查询调优？","detail":"在转向分布式架构之前，先拿下最便宜的收益：升级硬件（CPU/RAM/IOPS）、添加缺失的索引并修复高开销查询，可以解决大多数瓶颈。只有在这些手段用尽时才增加复杂性。","choices":[{"label":"没有 / 我还没有达到上限","next":"sol_vertical"},{"label":"是的，垂直扩展和调优已经不够","next":"bottleneck"}]},"bottleneck":{"kind":"step","title":"瓶颈在读，还是在写/数据量？","detail":"找出扩展问题的根源：是大量读查询压垮了单个节点（读密集），还是写入速率或总数据量超出了单个节点的容量（写/数据量密集）？这一区别正是副本与分片的根本差异。","choices":[{"label":"读流量占主导，写入可由单节点承载","next":"read_path"},{"label":"写入速率或数据量超出单个节点","next":"sol_sharding"}]},"read_path":{"kind":"step","title":"读取是重复/可缓存的，还是新鲜且多样的？","detail":"进一步拆分读瓶颈：是一遍又一遍地读取相同的结果（缓存非常有效），还是读取请求多样、广泛且最新的数据（需要额外的读能力）？这决定了选择缓存层还是只读副本。","choices":[{"label":"相同数据被反复读取，可接受轻微过时","next":"sol_cache"},{"label":"多样/最新的读取，需要原始读能力","next":"sol_read_replica"}]},"sol_vertical":{"kind":"solution","title":"建议：先进行垂直扩展 + 查询调优","detail":"在转向分布式架构之前的首选且最便宜的路径。升级硬件并修复查询/索引，可以在不增加运维复杂性的情况下解决大多数扩展问题。","steps":["原因：复杂性和风险最低；保持单节点时，事务/一致性模型不变，也无需拆分应用代码。","适用场景：尚未达到单节点的硬件上限，或者瓶颈实际上来自糟糕的查询/缺失的索引。","步骤：分析慢查询，添加缺失的索引，修复执行计划/统计信息；然后增加 CPU/RAM/IOPS。","注意：垂直扩展是有限的，其成本呈指数增长；单节点仍然是单点故障（HA 需另外建立副本）。","注意：接近上限时提前规划下一步（副本/分片）；仓促的迁移有风险。"]},"sol_read_replica":{"kind":"solution","title":"建议：只读副本（读扩展）","detail":"写入保留在单个节点（主库）上，读流量分发到一个或多个副本。这是为读密集型负载提升原始读能力和弹性的标准方法。","steps":["原因：读查询分散到多个副本；主库只处理写入，读吞吐量几乎线性增长。","适用场景：读写比高的负载；将报表和分析查询与主库分离；同时获得一个 HA 备库。","示例：PostgreSQL 流复制副本、MySQL 副本 + 代理路由、在应用中进行读写分离（read-write split）。","注意：使用异步副本时，复制延迟会导致 read-your-writes 问题（无法立即读到刚写入的数据）；关键读取应路由到主库。","注意：副本无法解决写瓶颈；单个写节点仍是上限 — 如果写入也是瓶颈，则需要分片。"],"related":["replication-status"]},"sol_cache":{"kind":"solution","title":"建议：缓存层","detail":"将频繁重复读取的结果保存在快速的内存层中，可大幅降低数据库负载。采用正确的模式，无需访问数据库即可响应，同时降低延迟和负载。","steps":["原因：重复读取以内存速度响应，根本不会到达数据库；这是收益/成本比最高的读扩展方法。","适用场景：相同数据被多次读取且可接受轻微过时；例如商品详情、设置、参考数据。","示例：使用 Redis/Memcached 的 cache-aside 模式；应用先访问缓存，未命中时再访问数据库。","注意：缓存失效很难；需明确设计 TTL 以及写入时更新/清除的策略。","注意：对于需要强一致性的读取（资金/库存），不要依赖可能过时的缓存；这类读取应从源头提供。"]},"sol_sharding":{"kind":"solution","title":"建议：分片（写/数据扩展）","detail":"数据按分片键（shard key）拆分并分布到多个独立节点；每个节点拥有一部分数据及其写入。这是扩展写入速率和总数据量的方法，但会显著增加复杂性。","steps":["原因：写入和存储分散到多个节点；单写入者和单节点数据上限消失，水平扩展成为可能。","适用场景：写吞吐量或数据量已无法由单个节点（及只读副本）承载；没有其他选择时。","示例：应用层的分片路由、Vitess（MySQL）、Citus（PostgreSQL）、文档数据库内置的分片。","注意：分片键的选择至关重要且难以逆转；糟糕的键会造成热点和分布不均。应选择与访问模式匹配的高基数键。","注意：跨分片的连接、事务和唯一性变得困难；应用复杂性、重新平衡（resharding）和运维负担显著增加。"],"related":["create-partition"]}}},"solver/decide-backup.json":{"id":"decide-backup","title":"决策：选择哪种备份策略？","icon":"🧭","category":"决策指南","intro":"选择备份策略需要平衡三个因素：可容忍的数据丢失量（RPO）、数据量，以及必须多快恢复（RTO）。没有一种方法适合所有场景。这棵决策树从 RPO 目标出发，引导你在逻辑导出、物理备份 + PITR 和快照之间做出选择，并提醒你 3-2-1 和验证是不容妥协的。","start":"root","nodes":{"root":{"kind":"step","title":"你的数据丢失目标（RPO）是多少？","detail":"从最具决定性的约束开始：发生灾难时，你最多能承受丢失几分钟/几小时的数据？如果要求接近零的数据丢失，则必须持续归档日志（PITR）；如果可以接受每日/每周备份的容忍度，较简单的方法就足够了。RPO 决定了策略的框架。","choices":[{"label":"接近零 — 我必须能够回到任意时间点","next":"sol_physical_pitr"},{"label":"可以接受每日/定期备份（丢失几小时）","next":"size"},{"label":"先提醒我通用最佳实践（3-2-1 / 验证）","next":"sol_321"}]},"size":{"kind":"step","title":"数据量和可移植性要求是什么？","detail":"如果定期备份就足够，下一个问题是规模和用途：数据库是小/中型，且希望备份可移植到其他环境/版本，还是数据量大，主要关注在几分钟内恢复？这决定了选择逻辑导出还是快照。","choices":[{"label":"小/中型，可移植且跨版本兼容","next":"sol_logical"},{"label":"数据量大，恢复必须非常快（低 RTO）","next":"sol_snapshot"}]},"sol_logical":{"kind":"solution","title":"建议：逻辑导出（dump）","detail":"在逻辑层面导出数据（模式定义 + 行）；输出为 SQL/文本（pg_dump plain 格式、mysqldump）或工具专有的二进制转储（pg_dump -Fc、Oracle Data Pump）。适用于中小型数据库、迁移、版本升级以及恢复选定表/对象的灵活、可移植方法。","steps":["原因：输出可移植（文本格式可读）；适合在版本/引擎之间迁移，以及恢复单个表/对象。","适用场景：中小型数据、版本升级/迁移、开发环境副本、需要逻辑（行级）恢复。","示例：PostgreSQL pg_dump/pg_dumpall、MySQL mysqldump、Oracle Data Pump（expdp/impdp；生成二进制 .dmp，使用 impdp SQLFILE= 以文本形式查看 DDL）。","注意：由于恢复时需要重新处理数据，对于大型数据库，备份和恢复都非常慢（高 RTO）。","注意：单靠它无法实现低 RPO；时间点恢复需要物理备份 + 日志归档。要获得一致性快照，请使用相应的一致性选项。"],"related":["logical-dump","last-backup-status"]},"sol_physical_pitr":{"kind":"solution","title":"建议：物理备份 + WAL/归档日志（PITR）","detail":"定期进行物理基础备份，并持续归档事务日志（WAL/redo/binlog）。这样可以执行时间点恢复（Point-in-Time Recovery）到任意时刻，将数据丢失降到接近零。","steps":["原因：借助持续的日志归档，可以从最近一次基础备份前滚到之后的任意事务，回到所需时刻（例如一条错误命令执行之前）— RPO 最低。","适用场景：大型关键生产数据库；需要低 RPO 和合理 RTO 的系统。","示例：PostgreSQL base backup + WAL 归档（pgBackRest/barman）、Oracle RMAN + archivelog、MySQL 物理备份 + binlog（xtrabackup）。","注意：搭建和运维更复杂；需持续监控归档日志是否不间断、无缺口地流转 — 一个缺口就会中断 PITR 链。","注意：日志归档存储持续增长；没有保留策略和存储规划，磁盘会被占满。"],"related":["last-backup-status","logical-dump"]},"sol_snapshot":{"kind":"solution","title":"建议：快照","detail":"在存储或虚拟化层对数据进行块级快照。即使对于非常大的数据库，备份和恢复也非常快；但它依赖于基础设施，需要谨慎使用。","steps":["原因：块级副本几乎瞬间创建，恢复非常快；对于大数据量，可提供最低的 RTO。","适用场景：TB 级数据，以快速恢复为优先；存储/云层支持快照时。","示例：SAN/LVM 快照、云盘快照（EBS 等）、ZFS/文件系统快照；需与数据库引擎协同一致地触发。","注意：快照依赖于存储系统，通常位于同一基础设施上；单靠它无法满足 3-2-1 — 需复制到异地。","注意：要实现应用一致性，创建快照时数据库必须处于相应模式（fsfreeze/备份模式/热备份）；否则得到的是“崩溃一致性”（crash-consistent）备份。"],"related":["last-backup-status"]},"sol_321":{"kind":"solution","title":"建议：3-2-1 规则 + 异地 + 强制验证","detail":"无论选择哪种方法，这些原则都必须作为策略的上层约束。仅有备份是不够的；备份必须位于不同介质、不同位置，并定期测试。这是对上述所有选项的补充框架。","steps":["3-2-1：至少保留 3 个副本，存放在 2 种不同的介质/类型上，其中 1 个副本在异地。","为何要异地：火灾、洪水、勒索软件或区域丢失可能一次性摧毁同一地点的所有副本；至少要有一个副本是隔离/远程的。","必须验证：未经测试的备份不算备份；定期进行真实的恢复演练，并测量恢复时间（RTO）。","不可变性：为防范勒索软件，保留一个不可删除/不可修改（immutable/WORM）或物理隔离（air-gapped）的副本。","注意：自动监控备份完整性（checksum）和可恢复性；为失败/过期的备份设置告警。备份本身也要加密。"],"related":["last-backup-status"]}}},"solver/decide-index.json":{"id":"decide-index","title":"决策：选择哪种索引类型？","icon":"🧭","category":"决策指南","intro":"正确的索引类型取决于查询的访问模式（等值、范围或文本/JSON 搜索）、列的基数以及读写平衡。错误的索引既浪费空间又拖慢写入。这棵决策树从查询的形态出发，引导你在 B-tree、Hash、Bitmap、GIN/GiST、部分索引和覆盖索引之间做出选择，并提醒你复合索引中列的顺序至关重要。","start":"root","nodes":{"root":{"kind":"step","title":"你的查询如何使用这一列？","detail":"访问模式是选择索引最具决定性的因素。你只用 '=' 匹配该列，还是进行范围/排序（>、<、BETWEEN、ORDER BY），或者搜索全文、JSON 字段、几何数据/数组？从这里开始；在需求明确之前不要选择索引类型。","choices":[{"label":"等值与范围/排序混合（=、>、<、BETWEEN、ORDER BY）","next":"cardinality"},{"label":"仅等值（=），无范围或排序","next":"sol_hash"},{"label":"全文、JSON/JSONB 字段、数组或几何搜索","next":"sol_gin_gist"},{"label":"查询总是针对同一小部分行（例如 WHERE status='active'）","next":"sol_partial"}]},"cardinality":{"kind":"step","title":"该列的基数（不同值的数量）是多少？","detail":"如果需要范围/排序，默认选择 B-tree；但在读密集的数据仓库中，对于不同值很少的列（低基数：性别、状态、是/否），Bitmap 索引可能更好。该列有很多唯一值，还是少数重复值？","choices":[{"label":"高基数（很多唯一值）或 OLTP 负载","next":"coverage"},{"label":"低基数且读密集 / 数据仓库（DWH）","next":"sol_bitmap"}]},"coverage":{"kind":"step","title":"查询是否只读取索引中的列？","detail":"该访问模式适合 B-tree。最后一项优化：如果查询返回和过滤的所有列都在索引中，引擎可以只用索引回答而无需访问表（index-only / 覆盖扫描）。你的查询读取少数几列，还是需要大部分行数据？","choices":[{"label":"是的，少数几列；无需访问表即可回答","next":"sol_covering"},{"label":"不，标准访问即可","next":"sol_btree"}]},"sol_btree":{"kind":"solution","title":"建议：B-tree（默认）","detail":"B-tree 是一种平衡树结构，支持多种访问模式：等值、范围、排序（ORDER BY）、使用 LIKE 'abc%' 的前缀匹配以及 MIN/MAX。它是大多数关系型引擎的默认索引类型，也是拿不准时的正确起点。","steps":["原因：单一结构即可高效支持等值和范围/排序；它是最通用的索引。","适用场景：OLTP 查询、WHERE + 范围 + ORDER BY 组合、主键/唯一键。","复合索引中列的顺序至关重要：把用于等值条件且选择性最高的列放在最前，范围列放在最后。(a,b) 索引有助于对 'a' 或 'a,b' 的过滤；通常对仅针对 'b' 的过滤没有帮助（最左前缀规则）。","示例：PostgreSQL/Oracle/MySQL(InnoDB)/MSSQL 默认的 B-tree/B+tree 索引。","注意：每个索引都会增加写入（INSERT/UPDATE/DELETE）开销和磁盘空间；不要保留不必要或重复的索引。"],"related":["slow-query"]},"sol_hash":{"kind":"solution","title":"建议：Hash 索引（仅等值）","detail":"Hash 索引存储列值的哈希，只对精确等值（'='）查找非常快。它不支持范围、排序或前缀搜索，因此适用场景很窄。","steps":["原因：等值查找的平均访问时间为常数（O(1)）；对于可放入内存的超大规模等值负载可能有优势。","适用场景：该列只用于 '='，且从不需要范围/排序。","引擎差异：PostgreSQL 有 hash 索引，但在大多数情况下 B-tree 已足够且更灵活；MySQL(InnoDB) 仅在内部以 adaptive hash index 的形式管理哈希索引，而 MEMORY 引擎显式使用 hash；在 Oracle 中，它出现在 cluster/hash cluster 的语境中。","注意：不支持范围、BETWEEN、ORDER BY 和 LIKE 'abc%'；一旦出现这些需求，选择 B-tree。","注意：默认的 B-tree 通常已经足够，未经测量不要切换到 hash。"],"related":["slow-query"]},"sol_bitmap":{"kind":"solution","title":"建议：Bitmap 索引（低基数 / DWH）","detail":"Bitmap 索引为每个不同的值维护一个位数组，并通过按位 AND/OR 极快地组合多个低基数条件。它在数据仓库和报表等读密集型负载中表现出色。","steps":["原因：在不同值很少的列上占用空间极小，并能通过位图运算高效地组合多个条件（WHERE gender=.. AND region=..）。","适用场景：低基数列、星型模型查询、数据仓库/报表、读密集型负载。","引擎差异：这是 Oracle 的传统强项；PostgreSQL 没有持久化的 bitmap 索引，但优化器可以在运行时生成 'bitmap heap scan'。MySQL/MSSQL 本身没有持久化的 bitmap 索引。","注意：不要用于频繁更新的（OLTP）表；bitmap 锁会严重降低行级并发。","注意：在高基数列上优势会消失；此时应优先选择 B-tree。"],"related":["slow-query"]},"sol_gin_gist":{"kind":"solution","title":"建议：GIN / GiST（文本、JSON、数组、几何）","detail":"PostgreSQL 为标准 B-tree 无法处理的访问类型提供了专门的索引家族。GIN 指向一个值内部的多个组成部分（词、JSONB 键、数组元素）；GiST 适合几何/范围/最近邻等查询。","steps":["原因：对于这些数据类型，B-tree 无法高效支持 '包含/重叠/邻近' 之类的查询；GIN/GiST 可以加速它们。","何时使用 GIN：全文搜索（tsvector）、JSONB 字段/键查找（@>、?）、数组包含查询；读多写少。","何时使用 GiST：几何/地理空间（PostGIS）、范围类型、最近邻（KNN）查询。","引擎差异：GIN/GiST 是 PostgreSQL 特有的。MySQL 中的 FULLTEXT 和 SPATIAL（R-tree）索引、Oracle 中的 Oracle Text 和 Spatial，以及 MSSQL 中的 Full-Text Index 用于类似目的。","注意：GIN 的写入开销大且构建慢（可通过 fastupdate 缓解）；在写密集型表上需测量其开销。"],"related":["slow-query"]},"sol_partial":{"kind":"solution","title":"建议：部分索引 / 筛选索引","detail":"部分索引只对满足条件（WHERE ...）的行建立索引，而不是整张表。如果查询总是针对同一小部分行，索引会小得多、快得多，写入开销也会下降。","steps":["原因：只为相关行建立索引，空间和维护开销都会降低；频繁查询的“热”子集会快得多。","适用场景：稳定、狭窄的条件，例如 WHERE status='active'、deleted_at IS NULL、priority='high'。","引擎差异：PostgreSQL 通过 'partial index'（CREATE INDEX ... WHERE ...）直接支持，MSSQL 通过 'filtered index' 支持。Oracle 使用基于函数/条件索引的模式实现等效功能；MySQL(InnoDB) 不直接支持。","注意：索引的 WHERE 条件必须与查询条件匹配；否则优化器无法使用该索引。","注意：如果子集增大，优势会缩小；确保条件是稳定的。"],"related":["slow-query"]},"sol_covering":{"kind":"solution","title":"建议：覆盖（INCLUDE）索引","detail":"覆盖索引包含查询所需的所有列，使引擎只用索引就能回答，完全无需访问表（heap）（index-only scan）。对于频繁执行且返回列少的查询，速度提升显著。","steps":["原因：消除了表访问（heap fetch）；磁盘 I/O 和延迟明显下降。","适用场景：频繁执行、WHERE + SELECT 列明确且结果集较小的查询。","引擎差异：MSSQL 和 PostgreSQL 使用 'INCLUDE' 将非键列添加到索引叶节点；在 MySQL(InnoDB) 中，二级索引已包含主键，因此通过复合索引实现覆盖；在 Oracle 中，当所有需要的列都在索引中时即进行仅索引访问。","注意：额外/过宽的 INCLUDE 列会使索引膨胀并拖慢写入；只添加真正需要的列。","注意：在 PostgreSQL 中，index-only scan 需要最新的 visibility map；定期 VACUUM 必不可少。"],"related":["slow-query"]}}},"solver/decide-partition.json":{"id":"decide-partition","title":"决策：选择哪种分区策略？","icon":"🧭","category":"决策指南","intro":"分区将一张大表在同一个逻辑名称下拆分为多个物理片段，便于删除旧数据，并让查询只访问相关片段（分区裁剪，partition pruning）。但在小表上，它只会增加复杂性。这棵决策树首先质疑是否真的需要分区，然后根据访问和分布模式引导你在 Range、List、Hash 和 Composite 之间做出选择。","start":"root","nodes":{"root":{"kind":"step","title":"表是否真的大到需要分区？","detail":"分区不是性能魔法；它是一种规模和维护（生命周期）工具。如果表不够大，或者查询借助索引已经表现良好，分区只会带来执行计划复杂性和管理开销。你的表是否达到数千万行以上/数百 GB，还是仍然是小/中型？","choices":[{"label":"是的，表非常大，和/或需要批量删除/归档旧数据","next":"access"},{"label":"不，小/中型表，或者我不确定","next":"sol_no_partition"}]},"access":{"kind":"step","title":"你如何访问数据，应该如何拆分？","detail":"分区键应当是大多数查询用于过滤的列；只有这样分区裁剪才会生效。主要的访问模式是什么？按日期/连续范围（最近 30 天），按特定的类别/区域值，还是没有明确的过滤条件、希望均匀分布负载？","choices":[{"label":"日期/序列号等连续范围（最近 X 天、按月）","next":"sol_range"},{"label":"类别/区域/租户等离散值集合","next":"sol_list"},{"label":"没有明确的范围/类别；目标是均匀分布负载","next":"sol_hash"},{"label":"既按日期又数据量大；需要在两个维度上拆分","next":"sol_composite"}]},"sol_no_partition":{"kind":"solution","title":"建议：（暂时）不要分区","detail":"分区对小/中型表没有好处；选好索引并进行正常维护就足够了。过早分区会使查询计划复杂化，选错键会导致无法裁剪，并增加运维开销。","steps":["原因：在小表上，全表扫描或索引访问已经很快；分区除了额外的管理和执行计划复杂性之外毫无增益。","何时保持现状：少于几百万行，无需批量删除旧数据，查询借助索引表现良好。","替代做法：合适的索引，必要时使用归档表，定期进行统计信息收集/VACUUM/ANALYZE。","注意：不要基于“以后会增长”的假设过早建立分区；在增长得到证实且合适的键明确后再切换。","引擎差异：这是适用于所有引擎的通用原则；PostgreSQL 声明式分区、MySQL PARTITION BY 以及 Oracle 和 MSSQL 的分区都可以在之后轻松添加。"],"related":[]},"sol_range":{"kind":"solution","title":"建议：Range 分区","detail":"Range 分区按某列的连续范围拆分行（例如日期范围：每月/每季度一个分区）。它是时序数据和持续增长的表最常用、最强大的策略。","steps":["原因：当查询按日期/范围过滤时，引擎只读取相关分区（分区裁剪）；旧数据可通过一条命令（分区 DROP）瞬间删除。","适用场景：时序数据、日志、订单/交易历史；“最近 N 天”查询和基于时间的归档。","分区键：选择绝大多数查询的 WHERE 子句中都包含的日期/连续列；否则分区裁剪不起作用。","示例：PostgreSQL RANGE 分区、Oracle range/interval 分区（自动创建新分区）、MySQL PARTITION BY RANGE（日期通常使用 RANGE COLUMNS）、MSSQL partition function/scheme。","注意：将未来范围的新分区创建自动化（Oracle interval 或定时作业）；被填满的 'default/maxvalue' 分区会成为热点。"],"related":[]},"sol_list":{"kind":"solution","title":"建议：List 分区","detail":"List 分区按某列的离散已知值对行分组（例如 region = 'TR','EU','US' 或租户 ID）。当存在自然、有限的类别时，它使访问和管理变得清晰。","steps":["原因：当查询按特定类别值过滤时，只读取该分区；按类别进行维护/删除也更容易。","适用场景：区域/国家、产品类型、状态、多租户隔离等离散且相对稳定的值集合。","分区键：值集合应当有限且可预测；如果经常新增值，考虑使用 Hash 而不是 List。","示例：PostgreSQL LIST 分区、Oracle list 分区、MySQL PARTITION BY LIST、带有合适边界的 MSSQL partition scheme。","注意：为未定义/新的值保留一个 'default' 分区；否则该行无法插入并会报错。"],"related":[]},"sol_hash":{"kind":"solution","title":"建议：Hash 分区（均匀分布）","detail":"Hash 分区根据键列的哈希值，将行相对均匀地分布到预先定义数量的分区中。在没有自然范围/类别时，用于避免热点并平衡负载。","steps":["原因：行均匀分布；减少了堆积在单个分区或主键末端的写入热点。","适用场景：写入量大，没有自然的范围/类别，目标是均衡 I/O 和大小（例如高基数的 id）。","分区键：选择高基数、分布均匀的列（通常是主键/唯一键）。","示例：PostgreSQL HASH 分区、Oracle hash 分区、MySQL PARTITION BY HASH/KEY。","注意：它无法为范围查询提供分区裁剪（哈希破坏了顺序）；“最近 N 天”之类的查询会扫描所有分区。之后更改分区数量需要重新分布数据。"],"related":[]},"sol_composite":{"kind":"solution","title":"建议：Composite（多级）分区","detail":"复合分区结合了两种策略：先在顶层拆分，再将每个分区划分为子分区（例如 Range-by-Hash 或 Range-by-List）。当同时需要基于时间的生命周期管理和均匀分布/类别隔离时使用。","steps":["原因：当单一策略不够时，它能同时满足两种需求；例如按月拆分（归档），在月内按哈希分布（防止热点）。","适用场景：超大时序数据 + 高并发写入，或日期 + 区域/租户的双重访问模式。","示例：Oracle composite（range-hash、range-list、list-hash 等）；在 PostgreSQL 中通过对分区再次分区（partition of partition）手动构建；MySQL 子分区（RANGE/LIST + HASH/KEY）。","注意：这是管理和模式复杂性最高的选项；只有在两个维度都是经过证实的需求时才选择。","注意：顶层/子层键选择错误会失去分区裁剪；验证两个层级都与查询模式匹配。"],"related":[]}}},"solver/decide-pooling.json":{"id":"decide-pooling","title":"决策：如何设置连接池？","icon":"🧭","category":"决策指南","intro":"每个数据库连接都有内存和调度开销；连接数不受控制地增长会使服务器变慢，甚至宕机。连接池通过复用少量持久连接来解决这一问题。这棵决策树指导你决定连接池的位置、使用哪种模式（session/transaction/statement）以及合适的大小，并强调为什么在 serverless 环境中必须使用外部连接池。","start":"root","nodes":{"root":{"kind":"step","title":"你的应用架构如何连接数据库？","detail":"连接池的决策从架构开始。是单个长期运行的应用进程（传统服务器），许多独立的进程/实例（微服务、大量 pod），还是随每个请求创建和销毁的 serverless/Lambda 函数？每种情况需要不同的连接池部署位置。","choices":[{"label":"一个/少数几个长期运行的应用进程","next":"sol_app_pool"},{"label":"许多进程/实例/pod 连接同一个数据库","next":"middleware"},{"label":"Serverless / Lambda / 短生命周期函数","next":"sol_serverless"},{"label":"先告诉我如何选择合适的连接池大小","next":"sol_size"}]},"middleware":{"kind":"step","title":"如何选择中间件连接池的模式？","detail":"客户端很多时，中间件连接池（PgBouncer、ProxySQL）可以使数据库上的连接数保持较低。但池模式取决于应用使用的功能：你是否依赖与会话绑定的状态（prepared statement、临时表、SET、advisory lock、会话级功能），还是所有操作都在单个事务内完成？","choices":[{"label":"我依赖会话状态（SET、临时表、会话级 prepared statement）","next":"sol_session_mode"},{"label":"事务简短且独立；我不保留会话状态","next":"sol_txn_mode"}]},"sol_app_pool":{"kind":"solution","title":"建议：应用内连接池","detail":"对于一个/少数几个长期运行的进程，应用内连接池库（HikariCP、Go database/sql 的内置连接池、.NET connection pooling）可以满足大多数需求。连接只打开一次，然后反复复用。","steps":["原因：消除了打开/关闭连接的开销；有限且可预测的连接数可以保护数据库。","适用场景：传统单体应用或少数几个应用实例；总连接需求低于数据库容量。","示例：Java HikariCP、.NET SqlConnection/Npgsql pooling、Node pg-pool、Python SQLAlchemy pool。","关于大小：根据核心数/磁盘数确定连接池大小（下一个决策）；“越多越好”并不适用。","注意：每个应用实例都会打开自己的连接池；实例数 × 池大小不得超过数据库的 max_connections。如果超过，改用中间件连接池。"],"related":["connection-storm"]},"sol_size":{"kind":"solution","title":"建议：正确设置连接池大小","detail":"连接池既不应太小（请求需等待），也不应太大。连接过多会造成 CPU 上下文切换和锁争用，降低吞吐量。池大小不是一个可以无限增大的数字，而是基于核心数和 I/O 的一个较小数值。","steps":["起始公式：常见的起点是 池大小 ≈ (核心数 × 2) + 有效磁盘主轴数；在 SSD/多核环境下，“核心数 × 2-4” 左右是不错的起点。","为何要保持较小：数据库真正的并行度受核心数限制；多余的连接只会排队，并通过上下文切换/锁争用拖慢一切。","规则：面对数百个并发客户端，解决方法不是扩大连接池，而是让一个小连接池快速周转（短事务 + 排队）。","总量限制：(应用实例数 × 池大小) < 数据库 max_connections；为超级用户和维护连接预留余量。","注意：从公式开始，但始终要测量；根据等待时间、活动连接数和队列深度指标进行调优。"],"related":["connection-storm"]},"sol_session_mode":{"kind":"solution","title":"建议：Session 池模式","detail":"在 session 模式下，客户端连接在其会话结束前固定绑定到一个后端服务器连接。会话级状态（SET、临时表、advisory lock、会话级 prepared statement）得以保留，但连接共享程度最低。","steps":["原因：对于需要在整个会话期间保留状态的应用，这是唯一安全的模式；其行为与直接连接数据库相同。","适用场景：使用 SET 会话变量、临时表、LISTEN/NOTIFY、会话级 prepared statement 或 advisory lock 的应用。","示例：PgBouncer pool_mode=session；在 ProxySQL 中，将对会话敏感的流量单独路由。","注意：连接共享程度低，因此客户端多时收益会缩小；尽可能使应用不依赖会话状态，并改用 transaction 模式。","引擎差异：PostgreSQL 使用 PgBouncer；在 MySQL 方面，ProxySQL 以不同方式处理类似行为，会话变量需要注意。"],"related":["connection-storm"]},"sol_txn_mode":{"kind":"solution","title":"建议：Transaction 池模式","detail":"在 transaction 模式下，服务器连接仅在一个事务期间借给客户端，事务结束后归还连接池。因此成千上万的客户端可以使用数据库上极少的真实连接工作；这是最常见的大规模模式。","steps":["原因：连接按事务移交，共享程度最高；用少量数据库连接即可承载大量客户端。","适用场景：简短、独立的事务；不保留（或被改造为不保留）会话状态的微服务/多 pod 架构。","示例：PgBouncer pool_mode=transaction（最常见的配置）、ProxySQL 默认的多路复用（multiplexing）行为。","注意：跨会话的功能会失效：会话级 prepared statement、临时表、SET 和 advisory lock 都不安全。对于协议级的命名 prepared statement，需验证应用/连接池的兼容性。","Statement 模式：更激进的（statement）模式在每条语句后移交连接；仅在非常特定的、无事务的只读场景中考虑，限制非常多。"],"related":["connection-storm"]},"sol_serverless":{"kind":"solution","title":"建议：serverless 必须使用外部连接池","detail":"Serverless/Lambda 函数扩展迅速，每个实例都会打开自己的连接；应用内连接池在这里无济于事，因为实例生命周期短且互不共享。当成百上千个实例突然启动时，数据库连接上限会被突破。解决方法是在数据库前面部署外部连接池。","steps":["原因：在短生命周期、互不共享的函数实例中，应用内连接池无法持久存在；扩容时会发生连接风暴。","适用场景：AWS Lambda、Cloud Functions、Azure Functions 等每个请求创建一个实例的环境。","示例：作为独立层部署的 PgBouncer/ProxySQL，或托管连接池（例如 AWS RDS Proxy、PostgreSQL 的 Data API/外部连接池服务）。","模式：通常首选 transaction 池模式；使函数代码不依赖会话状态。","注意：同时设置每个函数的并发上限（reserved concurrency）；将连接池和数据库 max_connections 一起规划，否则连接池本身会成为瓶颈。"],"related":["connection-storm"]}}},"solver/decide-cache.json":{"id":"decide-cache","title":"决策：选择哪种缓存策略？","icon":"🧭","category":"决策指南","intro":"缓存将频繁读取的数据保存在数据库前面的快速层中（例如 Redis），降低延迟和数据库负载。但并非所有数据都适合缓存，错误的失效处理会带来数据过时/错误的风险。这棵决策树首先检查你是否真的需要缓存，然后根据读写模式帮助你在 cache-aside、read-through、write-through 和 write-behind 之间做出选择，并介绍如何防止缓存击穿（stampede）。","start":"root","nodes":{"root":{"kind":"step","title":"你的负载是什么样的：读密集还是写密集？","detail":"当相同数据被多次读取（读密集）且不常变化时，缓存的收益最大。先确定总体模式：数据是读多改少，持续被更新，还是你不确定添加缓存是否值得？","choices":[{"label":"读取非常密集，数据相对很少变化","next":"read_pattern"},{"label":"写入也很密集；写入的数据会被立即/频繁读取","next":"write_pattern"},{"label":"不确定；我真的需要缓存吗？","next":"sol_no_cache"}]},"read_pattern":{"kind":"step","title":"应该由谁负责填充缓存？","detail":"在读密集场景中，关键选择是由谁填充缓存。是由应用显式管理缓存并自行回退到数据库（灵活性高），还是由缓存层在未命中时从后端数据库获取数据并透明地返回？","choices":[{"label":"由应用显式管理；我需要灵活性和控制力","next":"sol_cache_aside"},{"label":"由缓存层在未命中时从数据库获取（透明）","next":"sol_read_through"}]},"write_pattern":{"kind":"step","title":"写入时，一致性和写入速度哪个优先？","detail":"如果写入密集，你必须决定写入的数据在缓存和数据库之间的一致程度。是在每次写入时同时更新缓存和数据库以保证一致性（写入稍慢），还是先写入缓存，再在后台异步应用到数据库（写入极快，但有丢失风险）？","choices":[{"label":"一致性重要；读取必须始终是最新的","next":"sol_write_through"},{"label":"写入速度至关重要；可以接受略有延迟的一致性和丢失风险","next":"sol_write_behind"}]},"sol_no_cache":{"kind":"solution","title":"建议：暂时不要添加缓存","detail":"缓存不是免费的：它会引入一致性问题（过时数据）、额外的基础设施和失效复杂性。如果查询已经很快，或者数据在每次请求时都会变化，缓存不会带来好处，只会增加故障面。","steps":["原因：缓存会增加一致性、失效和运维开销。没有收益时就是净损失。","何时不需要：查询借助索引已经很快，数据每次读取都不同/与用户相关且不会复用，或者写入远多于读取。","替代做法：先进行索引/查询优化，使用物化视图，或利用数据库自身的 buffer/plan cache。","注意：不要“以防万一”添加缓存；没有经过测量的读瓶颈和可复用的数据，就不要添加。","下一步：一旦证实存在真正的读热点，从 cache-aside 开始。"],"related":["slow-query","high-cpu"]},"sol_cache_aside":{"kind":"solution","title":"建议：cache-aside（懒加载）","detail":"使用 cache-aside 时，应用先检查缓存；如果数据不存在（未命中），则从数据库读取，写入缓存后返回。这是最常见、最灵活的模式；缓存中只会填充实际被请求的数据。","steps":["原因：简单、灵活且有弹性；即使缓存故障，应用也能回退到数据库继续工作。只缓存被请求的数据。","适用场景：通用的读加速；读多、写入适中；Redis/Memcached 的经典用法。","失效：写入时更新或删除相关键（write-invalidate）；为每个键设置合理的 TTL，以限制过时程度。","示例：Redis GET → 不存在则查询数据库 → SET key value EX <ttl>。","注意：第一次请求总是未命中（冷启动）。如果同一个键同时被许多客户端请求，可能发生缓存击穿（cache stampede）— 参见防止击穿（下一项）。","击穿：热门键过期时，成千上万的请求同时打到数据库；可通过锁/single-flight、提前刷新或带随机抖动（jitter）的 TTL 来防止。"],"related":["high-cpu","slow-query"]},"sol_read_through":{"kind":"solution","title":"建议：read-through 缓存","detail":"使用 read-through 时，应用只与缓存交互；未命中时，由缓存层自己从数据库获取数据并填充（通过 provider/loader）。应用代码更简单，填充逻辑集中在一处。","steps":["原因：填充逻辑集中在缓存层；应用代码无需在各处重复“处理未命中”。","适用场景：可以使用统一缓存访问层（库/服务）的读密集型负载。","示例：缓存库/客户端（例如定义了 loader 的本地缓存，或位于 Redis 前面的访问层）在未命中时从数据库加载。","失效：仍然需要 TTL 和写入时失效；仅靠 read-through 无法保证数据新鲜。","注意：与 cache-aside 非常相似；区别在于“由谁填充”。这里同样存在击穿风险；采用相同的应对措施。","引擎说明：Redis 本身不提供 read-through；由应用/库层实现这种模式。"],"related":["slow-query"]},"sol_write_through":{"kind":"solution","title":"建议：write-through 缓存","detail":"使用 write-through 时，每次写入都同时（同步）写入缓存和数据库；两者一起更新。因此缓存永远不会落后于数据库，读取总能看到最新数据。","steps":["原因：缓存和数据库保持一致；如果写入的数据会被立即读取，就没有读到过时数据的风险。","适用场景：写入的数据会被立即、频繁读取，且需要强读一致性的场景。","示例：写入时，应用先更新数据库，然后立即将缓存键设置为新值（或删除它）。","注意：每次写入都要写两个目标，因此写入延迟增加；永远不会被读取的数据也可能被不必要地缓存。两次写入不是原子的（一个成功，另一个失败），可能导致不一致 — 出错时删除缓存条目。","双写陷阱：与分别写入数据库和缓存相比，“写数据库，删除（失效）缓存”的方式通常更安全。"],"related":[]},"sol_write_behind":{"kind":"solution","title":"建议：write-behind（write-back）缓存","detail":"使用 write-behind 时，写入先进入缓存并立即返回给用户；数据库写入在后台异步进行（批量/延迟）。写入速度最高，但存在持久性和一致性风险。","steps":["原因：写入延迟大幅下降，写入可以批量高效地传输到数据库；在写密集负载下减轻数据库压力。","适用场景：计数器、指标或活动流等写入量极高且可以接受略有延迟的一致性的场景。","示例：写入 Redis → 队列/后台 worker 按间隔批量刷新到数据库。","注意：如果缓存节点在写入数据库之前崩溃，数据会丢失；不要用于需要持久性的关键数据，或者使用持久化队列/AOF 加以保护。","注意：数据库会在一段时间内处于过时状态；直接读取数据库的其他系统会看到旧数据。应有意识地接受一致性延迟和丢失窗口。"],"related":[]}}},"solver/decide-datatype.json":{"id":"decide-datatype","title":"决策：选择合适的数据类型","icon":"🧭","category":"决策指南","intro":"选择数据类型是日后修正代价最高的决策之一：错误的类型会导致错误的结果（例如用 float 存储金额）、空间浪费和查询缓慢。这棵决策树引导你为数值、文本、日期/时间、键（PK）、半结构化（JSON）和类别（ENUM）字段选择合适的类型，并指出金额和字符集等经典陷阱。","start":"root","nodes":{"root":{"kind":"step","title":"你要存储什么类型的数据？","detail":"合适的类型由数据的性质决定。该字段属于哪一类：数字、文本、日期/时间、表的主键、灵活/半结构化（JSON）数据，还是一组有限的类别？","choices":[{"label":"数值（数量、金额、测量值、计数器）","next":"numeric"},{"label":"文本（名称、描述、代码）","next":"sol_text"},{"label":"日期和/或时间","next":"sol_datetime"},{"label":"我正在设计主键（PK）","next":"sol_pk"},{"label":"灵活/嵌套/可变字段（半结构化数据）","next":"sol_json"},{"label":"一组有限、已知的类别（状态、类型、优先级）","next":"sol_enum"}]},"numeric":{"kind":"step","title":"数字是整数还是小数 — 是否是金额？","detail":"对于数值，最关键的区别是精确性。该值是整数（数量、ID、计数器）还是小数？如果是小数，是否是金额/财务数值？二进制浮点数（FLOAT/DOUBLE）绝不能用于金额或任何要求精确的地方，因为它会产生舍入误差。","choices":[{"label":"整数（数量、计数器、id）","next":"sol_integer"},{"label":"金额或精确小数（价格、余额、比率）","next":"sol_decimal"}]},"sol_integer":{"kind":"solution","title":"建议：整数（SMALLINT / INT / BIGINT）","detail":"对于整数，选择能覆盖所需范围的最小类型。不必要的大类型会浪费空间和索引大小；但对于计数器/主键等可能增长的字段，足够的宽度对防止溢出至关重要。","steps":["原因：合适的宽度可以同时优化存储和索引/内存占用；算术运算很快。","何时使用 SMALLINT/INT：对于有限的范围（年龄、数量、小计数器），SMALLINT/INT 就足够了。","何时使用 BIGINT：对于持续递增的 ID/计数器以及行数可能达到数十亿的主键，选择 BIGINT — INT 溢出是导致生产中断的严重原因。","引擎差异：在 MySQL 中，INT UNSIGNED 使范围翻倍；PostgreSQL 没有 UNSIGNED，应改用 BIGINT。Oracle NUMBER(p)，MSSQL INT/BIGINT。","注意：不要“以防增长”到处使用 BIGINT；但对于递增的主键，一开始就用 BIGINT 是一份廉价的保险。"],"related":[]},"sol_decimal":{"kind":"solution","title":"建议：NUMERIC / DECIMAL — 金额绝不用 FLOAT","detail":"金额和精确小数应使用定点（精确）的 NUMERIC/DECIMAL。FLOAT/DOUBLE 是二进制浮点数，无法精确存储 0.1 这样的值；加法/乘法会产生分位漂移、错误的余额和审计问题。","steps":["原因：NUMERIC/DECIMAL 以十进制精确存储值；金额运算得到正确结果，没有舍入误差。","适用场景：价格、余额、税额、比率或利息等所有要求精确的字段。指定合适的小数位数：DECIMAL(precision, scale)，例如金额使用 DECIMAL(19,4)。","替代做法：有些系统以最小单位（分）将金额存储为 BIGINT；这同样能保证完全精确。","引擎差异：PostgreSQL NUMERIC、MySQL DECIMAL、Oracle NUMBER(p,s)、MSSQL DECIMAL/MONEY（注意 MONEY 的局限性）。","注意：FLOAT/DOUBLE 只适用于科学/测量的近似值以及性能比精确性更重要的场合 — 绝不能用于金额。"],"related":[]},"sol_text":{"kind":"solution","title":"建议：文本 — VARCHAR vs TEXT vs CHAR","detail":"对于文本，可变长度使用 VARCHAR/TEXT，真正固定长度使用 CHAR。在现代引擎中，VARCHAR 和 TEXT 的存储差异大多可以忽略；真正的决策在于是否施加语义上的限制（长度约束）。","steps":["VARCHAR(n)：如果长度可变且存在合理的上限（例如 email、姓名），使用 VARCHAR(n) — 该限制是一种数据校验手段。","TEXT：对于长度不可预测的长篇自由文本（描述、正文），使用 TEXT。","CHAR(n)：仅用于真正固定的长度（例如国家代码 'TR'，始终为 2 个字符）；对于短文本，CHAR 会在末尾补空格，通常没有必要。","引擎差异：在 PostgreSQL 中 VARCHAR 和 TEXT 性能相同，限制仅仅是一个约束。在 MySQL 中，VARCHAR 存储在行内，TEXT 可能单独存储，并且建立索引有前缀长度限制。在 Oracle 中使用 VARCHAR2。","字符集/排序规则：为支持 emoji 和完整 Unicode，MySQL 中始终使用 utf8mb4（旧的 'utf8' 为 3 字节，会破坏 emoji）；根据排序/比较需求选择排序规则（例如 utf8mb4_0900_ai_ci）。在 PostgreSQL 中，通常使用 UTF-8 数据库 + 合适的排序规则。"],"related":[]},"sol_datetime":{"kind":"solution","title":"建议：日期/时间 — 带时区的 TIMESTAMP","detail":"如果存储的是一个时刻（事件时间），选择具有时区感知的类型。不带时区存储会导致跨区域/夏令时变化时计算和比较出错。","steps":["原因：具有时区感知的类型会将时刻规范化为 UTC，并提供一致的比较和转换；对全球化系统必不可少。","何时使用 TIMESTAMPTZ：在 PostgreSQL 中，事件/交易时刻使用 TIMESTAMP WITH TIME ZONE（timestamptz）；以 UTC 存储，显示时转换为用户所在时区。","仅日期或时间：日历日使用 DATE，与时区无关的时间使用 TIME；时长使用 INTERVAL（在支持的引擎中）。","引擎差异：PostgreSQL timestamptz（实际存储 UTC）。在 MySQL 中，TIMESTAMP 会根据时区进行转换，但其范围有限（2038）；DATETIME 不做转换 — 应在应用中强制执行以 UTC 存储的规范。Oracle TIMESTAMP WITH TIME ZONE，MSSQL DATETIMEOFFSET。","注意：将时间存储为文本（VARCHAR）或 epoch INT 会破坏排序、范围和时区操作；应使用真正的日期/时间类型。"],"related":[]},"sol_pk":{"kind":"solution","title":"建议：主键 — BIGINT vs UUID","detail":"主键有两个主要选项：递增数字（BIGINT identity/sequence）或 UUID。数字主键体积小、对索引友好，但可预测且需要集中生成；UUID 可以分布式生成，但体积更大，其随机顺序可能损害索引。","steps":["BIGINT（递增）：最小、最快的索引；顺序插入对 B-tree 来说是理想的。单一数据库/集中生成时的默认选择；一开始就用 BIGINT 而非 INT 可安全避免溢出。","UUID：可以在客户端/分布式系统中无冲突地生成，并且在 ID 对外暴露时不会泄露序号。但它占用 16 字节（2×BIGINT）。","UUID 的顺序很重要：完全随机的 UUID（v4）不是顺序的，因此会导致索引中的分散插入（页分裂、缓存命中率低）；按时间排序的 UUID（v7/ULID）在很大程度上消除了这一问题。","引擎差异：PostgreSQL 有原生的 uuid 类型；在 MySQL 中将 UUID 存储为 BINARY(16)（而不是文本 CHAR(36)），并尽可能按顺序生成。MSSQL uniqueidentifier + NEWSEQUENTIALID，Oracle RAW(16)。","注意：将 UUID 以 VARCHAR/CHAR(36) 文本存储会使空间和索引大小增加约两到三倍；应以二进制形式存储。"],"related":[]},"sol_json":{"kind":"solution","title":"建议：JSON / JSONB — 适度使用","detail":"JSON 类型对于模式灵活、嵌套或稀疏的字段很强大；但可能会失去关系型约束（约束、索引、类型安全）。规则：将固定且频繁查询的字段放入普通列，只把真正可变的部分保留在 JSON 中。","steps":["原因：在单个列中灵活存储可变/可选字段；无需模式迁移即可添加字段。","适用场景：外部 API 负载、用户自定义字段或产品属性等不可预测的结构。","引擎差异：在 PostgreSQL 中使用 JSONB（二进制、可索引、配合 GIN 支持 @>/? 查询）— 优先于普通 JSON。MySQL 的 JSON 类型也是二进制的，通过 generated column + 索引进行查询。Oracle 21c+ 有 JSON 类型/OSON；2025 之前的 MSSQL 将 JSON 保存在 NVARCHAR 中并用函数处理；SQL Server 2025 和 Azure SQL 具有原生（二进制）json 类型。","注意：为频繁过滤的 JSON 字段建立索引（PostgreSQL GIN、MySQL generated column 索引）；否则每次查询都会扫描整个文档。","注意：不要把所有东西都塞进 JSON — 会失去关系完整性、外键和类型约束。需要查询/约束的字段应当是真正的列。"],"related":["decide-sqlnosql"]},"sol_enum":{"kind":"solution","title":"建议：ENUM vs 查找（参照）表","detail":"对于有限的类别集合有两种选择：引擎的 ENUM 类型，或查找表 + 外键。ENUM 紧凑简单，但添加值需要模式变更；查找表灵活，并且可以携带额外属性（描述、启用标志、排序）。","steps":["何时使用 ENUM：值非常稳定、很少变化（例如性别、星期几），且不需要额外属性；紧凑且易读。","何时使用查找表：值可能随时间变化，需要翻译/描述/排序/启用标志等额外字段，或报表需要与之连接 — 它更灵活。","引擎差异：PostgreSQL 和 MySQL 提供原生 ENUM 类型（在 PostgreSQL 中向 ENUM 添加值需要 ALTER TYPE，排序遵循定义顺序）。Oracle 和 MSSQL 没有内置 ENUM；使用 CHECK 约束或查找表。","注意：在 ENUM 中添加/删除值属于模式迁移，在大表上可能导致锁/开销；对于频繁变化的集合，优先使用查找表。","替代做法：对于小而稳定的集合，自由文本 + CHECK 约束也是一种实用的折中方案。"],"related":[]}}},"solver/decide-capacity.json":{"id":"decide-capacity","title":"决策：服务器规格规划","icon":"🧭","category":"决策指南","intro":"正确的规格规划从负载画像（OLTP、数据仓库、混合）出发，需要找出真正的瓶颈（CPU、I/O 或 RAM）。黄金法则：频繁访问的数据集（工作集）必须能放入 RAM；先测量再扩展，并为突发负载预留余量。这棵决策树帮助你识别负载，并根据瓶颈选择资源和扩展方向（垂直/水平）。","start":"root","nodes":{"root":{"kind":"step","title":"你的负载画像是什么？","detail":"规格规划从负载开始，因为 OLTP 和分析型负载需要完全不同的资源配置。系统的主要工作是什么：大量小而快的事务（OLTP），带有大规模扫描和聚合的报表/分析（DWH），还是两者混合？如果不确定，需要先进行测量。","choices":[{"label":"OLTP — 大量小而短的事务（Web/应用）","next":"bottleneck"},{"label":"分析 / 数据仓库（DWH）— 大规模扫描和聚合","next":"sol_dwh"},{"label":"混合负载，或者我不确定","next":"sol_measure"},{"label":"我知道瓶颈在哪；应该垂直扩展还是水平扩展？","next":"sol_scale"}]},"bottleneck":{"kind":"step","title":"瓶颈在哪里：RAM、I/O 还是 CPU？","detail":"在 OLTP 中，应找出真正的瓶颈，而不是盲目增加资源。查看你的指标：是频繁访问的数据放不进 RAM 导致磁盘读取量高（RAM/I/O），磁盘延迟/IOPS 不足（I/O），还是数据已经在内存中但 CPU 饱和（CPU）？扩展错误的资源会浪费金钱和时间。","choices":[{"label":"频繁访问的数据放不进 RAM；磁盘读取量高","next":"sol_ram"},{"label":"数据在内存中，但磁盘延迟/IOPS 不足（写密集）","next":"sol_io"},{"label":"数据在内存中且磁盘正常，但 CPU 持续饱和","next":"sol_cpu"}]},"sol_measure":{"kind":"solution","title":"建议：先测量，再规划规格","detail":"凭猜测购买硬件，几乎总是得到错误的规格。负载画像和瓶颈来自真实的指标。规格规划不是猜测，而是由测量驱动的迭代。","steps":["原因：是 OLTP 还是 DWH，瓶颈在 CPU、I/O 还是 RAM，只有在真实负载下测量才能明确；靠猜测是代价高昂的错误。","测量：CPU 使用率和运行队列、内存/缓存命中率（buffer cache hit）、磁盘 IOPS 和延迟、活动连接数和等待事件。","引擎工具：PostgreSQL pg_stat_* 和等待事件、Oracle AWR/ASH、MySQL Performance Schema、MSSQL DMV 和 wait stats。","方法：使用有代表性的负载进行测试，找出最紧张的资源，只扩展该资源，然后再次测量。一次只改变一种资源，以便隔离其影响。","下一步：负载画像明确后，进入相应的决策（OLTP 瓶颈、DWH 或水平/垂直扩展）。"],"related":["high-cpu","memory-pressure","slow-query"]},"sol_ram":{"kind":"solution","title":"建议：增加 RAM — 工作集必须能放入内存","detail":"OLTP 最关键的规则：频繁访问的数据和索引（工作集）必须能放入内存（buffer/cache）。如果放不下，每个查询都会访问磁盘，延迟激增，I/O 成为瓶颈。RAM 通常是回报最高的投资。","steps":["原因：从内存读取比从磁盘读取快数百到数千倍；工作集能放入 RAM 时，缓存命中率上升，磁盘 I/O 大幅下降。","适用场景：缓存命中率低，频繁出现 'disk read' 等待事件，活跃数据集大于可用 RAM。","方法：正确设置数据库内存区域的大小 — PostgreSQL shared_buffers + 操作系统页缓存、MySQL innodb_buffer_pool_size（通常占 RAM 的大部分）、Oracle SGA/buffer cache、MSSQL max server memory。","目标：理想情况下，工作集 + 索引都能放入内存；如果无法全部放下，至少要放下最常访问的“热”数据部分。","注意：不要把所有物理 RAM 都分配给 buffer pool；为操作系统、每个连接的内存和其他进程预留空间，否则交换（swap）会拖慢一切。"],"related":["memory-pressure","slow-query"]},"sol_io":{"kind":"solution","title":"建议：加强 I/O 层（IOPS/延迟）","detail":"即使数据在内存中，在写密集负载（commit、WAL/redo/binlog、checkpoint）下，磁盘速度仍然是决定性因素。这里的问题不是容量，而是 IOPS 和延迟。错误的磁盘类别会让系统在 CPU/RAM 很快的情况下依然不堪重负。","steps":["原因：为保证持久性，每次提交都会将日志写入磁盘；低 IOPS/高延迟的磁盘会直接限制事务吞吐量。","适用场景：磁盘延迟高，'log/WAL write' 等待时间长，checkpoint 期间出现瓶颈，写密集型 OLTP。","方法：使用 NVMe/SSD；将事务日志移到与数据文件分离的快速磁盘上；在云上，选择具有足够预置 IOPS/吞吐量的磁盘类别（例如 provisioned IOPS）。","目标：同时设定 IOPS 和延迟目标 — 不仅是 IOPS，p99 延迟也很重要。RAID/写缓存（BBU）可以降低写入延迟。","注意：在云上，磁盘吞吐量和 IOPS 通常还取决于实例规格；网络附加磁盘的延迟高于本地 NVMe。"],"related":["disk-full","slow-query"]},"sol_cpu":{"kind":"solution","title":"建议：解决 CPU 瓶颈（先优化查询，再加核心）","detail":"如果数据在内存中、磁盘正常而 CPU 饱和，根本原因通常是低效的查询（糟糕的执行计划、缺失的索引、大量小查询）或过高的并发。在增加核心之前先降低负载，通常更便宜也更有效。","steps":["原因：CPU 主要消耗在全表扫描、排序/哈希、执行计划编译以及过多连接带来的上下文切换上。修复这些比增加核心更便宜。","先优化：找出开销最大的查询（pg_stat_statements / AWR / Performance Schema），添加缺失的索引，减少 N+1 查询，缩小连接池。","然后扩展：如果查询已经健康而 CPU 仍然饱和，增加核心数（垂直扩展）— 尤其是对于高并发 OLTP。","引擎说明：连接越多 = 上下文切换越多；快速周转的小连接池往往胜过更多的核心（参见连接池决策）。","注意：如果按核心授权（Oracle/MSSQL），增加核心很昂贵；先进行查询优化可以降低成本。"],"related":["high-cpu","slow-query"]},"sol_dwh":{"kind":"solution","title":"建议：分析/DWH — 吞吐量、并行度和水平扩展","detail":"数据仓库负载由少量重型查询组成，这些查询扫描并聚合大表。这里的优先级不是低延迟，而是高扫描吞吐量、并行度，通常还需要可水平扩展的架构。","steps":["原因：查询扫描 TB 级数据；决定性因素是磁盘/内存带宽和并行运行的核心，而不是单个事务的延迟。","方法：高 I/O 吞吐量的存储、充足的 RAM（用于聚合/hash join）、大量核心和并行查询；在大规模下，列式存储或 MPP 架构非常高效。","水平扩展：对于大型 DWH，分布式/MPP（例如分片的分析引擎）通常比扩大单台机器扩展得更好；分区 + 分区裁剪可以缩小扫描范围。","引擎说明：不要照搬 OLTP 设置 — 对于 DWH，优先使用更大的 work_mem/排序区域、更高的并行度以及大块/预读（readahead）。","注意：不要在同一台服务器上混合 OLTP 和 DWH；重型报表会拖垮 OLTP。如有可能，将分析迁移到单独的副本/数据仓库。"],"related":["decide-shard-replica","decide-partition"]},"sol_scale":{"kind":"solution","title":"通用原则：垂直 vs 水平 + 余量 + 先测量","detail":"如何扩展与扩展哪种资源同样重要。垂直扩展（更大的机器）简单，但有上限且存在单点故障；水平扩展（更多机器）可以扩展，但会增加复杂性。无论哪种情况，都要为突发负载预留余量，并用测量结果验证决策。","steps":["垂直（scale-up）：在单台机器上增加资源（CPU/RAM/IO）— 最简单的选项，也是大多数 OLTP 的第一步；但存在物理/成本上限和单点故障。","水平（scale-out）：增加只读副本（针对读负载）或分片（针对写入/数据量）— 几乎无限的扩展能力，但会带来一致性和运维复杂性。","余量：不要按 100% 规划资源；在正常峰值负载下预留约 30% 的余量，以容纳突发峰值、维护和增长。","先测量：扩展之前，通过测量确认瓶颈，针对最紧张的资源，变更后再次测量。","注意：水平扩展并不能解决所有问题 — 分布式事务和连接代价高昂；只有在垂直扩展和优化都已用尽时才切换。"],"related":["decide-shard-replica","decide-ha"]}}},"solver/slow-write.json":{"id":"slow-write","title":"写入缓慢（INSERT/UPDATE 延迟）","icon":"🐢","category":"性能","intro":"INSERT/UPDATE/DELETE 操作比预期慢。首先确定写入在等待什么（commit/redo、锁、磁盘或每行开销），然后逐步深入到根本原因。","start":"root","nodes":{"root":{"kind":"step","title":"写入在等待什么？","detail":"通过查看等待事件对瓶颈进行分类：主要是 commit/redo（WAL）同步、锁、磁盘写入延迟，还是每行的索引/触发器开销？","command":{"oracle":"SELECT event, total_waits, ROUND(time_waited/100,1) AS sec_waited\nFROM v$system_event\nWHERE event IN ('log file sync','log file parallel write','enq: TX - row lock contention','db file sequential read')\nORDER BY time_waited DESC;","postgres":"SELECT wait_event_type, wait_event, count(*) AS sessions\nFROM pg_stat_activity\nWHERE state='active'\nGROUP BY 1,2 ORDER BY 3 DESC;","mysql":"SHOW GLOBAL STATUS LIKE 'Innodb_row_lock_waits';\nSHOW GLOBAL STATUS LIKE 'Innodb_os_log_pending_fsyncs';\nSHOW ENGINE INNODB STATUS\\G","mssql":"SELECT TOP 10 wait_type, waiting_tasks_count, wait_time_ms\nFROM sys.dm_os_wait_stats\nWHERE wait_type IN ('WRITELOG','PAGEIOLATCH_EX','LCK_M_X','LCK_M_U')\nORDER BY wait_time_ms DESC;"},"choices":[{"label":"等待 commit / redo（WAL 同步）","next":"commit"},{"label":"等待锁","next":"lock"},{"label":"怀疑是每行开销（索引/触发器）","next":"overhead"},{"label":"磁盘写入延迟高","next":"sol_disk"}]},"commit":{"kind":"step","title":"是提交模式问题还是 fsync？","detail":"每行单独提交（autocommit）会触发与行数相同次数的 redo/日志同步。反之，如果提交次数合理但每次提交都在等待 fsync，问题在于日志磁盘的延迟。","command":{"oracle":"SELECT name, value FROM v$sysstat WHERE name IN ('user commits','redo writes','redo size');\nSELECT ROUND(time_waited_micro/GREATEST(total_waits,1)/1000,2) AS ms_per_sync FROM v$system_event WHERE event='log file sync';","postgres":"SHOW synchronous_commit;\nSELECT wal_records, wal_fpi, wal_bytes, wal_buffers_full FROM pg_stat_wal; -- wal_sync/wal_sync_time PG14-17; PG18+: pg_stat_io (object = 'wal')","mysql":"SHOW VARIABLES LIKE 'innodb_flush_log_at_trx_commit';\nSHOW VARIABLES LIKE 'sync_binlog';\nSHOW GLOBAL STATUS LIKE 'Com_commit';","mssql":"SELECT io_stall_write_ms, num_of_writes, io_stall_write_ms/NULLIF(num_of_writes,0) AS ms_per_write\nFROM sys.dm_io_virtual_file_stats(DB_ID(), 2);"},"choices":[{"label":"Autocommit / 每行都提交","next":"sol_batch"},{"label":"提交在等待日志同步（fsync）","next":"sol_disk"}]},"lock":{"kind":"step","title":"谁持有锁？","detail":"如果写入在等待锁：可能是另一个会话持有同一行/索引，也可能是许多会话正试图写入同一个“热”行。","command":{"oracle":"SELECT s.sid, s.serial#, s.username, s.blocking_session, s.event, s.seconds_in_wait\nFROM v$session s WHERE s.blocking_session IS NOT NULL;","postgres":"SELECT pid, pg_blocking_pids(pid) AS blocked_by, wait_event, left(query,60) AS query\nFROM pg_stat_activity\nWHERE cardinality(pg_blocking_pids(pid)) > 0;","mysql":"SELECT requesting_engine_transaction_id AS waiter, blocking_engine_transaction_id AS blocker,\n       object_name, lock_type, lock_mode\nFROM performance_schema.data_lock_waits w\nJOIN performance_schema.data_locks l ON l.engine_lock_id = w.blocking_engine_lock_id;","mssql":"SELECT session_id, blocking_session_id, wait_type, wait_time, wait_resource\nFROM sys.dm_exec_requests\nWHERE blocking_session_id <> 0;"},"choices":[{"label":"另一个会话持有锁","next":"sol_lock"},{"label":"许多会话写入同一个热行","next":"sol_hotrow"}]},"overhead":{"kind":"step","title":"每行开销来自哪里？","detail":"每次 INSERT/UPDATE 还会更新表上的所有索引，并触发所有触发器/约束。索引过多或触发器过重都会显著拖慢写入。","command":{"oracle":"SELECT index_name, uniqueness FROM user_indexes WHERE table_name='T';\nSELECT trigger_name, triggering_event FROM user_triggers WHERE table_name='T';","postgres":"SELECT indexname, indexdef FROM pg_indexes WHERE tablename='t';\nSELECT tgname FROM pg_trigger WHERE tgrelid='t'::regclass AND NOT tgisinternal;","mysql":"SHOW INDEX FROM t;\nSHOW TRIGGERS LIKE 't';","mssql":"SELECT name, type_desc FROM sys.indexes WHERE object_id=OBJECT_ID('dbo.t');\nSELECT name, is_disabled FROM sys.triggers WHERE parent_id=OBJECT_ID('dbo.t');"},"choices":[{"label":"表上的索引过多 / 不必要","next":"sol_index"},{"label":"存在重型触发器或约束开销","next":"sol_trigger"}]},"sol_batch":{"kind":"solution","title":"根本原因：autocommit / 每行提交","detail":"每行单独提交会重复执行与行数相同次数的 redo/日志同步。通过对操作进行分组，可以大幅减少提交次数（从而减少 fsync 次数）。","steps":["在应用中关闭 autocommit，采用批量提交，每 N 行提交一次（例如 500-5000）。","使用多行 INSERT / 批量加载路径代替单行 INSERT（Postgres COPY、Oracle bulk insert、MySQL LOAD DATA、MSSQL BULK INSERT）。","使用 prepared statement 降低重复解析（硬解析）的开销。","变更后，确认 'log file sync' / WRITELOG 等待已经下降。"],"related":["active-sessions"]},"sol_disk":{"kind":"solution","title":"根本原因：redo/日志磁盘缓慢（fsync 延迟）","detail":"每次提交都要等到 redo/WAL 记录写入持久存储（fsync）。如果日志磁盘缓慢（写入延迟高、IOPS 不足），每次提交都要承受这个延迟。","steps":["将日志/redo 文件移到专用的低延迟磁盘（最好是 NVMe SSD）；与数据文件在物理上分离。","测量存储延迟（文件 I/O 统计信息）；如果每次写入的毫秒数较高，检查硬件/RAID 写缓存。","在虚拟化/云环境中，提高预置的 IOPS 和吞吐量上限。","如果持久性要求允许，可以考虑 group commit / commit delay 设置（例如 Postgres commit_delay）；但要权衡数据丢失的风险。"],"related":[]},"sol_lock":{"kind":"solution","title":"根本原因：另一个会话持有锁","detail":"写入正在等待另一个会话持有的行/索引锁。阻塞者通常是一个长时间运行的事务，或忘记提交的事务。","steps":["识别阻塞会话，检查它在做什么（是长时间的批处理，还是 idle in transaction？）。","如果阻塞者无故长时间保持打开，修复应用中的 commit/rollback 模式。","紧急情况下，以受控方式终止阻塞者（kill session / pg_terminate_backend / KILL）。","保持事务简短；不要在事务内部等待用户交互。"],"related":["blocking-sessions","current-locks"]},"sol_hotrow":{"kind":"solution","title":"根本原因：热行争用","detail":"许多会话试图写入同一行（例如计数器、序号、余额）；它们都在排队等待同一个锁，写入被串行化。","steps":["对于热点计数器模式，将值拆分为并行的桶（分片计数器），读取时求和。","将序号/ID 的生成从中心化的单行改为 sequence/identity 机制。","降低更新频率：在应用中缓冲累计值，并定期写入。","通过统一操作顺序和缩短锁持有时间来减少争用。"],"related":["current-locks"]},"sol_index":{"kind":"solution","title":"根本原因：索引开销过大","detail":"每次写入都会更新表上的每个索引。索引过多或存在未使用的索引会直接拖慢 INSERT/UPDATE。","steps":["识别未使用/重叠的索引（使用统计信息），并安全地删除它们。","简化过宽的复合索引；合并前导列重复的索引。","对于大型批量加载，考虑临时禁用索引，并在加载后重建。","变更后再次测量写入延迟，确认已有改善。"],"related":["slow-queries"]},"sol_trigger":{"kind":"solution","title":"根本原因：重型触发器 / 约束","detail":"每次写入时触发的触发器或正在校验的约束（尤其是没有索引的外键检查）会给每一行增加额外开销。","steps":["将触发器主体中的繁重工作（例如同步写日志）改为异步/基于队列。","为引用方（子表）的外键列建立索引；否则父表上的每次 DELETE/UPDATE 都会全表扫描子表。","如有可能，将行级触发器改为语句级。","将不必要的触发器逻辑，或已经演变为业务规则的逻辑，迁移到应用层。"],"related":[]}}},"solver/io-bottleneck.json":{"id":"io-bottleneck","title":"IO 等待高 / 磁盘延迟高","icon":"💽","category":"性能","intro":"系统正在等待磁盘 IO。首先区分负载是读密集还是写密集，然后深入到原因：buffer cache 不足、全表扫描、checkpoint 风暴、IOPS 上限或溢出到临时空间。","start":"root","nodes":{"root":{"kind":"step","title":"IO 是读密集还是写密集？","detail":"使用文件 IO 统计信息确定等待发生在读取端（从磁盘读取数据）还是写入端（redo/checkpoint/数据写入）。这会将决策树一分为二。","command":{"oracle":"SELECT function_name, small_read_reqs+large_read_reqs AS reads, small_write_reqs+large_write_reqs AS writes,\n       small_read_megabytes+large_read_megabytes+small_write_megabytes+large_write_megabytes AS mb\nFROM v$iostat_function ORDER BY mb DESC;","postgres":"SELECT sum(blks_read) AS reads, sum(blks_hit) AS hits FROM pg_stat_database;\nSELECT buffers_checkpoint, buffers_clean, buffers_backend FROM pg_stat_bgwriter;","mysql":"SHOW GLOBAL STATUS LIKE 'Innodb_data_read';\nSHOW GLOBAL STATUS LIKE 'Innodb_data_written';\nSHOW GLOBAL STATUS LIKE 'Innodb_data_reads';","mssql":"SELECT DB_NAME(database_id) AS db, io_stall_read_ms, io_stall_write_ms, num_of_reads, num_of_writes\nFROM sys.dm_io_virtual_file_stats(NULL, NULL)\nORDER BY io_stall_read_ms + io_stall_write_ms DESC;"},"choices":[{"label":"主要是读 IO","next":"read"},{"label":"主要是写 IO","next":"write"}]},"read":{"kind":"step","title":"读 IO 为什么高？","detail":"过多的物理读主要有三个原因：buffer cache 太小（工作集放不进 RAM）、查询执行全表扫描，或大型排序/哈希操作溢出到临时空间。","command":{"oracle":"SELECT ROUND((1-(pr.value/(dbg.value+cg.value)))*100,2) AS buffer_hit_pct\nFROM v$sysstat pr, v$sysstat dbg, v$sysstat cg\nWHERE pr.name='physical reads' AND dbg.name='db block gets' AND cg.name='consistent gets';\nSELECT sql_id, disk_reads, executions FROM v$sql ORDER BY disk_reads DESC FETCH FIRST 10 ROWS ONLY;","postgres":"SELECT ROUND(sum(blks_hit)*100.0/nullif(sum(blks_hit)+sum(blks_read),0),2) AS cache_hit_pct FROM pg_stat_database;\nSELECT query, shared_blks_read, temp_blks_read FROM pg_stat_statements ORDER BY shared_blks_read DESC LIMIT 10;","mysql":"SHOW GLOBAL STATUS LIKE 'Innodb_buffer_pool_read_requests';\nSHOW GLOBAL STATUS LIKE 'Innodb_buffer_pool_reads';\nSHOW GLOBAL STATUS LIKE 'Created_tmp_disk_tables';","mssql":"SELECT CAST(100.0 * a.cntr_value / NULLIF(b.cntr_value, 0) AS decimal(5,2)) AS buffer_cache_hit_ratio\nFROM sys.dm_os_performance_counters a\nJOIN sys.dm_os_performance_counters b ON b.object_name = a.object_name AND b.counter_name = 'Buffer cache hit ratio base'\nWHERE a.counter_name = 'Buffer cache hit ratio' AND a.object_name LIKE '%Buffer Manager%';"},"choices":[{"label":"buffer cache 命中率低，工作集放不下","next":"sol_cache"},{"label":"查询执行全表扫描","next":"sol_scan"},{"label":"大型排序/哈希溢出到临时空间","next":"sol_spill"}]},"write":{"kind":"step","title":"写 IO 为什么高？","detail":"写入端的瓶颈要么来自周期性的 checkpoint/刷写短时间内使磁盘饱和（checkpoint 风暴），要么来自存储缺乏维持负载所需的 IOPS/吞吐量。","command":{"oracle":"SELECT event, total_waits, ROUND(time_waited/100,1) AS sec\nFROM v$system_event\nWHERE event IN ('log file parallel write','db file parallel write','checkpoint completed')\nORDER BY time_waited DESC;","postgres":"-- PG17+:\nSELECT num_timed, num_requested, buffers_written FROM pg_stat_checkpointer;\n-- PG16 and earlier:\nSELECT checkpoints_timed, checkpoints_req, buffers_checkpoint, maxwritten_clean\nFROM pg_stat_bgwriter;\nSHOW checkpoint_completion_target;","mysql":"SHOW GLOBAL STATUS LIKE 'Innodb_buffer_pool_pages_dirty';\nSHOW GLOBAL STATUS LIKE 'Innodb_os_log_written';\nSHOW ENGINE INNODB STATUS\\G","mssql":"SELECT cntr_value FROM sys.dm_os_performance_counters WHERE counter_name='Checkpoint pages/sec';\nSELECT cntr_value FROM sys.dm_os_performance_counters WHERE counter_name='Page writes/sec';"},"choices":[{"label":"checkpoint/刷脏集中爆发（checkpoint 风暴）","next":"sol_checkpoint"},{"label":"存储跟不上持续负载（IOPS 上限）","next":"sol_iops"}]},"sol_cache":{"kind":"solution","title":"根本原因：buffer cache 不足","detail":"频繁访问的数据集（工作集）放不进 buffer cache，因此数据块不断被从磁盘重新读取。命中率低，物理读高。","steps":["增大 buffer/cache 大小：Oracle db_cache_size/SGA、Postgres shared_buffers、MySQL innodb_buffer_pool_size、MSSQL max server memory。","比较变更前后的命中率和物理读。","如果 RAM 在物理上不足，增加服务器内存，或通过归档/分区缩小工作集。","如果真正的瓶颈来自某一种扫描模式，转到缓存命中率决策树的扫描分支。"],"related":["cache-hit-ratio"]},"sol_scan":{"kind":"solution","title":"根本原因：全表扫描","detail":"由于没有合适的索引，查询从头到尾扫描大表；这既导致大量物理读，又用无用的数据块污染缓存。","steps":["获取物理读最多的查询的执行计划（EXPLAIN），找出全表扫描。","在过滤列和连接列上添加合适的索引；验证执行计划已切换为索引访问。","对于非常大的表，通过基于日期/范围的分区减少扫描的数据量。","只选择需要的列，而不是不必要的 'SELECT *'，以减少读取的数据块数量。"],"related":["slow-queries"]},"sol_spill":{"kind":"solution","title":"根本原因：排序/哈希溢出到临时空间","detail":"大型排序、hash join 或 group by 操作放不进工作内存，写入临时空间（temp/tempdb），产生大量读写 IO。","steps":["提高工作内存设置：Oracle pga_aggregate_target、Postgres work_mem、MySQL sort_buffer_size/join_buffer_size、MSSQL memory grant。","减少操作数据量：尽早应用过滤条件，去掉不必要的排序，改进连接顺序。","为常用的排序/分组添加合适的索引，防止溢出到磁盘。","将临时空间移到快速磁盘；如果问题持续，使用 undo/temp 压力决策树进一步深入排查。"],"related":["slow-queries"]},"sol_checkpoint":{"kind":"solution","title":"根本原因：checkpoint 风暴","detail":"脏页长时间累积，然后在短时间内批量写入磁盘；这些突发的写入浪潮会周期性地使 IO 饱和。","steps":["将 checkpoint 分散到更长时间：在 Postgres 中提高 checkpoint_completion_target，并平衡 checkpoint_timeout。","在 MySQL 中，根据存储设置 innodb_io_capacity / innodb_io_capacity_max，并调优自适应刷写（adaptive flushing）。","在 MSSQL 中，通过间接 checkpoint（target_recovery_time）平滑突发浪潮。","在 Oracle 中，通过 fast_start_mttr_target 平衡 checkpoint 频率，并增大 redo log 文件。"],"related":[]},"sol_iops":{"kind":"solution","title":"根本原因：IOPS / 吞吐量不足","detail":"存储缺乏满足负载所需读写请求的 IOPS 或带宽；队列深度和延迟持续偏高。","steps":["测量磁盘延迟（每个请求的毫秒数），并与硬件/云的 IOPS 上限进行比较。","迁移到更快的存储（HDD→SSD/NVMe），或在云上提高预置的 IOPS/吞吐量等级。","将数据、redo/日志和临时空间分布到不同磁盘上，以减少争用。","作为长期解决方案，降低总 IO 需求（索引、归档、查询优化）。"],"related":[]}}},"solver/undo-temp-pressure.json":{"id":"undo-temp-pressure","title":"Undo / TempDB / 临时空间压力","icon":"🧯","category":"容量","intro":"Undo/回滚空间、临时（temp）空间或 TempDB 承受压力。我们确定压力所在，并深入到根本原因，例如大型排序/哈希、长查询/长事务或空间泄漏。","start":"root","nodes":{"root":{"kind":"step","title":"压力在哪里：undo、temp，还是 tempdb 争用？","detail":"通过查看空间使用情况对压力来源进行分类：是 undo/回滚表空间正在被填满，通用临时（temp）空间被填满，还是在 MSSQL 中出现了 TempDB 页分配 latch 争用？","command":{"oracle":"SELECT tablespace_name, ROUND(used_percent,1) AS used_pct\nFROM dba_tablespace_usage_metrics\nWHERE tablespace_name LIKE '%UNDO%' OR tablespace_name LIKE 'TEMP%';","postgres":"SELECT datname, temp_files, pg_size_pretty(temp_bytes) AS temp_written\nFROM pg_stat_database ORDER BY temp_bytes DESC;","mysql":"SELECT * FROM information_schema.INNODB_TEMP_TABLE_INFO;\nSHOW GLOBAL STATUS LIKE 'Created_tmp_disk_tables';","mssql":"SELECT SUM(user_object_reserved_page_count)*8/1024 AS user_obj_mb,\n       SUM(internal_object_reserved_page_count)*8/1024 AS internal_obj_mb,\n       SUM(version_store_reserved_page_count)*8/1024 AS version_store_mb\nFROM sys.dm_db_file_space_usage;"},"choices":[{"label":"Undo/回滚空间正在被填满（ORA-01555 / ORA-30036）","next":"undo"},{"label":"临时（temp）空间正在被填满（排序/哈希溢出）","next":"temp"},{"label":"TempDB 分配 latch 争用（PFS/GAM/SGAM）","next":"tempdb"}]},"undo":{"kind":"step","title":"是什么在消耗 undo？","detail":"Undo 压力通常分为两类：长查询需要旧的 undo 来实现其开始时刻的一致性读（ORA-01555），或者长/未结束的事务产生大量 undo，填满空间并导致 ORA-30036。","command":{"oracle":"SELECT s.sid, s.username, t.used_ublk*8/1024 AS undo_mb, s.status, t.start_time\nFROM v$transaction t JOIN v$session s ON s.taddr=t.addr\nORDER BY undo_mb DESC;\nSELECT MAX(maxquerylen) AS longest_query_sec, MAX(tuned_undoretention) AS tuned_retention FROM v$undostat;","postgres":"SELECT pid, now()-xact_start AS xact_age, state, left(query,60) AS query\nFROM pg_stat_activity WHERE xact_start IS NOT NULL ORDER BY xact_start LIMIT 10;"},"choices":[{"label":"长查询需要旧的 undo（ORA-01555）","next":"sol_undo_query"},{"label":"长/未结束的事务正在累积 undo（ORA-30036）","next":"sol_undo_tx"}]},"temp":{"kind":"step","title":"是什么在填满临时空间？","detail":"Temp 压力要么来自大型排序/哈希操作溢出到磁盘（正常但规模巨大），要么来自单个会话持续占用 temp（未关闭的游标、泄漏、永不结束的查询）。","command":{"oracle":"SELECT s.sid, s.username, u.tablespace, u.blocks*8/1024 AS temp_mb, u.sqladdr\nFROM v$tempseg_usage u JOIN v$session s ON s.saddr=u.session_addr\nORDER BY temp_mb DESC;","postgres":"SELECT query, temp_blks_written, temp_blks_read\nFROM pg_stat_statements ORDER BY temp_blks_written DESC LIMIT 10;","mysql":"SELECT event_name, current_number_of_bytes_used\nFROM performance_schema.memory_summary_global_by_event_name\nWHERE event_name LIKE '%temptable%' OR event_name LIKE '%sort%';","mssql":"SELECT session_id, request_id, SUM(internal_objects_alloc_page_count)*8/1024 AS internal_mb\nFROM sys.dm_db_task_space_usage GROUP BY session_id, request_id ORDER BY internal_mb DESC;"},"choices":[{"label":"大型排序/哈希操作正在溢出到磁盘","next":"sol_temp_sort"},{"label":"单个会话占用 temp（泄漏/未关闭的游标）","next":"sol_temp_leak"}]},"tempdb":{"kind":"step","title":"TempDB 争用是分配问题还是容量问题？","detail":"MSSQL TempDB 中会出现两种不同的问题：PFS/GAM/SGAM 页上的 PAGELATCH 争用（大量小型临时对象），或大型排序/version store 在容量上填满 TempDB。","command":{"mssql":"SELECT session_id, wait_type, wait_duration_ms, resource_description\nFROM sys.dm_os_waiting_tasks\nWHERE wait_type LIKE 'PAGELATCH%' AND resource_description LIKE '2:%';\nSELECT COUNT(*) AS tempdb_data_files FROM sys.master_files WHERE database_id=2 AND type=0;"},"choices":[{"label":"PFS/GAM/SGAM latch 争用","next":"sol_tempdb_latch"},{"label":"大型排序/version store 正在填满 TempDB","next":"sol_temp_sort"}]},"sol_undo_query":{"kind":"solution","title":"根本原因：长查询 + undo 不足（ORA-01555）","detail":"长查询进行一致性读所需的旧 undo，因保留期已过或空间不足而被覆盖。","steps":["将 undo_retention 提高到超过最长查询时长（v$undostat.maxquerylen）的值。","扩大 UNDO 表空间或启用 autoextend，使保留期有足够的空间支撑。","对于关键报表，使用 ALTER TABLESPACE undotbs RETENTION GUARANTEE 保护旧的 undo（空间填满时 DML 可能失败；需谨慎）。","要永久解决，优化长查询或将其移到备库/副本上运行。"],"related":["slow-queries"]},"sol_undo_tx":{"kind":"solution","title":"根本原因：长/未结束的事务填满了 undo（ORA-30036）","detail":"单个巨型 DML 或延迟提交的未结束事务产生了过多的 undo；UNDO 表空间无法扩展，引发 ORA-30036。","steps":["识别占用 undo 最多的活动事务（v$transaction.used_ublk），并找到其所有者。","将单个巨型 DML 按日期/范围分块执行，并在中间提交。","在应用端修复忘记提交 / idle-in-transaction 的会话；必要时以受控方式终止它们。","临时为 UNDO 表空间增加空间；长期而言，根据负载规划保留期和空间大小。"],"related":["active-sessions"]},"sol_temp_sort":{"kind":"solution","title":"根本原因：大型排序/哈希正在填满临时空间","detail":"放不进内存的大型排序/哈希/group by 操作溢出到临时空间，并可能将其填满。","steps":["增加工作内存：Oracle pga_aggregate_target、Postgres work_mem、MySQL sort_buffer_size、MSSQL memory grant。","改进查询：去掉不必要的排序，尽早应用过滤条件，在排序/分组列上添加索引。","将超大作业拆分为多块；限制并发，避免它们同时占用 temp。","扩大临时空间并将其移到快速磁盘；根据核心数为 MSSQL TempDB 配置多个文件。"],"related":["slow-queries"]},"sol_temp_leak":{"kind":"solution","title":"根本原因：会话占用临时空间（泄漏）","detail":"由于未关闭的游标、永不结束的查询或打开的临时表，单个会话不释放临时空间。","steps":["识别占用 temp 最多的会话及其正在运行的 SQL。","确保应用中的游标/结果集和临时表被正确关闭。","以受控方式终止卡住/运行时间过长的会话，并确认空间已归还。","定义全局临时表，使其在会话/事务结束时自动清理。"],"related":["active-sessions"]},"sol_tempdb_latch":{"kind":"solution","title":"根本原因：TempDB PFS/GAM/SGAM latch 争用","detail":"当许多会话同时创建小型临时对象时，TempDB 的分配映射页（PFS/GAM/SGAM）上会出现 PAGELATCH 争用。","steps":["按 CPU 核心数比例配置 TempDB 数据文件数量，并保持大小相同（例如 4-8 个文件，所有文件的大小/autogrowth 相同）。","在新版 SQL Server 上，启用可减少分配争用的选项（memory-optimized tempdb metadata）。","减少临时表的创建：用表变量 / 单个查询替换不必要的 #temp 表。","变更后，确认 PAGELATCH 等待时间已下降。"],"related":["current-locks"]}}},"solver/long-transaction.json":{"id":"long-transaction","title":"长时间运行 / 未结束的事务","icon":"⏱️","category":"锁","intro":"长时间运行或未结束的事务正在累积锁、undo 和膨胀（bloat）。我们找出问题事务，确定其类型（idle in transaction、长批处理、影响），并安全地解决它。","start":"root","nodes":{"root":{"kind":"step","title":"最早的未结束事务是哪一个？","detail":"按开始时间列出未结束的事务。最早的那个通常就是问题的根源。检查其状态：是正在活跃运行，还是处于等待提交的 'idle in transaction' 状态？","command":{"oracle":"SELECT s.sid, s.serial#, s.username, s.status, t.start_time, t.used_ublk*8/1024 AS undo_mb\nFROM v$transaction t JOIN v$session s ON s.taddr=t.addr\nORDER BY t.start_time;","postgres":"SELECT pid, state, now()-xact_start AS xact_age, now()-state_change AS since_change, left(query,60) AS query\nFROM pg_stat_activity\nWHERE xact_start IS NOT NULL ORDER BY xact_start LIMIT 20;","mysql":"SELECT trx_id, trx_state, trx_started, TIMESTAMPDIFF(SECOND,trx_started,NOW()) AS secs, trx_mysql_thread_id, trx_rows_locked\nFROM information_schema.INNODB_TRX ORDER BY trx_started;","mssql":"SELECT s.session_id, s.login_name, t.transaction_begin_time,\n       DATEDIFF(second,t.transaction_begin_time,GETDATE()) AS secs\nFROM sys.dm_tran_active_transactions t\nJOIN sys.dm_tran_session_transactions st ON st.transaction_id=t.transaction_id\nJOIN sys.dm_exec_sessions s ON s.session_id=st.session_id\nORDER BY t.transaction_begin_time;"},"choices":[{"label":"idle in transaction（忘记提交，处于空闲）","next":"idle"},{"label":"活跃但运行时间极长的批处理/DML","next":"batch"},{"label":"事务持续期间正在累积锁/膨胀/undo","next":"impact"}]},"idle":{"kind":"step","title":"为什么会出现 idle in transaction？","detail":"会话打开了事务，但处于空闲状态，没有运行查询；也就是说，忘记了 commit/rollback。这会不必要地占用锁和 undo。是修复应用就足够，还是需要紧急终止它？","command":{"postgres":"SELECT pid, now()-state_change AS idle_age, left(query,80) AS last_query\nFROM pg_stat_activity WHERE state='idle in transaction' ORDER BY state_change;","mysql":"SELECT trx.trx_mysql_thread_id, trx.trx_started, p.time AS idle_secs, p.command\nFROM information_schema.INNODB_TRX trx\nJOIN information_schema.PROCESSLIST p ON p.id=trx.trx_mysql_thread_id\nWHERE p.command='Sleep' ORDER BY trx.trx_started;","oracle":"SELECT sid, serial#, status, last_call_et AS idle_secs\nFROM v$session WHERE status='INACTIVE' AND type='USER' AND last_call_et>300;","mssql":"SELECT s.session_id, s.status, DATEDIFF(second,s.last_request_end_time,GETDATE()) AS idle_secs\nFROM sys.dm_exec_sessions s\nWHERE s.session_id IN (SELECT session_id FROM sys.dm_tran_session_transactions)\n  AND s.status='sleeping';"},"choices":[{"label":"应用/连接池没有提交（永久修复）","next":"sol_idle"},{"label":"我需要立即终止它","next":"sol_kill"}]},"batch":{"kind":"step","title":"活跃的长事务在做什么？","detail":"该会话确实在运行（active）。它是单个巨型 DML/报表，还是一个正在让其他会话等待、必须紧急切断的阻塞者？","command":{"oracle":"SELECT s.sid, s.serial#, sq.sql_text, s.last_call_et AS running_secs\nFROM v$session s JOIN v$sql sq ON sq.sql_id=s.sql_id\nWHERE s.status='ACTIVE' AND s.type='USER' ORDER BY s.last_call_et DESC;","postgres":"SELECT pid, now()-query_start AS run_time, left(query,80) AS query\nFROM pg_stat_activity WHERE state='active' ORDER BY query_start LIMIT 10;","mysql":"SELECT id, time AS run_secs, state, LEFT(info,80) AS query\nFROM information_schema.PROCESSLIST WHERE command<>'Sleep' ORDER BY time DESC;","mssql":"SELECT r.session_id, r.status, r.total_elapsed_time/1000 AS secs, SUBSTRING(t.text,1,80) AS query\nFROM sys.dm_exec_requests r CROSS APPLY sys.dm_exec_sql_text(r.sql_handle) t\nORDER BY r.total_elapsed_time DESC;"},"choices":[{"label":"单个巨型 DML/报表；需要拆分","next":"sol_batch"},{"label":"它正在让其他会话等待；紧急终止","next":"sol_kill"}]},"impact":{"kind":"step","title":"长事务正在累积什么？","detail":"确定长事务的副作用：主要是导致其他会话等待的锁积压，还是因持有旧的行版本而阻碍清理（VACUUM/undo）所造成的膨胀？","command":{"postgres":"SELECT pid, pg_blocking_pids(pid) AS blocked_by FROM pg_stat_activity WHERE cardinality(pg_blocking_pids(pid))>0;\nSELECT max(age(backend_xmin)) AS max_xmin_age FROM pg_stat_activity WHERE backend_xmin IS NOT NULL;","oracle":"SELECT blocking_session, sid, serial#, seconds_in_wait, event\nFROM v$session WHERE blocking_session IS NOT NULL;","mssql":"SELECT session_id, blocking_session_id, wait_type, wait_time\nFROM sys.dm_exec_requests WHERE blocking_session_id<>0;"},"choices":[{"label":"锁积压正在让其他会话等待","next":"sol_lock_backlog"},{"label":"旧版本堆积正在阻碍清理（undo/膨胀）","next":"sol_bloat"}]},"sol_idle":{"kind":"solution","title":"根本原因：应用没有提交（idle in transaction）","detail":"连接池或应用打开事务后，不提交/回滚就让连接保持空闲。这会不必要地占用锁和 undo。","steps":["在应用代码中，确保每个打开事务的路径都有 commit/rollback；不要在事务内部等待用户输入或长时间 IO。","自动切断空闲事务：Postgres idle_in_transaction_session_timeout、MySQL 连接/超时设置、应用连接池的 maxLifetime。","按照“开启 autocommit，短事务”的原则配置连接池。","建立监控：针对长时间 idle-in-transaction 的会话发出告警。"],"related":["active-sessions","blocking-sessions"]},"sol_kill":{"kind":"solution","title":"解决方案：安全地终止问题会话","detail":"如果一个阻塞/长时间运行的会话正在让其他会话等待，必须以受控方式终止它。终止会触发回滚，可能需要空间/时间；需谨慎。","steps":["首先确认该会话是什么（哪个用户/应用、哪个作业），以免切断关键作业。","终止：Oracle ALTER SYSTEM KILL SESSION 'sid,serial#' IMMEDIATE; Postgres SELECT pg_terminate_backend(pid); MySQL KILL thread_id; MSSQL KILL session_id;","如果终止的是大型写事务，等待回滚（undo/日志回退）完成。","终止后，确认锁和等待会话已经清除；永久修复根本原因（空闲/批处理）。"],"related":["blocking-sessions"]},"sol_batch":{"kind":"solution","title":"根本原因：单个巨型 DML/报表","detail":"在单个事务中处理数百万行的 DML，或运行时间极长的报表，会在整个持续期间累积锁和 undo/膨胀。","steps":["按日期/范围/键将大型 DML 拆分为多块，每块之后提交。","将重型报表移到非高峰时段，或卸载到备库/只读副本上运行。","优化长查询，缩短其总时长（从而缩短其占用锁/undo 的时间）。","使批处理作业可观测：记录进度，并使其能够从中断处继续执行。"],"related":["slow-queries"]},"sol_lock_backlog":{"kind":"solution","title":"根本原因：锁积压队列","detail":"由于持有的锁，长事务在其身后形成了等待队列；许多会话正在等待同一资源。","steps":["识别锁链头部的会话（head blocker）；它是唯一能清空队列的节点。","缩短 head blocker（commit/rollback）或以受控方式终止它；观察队列消化情况。","为缩短锁持有时间，缩小事务规模，并定义一致的访问顺序。","在适当情况下，改用需要较弱锁的方式（行级锁、合适的隔离级别）。"],"related":["blocking-sessions","current-locks"]},"sol_bloat":{"kind":"solution","title":"根本原因：旧版本堆积（undo/膨胀）","detail":"长时间运行的事务阻止了对其之前的旧行版本的清理。在 Postgres 中表现为表/索引膨胀，在 Oracle 中表现为 undo 压力。","steps":["找到持有最早 xmin/事务的会话（Postgres backend_xmin，Oracle 中最早的活动事务）。","缩短/结束该事务，使清理（VACUUM / undo 回收）能够重新推进。","在 Postgres 中，事务结束后对膨胀的表执行 VACUUM（必要时使用 VACUUM FULL/pg_repack）。","长期采用保持事务简短的应用模式，并让 autovacuum 更加积极。"],"related":[]}}},"solver/plan-regression.json":{"id":"plan-regression","title":"查询执行计划退化（突然变慢）","icon":"📉","category":"性能","intro":"昨天还很快的查询今天突然变慢。通常是优化器切换到了更差的执行计划。我们从过时的统计信息、参数敏感性、数据量增长或执行计划突变中找出根本原因，并稳定执行计划。","start":"root","nodes":{"root":{"kind":"step","title":"执行计划真的变了吗？何时变的？","detail":"同一查询是否存在多个执行计划（plan_hash/plan_generation），变慢是从何时开始的？是否与最近的部署、升级、参数变更或大规模数据加载同时发生？","command":{"oracle":"SELECT sql_id, plan_hash_value, executions, ROUND(elapsed_time/GREATEST(executions,1)/1e6,3) AS sec_per_exec, first_load_time\nFROM v$sql WHERE sql_id='&sqlid' ORDER BY first_load_time;","postgres":"EXPLAIN (ANALYZE, BUFFERS) SELECT /* your query */ 1;\n-- Trend: SELECT query, calls, mean_exec_time FROM pg_stat_statements WHERE query LIKE '%...%';","mysql":"EXPLAIN FORMAT=JSON SELECT /* your query */ 1;\nSELECT DIGEST_TEXT, COUNT_STAR, AVG_TIMER_WAIT/1e9 AS avg_ms\nFROM performance_schema.events_statements_summary_by_digest WHERE DIGEST_TEXT LIKE '%...%';","mssql":"SELECT qs.plan_generation_num, qs.execution_count, qs.total_worker_time/qs.execution_count AS avg_cpu_us, qs.creation_time\nFROM sys.dm_exec_query_stats qs CROSS APPLY sys.dm_exec_sql_text(qs.sql_handle) t\nWHERE t.text LIKE '%...%';"},"choices":[{"label":"统计信息可能已过时","next":"stats"},{"label":"同一查询因参数不同而表现不同（sniffing/peeking）","next":"sniff"},{"label":"数据量显著增长","next":"sol_datagrowth"},{"label":"升级/flush 之后执行计划突然改变","next":"sol_planflip"}]},"stats":{"kind":"step","title":"统计信息是最新的吗？","detail":"优化器依据统计信息做出决策。如果最近一次 analyze 的日期较早或修改计数较高，估算可能确实已经偏离，从而导致糟糕的执行计划。","command":{"oracle":"SELECT table_name, num_rows, last_analyzed, stale_stats\nFROM dba_tab_statistics WHERE table_name='T';","postgres":"SELECT relname, n_live_tup, n_mod_since_analyze, last_analyze, last_autoanalyze\nFROM pg_stat_user_tables WHERE relname='t';","mysql":"SELECT table_name, update_time, table_rows FROM information_schema.tables WHERE table_name='t';\nSHOW INDEX FROM t;","mssql":"SELECT s.name, STATS_DATE(s.object_id,s.stats_id) AS last_updated, p.modification_counter, p.rows\nFROM sys.stats s CROSS APPLY sys.dm_db_stats_properties(s.object_id,s.stats_id) p\nWHERE s.object_id=OBJECT_ID('dbo.t');"},"choices":[{"label":"统计信息过时/缺失","next":"sol_stats"},{"label":"统计信息是最新的，但执行计划仍对参数敏感","next":"sniff"}]},"sniff":{"kind":"step","title":"是否存在参数敏感性？","detail":"查询根据首次执行时的参数值选择并缓存执行计划（MSSQL parameter sniffing / Oracle bind peeking）。对于分布倾斜的其他值，该执行计划可能很糟糕。","command":{"oracle":"SELECT sql_id, child_number, plan_hash_value, is_bind_sensitive, is_bind_aware\nFROM v$sql WHERE sql_id='&sqlid';","mssql":"SELECT qs.query_hash, qs.execution_count, qs.total_worker_time/qs.execution_count AS avg_cpu_us\nFROM sys.dm_exec_query_stats qs CROSS APPLY sys.dm_exec_sql_text(qs.sql_handle) t\nWHERE t.text LIKE '%...%';","postgres":"-- For generic vs custom plan behavior:\nPREPARE p AS SELECT /* your query */ 1;\nEXPLAIN EXECUTE p;"},"choices":[{"label":"是的，执行计划随参数在好坏之间切换","next":"sol_sniff"},{"label":"已知正确的执行计划，我想永久固定它","next":"sol_forceplan"}]},"sol_stats":{"kind":"solution","title":"根本原因：统计信息过时 / 缺失","detail":"基于与实际不符的旧统计信息，优化器错误估算了行数，选择了糟糕的执行计划。这是执行计划退化最常见的原因。","steps":["为受影响的表和索引收集最新的统计信息：Oracle DBMS_STATS.GATHER_TABLE_STATS、Postgres ANALYZE、MySQL ANALYZE TABLE、MSSQL UPDATE STATISTICS ... WITH FULLSCAN。","在分布倾斜的列上创建直方图/扩展统计信息。","收集统计信息后，用 EXPLAIN 验证执行计划，确认已恢复为良好的执行计划。","为防止再次发生，保持自动统计信息收集（autovacuum/auto update stats）启用，并设置合适的阈值。"],"related":["slow-queries"]},"sol_sniff":{"kind":"solution","title":"根本原因：参数敏感性（sniffing / bind peeking）","detail":"缓存的执行计划是针对首个到达的参数值选择的，对于分布倾斜的其他值来说很糟糕。目标是确保选择与参数值相适应的执行计划。","steps":["在 MSSQL 中，对关键查询使用 OPTION (RECOMPILE) 或 OPTIMIZE FOR 定义针对性行为；对于频繁变化的值，优先使用 recompile。","在 Oracle 中，使用 adaptive cursor sharing / bind-aware 行为；必要时按值范围拆分查询。","在 Postgres 中，观察 generic 与 custom plan 的选择；必要时用 plan_cache_mode 加以引导。","如果需要单一、永久且准确的执行计划，转到固定执行计划的解决方案。"],"related":["slow-queries"]},"sol_forceplan":{"kind":"solution","title":"解决方案：固定正确的执行计划（baseline / force plan）","detail":"如果存在已知良好的执行计划，将其固定以使优化器不再偏离，可以永久阻止退化。","steps":["Oracle：为良好的执行计划创建 SQL Plan Baseline（DBMS_SPM），或通过 SQL Plan Management 接受它。","MSSQL：在 Query Store 中选择良好的执行计划，并用 sp_query_store_force_plan 强制使用。","MySQL/Postgres：用优化器 hint（例如 index hint）或 pg_hint_plan 引导执行计划。","固定之后监控性能；如果数据模式发生根本变化，重新评估 baseline。"],"related":[]},"sol_datagrowth":{"kind":"solution","title":"根本原因：数据量增长，执行计划不再适用","detail":"随着表的增长，过去理想的执行计划（例如 nested loop）如今扩展性很差；优化器需要能够正确反映增长的统计信息和访问路径。","steps":["收集最新的统计信息，使优化器看到新的行数。","为增长的表添加合适的索引；修复如今扩展性差的访问路径（例如全表扫描/nested loop）。","在非常大的表上，通过基于日期/范围的分区限制扫描的数据量。","重写查询以适应增长（尽早过滤、分页、汇总/物化视图）。"],"related":["slow-queries"]},"sol_planflip":{"kind":"solution","title":"根本原因：执行计划突变（升级 / 缓存清空）","detail":"在版本升级、参数变更或执行计划缓存被清空之后，执行计划突然改变。优化器行为或环境条件发生了变化。","steps":["确定变化的诱因：升级/补丁、优化器参数、数据加载，还是重启/缓存清空。","如果原因是某个参数，撤销该变更，并以受控方式重新评估。","固定已知良好的执行计划（baseline / force plan），使优化器不再偏离。","对于升级后的退化，检查优化器的兼容性/就绪设置。"],"related":[]}}},"solver/replica-resync.json":{"id":"replica-resync","title":"副本损坏 / 需要重新同步","icon":"🔗","category":"复制","intro":"副本/备库与主库之间的同步已中断。确定复制是否已停止、落后多少，以及位点/GTID 链是否断裂；然后在跳过错误、等待或重建（reseed）中选择正确的路径。包含脑裂警告。","start":"root","nodes":{"root":{"kind":"step","title":"复制的总体状态如何？","detail":"先读取复制健康状况：应用线程是否在运行，延迟有多大，是否有错误？同时确认两个节点没有都在接受写入（脑裂）。","command":{"mysql":"SHOW REPLICA STATUS\\G\n-- Check: Replica_IO_Running, Replica_SQL_Running, Seconds_Behind_Source, Last_Error, Retrieved/Executed_Gtid_Set","postgres":"-- On the primary:\nSELECT client_addr, state, sent_lsn, replay_lsn, pg_wal_lsn_diff(sent_lsn,replay_lsn) AS lag_bytes FROM pg_stat_replication;\n-- On the replica:\nSELECT status, last_msg_receipt_time, latest_end_lsn FROM pg_stat_wal_receiver;","oracle":"SELECT name, value, unit, time_computed FROM v$dataguard_stats WHERE name IN ('transport lag','apply lag');\nSELECT dest_id, status, error, gap_status FROM v$archive_dest_status WHERE status<>'INACTIVE';","mssql":"SELECT ag.name AS ag, drs.synchronization_state_desc, drs.synchronization_health_desc,\n       drs.log_send_queue_size, drs.redo_queue_size\nFROM sys.dm_hadr_database_replica_states drs\nJOIN sys.availability_groups ag ON ag.group_id=drs.group_id;"},"choices":[{"label":"复制已停止 / 正在失败","next":"error"},{"label":"正在运行但落后很多（延迟大）","next":"lag"},{"label":"GTID/位点断裂、不一致 — 重建","next":"sol_reseed"},{"label":"两个节点都作为主库在写入（疑似脑裂）","next":"sol_splitbrain"}]},"error":{"kind":"step","title":"错误能修复，还是需要重建？","detail":"检查应用错误。有些错误（重复键、缺失行）可以通过安全地跳过单个事件来解决；但如果存在数据不一致/损坏，唯一正确的路径是重建副本。","command":{"mysql":"SHOW REPLICA STATUS\\G\n-- Check: Last_SQL_Errno, Last_SQL_Error, Last_IO_Errno, Last_IO_Error","postgres":"-- Examine the replica logs (PostgreSQL log): recovery / WAL apply error messages.\nSELECT status, last_msg_receipt_time FROM pg_stat_wal_receiver;","mssql":"SELECT * FROM sys.dm_hadr_automatic_seeding;\nSELECT database_id, synchronization_state_desc, is_suspended, suspend_reason_desc\nFROM sys.dm_hadr_database_replica_states WHERE is_local=1;"},"choices":[{"label":"单个/少量应用错误（可跳过）","next":"sol_skiperror"},{"label":"无法修复的不一致 / 损坏","next":"sol_reseed"}]},"lag":{"kind":"step","title":"延迟是暂时的还是持续的？","detail":"观察延迟随时间的趋势。它是在暂时的负载峰值之后逐渐缩小（追上），还是持续增长，导致副本永远追不上主库？","command":{"mysql":"SHOW REPLICA STATUS\\G\n-- Measure Seconds_Behind_Source several times: is it decreasing or increasing?","postgres":"-- Measure repeatedly on the replica:\nSELECT now()-pg_last_xact_replay_timestamp() AS replay_lag;","oracle":"SELECT name, value, time_computed FROM v$dataguard_stats WHERE name='apply lag';","mssql":"SELECT drs.redo_queue_size, drs.redo_rate, drs.log_send_queue_size\nFROM sys.dm_hadr_database_replica_states drs WHERE drs.is_local=0;"},"choices":[{"label":"暂时的负载峰值；延迟正在缩小","next":"sol_lag_wait"},{"label":"持续增长；无法追上","next":"sol_lag_perm"}]},"sol_skiperror":{"kind":"solution","title":"解决方案：谨慎跳过应用错误","detail":"如果复制因单个应用错误（例如重复键）而停止，并且你确定数据确实一致，可以跳过该事件并继续复制流。这是最后手段；盲目跳过会造成静默的数据漂移。","steps":["先弄清错误发生的原因，并通过比较两侧受影响的行来验证数据一致性。","在未使用 GTID 的 MySQL 上，跳过单个事件：STOP REPLICA; SET GLOBAL sql_replica_skip_counter=1; START REPLICA;（8.0.26 之前：sql_slave_skip_counter；需谨慎）。","使用 GTID 时，要跳过失败的事务，用一个空事务标记该 GTID，然后启动副本。","跳过后，在两侧之间运行行级一致性检查（checksum）；如果存在漂移，转为 reseed。"],"related":["replication-status"]},"sol_reseed":{"kind":"solution","title":"解决方案：重建副本（reseed / rebuild）","detail":"如果位点/GTID 链断裂、数据已经漂移或存在无法修复的错误，唯一可靠的路径是用从主库获取的全新副本重建副本。","steps":["从主库获取一致的基础备份：MySQL xtrabackup/mysqldump 或 CLONE、Postgres pg_basebackup、Oracle RMAN duplicate、MSSQL full+log backup。","清除副本上的旧状态并恢复备份（Postgres pg_basebackup -R、MSSQL RESTORE ... WITH NORECOVERY）。","从正确的位点/GTID 开始复制：MySQL CHANGE REPLICATION SOURCE TO ...; START REPLICA；对于 MSSQL，将数据库重新加入 AG（automatic seeding）。","监控追赶进度；延迟归零后，用 checksum 验证两侧的一致性。"],"related":["replication-status"]},"sol_lag_wait":{"kind":"solution","title":"根本原因：暂时的负载峰值（延迟正在缩小）","detail":"复制是健康的；由于主库上暂时的写入突增或网络波动，出现了延迟，但副本正在追赶。","steps":["通过多次测量确认延迟趋势确实在下降。","在负载窗口结束之前，必要时只将对延迟敏感的读取路由到主库。","如果反复出现，将写入突增（批处理作业、ETL）分散到更长时间，或移到非高峰时段。","将网络/应用延迟纳入监控，以便在暂时性延迟变为持续性之前发现它。"],"related":["replication-status"]},"sol_lag_perm":{"kind":"solution","title":"根本原因：副本跟不上主库","detail":"应用（apply/redo）端跟不上主库产生变更的速率；延迟持续增长。通常是单线程应用、副本硬件较慢或网络瓶颈所致。","steps":["找出应用瓶颈：限制它的是单线程应用、副本的磁盘/CPU，还是网络带宽？","启用/增加并行应用：MySQL 中使用多个副本 worker（replica_parallel_workers）；在 Postgres/Oracle 中检查应用资源。","至少将副本硬件提升到与主库相同的水平（尤其是磁盘 IOPS）。","减少副本上不必要的重型读查询，为应用留出资源；如果仍然跟不上，在 reseed 后重新评估。"],"related":["replication-status"]},"sol_splitbrain":{"kind":"solution","title":"警告：脑裂（两个主库）— 有数据丢失风险","detail":"如果两个节点都以主库角色接受写入，冲突的变更会在两侧累积；这是最危险的情况，可能导致数据丢失。首要任务是将写入收敛到单个节点。","steps":["立即将写入集中到单个节点：将应用流量严格路由到一个主库，将另一个设为只读/隔离。","确定权威（正确的）节点：从业务角度决定以哪一方的数据为准。","比较两侧冲突（分叉）的变更；手动调和失败一方的关键变更。","以权威节点为源，通过 reseed 重建另一个节点；然后建立防止双主的 fencing/quorum 机制。"],"related":["replication-status"]}}},"solver/cache-hit-low.json":{"id":"cache-hit-low","title":"缓存命中率低（buffer/cache hit）","icon":"🎯","category":"性能","intro":"缓存命中率低；数据不断从磁盘（或数据源）读取。确定问题出在数据库 buffer/shared cache 还是 Redis/应用缓存，然后进行测量和修复。","start":"root","nodes":{"root":{"kind":"step","title":"哪个缓存的命中率低？","detail":"测量命中率并隔离问题：是数据库 buffer/shared cache 命中率低（物理读高），还是 Redis/应用缓存出现大量未命中和淘汰？","command":{"oracle":"SELECT ROUND((1-(pr.value/(dbg.value+cg.value)))*100,2) AS buffer_hit_pct\nFROM v$sysstat pr, v$sysstat dbg, v$sysstat cg\nWHERE pr.name='physical reads' AND dbg.name='db block gets' AND cg.name='consistent gets';","postgres":"SELECT ROUND(sum(blks_hit)*100.0/nullif(sum(blks_hit)+sum(blks_read),0),2) AS cache_hit_pct\nFROM pg_stat_database;","mysql":"SHOW GLOBAL STATUS LIKE 'Innodb_buffer_pool_read_requests';\nSHOW GLOBAL STATUS LIKE 'Innodb_buffer_pool_reads';","mssql":"SELECT cntr_value AS buffer_cache_hit_ratio\nFROM sys.dm_os_performance_counters\nWHERE counter_name='Buffer cache hit ratio' AND object_name LIKE '%Buffer Manager%';","redis":"INFO stats\n# Check the keyspace_hits / keyspace_misses ratio and evicted_keys in INFO memory"},"choices":[{"label":"数据库 buffer/shared cache 命中率低","next":"dbcache"},{"label":"Redis/应用缓存中大量未命中 / 淘汰","next":"rediscache"}]},"dbcache":{"kind":"step","title":"为什么从磁盘读取：容量问题还是扫描模式问题？","detail":"buffer 命中率低有两个典型原因：频繁访问的数据（工作集）放不进内存（缓存太小），或者大型全表扫描用一次性数据块污染缓存，将有用的页挤出。","command":{"oracle":"SELECT component, current_size/1024/1024 AS mb FROM v$sga_dynamic_components WHERE component LIKE '%buffer cache%';\nSELECT sql_id, disk_reads, executions FROM v$sql ORDER BY disk_reads DESC FETCH FIRST 10 ROWS ONLY;","postgres":"SHOW shared_buffers;\nSELECT pg_size_pretty(pg_database_size(current_database())) AS db_size;\nSELECT query, shared_blks_read FROM pg_stat_statements ORDER BY shared_blks_read DESC LIMIT 10;","mysql":"SHOW VARIABLES LIKE 'innodb_buffer_pool_size';\nSELECT ROUND(SUM(data_length+index_length)/1024/1024) AS data_mb FROM information_schema.tables;","mssql":"SELECT physical_memory_kb/1024 AS server_ram_mb FROM sys.dm_os_sys_info;\nSELECT COUNT(*)*8/1024 AS buffer_pool_mb FROM sys.dm_os_buffer_descriptors;"},"choices":[{"label":"工作集放不进 RAM（缓存太小）","next":"sol_sizing"},{"label":"全表扫描污染缓存","next":"sol_scan"}]},"rediscache":{"kind":"step","title":"Redis 为什么未命中：maxmemory 淘汰还是 TTL/键管理？","detail":"如果 Redis 命中率低，要么是内存达到 maxmemory、键正在被淘汰，要么是键的 TTL 管理/缓存模式较弱（冷键、复用率低）。","command":{"redis":"INFO memory\n# used_memory, maxmemory, maxmemory_policy, evicted_keys\nCONFIG GET maxmemory-policy\nINFO keyspace\n# Key count and expires (keys with TTL) ratio per db"},"choices":[{"label":"maxmemory 已满，淘汰率高","next":"sol_maxmem"},{"label":"TTL/键模式较弱（复用率低）","next":"sol_ttl"}]},"sol_sizing":{"kind":"solution","title":"根本原因：缓存相对于工作集太小","detail":"频繁访问的数据大于分配的 buffer/cache，因此不断被淘汰并从磁盘重新读取。命中率低，物理读高。","steps":["扩大缓存以覆盖工作集：Oracle db_cache_size/SGA、Postgres shared_buffers、MySQL innodb_buffer_pool_size、MSSQL max server memory。","如果物理 RAM 不足，增加服务器内存；这是数据库扩展所依赖的主要资源。","缩小工作集：归档/分区旧数据，精简不必要的列/索引。","变更后，比较命中率和物理读，确认已有改善。"],"related":["cache-hit-ratio"]},"sol_scan":{"kind":"solution","title":"根本原因：扫描模式污染缓存","detail":"大型全表扫描用只使用一次就丢弃的数据块填满缓存，将热页挤出；这会降低命中率。","steps":["获取物理读最多的查询的执行计划，将全表扫描改为索引访问（添加合适的索引）。","只选择需要的列，而不是 'SELECT *'；减少扫描的数据块数量。","在大表上，通过分区将扫描范围缩小到相关区间。","将重型分析/扫描作业与 OLTP 缓存分离（只读副本或单独的时间窗口）。"],"related":["slow-queries","cache-hit-ratio"]},"sol_maxmem":{"kind":"solution","title":"根本原因：Redis maxmemory 淘汰","detail":"Redis 内存已达到 maxmemory 上限；旧键被淘汰以便为新键腾出空间（evicted_keys 持续上升）。这会丢失仍然需要的条目，并降低命中率。","steps":["提高 maxmemory 以匹配真实的工作集，或缩小数据集。","根据访问模式选择淘汰策略（例如 LRU/LFU：allkeys-lfu 在区分冷热数据方面通常很有效）。","清理大型/很少使用的结构；缩小值、去除不必要的字段、使用合适的数据类型。","如果需求是长期的，扩展 Redis（更多 RAM 或 cluster/分片），并开始监控 evicted_keys。"],"related":[]},"sol_ttl":{"kind":"solution","title":"根本原因：TTL / 缓存模式较弱","detail":"要么键根本没有设置 TTL，导致内存被冷数据填满，要么缓存模式的复用率低，导致大多数请求最终都未命中。","steps":["为缓存的键设置合适的 TTL，使变冷的数据自行过期。","缓存频繁读取的（热）数据；不要缓存很少使用的数据（避免用永远不会命中的条目填满缓存）。","在 cache-aside 模式中，正确实现未命中后的回填，并使用加锁/请求去重来应对“惊群”（thundering herd）。","监控 keyspace_hits/misses 比率，并根据数据改进 TTL 和键的设计。"],"related":[]}}},"solver/arch-oltp.json":{"id":"arch-oltp","title":"参考架构：三层 OLTP 应用","icon":"🏛️","category":"参考架构","intro":"传统业务应用（订单、支付、库存）运行大量简短、单行、对一致性要求严格的事务（OLTP）。这棵决策树从识别负载画像开始，逐步说明何时引入主关系型数据库、连接池、只读副本、缓存层、备份 + PITR 和监控。目标不是“一开始就加上所有组件”，而是在每个瓶颈得到证实时加入合适的组件。","start":"root","nodes":{"root":{"kind":"step","title":"你的负载画像是什么：事务组合和一致性要求？","detail":"OLTP 架构的第一个决策是负载是否真的是事务型的（简短、需要 ACID 的读写混合）。数据天然是关系型的且一致性至关重要（资金、库存、订单），负载以读为主，还是负载仍然很低？","choices":[{"label":"短事务、强一致性（资金/库存/订单）","next":"primary_db"},{"label":"读取占绝对主导，写入很少，流量在增长","next":"scale_reads"},{"label":"负载很低；最简单的配置就够了吗？","next":"sol_single_node"}]},"primary_db":{"kind":"step","title":"你是否已选定主数据库引擎？是否担心扩展问题？","detail":"关系型引擎是事务核心的正确基础（PostgreSQL、MySQL/InnoDB、Oracle、SQL Server）。引擎选定后，下一个问题是扩展：单实例目前是否足够，还是连接数和读负载正在增加？","choices":[{"label":"引擎已确定；单实例目前足够","next":"sol_single_node"},{"label":"连接数激增，资源即将耗尽","next":"sol_pooler"},{"label":"读负载在增长，正在给主写入实例带来压力","next":"scale_reads"}]},"scale_reads":{"kind":"step","title":"如何扩展读取：副本还是缓存？","detail":"如果读负载正在给主实例带来压力，有两个互补的选项。可以容忍数据略有延迟的读取（如报表/列表）可以路由到只读副本；如果同样的“热”数据被反复请求，微秒级延迟的缓存（Redis）可以减轻数据库压力。","choices":[{"label":"大量报表/列表读取；可以接受轻微延迟","next":"sol_read_replica"},{"label":"同样的热门数据被反复读取","next":"sol_cache"}]},"sol_single_node":{"kind":"solution","title":"建议：从单主库 + 连接池 + 备份开始","detail":"早期保持架构简单：一个关系型主实例、前面的一个连接池和定期备份就足够了。只有在经过测量的瓶颈迫使时才增加复杂性。","steps":["主数据库：PostgreSQL 或 MySQL/InnoDB 等符合 ACID 的关系型引擎；正确设置模式、约束和索引。","连接池：从一开始就部署应用端连接池（例如 HikariCP）或服务端 PgBouncer；它可以防止连接风暴。","备份 + PITR：每日全量备份 + WAL/binlog 归档以实现时间点恢复；将备份存放在单独的存储中。","注意：不要过早添加副本/分片/缓存；试图解决未经测量的问题只会扩大故障面。","监控：从一开始就收集延迟、错误率和连接数指标；不要错过增长信号。"],"related":["decide-pooling","decide-backup"]},"sol_pooler":{"kind":"solution","title":"建议：添加连接池","detail":"在 OLTP 中，当每个应用实例都打开自己的连接时，主数据库在数千个连接下会被内存和调度开销拖垮。在中间部署连接池，可以让大量应用请求共享少量真实的数据库连接。","steps":["PostgreSQL：PgBouncer（transaction pooling 模式）将大量客户端收敛为少量后端连接。","MySQL：ProxySQL 同样提供连接多路复用和路由。","池大小：通常与 CPU 核心数成比例；几十个后端连接就足够了。“越多越好”并不适用。","注意：在 transaction pooling 下，会话级功能（prepared statement、临时表、会话变量）受到限制；应用需相应编写。","下一步：如果连接池也饱和，问题不在连接，而在查询/事务时长；找出慢查询和长事务。"],"related":["decide-pooling","connection-storm"]},"sol_read_replica":{"kind":"solution","title":"建议：添加只读副本","detail":"写入保留在主库上，只读流量（报表、列表、搜索）路由到由主库供给的一个或多个异步副本。这样主库可以专注于写入和对一致性要求严格的读取。","steps":["拓扑：主库 -> 一个或多个异步副本；应用进行读写分离，并将读取发送到副本。","引擎：PostgreSQL 流复制、MySQL 副本或托管只读副本。","适用场景：对于可以容忍少许延迟的读取，例如报表/分析/列表；希望分担主库的 CPU/IO 时。","注意：复制延迟会导致读到过时数据；需要“立即读到自己刚写入的数据”的流程应从主库读取（read-your-writes）。","下一步：如果副本也不够，考虑为热数据使用缓存，或在写入端进行分片。"],"related":["replication-lag","decide-shard-replica"]},"sol_cache":{"kind":"solution","title":"建议：添加缓存层（Redis）","detail":"如果相同的热门数据（会话、目录、档案、页面片段）被反复读取，在数据库前面部署内存缓存，使大多数读取根本无需访问数据库。延迟下降，主数据库得到减负。","steps":["引擎：使用 Redis（或 Memcached）的 cache-aside 模式；应用先检查缓存，未命中时从数据库获取并写入缓存。","TTL + 失效：为每个键设置合理的 TTL；写入时删除/更新相关键，以免残留过时数据。","适用场景：读取多、数据相对很少变化，且相同的键被反复请求。","注意：缓存击穿；热门键过期时，成千上万的请求可能同时打到数据库。使用加锁/single-flight 和带随机抖动（jitter）的 TTL。","注意：不要把缓存当作“唯一的数据来源”（source of truth）；持久的真实数据始终在数据库中，即使缓存故障，应用也必须能够回退到数据库继续工作。"],"related":["decide-cache","cache-hit-low"]}}},"solver/arch-dwh.json":{"id":"arch-dwh","title":"参考架构：数据仓库 / 分析","icon":"🏛️","category":"参考架构","intro":"分析型查询（报表、聚合、大范围扫描）与 OLTP 事务有本质不同：数量少，但范围很大、运行时间长。这棵决策树从将分析与 OLTP 分离的决策开始，逐步介绍加载方式（ETL vs ELT）、列式/columnstore 存储、星型模型建模、分区 + 压缩以及使用物化视图进行预计算等组件，同时权衡并发与吞吐量之间的取舍。","start":"root","nodes":{"root":{"kind":"step","title":"你的分析负载是否与 OLTP 运行在同一个数据库上？","detail":"最常见的错误是在线上事务数据库上运行重型报表，拖累 OLTP 延迟。第一个决策是分离：分析是否仍在 OLTP 实例上运行，你是否正在构建独立的分析环境，还是正在决定如何将数据迁移过去？","choices":[{"label":"报表仍在线上 OLTP 上运行；拖慢了 OLTP","next":"sol_separate"},{"label":"已有独立的数据仓库；应如何加载数据？","next":"load_method"},{"label":"已有数据仓库；查询仍然很慢，应如何建模？","next":"model"}]},"load_method":{"kind":"step","title":"在哪里进行转换：加载之前还是之后？","detail":"将数据从源系统迁移到数据仓库时，是在加载前于单独的处理层中进行清洗/转换（ETL），还是先将原始数据加载到数据仓库，再在强大的仓库引擎内用 SQL 进行转换（ELT）？该决策取决于数据仓库的处理能力和对模式灵活性的需求。","choices":[{"label":"转换复杂/在外部进行；在加载前完成","next":"sol_etl"},{"label":"数据仓库很强大；原样加载，在内部用 SQL 转换","next":"sol_elt"}]},"model":{"kind":"step","title":"查询瓶颈在存储布局还是数据模型？","detail":"如果分析很慢，有两个主要的着力点。在行式存储中，大范围扫描会读取整行；列式存储只读取所需的列，且压缩效果好。在模型方面，过度规范化意味着大量连接，拖慢查询；星型模型（事实 + 维度）可以为分析简化结构。","choices":[{"label":"大范围聚合扫描很慢；只读取少数几列","next":"sol_columnar"},{"label":"连接过多，规范化模式复杂","next":"sol_star"},{"label":"同一个重型报表反复产生相同的结果","next":"sol_matview"}]},"sol_separate":{"kind":"solution","title":"建议：将分析与 OLTP 分离","detail":"事务型和分析型负载在同一引擎上会相互拖累：长报表占用锁/IO，延迟短事务。将分析迁移到单独的目标（只读副本或独立的分析型数据仓库）可以同时为两侧减负。","steps":["第一步：将轻量报表路由到 OLTP 只读副本，将重型分析路由到单独的分析引擎。","分析引擎：列式/分析型引擎（例如带 columnstore 的 SQL Server、ClickHouse 或列式数据仓库）在大范围扫描上比行式存储快很多倍。","并发 vs 吞吐量：OLTP 针对大量小事务调优（高并发），数据仓库针对少量巨型查询调优（高吞吐量）；无法在同一台机器上同时优化两者。","注意：数据仓库不是“生产环境的实时副本”；它是按间隔加载的数据，有延迟，但为分析而组织。","下一步：分离之后，决定加载方式（ETL/ELT）和模型（星型模型）。"],"related":["decide-sqlnosql","slow-query"]},"sol_etl":{"kind":"solution","title":"建议：ETL（加载前转换）","detail":"在 ETL 中，数据从源系统抽取（Extract），在单独的处理层中进行清洗和转换（Transform），然后才加载到数据仓库（Load）。只有干净的、符合模型的数据才会进入数据仓库。","steps":["适用场景：转换复杂，需要合并多个数据源，或者希望原始/敏感数据永远不进入数据仓库。","流程：源系统（OLTP/文件/API）→ 转换层 → 批量加载到数据仓库。","优点：保持数据仓库存储干净；敏感数据可以在加载前脱敏。","缺点：转换依赖于单独系统的能力；无法利用数据仓库的 SQL 灵活性。","注意：在批处理窗口（例如夜间）加载；规划好以避免加载期间与重型查询冲突。"],"related":["decide-capacity"]},"sol_elt":{"kind":"solution","title":"建议：ELT（在数据仓库内转换）","detail":"在 ELT 中，原始数据先加载到数据仓库，转换在数据仓库自身强大的 SQL 引擎内进行。由于现代列式数据仓库处理能力很强，你只需移动一次数据并在内部处理，而不是把转换带到数据那里。","steps":["适用场景：数据仓库引擎强大且可扩展；你还希望保留原始数据（审计/重新处理）。","流程：源系统 → 原始层（staging）→ SQL/转换视图 → 建模后的事实/维度层。","优点：一个引擎，灵活；由于保留了原始数据，之后可以更改转换并重新生成。","缺点：原始（可能敏感的）数据会进入数据仓库；访问控制和脱敏是必需的。","注意：分层建模（raw → staging → mart）；以幂等方式编写转换，使重新运行是安全的。"],"related":["decide-capacity"]},"sol_columnar":{"kind":"solution","title":"建议：列式 / columnstore 存储 + 压缩","detail":"分析型查询通常会扫描所有行中的少数几列。列式存储按列保存数据；它只读取所需的列，由于相似的值彼此相邻而压缩效果极好，IO 大幅下降。","steps":["引擎/功能：SQL Server columnstore 索引、ClickHouse 或列式数据仓库；PostgreSQL 方面的列式扩展。","压缩：列式布局带来高压缩率；更少的磁盘、更少的读取字节、更快的扫描。","分区：按日期/范围对大型事实表进行分区，使分区裁剪完全跳过无关的分区。","注意：列式存储不适合大量单行更新/点读取；不要把 OLTP 迁移上去。","并发 vs 吞吐量：列式存储擅长少量巨型扫描查询，而不是成千上万的小型并发查询。"],"related":["decide-partition","io-bottleneck"]},"sol_star":{"kind":"solution","title":"建议：星型模型（事实 + 维度）","detail":"在分析模型中，保存度量值的事实表位于中心，周围是描述性的维度表。与过度规范化的模式相比，连接数量减少，查询更简单，引擎能生成更好的执行计划。","steps":["结构：事实（度量值 + 维度键）位于中心；维度（时间、产品、客户）在其周围形成星形。","适用场景：报表需要连接许多表，规范化模式淹没在连接之中。","反规范化：在分析中刻意的反规范化是正常的；一致性在加载过程（ETL/ELT）中保证。","下一步：用物化视图预先计算频繁运行的重型聚合。","注意：不要把 OLTP 改为星型模型；该模型用于分析，而不是事务性写入。"],"related":["decide-index"]},"sol_matview":{"kind":"solution","title":"建议：用物化视图预先计算","detail":"如果同一个重型聚合（每日收入、月度汇总）反复运行并产生相同的结果，将结果物理存储在物化视图中，让报表读取预先计算好的结果。繁重的计算只运行一次，查询即时返回。","steps":["适用场景：某些汇总查询频繁、反复地扫描同一张大表。","刷新：按计划刷新物化视图（例如夜间批处理或增量刷新）；根据数据新鲜度需求设置频率。","引擎：PostgreSQL materialized view（REFRESH）、Oracle materialized view（甚至支持 query rewrite）、SQL Server indexed view。","注意：视图存在新鲜度窗口；需要实时数据时，必须使用实时查询。","下一步：如果刷新开销高，考虑增量刷新或基于分区的刷新。"],"related":["decide-partition"]}}},"solver/arch-microservice.json":{"id":"arch-microservice","title":"参考架构：微服务数据层","icon":"🏛️","category":"参考架构","intro":"在微服务中，最关键的决策是数据所有权：每个服务必须拥有自己的数据（database-per-service），不得直接访问其他服务的表。这棵决策树逐步介绍共享数据库反模式、在不使用分布式事务的情况下实现跨服务一致性（saga + outbox）、用 CDC 同步数据，以及跨服务查询（API 组合 vs CQRS 读模型）。目标是让服务真正独立、可单独部署。","start":"root","nodes":{"root":{"kind":"step","title":"服务如何拥有数据：共享数据库还是每个服务一个？","detail":"数据隔离是微服务架构的支柱。服务是使用单个共享数据库（隐藏的依赖），每个服务拥有自己的数据库（正确的模式），还是真正的挑战在于跨服务一致性/查询？先确定当前状态。","choices":[{"label":"所有服务都使用单个共享数据库","next":"sol_db_per_service"},{"label":"每个服务有自己的数据库，但操作需要跨服务一致性","next":"consistency"},{"label":"每个服务有自己的数据库，但需要组合查询/报表","next":"query"}]},"consistency":{"kind":"step","title":"如何保持跨服务工作流的一致性？","detail":"对于跨多个服务的操作（创建订单 → 收款 → 扣减库存），传统的分布式事务（2PC）既脆弱又无法扩展。应将流程拆分为本地事务 + 事件的步骤（saga），并可靠地发布事件（outbox）。你面对的是哪个问题？","choices":[{"label":"我正试图让多服务工作流保持原子性（2PC？）","next":"sol_saga"},{"label":"我无法让本地写入和事件发布保持一致（双写）","next":"sol_outbox"}]},"query":{"kind":"step","title":"如何实现展示来自多个服务的数据的查询？","detail":"数据拆分到多个服务后，就无法用单个 join 构建组合页面。主要有两种方法：在请求时调用相关服务的 API 并合并结果（API 组合），或维护一个由事件驱动、预先连接好的读模型（CQRS）。查询的频率和复杂度决定了选择。","choices":[{"label":"简单、不频繁的连接；调用几个服务再合并即可","next":"sol_api_composition"},{"label":"复杂/频繁的组合查询；需要现成的读模型","next":"sol_cqrs"}]},"sol_db_per_service":{"kind":"solution","title":"建议：Database-per-service（拆分共享数据库）","detail":"多个服务共享一个数据库是一种隐蔽而危险的依赖：一个服务的模式变更会破坏其他服务，表的所有权变得不清晰，独立部署变得不可能。每个服务必须拥有自己的数据。","steps":["原则：每个服务只读写自己的数据库；不能直接访问其他服务的表，只能通过该服务的 API 请求数据。","引擎独立：每个服务可以根据需求选择引擎（例如订单用 PostgreSQL，目录用 MongoDB，搜索用 Elasticsearch，购物车用 Redis）。","反模式：“共享数据库”：公共表、交叉连接和单一模式；这不是微服务，而是分布式单体。","拆分：先进行逻辑分离（单独的 schema/用户），再进行物理分离；去除跨表连接。","注意：拆分之后会出现一致性（saga/outbox）和组合查询（composition/CQRS）问题；这是这一转变的自然代价。"],"related":["decide-isolation","decide-sqlnosql"]},"sol_saga":{"kind":"solution","title":"建议：Saga 模式（避免分布式事务）","detail":"不要试图用单个分布式事务（2PC）使跨多个服务的操作保持原子性；2PC 会持有锁，并带来单点脆弱性和低可用性。应将流程拆分为在各自服务中作为本地事务执行的步骤，失败时用补偿步骤撤销它们。","steps":["原则：将工作流建模为本地事务 + 事件的链条；每一步触发下一步。","补偿：如果某一步失败，运行撤销前面步骤的补偿操作（例如支付失败时取消订单）。","编舞 vs 编排：在简单流程中，服务通过事件相互触发（choreography）；在复杂流程中，由中央编排器管理各步骤（orchestration）。","注意：saga 不是原子的；它提供最终一致性。中间状态可能对外可见，因此需相应地设计业务规则。","注意：不要在大规模微服务中使用 2PC/XA；阻塞和锁持有时间会让系统停滞。"],"related":["decide-isolation","long-transaction"]},"sol_outbox":{"kind":"solution","title":"建议：事务性 outbox（双写陷阱）","detail":"当一个服务既要写入自己的数据库又要发布事件时，分开执行这两步就是“双写”陷阱：如果一个成功而另一个失败，系统就会处于不一致状态。解决方法是在与业务数据相同的本地事务中，将事件写入 outbox 表，然后由单独的进程发布。","steps":["原则：业务记录 + 事件记录在一个本地事务中写入 outbox 表；要么都发生，要么都不发生（原子性）。","发布：单独的 relay/CDC 进程读取 outbox 行，将其发布到消息代理（例如 Kafka）并做标记。","使用 CDC：Debezium 等 CDC 工具读取数据库日志（WAL/binlog），并可靠地将 outbox 变更传送到 Kafka。","幂等性：消费者可能多次收到同一事件；使用事件 id 进行幂等处理（假定为 at-least-once 投递）。","注意：不要直接采用“先写数据库，再发布到 Kafka”的方式；发布时发生崩溃会导致事件丢失。"],"related":["replication-lag"]},"sol_api_composition":{"kind":"solution","title":"建议：API 组合（在请求时合并）","detail":"在需要组合数据的地方，调用方（例如 gateway/BFF）调用相关服务的 API，并在内存中合并结果。对于跨少数服务的简单连接，这是最简单的方案；无需保留额外的数据副本。","steps":["原则：组合器（API gateway/BFF）调用 service-A 和 service-B，合并结果后返回。","适用场景：连接简单、跨越的服务少且查询频率适中；不希望承担额外读模型的维护负担。","优点：无需额外的数据存储或同步；每个服务返回自己的最新数据。","注意：对于跨多个服务或大数据集的连接，延迟会累积，内存中的连接开销很大。","下一步：如果连接变得复杂或非常频繁，转向 CQRS 读模型。"],"related":[]},"sol_cqrs":{"kind":"solution","title":"建议：CQRS 读模型（由事件供给）","detail":"对于复杂且频繁的组合查询，维护一个由各服务发出的事件供给的、预先连接好并针对查询优化的读模型（物化视图/反规范化副本）。写入端保留在各服务中；读取端从一个地方快速提供现成的数据。","steps":["原则：服务将状态变更作为事件发布；投影（projection）监听这些事件并更新读模型。","存储选择：根据查询类型选择读模型存储：搜索用 Elasticsearch，灵活文档用 MongoDB，快速键访问用 Redis，关系型报表用 PostgreSQL。","适用场景：组合查询复杂/频繁，要求低延迟，且 API 组合的开销已经增大。","注意：读模型是最终一致的；在事件被处理之前，它会略微滞后。","幂等性：投影可能两次看到同一事件；使用事件 id 进行幂等更新，必要时跟踪顺序/版本。"],"related":["decide-cache"]}}},"solver/arch-eventdriven.json":{"id":"arch-eventdriven","title":"参考架构：事件驱动 / CQRS","icon":"🏛️","category":"参考架构","intro":"在事件驱动架构中，服务不直接相互调用，而是将事件发布到事件日志（例如 Kafka），由消费者异步处理。这棵决策树从构建事件主干开始，逐步介绍事件溯源（从事件推导状态）、使用 CQRS 进行命令/查询分离、读模型投影、幂等性、对最终一致性的容忍，以及“exactly-once 投递”的谬误。目标是构建一个松耦合、可扩展、一致性取舍清晰的系统。","start":"root","nodes":{"root":{"kind":"step","title":"在事件驱动架构中，你面临的是哪个决策？","detail":"事件驱动系统的基础是一个通过异步事件连接各服务的持久化事件日志。你是仍在构建主干，希望从事件推导状态（事件溯源），还是正在分离读写路径（CQRS）？","choices":[{"label":"我正在构建连接各服务的事件主干","next":"sol_event_log"},{"label":"我想从事件历史中推导状态","next":"sol_event_sourcing"},{"label":"我想分离读写路径（CQRS）","next":"cqrs_split"}]},"cqrs_split":{"kind":"step","title":"在 CQRS 中如何构建读取端？你对一致性的容忍度如何？","detail":"在 CQRS 中，命令（写）端和查询（读）端是分离的；写入事件会更新为读模型提供数据的投影。关键问题是：你能否容忍读模型落后于写入（最终一致性），还是期望每个事件都恰好被处理一次？","choices":[{"label":"我将通过投影从事件为读模型提供数据","next":"sol_read_model"},{"label":"读取必须是最新的；不能接受延迟","next":"sol_consistency"},{"label":"我希望事件被“恰好一次”处理","next":"sol_idempotency"}]},"sol_event_log":{"kind":"solution","title":"建议：持久化事件日志主干","detail":"事件驱动架构的核心是一个持久、有序的事件日志，生产者向其写入，消费者按各自的节奏读取。这一主干在时间和规模上实现了服务之间的解耦。","steps":["引擎：Apache Kafka（或类似的基于日志的平台）：分区、持久化、可重放的事件日志。","分区与顺序：只在单个分区内保证顺序；通过将同一实体的事件路由到同一个键来保持顺序。","消费者独立：每个消费者组保存自己的 offset；可以重新读取/回退而不影响生产者。","保留：将日志保留足够长的时间，以便新的消费者和重新处理能够读取历史。","注意：Kafka 不是队列，而是可重放的日志；应假定为 at-least-once 投递，并编写幂等的消费者。"],"related":[]},"sol_event_sourcing":{"kind":"solution","title":"建议：事件溯源（从事件推导状态）","detail":"在事件溯源中，你存储的不是实体的当前状态，而是施加在其上的所有（不可变的）变更事件；当前状态通过按顺序应用这些事件推导出来。它提供完整的审计历史，以及回到过去任意时间点的能力。","steps":["原则：事件流是唯一的数据来源（source of truth）；通过从头重放事件（或快照 + 后续事件）来重建状态。","快照：对于很长的事件流，定期创建快照，避免每次都从头重放，然后在其上应用后续事件。","优点：完整的审计记录，能够随时间回答“我们是如何到达这个状态的”，并能推导出不同的读模型。","注意：事件模式会演进；读取旧事件需要版本控制/upcasting，因为事件会永久存在。","注意：并非所有场景都需要；如果当前状态就足够，传统 CRUD 更简单。通常与 CQRS 一起使用。"],"related":[]},"sol_read_model":{"kind":"solution","title":"建议：读模型投影","detail":"在 CQRS 中，写入端产生事件；投影监听这些事件，并更新针对查询优化的读模型（反规范化视图）。读取由这个现成的模型提供，与写入路径完全分离，两侧可以独立扩展。","steps":["原则：事件 → 投影 → 读存储；读查询只访问这个反规范化模型。","存储选择：按查询类型：搜索用 Elasticsearch，灵活文档用 MongoDB，快速访问用 Redis，关系型用 PostgreSQL。","可重建性：由于读模型是从事件推导出来的，可以将其删除，并通过重新读取日志从头重建。","幂等性：投影可能多次看到同一事件；使用事件 id/版本进行幂等更新。","注意：读模型是最终一致的；在事件被处理之前它会落后于写入，因此需相应地设计 UI。"],"related":["decide-cache"]},"sol_consistency":{"kind":"solution","title":"建议：为容忍最终一致性而设计","detail":"在事件驱动/CQRS 系统中，读模型会略微落后于写入（最终一致性）。与其试图“消除”它，不如设计成能够容忍它；将少数需要严格即时一致性的流程单独处理。","steps":["原则：大多数业务流程可以容忍短暂延迟的一致性；向用户反馈“请求已收到，正在处理”。","Read-your-writes：如果用户必须立即看到自己的写入，从写入端/同步地提供该读取。","需要严格一致性的核心：将需要绝对即时一致性的工作（例如资金余额）保留在单个服务/数据库内，使用 ACID 事务；不要通过事件分布处理。","注意：不要掩盖最终一致性；在界面和业务规则中显式处理中间/过渡状态。","注意：“让整个系统都强一致”的目标会破坏事件驱动架构的优势（规模、松耦合）。"],"related":["decide-ha","replication-lag"]},"sol_idempotency":{"kind":"solution","title":"建议：幂等性（exactly-once 的谬误）","detail":"在分布式消息传递中，端到端的“exactly-once 投递”在实践中是一个谬误；由于网络和重试，消息重复是不可避免的。正确的目标是幂等处理：处理同一事件两次的结果必须与处理一次相同。","steps":["原则：接受 at-least-once 投递；在消费者中让重复变得无害。","方法：为每个事件分配唯一 id；记录已处理的 id，如果再次到达则跳过（去重），或使用天然幂等的操作（upsert、set）。","Exactly-once 语义：Kafka 的 'exactly-once' 功能只适用于 Kafka 自身的事务边界之内；没有幂等性，就无法保证对外部数据库的副作用。","顺序：将同一实体的事件路由到同一个分区键，避免乱序。","注意：“我配置了 exactly-once 投递，不需要去重”的假设会在生产中造成不一致；始终进行幂等处理。"],"related":[]}}},"solver/arch-multiregion.json":{"id":"arch-multiregion","title":"参考架构：多区域 / 全球","icon":"🏛️","category":"参考架构","intro":"当用户在地理上分散，或需要抵御区域性灾难时，数据会分布到多个区域。这棵决策树逐步介绍读取本地化（每个区域一个只读副本）、写入拓扑（单主 vs 多主 vs quorum）、数据驻留/合规（GDPR）、冲突解决，以及 RPO/RTO 与延迟之间的取舍。基本事实：由于物理定律，低延迟、强一致性和多区域写入无法同时完全实现；必须做出有意识的取舍。","start":"root","nodes":{"root":{"kind":"step","title":"为什么要采用多区域：读取本地化、写入/容灾还是合规？","detail":"多区域架构成本高且复杂；先明确动机。你是希望远方用户获得低读取延迟，需要跨区域写入/容灾能力，还是真正的要求是数据留在特定国家（法律上的数据驻留）？","choices":[{"label":"远方用户需要快速读取；写入可以留在一个区域","next":"sol_read_locality"},{"label":"需要跨区域写入 / 能承受丢失一个区域","next":"write_topology"},{"label":"数据在法律上必须留在特定国家/区域","next":"sol_data_residency"}]},"write_topology":{"kind":"step","title":"写入拓扑应该是什么：单主、多主还是 quorum？","detail":"对于跨区域写入，关键决策是主节点模型。是所有写入都发往单个区域（简单、一致，但远程写入很慢），每个区域都可以写入（写入延迟低但存在冲突），还是写入在获得多数区域确认后才被接受（quorum：一致但增加延迟）？","choices":[{"label":"一个区域写入，其他区域读取（简单且一致）","next":"sol_single_leader"},{"label":"每个区域都可以本地写入（写入延迟低）","next":"sol_multi_leader"},{"label":"多数确认后写入；一致性优先","next":"sol_quorum"}]},"sol_read_locality":{"kind":"solution","title":"建议：每个区域一个只读副本（读取本地化）","detail":"写入保留在单个主区域，同时在每个用户所在区域放置一个由其供给的只读副本。用户从最近的副本读取，读取延迟下降；由于写入仍然发往单一中心，一致性模型保持简单。","steps":["拓扑：一个主区域（写入）→ 每个用户区域一个异步只读副本。","引擎：PostgreSQL 流复制、MySQL 跨区域副本或托管的全球只读副本。","路由：应用将读取路由到最近的副本，将写入路由到主区域（geo DNS / 智能路由）。","注意：跨区域复制延迟会导致读到过时数据；需要 read-your-writes 的流程应从主库读取。","RPO/RTO：如果主区域宕机，可以提升异步副本，但会丢失部分数据（RPO > 0）；提前准备好故障转移计划。"],"related":["replication-lag","decide-shard-replica"]},"sol_single_leader":{"kind":"solution","title":"建议：单主写入","detail":"所有写入都发往单个主区域；其他区域是由该主区域异步供给的只读副本。这是最简单、一致性最强的模型；因为只有一个写入点，所以没有冲突。代价是来自远程区域的写入延迟很高。","steps":["原则：单一写入权威 → 没有冲突，一致性模型简单。","适用场景：写入量可以由一个区域承载，且远程写入延迟可以容忍。","故障转移：如果主区域宕机，将某个副本提升为主；必须具备自动/受控的故障转移和脑裂保护。","RPO/RTO：使用异步复制时，故障转移可能丢失少量数据（RPO）；同步复制可实现 RPO=0，但会增加写入延迟。","注意：远程用户的写入很慢；如果写入本地化至关重要，考虑多主或 quorum。"],"related":["decide-ha","failover-drill"]},"sol_multi_leader":{"kind":"solution","title":"建议：多主写入 + 冲突解决","detail":"由于每个区域都可以本地写入，写入延迟低，即使一个区域被切断，其他区域也能继续写入。代价很大：两个区域可能以不同方式更新同一条记录，你必须设计冲突解决机制。","steps":["原则：每个区域都是主节点；写入异步传播到其他区域并进行合并。","冲突解决：Last-write-wins（简单但有数据丢失风险）、应用特定的合并，或 CRDT 等无冲突数据类型。","引擎示例：多主（multi-master/multi-leader）配置（例如 PostgreSQL 的 EDB Postgres Distributed (BDR)/pgEdge、Redis Enterprise Active-Active (CRDT)、CouchDB）。Cassandra 是无主（leaderless）而非多主；通过 LOCAL_ONE/LOCAL_QUORUM，它允许每个区域本地写入，实现类似效果，并用 last-write-wins（时间戳）解决冲突。","适用场景：本地写入延迟和区域独立性比强一致性更重要。","注意：从一开始就设计冲突解决；“不会发生冲突”的假设会造成静默的数据丢失。注意唯一 ID 生成中的跨区域冲突。"],"related":["decide-ha"]},"sol_quorum":{"kind":"solution","title":"建议：基于 quorum 的写入（多数确认）","detail":"当多数（quorum）区域/副本确认后，写入才被接受；当读取也访问多数节点时，就能保证读到最新值。它能抵御区域丢失且具有强一致性；代价是每次写入都要等待到达多数节点的延迟。","steps":["原则：遵循 W + R > N 规则的多数确认；即使少数区域宕机，系统也能继续一致地写入。","引擎示例：基于共识的分布式数据库（例如使用 Raft/Paxos 的系统）、Cassandra 中的 QUORUM 一致性级别。","适用场景：既需要区域级容灾能力又需要强一致性，并且可以接受更高的写入延迟。","RPO/RTO：由于多数节点持久写入，RPO≈0；区域丢失后能自动重新形成多数，RTO 可以很低。","注意：地理上相距遥远的区域之间的 quorum 会因光速延迟为每次写入增加数十 ms；谨慎选择区域的数量和位置。"],"related":["decide-ha","decide-shard-replica"]},"sol_data_residency":{"kind":"solution","title":"建议：数据驻留 / 合规（GDPR）","detail":"某些数据（个人、健康、财务）在法律上必须保留在特定国家/区域的境内（例如 GDPR）。在这种情况下，架构设计不是围绕“到处复制”，而是让数据在地理上固定。","steps":["原则：将个人/受监管的数据保留在其产生的区域；限制或禁止向区域外复制。","区域分区：将用户路由到其注册所在的区域（geo-partitioning）；每个区域保存本区域居民的数据。","全局数据 vs 本地数据：目录等全局数据可以复制到各处；个人数据留在本区域。","注意：备份和只读副本同样会移动数据；备份存储区域和 DR 区域也在合规范围之内。","注意：“只为了低延迟就把数据复制到各处”这样的决定，对受监管数据可能构成违法；从一开始就将数据驻留约束纳入架构。"],"related":["security-incident","audit-setup"]}}},"solver/arch-highscale.json":{"id":"arch-highscale","title":"参考架构：大规模 / 高流量","icon":"🏛️","category":"参考架构","intro":"随着流量增长，保护数据库有一个正确的顺序：先用简单、便宜的手段，再用复杂的手段。这棵决策树逐步介绍 垂直扩展 → 只读副本 → 分片 的顺序、缓存模式（cache-aside、write-back）、用队列 + 背压（backpressure）进行缓冲、限流、用 bulkhead/circuit breaker 隔离数据库，以及防止热点。核心原则：将分片这类难以逆转的决策留到最后，先用尽便宜的收益。","start":"root","nodes":{"root":{"kind":"step","title":"瓶颈在哪里：单实例已饱和，读取或写入繁重，还是流量呈突发性？","detail":"要在正确的地方解决扩展问题，首先要找出瓶颈。是单实例的 CPU/内存已饱和，读取占主导，写入触及上限，还是突发的流量峰值正在压垮数据库？每种情况都指向不同的组件。","choices":[{"label":"单实例已饱和；我还没有尝试简单的手段","next":"scale_path"},{"label":"读取非常繁重且在增长","next":"sol_cache"},{"label":"突发的流量峰值 / 突发负载正在压垮数据库","next":"protect"}]},"scale_path":{"kind":"step","title":"你处于哪个扩展阶段：垂直扩展、只读副本还是分片？","detail":"扩展有正确的顺序。垂直扩展（更强大的硬件）是最简单的第一步；然后将读取分散到副本上；只有当单个主库再也无法承载写入时，才转向水平分区（分片）。你处于哪个阶段？","choices":[{"label":"我还没有升级硬件 / 还没有用尽基础调优","next":"sol_vertical"},{"label":"写入可由一个实例承载，但我需要分散读负载","next":"sol_read_replica"},{"label":"单个主库再也无法承载写入负载","next":"sol_sharding"}]},"protect":{"kind":"step","title":"如何保护数据库免受负载冲击：缓冲、限流还是隔离？","detail":"扩展并不总是让数据库变得更大；很多时候是要保护数据库免于过载。你是要用队列平滑突发的写入峰值，限制传入的请求速率（限流），还是防止单个依赖拖垮整个系统（bulkhead/circuit breaker）？","choices":[{"label":"我想平滑突发的写入峰值","next":"sol_queue"},{"label":"我想限制/公平分配传入的请求速率","next":"sol_ratelimit"},{"label":"我不希望一个故障的依赖拖垮整个系统","next":"sol_bulkhead"}]},"sol_vertical":{"kind":"solution","title":"建议：先进行垂直扩展和基础调优","detail":"最便宜、风险最低的一步是垂直扩展：增加更多 CPU/RAM/更快的磁盘，并修复基础问题（索引、连接池、内存参数）。在做出分片等复杂决策之前，先用尽这些收益。","steps":["第一步：升级硬件（CPU/RAM/NVMe），并根据负载调优 buffer/cache 内存参数。","便宜的收益：添加缺失的索引，修复重型查询，在前面部署连接池（PgBouncer/ProxySQL）；大多数“扩展”问题实际上是调优问题。","何时足够：如果升级后的单实例达到目标延迟/吞吐量，就此停止；不要再增加复杂性。","注意：垂直扩展存在物理和成本上限；接近上限时，转向只读副本和缓存。","下一步：如果读取占主导，使用副本 + 缓存；如果写入占主导且超出上限，使用分片。"],"related":["decide-capacity","high-cpu"]},"sol_read_replica":{"kind":"solution","title":"建议：用只读副本分散读取","detail":"写入保留在单个主库上，只读流量分散到一个或多个副本。它比分片简单得多，能解决大多数读密集型负载；主库只负责写入和对一致性要求严格的读取。","steps":["拓扑：主库（写入）→ 多个异步只读副本；读取通过负载均衡器分发到各副本。","引擎：PostgreSQL 流复制、MySQL 副本、托管只读副本。","适用场景：读写比严重偏向读取，且写入仍可由单个实例承载。","注意：复制延迟会导致读到过时数据；需要 read-your-writes 的读取应由主库提供。","下一步：如果副本也不够，或者真正的瓶颈在写入，先添加缓存，再考虑分片。"],"related":["replication-lag","decide-shard-replica"]},"sol_sharding":{"kind":"solution","title":"建议：分片（水平分区）作为最后手段","detail":"当单个主库无法承载写入负载时，按某个键将数据拆分为多个独立的部分（分片）；每个分片承担一部分写入。它很强大但难以逆转；会使跨分片查询和事务复杂化，因此留到最后。","steps":["分片键：选择能使负载和数据均匀分布、并让大多数查询落在单个分片上的键（例如 customer_id）。","方法：基于哈希（均匀分布）或基于范围（便于范围查询，但有热点风险）的分区。","引擎示例：MongoDB 分片、Citus（PostgreSQL）、Vitess（MySQL）或应用层分片。","注意：跨分片的连接/事务代价高且复杂；设计模式时使大多数事务能在单个分片内完成。","注意：错误的键会产生热点（负载堆积在一个分片上）；避免使用递增 id/日期等单调键，并记住重新分片（resharding）很困难。"],"related":["decide-shard-replica","decide-partition"]},"sol_cache":{"kind":"solution","title":"建议：缓存模式（cache-aside + write-back）","detail":"在读密集的高流量下，在数据库前面部署内存缓存（Redis），使大多数读取根本无需到达数据库。对于大量写入，write-back 会缓冲写入并批量刷新到数据库，减轻主库压力。","steps":["Cache-aside：应用先检查 Redis；未命中时从数据库获取并写入缓存。这可以将大部分读负载从数据库移走。","Write-back（write-behind）：对于计数器/指标等写入量极高的场景，先写入缓存，再在后台批量刷新到数据库。","TTL + 失效：为键设置合理的 TTL；写入时更新/删除相关键，以免残留过时数据。","注意：缓存击穿：热门键过期时，成千上万的请求同时打到数据库；使用加锁/single-flight、提前刷新和带抖动的 TTL。","注意：使用 write-back 时，如果缓存节点在写入数据库之前崩溃，数据会丢失；用持久化队列/AOF 保护关键数据。"],"related":["decide-cache","cache-hit-low"]},"sol_queue":{"kind":"solution","title":"建议：用队列进行缓冲（背压）","detail":"为防止突发的写入峰值直接冲击数据库，在中间放置一个持久化队列（例如 Kafka/消息队列），并以数据库能够承受的速率消费写入。队列吸收峰值；系统不会同步失败，而是以可控的延迟运行（背压）。","steps":["原则：生产者将事件写入队列；消费者以可持续的速率（可能批量）写入数据库。","引擎：Kafka 或消息队列；它充当将数据库与峰值流量隔离的缓冲区。","背压：如果队列持续增长，减慢生产者/向其发出信号；不要允许无限堆积，否则延迟会激增。","适用场景：写入流量突发性很强，且不必立即同步处理（可以接受异步处理）。","注意：队列引入了异步性；结果是最终一致的。使消费者具备幂等性（消息可能被重复投递）。"],"related":["connection-storm"]},"sol_ratelimit":{"kind":"solution","title":"建议：限流（限制请求速率）","detail":"通过按来源限制传入请求（限流），既能防止滥用，又能防止单个客户端耗尽所有资源。限制到达数据库的负载，可以使系统保持可预测和公平。","steps":["部署位置：在数据库之前（API gateway / 应用边缘）实施限流；只有被接受的请求才能到达数据库。","方法：使用 token bucket / sliding window 按客户端或按用户限速；超出时返回 429。","公平分配：使用配额，防止“吵闹的邻居”（noisy neighbor）吞噬全部容量。","适用场景：流量可能突然激增，或存在滥用/自动化客户端的风险。","注意：限流是一种保护措施，而不是扩展；如果存在真正的容量需求，还需解决实际瓶颈（垂直扩展/副本/分片）。"],"related":["connection-storm"]},"sol_bulkhead":{"kind":"solution","title":"建议：bulkhead + circuit breaker（隔离）","detail":"为防止某个依赖（例如变慢的数据库或外部服务）拖垮整个系统，将资源划分为多个隔舱（bulkhead），并暂时切断对持续失败的依赖的调用（circuit breaker）。这样局部故障就不会蔓延到整个系统。","steps":["Bulkhead：为不同类型的工作分配独立的连接池/线程；一条缓慢的路径不能耗尽整个池并让其他路径饿死。","Circuit breaker：当某个依赖超过错误/延迟阈值时，“断开”电路并在一段时间内快速失败；然后通过试探性调用（half-open）探测是否恢复。","超时：为每次数据库/服务调用设置合理的超时；挂起的调用不能锁死连接池。","适用场景：在依赖众多的系统中，一个组件变慢就存在级联故障风险时。","注意：为电路断开时设计优雅降级：从缓存返回过时数据或部分结果，而不是拒绝整个请求。"],"related":["connection-storm","high-cpu"]}}},"types/types-functions.json":{"schemaVersion":1,"sections":[{"id":"datatypes","title":"数据类型","icon":"🔤","rows":[{"id":"var-text","concept":"可变长度文本","engines":{"oracle":"VARCHAR2(n)","postgres":"VARCHAR(n) / TEXT","mysql":"VARCHAR(n)","mssql":"NVARCHAR(n)","mongodb":"string","redis":"string (value)","elasticsearch":"text / keyword"},"note":"在 ES 中，text 用于全文搜索，keyword 用于精确匹配/聚合。"},{"id":"fixed-text","concept":"固定长度文本","engines":{"oracle":"CHAR(n)","postgres":"CHAR(n)","mysql":"CHAR(n)","mssql":"NCHAR(n)","mongodb":"string","redis":"string","elasticsearch":"keyword"},"note":"文档/键值系统没有固定长度的概念；值以字符串形式存储。"},{"id":"large-text","concept":"大文本（CLOB/TEXT）","engines":{"oracle":"CLOB","postgres":"TEXT","mysql":"LONGTEXT / TEXT","mssql":"NVARCHAR(MAX)","mongodb":"string","redis":"string (up to 512 MB)","elasticsearch":"text"}},{"id":"int32","concept":"整数（32 位）","engines":{"oracle":"NUMBER(10)","postgres":"INTEGER / INT4","mysql":"INT","mssql":"INT","mongodb":"int (NumberInt / int32)","redis":"string (interpreted as an integer)","elasticsearch":"integer"},"note":"Oracle 没有单独的 INT 类型；使用 NUMBER。"},{"id":"int64","concept":"大整数（64 位）","engines":{"oracle":"NUMBER(19)","postgres":"BIGINT / INT8","mysql":"BIGINT","mssql":"BIGINT","mongodb":"long (NumberLong / int64)","redis":"string (INCR 64-bit integer)","elasticsearch":"long"}},{"id":"decimal","concept":"小数/精确数值（NUMERIC）","engines":{"oracle":"NUMBER(p,s)","postgres":"NUMERIC(p,s) / DECIMAL","mysql":"DECIMAL(p,s)","mssql":"DECIMAL(p,s) / NUMERIC","mongodb":"decimal (Decimal128)","redis":"none, stored as a string","elasticsearch":"scaled_float"},"note":"对于金额等精确值，优先使用这些类型而不是 float。"},{"id":"float","concept":"浮点数（FLOAT/DOUBLE）","engines":{"oracle":"BINARY_DOUBLE / FLOAT","postgres":"REAL / DOUBLE PRECISION","mysql":"FLOAT / DOUBLE","mssql":"FLOAT / REAL","mongodb":"double","redis":"none, string (INCRBYFLOAT)","elasticsearch":"float / double"}},{"id":"boolean","concept":"逻辑值（BOOLEAN）","engines":{"oracle":"NUMBER(1) / CHAR(1) (BOOLEAN in 23c)","postgres":"BOOLEAN","mysql":"TINYINT(1) / BOOL","mssql":"BIT","mongodb":"bool","redis":"none, 0/1 string or bit","elasticsearch":"boolean"},"note":"Oracle SQL 直到 23c 才引入 BOOLEAN 列类型；在此之前用 NUMBER/CHAR 模拟。"},{"id":"date","concept":"日期","engines":{"oracle":"DATE (includes time)","postgres":"DATE","mysql":"DATE","mssql":"DATE","mongodb":"Date (ISODate)","redis":"none (epoch string/number)","elasticsearch":"date (with format)"},"note":"Oracle DATE 类型同时包含日期和时间；并不是仅日期。"},{"id":"datetime","concept":"日期时间（timestamp/datetime）","engines":{"oracle":"TIMESTAMP","postgres":"TIMESTAMP","mysql":"DATETIME / TIMESTAMP","mssql":"DATETIME2","mongodb":"Date (ISODate)","redis":"none (epoch string/number)","elasticsearch":"date"}},{"id":"datetime-tz","concept":"带时区的日期时间","engines":{"oracle":"TIMESTAMP WITH TIME ZONE","postgres":"TIMESTAMPTZ","mysql":"TIMESTAMP (stored as UTC)","mssql":"DATETIMEOFFSET","mongodb":"Date (stored as UTC)","redis":"none (epoch UTC)","elasticsearch":"date (UTC, time zone via format)"},"note":"Mongo 和 ES 以 UTC 存储日期；时区在显示时应用。"},{"id":"binary","concept":"二进制数据（BLOB/binary）","engines":{"oracle":"BLOB / RAW","postgres":"BYTEA","mysql":"BLOB / VARBINARY","mssql":"VARBINARY(MAX)","mongodb":"BinData (binary)","redis":"string (binary-safe)","elasticsearch":"binary (base64, not searchable)"},"note":"Redis 字符串是二进制安全的，可以保存任意字节序列。"},{"id":"uuid","concept":"UUID/GUID","engines":{"oracle":"RAW(16) / SYS_GUID()","postgres":"UUID","mysql":"BINARY(16) / CHAR(36)","mssql":"UNIQUEIDENTIFIER","mongodb":"UUID (BinData subtype 4) / ObjectId","redis":"none, stored as a string","elasticsearch":"keyword"},"note":"Mongo 原生的 _id 是 ObjectId；UUID 也可以存储为 BinData。"},{"id":"json","concept":"JSON","engines":{"oracle":"JSON (21c+) / CLOB","postgres":"JSON / JSONB","mysql":"JSON","mssql":"JSON (2025) / NVARCHAR","mongodb":"object (native BSON)","redis":"not native / RedisJSON module","elasticsearch":"object / nested / flattened"},"note":"在 Postgres 中，查询/索引优先使用 JSONB；MongoDB 本身就是基于文档的。"},{"id":"auto-increment","concept":"自增（identity/serial）","engines":{"oracle":"GENERATED AS IDENTITY / SEQUENCE","postgres":"SERIAL / GENERATED ... IDENTITY","mysql":"AUTO_INCREMENT","mssql":"IDENTITY(1,1)","mongodb":"none (ObjectId or a counter pattern)","redis":"counter with INCR","elasticsearch":"none (automatic _id)"}},{"id":"enum","concept":"枚举/固定列表","engines":{"oracle":"via a CHECK constraint","postgres":"ENUM (CREATE TYPE)","mysql":"ENUM(...)","mssql":"via a CHECK constraint","mongodb":"none (validation with JSON Schema enum)","redis":"none, stored as a string","elasticsearch":"keyword"}},{"id":"array","concept":"数组","engines":{"oracle":"VARRAY / nested table / collection","postgres":"ARRAY (e.g. INTEGER[])","mysql":"none (via JSON array)","mssql":"none (JSON / table variable)","mongodb":"array (native)","redis":"List / Set (native)","elasticsearch":"any field can be an array (native)"},"note":"在 ES 中，每个字段都可以隐式保存数组；没有单独的数组类型。"},{"id":"ip-address","concept":"IP 地址","engines":{"oracle":"VARCHAR2 / UTL_INADDR","postgres":"INET / CIDR","mysql":"VARBINARY(16) / INET6_ATON","mssql":"none, via VARCHAR","mongodb":"none, stored as a string","redis":"none, stored as a string","elasticsearch":"ip"},"note":"Postgres 和 ES 中的原生 IP 类型支持范围/子网查询。"},{"id":"money","concept":"金额","engines":{"oracle":"NUMBER(p,2)","postgres":"MONEY / NUMERIC(p,2)","mysql":"DECIMAL(p,2)","mssql":"MONEY / DECIMAL(p,2)","mongodb":"decimal (Decimal128)","redis":"none, an integer in minor units (cents) is recommended","elasticsearch":"scaled_float"},"note":"为避免舍入误差，金额使用 DECIMAL/Decimal128 而不是 float 存储。"},{"id":"spatial","concept":"地理/空间","engines":{"oracle":"SDO_GEOMETRY (Oracle Spatial)","postgres":"GEOMETRY / GEOGRAPHY (PostGIS)","mysql":"GEOMETRY / POINT","mssql":"GEOGRAPHY / GEOMETRY","mongodb":"GeoJSON object (2dsphere index)","redis":"Geo (GEOADD, sorted set based)","elasticsearch":"geo_point / geo_shape"}}]},{"id":"functions","title":"常用函数","icon":"🧩","rows":[{"id":"coalesce","concept":"为 NULL 时返回默认值（COALESCE/NVL）","engines":{"oracle":"NVL(x, y) / COALESCE","postgres":"COALESCE(x, y)","mysql":"COALESCE(x, y) / IFNULL","mssql":"COALESCE(x, y) / ISNULL","mongodb":"$ifNull: [x, y]","redis":"none (application side)","elasticsearch":"?.field / painless ternary"}},{"id":"nullif-replace","concept":"为 NULL 时替换（ISNULL/IFNULL）","engines":{"oracle":"NVL(x, y)","postgres":"COALESCE(x, y)","mysql":"IFNULL(x, y)","mssql":"ISNULL(x, y)","mongodb":"$ifNull: [x, y]","redis":"none","elasticsearch":"painless: doc[f].size()==0 ? y : x"}},{"id":"now","concept":"当前日期时间（NOW/SYSDATE）","engines":{"oracle":"SYSDATE / SYSTIMESTAMP","postgres":"NOW() / CURRENT_TIMESTAMP","mysql":"NOW()","mssql":"GETDATE() / SYSDATETIME()","mongodb":"$$NOW / new Date()","redis":"TIME command","elasticsearch":"now (date math)"}},{"id":"current-date","concept":"当前日期","engines":{"oracle":"TRUNC(SYSDATE) / CURRENT_DATE","postgres":"CURRENT_DATE","mysql":"CURDATE()","mssql":"CAST(GETDATE() AS DATE)","mongodb":"$dateTrunc / new Date()","redis":"TIME (epoch)","elasticsearch":"now/d"}},{"id":"concat","concept":"拼接文本（concat）","engines":{"oracle":"a || b / CONCAT","postgres":"a || b / CONCAT","mysql":"CONCAT(a, b)","mssql":"CONCAT(a, b) / a + b","mongodb":"$concat: [a, b]","redis":"APPEND","elasticsearch":"painless: a + b"},"note":"在 MySQL 中，|| 默认表示 OR；拼接请使用 CONCAT。"},{"id":"substring","concept":"子字符串","engines":{"oracle":"SUBSTR(s, p, n)","postgres":"SUBSTRING(s, p, n)","mysql":"SUBSTRING(s, p, n)","mssql":"SUBSTRING(s, p, n)","mongodb":"$substrCP: [s, p, n]","redis":"GETRANGE key start end","elasticsearch":"painless: s.substring(p, e)"}},{"id":"length","concept":"字符串长度","engines":{"oracle":"LENGTH(s)","postgres":"LENGTH(s) / CHAR_LENGTH","mysql":"CHAR_LENGTH(s)","mssql":"LEN(s)","mongodb":"$strLenCP: s","redis":"STRLEN key","elasticsearch":"painless: s.length()"}},{"id":"upper-lower","concept":"大写/小写","engines":{"oracle":"UPPER(s) / LOWER(s)","postgres":"UPPER(s) / LOWER(s)","mysql":"UPPER(s) / LOWER(s)","mssql":"UPPER(s) / LOWER(s)","mongodb":"$toUpper / $toLower","redis":"none (application side)","elasticsearch":"painless: s.toUpperCase() / lowercase analyzer"}},{"id":"trim","concept":"去除空白（TRIM）","engines":{"oracle":"TRIM(s) / LTRIM / RTRIM","postgres":"TRIM(s)","mysql":"TRIM(s)","mssql":"TRIM(s)","mongodb":"$trim: { input: s }","redis":"none","elasticsearch":"painless: s.trim() / trim token filter"}},{"id":"replace","concept":"替换文本（REPLACE）","engines":{"oracle":"REPLACE(s, a, b)","postgres":"REPLACE(s, a, b)","mysql":"REPLACE(s, a, b)","mssql":"REPLACE(s, a, b)","mongodb":"$replaceAll / $replaceOne","redis":"SETRANGE (positional) / none","elasticsearch":"painless: s.replace(a, b)"}},{"id":"cast","concept":"类型转换（CAST/CONVERT）","engines":{"oracle":"CAST(x AS t) / TO_NUMBER","postgres":"CAST(x AS t) / x::t","mysql":"CAST(x AS t) / CONVERT","mssql":"CAST(x AS t) / CONVERT","mongodb":"$convert / $toInt, $toString","redis":"none (everything is a string)","elasticsearch":"painless: Integer.parseInt(...) / runtime field"}},{"id":"date-to-char","concept":"将日期格式化为文本（TO_CHAR/FORMAT）","engines":{"oracle":"TO_CHAR(d, 'YYYY-MM-DD')","postgres":"TO_CHAR(d, 'YYYY-MM-DD')","mysql":"DATE_FORMAT(d, '%Y-%m-%d')","mssql":"FORMAT(d, 'yyyy-MM-dd') / CONVERT","mongodb":"$dateToString: { format, date }","redis":"none","elasticsearch":"date field format / painless SimpleDateFormat"}},{"id":"parse-date","concept":"将文本转换为日期（TO_DATE/PARSE）","engines":{"oracle":"TO_DATE(s, 'YYYY-MM-DD')","postgres":"TO_DATE(s, 'YYYY-MM-DD')","mysql":"STR_TO_DATE(s, '%Y-%m-%d')","mssql":"CONVERT(date, s) / PARSE","mongodb":"$dateFromString: { dateString: s }","redis":"none","elasticsearch":"automatic parsing via date mapping"}},{"id":"date-add","concept":"日期加减（interval/DATEADD）","engines":{"oracle":"d + n / ADD_MONTHS / INTERVAL","postgres":"d + INTERVAL '1 day'","mysql":"DATE_ADD(d, INTERVAL 1 DAY)","mssql":"DATEADD(day, 1, d)","mongodb":"$dateAdd: { startDate, unit, amount }","redis":"none (add to the epoch)","elasticsearch":"date math: now+1d"}},{"id":"date-diff","concept":"两个日期之差（DATEDIFF）","engines":{"oracle":"d1 - d2 (days) / MONTHS_BETWEEN","postgres":"d1 - d2 / AGE(d1, d2)","mysql":"DATEDIFF(d1, d2)","mssql":"DATEDIFF(day, d2, d1)","mongodb":"$dateDiff: { startDate, endDate, unit }","redis":"none (epoch difference)","elasticsearch":"painless: ChronoUnit.DAYS.between(...)"}},{"id":"extract","concept":"提取日期部分（EXTRACT）","engines":{"oracle":"EXTRACT(YEAR FROM d)","postgres":"EXTRACT(YEAR FROM d) / DATE_PART","mysql":"EXTRACT(YEAR FROM d) / YEAR(d)","mssql":"DATEPART(year, d) / YEAR(d)","mongodb":"$year / $month / $dayOfMonth","redis":"none","elasticsearch":"painless: doc[f].value.year"}},{"id":"round","concept":"四舍五入（ROUND）","engines":{"oracle":"ROUND(x, n)","postgres":"ROUND(x, n)","mysql":"ROUND(x, n)","mssql":"ROUND(x, n)","mongodb":"$round: [x, n]","redis":"none","elasticsearch":"painless: Math.round(x)"}},{"id":"mod","concept":"取模（余数）","engines":{"oracle":"MOD(a, b)","postgres":"MOD(a, b) / a % b","mysql":"MOD(a, b) / a % b","mssql":"a % b","mongodb":"$mod: [a, b]","redis":"none","elasticsearch":"painless: a % b"}},{"id":"case","concept":"条件判断（CASE/DECODE）","engines":{"oracle":"CASE ... / DECODE(x, ...)","postgres":"CASE WHEN ... END","mysql":"CASE WHEN ... END / IF()","mssql":"CASE WHEN ... END / IIF()","mongodb":"$cond / $switch","redis":"none","elasticsearch":"painless: if/else"}},{"id":"greatest-least","concept":"最大值/最小值（GREATEST/LEAST）","engines":{"oracle":"GREATEST(...) / LEAST(...)","postgres":"GREATEST(...) / LEAST(...)","mysql":"GREATEST(...) / LEAST(...)","mssql":"GREATEST(...) / LEAST(...) (2022+)","mongodb":"$max / $min (expression)","redis":"none","elasticsearch":"painless: Math.max / Math.min"}},{"id":"string-agg","concept":"组内拼接（STRING_AGG/GROUP_CONCAT/LISTAGG）","engines":{"oracle":"LISTAGG(x, ',')","postgres":"STRING_AGG(x, ',')","mysql":"GROUP_CONCAT(x)","mssql":"STRING_AGG(x, ',')","mongodb":"$push + $reduce / $concatArrays","redis":"none","elasticsearch":"terms agg / scripted_metric"}},{"id":"row-number","concept":"行号（ROW_NUMBER）","engines":{"oracle":"ROW_NUMBER() OVER (...)","postgres":"ROW_NUMBER() OVER (...)","mysql":"ROW_NUMBER() OVER (...)","mssql":"ROW_NUMBER() OVER (...)","mongodb":"$setWindowFields: { $documentNumber }","redis":"none","elasticsearch":"none (top_hits / bucket_sort)"}},{"id":"limit","concept":"前 N 行（LIMIT/TOP/ROWNUM）","engines":{"oracle":"FETCH FIRST n ROWS ONLY / ROWNUM","postgres":"LIMIT n","mysql":"LIMIT n","mssql":"TOP n / OFFSET-FETCH","mongodb":"$limit: n / .limit(n)","redis":"LRANGE key 0 n-1","elasticsearch":"size: n"}},{"id":"current-user","concept":"当前用户","engines":{"oracle":"USER / SYS_CONTEXT","postgres":"CURRENT_USER","mysql":"CURRENT_USER() / USER()","mssql":"CURRENT_USER / SUSER_SNAME()","mongodb":"connectionStatus / $$USER (none, application side)","redis":"ACL WHOAMI","elasticsearch":"_security/_authenticate API"}},{"id":"gen-uuid","concept":"生成 UUID","engines":{"oracle":"SYS_GUID()","postgres":"gen_random_uuid()","mysql":"UUID()","mssql":"NEWID()","mongodb":"UUID() / ObjectId()","redis":"none (application side)","elasticsearch":"automatic _id / application side"}},{"id":"json-extract","concept":"提取 JSON 字段","engines":{"oracle":"JSON_VALUE(j, '$.a')","postgres":"j->>'a' / j #>> '{a}'","mysql":"JSON_EXTRACT(j, '$.a') / j->>'$.a'","mssql":"JSON_VALUE(j, '$.a')","mongodb":"doc.a (native access)","redis":"JSON.GET key $.a (RedisJSON)","elasticsearch":"doc['a'] / field access (native)"}},{"id":"regexp","concept":"正则表达式匹配（REGEXP）","engines":{"oracle":"REGEXP_LIKE(s, p)","postgres":"s ~ p / REGEXP_MATCHES","mysql":"s REGEXP p / REGEXP_LIKE","mssql":"REGEXP_LIKE (2025+) / before: LIKE / CLR","mongodb":"$regex / $regexMatch","redis":"none (SCAN MATCH glob pattern)","elasticsearch":"regexp query"},"note":"SQL Server 2025（以及 Azure SQL）新增了 REGEXP_LIKE、REGEXP_REPLACE 和 REGEXP_SUBSTR 等原生正则函数（REGEXP_LIKE 需要兼容级别 170）；更早的版本需要使用 LIKE 或 CLR 函数。"}]}]},"reference/params-oracle.json":{"engineId":"oracle","engine":"Oracle","order":1,"params":[{"name":"sga_target","default":"0","recommended":"专用服务器上约为 RAM 的 60%","unit":"bytes (K/M/G)","dynamic":"dynamic","desc":"为自动共享内存管理（ASMM）设置 SGA 总大小；自动分配各子池。","tags":["memory","memory","sga"]},{"name":"sga_max_size","default":"Same as sga_target","recommended":"等于或略高于 sga_target","unit":"bytes (K/M/G)","dynamic":"restart","desc":"设置 SGA 在运行时可以增长到的上限；增大该值需要重启实例。","tags":["memory","memory","sga"]},{"name":"pga_aggregate_target","default":"10M or 20% of the SGA","recommended":"约为 RAM 的 20%","unit":"bytes (K/M/G)","dynamic":"dynamic","desc":"设置所有服务器进程使用的 PGA 内存总目标值；影响排序和哈希操作。","tags":["memory","memory","pga","sort"]},{"name":"pga_aggregate_limit","default":"Greatest of: 2G, 200% of pga_aggregate_target, 3M x processes (not exceeding 120% of physical RAM minus SGA)","recommended":"默认值通常已足够","unit":"bytes (K/M/G)","dynamic":"dynamic","desc":"实例级 PGA 内存的硬上限；超出时，消耗最多的会话的调用会被中止（SYS 和后台进程除外）。在 Oracle 12c 中引入。","tags":["memory","memory","pga","limit"]},{"name":"memory_target","default":"0","recommended":"0（优先分别管理 SGA/PGA）","unit":"bytes (K/M/G)","dynamic":"dynamic","desc":"通过自动内存管理（AMM）将 SGA 和 PGA 合并到单一目标下；不建议在大型系统上使用。","tags":["memory","memory","amm"]},{"name":"db_cache_size","default":"0 (automatic)","recommended":"如使用 sga_target，则保持为 0","unit":"bytes (K/M/G)","dynamic":"dynamic","desc":"设置默认块大小的 buffer cache 大小；将数据块保留在内存中。","tags":["memory","memory","cache","buffer"]},{"name":"shared_pool_size","default":"0 (automatic)","recommended":"如使用 sga_target，则保持为 0","unit":"bytes (K/M/G)","dynamic":"dynamic","desc":"shared pool 的大小，用于保存 SQL/PLSQL 解析结果、数据字典缓存和库缓存。","tags":["memory","memory","shared","pool"]},{"name":"processes","default":"100 / 300 (depending on version)","recommended":"根据预期并发连接数调高","unit":"integer","dynamic":"restart","desc":"可连接到实例的操作系统进程的最大数量；修改需要重启。","tags":["connection","connection","process","process"]},{"name":"sessions","default":"Derived from processes (1.5x+22)","recommended":"默认推导值通常已足够","unit":"integer","dynamic":"restart","desc":"可同时存在的会话最大数量；根据 processes 值自动推导。","tags":["connection","connection","session","session"]},{"name":"open_cursors","default":"50","recommended":"根据应用需求设为 300-1000","unit":"integer","dynamic":"dynamic","desc":"设置单个会话可同时打开的游标最大数量。","tags":["cursor","cursor","session","session"]},{"name":"cursor_sharing","default":"EXACT","recommended":"EXACT（使用绑定变量）","unit":"EXACT | FORCE","dynamic":"dynamic","desc":"决定包含字面量的 SQL 语句是否通过绑定变量共享；FORCE 会替换字面量。","tags":["cursor","cursor","sql","parse"]},{"name":"optimizer_mode","default":"ALL_ROWS","recommended":"ALL_ROWS","unit":"ALL_ROWS | FIRST_ROWS_n","dynamic":"dynamic","desc":"设置基于成本的优化器的目标：优先考虑总吞吐量还是首行响应时间。","tags":["optimizer","optimize","plan","sql"]},{"name":"undo_retention","default":"900","recommended":"依据最长的查询/flashback 时长","unit":"seconds","dynamic":"dynamic","desc":"设置已提交的 undo 数据在可能情况下的保留时长；影响 ORA-01555 的风险。","tags":["undo","rollback","retention","flashback"]},{"name":"undo_tablespace","default":"UNDOTBS1","recommended":"大小充足的专用 undo 表空间","unit":"string (tablespace name)","dynamic":"dynamic","desc":"设置自动 undo 管理所使用的活动 undo 表空间；可在线修改。","tags":["undo","rollback","tablespace"]},{"name":"db_recovery_file_dest_size","default":"0","recommended":"足以容纳归档日志 + 备份 + flashback 日志","unit":"bytes (K/M/G)","dynamic":"dynamic","desc":"设置快速恢复区（FRA）的最大磁盘大小；用于存放归档日志和备份。","tags":["recovery","recovery","fra","archive"]},{"name":"db_writer_processes","default":"1 or based on CPU count","recommended":"约 CPU/8（写入负载高时调高）","unit":"integer","dynamic":"restart","desc":"设置将脏块写入磁盘的数据库写进程（DBWn）数量；使写入负载并行化。","tags":["io","write","dbwr","writer"]},{"name":"parallel_max_servers","default":"parallel_threads_per_cpu x cpu_count x concurrent_parallel_users (1/2/4) x 5; capped at processes - 15","recommended":"根据工作负载和 CPU 数量设置","unit":"integer","dynamic":"dynamic","desc":"限制实例中可同时运行的并行执行服务器（PX）的最大数量。","tags":["parallel","parallel","px","query"]},{"name":"statistics_level","default":"TYPICAL","recommended":"TYPICAL","unit":"BASIC | TYPICAL | ALL","dynamic":"dynamic","desc":"设置 AWR/ADDM 及自动统计信息收集的级别；BASIC 会禁用许多诊断功能。","tags":["statistics","statistics","awr","monitoring"]},{"name":"deferred_segment_creation","default":"TRUE","recommended":"TRUE（为空表节省空间）","unit":"TRUE | FALSE","dynamic":"dynamic","desc":"将空表/索引首个段的创建推迟到插入第一行时；节省空间。","tags":["segment","deferred-segment","space","storage"]},{"name":"audit_trail","default":"NONE (DB in databases created with DBCA)","recommended":"使用 DB 或统一审计","unit":"NONE | DB | DB,EXTENDED | OS | XML","dynamic":"restart","desc":"决定传统审计记录的写入位置和方式；修改需要重启。","tags":["audit","audit","security","security"]},{"name":"compatible","default":"Installed version (e.g., 19.0.0)","recommended":"在新版本功能稳定后再调高","unit":"version string","dynamic":"restart","desc":"决定启用哪个版本的功能；一旦调高便无法降低，且需要重启。","tags":["compatible","compatibility","version","version"]},{"name":"db_block_size","default":"8192","recommended":"8K（OLTP）；仅在创建时设置","unit":"bytes","dynamic":"restart","desc":"设置数据库的标准块大小；仅在创建数据库时设置，之后无法修改。","tags":["block","block","storage","io"]},{"name":"filesystemio_options","default":"none (platform-dependent)","recommended":"SETALL（Direct + Async I/O）","unit":"none | asynch | directio | setall","dynamic":"restart","desc":"控制文件系统上数据文件的异步 I/O 和直接 I/O 的使用。","tags":["io","io","async","directio"]},{"name":"log_archive_max_processes","default":"4","recommended":"根据归档目标数量调高","unit":"integer","dynamic":"dynamic","desc":"设置归档 redo log 的 ARCn 进程最大数量；调高可减少归档延迟。","tags":["archive","archive","redo","log"]}]},"reference/params-postgres.json":{"engineId":"postgres","engine":"PostgreSQL","order":2,"params":[{"name":"shared_buffers","default":"128MB","recommended":"约为 RAM 的 25%","unit":"bytes (kB/MB/GB)","dynamic":"restart","desc":"PostgreSQL 为缓存数据块分配的共享内存大小；修改需要重启。","tags":["memory","memory","cache","buffer"]},{"name":"effective_cache_size","default":"4GB","recommended":"约为 RAM 的 50-75%","unit":"bytes (kB/MB/GB)","dynamic":"dynamic","desc":"告知规划器操作系统 + PostgreSQL 缓存的预估总大小；影响索引的使用。","tags":["memory","memory","planner","cache"]},{"name":"work_mem","default":"4MB","recommended":"16-64MB（注意连接数）","unit":"bytes (kB/MB/GB)","dynamic":"dynamic","desc":"排序和哈希操作在溢出到磁盘之前可使用的内存；按查询计划节点分配。","tags":["memory","memory","sort","query"]},{"name":"maintenance_work_mem","default":"64MB","recommended":"256MB-1GB","unit":"bytes (kB/MB/GB)","dynamic":"dynamic","desc":"设置 VACUUM、CREATE INDEX、ALTER TABLE 等维护操作可使用的最大内存。","tags":["memory","memory","vacuum","maintenance"]},{"name":"max_connections","default":"100","recommended":"100-200（超出则使用连接池）","unit":"integer","dynamic":"restart","desc":"可接受的并发客户端连接最大数量；修改需要重启。","tags":["connection","connection","limit"]},{"name":"wal_buffers","default":"-1 (1/32 of shared_buffers)","recommended":"16MB","unit":"bytes (kB/MB/GB)","dynamic":"restart","desc":"设置在 WAL 记录写入磁盘之前保存它们的共享内存缓冲区大小。-1（自动）值的范围为最少 64kB、最多一个 WAL 段（通常为 16MB）。","tags":["wal","memory","memory","buffer"]},{"name":"max_wal_size","default":"1GB","recommended":"根据工作负载设为 4-16GB","unit":"bytes (MB/GB)","dynamic":"dynamic","desc":"设置触发自动 checkpoint 之前 WAL 可增长到的软上限。","tags":["wal","checkpoint","disk"]},{"name":"min_wal_size","default":"80MB","recommended":"1-2GB","unit":"bytes (MB/GB)","dynamic":"dynamic","desc":"为循环复用而保留 WAL 文件的下限；减少频繁的重新分配。","tags":["wal","checkpoint","disk"]},{"name":"checkpoint_completion_target","default":"0.9","recommended":"0.9","unit":"ratio (0-1)","dynamic":"dynamic","desc":"设置 checkpoint 写入在 checkpoint 间隔中分摊的比例；平滑 I/O 峰值。PG14 起默认值变为 0.9，之前为 0.5。","tags":["checkpoint","io","wal"]},{"name":"checkpoint_timeout","default":"5min","recommended":"15-30min","unit":"time (s/min)","dynamic":"dynamic","desc":"设置自动 checkpoint 之间的最长时间；调高可降低写入负载，但会延长恢复时间。","tags":["checkpoint","wal","recovery"]},{"name":"random_page_cost","default":"4.0","recommended":"SSD 设为 1.1","unit":"decimal","dynamic":"dynamic","desc":"设置规划器对随机磁盘访问的相对成本；在 SSD 上调低可促使使用索引扫描。","tags":["planner","optimize","io","index"]},{"name":"effective_io_concurrency","default":"1","recommended":"SSD 设为 200","unit":"integer","dynamic":"dynamic","desc":"磁盘可同时处理的并发 I/O 操作的预估数量；影响位图扫描中的预取。PG17 及更早版本默认值为 1，PG18 中为 16。","tags":["io","gc","ssd","prefetch"]},{"name":"default_statistics_target","default":"100","recommended":"100（大表按列调高）","unit":"integer","dynamic":"dynamic","desc":"设置 ANALYZE 收集的统计信息详细程度；调高可提高计划准确性，但会增加 analyze 时间。","tags":["statistics","statistics","analyze","planner"]},{"name":"autovacuum","default":"on","recommended":"on","unit":"on | off","dynamic":"dynamic","desc":"启用自动 vacuum 进程，用于清除死元组并收集统计信息；禁用会导致膨胀。","tags":["autovacuum","vacuum","maintenance","bloat"]},{"name":"autovacuum_vacuum_scale_factor","default":"0.2","recommended":"大表设为 0.05-0.1","unit":"ratio (0-1)","dynamic":"dynamic","desc":"设置表中需变更的行比例，达到后才执行 vacuum；调低可更频繁地清理大表。","tags":["autovacuum","vacuum","bloat","maintenance"]},{"name":"autovacuum_max_workers","default":"3","recommended":"3-6（表很多的系统可调高）","unit":"integer","dynamic":"restart","desc":"设置可同时运行的 autovacuum 工作进程最大数量；PG17 及更早版本修改需要重启。PG18 中可在 autovacuum_worker_slots 限制内通过 reload 修改。","tags":["autovacuum","vacuum","worker","maintenance"]},{"name":"synchronous_commit","default":"on","recommended":"on（关键数据）；off（性能优先）","unit":"on | off | local | remote_write | remote_apply","dynamic":"dynamic","desc":"决定提交是否等待 WAL 写入磁盘；off 在崩溃时存在少量数据丢失的风险。","tags":["wal","commit","durability","performance"]},{"name":"wal_level","default":"replica","recommended":"replica（用于复制/PITR）","unit":"minimal | replica | logical","dynamic":"restart","desc":"决定写入 WAL 的信息量；复制和逻辑复制都需要此设置，修改需要重启。","tags":["wal","replication","replication","pitr"]},{"name":"max_wal_senders","default":"10","recommended":"根据备库数量 + 备份数量","unit":"integer","dynamic":"restart","desc":"设置可并发流式传输 WAL 的进程最大数量（用于备库和备份）。","tags":["wal","replication","replication","standby"]},{"name":"wal_keep_size","default":"0","recommended":"足以覆盖备库延迟","unit":"bytes (MB/GB)","dynamic":"dynamic","desc":"为备库在 pg_wal 中保留的 WAL 最小量；在没有复制槽时保护延迟的备库。PG13 引入；此前使用 wal_keep_segments（段数）。","tags":["wal","replication","replication","standby"]},{"name":"log_min_duration_statement","default":"-1 (disabled)","recommended":"设为 1000ms 以捕获慢查询","unit":"milliseconds","dynamic":"dynamic","desc":"记录运行时间超过指定时长的语句；用于慢查询检测，-1 表示禁用。","tags":["logging","log","slow-query","slow-query"]},{"name":"idle_in_transaction_session_timeout","default":"0 (disabled)","recommended":"设为 60000ms 以防止锁被长期持有","unit":"milliseconds","dynamic":"dynamic","desc":"终止在打开的事务中空闲超过指定时间的会话；降低锁和膨胀的风险。","tags":["timeout","transaction","transaction","lock"]},{"name":"statement_timeout","default":"0 (disabled)","recommended":"取决于应用（OLTP 设低值）","unit":"milliseconds","dynamic":"dynamic","desc":"自动取消超过指定时间的语句；防止失控查询消耗资源。","tags":["timeout","query","query","limit"]},{"name":"shared_preload_libraries","default":"'' (empty)","recommended":"pg_stat_statements（用于监控）","unit":"string (comma-separated list)","dynamic":"restart","desc":"指定服务器启动时加载的共享库；pg_stat_statements 等扩展在此配置。","tags":["extension","extension","preload","monitoring"]},{"name":"max_parallel_workers_per_gather","default":"2","recommended":"2-4（取决于 CPU 数量）","unit":"integer","dynamic":"dynamic","desc":"设置单个 Gather/GatherMerge 节点可启动的并行工作进程最大数量；0 表示禁用并行。","tags":["parallel","parallel","worker","query"]}]},"reference/params-mysql.json":{"engineId":"mysql","engine":"MySQL","order":3,"params":[{"name":"innodb_buffer_pool_size","default":"128MB","recommended":"专用服务器上约为 RAM 的 70-80%","unit":"bytes (M/G)","dynamic":"dynamic","desc":"InnoDB 缓存数据和索引的主内存池；8.0 中可在线修改，但大幅调整需谨慎。","tags":["memory","memory sizing","innodb","buffer"]},{"name":"innodb_buffer_pool_instances","default":"1 (or 8 if size>1GB)","recommended":"大型缓冲池设为 8","unit":"integer","dynamic":"restart","desc":"设置 buffer pool 划分的实例数量；在并发下减少互斥锁争用，修改需要重启。","tags":["memory","memory sizing","innodb","concurrency"]},{"name":"innodb_redo_log_capacity","default":"100MB (8.0.30+)","recommended":"1-8GB（取决于写入负载；约为峰值时 1 小时的 redo）","unit":"bytes (M/G)","dynamic":"dynamic","desc":"设置 InnoDB redo log 占用的总磁盘空间；调高可缓解 checkpoint 压力并平滑峰值写入负载。MySQL 8.0.30 引入，可通过 SET GLOBAL/SET PERSIST 修改而无需重启。旧的 innodb_log_file_size（默认 48MB）和 innodb_log_files_in_group 自 8.0.30 起已弃用；8.0.30 之前通过这两个参数设置 redo 大小，且需要重启。","tags":["innodb","redo","log","io"]},{"name":"innodb_flush_log_at_trx_commit","default":"1","recommended":"1（完整 ACID）；2（性能）","unit":"0 | 1 | 2","dynamic":"dynamic","desc":"控制每次提交时是否将 redo log 写入并同步到磁盘；1 提供完整持久性，2/0 存在丢失风险。","tags":["innodb","commit","durability","acid"]},{"name":"innodb_flush_method","default":"8.0: fsync (Linux) / 8.4: O_DIRECT (if supported); Windows: unbuffered","recommended":"O_DIRECT（Linux）","unit":"fsync | O_DIRECT | O_DIRECT_NO_FSYNC","dynamic":"restart","desc":"定义 InnoDB 将数据文件和日志文件刷新到磁盘的方式；O_DIRECT 可避免双重缓冲，修改需要重启。","tags":["innodb","io","flush","gc"]},{"name":"innodb_io_capacity","default":"8.0: 200 / 8.4: 10000","recommended":"依据存储的实际 IOPS（8.0 上 SSD 设为 1000-2000）","unit":"integer (IOPS)","dynamic":"dynamic","desc":"设置 InnoDB 后台任务（flush、merge）使用的预估磁盘 IOPS 容量。MySQL 8.4 将默认值提高到 innodb_io_capacity=10000 和 innodb_io_capacity_max=20000（8.0 上为 200/2000）。","tags":["innodb","io","iops","ssd"]},{"name":"innodb_read_io_threads","default":"8.0: 4 / 8.4: half the number of logical CPUs (minimum 4)","recommended":"4-8（取决于读取负载）","unit":"integer","dynamic":"restart","desc":"设置处理读 I/O 请求的 InnoDB 后台线程数量；修改需要重启。","tags":["innodb","io","thread","read"]},{"name":"innodb_write_io_threads","default":"4","recommended":"4-8（取决于写入负载）","unit":"integer","dynamic":"restart","desc":"设置处理写 I/O 请求的 InnoDB 后台线程数量；修改需要重启。","tags":["innodb","io","thread","write"]},{"name":"max_connections","default":"151","recommended":"根据工作负载设为 200-500","unit":"integer","dynamic":"dynamic","desc":"设置允许的同时客户端连接最大数量；可在线修改。","tags":["connection","connections","limit"]},{"name":"thread_cache_size","default":"-1 (autosized)","recommended":"自动；短连接很多时调高","unit":"integer","dynamic":"dynamic","desc":"设置缓存中保留以供复用的线程数量；减少为新连接创建线程。","tags":["connection","connections","thread","cache"]},{"name":"table_open_cache","default":"4000","recommended":"根据打开的表数量设为 4000-8000","unit":"integer","dynamic":"dynamic","desc":"设置所有线程共用的已打开表句柄缓存大小；减少重新打开表的开销。","tags":["cache","table","table cache","open"]},{"name":"tmp_table_size","default":"16MB","recommended":"64-256MB","unit":"bytes (M/G)","dynamic":"dynamic","desc":"设置内存临时表在转换为磁盘表之前的最大大小；与 max_heap_table_size 共同生效。","tags":["memory","memory sizing","temp","temporary table"]},{"name":"max_heap_table_size","default":"16MB","recommended":"与 tmp_table_size 相同","unit":"bytes (M/G)","dynamic":"dynamic","desc":"设置 MEMORY 引擎表和内存临时表可增长到的最大大小。","tags":["memory","memory sizing","temp","heap"]},{"name":"sort_buffer_size","default":"256KB","recommended":"256KB-2MB（注意每连接成本）","unit":"bytes (K/M)","dynamic":"dynamic","desc":"为 ORDER BY 和 GROUP BY 排序按会话分配的缓冲区；设置过大会消耗内存。","tags":["memory","memory sizing","sort","buffer"]},{"name":"join_buffer_size","default":"256KB","recommended":"256KB-1MB（优先通过索引解决）","unit":"bytes (K/M)","dynamic":"dynamic","desc":"为无法使用索引的连接按会话分配的缓冲区；根本解决办法是添加合适的索引。","tags":["memory","memory sizing","join","buffer"]},{"name":"sync_binlog","default":"1","recommended":"1（完全安全）；高负载下 100-1000","unit":"integer","dynamic":"dynamic","desc":"设置每多少次提交将 binary log 同步到磁盘；1 最安全，较高的值以持久性换取性能。","tags":["binlog","replication","durability","commit"]},{"name":"binlog_expire_logs_seconds","default":"2592000 (30 days)","recommended":"依据备份/复制窗口","unit":"seconds","dynamic":"dynamic","desc":"设置 binary log 文件在自动清除前的保留时长；控制磁盘使用量。","tags":["binlog","log","retention","replication"]},{"name":"long_query_time","default":"10","recommended":"1（捕获慢查询）","unit":"seconds","dynamic":"dynamic","desc":"设置查询被记录到慢查询日志所需超过的时长；接受小数值。","tags":["slow-query","slow-query-log","log","performance"]},{"name":"slow_query_log","default":"OFF","recommended":"ON（用于诊断）","unit":"ON | OFF","dynamic":"dynamic","desc":"控制是否将超过 long_query_time 的查询写入慢查询日志；进行性能分析时应启用。","tags":["slow-query","slow-query-log","log","monitoring"]},{"name":"wait_timeout","default":"28800 (8 hours)","recommended":"依据应用连接池（例如 600）","unit":"seconds","dynamic":"dynamic","desc":"设置非交互式连接在被关闭前可保持空闲的最长时间；清理空闲连接。","tags":["timeout","connection","connections","idle"]},{"name":"max_allowed_packet","default":"64MB","recommended":"大型 BLOB/行设为 64-256MB","unit":"bytes (M/G)","dynamic":"dynamic","desc":"设置单个数据包或任意生成/中间行的最大大小；传输大型 BLOB 时应调高。","tags":["packet","packet size","blob","network"]},{"name":"innodb_lock_wait_timeout","default":"50","recommended":"OLTP 设为 10-50","unit":"seconds","dynamic":"dynamic","desc":"设置 InnoDB 事务等待行锁的超时时长。","tags":["innodb","lock","lock wait","timeout"]},{"name":"character_set_server","default":"utf8mb4","recommended":"utf8mb4","unit":"text (character set)","dynamic":"dynamic","desc":"设置服务器的默认字符集；utf8mb4 提供完整的 Unicode 支持（包括 emoji）。","tags":["charset","character-set","unicode","utf8mb4"]}]},"reference/params-mssql.json":{"engineId":"mssql","engine":"SQL Server","order":4,"params":[{"name":"max server memory (MB)","default":"2147483647","recommended":"总 RAM 减去为操作系统和其他服务预留的部分（约 10-25%）","unit":"MB","dynamic":"dynamic","desc":"设置 SQL Server buffer pool 及其他内存使用者可使用的最大内存量。默认值实际上不受限制；如果服务器上运行其他服务，必须调低以为操作系统留出足够内存。通过 sp_configure 设置；执行 RECONFIGURE 后立即生效。","tags":["memory","memory limit","buffer pool"]},{"name":"min server memory (MB)","default":"0","recommended":"在关键实例上，设为相对于 max 的合理下限","unit":"MB","dynamic":"dynamic","desc":"SQL Server 一旦分配便不会释放回操作系统的最小内存量。用于防止 SQL Server 在内存压力下释放过多内存。通过 sp_configure 设置；执行 RECONFIGURE 后生效。","tags":["memory","memory limit"]},{"name":"max degree of parallelism","default":"0","recommended":"通常为 8 或每个 NUMA 节点的物理核心数","unit":"count","dynamic":"dynamic","desc":"限制单个查询在并行执行中可使用的最大处理器（核心）数量。0 表示可使用所有核心；在 OLTP 工作负载中加以限制以避免过度并行。通过 sp_configure + RECONFIGURE 立即生效。","tags":["parallelism","parallel query","maxdop","cpu"]},{"name":"cost threshold for parallelism","default":"5","recommended":"现代硬件上设为 25-50","unit":"cost","dynamic":"dynamic","desc":"查询计划并行执行所需达到的最低预估成本阈值。默认值 5 非常低，会导致小查询被不必要地并行化；调高可改善整个服务器的并行行为。通过 sp_configure + RECONFIGURE 立即生效。","tags":["parallelism","parallel query","cpu"]},{"name":"max worker threads","default":"0","recommended":"0（由 SQL Server 计算）","unit":"count","dynamic":"dynamic","desc":"设置 SQL Server 为处理客户端请求可创建的最大工作线程数。0 表示根据处理器数量和架构自动计算。这是高级选项；通过 sp_configure + RECONFIGURE 应用，无需重启（sys.configurations.is_dynamic = 1）。","tags":["threads","worker","cpu","concurrency"]},{"name":"optimize for ad hoc workloads","default":"0","recommended":"1（开启）","unit":"0/1","dynamic":"dynamic","desc":"对于只运行一次的即席查询，首次执行时缓存一个较小的已编译计划存根，而非完整计划。防止计划缓存被一次性计划填满，改善内存使用。通过 sp_configure + RECONFIGURE 立即生效。","tags":["plan cache","memory","memory usage"]},{"name":"backup compression default","default":"0","recommended":"1（开启）","unit":"0/1","dynamic":"dynamic","desc":"使新备份默认压缩。压缩以一定的 CPU 开销显著减小备份大小并缩短备份时间。通过 sp_configure + RECONFIGURE 立即生效。","tags":["backup","backup size","compression","backup compression"]},{"name":"fill factor (%)","default":"0","recommended":"0 或 90-100（取决于工作负载）","unit":"%","dynamic":"restart","desc":"以百分比设置创建或重建（REBUILD）索引时叶级页的填充程度。0 和 100 表示完全填满页；较低的值可减少页拆分，但会增加磁盘和内存使用。服务器级默认值通过 sp_configure 修改，但在服务重启前不生效（is_dynamic = 0）；可立即使用索引级 FILLFACTOR 选项。","tags":["index","page split","storage","disk usage"]},{"name":"recovery interval (min)","default":"0","recommended":"0（自动）","unit":"minutes","dynamic":"dynamic","desc":"以目标最长恢复时间设置自动 checkpoint 的频率。0 表示由 SQL Server 按数据库自动管理目标恢复时间。通过 sp_configure + RECONFIGURE 立即生效。","tags":["checkpoint","recovery","crash recovery"]},{"name":"remote query timeout (s)","default":"600","recommended":"600 或 0（无限制），取决于工作负载","unit":"seconds","dynamic":"dynamic","desc":"服务器在超时前等待远程查询（通过链接服务器）响应的时间。0 表示无限期等待。通过 sp_configure + RECONFIGURE 立即生效。","tags":["timeout","remote","linked server"]},{"name":"network packet size (B)","default":"4096","recommended":"4096（除特殊情况外不要修改）","unit":"bytes","dynamic":"dynamic","desc":"通过网络传输数据时使用的数据包大小。除传输超大数据的特殊批量操作外，建议使用默认值；错误的设置会降低性能。通过 sp_configure + RECONFIGURE 生效。","tags":["network","network packet","packet"]},{"name":"数据库兼容级别（ALTER DATABASE）","default":"Depends on server version","recommended":"最新的已测试级别","unit":"level","dynamic":"dynamic","desc":"决定数据库以哪个 SQL Server 版本的行为和查询优化器模型运行。通过 ALTER DATABASE ... SET COMPATIBILITY_LEVEL 按数据库设置并立即生效；影响基数估计等优化器功能。","tags":["database","compatibility level","optimizer","compatibility"]},{"name":"MAXDOP（数据库范围的配置）","default":"0 (inherits the server setting)","recommended":"适合数据库工作负载的值","unit":"count","dynamic":"dynamic","desc":"在数据库级别覆盖服务器范围的 max degree of parallelism 设置。通过 ALTER DATABASE SCOPED CONFIGURATION SET MAXDOP 设置并立即生效；允许不同数据库采用不同的并行行为。","tags":["parallelism","parallel query","maxdop","database scoped"]},{"name":"tempdb 文件数量（建议）","default":"SQL Server 2016+ setup: equal to the number of logical cores, at most 8 (1 before)","recommended":"等于逻辑核心数，最多 8 个（必要时以 4 为步长增加）","unit":"count","dynamic":"dynamic","desc":"tempdb 数据文件的数量对于减少分配页争用很重要。一般建议使用大小相等的文件，数量最多等于核心数（最多 8 个）。通过 ALTER DATABASE tempdb ADD FILE 添加的文件无需重启即可立即使用；但如果现有文件已满，应将所有文件调整为相同大小以保持按比例填充的平衡。删除 tempdb 文件（REMOVE FILE）通常只能在重启后进行，因为文件无法清空。","tags":["tempdb","storage","data files","contention"]},{"name":"Trace Flag 1117 / 1118（说明）","default":"Default behavior for tempdb in SQL 2016+","recommended":"SQL 2016+ 中通常不需要；旧版本中开启","unit":"flag","dynamic":"restart","desc":"TF 1117 强制文件组中的所有文件同时增长；TF 1118 强制使用统一区分配而非混合区。自 SQL Server 2016 起，这些行为已是 tempdb 的默认行为，因此通常不需要这些标志。作为启动标志添加时需要重启。","tags":["trace flag","tempdb","note"]},{"name":"Trace Flag 3226","default":"Off","recommended":"开启（屏蔽备份成功消息）","unit":"flag","dynamic":"dynamic","desc":"阻止将成功的备份操作写入 SQL Server 错误日志和 Windows 事件日志。在日志备份频繁的系统上，可防止日志被不必要的消息填满（日志刷屏）。可在运行时通过 DBCC TRACEON(3226, -1) 立即启用；若要在重启后保持，还需将 -T3226 添加为启动参数。","tags":["trace flag","backup","backup messages","log"]},{"name":"lock pages in memory（Windows 策略说明）","default":"Off","recommended":"在特定场景下授予服务账户","unit":"policy","dynamic":"restart","desc":"一项 Windows 用户权限策略，允许 SQL Server 服务账户防止其内存页被操作系统分页到磁盘。可减少内存压力下的性能波动，但必须谨慎配置。修改该策略需要重启服务。","tags":["memory","memory paging","windows","policy","note"]}]},"reference/params-mongodb.json":{"engineId":"mongodb","engine":"MongoDB","order":5,"params":[{"name":"storage.wiredTiger.engineConfig.cacheSizeGB","default":"(RAM - 1GB) * 0.5","recommended":"保持默认值或约为可用 RAM 的 50%","unit":"GB","dynamic":"restart","desc":"以 GB 为单位设置分配给 WiredTiger 存储引擎内部缓存的内存量。默认使用（总 RAM - 1GB）的一半。仅在特殊情况下才需要手动设置该值，且需要重启 mongod。","tags":["memory","memory","cache","wiredtiger"]},{"name":"net.maxIncomingConnections","default":"1000000 (effectively limited by OS limits)","recommended":"根据工作负载和操作系统 ulimit 设置","unit":"count","dynamic":"restart","desc":"限制 mongod 或 mongos 实例可同时接受的最大传入连接数。若该值高于操作系统的文件描述符/连接限制 (ulimit)，则不起作用；实际上限由操作系统决定（可在 db.serverStatus().connections 中以 current + available 查看）。需在配置文件中修改，并需要重启。","tags":["network","network","connections","connection"]},{"name":"operationProfiling.slowOpThresholdMs","default":"100","recommended":"100 或更低，取决于工作负载","unit":"ms","dynamic":"dynamic","desc":"以毫秒为单位设置操作被视为慢操作并被 profiler 和日志记录的时长阈值。超过该阈值的操作会被记录，便于诊断。可在运行时通过 db.setProfilingLevel() 立即修改。","tags":["profiling","slow query","slow query","monitoring"]},{"name":"replication.oplogSizeMB","default":"~5% of disk size (max 50GB)","recommended":"足够覆盖维护窗口","unit":"MB","dynamic":"dynamic","desc":"以 MB 为单位设置副本集成员上存储操作的 oplog 集合的最大大小。足够大的 oplog 可让落后的成员追上进度而无需完全重新同步。可在运行中的副本集上通过 replSetResizeOplog 命令立即修改。","tags":["replication","oplog","replica set"]},{"name":"security.authorization","default":"disabled","recommended":"enabled（基于角色的访问控制）","unit":"enabled/disabled","dynamic":"restart","desc":"启用基于角色的访问控制 (RBAC)；启用后，客户端必须经过身份验证和授权。生产环境中必须始终启用。在配置文件中设置，需要重启。","tags":["security","security","authorization","authorization","rbac"]},{"name":"net.compression.compressors","default":"snappy,zstd,zlib","recommended":"snappy,zstd（默认值即可）","unit":"list","dynamic":"restart","desc":"设置客户端与集群内部网络流量可使用的压缩算法及其优先顺序。以一定的 CPU 开销降低网络带宽占用。在配置文件中设置，需要重启。","tags":["network","network","compression","compression"]},{"name":"storage.wiredTiger.collectionConfig.blockCompressor","default":"snappy","recommended":"snappy（速度）或 zstd（高压缩率）","unit":"algorithm","dynamic":"restart","desc":"设置 WiredTiger 将集合数据写入磁盘时使用的块压缩算法。snappy 侧重速度，zlib 和 zstd 提供更高的压缩率。仅对设置之后创建的集合生效，需要重启。","tags":["storage","storage","compression","compression","wiredtiger"]},{"name":"storage.directoryPerDB","default":"false","recommended":"需要分离磁盘时设为 true","unit":"true/false","dynamic":"restart","desc":"将每个数据库的数据存放在独立的子目录中，从而可以把不同数据库放在不同的磁盘卷上。只能在空的数据目录上启用。需在配置文件中修改，并需要重启。","tags":["storage","storage","directory"]},{"name":"setParameter.cursorTimeoutMillis","default":"600000","recommended":"默认值（10 分钟），视工作负载而定","unit":"ms","dynamic":"dynamic","desc":"以毫秒为单位设置空闲游标在被服务器自动关闭前的等待时间。防止被遗弃的游标占用资源。可在运行时通过 db.adminCommand({setParameter:1}) 立即修改。","tags":["cursor","timeout","setparameter"]},{"name":"setParameter.transactionLifetimeLimitSeconds","default":"60","recommended":"除非需要长事务，否则使用默认值","unit":"seconds","dynamic":"dynamic","desc":"以秒为单位设置多文档事务在被自动中止前可保持打开的最长时间。防止长时间运行的事务占用资源和 WiredTiger cache。可通过 setParameter 立即修改。","tags":["transaction","transaction","setparameter"]},{"name":"setParameter.maxTransactionLockRequestTimeoutMillis","default":"5","recommended":"默认值，视锁争用情况而定","unit":"ms","dynamic":"dynamic","desc":"以毫秒为单位设置事务在超时前等待获取所需锁的最长时间。调整锁等待行为可防止事务被长时间阻塞。可通过 setParameter 立即修改。","tags":["transaction","lock","lock","setparameter"]},{"name":"net.tls.mode","default":"disabled","recommended":"requireTLS（生产环境）","unit":"mode","dynamic":"restart","desc":"决定服务器对 TLS/SSL 加密连接的使用方式。requireTLS 强制所有连接加密；preferTLS 和 allowTLS 为过渡期提供中间模式。需与证书配置配合使用，需要重启。","tags":["security","security","tls","ssl","encryption"]},{"name":"storage.syncPeriodSecs","default":"60","recommended":"60（不要修改默认值）","unit":"seconds","dynamic":"dynamic","desc":"以秒为单位设置 WiredTiger 执行 checkpoint 的频率，即内存数据刷写到磁盘的间隔。默认值适合大多数工作负载，一般不建议修改。在配置文件中设置；运行时也可通过 db.adminCommand({ setParameter: 1, syncdelay: <seconds> }) 无需重启即可修改。","tags":["storage","storage","checkpoint","sync"]},{"name":"replication.enableMajorityReadConcern","default":"true","recommended":"true（5.0+ 中不可更改）","unit":"true/false","dynamic":"restart","desc":"允许使用 majority 读关注级别；该级别保证读取的数据已写入多数成员且不会被回滚。自 MongoDB 5.0 起该设置不可更改且始终为 true；在旧版本中（例如 PSA 架构）可设为 false。在配置文件中设置，需要重启。","tags":["replication","read concern","replica set"]},{"name":"processManagement.fork","default":"false","recommended":"使用 systemd 时为 false，手动运行守护进程时为 true","unit":"true/false","dynamic":"restart","desc":"使 mongod 进程 fork 并作为守护进程在后台运行。使用 systemd 或类似的服务管理器时通常保持 false，因为进程管理由服务管理器负责。在配置文件中设置，影响启动行为。","tags":["process","process","daemon","fork"]}]},"reference/params-redis.json":{"engineId":"redis","engine":"Redis","order":6,"params":[{"name":"maxmemory","default":"0 (unlimited)","recommended":"物理内存的约 70-75%","unit":"bytes","dynamic":"dynamic","desc":"设置 Redis 可用于存储数据的最大内存量。值为 0 表示不限制，可能导致内存耗尽；强烈建议设置上限。可通过 CONFIG SET maxmemory 立即修改。","tags":["memory","memory"]},{"name":"maxmemory-policy","default":"noeviction","recommended":"用作缓存时为 allkeys-lru，否则为 noeviction","unit":"policy","dynamic":"dynamic","desc":"达到 maxmemory 限制时决定 Redis 淘汰哪些键以及如何淘汰的策略。noeviction 返回写入错误，allkeys-lru 删除最近最少使用的键。可通过 CONFIG SET 立即修改。","tags":["memory","memory","eviction","cache"]},{"name":"maxclients","default":"10000","recommended":"根据工作负载和操作系统 ulimit 设置","unit":"count","dynamic":"dynamic","desc":"设置可接受的最大并发客户端连接数。操作系统的文件描述符限制可能会限制该值。可通过 CONFIG SET maxclients 立即修改。","tags":["connections","connection","clients"]},{"name":"appendonly","default":"no","recommended":"需要持久化时设为 yes","unit":"yes/no","dynamic":"dynamic","desc":"启用 AOF (Append Only File) 持久化机制；启用后，每个写操作都会追加到日志文件中。数据丢失风险低于 RDB 快照。可通过 CONFIG SET appendonly yes 立即启用。","tags":["persistence","persistence","aof"]},{"name":"appendfsync","default":"everysec","recommended":"everysec（均衡）","unit":"policy","dynamic":"dynamic","desc":"设置 AOF 文件同步到磁盘 (fsync) 的频率。always 每次写入都同步，最安全但较慢；everysec 每秒同步一次，在性能与持久性之间取得平衡；no 将同步交给操作系统。可通过 CONFIG SET 立即修改。","tags":["persistence","persistence","aof","fsync"]},{"name":"save","default":"3600 1 300 100 60 10000","recommended":"视工作负载而定，或禁用（仅使用 AOF）","unit":"rule","dynamic":"dynamic","desc":"定义将 RDB 快照保存到磁盘的条件（在给定时间内发生给定次数的变更）。设置为空值将完全禁用 RDB 快照。可通过 CONFIG SET save 立即修改。","tags":["persistence","persistence","rdb","snapshot"]},{"name":"tcp-keepalive","default":"300","recommended":"300（默认值即可）","unit":"seconds","dynamic":"dynamic","desc":"以秒为单位设置 Redis 向客户端连接发送 TCP keepalive ACK 的间隔。确保检测并关闭失效连接，清理半开连接。可通过 CONFIG SET 立即修改。","tags":["network","network","tcp","keepalive"]},{"name":"timeout","default":"0","recommended":"0（从不关闭连接）或合理的空闲时间","unit":"seconds","dynamic":"dynamic","desc":"以秒为单位设置客户端连接可保持空闲的最长时间；超过后连接将被关闭。值为 0 表示客户端可无限期空闲。可通过 CONFIG SET timeout 立即修改。","tags":["connections","connection","timeout","idle"]},{"name":"hz","default":"10","recommended":"10（繁忙系统上最高可设为 100）","unit":"Hz","dynamic":"dynamic","desc":"设置 Redis 每秒运行后台任务（过期键处理、检查客户端超时等）的次数。较高的值控制更精确，但会增加 CPU 使用率。可通过 CONFIG SET hz 立即修改。","tags":["performance","cpu","background"]},{"name":"activedefrag","default":"no","recommended":"内存碎片率高时设为 yes","unit":"yes/no","dynamic":"dynamic","desc":"启用主动内存碎片整理；在运行期间重新组织数据以减少内存碎片。适用于碎片率高且长时间运行的实例。可通过 CONFIG SET activedefrag yes 立即启用。","tags":["memory","memory","defragmentation"]},{"name":"maxmemory-samples","default":"5","recommended":"5（为更精确的淘汰可调高）","unit":"count","dynamic":"dynamic","desc":"设置 LRU 和 LFU 淘汰算法在选择键时采样的键数量。较高的值更接近真实的 LRU/LFU 行为，但会多消耗一些 CPU。可通过 CONFIG SET 立即修改。","tags":["memory","memory","eviction","lru"]},{"name":"io-threads","default":"1","recommended":"多核服务器上设为 2-4（取决于核数）","unit":"count","dynamic":"restart","desc":"设置用于读写客户端套接字的 I/O 线程数。在网络流量大的多核系统上可提升吞吐量。该设置只能在配置文件中修改，需要重启。","tags":["performance","threads","io","cpu"]},{"name":"repl-backlog-size","default":"1mb","recommended":"根据写入速率和中断时长适当调大","unit":"bytes","dynamic":"dynamic","desc":"设置主节点上为副本连接断开后进行部分重新同步而保留的复制缓冲区大小。足够大的缓冲区可让副本在短暂中断后无需完全同步即可追上进度。可通过 CONFIG SET 立即修改。","tags":["replication","replication","backlog"]},{"name":"repl-diskless-sync","default":"yes","recommended":"yes（高速网络上）","unit":"yes/no","dynamic":"dynamic","desc":"在完全同步期间，不写入磁盘，直接通过套接字将 RDB 数据发送给副本。可在存在磁盘 I/O 瓶颈的系统上加快同步。可通过 CONFIG SET repl-diskless-sync 立即修改。","tags":["replication","replication","diskless","sync"]},{"name":"requirepass","default":"(empty)","recommended":"设置强密码","unit":"password","dynamic":"dynamic","desc":"设置客户端执行命令前必须通过 AUTH 进行身份验证的密码。必须设置强密码以防止对 Redis 的未授权访问。可通过 CONFIG SET requirepass 立即修改。","tags":["security","security","authentication","password"]},{"name":"cluster-enabled","default":"no","recommended":"需要水平扩展时设为 yes","unit":"yes/no","dynamic":"restart","desc":"启用 Redis Cluster 模式；启用后，数据通过哈希槽分布到多个节点。用于水平扩展和自动数据分区。该设置只能在配置文件中修改，需要重启。","tags":["cluster","cluster","scaling","scaling"]},{"name":"cluster-node-timeout","default":"15000","recommended":"15000 或根据网络延迟调整的值","unit":"ms","dynamic":"dynamic","desc":"在集群模式下，以毫秒为单位设置节点无响应多长时间后被视为不可达 (fail)。直接影响故障转移行为和集群稳定性。可通过 CONFIG SET 立即修改。","tags":["cluster","cluster","timeout","failover"]},{"name":"aof-use-rdb-preamble","default":"yes","recommended":"yes（快速重新加载）","unit":"yes/no","dynamic":"dynamic","desc":"启用混合持久化格式：AOF 重写时在文件开头写入紧凑的 RDB 快照，其后为增量 AOF 命令。可缩短重启时的 AOF 加载时间。可通过 CONFIG SET 立即修改。","tags":["persistence","persistence","aof","rdb"]}]},"reference/params-elasticsearch.json":{"engineId":"elasticsearch","engine":"Elasticsearch","order":7,"params":[{"name":"-Xms / -Xmx（JVM 堆）","default":"Automatic depending on version (half of RAM)","recommended":"物理内存的 50%，最多约 30-31GB (Xms=Xmx)","unit":"GB","dynamic":"restart","desc":"设置 JVM 堆的初始大小 (-Xms) 和最大大小 (-Xmx)；两者应设为相同。建议不超过总内存的一半，也不超过压缩对象指针失效的约 32GB 界限。在 jvm.options 中设置，需要重启节点。","tags":["memory","memory","jvm","heap"]},{"name":"indices.memory.index_buffer_size","default":"10%","recommended":"10%（索引负载高时可调大）","unit":"% / bytes","dynamic":"restart","desc":"设置分配给索引期间存放新文档的内存缓冲区的堆占比。增大该缓冲区可提升写入密集型工作负载的索引吞吐量。在 elasticsearch.yml 中按节点设置，需要重启。","tags":["memory","memory","indexing","buffer"]},{"name":"thread_pool.write.queue_size","default":"10000","recommended":"10000（出现队列拒绝时谨慎调大）","unit":"count","dynamic":"restart","desc":"设置 index、delete 和 bulk 写操作线程池的队列长度。队列满时请求会被拒绝。值调得过高可能造成内存压力。在 elasticsearch.yml 中设置，需要重启。","tags":["thread pool","write","queue","queue"]},{"name":"thread_pool.search.queue_size","default":"1000","recommended":"1000（出现搜索拒绝时谨慎调大）","unit":"count","dynamic":"restart","desc":"设置搜索操作线程池的队列长度。并发搜索超过线程池容量时，请求在该队列中等待，队列满时则被拒绝。在 elasticsearch.yml 中设置，需要重启。","tags":["thread pool","search","search","queue"]},{"name":"indices.fielddata.cache.size","default":"unbounded","recommended":"设置 20-40% 之类的上限（用于内存保护）","unit":"% / bytes","dynamic":"restart","desc":"设置用于 text 字段排序和聚合的 fielddata 缓存的最大大小。由于默认不设上限，建议设置限制以防内存耗尽。在 elasticsearch.yml 中设置，需要重启。","tags":["memory","memory","fielddata","cache"]},{"name":"index.number_of_shards","default":"1","recommended":"根据数据量设置；每个分片目标 10-50GB","unit":"count","dynamic":"restart","desc":"设置索引拆分成的主分片数量。这是静态索引设置（不是需要重启，而是只能在创建索引时指定）：无法在现有索引上直接修改；需通过新建索引 + _reindex 生成新索引，或使用 _split（倍增）/ _shrink（缩减为约数）API 来更改。分片过多会增加集群开销，过少则限制扩展能力。","tags":["index","index","shard","scaling"]},{"name":"index.number_of_replicas","default":"1","recommended":"生产环境至少为 1（用于高可用）","unit":"count","dynamic":"dynamic","desc":"设置每个主分片保留的副本分片数量。副本同时提供高可用和读取性能。可通过 _settings API 在在线索引上即时修改。","tags":["index","index","replica","availability"]},{"name":"index.refresh_interval","default":"1s","recommended":"高索引负载下设为 30s 或 -1（临时）","unit":"duration","dynamic":"dynamic","desc":"设置 refresh 操作的执行频率，refresh 使新索引的文档对搜索可见。调大该值，或在批量加载期间设为 -1，可显著提升索引吞吐量。可通过 _settings API 即时修改。","tags":["index","index","refresh","indexing"]},{"name":"indices.breaker.total.limit","default":"95%","recommended":"95%（默认值；谨慎修改）","unit":"%","dynamic":"dynamic","desc":"通过设置所有 circuit breaker 合计可使用的最大堆占比，防止节点因 OutOfMemory 崩溃。可通过 _cluster/settings API 在集群范围内即时修改。","tags":["memory","memory","circuit breaker","stability"]},{"name":"cluster.routing.allocation.disk.watermark.low","default":"85%","recommended":"85%（默认值即可）","unit":"% / bytes","dynamic":"dynamic","desc":"节点磁盘使用率超过该阈值时，Elasticsearch 停止向该节点分配新分片（现有分片保留）。这是防止磁盘写满的第一道警告级别。可通过 _cluster/settings API 即时修改。","tags":["disk","allocation","watermark","storage"]},{"name":"cluster.routing.allocation.disk.watermark.high","default":"90%","recommended":"90%（默认值即可）","unit":"% / bytes","dynamic":"dynamic","desc":"节点磁盘使用率超过该阈值时，Elasticsearch 开始将分片从该节点迁移到其他节点。用于缓解磁盘即将写满的节点。可通过 _cluster/settings API 即时修改。","tags":["disk","allocation","watermark","storage"]},{"name":"cluster.routing.allocation.disk.watermark.flood_stage","default":"95%","recommended":"95%（默认值即可）","unit":"% / bytes","dynamic":"dynamic","desc":"磁盘使用率超过该临界阈值时，在受影响节点上有分片的索引会被置为只读 (read-only-allow-delete) 模式并阻止写入。可在磁盘完全写满前防止数据损坏；在 7.4+ 中，磁盘使用率降至 high watermark 以下后会自动解除阻止。在 8.x 中，对于大容量磁盘，起决定作用的是 max_headroom（默认 100GB 可用空间）而非百分比。可通过 _cluster/settings API 即时修改。","tags":["disk","allocation","watermark","read-only"]},{"name":"index.translog.durability","default":"request","recommended":"request（持久性）或 async（性能）","unit":"mode","dynamic":"dynamic","desc":"设置事务日志 (translog) 何时同步 (fsync) 到磁盘。request 在每个请求后同步，将数据丢失降至最低；async 定期同步，可提升性能，但崩溃时存在数据丢失风险。可通过 _settings API 即时修改。","tags":["translog","durability","durability","indexing"]},{"name":"indices.recovery.max_bytes_per_sec","default":"40mb","recommended":"可根据网络和磁盘容量调大","unit":"bytes/s","dynamic":"dynamic","desc":"设置分片恢复期间每个节点的最大数据传输速率。调大该值可加快恢复，但可能占用正常工作负载的网络和磁盘资源。可通过 _cluster/settings API 即时修改。","tags":["recovery","recovery","throttle","network"]},{"name":"action.destructive_requires_name","default":"true","recommended":"true（阻止破坏性的通配符操作）","unit":"true/false","dynamic":"dynamic","desc":"阻止使用通配符 (*) 或 _all 删除索引等破坏性操作，要求明确指定索引名称。这是防止误删所有索引的重要安全设置。可通过 _cluster/settings API 即时修改。","tags":["security","security","destructive","safety"]},{"name":"discovery.seed_hosts","default":"(empty / 127.0.0.1)","recommended":"集群中 master-eligible 节点的地址","unit":"list","dynamic":"restart","desc":"设置节点加入集群时联系的种子主机（master-eligible 节点）地址列表。集群发现以及节点相互查找所必需。在 elasticsearch.yml 中设置，需要重启。","tags":["discovery","cluster","cluster","network"]}]},"reference/waits-oracle.json":{"engineId":"oracle","engine":"Oracle","order":1,"waits":[{"name":"log file sync","kind":"wait","meaning":"会话已发出 COMMIT（或 ROLLBACK），正在等待 LGWR 进程将 redo 记录写入联机 redo log 文件并确认。","causes":"redo log I/O 缓慢、非常频繁的小提交、LGWR 进程过载、CPU 资源不足导致 LGWR 延迟、redo log 位于慢速磁盘上。","action":"降低提交频率（批量提交），将 redo log 文件迁移到高速/低延迟磁盘 (SSD)，删除不必要的提交，仅对可容忍数据丢失的工作考虑 COMMIT NOWAIT/BATCH（警告：这会降低持久性；看似已提交的事务可能在崩溃时丢失），并测量 log writer 的 I/O 延迟 (log file parallel write)。","tags":["commit","redo","io","lgwr"]},{"name":"db file sequential read","kind":"wait","meaning":"会话正在执行单块物理读；正在等待将块从磁盘读入 buffer cache，通常发生在索引访问或通过 ROWID 读取表块时。","causes":"索引扫描、低效的访问计划、buffer cache 过小、磁盘子系统缓慢、物理读过高导致块不在缓存中。","action":"调优 SQL 以减少不必要的读取，添加合适的索引，增大 buffer cache，改善 I/O 子系统，并通过 AWR/ASH 找出读取量最大的查询。","tags":["io","index","single-block","read"]},{"name":"db file scattered read","kind":"wait","meaning":"会话正在执行多块物理读；正在等待从磁盘读取多个块，通常发生在全表扫描或索引快速全扫描期间。","causes":"全表扫描、缺少索引或索引未被使用、低效的执行计划、大表扫描、优化器统计信息不正确。","action":"通过添加索引避免不必要的全扫描，检查执行计划，刷新统计信息，检查 db_file_multiblock_read_count 设置，并考虑对确实需要的全扫描进行并行化。","tags":["io","full-scan","multi-block","read"]},{"name":"direct path read","kind":"wait","meaning":"会话绕过 buffer cache 将块直接读入 PGA；通常出现在并行查询、大表扫描或 LOB 读取期间。","causes":"并行全表扫描、串行 direct path read 决策 (adaptive direct read)、大表扫描、临时表空间 (temp) 读取、LOB 访问。","action":"通过查询调优减少对大规模扫描的需求，评估 PGA 大小，temp 使用率高时优化排序/哈希操作，并提升 I/O 带宽。","tags":["io","direct-path","parallel","read"]},{"name":"direct path write","kind":"wait","meaning":"会话不经过 buffer cache 将块直接写入磁盘；通常出现在 direct-path insert、排序溢出或 LOB 写入期间。","causes":"direct-path INSERT (APPEND) 操作、排序/哈希溢出到临时表空间、并行 DML、LOB 写入、temp 或数据磁盘缓慢。","action":"为排序/哈希操作增大 PGA 以减少 temp 溢出，将 temp 表空间放在高速磁盘上，检查不必要的 direct-path 加载，并提升 I/O 性能。","tags":["io","direct-path","temp","write"]},{"name":"buffer busy waits","kind":"wait","meaning":"会话要访问 buffer cache 中的某个块，但该块正被另一个会话以不兼容的模式使用，因此等待其释放。","causes":"对同一块的大量并发访问（热块）、freelists/ASSM 不足、顺序索引右侧块争用 (right-hand index growth)、DML 密集度高的段。","action":"分散热块（哈希分区、反向键索引），使用 ASSM，增加 freelists/freelist groups，并在生成顺序键的表上改进 sequence 缓存和分布。","tags":["concurrency","buffer","hot-block","contention"]},{"name":"read by other session","kind":"wait","meaning":"另一个会话已在从磁盘读取本会话所需的块；本会话等待对同一块的该读取完成。","causes":"对相同热块的并发物理读请求、buffer cache 过小、I/O 缓慢、大量会话同时访问常用的索引或表块。","action":"增大 buffer cache 以减少物理读，识别热段并改进其访问计划，降低 I/O 延迟，消除不必要的全扫描。","tags":["io","concurrency","read","hot-block"]},{"name":"latch: cache buffers chains","kind":"wait","meaning":"会话正在等待获取保护 buffer cache 哈希链的 latch；在 buffer cache 中定位块时发生串行化。","causes":"产生过多逻辑 I/O 的低效 SQL、对热块的大量并发访问、通过嵌套循环反复访问相同的块、非常常用的索引根块/分支块。","action":"调优 SQL 以减少逻辑读，消除不必要的嵌套循环访问，分散热块，并通过 AWR/ASH 找出并修复产生过多 buffer gets 的查询。","tags":["latch","concurrency","logical-io","buffer"]},{"name":"cursor: pin S wait on X","kind":"wait","meaning":"会话要以共享 (S) 模式通过 mutex pin 一个游标，但因另一个会话以排他 (X) 模式持有同一游标而等待。","causes":"硬解析/软解析率高、常用 SQL 解析过于频繁、未使用绑定变量的字面量查询、子游标激增、大量会话同时解析同一 SQL。","action":"使用绑定变量降低解析负载，在应用中缓存游标 (session_cached_cursors)，减少不必要的重复解析，并修复 version count 高的 SQL。","tags":["mutex","parse","cursor","concurrency"]},{"name":"library cache lock","kind":"wait","meaning":"会话正在等待获取 library cache 中某对象（包、视图、表定义）句柄上的锁；通常出现在并发访问/修改对象定义时。","causes":"对正在使用的对象执行 DDL、包/视图被持续重新编译、无效对象、大量并发连接和解析、权限 (grant) 变更。","action":"在非高峰时段执行 DDL，避免不必要的编译，修复无效对象，并减少应用端过多的 connect/parse 循环。","tags":["library-cache","concurrency","ddl","parse"]},{"name":"library cache: mutex X","kind":"wait","meaning":"会话正在等待以排他 (X) 模式获取保护 library cache 中对象的 mutex；通常出现在高解析量和对象句柄争用的情况下。","causes":"过多的解析（硬/软）、大量不使用绑定变量的各类字面量 SQL、子游标数量多、对象频繁失效、大量会话同时访问同一对象。","action":"使用绑定变量和游标缓存减少解析，规范 SQL 字面量，修复 version count 过高的问题，并改善应用的连接行为。","tags":["mutex","library-cache","parse","concurrency"]},{"name":"enq: TX - row lock contention","kind":"wait","meaning":"会话要修改被另一事务锁定的行，正在等待该事务提交/回滚并释放行锁。","causes":"并发 UPDATE/DELETE 相同的行、长时间运行且不提交的事务、设计不佳的应用流程、通过唯一键/外键产生的间接行锁、挂起的会话。","action":"保持事务简短并及时提交，重新设计争用的访问模式，必要时使用 SELECT FOR UPDATE 强制有序访问，并通过 ASH 找出并解决阻塞会话。","tags":["lock","transaction","row-lock","contention"]},{"name":"enq: TX - allocate ITL entry","kind":"wait","meaning":"会话需要块中的 ITL (Interested Transaction List) 槽位才能对其进行操作，但因块中没有空闲的 ITL 槽位而必须等待。","causes":"INITRANS 设置过低、块大小较小、同一块中有大量并发事务、承受大量 DML 的高密度块。","action":"增大表/索引的 INITRANS，调高 PCTFREE 以减少每块的行数，分散热块，必要时重建对象。","tags":["lock","transaction","itl","block"]},{"name":"gc buffer busy acquire","kind":"wait","meaning":"在 RAC 环境中，会话要通过全局缓存获取某个块，但由于同一实例上的另一个会话已对同一块发起全局请求而等待。","causes":"RAC 中实例间共享热块、互连 (interconnect) 性能不足、顺序索引右侧争用、工作负载分区不合理、全局缓存流量大。","action":"在实例间合理划分工作负载（应用亲和性/服务），分散热段（反向键/分区），改善互连延迟，并通过 AWR RAC 部分和 GV$ 视图分析 gc 争用。","tags":["rac","global-cache","concurrency","interconnect"]},{"name":"gc cr block busy","kind":"wait","meaning":"在 RAC 环境中，会话向另一个实例请求一致性读 (CR) 块，正在等待该实例准备好块并通过互连传送。","causes":"实例间共享热块、互连缓慢或饱和、大量 redo/提交流量、工作负载分区不合理、过多的全局缓存 CR 请求。","action":"改善工作负载亲和性，优化互连带宽和延迟，减少热块，检查提交和 DML 模式，并调优 RAC。","tags":["rac","global-cache","cr","interconnect"]},{"name":"log file switch (checkpoint incomplete)","kind":"wait","meaning":"会话要切换到新的 redo log，但下一个日志文件的 checkpoint 尚未完成，因此 LGWR 无法重用该日志，所有 redo 生成都会停止。","causes":"redo log 文件过小或过少、DBWR/checkpoint I/O 缓慢、redo 生成速率高、日志组数量不足。","action":"增大 redo log 文件并添加日志组，提升 DBWR/checkpoint I/O 性能，进行减少 redo 生成的应用优化，并监控日志切换频率。","tags":["redo","checkpoint","log-switch","io"]},{"name":"free buffer waits","kind":"wait","meaning":"会话正在 buffer cache 中寻找干净/空闲的缓冲区以读入新块，但由于 DBWR 无法足够快地将脏缓冲区写入磁盘而等待空闲缓冲区。","causes":"DBWR 写 I/O 缓慢、buffer cache 过小、大量 DML 快速产生脏缓冲区、checkpoint 速度不足、磁盘子系统饱和。","action":"加快数据文件写 I/O，增大 buffer cache，评估 DBWR 进程数 (db_writer_processes)，并检查 DML 密集度和 checkpoint 设置。","tags":["buffer","dbwr","io","checkpoint"]},{"name":"Buffer Cache Hit Ratio","kind":"metric","meaning":"无需访问磁盘即可从 buffer cache 获得所请求块的比率；表示有多大比例的逻辑读不需要物理读。","causes":"比率低通常是由于 buffer cache 过小、低效的全扫描、冷缓存或物理读过高。单看该指标可能产生误导；高比率也可能掩盖糟糕的 SQL。","action":"不要单独以该比率为目标；应结合等待事件和物理读趋势进行评估。调优低效 SQL，减少不必要的逻辑读，并根据工作负载确定缓存大小。","tags":["metric","buffer-cache","ratio","memory"]},{"name":"Average Active Sessions","kind":"metric","meaning":"某一时间段内同时处于活动状态（在 CPU 上运行或等待某个事件）的平均会话数；概括数据库的整体负载和瓶颈强度。","causes":"AAS 高可能是由于 CPU 饱和、I/O 等待、锁/latch 争用或并发量增加。接近或超过 CPU 核数的值表明存在资源瓶颈。","action":"将 AAS 与 CPU 核数进行比较，通过 ASH 找出主要的等待类（CPU/IO/并发），调优产生最多负载的 SQL 和会话，并解决资源或并发问题。","tags":["metric","load","ash","sessions"]}]},"reference/waits-postgres.json":{"engineId":"postgres","engine":"PostgreSQL","order":2,"waits":[{"name":"LWLock:WALWrite","kind":"wait","meaning":"会话正在等待将 WAL 缓冲区写入磁盘所需的轻量级锁；通常由提交期间 WAL 写入的串行化引起。","causes":"WAL 磁盘 I/O 缓慢、非常频繁/小的提交、写负载高、WAL 写入带宽不足、大量后端进程同时写 WAL。","action":"将 WAL 迁移到高速/低延迟磁盘，组提交（评估 synchronous_commit、commit_delay），增大 wal_buffers，减少不必要的写操作。","tags":["wal","lwlock","commit","io"]},{"name":"LWLock:BufferContent","kind":"wait","meaning":"会话正在等待共享缓冲池中某个缓冲区保护页面内容的轻量级锁；表明存在对该页面并发访问引起的争用。","causes":"对同一页面的大量并发读写（热页）、顺序索引的右边缘争用、承受大量更新的小表、高并发。","action":"分散热页（fillfactor 设置、分区），减少顺序键争用，重构频繁更新的行，检查并发访问模式。","tags":["lwlock","buffer","concurrency","contention"]},{"name":"Lock:transactionid","kind":"wait","meaning":"会话正在等待锁定了其要修改的行的另一事务结束（提交/回滚）；锁等待针对目标事务的 ID。","causes":"并发 UPDATE/DELETE 相同的行、长时间运行且不提交的事务、idle-in-transaction 会话、设计不佳且存在争用的流程。","action":"保持事务简短并及时提交，限制 idle-in-transaction 会话 (idle_in_transaction_session_timeout)，重新设计争用的访问模式，通过 pg_stat_activity 找出阻塞查询。","tags":["lock","transaction","row-lock","contention"]},{"name":"Lock:tuple","kind":"wait","meaning":"会话正在等待获取某个特定元组（行）上的锁；在多个会话排队更新/锁定同一行时出现。","causes":"对同一行并发执行 SELECT FOR UPDATE/UPDATE、热行争用（例如计数器行）、行锁长时间持有、高并发。","action":"分散或排队处理热行更新，缩短事务时长，重新设计计数器等争用模式（例如批量更新），考虑使用 FOR UPDATE SKIP LOCKED。","tags":["lock","tuple","row-lock","contention"]},{"name":"Lock:relation","kind":"wait","meaning":"会话正在等待获取表/索引 (relation) 上的表级锁；通常出现在 DDL 与 DML 不兼容的锁模式之间发生争用时。","causes":"查询运行期间执行 ALTER/DROP/TRUNCATE 等 DDL、需要强锁的 VACUUM FULL、以不兼容模式并发访问同一张表、长时间运行的查询。","action":"在空闲时段配合较短的 lock_timeout 执行 DDL，使用定期 autovacuum 代替 VACUUM FULL，监控长查询，检查使表访问串行化的操作。","tags":["lock","relation","ddl","table-lock"]},{"name":"IO:DataFileRead","kind":"wait","meaning":"会话正在等待将数据文件页从磁盘读入 shared buffers；这是所请求页面不在缓存中时发生的物理读等待。","causes":"shared_buffers 过小、低效的执行计划和大规模扫描、冷缓存、磁盘子系统缓慢、索引使用不足。","action":"根据工作负载调整 shared_buffers 和操作系统页缓存，修复低效查询和缺失的索引，提升 I/O 性能，正确设置 effective_cache_size。","tags":["io","read","buffer","disk"]},{"name":"IO:WALSync","kind":"wait","meaning":"会话正在等待 WAL 被持久写入磁盘（fsync/同步）；为保证提交持久性，需等待 WAL 物理刷写完成。","causes":"存储 fsync 延迟高、提交非常频繁、WAL 磁盘性能低、磁盘无回写缓存 (write-back cache)、写负载高。","action":"将 WAL 磁盘迁移到 fsync 延迟低的高速存储，组提交 (commit_delay/commit_siblings)，根据业务需求评估 synchronous_commit，改善存储层。","tags":["wal","io","fsync","commit"]},{"name":"IO:DataFileWrite","kind":"wait","meaning":"会话正在等待将脏数据页写入磁盘；通常出现在后端进程为在缓冲池中找到干净页面而自行写入时。","causes":"数据磁盘写 I/O 缓慢、checkpoint 拥塞、background writer 不足、buffer pool 过小、写负载过高导致 backend 自行写出。","action":"平滑 checkpoint（checkpoint_completion_target、max_wal_size），调优 background writer（bgwriter_lru_maxpages），提升磁盘写性能，审查 shared_buffers。","tags":["io","write","checkpoint","bgwriter"]},{"name":"Client:ClientRead","kind":"wait","meaning":"backend 正在等待客户端发送下一条命令/数据；服务器空闲、等待客户端发送数据时出现。","causes":"网络缓慢、idle-in-transaction 会话、客户端应用收发数据缓慢、大结果集消费缓慢、连接池问题。","action":"检查应用端和网络延迟，限制 idle-in-transaction 会话，高效消费结果（如 cursor/批量），改进连接池。该等待通常不是数据库瓶颈。","tags":["client","network","idle","application"]},{"name":"IPC:ProcArrayGroupUpdate","kind":"wait","meaning":"会话正在等待组长 backend 以组方式在 ProcArray 中标记其事务结束；这是提交期间 ProcArray 更新的串行化。","causes":"提交/事务速率极高、并发短事务过多、连接数高、snapshot/ProcArray 访问频繁。","action":"控制事务速率和并发连接数（连接池），减少不必要的短事务，在极高并发下限制连接数，均衡工作负载。","tags":["ipc","commit","procarray","concurrency"]},{"name":"LWLock:LockManager","kind":"wait","meaning":"会话正在等待保护重量级锁表的 lock manager 轻量级锁；由锁获取/释放操作的串行化引起。","causes":"查询涉及大量表/分区（超出 fast-path 锁上限）、高并发、分区过多的表、获取/释放大量锁的工作负载。","action":"减少每个查询涉及的分区/表数量（分区裁剪），审查过度分区，用连接池控制并发，简化获取不必要锁的操作。","tags":["lwlock","lock-manager","concurrency","partitioning"]},{"name":"Timeout:VacuumDelay","kind":"wait","meaning":"vacuum/autovacuum 进程因基于成本的 vacuum 延迟而主动暂停；这是为限制 I/O 负载而有意产生的等待。","causes":"vacuum_cost_delay/autovacuum_vacuum_cost_delay 设置导致的限流、cost_limit 过低、vacuum 需清理大量表、autovacuum 设置不够激进。","action":"若 vacuum 过慢且膨胀/死元组不断累积，提高 cost_limit 并降低 cost_delay，调优 autovacuum worker 数量和阈值，并与 I/O 容量取得平衡。","tags":["vacuum","timeout","throttle","maintenance"]},{"name":"缓存命中率","kind":"metric","meaning":"无需访问磁盘、直接由 shared buffers 满足的块请求比例；根据 pg_stat_database 中的 blks_hit 和 blks_read 计算。","causes":"比率低通常表明 shared_buffers 过小、低效的大范围扫描、冷缓存或内存不足。由于操作系统页缓存也会服务读取，仅凭该比率可能产生误导。","action":"结合等待事件和 I/O 趋势解读该比率，调优 shared_buffers 和 effective_cache_size，修复低效查询；若持续偏低，检查内存和执行计划。","tags":["metric","cache","ratio","memory"]},{"name":"复制延迟","kind":"metric","meaning":"备库/副本在应用主库生成的 WAL 方面落后的程度；以字节（LSN 差值）或延迟秒数衡量。","causes":"网络缓慢、备库查询负载重（尤其是回放冲突）、备库磁盘 I/O 缓慢、主库写入速率高、长时间运行的只读查询延迟恢复。","action":"用 pg_stat_replication 监控 write/flush/replay 延迟，提升网络和备库 I/O 性能，管理冲突的长查询（hot_standby_feedback/max_standby_streaming_delay），审查同步复制的必要性。","tags":["metric","replication","lag","standby"]}]},"reference/waits-mysql.json":{"engineId":"mysql","engine":"MySQL","order":3,"waits":[{"name":"wait/synch/mutex/innodb/buf_pool_LRU_list_mutex","kind":"wait","meaning":"线程正在等待保护 InnoDB buffer pool LRU 链表的 mutex；表明页读取/淘汰期间存在串行化。8.0 中原来单一的 buf_pool_mutex 已被拆分；其他相关 instrument 为 buf_pool_free_list_mutex、buf_pool_flush_state_mutex 和 buf_pool_zip_mutex。","causes":"高并发下 buffer pool 访问频繁、只有单个 buffer pool 实例、频繁的页读取/淘汰、buffer pool 过小。","action":"增加 innodb_buffer_pool_instances 以拆分 mutex，按工作集大小设置 innodb_buffer_pool_size，调优查询以减少物理读，并控制并发。","tags":["innodb","mutex","buffer-pool","concurrency"]},{"name":"wait/io/file/innodb/innodb_data_file","kind":"wait","meaning":"线程正在等待针对 InnoDB 数据文件（ibd/ibdata）的物理文件 I/O（读/写）完成。","causes":"磁盘子系统缓慢、buffer pool 过小导致大量物理读、大表扫描、checkpoint/flush 压力、I/O 能力不足。","action":"扩大 buffer pool，修复低效查询和缺失的索引，迁移到高速存储（SSD/NVMe），按存储能力设置 innodb_io_capacity，并审查 flush 行为。","tags":["innodb","io","datafile","disk"]},{"name":"wait/lock/table/sql/handler","kind":"wait","meaning":"线程正在等待获取 SQL 层的表级 handler 锁（表锁）；以不兼容模式并发访问同一张表时出现。","causes":"需要表锁的操作、MyISAM 等表级锁存储引擎、LOCK TABLES 语句、DDL 与 DML 争用、长时间运行的查询。","action":"使用行级锁的 InnoDB，减少 LOCK TABLES 的使用，在低峰期执行 DDL，优化长查询，并审查导致表访问串行化的模式。","tags":["lock","table-lock","sql-layer","contention"]},{"name":"wait/synch/mutex/sql/LOCK_open","kind":"wait","meaning":"线程正在等待保护已打开表定义/对象字典的 LOCK_open mutex；这是表打开/关闭操作的串行化。","causes":"持续访问大量不同的表、table_open_cache/table_definition_cache 过小、频繁打开和关闭表、高并发、表/分区过多。","action":"增大 table_open_cache 和 table_open_cache_instances，调优 table_definition_cache，审查过多的表/分区，减少不必要的频繁打开表。","tags":["mutex","table-cache","sql-layer","concurrency"]},{"name":"行锁等待","kind":"wait","meaning":"事务正在等待另一事务持有的 InnoDB 行锁释放；表明存在行级锁争用。","causes":"对相同行的并发 UPDATE/DELETE、长时间不提交的事务、缺失索引导致的大锁范围（gap lock）、热点行争用、死锁环路。","action":"保持事务简短并及时提交，添加合适的索引以缩小锁定的行范围，重新设计争用模式，监控死锁（SHOW ENGINE INNODB STATUS），并审查隔离级别。","tags":["innodb","row-lock","transaction","contention"]},{"name":"log file waits","kind":"wait","meaning":"线程正在等待 InnoDB redo log buffer 释放空间或 redo log 写入磁盘；通常在 log buffer flush/checkpoint 压力下出现。","causes":"redo log 过小（8.0.30+ 为 innodb_redo_log_capacity；之前为 innodb_log_file_size × innodb_log_files_in_group）、日志磁盘缓慢、写入速率高、频繁 flush（innodb_flush_log_at_trx_commit=1 且提交频繁）、log buffer 不足。","action":"增大 redo 容量（8.0.30+ 为 innodb_redo_log_capacity，可动态修改；之前为 innodb_log_file_size，需要重启）和 innodb_log_buffer_size，将 redo log 移至高速磁盘，审查提交模式，并根据持久性要求评估 flush 设置。","tags":["innodb","redo","log","io"]},{"name":"wait/io/file/innodb/innodb_log_file","kind":"wait","meaning":"线程正在等待针对 InnoDB redo log 文件的物理 I/O（写入和 fsync）完成；为保证提交持久性，需等待 redo 落盘。","causes":"存储 fsync 延迟高、innodb_flush_log_at_trx_commit=1 下提交极其频繁、日志磁盘性能低、写负载高。","action":"将 redo log 磁盘迁移到低延迟高速存储，尽可能合并提交，根据业务需求评估 innodb_flush_log_at_trx_commit，并改进存储层。","tags":["innodb","redo","fsync","commit"]},{"name":"wait/synch/mutex/innodb/log_writer_mutex","kind":"wait","meaning":"线程正在等待保护 InnoDB redo log writer 线程结构的 mutex。8.0 中 redo 子系统已重新设计；原来单一的 log_sys_mutex 被 log_writer_mutex、log_flusher_mutex 和 log_checkpointer_mutex 等独立 instrument 取代。","causes":"写入/提交速率极高、大量生成 redo、log writer/flusher 线程争用、日志磁盘缓慢、log buffer 过小。","action":"平衡写负载和提交频率，考虑使用可减少 redo 生成的批量操作，调优 log buffer，并利用所用 MySQL 版本的 redo log 可扩展性改进。","tags":["innodb","mutex","redo","concurrency"]},{"name":"Innodb_buffer_pool_wait_free","kind":"metric","meaning":"线程在 buffer pool 中找不到空闲页、必须等待脏页 flush 以获得空闲页的次数；持续增长表明 buffer pool 存在压力。","causes":"buffer pool 过小、flush/写 I/O 缓慢、innodb_io_capacity 过低、高写负载下脏页生成过快、checkpoint 滞后。","action":"增大 innodb_buffer_pool_size，按存储能力设置 innodb_io_capacity/io_capacity_max，提升写 I/O 性能，并审查脏页比例（innodb_max_dirty_pages_pct）。","tags":["metric","innodb","buffer-pool","flush"]},{"name":"Threads_running","kind":"metric","meaning":"当前非空闲、正在执行查询的线程数；反映服务器上的瞬时并发执行情况及可能的争用强度。","causes":"查询突增、慢查询堆积、线程因锁/mutex 争用而停滞、CPU 饱和、惊群效应。","action":"Threads_running 突增时，找出慢查询/被阻塞的查询（SHOW PROCESSLIST），调优查询，用连接池和线程池限制并发，并消除争用源。","tags":["metric","concurrency","threads","load"]},{"name":"Innodb_row_lock_time_avg","kind":"metric","meaning":"获取行锁的平均等待时间（毫秒）；反映行级锁争用的严重程度。","causes":"对相同行的大量并发访问、长事务、缺失索引导致的大锁范围、接近死锁/锁等待超时的等待。","action":"缩短事务时长，用合适的索引缩小锁范围，重新设计争用的访问模式，并审查 innodb_lock_wait_timeout 和隔离级别。","tags":["metric","innodb","row-lock","contention"]},{"name":"history list length","kind":"metric","meaning":"InnoDB undo log 中尚未 purge（仍保存旧行版本）的事务数；反映为 MVCC 保留的旧版本的累积情况。","causes":"长时间未结束的事务（尤其是长只读事务）、purge 线程缓慢、DML 速率高、未提交/空闲事务。","action":"识别并终止长时间运行和空闲的事务，调优 purge 线程数（innodb_purge_threads），并在应用中保持事务简短；HLL 过高会导致性能和膨胀问题。","tags":["metric","innodb","undo","mvcc"]},{"name":"Innodb_data_reads","kind":"metric","meaning":"InnoDB 从磁盘执行的物理读请求总数；反映 buffer pool 无法满足、需访问磁盘的读负载。","causes":"buffer pool 过小、冷缓存、低效查询和大范围扫描、缺失索引、工作集大于内存。","action":"增大 buffer pool，修复低效查询和缺失的索引，结合 buffer pool 命中率监控物理读趋势，并提升 I/O 性能。","tags":["metric","innodb","io","read"]},{"name":"Innodb_buffer_pool_read_requests","kind":"metric","meaning":"由 InnoDB buffer pool 满足的逻辑读请求总数；与 Innodb_buffer_pool_reads（在 buffer pool 中未找到、需从磁盘读取的页数）一起用于计算 buffer pool 命中率。","causes":"该值高表示查询活动频繁。若 Innodb_buffer_pool_reads 与该值之比偏高（命中率低），则存在内存不足或访问低效。","action":"监控 buffer pool 命中率（1 - Innodb_buffer_pool_reads / Innodb_buffer_pool_read_requests）；若偏低，扩大 buffer pool 并优化查询，调优产生过多逻辑读的低效查询。","tags":["metric","innodb","buffer-pool","logical-io"]}]},"reference/waits-mssql.json":{"engineId":"mssql","engine":"SQL Server","order":4,"waits":[{"name":"CXPACKET","kind":"wait","meaning":"并行查询执行期间线程同步时产生的等待；在并行计划算子之间的工作分配/匹配尚未完成时出现。","causes":"并行度高、工作分配不均（倾斜）、MAXDOP 无限制/过高（0）且 cost threshold for parallelism 过低（默认 5）、缺失索引导致大规模并行扫描、低效的执行计划。","action":"按工作负载调优 cost threshold for parallelism 和 MAXDOP，修复缺失的索引和统计信息，不要单独将 CXPACKET 视为问题；借助 CXCONSUMER 分离出真正的等待，并针对低效的并行计划进行处理。","tags":["parallelism","cxpacket","query","cpu"]},{"name":"CXCONSUMER","kind":"wait","meaning":"在并行计划中，consumer 线程等待 producer 线程送来数据；通常是无害的并行等待。","causes":"正常并行执行中 consumer 线程的等待、数据流不均、producer 端延迟、低效的并行计划。","action":"CXCONSUMER 大多是良性的，本身无需处理；要找到真正的瓶颈，应关注 producer 端的底层等待类型（IO、LCK 等）和执行计划效率。","tags":["parallelism","cxconsumer","query","benign"]},{"name":"PAGEIOLATCH_SH","kind":"wait","meaning":"数据页从磁盘读入 buffer pool 期间，线程正在等待以共享（SH）模式保护该页的 latch；属于读取的物理 I/O 等待。","causes":"磁盘 I/O 缓慢、内存不足（buffer pool 过小）、大范围扫描和缺失索引、冷缓存、物理读负载重。","action":"评估内存（max server memory）和 buffer pool，修复低效查询和缺失的索引，改善存储 I/O 延迟，并找出并调优读取量最大的查询。","tags":["io","pageiolatch","read","buffer-pool"]},{"name":"PAGEIOLATCH_EX","kind":"wait","meaning":"线程正在等待以排他（EX）模式保护正从磁盘读取的数据页的 latch；通常是在等待将被修改的页的物理 I/O。","causes":"磁盘 I/O 缓慢、写工作负载重、内存不足、checkpoint/lazy writer 压力、进行大量数据修改的操作。","action":"提升存储读写性能，增加 buffer pool 内存，使大量写入的操作批量化、高效化，并分离和测量 tempdb 与数据磁盘的 I/O。","tags":["io","pageiolatch","write","buffer-pool"]},{"name":"PAGELATCH_UP","kind":"wait","meaning":"线程正在等待以更新（UP）模式保护已在内存中的页的 latch；这不是物理 I/O，而是对内存页并发访问的争用。","causes":"内存热点页争用，尤其是 tempdb 系统页（PFS/GAM/SGAM）争用、大量并发创建 tempdb 对象、末页（热点页）插入争用。","action":"将 tempdb 拆分为与核心数匹配、大小相等的数据文件，减少 tempdb 争用，分散热点页模式，并消除不必要的 tempdb 使用（spill 等）。","tags":["pagelatch","tempdb","concurrency","contention"]},{"name":"PAGELATCH_EX","kind":"wait","meaning":"线程正在等待以排他（EX）模式保护内存页的 latch；通常出现在键值递增（顺序）的表上发生末页插入争用时。","causes":"顺序/递增键索引上的末页插入争用、tempdb 系统页争用、对热点页的大量并发写入、插入速率高。","action":"针对末页争用，使用 OPTIMIZE_FOR_SEQUENTIAL_KEY = ON（SQL Server 2019+）或键分布（hash/GUID/反向索引），将 tempdb 拆分为多个文件，并重新设计大量插入的模式。","tags":["pagelatch","last-page-insert","concurrency","contention"]},{"name":"LCK_M_X","kind":"wait","meaning":"线程正在等待获取资源（行/页/表）上的排他锁；通常是在等待其他锁释放以修改数据（UPDATE/DELETE/INSERT）。","causes":"长时间运行的事务、对相同资源的并发修改、锁升级、缺失索引导致的大锁范围、阻塞会话。","action":"保持事务简短并及时提交，用合适的索引缩小锁范围，找出阻塞查询（sys.dm_exec_requests/blocking），审查锁升级和隔离级别，必要时考虑 RCSI。","tags":["lock","exclusive","blocking","transaction"]},{"name":"LCK_M_U","kind":"wait","meaning":"线程正在等待获取资源上的更新锁；UPDATE 扫描时读取一行并准备修改，需等待其他锁释放。","causes":"对相同行的并发更新、长事务、缺失索引导致扫描并锁定大量行、阻塞会话、锁争用。","action":"为 UPDATE 语句添加合适的索引以减少扫描/锁定的行数，缩短事务时长，分析阻塞链，并评估隔离级别和 RCSI 选项。","tags":["lock","update-lock","blocking","transaction"]},{"name":"WRITELOG","kind":"wait","meaning":"线程正在等待事务日志记录写入磁盘（log flush）；通常是在提交时等待日志持久化。","causes":"日志磁盘 I/O 缓慢、提交极其频繁/过小、写负载高、日志磁盘与数据磁盘争用、同步 Always On 复制的影响。","action":"将事务日志迁移到高速低延迟磁盘，批量提交，减少不必要的频繁提交，考虑 delayed durability（如适用），并测量日志 I/O 延迟。","tags":["log","writelog","commit","io"]},{"name":"ASYNC_NETWORK_IO","kind":"wait","meaning":"SQL Server 已准备好发送结果，但由于客户端消费数据不够快而在网络/客户端侧等待；通常是应用端瓶颈。","causes":"客户端应用处理结果缓慢（逐行消费）、大结果集、网络缓慢、应用中的 RBAR 处理、客户端资源不足。","action":"在应用中快速、批量地消费结果，减少不必要的大结果集（只取所需的列/行），并检查网络延迟。这通常是客户端/网络问题，而非 SQL Server 问题。","tags":["network","client","application","async"]},{"name":"SOS_SCHEDULER_YIELD","kind":"wait","meaning":"线程用完其 CPU 时间片（quantum）并主动让出 scheduler 时产生的等待；多表明 CPU 压力或 CPU 密集型处理。","causes":"CPU 密集型查询、高并发和 CPU 饱和、spinlock 争用、执行大量逻辑读的低效查询。","action":"找出并调优消耗 CPU 的低效查询（缺失索引、糟糕的执行计划），减少逻辑读，评估 CPU 容量和并发；若持续偏高，调查硬件/执行计划瓶颈。","tags":["cpu","scheduler","cpu-bound","yield"]},{"name":"RESOURCE_SEMAPHORE","kind":"wait","meaning":"查询正在等待运行所需的内存授予（memory grant）；在有足够的查询内存可用之前无法开始执行。","causes":"查询请求过多内存（大型排序/哈希）、基数估算错误（偏大）、服务器内存不足、大量并发的内存密集型查询、统计信息过期。","action":"更新统计信息以纠正过大的内存授予估算，优化需要大型排序/哈希的查询（索引、过滤），审查 Resource Governor 和 max server memory 设置，并限制并发的内存密集型负载。","tags":["memory","memory-grant","query","resource"]},{"name":"THREADPOOL","kind":"wait","meaning":"由于没有可用的 worker 线程运行新任务，任务正在等待线程池中的空闲线程；这是表明 worker 线程严重耗尽的关键等待类型。","causes":"长时间阻塞占用线程、并发连接极多、max worker threads 不足、大量并行、死锁/长锁链。","action":"紧急解决阻塞链，用连接池管理并发连接，消除长时间持有的锁，审查 max worker threads 设置；通过 DAC 连接以查找根本原因。","tags":["threadpool","worker-thread","blocking","critical"]},{"name":"HADR_SYNC_COMMIT","kind":"wait","meaning":"在 Always On 同步副本上，主副本在提交事务前等待日志记录在辅助副本上固化；即同步提交延迟。","causes":"到辅助副本的网络缓慢/延迟高、辅助副本日志磁盘 I/O 缓慢、辅助副本过载、副本地理位置遥远、写入速率高。","action":"改善副本之间的网络延迟和带宽，提升辅助副本日志磁盘性能，均衡辅助副本负载；若延迟至关重要，审查同步/异步副本拓扑。","tags":["alwayson","replication","sync-commit","hadr"]},{"name":"Page Life Expectancy","kind":"metric","meaning":"表示数据页在 buffer pool 中停留（未被逐出内存）的平均秒数的指标；是内存压力的重要指标。","causes":"PLE 偏低或快速下降意味着内存压力、低效的大范围扫描、工作集大于内存或内存不足。还应分别评估各 NUMA 节点。","action":"关注 PLE 趋势和骤降而非固定阈值，评估 buffer pool 内存（max server memory），调优消耗大量内存的低效查询，并按 NUMA 节点监控 PLE。","tags":["metric","memory","buffer-pool","ple"]},{"name":"Buffer cache 命中率","kind":"metric","meaning":"无需访问磁盘、由 buffer cache 满足的数据页请求比例；表示从内存读取的页所占百分比。","causes":"在现代系统上该比率通常很高（得益于预读），值偏低则表明存在显著的内存压力。仅凭比率高并不能保证性能良好。","action":"不要单独解读该指标，而应结合 PLE、PAGEIOLATCH 等待和物理读趋势；若有内存压力迹象，增加 buffer pool 内存并修复低效查询。","tags":["metric","memory","buffer-pool","ratio"]}]},"reference/waits-mongodb.json":{"engineId":"mongodb","engine":"MongoDB","order":5,"waits":[{"name":"WiredTiger cache used %","kind":"metric","meaning":"WiredTiger 存储引擎分配的内部缓存中已使用的比例；反映将数据保留在内存中的压力（默认目标阈值约为 80%）。","causes":"工作集大于缓存、缓存过小、大范围扫描、大量读写、大量未结束的事务。","action":"评估 WiredTiger 缓存大小（storage.wiredTiger.engineConfig.cacheSizeGB）和 RAM，添加可缩小工作集的索引，并在使用率接近 80% 目标和 95% 淘汰阈值时监控淘汰压力和延迟。","tags":["metric","wiredtiger","cache","memory"]},{"name":"WiredTiger cache dirty %","kind":"metric","meaning":"WiredTiger 缓存中尚未写入磁盘（脏）的数据百分比；反映写入压力和 checkpoint/淘汰负载（脏数据淘汰阈值通常约为 20%）。","causes":"写负载高、磁盘 I/O 缓慢、大型/长时间运行的写入、checkpoint 压力、缓存跟不上写入速率。","action":"若脏数据百分比接近淘汰阈值（20%），提升磁盘写性能，平衡写负载，评估缓存大小，并审查长时间写入和 checkpoint 间隔。","tags":["metric","wiredtiger","cache","write"]},{"name":"tickets available (read/write)","kind":"metric","meaning":"WiredTiger 为并发读写操作发放的并发 ticket 剩余数量；若降至零，新操作将排队等待 ticket。","causes":"慢操作长时间占用 ticket、磁盘 I/O 瓶颈、锁/争用、负载突增、长时间运行的查询耗尽 ticket。","action":"若可用 ticket 接近零并出现排队，优化慢查询（添加索引），改善磁盘 I/O，均衡负载；ticket 耗尽通常是更深层 I/O/争用瓶颈的症状。","tags":["metric","wiredtiger","concurrency","tickets"]},{"name":"global lock queue","kind":"metric","meaning":"等待全局/数据库/集合锁的读（readers）和写（writers）操作的队列长度；反映锁争用下等待的操作数量。","causes":"长时间持有的锁、大量写争用、长时间运行的操作、DDL 操作以及索引构建开始/结束时短暂持有的排他集合锁（4.2+ 混合构建；前台构建已不复存在）、I/O 缓慢导致锁持有时间过长。","action":"若队列持续饱满，找出并优化长时间运行的操作（currentOp），将索引构建和其他 DDL 安排在非高峰时段，改善 I/O，并审查争用的写入模式。","tags":["metric","lock","queue","concurrency"]},{"name":"复制延迟（secondary）","kind":"metric","meaning":"secondary 成员在应用 primary 的 oplog 方面落后的程度（秒）；反映副本集成员之间的数据延迟。","causes":"网络缓慢、secondary 磁盘 I/O 缓慢、secondary 读负载重、primary 写入速率高、secondary 硬件不足、长时间运行的操作。","action":"用 rs.printSecondaryReplicationInfo 监控延迟，提升网络和 secondary I/O 性能，均衡 secondary 负载，保持足够的 oplog 大小，并审查 write concern 要求。","tags":["metric","replication","lag","secondary"]},{"name":"oplog window","kind":"metric","meaning":"oplog 中最旧与最新操作之间的时间跨度（小时）；表示 secondary 在不丢失同步的情况下最多可以落后多久。","causes":"oplog 过小、写入速率突然升高、secondary 长时间离线、复制负载增加。","action":"若 oplog window 缩小，增大 oplog，使 secondary 延迟保持在窗口以内，并审查写负载和备份/同步要求；若超出窗口，则需要完整重新同步（resync）。","tags":["metric","replication","oplog","window"]},{"name":"page faults","kind":"metric","meaning":"操作系统必须从磁盘获取不在内存中的数据页的缺页次数（尤其是硬缺页）；反映数据溢出内存而落到磁盘。","causes":"工作集大于 RAM、内存不足、大范围扫描、缓存压力、其他进程消耗内存。","action":"评估服务器内存/工作集，添加索引以减少扫描的数据量，平衡 WiredTiger 缓存和操作系统内存，并结合 I/O 延迟监控硬缺页的增长。","tags":["metric","memory","page-fault","io"]},{"name":"connections current","kind":"metric","meaning":"当前对服务器打开的客户端连接数；反映连接池使用情况及距上限的接近程度。","causes":"连接池配置错误、连接泄漏、客户端突增、长时间占用的连接、微服务产生过多连接。","action":"将连接数与服务器上限（maxIncomingConnections）对比，在应用端正确设置连接池大小，修复连接泄漏并监控突增。","tags":["metric","connections","pool","capacity"]},{"name":"scanAndOrder / COLLSCAN 比率","kind":"metric","meaning":"不使用索引而全量扫描集合（COLLSCAN）或在内存中排序（scanAndOrder）的查询比例；是索引低效的指标。","causes":"缺失或不合适的索引、与查询不匹配的排序键、无过滤条件的宽泛查询、糟糕的查询模式。","action":"审查慢查询及其执行计划（explain），添加与查询和排序模式匹配的复合索引，修复产生 COLLSCAN 和 scanAndOrder 的查询，并监控 examined/returned 文档比例。","tags":["metric","index","collscan","query"]},{"name":"queued operations","kind":"metric","meaning":"因等待锁或资源而排队（尚无法活跃运行）的读写操作数量；表明系统无法满足需求。","causes":"锁争用、ticket 耗尽、I/O 瓶颈、长时间运行的查询、负载突增、磁盘缓慢。","action":"若队列增长，检查并优化活跃和长时间运行的操作（currentOp），改进索引和 I/O，均衡负载，并结合 ticket 和锁争用指标查找根本原因。","tags":["metric","queue","concurrency","load"]}]},"reference/waits-redis.json":{"engineId":"redis","engine":"Redis","order":6,"waits":[{"name":"keyspace 命中率","kind":"metric","meaning":"在内存中找到的键查找比例，根据 keyspace_hits 和 keyspace_misses 计算；反映 Redis 作为缓存的有效程度。","causes":"比率低表明内存不足导致淘汰、TTL 过短、冷缓存、大量查找从未缓存的唯一键，或键设计不佳。","action":"按 hits/(hits+misses) 监控命中率；若偏低，审查内存/淘汰策略和 TTL，预热频繁访问的数据，并改进键设计和缓存策略。","tags":["metric","cache","hit-ratio","keyspace"]},{"name":"evicted_keys","kind":"metric","meaning":"达到 maxmemory 上限时被淘汰策略从内存中移除（删除）的键数；是内存压力的直接指标。","causes":"超出 maxmemory 上限、内存不足、无 TTL 的键不断累积、数据量大于内存、淘汰策略不合适。","action":"若淘汰持续增长，增加内存或减少数据量，按工作负载选择 maxmemory-policy（如 allkeys-lru/volatile-lru），并审查 TTL 使用情况和 big key。","tags":["metric","memory","eviction","maxmemory"]},{"name":"blocked_clients","kind":"metric","meaning":"使用 BLPOP、BRPOP、WAIT 和 XREAD BLOCK 等阻塞命令等待事件/数据的客户端数量；在队列消费模式中属正常现象，但异常增长是警示信号。","causes":"消费者使用阻塞的 list/stream 命令、producer 端没有数据到达、用 WAIT 等待副本确认、消费者与生产者不平衡。","action":"将被阻塞客户端数与预期的消费者模型对比；若异常增长，调查 producer 端和复制确认情况，并平衡队列消费速率。","tags":["metric","clients","blocking","queue"]},{"name":"mem_fragmentation_ratio","kind":"metric","meaning":"操作系统分配给 Redis 的物理内存（RSS）与 Redis 实际使用的内存之比；反映内存碎片或 swap 情况。","causes":"明显大于 1 表示内存碎片，小于 1 则表示 Redis 已被 swap 出（到磁盘）。大小不一的键以及删除/插入循环会加剧碎片。","action":"碎片较高时，考虑启用 activedefrag 或重启；若小于 1，调查 swap 并增加内存；审查分配器（jemalloc）行为和 big key 模式。","tags":["metric","memory","fragmentation","swap"]},{"name":"instantaneous_ops_per_sec","kind":"metric","meaning":"Redis 当前每秒处理的命令数（瞬时吞吐量）；反映工作负载强度和流量突变。","causes":"流量突增、批处理作业负载、低效的客户端访问模式、未使用 pipelining 而逐条发送命令、读写高峰。","action":"结合延迟和 CPU 监控吞吐量，将骤降/突增与流量和慢命令关联，通过 pipelining/批处理提升效率，并做好容量规划。","tags":["metric","throughput","ops","load"]},{"name":"rdb_last_bgsave_status","kind":"metric","meaning":"上一次 RDB 后台保存（BGSAVE）的成功状态；反映持久化快照是否成功生成。","causes":"失败（err）状态可能由磁盘已满/空间不足、fork 所需内存不足、磁盘 I/O 错误、权限问题或负载过高引起。","action":"若状态为 'err'，检查磁盘空间、fork 所需内存（overcommit 设置）和磁盘健康状况，并查看日志；BGSAVE 失败会带来数据丢失风险，因此应审查持久化配置。","tags":["metric","persistence","rdb","bgsave"]},{"name":"master_link_status","kind":"metric","meaning":"副本到 master 的复制链路状态（up/down）；反映副本与 master 之间的连接是否健康。","causes":"'down' 状态可能由网络中断、master 不可达、复制同步错误、认证问题或 master 过载引起。","action":"若状态为 'down'，检查网络连通性、master 可达性和 auth 设置，查看副本日志，监控 master_repl_offset 延迟，并确认同步已完成。","tags":["metric","replication","master-link","availability"]},{"name":"延迟（slowlog）","kind":"metric","meaning":"通过 slowlog 和延迟监控检测到的、超过设定阈值的慢命令延迟；在单线程的 Redis 中，反映单条命令阻塞其他命令的风险。","causes":"时间复杂度高的命令（KEYS、大型 SORT、对大集合执行 SMEMBERS/HGETALL）、big key、缓慢的 Lua 脚本、fork/持久化暂停、swap。","action":"使用 SLOWLOG 和 LATENCY 命令找出慢命令，消除 O(N) 命令（用 SCAN 代替 KEYS）和 big key，优化脚本，并监控 fork/AOF/RDB 导致的暂停。","tags":["metric","latency","slowlog","performance"]},{"name":"expired_keys","kind":"metric","meaning":"因 TTL 到期而被删除的键总数；是基于 TTL 的过期行为和内存回收的指标。","causes":"带 TTL 的键过期是正常现象。突然的变化可能表明 TTL 策略变更、大批键同时过期或意外的删除模式。","action":"区分 expired 与 evicted（expired 是健康的 TTL 行为，evicted 是内存压力），监控突发过期潮对缓存未命中和负载的影响，并分散 TTL 分布。","tags":["metric","ttl","expiration","keyspace"]},{"name":"connected_clients vs maxclients","kind":"metric","meaning":"当前已连接客户端数距配置的 maxclients 上限的接近程度；反映连接容量耗尽的风险。","causes":"连接泄漏、连接池配置错误、客户端突增、长时间占用的空闲连接、大量微服务连接。","action":"将已连接客户端数与 maxclients 对比；接近上限时，正确设置连接池大小，修复连接泄漏，设置空闲连接超时，必要时增大 maxclients。","tags":["metric","connections","maxclients","capacity"]}]},"reference/waits-elasticsearch.json":{"engineId":"elasticsearch","engine":"Elasticsearch","order":7,"waits":[{"name":"JVM heap used %","kind":"metric","meaning":"节点 JVM heap 的使用百分比；heap 使用率高且持续饱满意味着内存压力和频繁 GC 的风险。","causes":"fielddata/聚合内存过多、分片过多、bulk/查询负载重、mapping 过大、heap 过小、内存泄漏模式。","action":"监控 heap 使用情况（尤其是 GC 后仍高于 75% 时应介入），减少分片数，限制重型聚合和 fielddata 的使用，并使 heap 不超过 RAM 的一半且低于 32GB。","tags":["metric","jvm","heap","memory"]},{"name":"GC 时长/频率","kind":"metric","meaning":"JVM 垃圾回收的时长和频率；长时间/频繁的 GC 暂停会使节点无响应并降低性能。","causes":"heap 压力高、老年代（old gen）大量累积、重型聚合/fielddata、heap 过小、分片过多、长时间运行的大型查询。","action":"消除导致长暂停（尤其是 old GC）的根本原因——heap 压力，优化消耗大量内存的查询，减少分片数；频繁且长时间的 GC 是节点不稳定的早期预警。","tags":["metric","jvm","gc","pause"]},{"name":"thread_pool write rejected","kind":"metric","meaning":"由于 write（索引/bulk）线程池队列已满而被拒绝的索引请求数；表明节点无法承受写负载。","causes":"bulk/索引速率过高、磁盘 I/O 缓慢、以大量小请求代替 bulk、节点容量不足、refresh 压力。","action":"以大小合适的 bulk 发送索引请求，按节点容量限制写入速率（backoff），改善磁盘 I/O，增大 refresh_interval，扩展容量。","tags":["metric","thread-pool","indexing","rejection"]},{"name":"thread_pool search rejected","kind":"metric","meaning":"由于 search 线程池队列已满而被拒绝的搜索请求数；表明节点无法承受查询负载。","causes":"并发查询过多、重型/缓慢的聚合、涉及过多分片、低效查询、节点/CPU 容量不足。","action":"在客户端限制查询并发，优化重型查询和聚合，减少不必要的分片扇出（routing/过滤），增加节点并增加副本分片数（副本分片也会处理搜索），扩展容量。","tags":["metric","thread-pool","search","rejection"]},{"name":"索引/搜索延迟","kind":"metric","meaning":"索引和搜索操作的平均延迟；由 index_time_in_millis 和 query_time_in_millis 指标得出，反映用户感受到的响应时间。","causes":"磁盘 I/O 缓慢、重型查询/聚合、GC 暂停、分片数过多、refresh/merge 压力、资源不足。","action":"用 slow log 找出并优化慢查询，为索引调优 bulk 和 refresh_interval，提升磁盘和 heap 性能，并结合线程池拒绝和 GC 解读延迟。","tags":["metric","latency","indexing","search"]},{"name":"refresh/flush 时间","kind":"metric","meaning":"用于 refresh（使新文档可被搜索）和 flush（将 translog 持久化到 Lucene）的时间；反映索引开销。","causes":"refresh 过于频繁（refresh_interval 过低）、磁盘 I/O 缓慢、大量索引、translog 过大、大量小 segment。","action":"大量索引期间，增大 refresh_interval（如 30s）或临时禁用，改善磁盘 I/O，调优 bulk 大小，审查 segment 合并行为。","tags":["metric","refresh","flush","indexing"]},{"name":"未分配的分片","kind":"metric","meaning":"未分配到任何节点的分片数量；未分配的主分片意味着数据不可访问（red 健康状态），未分配的副本分片意味着持久性/容量风险。","causes":"节点故障/离开、超出磁盘水位线、副本设置相对节点数量过高、分配规则/过滤器、分片恢复期间的延迟。","action":"使用 cluster allocation explain API 查明原因，恢复故障节点，解决磁盘空间/水位线问题，检查副本数量和分配规则；优先处理 red 状态。","tags":["metric","shards","allocation","cluster-health"]},{"name":"磁盘水位线 %","kind":"metric","meaning":"节点磁盘使用率相对于影响 Elasticsearch 分片放置的 low/high/flood-stage 阈值（默认 85%/90%/95%）的情况。","causes":"磁盘写满、数据增长、存储不足、分片分布不均衡、旧索引未清理。","action":"达到 low 水位线时，不再向该节点分配新分片；达到 high 水位线时，分片会迁移到其他节点；达到 flood-stage 时，索引变为只读。释放磁盘空间（删除旧索引/使用 ILM），扩展存储，均衡分片分布并监控水位线阈值。","tags":["metric","disk","watermark","storage"]},{"name":"fielddata/query cache 驱逐","kind":"metric","meaning":"因内存限制从 fielddata cache 和 query (filter) cache 中驱逐的条目数量；表明缓存效率低下和内存压力。","causes":"fielddata 使用率高（在 text 字段上排序/聚合）、缓存限制过小、查询过滤器高度多样、内存压力、过滤器从未被复用。","action":"在 text 字段上使用 keyword/doc_values 代替 fielddata，限制繁重的 fielddata 聚合，检查缓存大小；频繁的淘汰应结合 heap 压力一起评估。","tags":["metric","cache","fielddata","eviction"]},{"name":"circuit breaker 触发","kind":"metric","meaning":"circuit breaker（parent/fielddata/request breaker）预测请求将超出内存而拒绝该请求的次数；这是保护节点免于 OOM 的安全机制。","causes":"聚合/查询请求过多内存、fielddata 使用率高、bulk 请求过大、整体 heap 压力、结果集非常大。","action":"将 circuit breaker 触发视为 heap 压力的信号，优化消耗内存的查询，减少 fielddata/聚合的使用，降低 bulk 大小，并检查 heap 和分片数量。","tags":["metric","circuit-breaker","memory","oom"]},{"name":"pending tasks","kind":"metric","meaning":"等待 master 节点处理的 cluster state 更新任务队列；表明 master 无法跟上集群管理负载。","causes":"索引/分片数量过多、频繁的 mapping/settings 变更、master 节点资源不足、cluster state 过大、网络/master 选举问题。","action":"如果队列持续满载，减少分片/索引数量，限制频繁的动态 mapping 更新，使用独立的专用 master 节点，并检查 cluster state 大小和 master 资源。","tags":["metric","master","cluster-state","queue"]}]},"reference/alerts-oracle.json":{"engineId":"oracle","engine":"Oracle Database","order":1,"alerts":[{"metric":"Buffer cache 命中率","warn":"< 95%","crit":"< 90%","tool":"V$SYSSTAT / V$BUFFER_POOL_STATISTICS","note":"buffer cache 命中率表示无需访问磁盘、直接从内存读取的数据块百分比。较低的值表明物理读增加且 SGA 过小。单独看可能产生误导；务必结合 AWR 报告中的物理读一起评估。","tags":["memory","cache","sga"]},{"metric":"表空间使用率（%used）","warn":"> 85%","crit":"> 95%","tool":"DBA_TABLESPACE_USAGE_METRICS","note":"如果表空间使用率达到 100%，将无法分配新的 extent，操作会因 ORA-01653 等错误而停止。即使启用了 autoextend，也要关注磁盘上限。DBA_TABLESPACE_USAGE_METRICS 视图对自动扩展文件和固定大小文件都能给出正确的百分比。","tags":["disk","tablespace","capacity"]},{"metric":"PGA 内存使用率","warn":"> 90% pga_aggregate_target","crit":"> 100%（over-alloc）","tool":"V$PGASTAT / V$PGA_TARGET_ADVICE","note":"超出 PGA 目标时，排序和哈希操作会溢出到磁盘（temp），查询变慢。over allocation count 持续增加表明目标值不足。请结合服务器内存检查 pga_aggregate_target 设置。","tags":["memory","pga","temp"]},{"metric":"TEMP 表空间使用率","warn":"> 80%","crit":"> 95%","tool":"V$TEMPSEG_USAGE / V$SORT_SEGMENT / DBA_TEMP_FREE_SPACE","note":"TEMP 空间用于大型排序、哈希连接和全局临时表操作。空间写满时会引发 ORA-01652，查询失败。突然写满通常源于某个执行计划不佳或排序数据过多的单条查询。","tags":["temp","disk","sort"]},{"metric":"平均活动会话数（AAS）","warn":"> CPU 核数","crit":"> 2 倍 CPU 核数","tool":"V$ACTIVE_SESSION_HISTORY / OEM","note":"平均活动会话数用一个数字概括数据库当前的负载。如果该值超过 CPU 核数，说明会话正在排队等待 CPU 或等待事件。如果持续保持在核数的数倍，则需要进行瓶颈分析。","tags":["cpu","load","session"]},{"metric":"Data Guard apply lag","warn":"> 60 s","crit":"> 300 s","tool":"V$DATAGUARD_STATS (apply lag)","note":"apply lag 是备库落后于主库的程度。较高的 apply lag 会增加故障转移时需要应用的 redo 量，从而延长恢复时间（RTO）；如果 redo 已到达备库，则不会丢失数据。潜在的数据丢失（RPO）由 transport lag 决定。请结合 transport lag 一起检查，以确定是网络慢还是 redo 应用慢。","tags":["replication","dataguard","lag"]},{"metric":"快速恢复区（FRA）使用率","warn":"> 85%","crit":"> 95%","tool":"V$RECOVERY_FILE_DEST / V$RECOVERY_AREA_USAGE","note":"FRA 写满时，归档日志生成停止，整个数据库挂起。这是生产系统中最常见的停机原因之一。请定期检查 RMAN 删除策略和 db_recovery_file_dest_size 的值。","tags":["disk","fra","archivelog"]},{"metric":"阻塞会话数","warn":">= 1（> 30 s）","crit":">= 1（> 120 s）","tool":"V$LOCK / V$SESSION (blocking_session)","note":"当一个会话持有的锁使其他会话等待时，整个应用会出现连锁式变慢。长时间阻塞通常源于未提交的事务或应用程序缺陷。找到阻塞链的根会话，必要时将其终止。","tags":["lock","blocking","concurrency"]},{"metric":"失效对象数","warn":"> 0","crit":"> 50","tool":"DBA_OBJECTS (status='INVALID')","note":"失效的包、视图或触发器可能在运行时导致意外错误。它们通常在 DDL 变更后出现。使用 UTL_RECOMP.RECOMP_PARALLEL 或 utlrp.sql 重新编译来解决，并调查持续失效对象的根本原因。","tags":["object","compile","maintenance"]},{"metric":"运行时间最长的查询","warn":"> 5 分钟","crit":"> 30 分钟","tool":"V$SQL / V$SESSION_LONGOPS","note":"单条长时间运行的查询可能独占资源并拖慢其他操作。除批处理作业外，OLTP 系统中运行数分钟的查询通常源于缺失索引或执行计划不佳。使用 SQL Monitor 报告检查执行计划和实际行数。","tags":["query","performance","sql"]},{"metric":"alert log 中的 ORA 错误","warn":">= 1（非严重）","crit":"ORA-00600 / ORA-07445 / ORA-04031","tool":"alert log / V$DIAG_ALERT_EXT","note":"alert log 是数据库的健康日志。ORA-00600 和 ORA-07445 是可能表明数据损坏的内部错误，而 ORA-04031 表明 shared pool 内存耗尽。如果出现这些错误，请立即调查，必要时提交给 Oracle Support。","tags":["error","alertlog","monitoring"]},{"metric":"redo log 切换频率","warn":"> 6 次/小时","crit":"> 12 次/小时","tool":"V$LOG_HISTORY","note":"过于频繁的 redo log 切换表明日志文件过小且存在 checkpoint 压力。理想情况下，高峰时段每 15-20 分钟切换一次。过于频繁的切换会增加 IO 和归档负载；请考虑扩大 redo log 组。","tags":["redo","io","checkpoint"]},{"metric":"最近一次成功 RMAN 备份的时长","warn":"> 24 小时","crit":"> 48 小时","tool":"V$RMAN_BACKUP_JOB_DETAILS","note":"备份时长决定了可恢复到的最近时间点，直接反映数据丢失的风险。如果失败或跳过的备份未被发现，灾难发生时将无路可退。还要单独监控归档日志备份，以确保具备时间点恢复能力。","tags":["backup","rman","recovery"]},{"metric":"磁盘 IO 延迟（db file sequential read）","warn":"> 10 ms","crit":"> 20 ms","tool":"V$SYSTEM_EVENT / AWR","note":"单块读的平均等待时间反映存储层的性能。在现代闪存/SSD 系统上，该值应为几毫秒。持续的高延迟表明存在存储瓶颈或物理读过多；请将 SGA 和存储层一起评估。","tags":["io","disk","latency"]}]},"reference/alerts-postgres.json":{"engineId":"postgres","engine":"PostgreSQL","order":2,"alerts":[{"metric":"缓存命中率","warn":"< 99%","crit":"< 95%","tool":"pg_stat_database / postgres_exporter","note":"缓存命中率是仅由 shared_buffers 提供的块所占的百分比；由操作系统缓存提供的读取也计入 blks_read，因此该比率可能使实际磁盘读取看起来比实际更差。在健康的 PostgreSQL OLTP 系统中，该比率应高于 99%。较低的值表明内存不足或缓存为冷缓存；它由 blks_hit 和 blks_read 计算得出。","tags":["memory","cache"]},{"metric":"连接数（connections vs max_connections）","warn":"> 80%","crit":"> 90%","tool":"pg_stat_activity / max_connections","note":"活动连接接近 max_connections 上限会导致新连接失败。由于每个连接都会消耗内存，连接数过高还会造成内存压力。建议使用 PgBouncer 等连接池保持较低的后端进程数。","tags":["connection","limit","pool"]},{"metric":"复制延迟","warn":"> 10 MB","crit":"> 100 MB","tool":"pg_stat_replication (replay_lag / lsn diff)","note":"复制延迟是备库落后于主库的 WAL 量（以字节计）。延迟高意味着只读副本上的数据过时，且故障转移时存在数据丢失风险。请同时监控以字节计的 LSN 差值和 replay_lag 时长。","tags":["replication","lag","standby"]},{"metric":"最旧事务的时长","warn":"> 5 分钟","crit":"> 30 分钟","tool":"pg_stat_activity (xact_start)","note":"长时间运行的未结束事务会阻止 VACUUM 清除死元组，导致表膨胀。处于 'idle in transaction' 状态的会话还会不必要地持有锁。识别此类会话，并使用 idle_in_transaction_session_timeout 加以限制。","tags":["query","vacuum","transaction"]},{"metric":"死锁","warn":"> 1 次/分钟","crit":"> 5 次/分钟","tool":"pg_stat_database (deadlocks)","note":"当两个事务互相等待对方的锁时就会发生死锁，PostgreSQL 通过取消其中一个来解决。偶尔出现死锁是可能的，但频繁出现表明应用中的加锁顺序不一致。启用 log_lock_waits 以查看哪些查询发生冲突。","tags":["lock","deadlock","concurrency"]},{"metric":"临时文件使用量","warn":"> 1 GB/小时","crit":"> 10 GB/小时","tool":"pg_stat_database (temp_bytes)","note":"当无法放入 work_mem 的排序和哈希操作溢出到磁盘时，会创建临时文件。临时文件使用量高既会导致变慢，也会造成磁盘压力。对于频繁溢出的查询，请考虑增大 work_mem 或优化查询。","tags":["temp","disk","work_mem"]},{"metric":"Autovacuum 滞后（死元组）","warn":"> 10% 死元组","crit":"> 20% 死元组","tool":"pg_stat_user_tables (n_dead_tup)","note":"死元组比例上升表明 autovacuum 跟不上，表膨胀正在加剧。膨胀的表既占用空间又拖慢扫描。在繁忙的表上调低 autovacuum_vacuum_scale_factor，使清理更频繁地运行。","tags":["vacuum","bloat","maintenance"]},{"metric":"磁盘使用率（data 目录）","warn":"> 85%","crit":"> 95%","tool":"df / node_exporter","note":"PostgreSQL 在磁盘写满之前没有任何自我保护机制。数据盘写满时，写入会以 'could not extend file' / 53100 失败；如果 WAL（pg_wal）所在磁盘写满，服务器会以 PANIC 关闭。WAL 和临时文件可能导致突然增长；及早干预至关重要。","tags":["disk","capacity"]},{"metric":"WAL 保留（replication slot 滞后）","warn":"> 1 GB","crit":"> 10 GB","tool":"pg_replication_slots (restart_lsn)","note":"未使用或滞后的 replication slot 会阻止 WAL 文件被删除，使 pg_wal 目录膨胀。最终可能导致磁盘写满。识别并删除失效的 slot，并使用 max_slot_wal_keep_size 设置上限。","tags":["wal","slot","disk"]},{"metric":"checkpoint 频率（requested checkpoint）","warn":"> 10% requested 比例","crit":"> 30% requested 比例","tool":"PG16 及更早版本：pg_stat_bgwriter (checkpoints_req) / PG17+：pg_stat_checkpointer (num_requested)","note":"当 requested checkpoint 相对于 timed checkpoint 的比例上升时，说明 max_wal_size 过小。频繁的 checkpoint 会导致 IO 尖峰和性能下降。检查 max_wal_size 和 checkpoint_completion_target 设置以分散负载。","tags":["checkpoint","wal","io"]},{"metric":"最近一次成功备份的时长","warn":"> 24 小时","crit":"> 48 小时","tool":"pgBackRest / barman info","note":"备份时长决定了可返回的最近时间点，直接体现你的 RPO。时间点恢复需要不间断的备份链和 WAL 归档。备份不仅要做，还必须通过定期恢复测试验证其有效性。","tags":["backup","recovery","rpo"]},{"metric":"磁盘 IO 延迟","warn":"> 10 ms","crit":"> 20 ms","tool":"node_exporter / iostat","note":"存储读写延迟直接影响查询时间。在现代 SSD 系统上，该值应为几毫秒。持续的高延迟表明存在磁盘瓶颈或物理读过多；请将 shared_buffers 和存储层一起评估。","tags":["io","disk","latency"]},{"metric":"事务 ID 回卷（age）","warn":"> 10 亿","crit":"> 15 亿","tool":"pg_database (age(datfrozenxid))","note":"如果事务 ID 的 age 接近 20 亿，PostgreSQL 会停止所有写入以防止数据丢失（回卷保护）。这是最危险的 PostgreSQL 故障之一。监控 datfrozenxid 的 age，并确保 autovacuum 的 freeze 工作没有落后。","tags":["wraparound","vacuum","critical"]}]},"reference/alerts-mysql.json":{"engineId":"mysql","engine":"MySQL","order":3,"alerts":[{"metric":"InnoDB 缓冲池命中率","warn":"< 99%","crit":"< 95%","tool":"SHOW ENGINE INNODB STATUS / mysqld_exporter","note":"buffer pool 命中率是无需访问磁盘、直接从内存读取的数据页和索引页的百分比。较低的值表明 innodb_buffer_pool_size 相对工作集过小。在专用服务器上，通常将约 70-80% 的内存分配给 buffer pool（共享服务器上更少）。","tags":["memory","cache","innodb"]},{"metric":"连接数（Threads_connected vs max_connections）","warn":"> 80%","crit":"> 90%","tool":"SHOW STATUS / max_connections","note":"当连接数接近 max_connections 上限时，新连接会以 'Too many connections' 被拒绝。Max_used_connections 显示历史峰值。在应用侧使用连接池控制并发连接数。","tags":["connections","limit","thread"]},{"metric":"副本延迟（Seconds_Behind_Source）","warn":"> 30 s","crit":"> 300 s","tool":"SHOW REPLICA STATUS","note":"Seconds_Behind_Source 以秒为单位给出副本落后于源库的程度。延迟高意味着只读副本上的数据过时以及故障转移风险。在单线程应用的情况下，大事务期间该值可能产生误导；请结合 GTID 差距一起确认。","tags":["replication","lag","replica"]},{"metric":"InnoDB 行锁等待时间","warn":"avg > 1 s","crit":"avg > 5 s","tool":"SHOW STATUS (Innodb_row_lock_time_avg)","note":"平均行锁等待时间表示事务之间相互等待的程度。较高的值说明存在严重冲突的更新或长时间运行的事务。请结合 Innodb_row_lock_waits 一起检查，并在 performance_schema 中找出冲突的查询。","tags":["lock","blocking","innodb"]},{"metric":"History list length（undo）","warn":"> 100000","crit":"> 1000000","tool":"SHOW ENGINE INNODB STATUS","note":"history list length 是尚未 purge 的 undo 记录数，会因长时间运行的未结束事务而增长。该值较高表明 purge 落后，且由于 MVCC 导致读取性能下降。找到并终止长时间运行的事务，使列表得以缩短。","tags":["undo","transaction","mvcc"]},{"metric":"失败的连接（Aborted_connects）","warn":"> 5 次/分钟","crit":"> 20 次/分钟","tool":"SHOW STATUS (Aborted_connects)","note":"Aborted_connects 统计在认证或握手阶段失败的连接。突然激增可能表明密码错误、网络问题或可能的攻击尝试。请结合 Aborted_clients 一起检查，以确定连接在哪里断开。","tags":["connections","error","security"]},{"metric":"写入磁盘的临时表","warn":"> 25% 比例","crit":"> 50% 比例","tool":"SHOW STATUS (Created_tmp_disk_tables)","note":"内存临时表溢出到磁盘的比例表明查询超出了 tmp_table_size 限制。磁盘临时表会明显拖慢查询。优化包含 GROUP BY 和 BLOB/TEXT 列的查询，并考虑增大 tmp_table_size 和 max_heap_table_size。","tags":["temp","disk","query"]},{"metric":"慢查询","warn":"> 5 次/分钟","crit":"> 20 次/分钟","tool":"slow query log / Slow_queries 状态","note":"超过 long_query_time 阈值的查询会写入 slow query log，是性能问题的主要来源。频繁的慢查询说明缺失索引或查询编写不佳。使用 pt-query-digest 分析日志，并优先处理代价最高的查询。","tags":["query","performance","slowlog"]},{"metric":"CPU 使用率","warn":"> 80%","crit":"> 90%","tool":"node_exporter / top","note":"持续的高 CPU 使用率可能源于繁重的查询、缺失索引或连接过多。短暂峰值属于正常，但持续饱和会导致查询排队。使用 performance_schema 或 slow log 找出消耗 CPU 最多的查询。","tags":["cpu","load"]},{"metric":"磁盘使用率（data 目录）","warn":"> 85%","crit":"> 95%","tool":"df / node_exporter","note":"data 目录写满会使 MySQL 无法写入并导致表错误。binlog 文件、undo 表空间和临时表空间（ibtmp1）的增长可能突然写满磁盘；redo log 大小固定（8.0.30+ #innodb_redo，innodb_redo_log_capacity）。确保通过 binlog_expire_logs_seconds 清除旧的 binlog。","tags":["disk","capacity","binlog"]},{"metric":"运行时间最长的查询","warn":"> 60 s","crit":"> 300 s","tool":"SHOW PROCESSLIST / information_schema.PROCESSLIST","note":"单条长时间运行的查询可能因持有锁和消耗资源而影响其他事务。在 OLTP 系统中，运行数分钟的查询通常源于执行计划不佳。使用 EXPLAIN 检查执行计划，必要时终止该查询并加以优化。","tags":["query","performance"]},{"metric":"最近一次成功备份的时长","warn":"> 24 小时","crit":"> 48 小时","tool":"xtrabackup / mysqldump 作业日志","note":"备份时长决定了可返回的最近时间点，也就决定了 RPO。结合 binlog 备份即可实现时间点恢复。仅做备份还不够；请通过定期恢复测试验证其有效性。","tags":["backup","recovery","rpo"]},{"metric":"磁盘 IO 延迟","warn":"> 10 ms","crit":"> 20 ms","tool":"iostat / node_exporter","note":"存储延迟直接转化为查询时间，对于无法放入 buffer pool 的工作负载尤为关键。在现代 SSD 系统上预期为几毫秒。高延迟表明存在磁盘瓶颈或物理读过多；同时检查 buffer pool 大小。","tags":["io","disk","latency"]}]},"reference/alerts-mssql.json":{"engineId":"mssql","engine":"SQL Server","order":4,"alerts":[{"metric":"Page Life Expectancy (PLE)","warn":"< 900 s","crit":"< 300 s","tool":"sys.dm_os_performance_counters","note":"Page Life Expectancy 以秒为单位表示数据页在被逐出 buffer pool 之前预计能在内存中停留的时间。较低的值表明存在内存压力，页面被快速从缓存中刷出。请按每个 NUMA 节点分别监控，并将突然下降视为内存不足。","tags":["memory","cache","ple"]},{"metric":"Buffer cache 命中率","warn":"< 98%","crit":"< 95%","tool":"sys.dm_os_performance_counters","note":"buffer cache 命中率是无需访问磁盘、直接从内存读取的页面百分比。在 SQL Server 中该值通常很高；请结合 PLE 一起评估。由于单独看可能产生误导，分析内存瓶颈时应以 PLE 作为主要指标。","tags":["memory","cache"]},{"metric":"被阻塞的进程","warn":">= 1（> 5 s）","crit":">= 1（> 30 s）","tool":"sys.dm_exec_requests (blocking_session_id)","note":"当一个会话持有的锁使其他会话等待时，就会形成阻塞链，进而导致整个应用变慢。长时间阻塞通常源于未提交的事务或不合适的隔离级别。使用 blocked process report 找出阻塞链头部的会话（head blocker）。","tags":["lock","blocking","concurrency"]},{"metric":"事务日志使用率（%used）","warn":"> 75%","crit":"> 90%","tool":"sys.dm_db_log_space_usage / DBCC SQLPERF(LOGSPACE)","note":"事务日志写满时，数据库无法写入并以错误 9002 停止。在 FULL 恢复模式下，如果不做日志备份，日志会持续增长。监控日志备份频率和 VLF 数量，确保日志能被截断并重用。","tags":["log","disk","recovery"]},{"metric":"tempdb 使用率（%used）","warn":"> 80%","crit":"> 95%","tool":"sys.dm_db_file_space_usage","note":"tempdb 由临时表、排序、version store 和哈希操作共享使用。写满时，整个服务器上的查询都会失败。突然增长通常来自大型查询或长时间运行的快照事务；请单独监控 version store 的大小。","tags":["tempdb","disk","sort"]},{"metric":"运行时间最长的查询","warn":"> 60 s","crit":"> 300 s","tool":"sys.dm_exec_requests / Query Store","note":"单条长时间运行的查询可能因持有锁和消耗资源而影响其他工作。在 OLTP 系统中，运行数分钟的查询通常源于缺失索引、parameter sniffing 或执行计划不佳。使用 Query Store 调查执行计划回归。","tags":["query","performance"]},{"metric":"Availability Group redo queue","warn":"> 1 MB","crit":"> 100 MB","tool":"sys.dm_hadr_database_replica_states","note":"redo queue 表示辅助副本上仍等待应用的日志量。redo queue 过高会延长故障转移后的恢复时间（RTO），并意味着可读副本上的数据过时。请结合 redo_rate 一起检查，以预测延迟何时消除。","tags":["replication","ag","lag"]},{"metric":"死锁率","warn":"> 1 次/分钟","crit":"> 5 次/分钟","tool":"system_health XEvent / 性能计数器","note":"当两个事务互相等待对方的锁时就会发生死锁，SQL Server 通过选择其中一个作为牺牲品来解决。偶尔出现死锁是可能的，但频繁出现表明加锁顺序不一致。从 system_health 扩展事件会话中提取死锁图，并修复冲突的查询。","tags":["lock","deadlock","concurrency"]},{"metric":"工作线程使用率","warn":"> 80%","crit":"> 90%","tool":"sys.dm_os_schedulers / max worker threads","note":"工作线程池耗尽时，新请求会在 THREADPOOL 等待上排队，服务器可能失去响应。通常由长时间阻塞或过多并发连接引起。通过解决阻塞链来减轻线程压力。","tags":["cpu","thread","load"]},{"metric":"CPU 使用率","warn":"> 80%","crit":"> 90%","tool":"sys.dm_os_ring_buffers / Performance Monitor","note":"持续的高 CPU 可能来自繁重的查询、缺失索引或执行计划不佳。短暂峰值属于正常，但持续饱和会导致查询排队。使用 sys.dm_exec_query_stats 找出消耗 CPU 最多的查询。","tags":["cpu","load"]},{"metric":"最近一次成功备份的时长","warn":"> 24 小时（完整）","crit":"> 48 小时（完整）","tool":"msdb.dbo.backupset","note":"备份时长决定了可返回的最近时间点和你的 RPO。在 FULL 恢复模式下，借助日志备份可以实现时间点恢复。监控完整备份、差异备份和日志备份链以确保可恢复性，并定期进行恢复测试。","tags":["backup","recovery","rpo"]},{"metric":"磁盘 IO 延迟（平均读/写）","warn":"> 15 ms","crit":"> 30 ms","tool":"sys.dm_io_virtual_file_stats","note":"每个文件的平均读写延迟反映存储性能。数据文件预期约为 15 ms，日志文件应更低。持续的高延迟表明存在存储瓶颈；使用 sys.dm_io_virtual_file_stats 找出最慢的文件。","tags":["io","disk","latency"]}]},"reference/alerts-mongodb.json":{"engineId":"mongodb","engine":"MongoDB","order":5,"alerts":[{"metric":"WiredTiger cache 脏页比例","warn":"> 5%","crit":"> 20%","tool":"db.serverStatus().wiredTiger.cache","note":"脏缓存比例是 WiredTiger cache 中已修改但尚未写入磁盘的页面百分比。超过 20% 意味着存在淘汰压力和写入瓶颈；MongoDB 会强制应用线程执行淘汰。请将缓存大小和磁盘写入吞吐量一起评估。","tags":["memory","cache","wiredtiger"]},{"metric":"WiredTiger cache 使用率（used %）","warn":"> 80%","crit":"> 95%","tool":"db.serverStatus().wiredTiger.cache","note":"当缓存使用率超过 80% 的驱逐目标时，WiredTiger 开始积极驱逐页面，性能下降。如果工作集无法放入缓存，物理读会增加。检查 wiredTigerCacheSizeGB 设置和服务器内存。","tags":["memory","cache","wiredtiger"]},{"metric":"可用 ticket（read/write tickets）","warn":"available/totalTickets < 20% 或 queueLength > 0（持续）","crit":"available = 0 且 queueLength 持续增长","tool":"db.serverStatus().wiredTiger.concurrentTransactions","note":"WiredTiger 使用 ticket 机制限制并发读写操作。7.0 之前默认固定为 128 个 ticket；7.0+ 中 ticket 数量会动态调整（storageEngineConcurrencyAdjustmentAlgorithm: throughputProbing），因此应监控 available/totalTickets 比例和 queueLength，而不是绝对数量。ticket 耗尽、操作排队是严重瓶颈的信号；通常由慢查询或磁盘 I/O 饱和引起。","tags":["concurrency","ticket","bottleneck"]},{"metric":"复制延迟","warn":"> 10 s","crit":"> 60 s","tool":"rs.printSecondaryReplicationInfo()","note":"复制延迟以秒为单位给出从节点落后于主节点的程度。延迟高意味着根据 read preference 可能读到过时数据，且故障转移时存在数据丢失风险。通常由从节点上的磁盘 I/O 或网络瓶颈，或长时间运行的操作引起。","tags":["replication","lag","replicaset"]},{"metric":"连接使用率（connections %）","warn":"> 80%","crit":"> 90%","tool":"db.serverStatus().connections","note":"活动连接接近配置的上限会导致新连接被拒绝。由于每个连接都会消耗内存和一个线程，连接数过高还会造成资源压力。在应用侧正确设置连接池大小，以防止连接风暴。","tags":["connection","limit","pool"]},{"metric":"Page fault 率","warn":"> 100 次/s","crit":"> 1000 次/s","tool":"db.serverStatus() / extra_info page_faults","note":"当数据不在内存中、必须从磁盘读取时就会发生 page fault。较高的 fault 率表明工作集无法放入内存，系统处于磁盘受限状态。持续的高值需要升级内存或优化查询/索引。","tags":["memory","disk","pagefault"]},{"metric":"排队的操作","warn":"> 10","crit":"> 50","tool":"db.serverStatus().globalLock.currentQueue","note":"globalLock currentQueue 表示因等待锁或资源而排队的读/写数量。队列持续满载表明服务器无法跟上传入负载。请结合 ticket 耗尽和磁盘 I/O 一起检查，以确定瓶颈来源。","tags":["concurrency","queue","lock"]},{"metric":"Oplog 窗口","warn":"< 24 小时","crit":"< 6 小时","tool":"rs.printReplicationInfo()","note":"oplog 窗口是 oplog 覆盖的时间跨度，决定了从节点可以断开多久仍能重新同步。如果窗口变窄，落后的成员将需要完整重新同步。写入负载增加时窗口会缩短；请相应增大 oplog 大小。","tags":["oplog","replication","recovery"]},{"metric":"运行时间最长的查询","warn":"> 30 s","crit":"> 120 s","tool":"db.currentOp() / slow query log","note":"长时间运行的查询会因消耗资源和持有锁而影响其他操作。它们通常来自因缺失索引而执行集合扫描（COLLSCAN）的查询。使用 profiler 和 currentOp 找出慢操作，并添加合适的索引。","tags":["query","performance","index"]},{"metric":"CPU 使用率","warn":"> 80%","crit":"> 90%","tool":"node_exporter / mongostat","note":"持续的高 CPU 可能来自无索引的查询、繁重的聚合操作或过多的连接。短暂峰值属于正常，但持续饱和会导致操作排队。使用 profiler 找出消耗 CPU 最多的查询并加以优化。","tags":["cpu","load"]},{"metric":"磁盘使用率（dbPath）","warn":"> 85%","crit":"> 95%","tool":"df / node_exporter","note":"data 目录写满会使 MongoDB 无法写入，并可能导致数据损坏。尽管 WiredTiger 压缩有所帮助，journal 和不断增长的集合仍可能迅速写满磁盘。做好容量规划，并通过 TTL 索引自动清除不需要的数据。","tags":["disk","capacity"]},{"metric":"距最近一次成功备份的时间","warn":"> 24 小时","crit":"> 48 小时","tool":"mongodump / Ops Manager / 快照记录","note":"备份时长决定了可返回的最近时间点和你的 RPO。结合 oplog 即可实现时间点恢复。仅做备份还不够；请通过定期恢复测试进行验证，并确保在分片集群中获取一致性快照。","tags":["backup","recovery","rpo"]}]},"reference/alerts-redis.json":{"engineId":"redis","engine":"Redis","order":6,"alerts":[{"metric":"内存使用（used_memory vs maxmemory）","warn":"> 80%","crit":"> 95%","tool":"INFO memory / redis_exporter","note":"used_memory 与 maxmemory 的比值表示 Redis 内存的使用程度。达到上限时，会根据 maxmemory-policy 淘汰键或拒绝写入。跟踪内存增长，以防止意外 OOM 和淘汰潮。","tags":["memory","maxmemory","capacity"]},{"metric":"键空间命中率","warn":"< 90%","crit":"< 80%","tool":"INFO stats (keyspace_hits/misses)","note":"命中率是请求的键在缓存中被找到的百分比，由 keyspace_hits 和 keyspace_misses 计算得出。命中率低说明缓存使用无效、TTL 设置不当或缓存为冷缓存。对于缓存类负载，该比率应尽可能高。","tags":["cache","hit","performance"]},{"metric":"被淘汰的键（evicted_keys）","warn":"> 1 / s","crit":"> 100 次/s","tool":"INFO stats (evicted_keys)","note":"evicted_keys 是因内存已满而按 maxmemory-policy 被移除的键数量。持续淘汰说明内存不足，重要数据可能丢失。可通过扩容内存或重新审视 TTL 策略或数据模型来减少淘汰。","tags":["memory","eviction","maxmemory"]},{"metric":"阻塞的客户端 (blocked_clients)","warn":"> 50","crit":"> 200","tool":"INFO clients (blocked_clients)","note":"blocked_clients 是在 BLPOP/BRPOP/WAIT 等阻塞命令上等待的客户端数量。高于预期的值说明消费者处理不过来或队列积压在增长。在基于队列的负载中，检查生产者/消费者的平衡。","tags":["client","blocking","queue"]},{"metric":"内存碎片率 (mem_fragmentation_ratio)","warn":"> 1.5","crit":"> 2.0","tool":"INFO memory","note":"碎片率是从操作系统获得的内存除以 Redis 使用的内存。高于 1.5 表示碎片过多；低于 1.0 表示内存溢出到 swap，非常危险。可通过启用 activedefrag 或重启来减少碎片。","tags":["memory","fragmentation"]},{"metric":"主节点链接状态 (master_link_status)","warn":"down (< 10 s)","crit":"down (> 30 s)","tool":"INFO replication (master_link_status)","note":"master_link_status 显示副本到主节点连接的状态 (up/down)。'down' 表示复制已中断，副本正在提供过期数据。长时间断开需要完整重新同步 (full resync)；应立即检查网络和主节点健康状况。","tags":["replication","master","connection"]},{"metric":"复制延迟（master_repl_offset 差值）","warn":"> 1 MB","crit":"> 10 MB","tool":"INFO replication (offset diff)","note":"主节点与副本偏移量之差以字节表示副本落后的程度。差值大意味着故障转移时有数据丢失风险，只读副本上数据不一致。检查网络延迟和副本负载，确保差距能够缩小。","tags":["replication","lag","offset"]},{"metric":"最近一次 RDB bgsave 状态 (rdb_last_bgsave_status)","warn":"err（最近一次尝试）","crit":"err（> 24 小时）","tool":"INFO persistence","note":"rdb_last_bgsave_status 显示最近一次后台快照是否成功 (ok)。bgsave 失败意味着持久化未生效，重启时存在数据丢失风险。通常由磁盘已满或 fork 所需内存不足导致。","tags":["backup","persistence","rdb"]},{"metric":"连接使用率 (connected_clients)","warn":"> 80% maxclients","crit":"> 90% maxclients","tool":"INFO clients / maxclients","note":"connected_clients 接近 maxclients 上限时，新连接会被拒绝。连接泄漏或连接池配置错误可能导致突增。在应用端正确配置连接池并关闭空闲连接。","tags":["connection","limit","client"]},{"metric":"瞬时命令延迟","warn":"> 1 ms","crit":"> 10 ms","tool":"redis-cli --latency / LATENCY DOCTOR","note":"由于 Redis 是单线程的，命令延迟通常在微秒级。延迟升高可能源于慢命令（KEYS、大型 SORT）、fork 期间的阻塞或 swap 使用。使用 slowlog 和 LATENCY 命令定位原因。","tags":["latency","performance","slowlog"]},{"metric":"CPU 使用率","warn":"> 80%（单核）","crit":"> 90%（单核）","tool":"top / redis_exporter","note":"由于 Redis 主线程只使用一个核心，单核饱和会拖慢整个服务器。CPU 过高可能源于高开销命令、连接过多或 Lua 脚本。使用 SLOWLOG 找出高开销命令，必要时将负载拆分到多个实例。","tags":["cpu","load"]},{"metric":"被拒绝的连接 (rejected_connections)","warn":"> 0","crit":"> 10 / min","tool":"INFO stats (rejected_connections)","note":"rejected_connections 是因达到 maxclients 上限而被拒绝的连接数。值大于零意味着客户端未得到服务，应用中正在出现错误。应立即排查 maxclients 值和连接泄漏。","tags":["connection","error","limit"]}]},"reference/alerts-elasticsearch.json":{"engineId":"elasticsearch","engine":"Elasticsearch","order":7,"alerts":[{"metric":"JVM 堆使用率","warn":"> 75%","crit":"> 85%","tool":"_nodes/stats/jvm / elasticsearch_exporter","note":"JVM 堆使用率反映节点的内存压力；持续高于 75% 意味着频繁 GC 并可能出现 OutOfMemory。堆超过 85% 时节点可能无响应或脱离集群。将堆大小设为服务器内存的约一半，且不超过 30-31 GB。","tags":["memory","jvm","heap"]},{"metric":"未分配分片数 (unassigned shards)","warn":">= 1（副本）","crit":">= 1（主分片）","tool":"_cluster/health / _cat/shards","note":"未分配分片意味着部分数据不可访问或未被复制。未分配的主分片意味着数据丢失风险；未分配的副本意味着丧失冗余，集群变为 yellow/red。使用 _cluster/allocation/explain 查找原因。","tags":["shard","cluster","health"]},{"metric":"磁盘水位线使用率","warn":"> 85% (low)","crit":"> 90% (high) / > 95% (flood_stage)","tool":"_nodes/stats/fs / _cat/allocation","note":"Elasticsearch 根据磁盘使用率分级采取措施：达到 85% low 水位线时停止向该节点分配新分片，达到 90% high 水位线时将现有分片迁移到其他节点，达到 95% flood_stage 时对索引施加 read_only_allow_delete 块。在 8.x 中，对于大容量磁盘，max_headroom 值（low 200GB / high 150GB / flood 100GB 可用空间）优先于百分比。在 7.4+ 中，磁盘使用率降到 high 水位线以下后该块会自动解除；尽管如此，仍应在超过水位线之前释放磁盘空间。","tags":["disk","watermark","capacity"]},{"metric":"线程池被拒绝的操作 (rejected)","warn":"> 0 (write/search)","crit":"> 100 / min","tool":"_cat/thread_pool / _nodes/stats/thread_pool","note":"线程池队列已满时，传入请求会被拒绝；write 池中的拒绝意味着索引写入丢失，search 池中的拒绝意味着查询失败。持续的拒绝表明节点跟不上传入负载。通过减小 bulk 大小和并发度来平衡负载。","tags":["threadpool","rejected","load"]},{"metric":"GC 暂停时间 (old gen GC pause)","warn":"> 1 s","crit":"> 5 s","tool":"GC 日志 / _nodes/stats/jvm","note":"长时间 GC 暂停期间节点无法响应，可能与主节点失去连接，从而触发分片重新分配。频繁且长时间的老年代 GC 是堆压力的症状。检查堆大小以及占用大量内存的聚合和 fielddata 使用。","tags":["jvm","gc","pause"]},{"metric":"索引延迟","warn":"> 50 ms","crit":"> 200 ms","tool":"_nodes/stats/indices (index_time/index_total)","note":"平均索引时间反映文档写入性能，由 index_time_in_millis 除以 index_total 计算得出。数值升高表明存在磁盘 IO 瓶颈、繁重的分析链或 refresh 压力。优化 bulk 大小和 refresh_interval 设置。","tags":["indexing","latency","performance"]},{"metric":"搜索延迟","warn":"> 100 ms","crit":"> 500 ms","tool":"_nodes/stats/indices (query_time/query_total)","note":"平均查询时间直接反映用户感受到的搜索性能，由 query_time_in_millis 除以 query_total 计算得出。数值升高可能源于繁重的聚合、分片过多或缓存为冷缓存。启用 slow log 找出高开销查询。","tags":["search","latency","performance"]},{"metric":"待处理任务","warn":"> 10","crit":"> 100","tool":"_cluster/pending_tasks","note":"待处理任务是主节点尚未应用的集群状态变更（mapping、分片分配等）。持续堆积表明主节点过载或集群状态过大。通过减少过多的分片数量来减轻主节点压力。","tags":["cluster","master","queue"]},{"metric":"断路器触发次数","warn":"> 0 (tripped)","crit":"> 10 / min","tool":"_nodes/stats/breakers","note":"断路器会取消请求，以防止某个操作消耗过多内存并使节点陷入 OutOfMemory。频繁触发表明繁重的聚合或大型请求造成了内存压力。监控 tripped 计数并优化有问题的查询。","tags":["memory","breaker","jvm"]},{"metric":"集群健康状态 (cluster status)","warn":"yellow","crit":"red","tool":"_cluster/health","note":"集群状态可以是 green（所有分片已分配）、yellow（缺少副本）或 red（缺少主分片，数据不可访问）。red 需要立即处理，因为部分数据已丢失或不可访问。yellow 表示丧失冗余，不可忽视。","tags":["cluster","health","shard"]},{"metric":"CPU 使用率","warn":"> 80%","crit":"> 90%","tool":"_nodes/stats/os / node_exporter","note":"持续的高 CPU 可能源于繁重的查询、过多的索引写入或 merge 操作。短暂峰值属正常，但持续饱和会导致查询和索引排队。使用 hot threads API (_nodes/hot_threads) 找出消耗 CPU 最多的操作。","tags":["cpu","load"]},{"metric":"最近一次成功快照的时长","warn":"> 24 小时","crit":"> 48 小时","tool":"_snapshot/_status / SLM","note":"快照时长决定了可恢复到的最近时间点以及 RPO。应通过 Snapshot Lifecycle Management (SLM) 确保自动、定期备份。如果失败或跳过的快照未被发现，灾难发生时将无法恢复数据；应定期验证快照状态。","tags":["backup","snapshot","rpo"]}]},"cloud/cloud-ops-1.json":{"category":"连接 & 备份 & 副本","categoryId":"cloud-core","order":1,"providers":[{"id":"aws-rds","name":"AWS RDS","color":"#B85C00"},{"id":"aws-aurora","name":"Aurora","color":"#7B3FE4"},{"id":"gcp-cloudsql","name":"Cloud SQL","color":"#C5372C"},{"id":"azure-sql","name":"Azure SQL","color":"#0067B8"},{"id":"mongodb-atlas","name":"Atlas","color":"#00843D"}],"concepts":[{"id":"list-instances","title":"列出实例 / 数据库","aliases":["list","instances","list all"],"providers":{"aws-rds":{"command":"aws rds describe-db-instances --query 'DBInstances[].DBInstanceIdentifier'","note":"列出所有 RDS 实例的标识符。添加 --region 以指定区域。","danger":"safe"},"aws-aurora":{"command":"aws rds describe-db-clusters --query 'DBClusters[].DBClusterIdentifier'","note":"列出 Aurora 集群标识符；查看 DBClusterMembers 字段以了解集群成员。","danger":"safe"},"gcp-cloudsql":{"command":"gcloud sql instances list","note":"列出项目中的所有 Cloud SQL 实例。添加 --project 以指定项目。","danger":"safe"},"azure-sql":{"command":"az sql db list -g <rg> -s <server>","note":"列出指定资源组 (-g) 和逻辑服务器 (-s) 中的数据库。使用 'az sql server list' 列出服务器。","danger":"safe"},"mongodb-atlas":{"command":"atlas clusters list","note":"列出 Atlas 项目中的所有集群。添加 --projectId 以指定项目。","danger":"safe"}}},{"id":"connect","title":"连接到实例（查找 endpoint + 连接）","aliases":["connect","endpoint","connection string","connect to"],"providers":{"aws-rds":{"command":"aws rds describe-db-instances --db-instance-identifier <id> --query 'DBInstances[0].Endpoint.Address'","note":"返回实例连接地址 (host)。从 Endpoint.Port 获取端口，并使用 psql/mysql 客户端连接。","danger":"safe"},"aws-aurora":{"command":"aws rds describe-db-clusters --db-cluster-identifier <id> --query 'DBClusters[0].Endpoint'","note":"返回集群的写入 endpoint。只读访问请使用 ReaderEndpoint 字段。","danger":"safe"},"gcp-cloudsql":{"command":"gcloud sql instances describe <instance> --format='value(ipAddresses[].ipAddress)'","note":"返回实例的 IP 地址。建议使用 Cloud SQL Auth Proxy 进行安全连接。","danger":"safe"},"azure-sql":{"command":"az sql db show-connection-string -s <server> -n <db> -c ado.net","note":"为指定的客户端类型（-c: ado.net、jdbc、odbc、php 等）生成连接字符串；用户/密码需自行填写。","danger":"safe"},"mongodb-atlas":{"command":"atlas clusters connectionStrings describe <clusterName>","note":"返回集群的标准连接字符串和 SRV 连接字符串。需要 IP 访问列表条目和数据库用户。","danger":"safe"}}},{"id":"take-snapshot","title":"执行手动备份 / 快照","aliases":["snapshot","backup","backup"],"providers":{"aws-rds":{"command":"aws rds create-db-snapshot --db-instance-identifier <id> --db-snapshot-identifier <snap-id>","note":"为实例创建手动快照（保留至手动删除）。可能出现短暂的 I/O 暂停。","danger":"caution"},"aws-aurora":{"command":"aws rds create-db-cluster-snapshot --db-cluster-identifier <id> --db-cluster-snapshot-identifier <snap-id>","note":"为 Aurora 集群创建手动快照。由于 Aurora 备份是连续的，这是一份固定副本。","danger":"caution"},"gcp-cloudsql":{"command":"gcloud sql backups create --instance=<instance>","note":"为实例创建按需备份。操作期间可能会对性能产生短暂影响。","danger":"caution"},"azure-sql":{"command":"az sql db export -s <server> -n <db> -u <user> -p <pwd> --admin-user <adm> --admin-password <pwd> --storage-key <key> --storage-key-type StorageAccessKey --storage-uri https://<acct>.blob.core.windows.net/<container>/<file>.bacpac","note":"Azure SQL 会自动备份；如需可移植的手动备份，此命令将数据库导出为 BACPAC。时间点还原是另一项独立功能。","danger":"caution"},"mongodb-atlas":{"command":"atlas backups snapshots create <clusterName>","note":"启动集群的按需备份快照。必须启用 Cloud Backup。","danger":"caution"}}},{"id":"restore-pitr","title":"还原到某个时间点 (point-in-time restore)","aliases":["pitr","restore","point-in-time","roll back"],"providers":{"aws-rds":{"command":"aws rds restore-db-instance-to-point-in-time --source-db-instance-identifier <src> --target-db-instance-identifier <new> --restore-time 2026-07-03T10:00:00Z","note":"基于源实例在指定时间的状态创建一个新实例；不会覆盖现有实例。使用 --use-latest-restorable-time 还原到最新时间点。","danger":"caution"},"aws-aurora":{"command":"aws rds restore-db-cluster-to-point-in-time --source-db-cluster-identifier <src> --db-cluster-identifier <new> --restore-to-time 2026-07-03T10:00:00Z","note":"将 Aurora 集群还原到指定时间，作为一个新集群。之后必须使用 create-db-instance 添加实例。","danger":"caution"},"gcp-cloudsql":{"command":"gcloud sql instances clone <src> <new> --point-in-time '2026-07-03T10:00:00Z'","note":"以新实例的形式创建源实例在指定时间的克隆。源实例上必须启用 Binary/WAL 日志。","danger":"caution"},"azure-sql":{"command":"az sql db restore -g <rg> -s <server> -n <db> --dest-name <new-db> --time '2026-07-03T10:00:00'","note":"将数据库还原到指定时间，作为一个新数据库；源数据库保持不变。时间为 UTC。","danger":"caution"},"mongodb-atlas":{"command":"atlas backups restores start pointInTime --clusterName <cluster> --pointInTimeUTCMillis <ms> --targetClusterName <target> --targetProjectId <projId>","note":"使用 Continuous Cloud Backup 启动到指定 UTC 时间的还原。目标集群将被覆盖；请谨慎操作。","danger":"danger"}}},{"id":"read-replica","title":"创建只读副本","aliases":["replica","read replica","read replica"],"providers":{"aws-rds":{"command":"aws rds create-db-instance-read-replica --db-instance-identifier <replica-id> --source-db-instance-identifier <src>","note":"从源实例创建异步只读副本。跨区域复制请使用 --source-region。","danger":"caution"},"aws-aurora":{"command":"aws rds create-db-instance --db-instance-identifier <reader-id> --db-cluster-identifier <cluster> --engine aurora-postgresql --db-instance-class db.r6g.large","note":"在 Aurora 中，reader 是添加到集群中的实例，共享集群存储。将 engine 值设为与集群一致 (aurora-mysql / aurora-postgresql)。","danger":"caution"},"gcp-cloudsql":{"command":"gcloud sql instances create <replica> --master-instance-name=<src> --region=<region>","note":"从主 (master) 实例创建只读副本。源实例上必须启用二进制日志。","danger":"caution"},"azure-sql":{"command":"az sql db replica create -g <rg> -s <server> -n <db> --partner-server <target-server>","note":"在另一台服务器上为数据库创建一个活动的异地复制可读辅助数据库。目标服务器必须已存在。","danger":"caution"},"mongodb-atlas":{"command":"atlas clusters update <clusterName> --file cluster.json","note":"Atlas 集群本身就是已复制的副本集。如需额外的读取能力，在 replicationSpecs 下添加 readOnly 或 analytics 节点并执行 update。","danger":"caution"}}},{"id":"failover","title":"故障转移","aliases":["failover","fail over","switch to standby"],"providers":{"aws-rds":{"command":"aws rds reboot-db-instance --db-instance-identifier <id> --force-failover","note":"在 Multi-AZ 实例上，强制重启以触发到备用实例的故障转移。会有短暂停机。","danger":"danger"},"aws-aurora":{"command":"aws rds failover-db-cluster --db-cluster-identifier <cluster> --target-db-instance-identifier <reader>","note":"将 Aurora 集群中的一个 reader 提升为新的 writer。如未指定目标，将选择合适的副本；会有短暂停机。","danger":"danger"},"gcp-cloudsql":{"command":"gcloud sql instances failover <instance>","note":"将 HA（高可用）实例故障转移到备用区域。仅适用于启用了 HA 的实例；会发生停机。","danger":"danger"},"azure-sql":{"command":"az sql failover-group set-primary -g <rg> -s <secondary-server> -n <fog-name>","note":"将自动故障转移组中的主角色转移到辅助服务器（计划内故障转移）。可使用 --allow-data-loss 强制执行，但有数据丢失风险。","danger":"danger"},"mongodb-atlas":{"command":"atlas clusters failover <clusterName>","note":"Atlas 会自动进行故障转移；此命令用于测试时手动触发故障转移。主节点会变更，应用必须重新连接。","danger":"danger"}}}]},"cloud/cloud-ops-2.json":{"category":"配置 & 扩缩容 & 监控","categoryId":"cloud-config","order":2,"providers":[{"id":"aws-rds","name":"AWS RDS","color":"#B85C00"},{"id":"aws-aurora","name":"Aurora","color":"#7B3FE4"},{"id":"gcp-cloudsql","name":"Cloud SQL","color":"#C5372C"},{"id":"azure-sql","name":"Azure SQL","color":"#0067B8"},{"id":"mongodb-atlas","name":"Atlas","color":"#00843D"}],"concepts":[{"id":"scale-instance","title":"更改实例规格（垂直扩展）","aliases":["scale","size","resize","scale up","vertical"],"providers":{"aws-rds":{"command":"aws rds modify-db-instance --db-instance-identifier <id> --db-instance-class db.r6g.xlarge --apply-immediately","note":"--apply-immediately 立即应用变更并重启实例（短暂停机）。不带此标志时，变更将在下一个维护时段应用。在 Multi-AZ 实例上，停机时间缩短至大约故障转移所需的时间。","danger":"caution"},"aws-aurora":{"command":"aws rds modify-db-instance --db-instance-identifier <writer-or-reader-id> --db-instance-class db.r6g.2xlarge --apply-immediately","note":"在 Aurora 中，每个实例（writer 和 reader）单独扩缩容；变更的是实例规格，而不是集群。先扩展 reader，然后通过故障转移替换 writer，以尽量减少停机。使用 Serverless v2 时，通过 min/max ACU 进行扩缩容。","danger":"caution"},"gcp-cloudsql":{"command":"gcloud sql instances patch <id> --tier db-custom-4-16384","note":"自定义机器类型以 db-custom-CPU-MEMORY 指定（示例：4 vCPU、16384 MB RAM）。变更会重启实例，造成短暂停机。也可以使用预定义层级（如 db-n1-standard-4）。","danger":"caution"},"azure-sql":{"command":"az sql db update -g <rg> -s <server> -n <db> --service-objective S3","note":"在 DTU 模型中使用 S3/P2 等服务目标；在 vCore 模型中使用 --edition GeneralPurpose --family Gen5 --capacity 4。扩缩容在线进行；操作接近结束时可能出现几秒钟的短暂连接中断。","danger":"caution"},"mongodb-atlas":{"command":"atlas clusters update <name> --tier M30","note":"Atlas 以滚动方式调整副本集节点规格，因此不会停机。需要 --projectId 标志或默认项目。降级时注意磁盘和内存限制。","danger":"caution"}}},{"id":"parameter-group","title":"更改参数 (parameter group / flags / server config)","aliases":["parameter","parameter group","flags","config","setting"],"providers":{"aws-rds":{"command":"aws rds modify-db-parameter-group --db-parameter-group-name <pg> --parameters \"ParameterName=max_connections,ParameterValue=500,ApplyMethod=pending-reboot\"","note":"动态参数使用 ApplyMethod=immediate，静态参数使用 pending-reboot。参数组必须关联到实例；静态参数变更在重启后生效。使用 create-db-parameter-group 创建参数组。","danger":"caution"},"aws-aurora":{"command":"aws rds modify-db-cluster-parameter-group --db-cluster-parameter-group-name <cpg> --parameters \"ParameterName=innodb_lock_wait_timeout,ParameterValue=100,ApplyMethod=immediate\"","note":"在 Aurora 中，集群级参数通过 DB 集群参数组管理，实例级参数通过 DB 参数组 (modify-db-parameter-group) 管理。静态参数需要在整个集群范围内重启。","danger":"caution"},"gcp-cloudsql":{"command":"gcloud sql instances patch <id> --database-flags max_connections=500,log_min_duration_statement=1000","note":"--database-flags 一次性设置所有标志；之前设置但不在列表中的标志会被重置，因此应将它们全部一起指定。某些标志需要重启实例。使用 'gcloud sql instances describe <id>' 查看当前标志。","danger":"caution"},"azure-sql":{"command":"az sql db configure -g <rg> -s <server> -n <db> ...   # DB-level settings via T-SQL: ALTER DATABASE SCOPED CONFIGURATION SET MAXDOP = 4;","note":"在 Azure SQL 中，设置大多通过 T-SQL 而非传统的服务器配置来应用：ALTER DATABASE SCOPED CONFIGURATION ...；服务器级的 sp_configure 受限。弹性池/数据库选项请使用 'az sql db update' 的标志。","danger":"caution"},"mongodb-atlas":{"command":"atlas clusters advancedSettings update <name> --oplogMinRetentionHours 24 --oplogSizeMB 20480","note":"在 Atlas 中，底层 MongoDB 进程参数只能通过 'advancedSettings' 进行有限设置（oplog、全文搜索、索引深度等）。大多数服务器参数由 Atlas 管理，无法直接更改。","danger":"caution"}}},{"id":"monitoring","title":"查看指标和性能","aliases":["monitoring","metric","observability","performance","insights"],"providers":{"aws-rds":{"command":"aws cloudwatch get-metric-statistics --namespace AWS/RDS --metric-name CPUUtilization --dimensions Name=DBInstanceIdentifier,Value=<id> --start-time 2026-07-05T00:00:00Z --end-time 2026-07-05T01:00:00Z --period 300 --statistics Average","note":"基本指标（CPU、连接、IOPS、延迟）位于 CloudWatch AWS/RDS 命名空间。如需查询级分析，请启用 Performance Insights：'aws pi get-resource-metrics --service-type RDS ...'，或使用控制台中的 Performance Insights 仪表板。","danger":"safe"},"aws-aurora":{"command":"aws cloudwatch get-metric-statistics --namespace AWS/RDS --metric-name AuroraReplicaLag --dimensions Name=DBInstanceIdentifier,Value=<reader-id> --start-time 2026-07-05T00:00:00Z --end-time 2026-07-05T01:00:00Z --period 60 --statistics Maximum","note":"Aurora 特有指标 (AuroraReplicaLag、VolumeReadIOPs、CommitLatency、buffer cache hit) 同样位于 AWS/RDS 命名空间。建议在 Aurora 上使用 Performance Insights，它提供 top SQL 和 wait event 分析。","danger":"safe"},"gcp-cloudsql":{"command":"gcloud monitoring time-series list --filter='metric.type=\"cloudsql.googleapis.com/database/cpu/utilization\" AND resource.labels.database_id=\"<project>:<id>\"' --interval-start-time=2026-07-05T00:00:00Z --interval-end-time=2026-07-05T01:00:00Z","note":"Cloud SQL 指标位于 Cloud Monitoring 中的 cloudsql.googleapis.com/... 下。如需查询级分析，请启用 Cloud SQL Query Insights（控制台或 API）；它会显示最慢的查询和 wait event。","danger":"safe"},"azure-sql":{"command":"az monitor metrics list --resource <db-resource-id> --metric \"cpu_percent\" --interval PT5M --start-time 2026-07-05T00:00:00Z --end-time 2026-07-05T01:00:00Z","note":"cpu_percent、dtu_consumption_percent、storage_percent 和 connection_successful 等指标来自 Azure Monitor。查询性能请使用 Query Performance Insight 和 Intelligent Insights (Azure SQL Analytics) 仪表板。","danger":"safe"},"mongodb-atlas":{"command":"atlas metrics processes <host>:<port> --granularity PT5M --period P1D --type SYSTEM_CPU_USER,QUERY_TARGETING_SCANNED_OBJECTS_PER_RETURNED","note":"进程级度量通过 'atlas metrics processes' 获取，磁盘度量通过 'atlas metrics disks' 获取。慢查询建议请使用 Performance Advisor：'atlas performanceAdvisor slowQueryLogs list' 和 'atlas performanceAdvisor suggestedIndexes list'。","danger":"safe"}}},{"id":"maintenance-window","title":"维护时段与更新管理","aliases":["maintenance","maintenance window","window","update","patch"],"providers":{"aws-rds":{"command":"aws rds modify-db-instance --db-instance-identifier <id> --preferred-maintenance-window Sun:03:00-Sun:04:00   # apply pending maintenance now: aws rds apply-pending-maintenance-action --resource-identifier <arn> --apply-action system-update --opt-in-type immediate","note":"时段使用 UTC，格式为 ddd:hh24:mi-ddd:hh24:mi，且至少为 30 分钟。使用 'aws rds describe-pending-maintenance-actions' 列出待处理的维护，并通过 apply-pending-maintenance-action 立即应用或在下一个时段应用。","danger":"caution"},"aws-aurora":{"command":"aws rds modify-db-cluster --db-cluster-identifier <cluster> --preferred-maintenance-window Sun:03:00-Sun:04:00","note":"在 Aurora 中，集群级维护时段使用 modify-db-cluster 设置，实例级时段使用 modify-db-instance 设置。待处理的引擎/OS 更新通过 apply-pending-maintenance-action 管理；引擎版本升级可能需要停机。","danger":"caution"},"gcp-cloudsql":{"command":"gcloud sql instances patch <id> --maintenance-window-day SUN --maintenance-window-hour 3 --maintenance-release-channel production","note":"设置日期 (MON..SUN)、小时（0-23，实例本地/UTC 时间）以及发布通道（production 或 preview）。使用 --deny-maintenance-period-* 标志还可以定义一个不执行任何维护的时间段。","danger":"caution"},"azure-sql":{"command":"az sql db update -g <rg> -s <server> -n <db> --maint-config-id SQL_Default   # or custom: /subscriptions/<sub>/providers/Microsoft.Maintenance/publicMaintenanceConfigurations/SQL_<Region>_DB_1","note":"Azure SQL 维护时段通过分配预定义的 Maintenance Configuration 来管理（以工作日/周末晚间时段替代默认时段）。使用 'az maintenance public-configuration list' 列出配置。","danger":"caution"},"mongodb-atlas":{"command":"atlas maintenanceWindows update --dayOfWeek 1 --hourOfDay 3 --projectId <projectId>   # defer: atlas maintenanceWindows defer","note":"dayOfWeek 1=Sunday ... 7=Saturday，hourOfDay 0-23 (UTC)。使用 'atlas maintenanceWindows defer' 将待处理的维护推迟一周，使用 'atlas maintenanceWindows start' 立即开始维护。","danger":"caution"}}},{"id":"encryption","title":"查看/配置静态加密 (at-rest) 状态","aliases":["encryption","encryption at rest","at-rest","kms","tde","cmek"],"providers":{"aws-rds":{"command":"aws rds describe-db-instances --db-instance-identifier <id> --query 'DBInstances[0].[StorageEncrypted,KmsKeyId]'   # at creation: aws rds create-db-instance ... --storage-encrypted --kms-key-id <kms-arn>","note":"RDS 静态加密只能在创建实例时启用；如需在现有未加密实例上启用，应创建快照的加密副本并从中还原。密钥由 AWS KMS 管理。","danger":"caution"},"aws-aurora":{"command":"aws rds describe-db-clusters --db-cluster-identifier <cluster> --query 'DBClusters[0].[StorageEncrypted,KmsKeyId]'   # at creation: aws rds create-db-cluster ... --storage-encrypted --kms-key-id <kms-arn>","note":"在 Aurora 中，加密在集群级设置，且只能在创建时设置。如需加密现有未加密集群，应将快照复制为加密副本并进行还原。所有实例和备份使用同一个 KMS 密钥。","danger":"caution"},"gcp-cloudsql":{"command":"gcloud sql instances create <id> --disk-encryption-key projects/<project>/locations/<region>/keyRings/<ring>/cryptoKeys/<key>   # to view: gcloud sql instances describe <id> --format='value(diskEncryptionConfiguration.kmsKeyName)'","note":"Cloud SQL 默认使用 Google 管理的密钥加密；客户管理的密钥 (CMEK) 只能在创建实例时通过 --disk-encryption-key 指定。必须授予 Cloud SQL 服务账号对该密钥的加密/解密权限。","danger":"caution"},"azure-sql":{"command":"az sql db tde set -g <rg> -s <server> -d <db> --status Enabled   # or T-SQL: ALTER DATABASE <db> SET ENCRYPTION ON;   # status: az sql db tde show -g <rg> -s <server> -d <db>","note":"在 Azure SQL 中，静态加密由 Transparent Data Encryption (TDE) 提供，新数据库默认启用。如需使用自己的密钥，在服务器级配置 'az sql server tde-key set' (Azure Key Vault / BYOK)。","danger":"caution"},"mongodb-atlas":{"command":"atlas clusters describe <name> --output json | jq '.encryptionAtRestProvider'   # enable BYOK: atlas encryptionAtRest enable AWS --awsKmsMasterKeyID <keyId> --awsKmsRegion <region> --awsKmsRoleID <roleId>","note":"在 Atlas 中，数据默认进行静态加密。此外，可通过 'atlas encryptionAtRest' 在项目范围内启用基于 AWS KMS / Azure Key Vault / GCP KMS 的客户密钥 (BYOK) 信封加密，并应用到集群。","danger":"caution"}}},{"id":"storage-autoscale","title":"扩展存储 / 启用自动扩展","aliases":["storage","disk space","autoscale","grow","disk"],"providers":{"aws-rds":{"command":"aws rds modify-db-instance --db-instance-identifier <id> --allocated-storage 200 --max-allocated-storage 1000 --apply-immediately","note":"--allocated-storage 设置当前大小；--max-allocated-storage 设置存储自动扩展的上限。存储扩展在线进行；但两次存储修改之间必须至少间隔 6 小时，且存储无法缩小。","danger":"caution"},"aws-aurora":{"command":"# Aurora storage is automatic; no manual setting required\naws rds describe-db-clusters --db-cluster-identifier <cluster> --query 'DBClusters[0].AllocatedStorage'","note":"Aurora 存储完全自动扩展（以 10 GB 为增量，根据引擎最多可达 128 TB），随使用量增长；无需设置 --allocated-storage / --max-allocated-storage。只需监控使用量，无需管理大小。","danger":"safe"},"gcp-cloudsql":{"command":"gcloud sql instances patch <id> --storage-auto-increase --storage-size 200GB   # upper limit: --storage-auto-increase-limit 1000","note":"--storage-auto-increase 在磁盘接近已满时启用自动增长；--storage-auto-increase-limit 设置上限 (0 = 无限制)。存储只能增加，不能减少；请谨慎选择初始大小和上限。","danger":"caution"},"azure-sql":{"command":"az sql db update -g <rg> -s <server> -n <db> --max-size 500GB","note":"在 Azure SQL 中，数据库最大大小通过 --max-size 设置；数据库在分配的空间内自动增长。最大大小取决于所选的服务层级/vCore 限制。在 Hyperscale 层级中，存储自动扩展，几乎没有上限。","danger":"caution"},"mongodb-atlas":{"command":"atlas clusters update <name> --diskSizeGB 200   # for autoscaling, diskGBEnabled=true via extended configuration (advancedSettings / API)","note":"磁盘大小通过 --diskSizeGB 增加。在 Atlas 中，M10 及更大的集群默认启用存储自动扩展 (Auto-scale storage)；磁盘使用率达到 90% 时会自动增长。启用/禁用在集群配置中管理 (autoScaling.diskGBEnabled)。","danger":"caution"}}}]},"guide/best-practices.json":{"topics":[{"id":"indexing-strategy","title":"索引策略","category":"性能","summary":"索引可加快读取查询，但会增加写入和存储开销；应根据需要和测量结果添加。","points":["针对 WHERE、JOIN 和 ORDER BY 列；在复合索引中将选择性最高的列放在最前面（最左前缀规则）。","使用覆盖索引：将查询所需的所有列都包含在索引中，以消除回表访问 (lookup)。","识别并删除未使用和冗余的索引（重复索引或其他索引的前缀）；每个索引都会拖慢 INSERT/UPDATE/DELETE。","避免在低基数列（如性别、布尔值）上创建单独的 B-tree 索引；如有需要，使用部分/筛选索引。","使用 EXPLAIN/EXPLAIN ANALYZE 验证索引确实被使用；函数或类型转换会使索引失效 (如 WHERE UPPER(name))。"],"level":"junior"},{"id":"normalization","title":"规范化 vs 反规范化","category":"设计","summary":"规范化确保数据完整性且无冗余；反规范化是为读取性能而做出的有意取舍。","points":["在 OLTP 系统中默认以 3NF 为目标；防止数据冗余和更新异常。","仅在存在经测量确认的性能瓶颈时才进行反规范化，并说明理由。","通过触发器、物化视图或应用层保证反规范化数据的一致性。","对于报表/分析负载，考虑使用单独的反规范化模式（星型模式）或只读副本。","过早的反规范化是技术债；应先尝试通过合适的索引和查询解决问题。"],"level":"senior"},{"id":"data-types","title":"选择正确的数据类型","category":"设计","summary":"正确且尽可能小的数据类型可直接改善存储、索引大小和性能。","points":["选择满足需求的最小类型：年龄用 SMALLINT，金额用 DECIMAL/NUMERIC（绝不使用 FLOAT）。","时间数据始终带时区存储 (TIMESTAMPTZ / UTC)；避免存储本地时间。","对于固定、有限的值集合，使用 ENUM 或查找表；加以约束，而不是允许自由文本。","对于变长文本，按实际需要确定长度或使用 TEXT，而不是不必要的大 VARCHAR(255)。","如果使用 UUID 作为主键，注意索引碎片；考虑使用顺序 UUID (v7) 或 BIGINT identity。","有意识地选择 NULL 语义；在设计中明确区分“未知”与“空”。"],"level":"junior"},{"id":"naming-conventions","title":"命名规范","category":"设计","summary":"一致的命名使可读性、维护和团队沟通更容易；从一开始就确定标准。","points":["选定一种规范并处处应用：snake_case 应用广泛，且不易受大小写敏感问题影响。","确定表名使用单数还是复数；优先使用有意义、不缩写的列名。","外键命名应能明显看出来源（如 customer_id），并为索引/约束使用标准前缀 (fk_、ix_、uq_)。","避免使用保留字 (order、user、group) 作为表名/列名。","布尔列使用 is_/has_ 前缀使其含义明确；日期列添加 _at、_date 等一致的后缀。"],"level":"junior"},{"id":"partitioning-strategy","title":"分区策略","category":"性能","summary":"分区将大表拆分为易于管理的部分；可改善维护、数据清除和查询性能。","points":["根据查询和归档模式选择分区键；对于时间序列，基于日期的 RANGE 分区最常见。","验证分区裁剪是否生效：查询的 WHERE 子句必须包含分区键，否则会扫描所有分区。","不要删除旧数据，而是通过分区 DROP/DETACH 即时、几乎无锁地清除。","分区数量过多会增加规划和元数据开销；选择合理的粒度（按月/按周）。","设置自动创建未来分区的维护作业；防止因写入没有匹配分区而出错。"],"level":"senior"},{"id":"connection-pooling","title":"连接池","category":"运维","summary":"连接池消除了建立连接的高昂开销，并保护数据库免受连接洪峰冲击。","points":["不要为每个请求打开新连接；在应用中或中间层 (PgBouncer、HikariCP) 使用连接池。","根据 CPU 核数和负载确定连接池大小；过大的连接池会压垮数据库（通常小连接池更好）。","有意识地设置连接生命周期、空闲和获取超时值；启用泄漏检测。","根据数据库 max_connections 上限规划连接池；计入多个应用实例的总和。","使用短事务，使连接尽快归还连接池；不要在事务内等待外部 I/O。"],"level":"senior"},{"id":"transaction-design","title":"事务设计（简短且原子）","category":"性能","summary":"事务应简短、原子，并覆盖单个逻辑工作单元；这样可将锁持有时间降到最低。","points":["事务尽可能简短；不要将用户交互、外部 API 调用或长时间计算包含在事务中。","只将必须同时成功或同时失败的操作放在同一个事务中。","始终按相同顺序更新行，以减少锁冲突（这是防止死锁的基础）。","避免不必要的大范围锁；不要将只读操作放入不必要的事务块中。","为错误定义明确的回滚路径；不要留下部分提交或未结束的事务。","长时间的批量操作应分批提交；单个巨大事务会导致 WAL/undo 膨胀。"],"level":"senior"},{"id":"isolation-levels","title":"选择隔离级别","category":"性能","summary":"隔离级别决定一致性与并发之间的平衡；应根据业务规则有意识地选择。","points":["了解异常类型：脏读、不可重复读、幻读；理解哪个级别能防止哪种异常。","Read Committed 对大多数 OLTP 已足够；仅在确有需要时才提高级别。","对于关键的资金/库存操作，使用 Serializable 或显式锁定 (SELECT ... FOR UPDATE)。","在 Serializable 下，为序列化失败添加自动重试逻辑。","了解 MVCC 引擎与基于锁的引擎之间的行为差异；同一级别在不同引擎上可能提供不同的保证。"],"level":"expert"},{"id":"deadlock-prevention","title":"防止死锁","category":"性能","summary":"当事务相互等待对方的资源时就会发生死锁；通过设计可以在很大程度上加以预防。","points":["始终以一致、相同的顺序访问资源/行；顺序不一致是死锁最常见的根源。","保持事务简短并减少锁的数量；避免不必要的大范围更新。","必要时通过提前加锁 (SELECT ... FOR UPDATE) 使冲突可预测。","在应用中捕获死锁错误并自动重试事务；完全消除死锁通常是不可能的。","定期检查死锁日志/死锁图；通过调整索引或访问顺序消除反复出现的模式。"],"level":"expert"},{"id":"sharding-strategy","title":"分片 / 水平扩展策略","category":"设计","summary":"分片通过将数据分布到多个节点来突破单服务器的限制；由于会显著增加复杂度，应作为最后手段。","points":["先用尽垂直扩展、索引、缓存和只读副本等手段；仅在确有必要时才分片。","谨慎选择分片键：它必须使跨分片查询少、分布均匀，且不产生热点。","使用一致性哈希或虚拟分片进行再平衡 (resharding)；从一开始就为增长做规划。","跨分片 JOIN 和分布式事务开销很高；设计模式时应尽量减少它们。","解决全局唯一 ID 生成（snowflake 风格的顺序 ID 等）和分片路由层的问题。"],"level":"expert"},{"id":"backup-strategy","title":"备份策略 (3-2-1)","category":"备份 & HA","summary":"可靠的备份策略是抵御灾难的最后一道防线；备份在经过测试之前不算数。","points":["遵循 3-2-1 规则：3 份副本、2 种不同介质、1 份异地副本（offsite/不同区域）。","将全量 + 增量备份与 WAL/binlog 归档结合，实现时间点恢复（PITR）。","定期通过实际恢复来测试备份；未经测试的备份不算备份。","对备份进行静态加密和传输加密；限制访问，并按策略设定保留期。","保留一份不可变或物理隔离（air-gapped）的副本以防范勒索软件。"],"level":"junior"},{"id":"hadr-design","title":"HA/DR 设计与 RPO/RTO","category":"备份 & HA","summary":"高可用与容灾设计始于将业务目标定义为 RPO 和 RTO 指标。","points":["与业务方共同确定 RPO（可接受的数据丢失）和 RTO（可接受的停机时间）目标。","同步复制可实现接近零的 RPO，但会增加延迟；异步复制性能好，但存在数据丢失风险。","配置自动故障转移，并通过定期故障转移演练验证其确实有效。","使用仲裁/见证（quorum/witness）机制防止脑裂；绝不允许两个节点同时成为主节点。","将 DR 环境部署在不同的物理区域；能够承受单个区域/数据中心故障。"],"level":"expert"},{"id":"capacity-planning","title":"容量规划与增长","category":"运维","summary":"容量规划通过按增长趋势提前准备资源，避免意外中断。","points":["持续测量数据增长率、IOPS、CPU、内存和连接使用情况，并得出趋势。","为磁盘使用率设置早期阈值告警（例如 75%/85%）；在空间耗尽前采取行动。","基于预测规划增长；考虑峰值负载和季节性波动。","定期检查连接数、表/索引膨胀以及归档需求。","在达到瓶颈之前确定扩展路径（垂直扩展、副本、分区/分片）。"],"level":"senior"},{"id":"migration-strategy","title":"迁移策略（最短停机时间）","category":"运维","summary":"模式和数据迁移应遵循可回退的计划，尽可能在线进行，将停机时间降至最低。","points":["将每次迁移设计为可回退且幂等；没有回滚计划绝不上线生产。","避免在大表上执行阻塞式 DDL；使用在线模式变更工具（PG：pg-osc/pgroll；MySQL：gh-ost、pt-osc）。","对向后/向前兼容的变更采用 expand-contract 模式：先添加，再部署，最后删除旧的部分。","以小批次迁移大量数据；不要用单个巨型 UPDATE 造成锁和 WAL 膨胀。","先在类生产环境中以真实数据量演练迁移，并测量其耗时。","提前将切换时间点、验证步骤和回滚决策写入书面计划。"],"level":"senior"},{"id":"schema-versioning","title":"模式版本管理（Flyway/Liquibase）","category":"运维","summary":"模式变更应通过与代码共存、有版本控制并自动应用的迁移来管理；手动干预容易出错。","points":["使用 Flyway/Liquibase 等工具对所有模式变更进行版本管理，并纳入源代码控制。","不要在生产环境手动执行 DDL；每项变更都必须通过经过评审的迁移文件。","保持迁移有序且唯一；绝不修改已应用的迁移文件，应通过新的迁移进行修复。","在 CI/CD 流水线中自动应用并验证迁移；确保各环境间的模式一致性。","让应用代码与模式版本同步推进；对向后兼容的过渡采用 expand-contract 模式。"],"level":"senior"},{"id":"least-privilege","title":"最小权限原则","category":"安全","summary":"每个用户和应用只应拥有完成其工作所需的权限；多余的权限会扩大攻击面。","points":["为应用使用独立的受限权限账户；绝不以 superuser/admin 身份连接。","将权限授予角色而不是直接授予用户；通过角色成员关系管理访问。","仅允许所需的表和操作（SELECT/INSERT/UPDATE）；避免宽泛的 GRANT ALL。","定期审查权限并回收未使用的权限；员工/服务离开时立即移除其访问权限。","不要在代码中嵌入机密信息（密码、连接字符串）；使用机密管理器（vault）并定期轮换。"],"level":"junior"},{"id":"encryption-basics","title":"加密基础（静态/传输中）","category":"安全","summary":"数据在存储和传输时都应加密；加密是满足合规和防止数据泄露的基本保护。","points":["强制传输使用 TLS；拒绝未加密的连接并启用证书验证。","实施静态加密（TDE 或磁盘/卷加密），使被盗的磁盘/备份无法被读取。","认真对待密钥管理：将密钥保存在独立的 KMS/HSM 中并定期轮换。","对信用卡号、身份证号等高度敏感字段，在应用层额外进行加密（字段级）。","同样加密备份；将生产环境的保护扩展到备份和副本。","在测试/开发环境中对敏感数据进行脱敏或匿名化（data masking）。"],"level":"senior"},{"id":"monitoring-basics","title":"监控基础（黄金信号）","category":"运维","summary":"有效的监控意味着采集正确的指标并基于有意义的阈值告警；在用户察觉之前发现问题。","points":["监控黄金信号：延迟、流量、错误率和饱和度。","增加数据库特有的指标：慢查询、锁/等待时间、连接数、缓存命中率、复制延迟。","将告警与影响和可操作的阈值挂钩，而非症状；嘈杂的告警会导致告警疲劳。","始终启用慢查询日志和查询统计（如 pg_stat_statements），并定期审查。","保留历史指标；为趋势分析和容量规划建立基线。","务必对剩余磁盘空间、备份是否成功等关键状况设置告警。"],"level":"junior"},{"id":"query-design","title":"查询设计与 N+1 防范","category":"性能","summary":"高效的查询以最少的往返只获取所需数据；N+1 是最常见、最隐蔽的性能缺陷。","points":["只选择所需列而不是 SELECT *；这可以改善网络、内存开销并增加覆盖索引的机会。","防止 N+1 问题：用 JOIN 或批量（IN）查询代替循环中的单条查询；在 ORM 中启用预加载（eager loading）。","对大结果集进行分页；在大数据量上优先使用 keyset（seek）分页而非 OFFSET。","尽可能将处理逻辑交给查询；不要取出整张表再在应用中过滤/聚合。","用 EXPLAIN ANALYZE 检查实际执行计划和行数估算；消除全表扫描和错误估算。","为频繁且开销大的聚合查询配备物化视图或预计算汇总表。"],"level":"junior"},{"id":"large-table-management","title":"大表管理（归档/分区）","category":"性能","summary":"持续增长的表会随时间拖慢查询、维护和备份；通过归档和分区加以控制。","points":["区分热（活跃）数据与冷（历史）数据；将旧数据移至归档表或低成本存储。","按日期/范围对表进行分区；用分区 DROP 代替 DELETE，瞬间清除旧数据。","以小批次执行大规模删除/更新；单个巨型 DELETE 会产生锁、WAL 膨胀和表膨胀。","定期监控膨胀和表/索引大小；必要时执行在线重组（VACUUM/reorg）。","保留适合高频查询的索引；在大表上维护不必要的索引代价非常高。"],"level":"senior"},{"id":"caching-strategy","title":"缓存策略","category":"性能","summary":"缓存将重复读取从数据库转移出去，从而降低延迟和负载；真正的难点在于失效处理。","points":["将缓存用于开销大、读取频繁、很少变化的数据；不要试图缓存一切。","选择失效策略：TTL、写时更新（write-through）或写时删除（write-invalidate）。","根据业务规则确定对过期数据的容忍度；在要求强一致性的场景中不要依赖缓存。","通过加锁或请求合并防止缓存击穿（大量请求同时填充空缓存）。","监控缓存命中率；命中率低说明缓存没有发挥价值。","缓存是加速层，而非持久状态；缓存丢失时系统必须仍能正确运行。"],"level":"senior"},{"id":"statistics-maintenance","title":"统计信息与维护纪律","category":"运维","summary":"最新的统计信息和定期维护可确保查询优化器做出正确决策，性能不会随时间下降。","points":["保持统计信息最新（ANALYZE / auto-analyze）；过时的统计信息会让优化器选择错误的执行计划。","定期运行 VACUUM/清理以清除死行和膨胀；这对 MVCC 引擎至关重要。","监控索引碎片，必要时在线重建（REINDEX/rebuild）。","批量加载后手动更新统计信息；避免优化器基于旧的数据分布工作。","将维护作业安排在低流量时段，监控其耗时/影响并实现自动化。","警惕执行计划回归（查询突然变慢）；首先检查统计信息是否新鲜。"],"level":"senior"}]},"guide/best-practices-2.json":{"topics":[{"id":"iac","title":"以代码管理基础设施（IaC）","category":"运维","summary":"数据库服务器、网络和配置应以有版本控制的代码定义，而非手工配置，从而使环境可复现、可审计、可回退。","points":["使用 Terraform/Pulumi/CloudFormation 等工具将服务器、参数组、网络和用户定义作为代码保存，并纳入源代码控制。","禁止手动控制台变更（click-ops）；执行漂移检测并修复偏离代码的资源。","通过变量管理各环境（dev/stage/prod）之间的差异；用同一模块构建类生产环境。","将状态文件保存在远端并加锁（例如 S3 + DynamoDB lock）；不要让机密以明文形式泄露到状态中。","通过 plan/apply 流程审查变更；在生产环境前设置审批关卡。"],"level":"senior"},{"id":"automation-idempotency","title":"自动化与幂等性","category":"运维","summary":"运维任务应从手动重复执行的命令转变为幂等的自动化，无论运行多少次都产生相同的结果。","points":["将每个脚本写成幂等的，使重复运行既不会失败也不会破坏状态（例如用 CREATE IF NOT EXISTS 代替 CREATE）。","将周期性维护任务（备份、清理、统计信息）交给有调度、有日志的自动化；不要依赖手动执行。","在脚本中内置明确的前置条件检查和安全退出；重新运行未完成的操作时应从中断处继续完成。","通过代码运行自动化（Ansible/脚本 + CI）；不要依赖有人在生产环境的终端中输入命令。","为关键操作提供 dry-run 支持；在执行前展示其影响。"],"level":"senior"},{"id":"observability","title":"可观测性（指标/日志/追踪）","category":"运维","summary":"可观测性将指标、日志和分布式追踪结合起来，让你从“发生了什么”深入到“为什么发生”。","points":["同时采集三种信号：指标（趋势）、日志（上下文）、追踪（请求路径）；任何一种单独都无法揭示根因。","端到端追踪查询：将应用追踪与数据库延迟关联起来（使用 OpenTelemetry 等标准）。","输出带有 correlation/trace id 的结构化（JSON）日志；自由文本日志难以检索。","避免高基数标签；控制基数爆炸和成本。","定义 SLO 并跟踪错误预算；将可观测性与业务目标挂钩。"],"level":"senior"},{"id":"alerting-strategy","title":"告警策略（低噪声、可操作）","category":"运维","summary":"好的告警策略只在真实且可操作的情况下唤醒人；噪声会导致告警疲劳和倦怠。","points":["基于影响和用户体验（SLO 违规）而非症状告警；不要因每次指标波动而呼叫值班。","每条告警都需要明确的负责人、条件和 runbook；没有既定处置动作的告警毫无价值。","在阈值上使用迟滞和持续时间（for: 5m）；避免瞬时尖峰触发告警。","区分呼叫（paging）与通知（工单/邮件）；只对需要立即干预的情况进行呼叫。","定期审查告警；修复或删除那些持续触发却被忽视的告警。","务必覆盖备份失败、磁盘已满、复制停止等无声的灾难。"],"level":"senior"},{"id":"dr-testing","title":"DR/故障转移演练纪律","category":"备份 & HA","summary":"只有经过定期、真实演练验证的容灾计划才可靠；未经测试的计划只是一种假设。","points":["安排故障转移和恢复演练并定期执行；一年审阅一次的纸面计划是不够的。","在每次演练中测量实际 RTO 和 RPO；报告与目标的偏差并加以改进。","在类生产环境中以真实流量演练计划内故障转移；不要只是启动和停止备库。","通过 game day 模拟区域丢失、磁盘故障和数据损坏等场景。","每次演练后将经验教训记录到 runbook 中；把凭感觉记住的步骤变成自动化。"],"level":"expert"},{"id":"blue-green-deploy","title":"蓝绿 / 零停机部署","category":"运维","summary":"蓝绿部署和金丝雀部署让新版本与旧版本并行运行并逐步切换流量，将停机时间和风险降至最低。","points":["维护两套生产环境（blue/green）；将新版本部署到空闲的一套，验证后一步切换流量。","出现问题时立即将流量切回旧环境；回滚不应需要重新部署。","使用金丝雀时，将一小部分流量发送到新版本，观察指标并逐步增加。","将数据库模式与部署解耦：两个代码版本都必须能在同一模式下运行（expand-contract）。","提前定义健康检查和自动回滚阈值；缩短等待人工决策的时间。"],"level":"senior"},{"id":"expand-contract","title":"向后兼容的模式变更（expand-contract）","category":"设计","summary":"expand-contract 模式将模式变更拆分为添加（expand）、迁移、最后删除旧结构（contract），实现零停机过渡。","points":["先扩展：以向后兼容的方式（可为空或带默认值）添加新列/表，不破坏旧结构。","部署应用，使其通过双写或双读同时兼容新旧模式。","在后台以小批次将数据迁移到新结构并验证；不要用一次巨型迁移造成锁。","只有在所有实例都切换到新版本并完成验证后才收缩：删除旧列/旧代码。","每一步都在单独的部署中完成；绝不要“同时添加新的并删除旧的”，否则将无法回滚。","在数据一致之后，最后再添加 NOT NULL 等约束。"],"level":"expert"},{"id":"secrets-management","title":"机密管理（密码/密钥）","category":"安全","summary":"数据库密码、API 密钥和连接字符串不得嵌入代码或配置中；应通过集中式机密管理器安全存储并轮换。","points":["不要在代码、仓库、env 文件或镜像中嵌入机密；使用 Vault、AWS Secrets Manager 或 KMS 等机密存储。","在运行时将机密注入应用；不要以明文形式留在磁盘上。","定期轮换密码和密钥；尽可能使用短期、自动生成的凭据。","以最小权限并配合审计日志授予访问；必须能追溯谁在何时访问了哪个机密。","扫描代码库中泄露的机密（git-secrets、trufflehog）；立即吊销并轮换已泄露的机密。","让人员访问成为临时且基于审批的（just-in-time），而非个人永久持有。"],"level":"senior"},{"id":"cost-optimization","title":"云成本优化","category":"运维","summary":"通过合理调整规格、存储分层和清理未使用的资源，可以在不牺牲性能的前提下显著降低云数据库成本。","points":["根据实际使用指标确定实例规格；对长期低利用率的服务器降配（right-sizing）。","对可预测的基础负载使用预留容量/节省计划；可变负载保持按需计费。","将冷数据移至更便宜的存储层；将热数据与很少访问的数据分开。","清理未使用的资源：孤立的快照、闲置的副本和废弃的测试数据库都是成本漏洞。","通过标签将成本归属到负责人并设置预算告警；立即发现意外，而不是等到月底。","根据业务需求审查备份保留期和副本数量；过多既是风险也是成本。"],"level":"senior"},{"id":"multi-tenancy","title":"多租户设计","category":"设计","summary":"在多租户系统中，隔离模型（共享表、独立模式、独立数据库）是在安全、成本和可扩展性之间的有意权衡。","points":["选择隔离模型：共享表（tenant_id）、每租户一个模式或每租户一个数据库；每种方案的成本/隔离平衡各不相同。","在共享模型中，对每个查询应用 tenant_id 过滤；通过行级安全（RLS）在引擎层面保证不发生泄露。","在相关索引中将 tenant_id 放在首位；防止跨租户扫描和热点。","针对吵闹邻居效应设置资源限制和配额；一个租户不得耗尽其他租户的资源。","从一开始就设计将大型或高级租户迁移到独立模式/数据库的路径（租户迁移）。","按租户考虑备份、删除和数据可移植性；必须能够响应 KVKK/GDPR 删除请求。"],"level":"expert"},{"id":"time-series-design","title":"时序数据设计","category":"设计","summary":"时序数据因写入量大和基于时间的查询模式而特殊；应采用基于日期的分区、汇总（rollup）和保留策略进行设计。","points":["使用基于时间戳的 RANGE 分区拆分数据；用分区 DROP 低成本清除旧数据。","不要原样堆积原始数据，而应生成固定间隔的汇总（rollup/continuous aggregate）；让查询在汇总数据上快速运行。","在索引中将时间和标签（tag/source）组件放在前面；典型查询模式是“特定来源、特定范围”。","选择合适的工具：在大数据量下，TimescaleDB、ClickHouse 等时序引擎可能比关系型引擎高效得多。","从一开始就定义保留策略；原始数据短期保留，聚合数据长期保留。","始终以 UTC 存储时间；在查询层处理本地时间和夏令时变化。"],"level":"senior"},{"id":"event-sourcing","title":"事件溯源 / CQRS 基础","category":"设计","summary":"事件溯源记录的不是状态，而是改变状态的事件；CQRS 将读模型和写模型分离，使二者各自独立扩展。","points":["不要覆盖当前状态，而是累积不可变的事件流；通过重放事件推导出当前状态。","使用 CQRS 时，将写模型（命令）与读模型（查询）分离；通过反规范化投影加速读取端。","对事件模式进行版本管理并以向后兼容的方式演进；绝不删除旧事件，通过 upcasting 将其转换为新版本。","接受读取投影是最终一致的，并在设计中考虑延迟。","创建快照，避免每次都从头重放长事件流的开销。","不要处处使用该模式，而应用于需要审计跟踪、复杂业务规则和历史记录的领域；不要低估它带来的复杂性。"],"level":"expert"},{"id":"data-retention","title":"数据保留与归档策略","category":"运维","summary":"并非所有数据都要永久保存；基于法律、业务和成本要求制定的保留与归档策略，既能满足合规又能提升性能。","points":["根据业务和法律（KVKK/GDPR）要求为每类数据定义保留期；含糊的“以防万一先留着”不是策略。","自动归档或删除过期数据；不要依赖手动清理，它会被遗忘，而表却在不断膨胀。","将热数据放在快速存储上，归档放在廉价/冷存储上；按访问频率分层。","从一开始就设计能处理个人数据删除/匿名化请求的机制（被遗忘权）。","确保归档数据在需要时可检索、可查询；归档不应意味着无法访问。","在审计日志中记录删除操作；必须能追溯删除了什么、何时删除以及为何删除。"],"level":"senior"},{"id":"change-management","title":"变更管理与生产安全","category":"运维","summary":"对生产数据库的每项变更都必须经过评审、可回退且可追溯；不受控的手动干预是最常见的停机原因。","points":["不要在生产环境手动执行临时 DDL/DML；每项变更都应通过经同行评审的迁移或脚本。","在低流量时段并备好回滚计划后再应用变更；不要在无计划的情况下触碰生产环境。","先在类生产环境中以真实数据量演练高风险命令；测量其耗时和锁影响。","限制并审计生产访问：必须记录谁在何时执行了哪条命令（just-in-time 访问）。","在删除/更新之前，核实 WHERE 子句和受影响的行数；在事务中执行，并先用 SELECT 确认。","将变更与日历和沟通挂钩；不要用未经通知的生产变更让其他团队措手不及。"],"level":"senior"}]},"guide/best-practices-3.json":{"topics":[{"id":"plan-reading-expert","title":"如何阅读执行计划","category":"性能","summary":"执行计划展示优化器的估算及其选择的物理算子；瓶颈隐藏在估算行数与实际行数的偏差之中。","points":["区分访问方法：Full/Table Scan 适用于大表上的大范围读取，Index Seek/Range Scan 适用于选择性强的谓词，Index Full Scan 适用于有序读取；Rows Removed by Filter 或 predicate/access 的划分可显示选择性在何处丢失。","使连接方法与负载相匹配：Nested Loop 适合小外表集 + 有索引的内表集，Hash Join 适合大型等值连接，Merge Join 适合两个已排序的集合；错误的选择通常源于错误的基数估算。","排查基数估算错误：用 EXPLAIN ANALYZE（PG）、DBMS_XPLAN ALLSTATS LAST（Oracle）或 SET STATISTICS PROFILE（MSSQL）对比估算行数与实际行数；偏差超过 10 倍说明执行计划的选择可能存在根本性错误。","按真实开销阅读执行计划：找出消耗时间/逻辑读最多的算子（logical reads、buffers）；优化器的“cost”单位是估算值，并非实际瓶颈。","留意溢出/临时空间警告：内存放不下而溢写到磁盘的 Hash/Sort 算子（PG 的 'Sort Method: external merge'，MSSQL 执行计划中带 SpillToTempDb 的 Sort/Hash Warning）表明 work_mem/排序区不足。","由内向外、自下而上阅读算子树：内层节点先执行，行向上流动；将 loops 乘以实际时间，计算每个算子的总开销。"],"level":"expert"},{"id":"parameter-sniffing-deep","title":"参数嗅探与绑定变量窥探（bind peeking）","category":"性能","summary":"优化器根据首次调用的参数值生成并缓存执行计划；当之后分布不同的值沿用该计划时，性能可能发生剧烈变化。","points":["识别症状：同一查询在某些参数下很快，在另一些参数下非常慢；这表明缓存的执行计划是针对一个非典型的“首个”值编译的。","在分布倾斜的列上风险很高；直方图使优化器对取值敏感，而单个缓存计划使其对取值不敏感，这种矛盾正是问题根源。","在 MSSQL 中，OPTION (RECOMPILE) 在每次执行时生成执行计划，OPTIMIZE FOR UNKNOWN 基于平均密度选择均衡的计划，OPTIMIZE FOR (@p = value) 则固定一个典型值。","在 Oracle 中，自适应游标共享（adaptive cursor sharing）和 bind-aware 游标会为倾斜值生成不同的执行计划；在 PG 中，custom 与 generic 计划的选择由 plan_cache_mode 控制。","要永久解决，需按取值分离执行计划：为高度非典型的值设计语句拆分、过滤/部分索引或专用访问路径。","在 OLTP 中，过多的 RECOMPILE 会增加 CPU 和编译（硬解析）开销；对高频执行的查询要测量并保持平衡。"],"level":"expert"},{"id":"plan-stability","title":"执行计划稳定性：基线、hint 与计划指南","category":"性能","summary":"使用引擎特有的固化机制冻结关键查询经过验证的执行计划，防止统计信息/版本变更时出现意外的执行计划回归。","points":["在 Oracle 中，用 SQL Plan Baselines（SPM）固定已批准的执行计划；新计划在被“接受”之前仅作为候选收集，并通过 evolve 受控验证。","在 MSSQL 中，用 Query Store 检测执行计划回归，并通过“Force Plan”强制使用已知良好的计划；Automatic Plan Correction 可将此自动化。","PostgreSQL 没有内置的执行计划冻结功能；稳定性来自 pg_hint_plan 扩展，或来自查询改写、扩展统计信息和索引设计。","将 hint 作为最后手段，并附上理由和注释；hint 以物理结构和数据分布为前提，一旦它们改变就会变得有害。","定期审计基线/强制计划清单；随着数据增长，固定的执行计划可能逐渐失去最优性，不要陷入“稳定但很慢”的陷阱。","在版本升级前采集关键查询的执行计划；准备基线作为应对优化器行为变化的安全网。"],"level":"expert"},{"id":"latch-mutex-contention","title":"Latch、mutex 与 spinlock 争用","category":"性能","summary":"Latch/mutex 是保护内存结构的轻量级锁；在高并发下，对它们的争用会在 spinlock 循环中消耗 CPU 并使扩展停滞。","points":["区分锁与 latch：锁在事务期间保护逻辑数据（行/表），latch 在微秒级时间内保护物理内存结构（缓冲区、哈希链）；二者的诊断方法不同。","在 Oracle 中，'cache buffers chains' latch 争用通常指向热块；小型高频读取表或单调递增索引的前沿（右侧索引）是常见原因。","使用顺序递增键（identity/sequence）的插入会导致末页/末块争用（热页、PAGELATCH）；可通过哈希分区、反向键索引或随机化键来分散。","在 MSSQL 中，监控 spinlock 统计信息和 PAGELATCH_* 等待；对于 tempdb 分配争用，使用多个数据文件和合理的 tempdb 配置。","spinlock 争用表现为 CPU 高而吞吐量低；解决方法是在代码/架构层面减少争用，增加硬件往往会使情况更糟。","NUMA 感知和限制连接数（连接池）可减少 latch 争用；过度并行会加大 latch 压力。"],"level":"expert"},{"id":"wait-event-methodology","title":"等待事件分析方法论","category":"性能","summary":"通过测量系统把时间花在哪些等待上来管理性能，而不是靠猜测；自上而下的等待分析是系统性缩小瓶颈范围的科学方法。","points":["将 DB time 分解为各组成部分：区分 CPU 时间与等待时间；从最大的部分入手，不要陷入微观优化。","找出最主要的等待事件：Oracle 中使用 ASH/AWR 和“Top Wait Events”，PG 中使用 pg_stat_activity + pg_wait_sampling，MSSQL 中使用 sys.dm_os_wait_stats 和 Query Store。","解读等待类别：I/O 等待指向存储/执行计划，锁等待指向并发设计，latch 等待指向热点，网络/提交等待指向配置问题。","下钻到会话/查询级别：从总等待出发，定位贡献最多的 SQL，再到该 SQL 的执行计划，从而隔离原因（drill-down）。","将测量与负载时间窗口挂钩：取累计计数器在两个时间点之间的差值（delta）；总量具有误导性，重要的是速率和时间区间。","将空闲/无害等待从噪声中过滤掉；并非每个等待都是问题，关键在于影响用户响应时间的前台等待。"],"level":"expert"},{"id":"plan-regression-detection","title":"检测执行计划回归","category":"性能","summary":"突然变慢的查询通常源于执行计划变化而非代码变更；主动检测回退比被动救火成本更低。","points":["保留并对比执行计划历史：使用 MSSQL Query Store 和 Oracle AWR/SQL Monitor 跟踪同一 sql_id/query_hash 的计划变化及耗时/资源趋势。","识别回退的诱因：统计信息更新、数据量增长、版本升级、参数变更或删除索引是最常见的原因。","建立自动告警：当 plan hash 变化且平均耗时/逻辑读超过阈值时发出告警；跟踪单次执行指标，而非累计值。","准备快速回滚路径：用 Force Plan（MSSQL）或 SQL Plan Baseline（Oracle）临时固定已知良好的执行计划，之后再修复根因。","对于统计信息导致的回退，检查锁定/当前统计信息和直方图的新鲜度；自动统计信息收集可能被安排在关键时段运行。","升级前进行回归测试：针对新旧优化器重放具有代表性的负载，并报告执行计划差异（类似 Oracle SPA 的方法）。"],"level":"expert"},{"id":"mvcc-vacuum-internals","title":"MVCC 与 vacuum/undo 内部机制","category":"内部机制","summary":"MVCC 通过保留旧的行版本避免读写互相阻塞；这些版本如何保存、保存在哪里（undo 还是表内）决定了维护策略。","points":["在 PostgreSQL 中，旧版本以死元组形式保留在表内；由 VACUUM 清理，否则会产生膨胀和索引增长。","Oracle 和 MySQL/InnoDB 将旧版本保存在 undo/回滚段中；如果长时间运行的读取耗尽了 undo，就会出现 'ORA-01555 snapshot too old' 等错误。","根据负载调优 autovacuum：对高频更新/删除的表降低 scale factor 并减小 cost delay；为变化剧烈的表设置表级参数。","保持 visibility map 健康；index-only scan 和冻结元组跟踪都依赖它，忽视它会导致性能下降。","监控事务 ID 回卷（wraparound）风险：在 PG 中跟踪 datfrozenxid/age 指标，防止激进/防回卷 vacuum 在业务时段触发。","促进 HOT（Heap-Only Tuple）更新：不修改索引列的更新会原地完成，不会导致索引增长；合适的 fillfactor 有助于实现这一点。"],"level":"expert"},{"id":"wal-redo-log-mechanics","title":"WAL/redo/事务日志机制","category":"内部机制","summary":"在数据页写入磁盘之前先将变更写入日志，从而实现持久性；日志流的速率和大小同时决定持久性和写入性能。","points":["理解预写原则：提交要返回，只需相关日志记录已落到持久存储上；脏数据页随后再延迟写入。","调优组提交和日志刷新行为：在 PG 中通过 commit_delay/synchronous_commit、在 Oracle 中通过日志写进程行为，在 fsync 频率与工作负载的持久性要求之间取得平衡。","监控日志生成速率（WAL/redo 生成量）；突然增加意味着批量更新、checkpoint 之后的整页镜像（full_page_writes/FPI）或索引维护，并会加大 DR 延迟。","日志不仅是恢复的来源，也是复制和 CDC 的来源；逻辑复制和 WAL 传送都以此流为输入，因此应据此设计归档/清理策略。","将日志磁盘与数据磁盘分开，并放在低延迟存储上；日志写入是顺序的且对延迟敏感，提交吞吐量直接取决于它。","防止日志无节制增长：在 MSSQL 中使用正确的恢复模式并定期进行日志备份；在 PG 中确保归档/复制槽不落后。"],"level":"expert"},{"id":"buffer-pool-management","title":"Buffer/shared pool 管理与淘汰","category":"内部机制","summary":"buffer pool 通过将热数据保留在内存中来减少磁盘 I/O；决定哪个页在何时被淘汰的算法决定了命中率和稳定性。","points":["不要只盯着 buffer cache 命中率；即使命中率很高也可能掩盖物理读的延迟，因此还要关注 I/O 的绝对量和等待时间。","理解淘汰算法：为避免纯 LRU 被顺序扫描污染，PG 使用 clock-sweep，Oracle 使用 touch-count/LRU 变体，InnoDB 使用中点插入 LRU。","防止大型一次性扫描将热数据挤出缓冲池（缓存污染）；通过独立的 buffer pool 或适合扫描的访问策略加以保护。","管理脏页比例和写入压力：脏页过多会在 checkpoint 时造成 I/O 尖峰，写入过于激进则会产生不必要的 I/O；应测量并找到平衡。","按工作集确定内存大小；目标是覆盖被活跃访问的数据集而非全部数据，过度配置的收益递减。","在 Oracle shared pool 中，监控库缓存/字典缓存争用和频繁的硬解析；使用绑定变量代替字面量 SQL 可减轻 shared pool 压力。"],"level":"expert"},{"id":"checkpoint-tuning","title":"Checkpoint 调优","category":"内部机制","summary":"checkpoint 通过将脏页写入磁盘来缩短恢复时间；checkpoint 过于频繁会产生 I/O 峰值，过于稀疏则会导致恢复时间过长，必须调优这一权衡。","points":["在恢复时间与运行时 I/O 之间取得平衡：checkpoint 稀疏意味着写入负载较低但崩溃恢复较长，频繁则相反。","在 PG 中，通过 checkpoint_timeout 和 max_wal_size 拉开 checkpoint 间隔；通过 checkpoint_completion_target 将写入分摊到整个间隔中以平滑 I/O 峰值。","MSSQL 间接 checkpoint（目标恢复时间）提供持续的后台写入；它通过避免突发的大规模 checkpoint 波动使延迟可预测。","将 checkpoint 频率与日志/WAL 生成速率关联起来；在高写入时段，监控触发的是大小条件而不是时间条件。","full_page_writes（PG）会在 checkpoint 后首次修改页时记录整页；如果 checkpoint 过于频繁，WAL 量会激增，从而增加 DR 和磁盘成本。","识别“checkpoint 下沉”症状：checkpoint 期间写入延迟和 fsync 等待上升；检查存储队列和分布情况。"],"level":"expert"},{"id":"statistics-strategy","title":"统计信息收集策略","category":"内部机制","summary":"优化器决策的质量取决于统计信息的新鲜度和精细度；采样率、直方图和扩展统计信息决定基数估计。","points":["根据数据量和倾斜程度选择采样率：样本过小会在倾斜分布上产生错误估计，样本过大则带来不必要的收集成本。","用直方图刻画倾斜：PG default_statistics_target、Oracle FREQUENCY/HYBRID 直方图和 MSSQL 统计信息直方图会向优化器揭示高频和不均衡的值。","通过扩展统计信息捕获列间相关性：PG CREATE STATISTICS（ndistinct/dependencies）和 Oracle 扩展统计信息可修正相互依赖列上的估计错误。","将收集计划与工作负载错开：防止自动统计信息/收集作业在关键时段运行而引发 I/O 和计划不稳定。","管理快速增长表上统计信息过时的风险；基于阈值的自动更新可能滞后，因此应为关键表增加手动/定时收集。","要知道统计信息变化可能导致计划回归；在关键环境中，锁定统计信息（locked stats）或通过 pending publish 受控地发布。"],"level":"expert"},{"id":"partition-pruning-join","title":"分区裁剪与分区智能连接","category":"扩展/架构","summary":"分区并不只是拆分表；真正的收益在于查询从不读取无关分区（裁剪），并且连接按分区并行执行。","points":["根据最频繁且选择性最高的过滤条件选择分区键；只有当查询谓词包含分区键时裁剪才会生效，否则会扫描所有分区。","区分静态裁剪和动态裁剪：常量谓词在编译时排除分区，绑定/连接值在运行时排除分区（运行时裁剪）；据此验证执行计划。","要实现分区智能连接，两张表需使用兼容的方案按同一键分区；优化器会将大型连接化简为按分区的小型连接并加以并行化。","平衡分区的数量与粒度：分区过多意味着元数据和规划成本，过少则裁剪效果弱；应按访问模式确定。","将生命周期管理与分区结合：对旧分区执行 DETACH/DROP 或归档；这样可以代替 DELETE，实现即时、低日志量的清理。","注意，函数/类型转换或掩盖分区键的表达式会破坏裁剪；用 EXPLAIN 验证扫描的分区数量。"],"level":"expert"},{"id":"shard-key-selection","title":"分片键选择与热点","category":"扩展/架构","summary":"分片键决定了分布式系统的命运；错误的选择会将负载堆积到单个节点（热点分片）、使扩展停滞，并让跨分片查询代价高昂。","points":["选择能均匀分布负载的高基数键；低基数或基于时间/单调递增的键会将负载堆积到单个分片上并形成热点。","设计分片键时，应让最频繁的查询在单个分片上完成；跨分片（scatter-gather）查询会成倍增加延迟和协调成本。","哈希分片可均衡负载，但会打散范围查询；范围分片适合范围查询，但会增加热点风险；应根据访问模式选择。","使用一致性哈希在添加/移除节点时尽量减少数据重分布；固定取模哈希在节点数变化时几乎会迁移所有数据。","从一开始就设计重新分片计划；事后更换分片键代价高且风险大，因此应提前对增长场景建模。","按租户/客户拆分时，准备专用分片或子分区（split）策略，以免超大租户（whale）占满单个分片。"],"level":"expert"},{"id":"read-write-splitting","title":"读写分离与副本路由","category":"扩展/架构","summary":"将读请求路由到副本可减轻写主库的压力；但由于复制延迟可能破坏一致性保证，路由必须经过审慎设计。","points":["将读负载分发到副本时，持续监控复制延迟；过时的副本可能向用户展示旧数据。","将需要 read-your-writes（写入的用户立即读取）的流程路由到主库，或通过因果读取/令牌保证副本是最新的。","在基础设施层而非应用中进行路由：通过代理/路由器透明地分发（MySQL 使用 ProxySQL；PG 使用 Pgpool-II 或配置独立读/写端点的 HAProxy，因为 PgBouncer 不做读写分离；MSSQL 通过 application intent 实现只读路由）。","当超过延迟阈值时将副本移出池；将读请求发送到高延迟副本会在“扩展”的假象下造成数据不一致。","将分析/报表等繁重且可容忍延迟的读取隔离到专用副本上；使 OLTP 副本免受该负载影响。","测试在故障转移场景中读路由能否自动更新；不知道新主库的路由器会导致脑裂或停机。"],"level":"expert"},{"id":"connection-pool-sizing","title":"连接池大小计算","category":"扩展/架构","summary":"连接池大小应依据利特尔定律（Little's law）和核心数计算确定，而非凭直觉；连接过多会因争用和上下文切换而降低吞吐量。","points":["应用利特尔定律：所需并发度 ≈ 请求速率（吞吐量）× 平均处理时间；根据实测值而非猜测得出目标并发度。","小的连接池往往更快：当活动连接数超过物理核心/磁盘的并行能力时，CPU 上下文切换和 latch 争用会降低吞吐量。","作为粗略的起点，使用接近核心数的核心连接池（例如 PgBouncer 后面少量的真实连接），并在负载下测量调优。","将应用端连接池与数据库 max_connections 一并规划；N 个应用实例 × 连接池大小不得超过数据库限制。","在 PG 这类每连接进程成本较高的引擎上，使用 PgBouncer/事务级连接池；每个连接都会消耗内存和调度开销。","监控连接池饱和度、队列等待时间和超时；队列持续增长意味着存在瓶颈或容量不足，不要盲目扩大连接池。"],"level":"expert"},{"id":"lock-escalation-deadlock","title":"锁升级与死锁预防模式","category":"可靠性","summary":"大量行锁可能升级为表锁并扼杀并发；死锁是加锁顺序不一致所导致的、可以避免的结果。","points":["拆分会触发锁升级的大型批量操作（分批/分块）；在单个事务中锁定数百万行会导致锁升级和阻塞。","预防死锁的基本模式是一致的加锁顺序：所有代码路径必须按相同顺序锁定资源（例如按主键顺序）。","保持事务简短，只完成单个原子工作单元；包含用户交互、远程调用或长时间计算的事务会不必要地长时间持有锁。","审慎选择隔离级别：隔离级别过高会导致过多加锁，过低会导致异常；在 MVCC 引擎中，读取本身已能避免大多数锁。","收集死锁图/跟踪（MSSQL deadlock graph、PG log_lock_waits、Oracle trace）；记录牺牲者，并将反复出现的模式追溯到根因。","实现重试逻辑：以幂等方式重试作为死锁牺牲者的事务；这是应对设计上无法避免的争用的安全网。"],"level":"expert"},{"id":"long-transaction-bloat","title":"长事务与膨胀/undo 管理","category":"可靠性","summary":"长时间未关闭的事务会阻止 MVCC 引擎清理旧版本，引发膨胀、undo 压力和性能崩溃的连锁反应。","points":["监控长时间运行/未关闭的事务并告警：PG 中的 idle in transaction 和 Oracle 中占用活动 undo 的长查询会阻止清理（vacuum/undo 回收）。","在 PG 中，单个长事务会阻止 VACUUM 清除死元组；表和索引会膨胀，一个“僵尸”会话就可能拖慢整个数据库。","使用 idle_in_transaction_session_timeout 和 statement_timeout 自动终止卡住的事务；针对应用泄漏建立一道防线。","在 Oracle/InnoDB 中，长时间读取会耗尽 undo 并产生“snapshot too old”；按工作负载中最长的查询确定 undo 空间/保留时间。","以较短的提交间隔拆分批处理作业；单个巨型事务会累积 undo/WAL，并危险地延长回滚时间。","测量并回收已有膨胀：使用 pgstattuple 测量膨胀，并通过 VACUUM FULL/pg_repack 或在线重组回收空间、恢复索引效率。"],"level":"expert"},{"id":"dr-game-day","title":"DR 演练（game day）与 RPO/RTO 验证","category":"可靠性","summary":"未经测试的备份和故障转移计划只是假设；只有在真实演练中测量 RPO/RTO，才能证明 DR 能力。","points":["根据业务需求得出 RPO 和 RTO，并在实践中测量：备份/复制延迟决定 RPO，恢复 + 验证时间决定 RTO；对比目标与实际。","定期自动地还原备份：从未还原过的备份是“薛定谔的备份”；没有还原测试，备份存在的说法就未经验证。","在接近生产的环境中，以真实的故障场景（主库丢失、区域故障、数据损坏）规划 game day 演练。","端到端执行故障转移和回切流程；包括 DNS/连接路由、应用重连和副本提升。","证明时间点恢复（PITR）能力：在逻辑损坏场景中，测试能否利用日志归档 + 全量备份恢复到特定时间点。","每次演练都要产出实测 RTO/RPO、意外发现和行动项；更新运行手册，并在下次演练中验证改进效果。"],"level":"expert"},{"id":"observability-slo","title":"可观测性与 SLO/黄金信号","category":"可靠性","summary":"要按业务影响管理数据库，应将黄金信号（延迟、流量、错误、饱和度）与 SLO 关联，并通过错误预算确定优先级。","points":["监控四大黄金信号：延迟、流量（吞吐量）、错误率（errors）和饱和度；无需深挖根因即可概括健康状况。","使用百分位数（p95/p99）而非平均值测量延迟；平均值会掩盖尾部延迟和最差的用户体验。","定义 SLO 和错误预算；针对症状（SLO 违规）告警，而不是针对每个底层指标的波动告警，以防止告警疲劳。","对资源应用 USE（Utilization、Saturation、Errors）方法：分别观察 CPU、内存、磁盘 I/O 和连接池的饱和度。","跨层关联监控：在同一时间轴上查看 查询 -> wait event -> 资源饱和 -> 业务指标 这一链条。","同时建设指标 + 日志 + 链路追踪；仅靠指标只能回答“是什么”，日志/追踪回答“为什么”，缺少它们 MTTR 会增加。"],"level":"expert"},{"id":"least-privilege-roles","title":"最小权限与角色设计","category":"安全","summary":"让每个账户只拥有完成其工作所需的最小权限，可缩小入侵的影响范围；角色提供了可管理的抽象，避免直接授予权限。","points":["将权限授予角色而非用户；按职能（读取、写入、DDL、运维）设计角色并将用户分配到角色，避免直接授权。","只为应用账户授予所需的对象和操作权限；生产应用账户上不得存在 DDL、SUPERUSER/DBA 及宽泛的 schema 权限。","对管理员的人工访问进行个人化并审计：使用个人身份 + 按需提权（JIT/break-glass）访问，而不是共享的管理员账户。","撤销 PUBLIC/默认的宽泛权限；配置默认权限和 schema 级控制，防止默认权限扩散到新对象上。","定期审查特权账户（访问权限重新认证）；定期清理未使用的账户、离职员工的账户和权限过大的账户。","实施职责分离；同一个人不得既能修改生产环境又能抹除审计轨迹，因此应以单独的权限保护审计写入。"],"level":"expert"},{"id":"encryption-decision","title":"加密决策：静态加密、传输加密、TDE","category":"安全","summary":"加密方式应根据威胁模型选择；每一层防护不同的攻击，而密钥管理往往比加密算法更关键。","points":["明确威胁：静态（磁盘/备份被盗）、传输中（网络窃听）和应用级（对任何人不可见，包括 DBA）是不同的防护；确定你需要哪一种。","对于传输加密，在客户端-服务器连接上强制使用 TLS；不要禁用证书验证，否则会遭受中间人（MITM）攻击。","对于静态加密，将 TDE 与文件系统/磁盘加密方案进行比较；TDE 可保护数据文件和备份，但无法保护内存中的数据或经授权的 DBA 访问。","对于最敏感的字段，使用应用级/列级加密或 Always Encrypted 这类客户端方案，使 DBA 也无法看到明文。","认真对待密钥管理：将密钥存放在与数据分离的 KMS/HSM 中，并规划轮换和访问控制；密钥一旦泄露，加密便毫无价值。","测量性能和功能影响：加密列上的索引/搜索会受到限制，TDE 会增加 CPU 开销；应将其作为经过测量的权衡来决策。"],"level":"expert"},{"id":"audit-strategy","title":"审计策略","category":"安全","summary":"审计轨迹为安全事件和合规要求提供证据；记录什么、轨迹存放在哪里以及保留多久，决定了噪声与证据之间的平衡。","points":["根据威胁和合规要求确定审计内容：优先审计特权操作、登录/登录失败、schema（DDL）变更和敏感数据访问。","不要陷入“全部记录”的陷阱；过度审计会降低性能并使信号淹没在噪声中，因此应有选择、有目的地针对关键事件。","将审计轨迹写入生产 DBA 无法修改的独立安全目标（外部日志收集器/SIEM）；通过职责分离防止轨迹被篡改。","利用引擎能力：通过 Oracle Unified/Fine-Grained Audit、MSSQL SQL Audit 和 PG pgAudit 按需调整详细程度。","根据合规要求（KVKK/GDPR/PCI-DSS 等）设置保留期，并将归档/清理策略自动化；保持轨迹的完整性。","不要只收集轨迹；应定期审查并将其与告警关联：审计只有在标记异常并转化为行动时才产生价值，而不是成为一个死档案。"],"level":"expert"}]},"guide/best-practices-4.json":{"topics":[{"id":"oracle-rac-cache-fusion","title":"Oracle RAC：Cache Fusion 与互连","category":"Oracle","summary":"在 RAC 中，多个实例共享同一个数据库；Cache Fusion 通过互连在节点之间传送块（而非经由磁盘）来维持一致性。","points":["当一个实例需要位于另一个实例 buffer cache 中的块时，Cache Fusion 通过私有互连以 RAM 到 RAM 的方式传送；不会先写入磁盘再读回（disk ping）。","Global Cache Service（GCS/LMS 进程）管理块的所有权和模式（共享/独占），Global Enqueue Service（GES）则管理锁和 enqueue。","每个块都有一个主节点；块的位置在 Global Resource Directory 中跟踪，并可根据访问强度通过 DRM（Dynamic Resource Mastering）重新指定主节点。","互连需要专用的低延迟网络；使用 Jumbo Frames（MTU 9000），必要时使用 RDMA/RoCE 或 InfiniBand，且绝不能与公共网络共享。","“gc buffer busy”和“gc cr/current block busy”等待事件表明存在热块争用；增大序列 CACHE、使用反向键索引或通过分区分散负载会有所帮助。","如果 gc blocks lost 统计值持续增加，说明互连上存在丢包；检查 NIC、交换机缓冲区和套接字缓冲区大小（rmem/wmem）。"],"level":"expert"},{"id":"oracle-rac-node-eviction","title":"Oracle RAC：节点驱逐与脑裂","category":"Oracle","summary":"Clusterware 会将无响应的节点驱逐出集群以防止脑裂；它根据网络心跳、磁盘心跳和表决磁盘多数做出判断。","points":["CSSD 监控两种独立的心跳：通过互连的网络心跳（misscount，默认 30 s）和通过表决磁盘的磁盘心跳（disktimeout，默认 200 s）。","发生脑裂时，存活的子集群（cohort）由表决磁盘多数决定；因此表决磁盘的数量必须为奇数（3 或 5），且必须位于不同的故障组中。","驱逐通常通过节点重启或无重启隔离（仅重启 stack）来执行；目的是阻止故障节点写入共享存储。","常见的驱逐原因：互连中断、高负载下 CSSD 得不到 CPU、表决磁盘 I/O 延迟，以及 OCR/表决磁盘访问问题。","进行根因分析时，将 ocssd.log、CSSD 跟踪文件、alert log 和操作系统消息与 CHM/oclumon（Cluster Health Monitor）数据结合起来审查。"],"level":"expert"},{"id":"oracle-asm-redundancy-rebalance","title":"Oracle ASM：磁盘组冗余与重新平衡","category":"Oracle","summary":"ASM 通过在磁盘组的各磁盘间条带化数据并按故障组镜像来保护数据；添加或删除磁盘时会自动重新平衡 extent。","points":["冗余级别：EXTERNAL（无镜像，由存储阵列负责保护）、NORMAL（2 份副本，可容忍 1 块磁盘丢失）、HIGH（3 份副本，可容忍同时丢失 2 块）。","ASM 镜像的是故障组而非磁盘；主 extent 位于一个故障组中，其镜像副本位于不同的故障组中，因此即使整个故障组失效也不会丢失数据。","添加或删除磁盘会触发重新平衡以重新分布 extent；ASM_POWER_LIMIT（0-1024）设置速度与资源消耗之间的平衡。","AU（Allocation Unit）大小在创建磁盘组时设定；对于大型数据库，4 MB 的 AU 可提高 I/O 效率。","表决磁盘和 OCR 可存放在 normal/high 冗余的磁盘组中；quorum 故障组仅用于表决磁盘，不存放数据 extent。"],"level":"expert"},{"id":"oracle-data-guard-modes","title":"Oracle Data Guard：备库类型与保护模式","category":"Oracle","summary":"Data Guard 通过将 redo 传送到备库来提供灾难恢复；物理/逻辑备库的选择和三种保护模式决定了数据丢失与可用性之间的平衡。","points":["物理备库是主库逐块相同的副本（Redo Apply/MRP）；借助 Active Data Guard，可在打开状态下以只读方式查询。逻辑备库将 redo 转换为 SQL 并应用（SQL Apply），可承载不同的结构/索引。","Maximum Performance（默认）：redo 以异步方式（ASYNC）传输，主库不等待确认；对性能零影响，但存在少量数据丢失风险。","Maximum Availability：redo 以同步方式（SYNC/AFFIRM）传输；若备库不可达，主库不会停止，而是退回到 MAX PERFORMANCE 行为，并在备库恢复后重新同步。","Maximum Protection：至少一个备库确认之前主库不提交；保证零数据丢失，但若所有备库都丢失，主库将关闭。","Fast-Start Failover（FSFO）使用 observer 进程在主库故障时自动故障转移；结合 FastStartFailoverThreshold 和同步传输可实现 RPO=0 的目标。","在备库上通过 v$dataguard_stats 以及 'apply lag'/'transport lag' 指标监控应用延迟；实时应用（real-time apply）会在 redo 归档之前就应用它。"],"level":"expert"},{"id":"oracle-exadata-offload","title":"Oracle Exadata：Smart Scan 与存储索引","category":"Oracle","summary":"在 Exadata 上，存储单元（storage cell）承担部分查询处理（offload），只将相关数据返回给数据库节点；Smart Scan、存储索引和 HCC 可大幅减少 I/O。","points":["对于全扫描查询，Smart Scan 在存储单元中执行谓词过滤和列投影；返回给数据库节点的只是所需的行/列而非数据块，从而减少 interconnect 流量。","Smart Scan 只在直接路径读（direct path read）时生效；对从 buffer cache 读取的块或某些操作不会触发；通过 'cell physical IO bytes eligible for predicate offload' 和 'cell physical IO interconnect bytes returned by smart scan' 统计量衡量收益（'cell physical IO bytes saved by storage index' 只反映存储索引带来的节省）。","存储索引在内存中为每个 1 MB 存储区域保存列的最小/最大值；若查询谓词落在该范围之外，则该区域无需读取直接跳过（其作用类似于反向索引）。","存储索引不持久化；它们驻留在内存中，存储单元重启后重新构建。数据按物理顺序排列（聚簇）可提高跳过比例。","HCC（Hybrid Columnar Compression）在压缩单元（Compression Unit）内按列压缩数据；QUERY LOW/HIGH 适用于查询密集型数据，ARCHIVE LOW/HIGH 适用于很少访问的归档数据，以较高的 CPU 开销换取极高的压缩率。"],"level":"expert"},{"id":"oracle-awr-ash-analysis","title":"使用 Oracle AWR 和 ASH 进行性能分析","category":"Oracle","summary":"AWR 定期收集性能快照，ASH 每秒对活动会话采样；二者结合，从等待事件的角度回答“时间花在了哪里”。","points":["AWR 默认每 60 分钟生成一次快照并保留 8 天；两个快照之间的 AWR 报告显示哪些 SQL 和等待事件消耗了 DB Time（需要 Diagnostics Pack 许可）。","分析始终从 'Top 10 Foreground Events by Total Wait Time' 和 DB Time 入手；在那里看清 CPU、I/O 与并发之间的比重，而不是迷失在单个统计量中。","ASH 每秒采样一次 v$active_session_history，捕获短暂、突发的问题（尖峰）；它能按 SQL_ID 和阻塞会话揭示 AWR 遗漏的瞬时锁/latch 风暴。","平均活动会话数（AAS = DB Time / 经过时间）是用单个数字概括系统负载的指标；AAS 超过 CPU 核数表示系统已饱和。","SQL 调优时，从 AWR 的 'SQL ordered by Elapsed Time/Gets' 部分取出代价最高的 SQL_ID，用 ASH 确认该 SQL 在哪个步骤上等待，再用 SQL Monitor 报告逐行检查执行计划。"],"level":"expert"},{"id":"oracle-scn-read-consistency","title":"Oracle SCN 与读一致性","category":"Oracle","summary":"系统变更号（SCN）是数据库的逻辑时钟；Oracle 按查询开始时的 SCN 从 undo 构建一致性映像（读一致性），从而将读者与写者隔离开。","points":["SCN 随每次提交单调递增，并记录在 redo/undo、控制文件和块头中；若某个块的最后变更 SCN 大于查询 SCN，则该块会被“回滚”。","为实现读一致性，Oracle 生成当前块的 CR（Consistent Read）副本，并利用 undo 将其回滚到查询的起始 SCN；因此“读不阻塞写，写不阻塞读”。","若 undo 不足或超出 undo_retention，长时间运行的查询会收到 'ORA-01555 snapshot too old'；解决办法是扩大 undo 表空间、调整保留时间或缩短查询。","语句级一致性为每条 SQL 语句使用单独的 SCN；SERIALIZABLE 或只读事务则对所有语句沿用事务开始时的同一个 SCN。","在 RAC 中，SCN 在所有实例之间保持同步（Lamport 或 broadcast-on-commit）；为实现全局一致性，SCN 的推进在节点之间协调进行。"],"level":"expert"},{"id":"postgres-patroni-failover","title":"PostgreSQL：使用 Patroni 实现自动故障转移","category":"PostgreSQL","summary":"Patroni 将 PostgreSQL 集群状态保存在分布式共识存储（DCS）中，提供领导者选举和自动故障转移；并通过 watchdog 防止脑裂。","points":["集群状态和领导者锁保存在 etcd/Consul/ZooKeeper 等 DCS 中；领导者定期续约一个带 TTL 的锁，若无法续约，锁过期并启动新的领导者选举。","故障转移时，Patroni 选择数据最新的副本（LSN 最高者）；新领导者被提升，其余副本重新连接到它（reinitialize/rewind）。","watchdog（softdog）是硬件级安全网：当旧领导者与 DCS 失去联系时重置该机器，防止两个节点同时成为领导者（脑裂）。","maximum_lag_on_failover 防止严重落后的副本成为领导者而导致数据丢失；synchronous_mode 可实现 RPO=0 的目标，但写入延迟会增加。","客户端不应直接连接某个节点，而应通过使用 Patroni REST 健康检查的 HAProxy 或 VIP 连接；这样领导者变更时，流量会自动路由到新领导者。","DCS 多数派（quorum）是必需的；请使用 3 或 5 个成员的 etcd 集群。若 DCS 不可达，Patroni 会采取保守策略，可能将当前领导者降级为只读。"],"level":"expert"},{"id":"postgres-logical-replication-upgrade","title":"PostgreSQL：使用逻辑复制实现零停机大版本升级","category":"PostgreSQL","summary":"由于逻辑复制能够跨版本传递行变更，它可以在接近零停机的情况下完成大版本升级；新旧版本会并行运行一段时间。","points":["逻辑复制从 WAL 中对变更进行逻辑解码（INSERT/UPDATE/DELETE）；与物理复制不同，它可以跨不同的大版本和不同的平台工作。","流程：在新版本上创建空 schema，在旧版本上定义 PUBLICATION，在新版本上定义 SUBSCRIPTION；先复制表（初始同步），然后开始传输实时变更。","每张表都必须有 REPLICA IDENTITY；对于没有主键的表，复制 UPDATE/DELETE 需要 REPLICA IDENTITY FULL，否则会报错。","DDL 变更不会自动复制，序列的当前值也不会传递；切换时须在新版本上手动推进序列，否则会出现键冲突。","切换时，短暂停止应用对旧版本的写入，待延迟降为零（用 pg_stat_replication 确认）后将应用指向新版本；必要时准备好反向订阅以便快速回退。","限制：大对象和部分 DDL 不会被复制；必须设置 wal_level=logical 以及足够的 max_replication_slots/max_wal_senders。"],"level":"expert"},{"id":"postgres-pgvector-similarity","title":"PostgreSQL：使用 pgvector 进行相似度搜索","category":"PostgreSQL","summary":"pgvector 存储向量嵌入并执行近似最近邻（ANN）搜索；HNSW 和 IVFFlat 索引决定准确度、速度与内存之间的权衡。","points":["按使用场景选择距离运算符：<-> L2（欧氏距离），<=> 余弦，<#> 内积；索引必须与查询使用相同的运算符类，否则不会被使用。","HNSW 构建多层图；召回率高、查询快，但需要更多内存且构建较慢。m（连接数）和 ef_construction 调节构建质量，ef_search 调节查询时的准确度。","IVFFlat 将向量划分为 lists 个簇，每次查询只扫描 probes 个簇；内存占用较少，但索引必须在数据加载之后创建（空表上的簇没有意义）。","对于 IVFFlat，100 万行以内从 lists ≈ rows/1000 开始，超过 100 万行则取 ≈ sqrt(rows)；增大 probes 会提高召回率但使查询变慢，应通过实测调整这一权衡。","ANN 返回近似结果；若要求 100% 精确，则需要不使用索引的精确扫描。带过滤（WHERE）的搜索可能降低索引召回率；必要时可考虑 iterative scan 或部分索引。","向量维度和行数决定内存需求；构建期间应保持足够大的 maintenance_work_mem，否则 HNSW 构建会溢出到磁盘并变得非常慢。"],"level":"expert"},{"id":"postgres-citus-distributed","title":"PostgreSQL：使用 Citus 进行分布式扩展","category":"PostgreSQL","summary":"Citus 按分布列将表拆分为分片并分散到各 worker 节点，从而横向扩展 PostgreSQL；分布列的选择决定性能。","points":["coordinator 节点接收查询，并根据分布列将其路由到相关分片；worker 节点存储分片并并行处理。分片是标准的 PostgreSQL 表（按哈希范围拆分）。","分布列应是大多数查询和 JOIN 的共同过滤条件（例如 tenant_id）；列值相同的行落在同一分片中，因此 JOIN 和外键在同一节点内保持本地化（colocation）。","colocation 至关重要：相互 JOIN 的表若使用相同的分布列和分片数进行分布，则无需跨节点重分布数据（repartition）即可执行。","将与所有分片频繁 JOIN 的小表设为 reference table；它们会被完整复制到每个 worker，消除分布式 JOIN 中的网络开销。","不包含分布列的查询会扇出到所有分片，代价高昂；在多租户 SaaS 中，按 tenant_id 过滤可将查询收敛到单个分片（router execution）。"],"level":"expert"},{"id":"postgres-timescaledb-hypertable","title":"PostgreSQL：TimescaleDB 超表与连续聚合","category":"PostgreSQL","summary":"TimescaleDB 通过超表（hypertable）将时序数据自动按时间区间拆分为 chunk，并通过预先计算的连续聚合（continuous aggregate）来扩展时序数据处理能力。","points":["超表从外部看是一张表，但在后台按时间（以及可选的空间）列拆分为 chunk；查询只访问相关时间范围内的 chunk（chunk exclusion）。","设置 chunk_time_interval，使最活跃的 chunk 能放入内存（shared_buffers）；过小会导致过多的 chunk/规划开销，过大则排除效果不佳。","连续聚合是增量刷新的物化视图；它由刷新策略在后台保持最新，只重新计算发生变化的时间桶。","借助实时聚合（real-time aggregation），连续聚合会将已物化的旧数据与原始表中的最新数据合并，在查询时返回最新结果。","旧 chunk 可通过列式压缩大幅缩小；保留策略通过 drop_chunks 自动删除超过指定时长的 chunk（远比逐行 DELETE 便宜）。"],"level":"expert"},{"id":"postgres-vacuum-xid-wraparound","title":"PostgreSQL：VACUUM 与 XID 回卷","category":"PostgreSQL","summary":"MVCC 会留下死行版本；VACUUM 清理这些版本并将行标记为“冻结”（frozen），防止 32 位事务 ID（XID）回卷导致的数据丢失。","points":["PostgreSQL 使用循环的 32 位 XID；每个行版本都标记有 xmin。若足够旧的行未被冻结，XID 回卷时“过去”的事务看起来会处于“未来”，行可能突然变得不可见。","autovacuum 承担两项工作：常规清理（死元组、膨胀）和冻结；对于接近 autovacuum_freeze_max_age 的表，即使禁用了 autovacuum，也会触发强制的“防回卷”（anti-wraparound）vacuum。","接近回卷时会记录警告；若非常接近上限，数据库会拒绝新的写入并进入只读状态以防止数据丢失，解决办法是执行激进的 VACUUM（FREEZE）。","通过 pg_database.datfrozenxid 和 age() 监控最老的未冻结 XID；长时间打开的事务（idle in transaction）和未使用的复制槽会阻碍 VACUUM 推进，增加膨胀和回卷风险。","高写入负载下，autovacuum 跟不上会导致膨胀；应提高 autovacuum_vacuum_cost_limit、降低 naptime，并对大表/热点表应用表级 autovacuum 设置。","VACUUM 通常不会将空间归还给操作系统，而是将其标记为可重用。物理收缩需要 VACUUM FULL（锁表）或 pg_repack（在线）。"],"level":"expert"},{"id":"mysql-innodb-cluster","title":"MySQL：Group Replication 与 InnoDB Cluster","category":"MySQL","summary":"InnoDB Cluster 是基于 Group Replication 的多数派（quorum）集群，通过 MySQL Router 向应用提供单一连接点，从而实现高可用。","points":["Group Replication 通过基于 Paxos 的组通信层将事务传播到组内；只有组内多数成员（quorum）同意后事务才提交，从而实现自动故障转移和一致性。","单主模式（默认）只允许在一个节点上写入，主节点故障时组会自动选举新的主节点；多主模式下所有节点都接受写入，但冲突（certification）处理更加复杂。","MySQL Router 读取集群元数据并向应用提供两个端口：写入发往当前主节点（6446），读取发往副本（6447）；故障转移时路由会更新，应用无需更改连接。","失去多数派（例如 3 个节点中有 2 个宕机）会使组阻塞；为防止脑裂，集群不再接受新的写入，需要使用 group_replication_force_members 手动恢复。","Group Replication 要求基于行的 binlog（ROW 格式）、GTID 和 InnoDB；每张表都必须有主键，否则节点无法加入集群。","新加入的节点先通过从现有 donor 进行状态传输完成同步（distributed recovery：clone 插件或 binlog），然后切换到实时流。"],"level":"expert"},{"id":"mysql-semisync-replication","title":"MySQL：半同步复制","category":"MySQL","summary":"半同步复制要求主库在确认提交之前，等待至少一个副本将事务接收到其 relay log 中，从而减少数据丢失。","points":["在异步复制中，主库无需等待副本即确认提交；若主库崩溃，尚未传输的事务会丢失。半同步会等待至少 N 个副本的接收确认（rpl_semi_sync_source_wait_for_replica_count）。","AFTER_SYNC（默认，推荐）：主库先收到副本确认，再向客户端返回提交结果；这保证了客户端看到“成功”的每个事务至少存在于一个副本上。","在 AFTER_COMMIT 模式下，主库先在本地提交，然后等待副本；若副本落后，其他会话可能看到尚未复制的数据（类似幻读）。","若超出 rpl_semi_sync_source_timeout，主库会自动退回异步模式而不会停止；副本恢复后再回到半同步，因此这种“保证”是有条件的。","半同步保证的是“已接收”，而不是“已在副本上应用”；若要严格实现 RPO=0，Group Replication 或外部共识层更为合适。"],"level":"expert"},{"id":"mysql-undo-purge","title":"MySQL InnoDB：undo 表空间与 purge","category":"MySQL","summary":"InnoDB 为实现 MVCC 将旧行版本保存在 undo log 中；purge 线程清理不再被任何读者需要的 undo 记录和死版本。","points":["每次变更都会为回滚和一致性读生成 undo log 记录；长时间运行的事务会使旧的 read view 保持存活，阻碍 purge 推进并导致 undo 增长（history list length 增加）。","若 history list length（SHOW ENGINE INNODB STATUS）持续增长，说明 purge 跟不上；主要原因通常是长时间打开的事务或保持活动的旧读（例如 mysqldump --single-transaction）。","innodb_purge_threads 控制 purge 并行度，innodb_max_purge_lag 在 purge 严重落后时为 DML 增加人为延迟，防止负载压垮 purge。","在 MySQL 8 中，undo 保存在独立的 undo 表空间中，增长的 undo 文件可通过自动截断（innodb_undo_log_truncate）收缩；在旧版本中，undo 位于系统表空间内且永不收缩。","膨胀的 undo 迫使一致性读遍历更长的版本链（I/O 和 CPU）；短事务和 autocommit 规范可将 undo 压力降到最低。"],"level":"expert"},{"id":"mysql-buffer-pool-lru","title":"MySQL InnoDB：buffer pool 实例与 LRU","category":"MySQL","summary":"InnoDB buffer pool 将数据页和索引页保存在内存中；中点插入（midpoint insertion）LRU 算法可防止表扫描将热点数据挤出内存。","points":["在专用数据库服务器上，buffer pool 通常设置为 RAM 的约 70-80%（innodb_buffer_pool_size；共享服务器上更低）；目标是将频繁访问的工作集保留在内存中，最大限度减少磁盘 I/O。","LRU 列表分为 'young'（热）和 'old'（冷）两个子列表；新读取的页不是插入列表头部，而是插入 old 区域的头部（中点，约 37%），因此一次性扫描不会挤出热点页。","old 区域中的页只有在经过 innodb_old_blocks_time 之后再次被访问时才会被提升到 young；这可防止大型全扫描污染 buffer pool。","innodb_buffer_pool_instances 将 buffer pool 拆分为多个独立实例；由于每个实例都有自己的 mutex，高并发下 buffer pool mutex 争用会减少（对大型 buffer pool 才有意义）。","监控 buffer pool 命中率以及空闲页/脏页的比例；大量脏页和激进的刷新可能导致“checkpoint 风暴”，innodb_io_capacity 根据存储能力设定刷新速率。"],"level":"expert"},{"id":"sqlserver-always-on-ag","title":"SQL Server：Always On 可用性组","category":"SQL Server","summary":"可用性组（Availability Group）将一组数据库复制到同步/异步副本；在 WSFC 上提供自动故障转移、可读副本和只读路由。","points":["使用同步提交副本时，主副本在提交前等待辅助副本将日志记录固化（harden）；这可实现 RPO=0，但若副本落后，会增加主副本上的提交延迟。","异步提交副本不会让主副本等待；适用于远程灾难恢复（DR）站点，但故障转移可能丢失部分数据（RPO>0），且只支持强制手动故障转移（FORCE_FAILOVER_ALLOW_DATA_LOSS）。","只有在保持 WSFC 仲裁的前提下，处于同步提交和“自动故障转移”模式的一对副本之间才会发生自动故障转移；健康检查通过 sp_server_diagnostics 和数据库运行状况检测完成。","可读辅助副本可分担主副本的报表负载；借助只读路由，使用 ApplicationIntent=ReadOnly 连接的客户端会通过侦听器自动路由到可读副本。","可读副本上的查询自动使用快照隔离；为此，主副本上被修改的行会添加 14 字节的版本标记，辅助副本上的 tempdb 版本存储负载也会增加；此外，若 redo 应用落后，副本上的数据可能过时。","AG 侦听器通过虚拟网络名称 + IP 向客户端提供单一连接点；故障转移时侦听器移至新的主副本，应用无需更改连接字符串即可继续工作。"],"level":"expert"},{"id":"sqlserver-columnstore","title":"SQL Server：列存储索引与批处理模式","category":"SQL Server","summary":"列存储索引以高压缩率按列存储数据；批处理模式（batch mode）处理和段消除可大幅加速分析型查询。","points":["数据在行组（rowgroup，约 100 万行）中拆分为列段，每个段都经过压缩；只读取查询所需的列，从而显著减少 I/O。","每个段都保存最小/最大值元数据；若查询谓词超出段的值范围，该段无需读取直接跳过（段消除），因此按排序顺序加载数据非常重要。","批处理模式按约 900 行一组处理，而不是逐行处理，提高每个 CPU 周期完成的工作量；在聚合和连接上比行模式快许多倍（SQL 2019+ 在行存储上也可使用批处理模式）。","聚集列存储非常适合大型分析/数据仓库事实表；对于 OLTP + 分析的混合负载，可在行存储表上使用非聚集列存储构建实时运营分析（HTAP）。","热（尚未压缩的）行以行方式保存在增量存储（deltastore）中，满后由 tuple mover 压缩；大量小批量加载会导致碎片，因此应优先使用批量加载。"],"level":"expert"},{"id":"sqlserver-inmemory-oltp","title":"SQL Server：内存中 OLTP（Hekaton）","category":"SQL Server","summary":"内存中 OLTP 通过无锁/无 latch 结构处理内存优化表并使用本机编译存储过程，消除高并发 OLTP 负载中的瓶颈。","points":["内存优化表完全驻留在内存中，所有行版本也都在内存中；并发通过无锁的乐观 MVCC 处理，消除了传统的 latch 和锁争用。","为保证持久性，变更仍会写入日志并刷新到检查点文件（data/delta）；SCHEMA_ONLY 表不记录日志，重启后被清空（例如非常适合临时暂存/会话数据）。","本机编译存储过程将 T-SQL 编译为机器码（DLL），消除解释开销；它只能访问内存优化表，并且只支持有限的 T-SQL 子集。","由于并发是乐观的，冲突在运行时检测；写-写冲突会以错误回滚事务（需要重试），因此应用必须包含重试逻辑。","最适合的场景：极高的插入速率（例如 IoT/遥测数据写入）、存在锁/latch 瓶颈的热点表以及使用 tempdb 的临时表；所有数据必须能放入内存，并须密切监控内存压力。"],"level":"expert"},{"id":"sqlserver-query-store","title":"SQL Server：使用 Query Store 管理执行计划","category":"SQL Server","summary":"Query Store 持久保存查询文本、执行计划和运行时统计信息；可用于检测计划回归并强制使用特定计划。","points":["Query Store 在数据库内部保存每个查询随时间使用过的所有执行计划，以及每个计划的耗时/CPU/读取指标；它用历史数据回答“为什么这个查询昨天快、今天慢”。","计划回归（新编译的计划比旧计划差）可通过 'Regressed Queries' 和 'Top Resource Consumers' 报告发现；用 sys.sp_query_store_force_plan 强制使用已知良好的计划即可恢复稳定。","借助 SQL Server 2022 中的 Query Store hints，无需强制计划或修改查询文本即可应用提示（例如 RECOMPILE、MAXDOP）；这对调优第三方应用的查询非常有用。","自动计划更正（automatic tuning，FORCE_LAST_GOOD_PLAN）会检测回归、自动强制使用之前的良好计划，若无改善则撤销；在生产环境中应在监督下启用。","正确设置捕获模式（ALL/AUTO/CUSTOM）和存储大小；AUTO 会过滤掉罕见/无关紧要的查询以减少开销和空间，Query Store 存满后会切换为只读模式并停止收集数据。"],"level":"expert"},{"id":"mongodb-sharding-deep","title":"MongoDB：分片、chunk 与 balancer","category":"MongoDB","summary":"MongoDB 按分片键将数据拆分为 chunk 并分布到各分片；balancer 负责均衡 chunk，分片键的选择决定数据分布和查询路由。","points":["数据按分片键范围拆分为 chunk 并分布到各分片；mongos 路由器根据分片键只将查询路由到相关分片（定向查询），否则广播到所有分片（scatter-gather）。","好的分片键基数高、频率低且不单调递增；单调递增的键（例如时间戳/ObjectId）会使所有新写入集中到单个分片（热点），而哈希分片可将其分散开。","当各分片之间的 chunk 数量失衡时，balancer 在后台迁移 chunk；迁移期间源与目标之间保持一致性，但会产生 I/O 开销，可安排在维护窗口内进行。","jumbo chunk 是超过最大 chunk 大小、且因分片键基数低而无法拆分的 chunk；它无法迁移并造成失衡，解决办法是使用选择性更高的（复合）分片键。","区域分片（zone sharding，tag-aware）将特定的分片键范围固定到特定分片上；用于按地理位置或硬件引导数据放置（例如将 EU 数据放在 EU 区域的分片上）。","尽管新版本引入了基于数据大小而非 chunk 数量的均衡以及自动碎片整理，但原则不变：要实现定向查询，每次读/写都必须包含分片键。"],"level":"expert"},{"id":"mongodb-oplog-election","title":"MongoDB：副本集 oplog 与选举","category":"MongoDB","summary":"在副本集中，主节点将写操作记录到 oplog，从节点应用这些操作；主节点故障时，通过类 Raft 的选举选出新的主节点。","points":["oplog 是一个 capped collection，以幂等（可安全重复应用）的形式记录主节点上的每个变更；从节点持续跟踪并应用 oplog 以保持同步。","oplog 大小决定复制窗口；若从节点离线时间超过 oplog 覆盖的范围，它会变为“stale”状态并需要完整重新同步（initial sync），因此 oplog 的大小必须能承受工作负载。","主节点故障时，从节点使用类 Raft 协议投票；获得多数票的候选者成为主节点，因此应保持奇数个投票成员（必要时使用 arbiter）以维持多数派。","writeConcern 'majority' 保证写入已到达多数节点，因此在故障转移时不会被回滚（回滚保护）；w:1 更快，但主节点故障时写入可能丢失。","readConcern 和 read preference 控制读取的新鲜度；从从节点读取可分散负载，但由于复制延迟可能返回过时数据，'majority'/'linearizable' 则提供更强的保证。","若旧主节点在网络分区后处于少数派，它会降级为从节点；重新加入新主节点时，它会将未提交的写入移入 rollback 文件（majority write concern 可避免这种情况）。"],"level":"expert"},{"id":"mongodb-wiredtiger-checkpoint","title":"MongoDB：WiredTiger 检查点与日志（journal）","category":"MongoDB","summary":"WiredTiger 通过定期检查点和预写日志（journal）提供持久性；借助文档级并发和压缩实现高吞吐量。","points":["WiredTiger 默认约每 60 秒执行一次检查点，将一致的内存快照写入磁盘；在两次检查点之间，通过将每次写入记录到 journal（WAL）来保证持久性。","没有 journal 时，崩溃会丢失最后一次检查点之后的写入；有 journal 时，恢复过程会加载最后一次检查点并从 journal 重放后续操作。","文档级并发控制允许同时更新同一集合中的不同文档；MVCC 快照提供一致性读。","WiredTiger 缓存（默认约为 RAM 的 50%）保存频繁访问的数据和索引；若工作集无法放入缓存，驱逐（eviction）和磁盘 I/O 会增加，缓存饱和会严重降低性能。","存储压缩默认使用 snappy（兼顾速度与压缩率）；zstd/zlib 压缩率更高但需要更多 CPU。索引还会通过前缀压缩进一步缩小。"],"level":"expert"},{"id":"redis-cluster-hashslot","title":"Redis Cluster：哈希槽与重新分片","category":"Redis","summary":"Redis Cluster 将键空间划分为 16384 个哈希槽并分布到各节点，从而实现横向扩展；客户端通过 MOVED/ASK 重定向找到正确的节点。","points":["每个键通过 CRC16(key) mod 16384 映射到一个槽；每个 master 拥有特定的槽范围，因此集群无需中央路由器即可分布数据。","若客户端访问了错误的节点，该节点以 MOVED 回复，指明该槽的永久所有者，客户端随即更新其槽映射。重新分片期间，对正在迁移的槽使用临时的 ASK 重定向。","重新分片将槽（及其中的键）在线从一个节点迁移到另一个节点；迁移逐键进行，集群在整个过程中持续提供服务。","多键操作（MSET、事务、Lua）只适用于同一槽中的键；使用哈希标签（{...}）将相关键归入同一槽，例如 user:{123}:name 和 user:{123}:email。","为实现高可用，每个 master 至少配置一个副本；master 故障时其副本被提升。集群由多数 master 做出决定，若失去多数，写入将停止。","若有任何槽未分配或没有 master，集群将拒绝所有写入（启用 cluster-require-full-coverage 时）；必须分配全部 16384 个槽。"],"level":"expert"},{"id":"redis-replication-psync","title":"Redis：复制与 PSYNC","category":"Redis","summary":"在 Redis 异步复制中，master 先将数据快照传输给副本，然后持续发送后续命令；短暂断开后，PSYNC 执行部分重新同步。","points":["初始同步时，master 生成 RDB 快照并发送给副本（全量重新同步）；同时将新到达的写入缓存在复制积压缓冲区（replication backlog）中，并在 RDB 之后发送。","PSYNC 跟踪副本的复制偏移量和 master 复制 ID；短暂断开后，若遗漏的数据仍在积压缓冲区中，则只发送缺失部分（部分重新同步），避免代价高昂的全量重新同步。","repl-backlog-size 决定可容忍多长时间的断开；在高写入速率下，积压缓冲区过小会导致断开后执行全量重新同步，这意味着 master 上要生成 RDB 并产生网络开销。","复制是异步的：master 不等待副本即确认写入，因此 master 突然崩溃时最新的写入可能丢失；可使用 WAIT 命令等待指定数量的副本接收到写入，但这并不是真正的同步保证。","使用无盘复制（repl-diskless-sync）时，master 可直接将 RDB 流式写入 socket 而不落盘；在磁盘慢但网络快的环境中可加快初始同步。"],"level":"expert"},{"id":"redis-memory-fragmentation","title":"Redis：内存碎片与 jemalloc","category":"Redis","summary":"Redis 默认使用 jemalloc；大小不一的分配和释放会随时间产生内存碎片，使 RSS 看起来大于实际使用的数据量。","points":["mem_fragmentation_ratio = used_memory_rss / used_memory；接近 1 为理想状态，明显大于 1（例如 >1.5）表示存在碎片，小于 1 则表示数据被交换到磁盘（不良）。","不同大小的键不断创建和删除时，jemalloc arena 中会留下无法利用的空隙，从而产生碎片；频繁修改大值会使情况更糟。","主动碎片整理（activedefrag yes）借助 jemalloc 在运行时压缩内存块以减少碎片；通过调整其阈值（active-defrag-ignore-bytes、-threshold-lower/upper）来限制开销。","正确设置 maxmemory 和 maxmemory-policy；由于 jemalloc 实际分配的 RSS 可能超过 maxmemory，应将 maxmemory 设置得低于 RAM，以避免操作系统 OOM。","若 used_memory 较低但 RSS 居高不下，重启（或通过故障转移重启节点）通常能彻底重置碎片；在大多数情况下，主动碎片整理可减少重启的需要。"],"level":"expert"},{"id":"elasticsearch-shard-sizing","title":"Elasticsearch：分片大小规划","category":"Elasticsearch","summary":"每个分片都是一个 Lucene 索引，具有固定的资源开销；分片过多过小（oversharding）或分片过大都会降低性能和稳定性。","points":["经验法则：将分片大小保持在 10-50 GB 范围内；过大的分片会减慢恢复和重新均衡，过小的分片（oversharding）会给集群状态和堆带来不必要的负担。","每个分片，尤其是每个映射字段，都会消耗堆内存。旧的“每 GB 堆约 20 个分片”规则自 8.3 起不再适用（每个分片的堆开销大幅下降）；当前建议是在数据节点上为每个索引的每个映射字段预留约 1 KB 堆内存外加额外开销，并使每个节点的分片数低于 cluster.max_shards_per_node（默认 1000）。分片过多仍会使集群状态膨胀并增加协调开销。","查询延迟与分片数有关：查询会扇出到所有相关分片，然后合并结果；分片数超出需要时，即使是小查询也会因协调开销而变慢。","对于时序/日志数据，应使用基于时间的索引（data streams）而不是一个巨大的索引；这样按索引迁移/删除旧数据（ILM）的成本很低，分片也易于管理。","主分片数在创建索引时确定，之后无法更改（只能通过 reindex/split/shrink）；因此应根据增长预估提前规划好，而副本数可以动态调整。"],"level":"expert"},{"id":"elasticsearch-ilm-tiers","title":"Elasticsearch：ILM 与 hot-warm-cold-frozen 分层","category":"Elasticsearch","summary":"索引生命周期管理（ILM）根据生命周期阶段在不同成本/性能层之间自动迁移索引，从而经济地管理时序数据。","points":["ILM 策略让索引依次经历各阶段：hot（活跃写入 + 频繁查询，高速 SSD）、warm（写入结束，偶尔查询）、cold（很少查询，较便宜的硬件）、frozen（极少查询，通过 searchable snapshot 从对象存储提供服务）。","当 hot 索引达到指定大小/时长/文档数时，rollover 会创建新索引；这样单个索引不会无限增长，每个阶段都按索引进行管理。","frozen 层通过 searchable snapshot 将数据保存在对象存储（S3/blob）中，仅在需要时将部分数据拉入内存；可大幅降低磁盘成本，但查询延迟会增加。","迁移到 warm/cold 时，通过 force merge、shrink 和减少副本来优化存储和堆；由于这些阶段的数据不再变化，可将段合并为单个段。","将数据层映射到节点角色（data_hot/data_warm/data_cold/data_frozen）；ILM 使用分片分配过滤器将索引放置到具备相应硬件的节点上。"],"level":"expert"},{"id":"elasticsearch-segment-merge","title":"Elasticsearch：段合并与 force merge","category":"Elasticsearch","summary":"Lucene 每次 refresh 都会创建新的不可变段；后台合并负责合并和清除段，而 force merge 是需要谨慎使用的手动操作。","points":["每次 refresh（默认 1 s）都会产生一个新段，删除操作在段中被标记为“tombstone”；大量小段会减慢读取，因此由后台合并将它们合并。","已删除/已更新的文档只有在合并时才会从磁盘上物理移除；在大量更新负载下，“deleted docs”会在段中累积，磁盘/内存占用随之膨胀。","对不再写入的（只读）索引执行 force merge 合并到 1 个段，可提升查询速度和磁盘效率；这是 ILM warm 阶段的典型步骤。","切勿对正在写入的索引执行 force merge；它产生的超大段不会进入正常的合并周期，后续删除无法被清理，并会长时间占满 I/O 和堆。","批量加载期间临时增大 refresh_interval（例如 -1 或 30s）可产生更少、更大的段，从而降低合并负载并提高写入吞吐量。"],"level":"expert"},{"id":"elasticsearch-node-roles","title":"Elasticsearch：节点角色与集群拓扑","category":"Elasticsearch","summary":"Elasticsearch 节点承担 master、data、coordinating 和 ingest 等角色；在大型集群中分离角色可提供稳定性和可扩展性。","points":["具备 master 资格的节点管理集群状态（索引、分片放置、mapping）；为实现稳定的选举，应使用奇数个（通常为 3 个）专用 master 节点，并依靠 quorum（多数派）防止脑裂。","data 节点承载分片并处理索引和查询 I/O；它们消耗最多的 CPU/RAM/磁盘，并可划分为数据层（hot/warm/cold/frozen）。","coordinating-only 节点不承担其他角色，负责将请求分发到相关分片并合并结果（scatter-gather）；它通过承担繁重的搜索/聚合合并工作来保护 data 节点，并起到负载均衡器的作用。","ingest 节点在索引之前通过 pipeline 处理数据（转换、扩充）；将繁重的 ingest pipeline 移到专用 ingest 节点上可保护 data 节点的查询性能。","切勿让 master 节点分担繁重的查询/数据负载；master 在负载下变慢会延迟集群状态更新，导致整个集群不稳定（分片分配延迟、选举）。"],"level":"expert"}]},"guide/glossary.json":{"terms":[{"term":"ACID","full":"原子性（Atomicity）、一致性（Consistency）、隔离性（Isolation）、持久性（Durability）","definition":"使事务可靠的四个核心特性：原子性（要么全部完成，要么全部不做）、一致性（规则得到保持）、隔离性（并发事务互不破坏）和持久性（已提交的数据不会丢失）。它是关系型数据库的事务保证。","tags":["transaction","transaction","acid","consistency","consistency"]},{"term":"BASE","full":"基本可用（Basically Available）、软状态（Soft state）、最终一致（Eventually consistent）","definition":"一种宽松的一致性方法，是 ACID 的替代方案，在分布式和 NoSQL 系统中备受青睐。系统优先保证始终可用，并接受数据随时间趋于一致（最终一致性）。它以牺牲严格一致性换取可扩展性。","tags":["base","distributed","distributed","eventual-consistency","nosql"]},{"term":"MVCC","full":"多版本并发控制（Multi-Version Concurrency Control）","definition":"一种并发方法，通过保留同一行的多个版本，使读者和写者无需相互加锁即可工作。每个事务看到自己的数据快照，因此读不阻塞写，写也不阻塞读。用于 PostgreSQL 和 Oracle 等系统。","tags":["concurrency","concurrency","lock","mvcc","snapshot"]},{"term":"WAL","full":"预写日志（Write-Ahead Log）","definition":"一种原则：在变更写入数据文件之前，先将日志记录持久地写入磁盘。崩溃恢复和持久性由它保证。在 PostgreSQL 中称为 WAL，在 SQL Server 中称为事务日志。","tags":["wal","log","recovery","recovery","durability"]},{"term":"重做日志（Redo Log）","full":"","definition":"记录对数据库所做变更的日志，用于重新应用（redo）崩溃中丢失的工作。恢复期间，它将已提交但尚未写入磁盘的变更前滚。Oracle 的 redo log 对应于 WAL 这一通用概念。","tags":["redo","log","recovery","recovery","oracle"]},{"term":"Undo / 回滚段（Rollback Segment）","full":"","definition":"存储事务所修改数据的前镜像的区域。它有两个用途：通过回滚撤销变更，以及在 MVCC 下向读者提供旧版本。在 Oracle 中称为 undo/回滚段，在其他系统中称为 undo/版本存储。","tags":["undo","rollback","rollback","mvcc","segment"]},{"term":"Buffer Pool / Buffer Cache","full":"","definition":"保存常用数据页和索引页的内存缓存。由于磁盘读取代价高，数据库性能在很大程度上取决于这里的命中率（缓存命中率）。在 MySQL InnoDB 中称为 buffer pool，在 SQL Server 中称为 buffer cache。","tags":["buffer-pool","buffer-cache","cache","cache","memory"]},{"term":"脏页（Dirty Page）","full":"","definition":"在内存中已修改但尚未写入磁盘的数据页。持久性由 WAL 保护；页本身保持为脏，直到 checkpoint 或后台写进程将其刷新到磁盘。脏页过多会延长恢复时间。","tags":["dirty-page","dirty-page","memory","checkpoint","io"]},{"term":"LSN","full":"日志序列号（Log Sequence Number）","definition":"分配给 WAL/事务日志中每条记录的、持续递增的唯一序列号。用于确定某个页已更新到哪条日志记录，以及恢复应从何处继续。它也用于跟踪复制进度。","tags":["lsn","log","sequence-number","recovery","replication"]},{"term":"SCN","full":"系统变更号（System Change Number）","definition":"在 Oracle 中，分配给数据库中每个提交点的、单调递增的逻辑时间戳。用于读一致性、MVCC 以及恢复期间的事件排序。可视为 Oracle 中与 LSN 相对应的概念。","tags":["scn","oracle","timestamp","read-consistency","commit"]},{"term":"隔离级别（Isolation Level）","full":"隔离级别（Isolation Level）","definition":"决定并发事务在多大程度上可以看到彼此变更的设置。级别包括 Read Uncommitted、Read Committed、Repeatable Read 和 Serializable；级别越高，一致性越强，并发性越低。它控制脏读/不可重复读/幻读等异常。","tags":["isolation","isolation-level","transaction","concurrency","concurrency"]},{"term":"脏读（Dirty Read）","full":"","definition":"一个事务读取了另一个事务尚未提交（并可能回滚）的变更。由于读取的数据可能无效，这是一种一致性异常。只有在 Read Uncommitted 隔离级别下才可能发生。","tags":["dirty-read","anomaly","isolation","transaction","consistency"]},{"term":"不可重复读（Non-repeatable Read）","full":"","definition":"在同一事务中两次读取同一行时看到不同的值，原因是另一个事务在两次读取之间提交了变更。可能发生在 Read Committed 下；在 Repeatable Read 及以上级别可避免。","tags":["non-repeatable-read","anomaly","isolation","transaction","non-repeatable"]},{"term":"幻读（Phantom Read）","full":"","definition":"在同一事务中以相同条件再次执行查询时，由于中间发生了插入/删除，结果行集发生变化（出现新的“幻影”行）。按照 SQL 标准，在 Serializable 级别可避免；PostgreSQL 在 Repeatable Read 级别也能避免。","tags":["phantom-read","phantom","anomaly","serializable","isolation"]},{"term":"死锁（Deadlock）","full":"","definition":"两个或多个事务各自等待对方持有的锁，导致谁都无法继续的情况。数据库会检测到死锁并打破循环，通常选择代价最小的事务作为“牺牲者”并将其回滚。始终按相同顺序获取锁可降低风险。","tags":["deadlock","deadlock","lock","lock","concurrency"]},{"term":"锁升级（Lock Escalation）","full":"","definition":"当一个事务持有大量行/页锁时，数据库将其转换为单个粒度更粗的锁（例如表锁）。它减少了锁管理所需的内存，但会降低并发性并可能增加阻塞。在 SQL Server 中尤为常见。","tags":["lock-escalation","lock","lock","concurrency","sql-server"]},{"term":"Latch","full":"","definition":"一种轻量、低开销的并发锁，用于在极短时间内保护内存结构（例如 buffer pool 页）。与事务锁不同，它保护的是物理内存结构而非逻辑数据，且持有时间极短。","tags":["latch","lock","memory","concurrency","internal"]},{"term":"B-Tree 索引","full":"平衡树索引（Balanced Tree Index）","definition":"最常见的索引类型，将数据保存在有序的平衡树结构中。适用于等值和范围查询、排序以及前缀搜索。它是大多数数据库的默认索引类型。","tags":["btree","index","index","search","range"]},{"term":"哈希索引（Hash Index）","full":"","definition":"一种通过哈希函数定位键的索引类型。只对精确等值（=）查找非常快；不能用于范围查询或排序。例如 PostgreSQL 和 MySQL Memory 表中提供该索引。","tags":["hash-index","index","index","equality","search"]},{"term":"位图索引（Bitmap Index）","full":"","definition":"为每个不同值保存位数组的索引，适用于低基数列（不同值很少）。可通过 AND/OR 快速组合多个条件；对数据仓库/OLAP 负载很有效，但在大量写入下效率低。","tags":["bitmap-index","index","index","olap","cardinality"]},{"term":"覆盖索引（Covering Index）","full":"","definition":"包含查询所需全部列的索引；查询仅凭索引即可完成，无需访问表（堆/聚集）。它通过消除代价高昂的行查找来提升性能。可通过 INCLUDE 列创建。","tags":["covering-index","index","index","performance","lookup"]},{"term":"聚集索引（Clustered Index）","full":"","definition":"按索引键对表中的行进行物理排序的索引；表本身就按该顺序存储。一张表只能有一个。对范围查询很快；在 InnoDB 中，主键始终是聚集的。","tags":["clustered-index","index","index","physical","primary-key"]},{"term":"基数（Cardinality）","full":"","definition":"列中不同值的数量。高基数（例如 email）表示不同值很多，低基数（例如性别）表示不同值很少。它是优化器选择索引和估算计划代价的关键指标。","tags":["cardinality","cardinality","statistics","optimizer","distinct"]},{"term":"选择性（Selectivity）","full":"","definition":"表示过滤条件返回的行占总行数的比例有多小。高选择性（行少）使索引更具优势；低选择性（行多）时全扫描可能更便宜。优化器在选择计划时会使用它。","tags":["selectivity","selectivity","optimizer","index","statistics"]},{"term":"执行计划（Execution Plan）","full":"","definition":"展示优化器为执行查询所选步骤（扫描类型、连接方法、排序等）的路线图。用 EXPLAIN/EXPLAIN ANALYZE 检查它可以找出性能问题的根本原因。比较估算代价与实际代价非常重要。","tags":["execution-plan","query-plan","explain","optimizer","performance"]},{"term":"基于代价的优化器（Cost-Based Optimizer）","full":"CBO","definition":"根据统计信息估算各种可能执行计划的代价并选择最便宜者的查询优化器。最新的统计信息是做出正确决策的前提；过时的统计信息会导致糟糕的计划。它是现代数据库的标准做法。","tags":["cbo","optimizer","cost","statistics","query-plan"]},{"term":"统计信息","full":"统计信息","definition":"为表/索引收集的元数据，供优化器估算行数、基数和值分布。统计信息准确意味着查询计划良好；过时时须用 ANALYZE/UPDATE STATISTICS 刷新。它们是性能不佳最常见的原因之一。","tags":["statistics","statistics","optimizer","analyze","query-plan"]},{"term":"直方图（Histogram）","full":"","definition":"一种统计结构，将列中的值划分为若干范围（桶）以概括其分布。它使优化器能更准确地估算行数，尤其是在分布倾斜时。它是统计信息的组成部分。","tags":["histogram","statistics","statistics","optimizer","distribution"]},{"term":"分区（Partition）","full":"","definition":"将大表在物理上拆分为较小部分（例如按日期或范围），而逻辑上仍为一张表的技术。查询只扫描相关部分（分区裁剪），维护和归档也更容易。与分片的区别：它仍位于同一服务器/实例内。","tags":["partition","partitioning","scaling","pruning","big-data"]},{"term":"分片","full":"","definition":"根据分片键将数据横向分布到多个独立服务器/数据库上的方法。它提供超越单机限制的横向扩展，但会使查询和事务管理复杂化。与分区的区别：数据分布在不同节点上。","tags":["sharding","scaling","distributed","distributed","horizontal-scaling"]},{"term":"复制（同步/异步）","full":"","definition":"将数据从主节点复制到一个或多个副本（replica）。同步复制中，提交需等待副本确认（无数据丢失，延迟较高）；异步复制中则不等待（速度快，但若副本落后可能丢失数据）。用于 HA 和读扩展。","tags":["replication","replication","synchronous","asynchronous","ha"]},{"term":"逻辑复制与物理复制","full":"","definition":"物理复制在磁盘块/WAL 级别创建完全相同的副本（相同版本、整个数据库）。逻辑复制在行/SQL 级别传输变更；它支持选择性地复制表，并可跨版本和不同 schema 传输。CDC 和升级场景首选逻辑复制。","tags":["logical-replication","physical-replication","replication","cdc","wal"]},{"term":"故障转移","full":"","definition":"主节点故障时，自动或手动将服务转移到备用节点。它将停机时间降到最低；RPO 决定可能丢失多少数据，RTO 决定恢复需要多长时间。它在计划外故障时发挥作用。","tags":["failover","ha","high-availability","standby","rpo"]},{"term":"主备切换（Switchover）","full":"","definition":"有计划、受控地互换主库和备库角色（例如为了维护）。与故障转移的区别：它是计划内操作而非故障，通常不会丢失数据。","tags":["switchover","planned","ha","maintenance","standby"]},{"term":"RPO","full":"恢复点目标（Recovery Point Objective）","definition":"灾难发生后可接受的最大数据丢失量（以时间表示）。例如 RPO 为 5 分钟，表示最多容忍 5 分钟的数据丢失。备份频率和复制类型据此目标来选择。","tags":["rpo","data-loss","dr","disaster-recovery","backup"]},{"term":"RTO","full":"恢复时间目标（Recovery Time Objective）","definition":"中断后系统重新投入运行的最长目标时间。例如 RTO 为 1 小时，表示服务最迟须在 1 小时内恢复。HA/DR 架构和自动化程度均围绕该目标设计。","tags":["rto","recovery-time","dr","disaster-recovery","ha"]},{"term":"HA","full":"高可用（High Availability）","definition":"使系统在发生故障时仍能不中断或极少中断地提供服务的设计目标。通过冗余、自动故障转移和健康检查实现。通常以正常运行时间百分比表示（例如 99.99%）。","tags":["ha","high-availability","high-availability","failover","redundancy"]},{"term":"DR","full":"灾难恢复（Disaster Recovery）","definition":"在火灾、洪水或整个区域中断等重大灾难之后，通常在地理上独立的区域恢复系统和数据的计划与流程。以 RPO 和 RTO 目标来衡量。HA 关注日常故障，DR 关注重大灾难。","tags":["dr","disaster-recovery","disaster-recovery","rpo","rto"]},{"term":"仲裁（Quorum）","full":"","definition":"在分布式系统中，使某项决策（例如领导者选举或写入确认）生效所需的最少多数节点数（通常为 N/2 + 1）。用于防止脑裂并保持一致性。这也是集群规模大多为奇数的原因。","tags":["quorum","majority","distributed","distributed","split-brain"]},{"term":"脑裂（Split-Brain）","full":"","definition":"网络分区后，集群的两半都认为自己是主节点并各自独立写入的情况；这会导致数据冲突和损坏。可通过仲裁（quorum）和隔离（fencing）机制防止。它是 HA 集群中最危险的故障场景之一。","tags":["split-brain","distributed","quorum","ha","consistency"]},{"term":"TDE","full":"透明数据加密（Transparent Data Encryption）","definition":"一种对应用透明的加密方法，使数据库文件（数据和日志）在磁盘上保持加密。即使磁盘或备份文件被盗也能保护数据（静态数据）。它不保护网络上传输的数据（传输中数据），这需要 TLS。","tags":["tde","encryption","encryption","security","security"]},{"term":"RBAC","full":"基于角色的访问控制（Role-Based Access Control）","definition":"一种访问控制模型：权限分配给角色而非直接分配给用户，用户成为角色的成员。它简化了权限管理，更便于贯彻最小权限原则。随着用户数量增长，管理仍保持可扩展。","tags":["rbac","role","privilege","security","security"]},{"term":"连接池（Connection Pool）","full":"","definition":"预先创建连接并重复使用的池，避免应用反复打开和关闭数据库连接。它降低连接建立开销、限制资源消耗并控制负载。通过防止连接过多来保护数据库。","tags":["connection-pool","connection-pool","performance","resource","application"]},{"term":"预编译语句（Prepared Statement）","full":"","definition":"一种结构：SQL 语句只解析和规划一次，然后以不同的参数值重复执行。它通过重用计划提升性能，并防御 SQL 注入。与绑定变量配合使用。","tags":["prepared-statement","bind","security","sql-injection","performance"]},{"term":"绑定变量（Bind Variable）","full":"","definition":"通过占位符传递的参数，而不是将值嵌入 SQL 文本中。它避免同一查询因值不同而被重新规划，从而高效利用计划缓存，并防止 SQL 注入。对字面值频繁变化的查询至关重要。","tags":["bind-variable","parameter","prepared-statement","plan-cache","security"]},{"term":"参数嗅探（Parameter Sniffing）","full":"","definition":"优化器根据首次执行时的参数值为预编译/参数化查询生成并缓存计划。若该值不具代表性，对后续不同值表现不佳的计划可能会长期存在。解决办法包括重新编译计划（recompile）和优化器提示。","tags":["parameter-sniffing","plan-cache","optimizer","performance","sql-server"]},{"term":"VACUUM / 膨胀（Bloat）","full":"","definition":"由于 MVCC，已删除/已更新行的死版本会在表中累积；这种膨胀称为 bloat。VACUUM 回收这些死行的空间并更新统计信息（PostgreSQL）。若不定期执行，磁盘会膨胀，性能会下降。","tags":["vacuum","bloat","bloated","mvcc","postgresql"]},{"term":"压缩合并（Compaction）","full":"","definition":"在基于 LSM 树的存储（例如 Cassandra、RocksDB）中，合并大量小数据文件并清除死记录/过时记录的过程。它提升读取性能并回收磁盘空间。它相当于 LSM 世界中的 VACUUM。","tags":["compaction","lsm-tree","merge","cassandra","rocksdb"]},{"term":"Oplog","full":"操作日志（Operations Log）","definition":"在 MongoDB 中，一个特殊的固定大小（capped）集合，按顺序记录应用到主节点的所有写操作。副本集成员从中读取以与主节点保持同步。它是 CDC 和复制的基础。","tags":["oplog","mongodb","replication","cdc","log"]},{"term":"Binlog","full":"二进制日志（Binary Log）","definition":"在 MySQL 中，记录所有修改数据的操作（或行变更）的二进制日志。用于复制、时间点恢复和 CDC。可采用 row、statement 或 mixed 格式。","tags":["binlog","mysql","replication","cdc","recovery"]},{"term":"CDC","full":"变更数据捕获（Change Data Capture）","definition":"捕获数据库中的插入/更新/删除变更并将其以流的形式传送到其他系统（数据仓库、搜索引擎、消息队列）的技术。通常通过读取事务日志（binlog、WAL、oplog）以低开销运行。它是实时数据集成的基础。","tags":["cdc","change-data-capture","data-streaming","binlog","integration"]},{"term":"物化视图（Materialized View）","full":"","definition":"将查询结果物理存储在磁盘上的视图；与普通视图不同，它不会在每次查询时重新计算。它能加速代价高昂的聚合和连接，但其数据会过时，必须通过刷新（refresh）来更新。常见于 OLAP/报表场景。","tags":["materialized-view","view","cache","olap","reporting"]},{"term":"外键（Foreign Key）","full":"","definition":"一种参照完整性约束，使一张表中的列可以引用另一张表的主键/候选键。它防止引用不存在的行并保持关系完整。ON DELETE/UPDATE CASCADE 等规则定义依赖行的行为。","tags":["foreign-key","foreign-key","integrity","relationship","constraint"]},{"term":"规范化（Normalization）","full":"规范化（Normalization）","definition":"将数据拆分为相关联的表以减少冗余、降低不一致风险的过程（1NF、2NF、3NF 等范式）。它可防止更新异常，在 OLTP 系统中是首选。代价是查询可能需要更多连接。","tags":["normalization","normalization","design","oltp","redundancy"]},{"term":"反规范化（Denormalization）","full":"反规范化（Denormalization）","definition":"有意复制数据以减少连接数量、提升读取性能的技术。它能加快查询，但会带来数据重复和更新复杂性。适用于报表/OLAP 和读密集型负载。","tags":["denormalization","denormalization","performance","olap","read"]},{"term":"OLTP","full":"联机事务处理（Online Transaction Processing）","definition":"处理大量短小、频繁且并发的事务（插入/更新/删除、单行读取）的系统类型；例如银行、电子商务。它要求低延迟和高并发，通常采用规范化设计。它是与 OLAP 相对的操作型系统。","tags":["oltp","transaction","transaction","operational","normalization"]},{"term":"OLAP","full":"联机分析处理（Online Analytical Processing）","definition":"在大型数据集上运行复杂聚合和分析查询的系统类型；例如报表、商业智能。运行的查询少但繁重，通常采用反规范化和列式设计。它是与 OLTP 相对的分析型系统。","tags":["olap","analytics","reporting","data-warehouse","columnar"]},{"term":"ETL","full":"抽取、转换、加载（Extract, Transform, Load）","definition":"从数据源抽取数据（Extract）、按业务规则转换（Transform）并加载到目标系统（通常是数据仓库）（Load）的过程。在加载后于目标端进行转换的变体称为 ELT。它是数据集成的经典方法。","tags":["etl","data-integration","data-warehouse","elt","transformation"]},{"term":"热/冷数据（Hot/Cold Data）","full":"","definition":"频繁访问的当前数据称为“热”数据；很少访问的旧数据/归档数据称为“冷”数据。热数据放在快速/昂贵的存储上，冷数据放在便宜/慢速的存储上（数据分层）。这是一种平衡成本与性能的策略。","tags":["hot-data","cold-data","hot-cold","archive","tiering"]},{"term":"索引碎片（Index Fragmentation）","full":"","definition":"随着时间推移，插入/删除/更新会使索引页的逻辑顺序与物理顺序不一致，并降低页的填充度。它造成不必要的 I/O 和空间浪费，降低性能。可通过 REBUILD 或 REORGANIZE 修复。","tags":["index-fragmentation","fragmentation","index","maintenance","performance"]},{"term":"两阶段提交（Two-Phase Commit）","full":"2PC","definition":"为跨多个数据库/资源的分布式事务提供原子提交的协议。它分两个阶段工作：首先协调者询问所有参与者是否就绪（prepare）；若全部同意则提交，若有一个拒绝则全部回滚。它速度较慢，且对协调者故障敏感。","tags":["two-phase-commit","2pc","distributed","distributed","atomic"]}]},"guide/glossary-2.json":{"terms":[{"term":"LSM 树（LSM Tree）","full":"日志结构合并树（Log-Structured Merge Tree）","definition":"一种存储结构：先将写入累积在内存结构（memtable）中，再以有序块的形式刷新到磁盘。它将随机写转换为顺序写以获得高写入吞吐量；读取则需扫描多个层级。RocksDB、Cassandra 和 LevelDB 都以它为基础。","tags":["storage","storage engine","lsm","write"]},{"term":"B+ 树（B+ Tree）","full":"","definition":"一种平衡树结构：所有数据保存在叶节点中，内部节点只包含路由键。相互链接的叶节点可加速范围扫描。它是关系型数据库索引最常用的结构。","tags":["index","index structure","storage","storage engine","btree"]},{"term":"布隆过滤器（Bloom Filter）","full":"","definition":"一种概率型数据结构，用很少的内存检查某个元素是否在集合中。它回答“一定不在”或“可能在”；可能出现误报，但不会漏报。在基于 LSM 的引擎中用于避免不必要的磁盘读取。","tags":["storage","storage engine","probabilistic","filter","performance"]},{"term":"一致性哈希（Consistent Hashing）","full":"","definition":"一种分布方法，确保添加或移除节点时只有一小部分键需要迁移。键和节点被放置在一个虚拟环上。用于分布式缓存和 NoSQL 系统中的数据分布。","tags":["distributed systems","distributed","hashing","sharding","scaling"]},{"term":"向量时钟（Vector Clock）","full":"","definition":"一种为每个节点保存计数器的结构，用于跟踪分布式系统中事件的因果顺序。它判断两个更新是否存在先后关系，还是并发的（冲突的）。用于冲突检测和最终一致性。","tags":["distributed systems","distributed","clock","causality","consistency"]},{"term":"Gossip 协议（Gossip Protocol）","full":"","definition":"一种通信方法：每个节点定期将自身状态传播给随机选择的几个对等节点，使信息像流行病一样传遍整个集群。它不需要中央协调者，具有可扩展性和容错性。Cassandra 和 Consul 用它进行成员管理。","tags":["distributed systems","distributed","gossip","cluster","membership"]},{"term":"Raft","full":"","definition":"一种旨在比 Paxos 更易理解的共识算法。它选举一个领导者，将所有写入追加到通过领导者复制的日志中，并在获得多数确认后使其持久化。etcd 和 Consul 都使用它。","tags":["distributed systems","distributed","consensus","raft","replication"]},{"term":"Paxos","full":"","definition":"一种经典的共识算法，保证在不可靠的网络上多数节点就单个值达成一致。它是正确的，但被认为实现复杂；它构成了许多分布式系统的理论基础。用于 Google Chubby 等系统。","tags":["distributed systems","distributed","consensus","paxos","agreement"]},{"term":"领导者选举（Leader Election）","full":"","definition":"在分布式集群中选出单个节点（领导者）来协调特定任务的过程。若当前领导者故障，会自动选出新的领导者。为防止脑裂，通常需要多数（quorum）批准。","tags":["distributed systems","distributed","leader","election","coordination"]},{"term":"仲裁写/读（Quorum Write/Read）","full":"","definition":"读取或写入被视为有效所需的最少节点数。当 W + R > N 成立时，可保证读到最新数据。它使得在一致性和可用性之间调整权衡成为可能。","tags":["distributed systems","distributed","quorum","consistency","replication"]},{"term":"WAL 传送（WAL Shipping）","full":"预写日志传送（Write-Ahead Log Shipping）","definition":"将主服务器产生的 WAL 记录发送到备用服务器并在那里重放。这样备库就能保持主数据库的近乎实时的副本。它是 PostgreSQL 流复制的基础。","tags":["replication","replication","wal","high-availability","standby"]},{"term":"日志传送（Log Shipping）","full":"","definition":"一种简单的复制方法，定期将事务日志备份复制到备用服务器并还原。其延迟高于流复制，但易于搭建。在 SQL Server 中常用于灾难恢复。","tags":["replication","replication","log","backup","standby"]},{"term":"快照隔离（Snapshot Isolation）","full":"","definition":"一种隔离级别：每个事务看到的是其开始时刻数据的一致性快照。读者不阻塞写者；但它容易出现 write skew 等异常。与 MVCC 一起实现。","tags":["isolation level","isolation","mvcc","snapshot","concurrency"]},{"term":"可串行化快照隔离（SSI）","full":"可串行化快照隔离（Serializable Snapshot Isolation）","definition":"一种在快照隔离之上通过跟踪危险的读写依赖来提供真正可串行化的方法。检测到冲突时，其中一个事务会被回滚。用于 PostgreSQL 的 SERIALIZABLE 级别。","tags":["isolation level","isolation","serializable","ssi","concurrency"]},{"term":"间隙锁（Gap Lock）","full":"","definition":"作用于索引记录之间间隙（尚不存在的范围）的锁，防止新行插入该范围。用于防止幻读。在 InnoDB 的 REPEATABLE READ 级别下发挥作用。","tags":["locking","lock","innodb","phantom","concurrency"]},{"term":"Next-Key 锁（Next-Key Lock）","full":"","definition":"索引记录锁与其之前间隙锁（gap lock）的组合。它同时保护现有行和范围，从而防止幻影插入。它是 InnoDB 默认的行锁行为。","tags":["locking","lock","innodb","gap","concurrency"]},{"term":"乐观锁（Optimistic Locking）","full":"","definition":"一种假定冲突很少发生、无需加锁即可工作的方法，只在提交时检查数据是否被修改。通常通过版本号或时间戳实现。若发现冲突，则重试该操作。","tags":["locking","lock","optimistic","concurrency","concurrency"]},{"term":"悲观锁（Pessimistic Locking）","full":"","definition":"一种假定冲突必然发生、在读取或修改数据时立即加锁的方法。其他事务需等待锁释放。它在高争用环境中保护数据完整性，但会降低并发性。","tags":["locking","lock","pessimistic","concurrency","concurrency"]},{"term":"幂等性（Idempotency）","full":"","definition":"一个操作执行一次或多次都产生相同结果的特性。它使网络错误后的安全重试成为可能。在分布式系统中，它防止重复请求产生副作用。","tags":["distributed systems","distributed","idempotent","retry","reliability"]},{"term":"熔断器（Circuit Breaker）","full":"","definition":"一种设计模式，通过暂时切断对持续失败的服务的调用来保护系统。在熔断器“打开”期间，请求被快速拒绝，一段时间后再进行试探性调用。它可防止级联故障。","tags":["distributed systems","distributed","fault tolerance","resilience","pattern"]},{"term":"Saga 模式（Saga Pattern）","full":"","definition":"一种管理跨多个服务的长事务的模式：每一步都是本地事务，失败时使用补偿事务。在分布式环境中，它提供最终一致性而非 ACID。常见于微服务架构。","tags":["distributed systems","distributed","saga","microservices","pattern"]},{"term":"Outbox 模式（Outbox Pattern）","full":"","definition":"一种模式：在同一事务中将消息写入“outbox”表，使数据库更新与消息发布具有原子性。由单独的进程读取该表并发布消息。这可防止数据与事件发布之间的不一致。","tags":["distributed systems","distributed","outbox","messaging","pattern"]},{"term":"扇出（Fan-out）","full":"","definition":"将单个操作分发给许多子操作或目标；例如将一条帖子写入数千名关注者的信息流中。可在写入时进行（写扩散），也可在读取时进行（读扩散）。是扩展时的重要设计决策。","tags":["distributed systems","distributed","scaling","fanout","architecture"]},{"term":"热点（Hotspot）","full":"","definition":"负载过度集中在某个特定节点、分区或键上。该点会成为瓶颈并限制整个系统的性能。选择不当的分片键是常见原因。","tags":["distributed systems","distributed","hotspot","performance","sharding"]},{"term":"倾斜（数据不均衡）","full":"","definition":"数据或负载在各分区之间分布不均。一些分区溢满而另一些空闲，并行效率随之下降。可通过键的选择或加盐（salting）来均衡。","tags":["distributed systems","distributed","skew","imbalance","sharding"]},{"term":"部分索引（Partial Index）","full":"","definition":"只覆盖满足 WHERE 条件的表行的索引。它减小索引大小、降低维护成本，并加速频繁查询的数据子集。例如可用于只索引活跃行。","tags":["index","index type","postgresql","partial","performance"]},{"term":"表达式索引（Expression Index）","full":"","definition":"基于表达式或函数结果而非列构建的索引。例如在 LOWER(email) 上构建，可加速不区分大小写的搜索。查询必须使用相同的表达式。","tags":["index","index type","expression","function","performance"]},{"term":"GIN 索引","full":"通用倒排索引（Generalized Inverted Index）","definition":"一种对值内多个组成部分建立索引的倒排索引类型。适用于一个字段包含多个键的场景，例如数组、JSONB 和全文搜索。用于 PostgreSQL。","tags":["index","index type","postgresql","gin","jsonb"]},{"term":"GiST 索引","full":"通用搜索树（Generalized Search Tree）","definition":"一种基于树的可扩展索引框架；可为不同数据类型定义自定义搜索策略。非常适合地理空间（geometry）、范围和最近邻查询。它是 PostGIS 的基础。","tags":["index","index type","postgresql","gist","geospatial"]},{"term":"TOAST","full":"超大属性存储技术（The Oversized-Attribute Storage Technique）","definition":"PostgreSQL 将无法放入单页的大字段值压缩并存储到单独区域的技术。大文本和 JSONB 值会被透明地拆分。它通过保持主表紧凑来维持性能。","tags":["storage","storage engine","postgresql","toast","large-values"]},{"term":"仅索引扫描（Index-Only Scan）","full":"","definition":"一种仅凭索引即可生成结果、完全不读取表的扫描，因为所需数据全部在索引中。它可显著减少磁盘访问。在 PostgreSQL 中它与可见性映射（visibility map）配合工作。","tags":["index","index scan","scan","performance","postgresql"]},{"term":"HTAP","full":"混合事务/分析处理（Hybrid Transactional/Analytical Processing）","definition":"在单个系统中同时运行事务型（OLTP）和分析型（OLAP）负载的方法。它消除了为分析而将数据迁移到单独存储（ETL）所带来的延迟。通常结合行存储和列存储。","tags":["analytics","oltp","olap","htap","architecture"]},{"term":"分片键（Sharding Key）","full":"","definition":"决定某行落在哪个分片上的一列或多列。选择得当的键能均匀分布负载并防止热点。选择不当会导致数据不均衡和代价高昂的跨分片查询。","tags":["distributed systems","distributed","sharding","key","scaling"]},{"term":"重新均衡（Rebalancing）","full":"","definition":"在添加或移除节点、或负载失衡时，在各分区之间重新分布数据。目的是均衡负载并高效利用资源。运行期间会产生网络和 I/O 开销。","tags":["distributed systems","distributed","rebalancing","scaling","sharding"]},{"term":"隔离（Fencing）","full":"","definition":"一种机制，阻止过时或被隔离的节点写入共享资源，以防止数据损坏。通常使用递增的“fencing token”，使过时的请求被拒绝。它可限制脑裂的影响。","tags":["distributed systems","distributed","fencing","safety","split-brain"]},{"term":"水位线（Watermark）","full":"","definition":"在流处理系统中，表示某一时间之前的所有事件都视为已到达的进度标记。用于处理迟到事件，并决定何时关闭窗口计算。","tags":["stream processing","streaming","watermark","event time","event"]},{"term":"墓碑（Tombstone）","full":"","definition":"为表示某条记录已被删除而写入的特殊标记。在数据不会立即物理删除的 LSM 和分布式系统中，它确保删除操作传播到所有副本。实际清理在 compaction 时进行。","tags":["storage","storage engine","lsm","tombstone","delete"]},{"term":"压缩合并（Compaction，LSM）","full":"","definition":"在 LSM 树中合并并重写磁盘上的有序文件（SSTable）。它通过清除旧版本和 tombstone 来改善读取性能和磁盘使用。它在后台运行并产生写入负载。","tags":["storage","storage engine","lsm","compaction","maintenance"]},{"term":"读修复（Read Repair）","full":"","definition":"一种技术：在读取过程中检测到副本之间不一致时，将最新值写入落后的节点以进行修复。系统借助正常的读取流量使自身趋于一致。用于 Cassandra 等系统。","tags":["distributed systems","distributed","repair","consistency","replication"]},{"term":"提示移交（Hinted Handoff）","full":"","definition":"当目标节点暂时不可达时，将写入作为“hint”存储在另一个节点上，待该节点恢复后再投递。它确保在短暂故障期间写入不会丢失。它为最终一致性提供支持。","tags":["distributed systems","distributed","handoff","availability","replication"]},{"term":"最终一致性（Eventual Consistency）","full":"","definition":"一种弱一致性模型，保证在没有新的更新时，所有副本最终都会收敛到相同的值。它允许短暂的不一致，但提供高可用性和低延迟。它是许多 NoSQL 系统的默认模型。","tags":["distributed systems","distributed","consistency","eventual","nosql"]},{"term":"强一致性（Strong Consistency）","full":"","definition":"一种一致性模型：写入完成后，所有读者都能立即看到最新值。它使用起来很直观，但由于需要协调，其延迟和可用性代价较高。金融系统首选此模型。","tags":["distributed systems","distributed","consistency","strong","strong consistency"]},{"term":"线性一致性（Linearizability）","full":"","definition":"最强的一致性级别，保证每个操作看起来都在其开始与结束之间的某一瞬间生效，且所有操作都符合单一的实时顺序。系统表现得如同只有一个副本。实现代价高昂。","tags":["distributed systems","distributed","consistency","linearizability","strong consistency"]},{"term":"两阶段锁（2PL）","full":"两阶段锁（Two-Phase Locking）","definition":"一种两阶段加锁规则：事务先只获取锁（扩展阶段），然后只释放锁（收缩阶段）。这一规则保证可串行化。在其严格形式（Strict 2PL）中，锁一直持有到提交。","tags":["locking","lock","2pl","serializable","concurrency"]},{"term":"死锁检测（Deadlock Detection）","full":"","definition":"检测死锁（两个或多个事务相互等待对方持有的锁）的机制。通常通过在等待图（wait-for graph）中查找环来发现，并选择一个事务作为牺牲者回滚。随后系统恢复运转。","tags":["locking","lock","deadlock","deadlock detection","concurrency"]}]},"guide/glossary-3.json":{"terms":[{"term":"写放大（Write Amplification）","full":"写放大（Write Amplification）","definition":"单次逻辑写入在存储层引起多得多的物理写入（LSM compaction、页重写、WAL、SSD 块擦除）。数值过高会磨损磁盘并降低写入吞吐量。","tags":["storage","lsm","performance","ssd"]},{"term":"读放大（Read Amplification）","full":"读放大（Read Amplification）","definition":"单次逻辑读取实际需要多次物理/内部读取。在 LSM 树中跨多个层级查找一个键是典型例子；布隆过滤器和 compaction 可降低读放大。","tags":["storage","lsm","performance"]},{"term":"LSM-tree","full":"日志结构合并树（Log-Structured Merge Tree）","definition":"一种数据结构：先将写入吸收到内存结构（memtable）中，以有序块（SSTable）的形式刷新到磁盘，并在后台合并（compaction）。非常适合写密集型负载；RocksDB 和 Cassandra 都使用它。","tags":["storage","lsm","write-heavy"]},{"term":"B+tree","full":"B+ 树（B+ Tree）","definition":"关系型数据库索引的经典结构。所有数据都保存在叶节点中，叶节点通过链表串联；它为点查询和范围查询提供均衡的读取性能。","tags":["index","btree","storage"]},{"term":"填充因子（Fill Factor）","full":"填充因子（Fill Factor）","definition":"规定索引/表页初始填充程度的比率。预留空闲空间可减少后续更新/插入时的页分裂；在 PG 中为 fillfactor，在 MSSQL 中为 fill factor。","tags":["index","storage","tuning"]},{"term":"HOT 更新（HOT Update）","full":"仅堆元组更新（Heap-Only Tuple Update）","definition":"在 PostgreSQL 中，不修改任何索引列的更新会在同一页上创建新的行版本，且不更新索引。它可减少索引膨胀并降低更新代价。","tags":["postgres","mvcc","update","performance"]},{"term":"膨胀（Bloat）","full":"表/索引膨胀（Table/Index Bloat）","definition":"在 MVCC 引擎中，由于未清理的死行版本或空页，表/索引占用的空间超出所需。它会增加扫描代价；可通过 VACUUM/reorg 回收空间。","tags":["mvcc","postgres","maintenance","bloat"]},{"term":"Autovacuum","full":"自动 VACUUM（Automatic VACUUM）","definition":"PostgreSQL 的后台进程，自动执行死元组清理、统计信息收集和事务 ID 冻结。通过阈值和代价参数根据工作负载进行调优。","tags":["postgres","vacuum","maintenance"]},{"term":"冻结 / 回卷（Freeze / Wraparound）","full":"事务 ID 冻结 / 回卷（Transaction ID Freeze / Wraparound）","definition":"在 PostgreSQL 中，将旧行标记为 'frozen'，以防止 32 位事务 ID 回卷。若被忽视，数据库将停止接受写入以防止数据丢失。","tags":["postgres","mvcc","vacuum","wraparound"]},{"term":"检查点（Checkpoint）","full":"检查点（Checkpoint）","definition":"将内存中的脏页写入磁盘，并记录恢复起始点的事件。它缩短崩溃恢复时间；其频率需要在 I/O 峰值和恢复时间之间权衡。","tags":["recovery","wal","internals","checkpoint"]},{"term":"模糊检查点（Fuzzy Checkpoint）","full":"模糊检查点（Fuzzy Checkpoint）","definition":"一种在不停止系统的情况下，在后台持续、分散地写入脏页的检查点类型。它通过避免突发的 I/O 激增（checkpoint 尖峰）使响应时间可预测。","tags":["recovery","checkpoint","internals"]},{"term":"Redo","full":"重做日志 / 前滚（Redo Log / Roll Forward）","definition":"为在恢复期间重新应用（前滚）事务所做变更而保存的记录。它恢复崩溃前已提交但尚未写入磁盘的变更。","tags":["recovery","oracle","wal","redo"]},{"term":"Undo","full":"Undo 日志 / 回滚（Undo Log / Rollback）","definition":"为回滚事务以及为 MVCC 提供旧行版本而保存的记录。在 Oracle/InnoDB 中，旧版本从 undo 段中读取。","tags":["mvcc","oracle","innodb","undo","rollback"]},{"term":"MVCC 快照（MVCC Snapshot）","full":"MVCC 快照（MVCC Snapshot）","definition":"决定哪些行版本可见、使事务看到一致性读取结果的逻辑时间点视图。它在事务开始时或语句开始时获取（取决于隔离级别）。","tags":["mvcc","concurrency","isolation","snapshot"]},{"term":"隔离异常（Isolation Anomaly）","full":"隔离异常（Isolation Anomalies）","definition":"隔离不足时并发事务产生的不一致：脏读（读取未提交数据）、不可重复读（同一行发生变化）、幻读（出现新行）以及 write skew（看似独立的写入共同破坏某条规则）。","tags":["isolation","concurrency","anomaly","acid"]},{"term":"2PL","full":"两阶段锁（Two-Phase Locking）","definition":"一种通过锁保证可串行化的协议：事务先只获取锁（扩展阶段），到某一时刻之后只释放锁（收缩阶段）。Strict 2PL 将锁一直持有到提交。","tags":["concurrency","locking","serializable","2pl"]},{"term":"MVTO","full":"多版本时间戳排序（Multi-Version Timestamp Ordering）","definition":"一种并发控制方法：为事务分配时间戳，并据此对多版本数据上的读/写进行排序。它用时间戳而非锁来解决冲突。","tags":["concurrency","mvcc","timestamp","protocol"]},{"term":"互斥锁（Mutex）","full":"互斥（Mutual Exclusion）","definition":"一种互斥原语，保证同一时间只有一个线程访问某资源。在数据库中，它在保护共享内存结构方面起到类似 latch 的作用。","tags":["concurrency","internals","mutex"]},{"term":"自旋锁（Spinlock）","full":"自旋锁（Spinlock）","definition":"一种持有时间极短的锁，使线程在锁释放之前循环等待（忙等），而不是休眠。对于极短的临界区开销很低；在争用下会消耗 CPU。","tags":["concurrency","internals","spinlock","cpu"]},{"term":"Buffer Pool","full":"Buffer Pool / Buffer Cache","definition":"一种通过将频繁访问的数据页保存在内存中来减少磁盘 I/O 的缓存。淘汰算法（LRU、clock-sweep）决定淘汰哪个页；其大小根据工作集来设定。","tags":["memory","cache","internals","buffer"]},{"term":"工作集（Working Set）","full":"工作集（Working Set）","definition":"工作负载在给定时间窗口内主动访问的数据集合。当 buffer pool 足以容纳该集合时，物理 I/O 降到最低；它不同于数据总量。","tags":["memory","cache","sizing","performance"]},{"term":"倾斜（Skew）","full":"数据倾斜（Data Skew）","definition":"值分布不均、某些值出现频率远高于其他值的情况。它是基数估算错误、参数嗅探问题和分片热点的主要原因；可通过直方图处理。","tags":["statistics","distribution","skew","performance"]},{"term":"计划缓存（Plan Cache）","full":"计划缓存（Plan Cache）","definition":"存储已编译执行计划以便重用的内存区域。它避免硬解析开销，但带来参数嗅探和过时计划的风险；在 MSSQL 中称为 plan cache，在 Oracle 中称为 library cache。","tags":["optimizer","plan-cache","parsing"]},{"term":"硬解析/软解析（Hard/Soft Parse）","full":"硬解析 / 软解析（Hard Parse / Soft Parse）","definition":"硬解析：从头解析、优化 SQL 并生成计划（代价高）。软解析：在缓存中找到现有计划并重用（代价低）。绑定变量可提高软解析比例。","tags":["parsing","oracle","performance","plan-cache"]},{"term":"连接风暴（Connection Storming）","full":"连接风暴（Connection Storm）","definition":"短时间内同时打开大量新的数据库连接。连接建立开销和内存压力会压垮数据库；通常源于连接池耗尽或重连潮。","tags":["connections","pooling","overload"]},{"term":"惊群（Thundering Herd）","full":"惊群问题（Thundering Herd Problem）","definition":"某一事件（缓存过期、锁被释放、服务恢复）发生后，大量客户端同时访问同一资源。可通过背压、抖动（jitter）和 single-flight 模式缓解。","tags":["concurrency","cache","overload","resilience"]},{"term":"背压（Backpressure）","full":"背压（Backpressure）","definition":"系统在无法承受负载时减慢或拒绝生产者，而不是照单全收（队列上限、速率限制）。它使系统在过载时平稳降级而不是崩溃。","tags":["resilience","flow-control","overload"]},{"term":"租约（Lease）","full":"租约（Lease）","definition":"授予节点的、有固定期限的领导权/资源权，到期后自动失效。由于有时间限制，它可防止卡住的节点永远保持领导者身份。","tags":["distributed","leader-election","lease"]},{"term":"读己之写（Read-Your-Writes）","full":"读己之写一致性（Read-Your-Writes Consistency）","definition":"一种会话一致性，保证用户在紧接着的下一次读取中总能看到自己的变更。当路由到存在延迟的副本时，通过特殊的路由/令牌实现。","tags":["distributed","consistency","session","replication"]},{"term":"因果一致性（Causal Consistency）","full":"因果一致性（Causal Consistency）","definition":"一种强于最终一致性的一致性模型，保证存在因果关系的操作在所有节点上以相同顺序被看到。无关（并发）的操作可能以不同顺序被看到。","tags":["distributed","consistency","causal"]},{"term":"CAP","full":"一致性（Consistency）、可用性（Availability）、分区容错性（Partition tolerance）","definition":"该定理指出：在发生网络分区时，分布式系统只能在一致性和可用性之间二选一。没有分区时，这一权衡并非必需。","tags":["distributed","cap","consistency","theory"]},{"term":"PACELC","full":"PACELC 定理（PACELC Theorem）","definition":"一个扩展 CAP 的模型：发生分区（P）时在可用性（Availability）与一致性（Consistency）之间选择，否则（E）在延迟（Latency）与一致性（Consistency）之间选择。它还涵盖了正常运行期间延迟与一致性的权衡。","tags":["distributed","cap","pacelc","latency","theory"]},{"term":"热点分区（Hot Partition）","full":"热点分区 / 热点（Hot Partition / Hot Spot）","definition":"负载不成比例地堆积在单个分区/分片上。源于低基数、基于时间或单调递增的分片键；它使该节点成为瓶颈并使扩展失效。","tags":["distributed","sharding","hot-spot","skew"]},{"term":"列存储（Columnstore）","full":"面向列的存储（Column-Oriented Storage）","definition":"一种按列而非按行存储数据的格式。分析型查询只读取所需的列，具有高压缩率和向量化执行；非常适合 OLAP。","tags":["olap","columnar","analytics","compression"]},{"term":"向量化执行（Vectorized Execution）","full":"向量化执行（Vectorized Execution）","definition":"查询算子一次处理一个列值块（批），而不是逐行处理。它更好地利用 CPU 缓存和 SIMD，大幅提高分析型查询的吞吐量。","tags":["olap","execution","performance","columnar"]},{"term":"区域映射 / Min-Max（Zone Map / Min-Max）","full":"区域映射 / Min-Max 索引（Zone Map / Min-Max Index）","definition":"为每个数据块存储列最小值和最大值的轻量级元数据。通过跳过不可能匹配查询谓词的块而不读取它们（块跳过）来降低扫描代价。","tags":["olap","index","pruning","columnar"]},{"term":"逻辑复制","full":"逻辑复制","definition":"一种发布和订阅行级变更（逻辑事件）而非物理块的复制方式。它支持选择性复制表、跨版本和双向场景；PG 的 publication/subscription 提供了这一功能。","tags":["replication","postgres","logical","cdc"]},{"term":"GTID","full":"全局事务标识符（Global Transaction Identifier）","definition":"在 MySQL 复制中为每个事务分配的全局唯一标识符。通过按事务 ID 而非文件/偏移量跟踪副本位置，使故障转移和重新配置安全而简单。","tags":["mysql","replication","gtid","failover"]},{"term":"高水位线（High Water Mark）","full":"高水位线（High Water Mark）","definition":"段/表中曾经使用过的最高块边界。在 Oracle 中，全表扫描会读到该边界为止；由于大批量删除后它不会下降，可能导致扫描空块和膨胀。","tags":["oracle","storage","internals","scan"]}]},"playbooks/playbooks.json":{"schemaVersion":1,"playbooks":[{"id":"db-slow","title":"数据库变慢","icon":"🐢","symptom":"整个应用变慢；查询耗时远超平常，用户在抱怨。","steps":[{"title":"首先弄清到底是什么慢","detail":"是全部都慢，还是某个特定查询/页面慢？从什么时候开始，是否与部署/补丁/数据增长同时发生？是全系统范围还是单个数据库？这样可以将排查范围缩小到一个方向：CPU、I/O、锁或计划变更。"},{"title":"查看活动会话和等待事件","detail":"当前正在运行什么、在等待什么？占主导地位的等待事件（I/O、锁、日志）指向瓶颈所在。","engines":{"oracle":"SELECT sid, username, status, event, sql_id FROM v$session WHERE status='ACTIVE' AND type='USER';","postgres":"SELECT pid, state, wait_event_type, wait_event, query FROM pg_stat_activity WHERE state='active';","mysql":"SELECT id, user, command, time, state, info FROM information_schema.processlist WHERE command<>'Sleep' ORDER BY time DESC;","mssql":"SELECT session_id, status, wait_type, wait_time, blocking_session_id FROM sys.dm_exec_requests WHERE session_id IN (SELECT session_id FROM sys.dm_exec_sessions WHERE is_user_process = 1);","mongodb":"db.currentOp({ active: true, secs_running: { $gt: 1 } })","redis":"SLOWLOG GET 10"}},{"title":"检查阻塞","detail":"变慢最常见的原因是一个会话阻塞了其他会话。找到阻塞会话并确定阻塞链的源头。","engines":{"oracle":"SELECT blocking_session, sid, seconds_in_wait, event FROM v$session WHERE blocking_session IS NOT NULL;","postgres":"SELECT pid, pg_blocking_pids(pid) AS blocked_by, query FROM pg_stat_activity WHERE cardinality(pg_blocking_pids(pid))>0;","mysql":"SELECT * FROM sys.innodb_lock_waits\\G","mssql":"SELECT session_id, blocking_session_id, wait_type FROM sys.dm_exec_requests WHERE blocking_session_id<>0;"}},{"title":"找出代价最高的查询","detail":"找出资源消耗最多的前 10 个查询。通常 90% 的问题来自少数几个查询。","engines":{"oracle":"SELECT sql_id, elapsed_time/1e6 sec, executions, sql_text FROM v$sql ORDER BY elapsed_time DESC FETCH FIRST 10 ROWS ONLY;","postgres":"SELECT calls, total_exec_time, mean_exec_time, query FROM pg_stat_statements ORDER BY total_exec_time DESC LIMIT 10;","mysql":"SELECT digest_text, count_star, sum_timer_wait/1e12 sec FROM performance_schema.events_statements_summary_by_digest ORDER BY sum_timer_wait DESC LIMIT 10;","mssql":"SELECT TOP 10 total_elapsed_time/execution_count avg_us, execution_count, SUBSTRING(t.text,1,200) FROM sys.dm_exec_query_stats qs CROSS APPLY sys.dm_exec_sql_text(qs.sql_handle) t ORDER BY total_elapsed_time DESC;"}},{"title":"检查可疑查询的执行计划","detail":"获取慢查询的 EXPLAIN。查找全表扫描、缺失的索引、糟糕的连接顺序或过时的统计信息。若计划突然改变，考虑收集统计信息。","engines":{"oracle":"SELECT * FROM TABLE(DBMS_XPLAN.DISPLAY_CURSOR('&sql_id', NULL, 'ALLSTATS LAST'));","postgres":"EXPLAIN (ANALYZE, BUFFERS) <query>;","mysql":"EXPLAIN ANALYZE <query>;","mssql":"SET STATISTICS PROFILE ON; <query>; SET STATISTICS PROFILE OFF;","mongodb":"db.my_collection.find({...}).explain('executionStats')  // COLLSCAN or IXSCAN?"}},{"title":"核实系统资源","detail":"在操作系统层面检查 CPU、I/O 等待（iowait）、内存和磁盘使用情况。检查缓存命中率；若偏低，可能存在内存瓶颈。问题也可能在数据库之外（磁盘已满、嘈杂邻居 VM）。"},{"title":"决定：快速修复还是永久修复","detail":"立即缓解：终止失控查询、添加缺失的索引、收集统计信息。永久修复：重新设计查询/索引、修正内存设置、确保计划稳定（baseline/hint）。"}],"related":["active-sessions","blocking-sessions","top-slow-queries","explain-plan","cache-hit-ratio"]},{"id":"disk-full","title":"磁盘/表空间已满","icon":"💽","symptom":"出现 'unable to extend'、'disk full'、'log file full' 错误；写入正在停止。","steps":[{"title":"不要惊慌；先找出是什么满了","detail":"是数据文件、事务日志、归档/redo 区域、临时空间还是操作系统磁盘？清理错了地方无济于事。在操作系统层面用 df -h 检查磁盘，并在数据库内查看空间相关视图。"},{"title":"测量使用情况","detail":"哪个表空间/文件/目录满了，使用率是多少？","engines":{"oracle":"SELECT d.tablespace_name, ROUND((1-(d.alloc-NVL(f.free,0))/d.maxb)*100,1) pct_free FROM (SELECT tablespace_name, SUM(bytes) alloc, SUM(GREATEST(bytes,maxbytes)) maxb FROM dba_data_files GROUP BY tablespace_name) d LEFT JOIN (SELECT tablespace_name, SUM(bytes) free FROM dba_free_space GROUP BY tablespace_name) f ON f.tablespace_name = d.tablespace_name;","postgres":"SELECT datname, pg_size_pretty(pg_database_size(datname)) FROM pg_database ORDER BY 2 DESC; -- use OS df for the disk","mysql":"SELECT table_schema, ROUND(SUM(data_length+index_length)/1048576,1) mb FROM information_schema.tables GROUP BY table_schema ORDER BY mb DESC;","mssql":"SELECT name, size/128.0 size_mb, size/128.0 - CAST(FILEPROPERTY(name,'SpaceUsed') AS int)/128.0 free_mb FROM sys.database_files;","redis":"INFO memory  # used_memory vs maxmemory"}},{"title":"找出最大的空间占用者","detail":"确定最大的表/段、最大的日志/归档文件以及不再需要的旧备份。大多数情况下，罪魁祸首是某张失控的表或累积的归档日志。","engines":{"oracle":"SELECT segment_name, bytes/1048576 mb FROM dba_segments ORDER BY bytes DESC FETCH FIRST 10 ROWS ONLY;","postgres":"SELECT relname, pg_size_pretty(pg_total_relation_size(relid)) FROM pg_statio_user_tables ORDER BY pg_total_relation_size(relid) DESC LIMIT 10;","mssql":"SELECT TOP 10 OBJECT_NAME(object_id), SUM(reserved_page_count)*8/1024 mb FROM sys.dm_db_partition_stats GROUP BY object_id ORDER BY mb DESC;"}},{"title":"安全地回收空间（快速）","detail":"对于日志/归档：先备份，再清除旧的归档日志/备份（Oracle：RMAN delete archivelog；MSSQL：执行日志备份并收缩日志）。在 Redis 中：maxmemory-policy 与淘汰。切勿手动删除正在使用的数据文件；切勿在没有备份的情况下删除归档日志。"},{"title":"永久扩容","detail":"为数据文件增加空间或启用自动扩展（Oracle：ALTER DATABASE DATAFILE ... AUTOEXTEND ON MAXSIZE）、扩大磁盘、添加表空间。修复根本原因：日志轮转、归档清理作业、保留策略。"},{"title":"防止再次发生","detail":"设置磁盘使用率告警（80%/90% 阈值），自动化归档/日志清理，并明确定义保留策略。避免再次需要打开这份应急手册。"}],"related":["free-space","largest-tables","database-size","reclaim-space","last-backup-status"]},{"id":"blocking","title":"阻塞与锁危机","icon":"🔒","symptom":"查询挂起，出现 'lock wait timeout'，大量会话在等待，但 CPU 很低。","steps":[{"title":"找到阻塞链的源头（root blocker）","detail":"弄清谁在阻塞谁；真正的元凶是自身不等待任何东西、却让别人等待的会话。集中关注位于阻塞链顶端的会话。","engines":{"oracle":"SELECT blocking_session, sid, serial#, username, seconds_in_wait, sql_id FROM v$session WHERE blocking_session IS NOT NULL ORDER BY seconds_in_wait DESC;","postgres":"SELECT pid, usename, pg_blocking_pids(pid) blocked_by, state, query FROM pg_stat_activity WHERE cardinality(pg_blocking_pids(pid))>0;","mysql":"SELECT waiting_pid, waiting_query, blocking_pid, blocking_query FROM sys.innodb_lock_waits;","mssql":"SELECT session_id, blocking_session_id, wait_type, wait_resource FROM sys.dm_exec_requests WHERE blocking_session_id<>0;","mongodb":"db.currentOp({ waitingForLock: true })"}},{"title":"弄清 root blocker 在做什么","detail":"是一个已打开但未提交的事务吗？应用是否让它处于 'idle in transaction' 状态？是一个长时间运行的批处理吗？在盲目终止之前先弄清它是什么——它可能马上就要完成了。","engines":{"oracle":"SELECT s.sid, s.prev_sql_id, t.start_time, t.used_ublk FROM v$transaction t JOIN v$session s ON t.ses_addr=s.saddr;","postgres":"SELECT pid, state, xact_start, now()-xact_start age, query FROM pg_stat_activity WHERE pid=<root_pid>;"}},{"title":"决定：等待、由应用解决，还是终止","detail":"若即将完成，就等待。若应用可以提交/回滚，就触发它。否则，若业务已停滞，就终止 root blocker。终止之前要考虑影响（回滚时间、数据）。"},{"title":"必要时终止 root blocker","detail":"只终止位于阻塞链顶端的会话；其下的会话会自动释放。","engines":{"oracle":"ALTER SYSTEM KILL SESSION 'sid,serial#' IMMEDIATE;","postgres":"SELECT pg_terminate_backend(<root_pid>);","mysql":"KILL <root_id>;","mssql":"KILL <root_session_id>;","mongodb":"db.killOp(<opid>)"}},{"title":"修复根本原因","detail":"缺失的索引可能导致大范围加锁；应用可能使事务保持打开的时间过长；隔离级别可能不正确。添加索引、缩短事务、设置 'idle in transaction' 超时，并选择合适的隔离级别。"}],"related":["blocking-sessions","kill-session","open-transactions"]},{"id":"connection-storm","title":"连接风暴（连接过多）","icon":"🌊","symptom":"出现 'too many connections'、'ORA-00018'、'remaining connection slots reserved'——新连接被拒绝。","steps":[{"title":"测量当前状态","detail":"当前有多少连接，上限是多少，活动与空闲连接各有多少？","engines":{"oracle":"SELECT COUNT(*) total, SUM(CASE WHEN status='ACTIVE' THEN 1 ELSE 0 END) active FROM v$session WHERE type='USER';","postgres":"SELECT count(*), state FROM pg_stat_activity WHERE backend_type = 'client backend' GROUP BY state;","mysql":"SHOW STATUS LIKE 'Threads_connected'; SHOW VARIABLES LIKE 'max_connections';","mssql":"SELECT COUNT(*), status FROM sys.dm_exec_sessions WHERE is_user_process=1 GROUP BY status;","redis":"INFO clients"}},{"title":"确定来源：谁/从哪里","detail":"是哪个应用/主机/用户在打开这些连接？通常是某台应用服务器的连接池暴涨，或存在连接泄漏（大量 idle in transaction 会话）。","engines":{"postgres":"SELECT client_addr, usename, state, count(*) FROM pg_stat_activity WHERE backend_type = 'client backend' GROUP BY 1,2,3 ORDER BY 4 DESC;","mysql":"SELECT host, user, count(*) FROM information_schema.processlist GROUP BY host, user ORDER BY 3 DESC;","mssql":"SELECT host_name, program_name, login_name, COUNT(*) FROM sys.dm_exec_sessions WHERE is_user_process=1 GROUP BY host_name, program_name, login_name ORDER BY 4 DESC;"}},{"title":"立即争取喘息空间","detail":"清理长时间空闲/idle in transaction 的连接。必要时批量断开问题应用的连接。使用为管理员登录预留的连接槽（PG superuser_reserved_connections）。"},{"title":"正确的解决办法：连接池","detail":"无限制地调高 max_connections 会消耗内存，只是推迟问题。正确的做法是在应用端使用连接池（HikariCP、PgBouncer、ProxySQL）并设置合理的池大小。池大小通常约为（核数*2 + 磁盘数）——参见 Tools > Connection Pool 计算器。"},{"title":"防止泄漏和再次发生","detail":"确保应用关闭连接（finally/using）。设置 idle_in_transaction_session_timeout 和 wait_timeout。设置连接数告警。"}],"related":["connection-count","kill-session","open-transactions"]},{"id":"replication-lag","title":"复制延迟/停止","icon":"🔁","symptom":"备库/副本正在落后或已完全停止；只读副本显示的是过时数据。","steps":[{"title":"延迟有多大，是在增长还是保持稳定","detail":"延迟是多少秒/字节？是稳定的偏差还是持续增长？若在增长，说明副本的应用跟不上；若保持稳定，则是出现过短暂停顿。","engines":{"oracle":"SELECT name, value, unit FROM v$dataguard_stats WHERE name LIKE '%lag%';","postgres":"SELECT client_addr, state, write_lag, flush_lag, replay_lag FROM pg_stat_replication;","mysql":"SHOW REPLICA STATUS\\G  -- Seconds_Behind_Source","mssql":"SELECT ar.replica_server_name, drs.redo_queue_size, drs.log_send_queue_size FROM sys.dm_hadr_database_replica_states drs JOIN sys.availability_replicas ar ON drs.replica_id=ar.replica_id;","redis":"INFO replication  # master_repl_offset vs slave offset"}},{"title":"复制是否在运行，是否有错误","detail":"IO/SQL 线程状态如何、应用进程如何、连接是否断开？若有错误（例如冲突行、缺失的表），应用已停止；请阅读错误信息。","engines":{"mysql":"SHOW REPLICA STATUS\\G  -- Replica_IO_Running / Replica_SQL_Running / Last_Error","postgres":"SELECT status, last_msg_receipt_time, latest_end_lsn FROM pg_stat_wal_receiver;  -- on the standby","oracle":"SELECT process, status, sequence#, thread# FROM v$managed_standby WHERE process LIKE 'MRP%' OR process LIKE 'RFS%';"}},{"title":"找出瓶颈","detail":"为什么跟不上？是单线程应用加上繁重的写入负载（PG/MySQL 大多串行应用）、备库上的 I/O 瓶颈、网络，还是长查询阻塞了应用（PG hot_standby_feedback / recovery conflict）？"},{"title":"消除延迟","detail":"加快应用：启用并行应用（MySQL replica_parallel_workers——8.0.26 之前为 slave_parallel_workers；PG 没有该功能，但有相关的 recovery 设置）、改善备库 I/O、解决阻塞应用的查询。若因错误而停止：修复错误（手动解决冲突行）并恢复复制。"},{"title":"若已损坏则重建","detail":"若副本严重落后/已损坏，重新同步是最干净的做法：用新的备份重建备库（PG pg_basebackup、MySQL clone/xtrabackup、Oracle RMAN duplicate）。关键：重建之前先找出原因，否则问题会再次发生。"}],"related":["replication-status","list-replicas","promote-failover"]},{"id":"high-cpu","title":"CPU 过高","icon":"🔥","symptom":"服务器 CPU 达到 90% 以上；整个系统变慢，load average 很高。","steps":[{"title":"确认 CPU 确实是被数据库消耗的","detail":"在操作系统层面用 top/mpstat 确认消耗 CPU 的进程确实是数据库。有时消耗者是相邻进程或备份/杀毒软件。若 iowait 很高，问题在磁盘而非 CPU。"},{"title":"找出 CPU 消耗最多的查询","detail":"按 CPU 时间排序找出排名靠前的查询。通常是少数几个查询（糟糕的计划、全扫描、缺失的索引）在消耗 CPU。","engines":{"oracle":"SELECT sql_id, cpu_time/1e6 cpu_sec, executions, sql_text FROM v$sql ORDER BY cpu_time DESC FETCH FIRST 10 ROWS ONLY;","postgres":"SELECT query, calls, total_exec_time, mean_exec_time FROM pg_stat_statements ORDER BY total_exec_time DESC LIMIT 10;","mysql":"SELECT digest_text, count_star, sum_timer_wait/1e12 sec FROM performance_schema.events_statements_summary_by_digest ORDER BY sum_timer_wait DESC LIMIT 10;","mssql":"SELECT TOP 10 total_worker_time/execution_count avg_cpu, execution_count, SUBSTRING(t.text,1,200) FROM sys.dm_exec_query_stats q CROSS APPLY sys.dm_exec_sql_text(q.sql_handle) t ORDER BY total_worker_time DESC;"}},{"title":"检查计划/索引问题","detail":"获取问题查询的执行计划。全表扫描、nested loop 爆炸、hash join 溢出，或参数嗅探/过时统计信息导致的糟糕计划都会消耗 CPU。添加索引或收集统计信息。"},{"title":"立即缓解","detail":"终止失控查询，必要时用 Resource Manager/Resource Governor 对其加以限制。对于突然出现的负载，检查最近的部署/参数变更。"},{"title":"永久修复","detail":"修复查询/索引，确保计划稳定，并安排统计信息收集作业。若 CPU 持续处于 100%，可能需要扩容（增加 CPU、将读取分散到只读副本）。"}],"related":["top-slow-queries","explain-plan","active-sessions","update-statistics"]},{"id":"memory-pressure","title":"内存压力 / OOM","icon":"🧠","symptom":"OOM killer 终止进程，出现 'out of memory'、缓存命中率低、过度 swap，Redis 报 'OOM command not allowed'。","steps":[{"title":"谁在消耗内存","detail":"是数据库分配的内存（buffer pool/SGA）、每个会话的内存（PGA/sort/work_mem），还是整个操作系统？在操作系统层面检查 free/vmstat，并在数据库内查看内存相关视图。","engines":{"oracle":"SELECT component, current_size/1048576 mb FROM v$memory_dynamic_components WHERE current_size>0;","postgres":"SHOW shared_buffers; SHOW work_mem; -- work_mem * connections * number of sorts can blow up in total","mysql":"SHOW VARIABLES LIKE 'innodb_buffer_pool_size'; SHOW STATUS LIKE 'Innodb_buffer_pool%';","redis":"INFO memory  # used_memory, maxmemory, mem_fragmentation_ratio"}},{"title":"区分配置问题与负载问题","detail":"内存设置的总和是否超过物理 RAM（过度分配）？计算 shared_buffers + (work_mem * 并发数)。还是只是瞬时负载（巨大的排序/哈希、大量连接）？"},{"title":"Redis 特有：淘汰与碎片","detail":"若 used_memory 已达到 maxmemory，检查 maxmemory-policy（使用 noeviction 时写入会被拒绝）。若 mem_fragmentation_ratio 很高，使用 MEMORY PURGE / activedefrag。用 --bigkeys 找出大键。","engines":{"redis":"CONFIG GET maxmemory-policy\nMEMORY DOCTOR\nredis-cli --bigkeys"}},{"title":"立即缓解","detail":"终止吞噬内存的失控查询/会话，临时调低 work_mem 等会话内存，断开不必要的连接。若系统正在 swap，首要任务是摆脱 swap。"},{"title":"正确规划容量","detail":"使用 Tools > Memory/Buffer Pool 计算器合理分配 RAM。一般规则：在专用服务器上，buffer pool/SGA 约为 RAM 的 70-80%；会话内存总量乘以并发数，不得超过 RAM。必要时增加 RAM。"}],"related":["cache-hit-ratio","show-config-param","reclaim-space"]},{"id":"log-growth","title":"事务日志 / undo 膨胀","icon":"📈","symptom":"事务日志文件或 undo/redo 区域快速增长；出现 'log full'、'ORA-01555 snapshot too old'、'unable to extend undo'。","steps":[{"title":"找出使日志增长的未结事务","detail":"在最老的活动事务结束之前，日志/undo 无法被清理。一个长时间打开的事务就可能占住整个日志。","engines":{"oracle":"SELECT s.sid, s.username, t.start_time, t.used_ublk FROM v$transaction t JOIN v$session s ON t.ses_addr=s.saddr ORDER BY t.start_time;","postgres":"SELECT pid, state, xact_start, now()-xact_start age, query FROM pg_stat_activity WHERE xact_start IS NOT NULL ORDER BY xact_start;","mysql":"SELECT trx_id, trx_started, trx_mysql_thread_id FROM information_schema.innodb_trx ORDER BY trx_started;","mssql":"DBCC OPENTRAN;"}},{"title":"理解各引擎特有的根本原因","detail":"MSSQL：在 FULL 恢复模式下，若不做日志备份，日志会无限增长。Oracle：ORA-01555 = undo 保留时间/大小不足 + 长查询。PG：若复制槽不推进，WAL 会累积；长事务会阻塞 VACUUM。"},{"title":"针对 MSSQL 日志的正确做法","detail":"执行日志备份（BACKUP LOG）——这会使日志可以重用。若恢复模式确实应为 SIMPLE，则予以更改。之后，如有需要（且仅在需要时）再收缩日志。","engines":{"mssql":"BACKUP LOG [DB] TO DISK='C:\\backup\\DB_log.trn';\n-- Then, if needed: DBCC SHRINKFILE (N'DB_log', 512);"}},{"title":"PG WAL / 复制槽检查","detail":"不推进的（inactive）复制槽会导致 WAL 累积。删除之前，先核实该槽的所有者（slot_type、database、plugin）：它可能是暂时断开的备库，也可能是逻辑订阅者/CDC（例如 Debezium）。若删除该槽，所连接的备库必须重建，订阅者/CDC 端也必须重新同步。只有确实不再使用时才删除；如需长期防护，请用 max_slot_wal_keep_size（PG13+）设置上限。","engines":{"postgres":"SELECT slot_name, active, pg_size_pretty(pg_wal_lsn_diff(pg_current_wal_lsn(), restart_lsn)) retained FROM pg_replication_slots ORDER BY 3 DESC;\n-- If unneeded: SELECT pg_drop_replication_slot('slot_name');"}},{"title":"调整 undo/保留设置并防止再次发生","detail":"Oracle：扩大 UNDO 表空间，根据长查询设置 undo_retention（或 RETENTION GUARANTEE）。在应用中避免长时间打开的事务。监控日志备份作业是否运行。"}],"related":["open-transactions","reclaim-space","last-backup-status"]},{"id":"deadlocks","title":"死锁分析","icon":"♻️","symptom":"出现 'deadlock detected'、'ORA-00060'、'chosen as deadlock victim'——事务相互锁住，其中一个被中止。","steps":[{"title":"死锁是正常现象，但不应频繁发生","detail":"数据库会检测到死锁并通过选择牺牲者来解决——这是设计使然。目标是降低发生频率。应用应重试被选为牺牲者的事务。首先获取死锁图。"},{"title":"获取死锁图/详情","detail":"哪两个（或更多）会话、哪些资源、哪些锁模式发生了冲突？","engines":{"oracle":"-- Check the trace file referenced in the alert log (deadlock graph). Also:\nSELECT * FROM dba_hist_active_sess_history WHERE event LIKE 'enq: TX%' ORDER BY sample_time DESC;","postgres":"-- log_lock_waits=on; deadlock details are in postgresql.log. Check the deadlock_timeout setting.","mysql":"SHOW ENGINE INNODB STATUS\\G  -- LATEST DETECTED DEADLOCK section","mssql":"-- Deadlock XML in the system_health Extended Events session (ring_buffer, recent events):\nSELECT x.ev.value('@timestamp','datetime2') AS event_time, x.ev.query('.') AS deadlock_xml\nFROM (SELECT CAST(t.target_data AS XML) td FROM sys.dm_xe_session_targets t\n      JOIN sys.dm_xe_sessions s ON s.address = t.event_session_address\n      WHERE s.name = N'system_health' AND t.target_name = N'ring_buffer') r\nCROSS APPLY r.td.nodes('//RingBufferTarget/event[@name=\"xml_deadlock_report\"]') AS x(ev);\n-- For longer history, the event_file target:\n-- SELECT CAST(event_data AS XML) FROM sys.fn_xe_file_target_read_file(N'system_health*.xel', NULL, NULL, NULL) WHERE object_name = N'xml_deadlock_report';"}},{"title":"消除冲突模式","detail":"最常见的原因：两个事务以相反的顺序访问资源。在所有代码路径中以相同的顺序访问资源。第二常见的原因：缺失索引导致大范围的范围锁——添加所需的索引。"},{"title":"缩短锁的持续时间并缩小范围","detail":"保持事务简短（不要在事务内包含用户交互）。选择合适的隔离级别。必要时尽早获取行锁（使用确定性排序的 SELECT ... FOR UPDATE）。"},{"title":"应用中的重试必不可少","detail":"死锁无法完全消除；为牺牲者事务实现自动重试（指数退避）。这样用户就不会看到错误。"}],"related":["blocking-sessions","open-transactions","list-indexes"]},{"id":"db-down","title":"数据库无法打开 / 无法访问","icon":"🚨","symptom":"无法连接到服务，出现 'connection refused'，实例无法启动，listener 无响应。","steps":[{"title":"问题出在哪里：网络、服务还是实例","detail":"服务器是否可达（ping/ssh）？数据库进程/服务是否在运行（ps/systemctl/services.msc）？端口是否在监听（ss -ltnp / telnet host port）？这样可将问题定位到某一层：网络 / 服务 / 数据库实例。"},{"title":"检查服务和 listener","detail":"若数据库进程未启动，尝试启动它；若已启动但连接失败，检查 listener/端口和认证层。","engines":{"oracle":"-- OS: lsnrctl status  (listener)\n-- SQL*Plus: STARTUP;  (if the instance is down)\nSELECT status FROM v$instance;","postgres":"-- OS: systemctl status postgresql ; pg_isready -h host\n-- Logs: the last lines tell why it did not start","mysql":"-- OS: systemctl status mysqld ; mysqladmin ping","mssql":"-- Services / SQL Server Configuration Manager; SQL Server Error Log","redis":"redis-cli ping  # expect PONG"}},{"title":"阅读日志——答案就在那里","detail":"若数据库无法打开，原因会写在日志中：alert log（Oracle）、postgresql.log / 启动日志、mysqld error log、SQL Server ERRORLOG、redis log。磁盘已满、文件损坏、权限、端口冲突、文件缺失——都会在这里体现。"},{"title":"处理常见原因：磁盘、权限、端口、损坏","detail":"磁盘是否 100% 已满（最常见的原因）？数据目录权限是否被破坏？端口是否被其他进程占用？是否需要恢复（崩溃之后）？控制文件/redo log 是否损坏？根据日志中的信息逐一排查。"},{"title":"恢复并保留事件记录","detail":"崩溃恢复通常会自动完成；请耐心等待。若存在损坏，从备份/PITR 恢复（参见“数据损坏”应急手册）。事后撰写根本原因分析（RCA）：发生了什么、为什么发生、如何防止再次发生。"}],"related":["version-uptime","connect-cli","last-backup-status"]},{"id":"corruption","title":"数据损坏","icon":"🧯","symptom":"读取时出现 'ORA-01578'、'page corruption'、'checksum mismatch'、'MISCONF' 等 I/O 一致性错误。","steps":[{"title":"保持冷静，阻止扩散","detail":"慌乱的干预只会让情况更糟。首先确定影响范围，并尽可能停止对受影响对象的写入。若与硬件有关（磁盘/控制器），还要检查操作系统/硬件日志——修好数据库并不能修好磁盘。"},{"title":"确定范围：损坏了多少","detail":"是单个块/页/对象，还是大范围损坏？运行完整性检查。","engines":{"oracle":"RMAN> VALIDATE DATABASE;  -- or: SELECT * FROM v$database_block_corruption;","postgres":"-- With data_checksums enabled it shows up in the logs; check indexes/heap with pg_amcheck:\n-- pg_amcheck --heapallindexed -d mydb","mysql":"CHECK TABLE customer;  -- for InnoDB, innodb_force_recovery is the last resort","mssql":"DBCC CHECKDB ('MyDB') WITH NO_INFOMSGS, ALL_ERRORMSGS;","redis":"redis-check-rdb dump.rdb\n# AOF (Redis 7+ multi-part: under appendonlydir/, pass the manifest file):\nredis-check-aof appendonlydir/appendonly.aof.manifest\n# --fix TRUNCATES everything after the corrupted point (writes in that part are lost); back up the directory first:\n# redis-check-aof --fix appendonlydir/appendonly.aof.manifest"}},{"title":"优先事项：从备份恢复","detail":"出现损坏时，最安全的做法是从可靠的备份还原。若支持块/页级恢复，只恢复损坏的部分（Oracle RMAN BLOCKRECOVER、MSSQL 页还原）。修复工具（DBCC REPAIR_ALLOW_DATA_LOSS、innodb_force_recovery）会导致数据丢失——它们是最后手段。"},{"title":"验证完整性","detail":"恢复后，重新运行检查（DBCC CHECKDB / RMAN VALIDATE / CHECK TABLE），确认结果干净。同时在应用端验证数据一致性（行数、关键汇总值）。"},{"title":"根本原因：通常是硬件","detail":"损坏通常源于磁盘/内存/控制器故障。测试/更换硬件，启用校验和（data_checksums、PAGE_VERIFY CHECKSUM），并强化备份 + 验证流程。定期安排 DBCC CHECKDB / RMAN VALIDATE。"}],"related":["last-backup-status","restore-dump","db-down"]},{"id":"long-running-query","title":"长时间运行的查询 / 作业","icon":"⏱️","symptom":"某个查询/作业已运行数小时仍未结束；它消耗资源或影响其他操作。","steps":[{"title":"捕获查询及其运行时长","detail":"是哪个查询、已运行多久、哪个用户/应用？","engines":{"oracle":"SELECT sid, serial#, sql_id, last_call_et/60 running_min, username FROM v$session WHERE status='ACTIVE' AND type='USER' ORDER BY last_call_et DESC;","postgres":"SELECT pid, now()-query_start dur, usename, query FROM pg_stat_activity WHERE state='active' ORDER BY query_start;","mysql":"SELECT id, user, time, state, info FROM information_schema.processlist WHERE command='Query' ORDER BY time DESC;","mssql":"SELECT session_id, total_elapsed_time/1000 sec, status, command FROM sys.dm_exec_requests WHERE session_id IN (SELECT session_id FROM sys.dm_exec_sessions WHERE is_user_process = 1) ORDER BY total_elapsed_time DESC;","mongodb":"db.currentOp({ active: true, secs_running: { $gt: 60 } })"}},{"title":"是在推进还是卡住了","detail":"它真的在运行，还是在等待锁？查看进度指标（Oracle v$session_longops、PG pg_stat_progress_* 视图）。若在等待，请转到“阻塞与锁危机”应急手册。","engines":{"oracle":"SELECT sid, opname, sofar, totalwork, ROUND(sofar/NULLIF(totalwork,0)*100,1) pct, time_remaining FROM v$session_longops WHERE sofar<totalwork;","postgres":"SELECT * FROM pg_stat_progress_create_index;  -- or pg_stat_progress_vacuum / _cluster / _analyze"}},{"title":"决定：等待还是取消","detail":"若正在推进且很快就会完成，就等待（取消带来的回滚代价可能使其耗时更长）。若已卡住、运行有误或影响生产，就终止它。取消前要考虑回滚时间。"},{"title":"必要时终止","detail":"优先只取消查询（保留会话）；若无效，再终止会话。","engines":{"oracle":"ALTER SYSTEM CANCEL SQL 'sid,serial#';  -- 18c+; otherwise KILL SESSION","postgres":"SELECT pg_cancel_backend(pid);  -- otherwise pg_terminate_backend(pid)","mysql":"KILL QUERY <id>;","mssql":"KILL <session_id>;","mongodb":"db.killOp(<opid>)"}},{"title":"永久修复","detail":"优化查询（索引、计划、分页/分批），将繁重作业移到非高峰时段，必要时分块运行。对于周期性报表，考虑使用物化视图 / 只读副本。"}],"related":["active-sessions","kill-session","explain-plan","top-slow-queries"]},{"id":"xid-wraparound","title":"Postgres XID 回卷 / autovacuum 危机","icon":"🧬","symptom":"出现 'database is not accepting commands to avoid wraparound data loss'；autovacuum 跟不上，datfrozenxid 不断老化。","steps":[{"title":"理解紧迫性","detail":"PostgreSQL 事务 ID 是 32 位的；接近耗尽时，服务器会停止写入以防止数据丢失。这是一种关键且时间紧迫的情况。好消息是：它可以解决；需要的是有条不紊，而不是惊慌。"},{"title":"测量离上限有多近","detail":"取出每个数据库中最老的未冻结 XID 的年龄；它与 autovacuum_freeze_max_age（默认 200M）的接近程度反映了危险程度。","engines":{"postgres":"SELECT datname, age(datfrozenxid) AS xid_age\nFROM pg_database ORDER BY xid_age DESC;"}},{"title":"找出最老的表","detail":"问题通常来自少数几张大表/频繁写入的表；集中关注 relfrozenxid 最老的值。","engines":{"postgres":"SELECT relname, age(relfrozenxid) AS xid_age\nFROM pg_class WHERE relkind IN ('r','m','t')\nORDER BY xid_age DESC LIMIT 20;"}},{"title":"清除阻碍 VACUUM 的因素","detail":"长时间打开的事务、废弃的复制槽和预备事务会阻碍清理（冻结）。找出并解决它们——否则无论 VACUUM 运行多久都不会有进展。","engines":{"postgres":"SELECT pid, now()-xact_start AS age, state FROM pg_stat_activity WHERE xact_start IS NOT NULL ORDER BY age DESC;\nSELECT slot_name, active FROM pg_replication_slots WHERE NOT active;\nSELECT * FROM pg_prepared_xacts;"}},{"title":"执行激进的 FREEZE","detail":"阻碍因素清除后，从最老的表开始强制冻结。即使服务器已停止分配新的事务 ID，也不要切换到单用户模式（postgres --single）：当前的 PostgreSQL 文档指出已不再需要且应避免使用。以超级用户身份在正常模式下连接并运行 VACUUM 就足够了；在 PG14+ 中，接近回卷时 VACUUM 的 failsafe 机制会启动。","engines":{"postgres":"VACUUM (FREEZE, VERBOSE) oldest_table;\n-- Entire database: vacuumdb --all --freeze --jobs=4"}},{"title":"永久措施：强化并监控 autovacuum","detail":"提高 autovacuum_vacuum_cost_limit，降低 autovacuum_naptime，并为写入频繁的表设置表级存储参数。为 age(datfrozenxid) 设置告警；再也不要经历这种危机。"}],"related":["open-transactions","reclaim-space"]},{"id":"tempdb-contention","title":"tempdb 争用（SQL Server）","icon":"🌀","symptom":"PAGELATCH_UP/EX 等待，tempdb 分配页（PFS/GAM/SGAM）上的争用，整体变慢。","steps":[{"title":"确认争用","detail":"tempdb 系统页（例如 2:1:1）上的 PAGELATCH 等待表明存在分配争用。","engines":{"mssql":"SELECT session_id, wait_type, wait_duration_ms, resource_description\nFROM sys.dm_os_waiting_tasks\nWHERE wait_type LIKE 'PAGELATCH%' AND resource_description LIKE '2:%'\nORDER BY wait_duration_ms DESC;"}},{"title":"分解 tempdb 使用情况","detail":"谁在使用多少 tempdb？是内部对象（排序/哈希溢出）、版本存储，还是用户对象？","engines":{"mssql":"SELECT * FROM sys.dm_db_file_space_usage;\nSELECT TOP 10 session_id, user_objects_alloc_page_count, internal_objects_alloc_page_count\nFROM sys.dm_db_task_space_usage ORDER BY internal_objects_alloc_page_count DESC;"}},{"title":"修正文件配置","detail":"为 tempdb 使用多个大小相同的数据文件，数量等于 CPU 核数（最多 8 个）。SQL 2016+ 在安装时即推荐这样做；旧版本使用跟踪标志 1117/1118。将文件放在快速磁盘上（本地 NVMe）。"},{"title":"找出失控查询","detail":"带有大量排序/哈希溢出的查询会使 tempdb 暴涨。针对内存授予不足或计划糟糕的查询；通过索引/计划减少溢出。","engines":{"mssql":"SELECT TOP 10 t.text, mg.requested_memory_kb, mg.granted_memory_kb\nFROM sys.dm_exec_query_memory_grants mg CROSS APPLY sys.dm_exec_sql_text(mg.sql_handle) t\nORDER BY mg.requested_memory_kb DESC;"}},{"title":"其他数据库中的类似问题","detail":"同样的“临时空间争用”在 Oracle 中对应 TEMP 表空间（v$tempseg_usage / v$sort_usage），在 PostgreSQL 中对应 temp_files 和 work_mem 溢出。根本解决办法始终相同：修复发生溢出的查询并加快临时空间。"}],"related":["free-space","top-slow-queries","reclaim-space"]},{"id":"cert-expiry","title":"TLS / 证书过期","icon":"🔐","symptom":"出现 'certificate has expired'、'SSL handshake failed'，客户端突然无法连接（代码没有任何变更）。","steps":[{"title":"问题出在哪里：服务器、客户端还是 CA？","detail":"是服务器证书过期、客户端证书过期，还是证书链中的某个 CA 过期？典型症状是“昨天还能用，今天不行了，代码没改过”。"},{"title":"检查到期时间","detail":"直接从服务器读取证书的到期日期（一种快速且与数据库无关的方法）。","engines":{"postgres":"openssl s_client -connect host:5432 -starttls postgres </dev/null 2>/dev/null | openssl x509 -noout -dates","mongodb":"openssl s_client -connect host:27017 </dev/null 2>/dev/null | openssl x509 -noout -enddate","redis":"openssl s_client -connect host:6379 </dev/null 2>/dev/null | openssl x509 -noout -enddate"}},{"title":"安装并加载新证书","detail":"替换证书，大多数情况下无需重启即可重新加载服务。","engines":{"postgres":"-- Renew ssl_cert_file/ssl_key_file, then:\nSELECT pg_reload_conf();","mysql":"ALTER INSTANCE RELOAD TLS;","mssql":"-- SQL Server Configuration Manager > Certificate; a service restart is required","mongodb":"// Renew the net.tls.certificateKeyFile file (at the same path), then load it without a restart (5.0+):\ndb.adminCommand({rotateCertificates: 1})","redis":"CONFIG SET tls-cert-file /path/new.crt\nCONFIG SET tls-key-file /path/new.key","elasticsearch":"# ES watches the certificate/key files (PEM, PKCS#12) defined under xpack.security.*.ssl and\n# reloads them automatically when the file at the SAME path changes (no restart needed).\n# If the path, file name or keystore password changes, a rolling restart is required.\n# Note: POST _nodes/reload_secure_settings is only for reloadable keystore settings, not for TLS certificates.\n# Verify: check the new expiry date with openssl s_client and look in the ES logs for\n# an entry showing the certificate was reloaded."}},{"title":"验证","detail":"用上面的 openssl 命令确认使用新证书的 TLS 握手成功且到期时间已延长；用客户端进行端到端连接。"},{"title":"永久措施：杜绝再次发生","detail":"在证书到期前 30 天设置告警；使用自动续期（ACME/Let's Encrypt、HashiCorp Vault、cert-manager）。将续期后的重新加载自动化。"}],"related":["connect-cli"]},{"id":"upgrade-rollback","title":"版本升级 / 补丁回退","icon":"⏪","symptom":"升级/打补丁后出现问题：性能回退、应用不兼容或数据库无法打开。","steps":[{"title":"先停下来：你有回退方案吗？","detail":"若在生产环境升级之前没有做完整备份 +（如有可能）存储快照，最安全的做法是向前修复。在大多数数据库中，回退意味着从备份还原。这一步本应在升级之前就规划好。"},{"title":"对问题进行分类","detail":"是优化器计划回归（查询变慢）、应用/驱动不兼容，还是无法打开/损坏？解决路径会因此大不相同。"},{"title":"各数据库的回退路径","detail":"Oracle：用 datapatch 回退补丁，或使用 Flashback Database（若已创建 guaranteed restore point）。PostgreSQL：pg_upgrade 之后的回退 = 旧二进制文件 + 旧数据目录的备份。MySQL：原地降级（DOWNGRADE）通常不受支持——请从备份还原。SQL Server：不支持降级——从升级前的备份还原。无论哪种情况：快照/备份都是你的保险。"},{"title":"若是计划回归：无需回滚即可修复","detail":"通常不需要回滚整个版本；只是计划发生了变化。","engines":{"oracle":"ALTER SYSTEM SET optimizer_features_enable='19.1.0';  -- pin to the old behavior\n-- or lock in the good plan with a SQL Plan Baseline","postgres":"ANALYZE;  -- fresh statistics first\n-- if needed, temporarily: steer the plan with settings such as SET enable_nestloop=off;"}},{"title":"永久措施：下一次","detail":"先在预发布环境用真实负载测试升级，采用金丝雀/滚动升级，创建 guaranteed restore point / 快照，并记录回退流程。"}],"related":["last-backup-status","restore-dump","version-uptime"]},{"id":"es-red-cluster","title":"Elasticsearch 集群 red/yellow 与未分配分片","icon":"🟥","symptom":"_cluster/health 为 'red' 或 'yellow'；存在未分配分片；读/写受限或数据无法访问。","steps":[{"title":"颜色与原因","detail":"yellow = 副本分片未分配（数据存在，但冗余降低）。red = 至少有一个主分片缺失（该部分数据无法访问）。首先确定是哪一种以及原因。","engines":{"elasticsearch":"GET _cluster/health?level=indices\nGET _cluster/allocation/explain"}},{"title":"列出未分配分片及原因","detail":"unassigned.reason 列给出根本原因（NODE_LEFT、ALLOCATION_FAILED、DISK 等）。","engines":{"elasticsearch":"GET _cat/shards?v&h=index,shard,prirep,state,unassigned.reason&s=state"}},{"title":"处理常见原因","detail":"1) 磁盘水位线 → 超过 low 时，节点不再接收新分片；达到 flood_stage 时，索引变为只读。2) 节点宕机 / 离开。3) 分配被保持在禁用状态。4) 副本数 > 符合条件的节点数。"},{"title":"修复","detail":"根据实际原因解除阻塞。","engines":{"elasticsearch":"# If disk space was freed, remove the read-only block (7.4+ also lifts it automatically once below the high watermark):\nPUT _all/_settings {\"index.blocks.read_only_allow_delete\": null}\n# If allocation was left restricted, return it to the default (all):\nPUT _cluster/settings {\"persistent\":{\"cluster.routing.allocation.enable\":null}}\n# Retry failed allocations:\nPOST _cluster/reroute?retry_failed=true"}},{"title":"若仍为 red 且主分片确实已丢失","detail":"从快照还原受影响的索引，或从源数据重新索引（_reindex）。永久措施：磁盘水位线告警、合适的副本数（≥1）以及节点健康监控。"}],"related":["replication-status","list-replicas","free-space"]},{"id":"time-skew","title":"时钟偏差 / NTP 问题","icon":"⏱️","symptom":"集群节点之间存在时钟差异；复制/选举不一致，出现 'certificate not yet valid'，日志中时间跳变。","steps":[{"title":"识别症状","detail":"时钟偏差很隐蔽：Mongo/Redis Sentinel 选举反复切换、TLS 'not yet valid/expired' 错误、Kerberos 错误、日志中时间戳倒退。在分布式系统中，它是大多数“无法解释”的怪异现象的根源。"},{"title":"测量差异","detail":"在每个节点上比较时间源和偏移量：`timedatectl`、`chronyc tracking`（或 `ntpq -p`），并在各节点之间比较 `date -u`。它们相差多少 ms/s？"},{"title":"修复 NTP/chrony","detail":"chronyd/ntpd 是否在运行，能否访问其时间源，是否已同步？启动服务并修复时间源；对于较大的偏移量，要谨慎地逐步调整（突然跳变可能触发某些数据库的问题）。"},{"title":"验证受影响的组件","detail":"时钟修复后，检查：MongoDB 副本集选举是否稳定，Redis Sentinel/Cluster 是否健康，TLS 握手是否成功，复制延迟指标是否已恢复？"},{"title":"永久措施","detail":"在所有节点上强制启用 NTP 并监控偏移量（告警：>100ms）。在虚拟机上，明确宿主机与客户机的时间同步设置；最好使用单一、可靠的时间源层级。"}],"related":["replication-status"]}]},"playbooks/hadr.json":{"schemaVersion":1,"playbooks":[{"id":"hadr-oracle-dataguard","title":"Oracle Data Guard（物理备库）搭建","icon":"🛡️","symptom":"目标：使用物理备库（Data Guard）为 Oracle 搭建灾难恢复和高可用；在主库故障时能够切换到备库。","steps":[{"title":"将主库置于 FORCE LOGGING 和 ARCHIVELOG 模式","detail":"为使备库与主库保持一致，所有变更都必须写入 redo。FORCE LOGGING 会记录包括 NOLOGGING 操作在内的所有操作。开启 ARCHIVELOG 模式时，数据库需在 MOUNT 状态下启动。","engines":{"oracle":"ALTER DATABASE FORCE LOGGING;\n-- For ARCHIVELOG:\nSHUTDOWN IMMEDIATE;\nSTARTUP MOUNT;\nALTER DATABASE ARCHIVELOG;\nALTER DATABASE OPEN;"}},{"title":"添加 standby redo log 文件","detail":"standby redo log（SRL）的组数应比 online redo log 多一组，且大小相同。它们需在主库和备库上都创建；实时应用需要它们。示例中，若 online redo 为 200M，则 SRL 也为 200M。","engines":{"oracle":"ALTER DATABASE ADD STANDBY LOGFILE ('/u01/oradata/srl01.log') SIZE 200M;\nALTER DATABASE ADD STANDBY LOGFILE ('/u01/oradata/srl02.log') SIZE 200M;\nALTER DATABASE ADD STANDBY LOGFILE ('/u01/oradata/srl03.log') SIZE 200M;\nALTER DATABASE ADD STANDBY LOGFILE ('/u01/oradata/srl04.log') SIZE 200M;"}},{"title":"设置 log_archive_dest 及相关参数","detail":"在主库上定义 dest_2，将 redo 传输到备库。设置 DB_UNIQUE_NAME、LOG_ARCHIVE_CONFIG（两端的 db_unique_name）、FAL_SERVER 和 STANDBY_FILE_MANAGEMENT=AUTO。这样文件会在备库上自动创建。","engines":{"oracle":"ALTER SYSTEM SET LOG_ARCHIVE_CONFIG='DG_CONFIG=(orcl_prim,orcl_stby)';\nALTER SYSTEM SET LOG_ARCHIVE_DEST_2='SERVICE=orcl_stby ASYNC VALID_FOR=(ONLINE_LOGFILES,PRIMARY_ROLE) DB_UNIQUE_NAME=orcl_stby';\nALTER SYSTEM SET FAL_SERVER='orcl_stby';  -- for role changes; on the standby FAL_SERVER='orcl_prim'\nALTER SYSTEM SET STANDBY_FILE_MANAGEMENT=AUTO;"}},{"title":"用 RMAN DUPLICATE FOR STANDBY 创建备库","detail":"在备库服务器上设置好密码文件和静态 listener/tnsnames 后，RMAN 连接到主库和备库（AUXILIARY）。FROM ACTIVE DATABASE 无需备份即可通过网络复制；DORECOVER 应用 redo。","engines":{"oracle":"rman TARGET sys/password@orcl_prim AUXILIARY sys/password@orcl_stby\nRUN {\n  DUPLICATE TARGET DATABASE FOR STANDBY FROM ACTIVE DATABASE\n  DORECOVER\n  NOFILENAMECHECK;\n}"}},{"title":"启动 redo 应用（MRP）并验证","detail":"在备库处于 MOUNT 状态时启动托管恢复；使用 USING CURRENT LOGFILE 实现实时应用。通过 v$managed_standby 确认应用进程（MRP0）正在运行。","engines":{"oracle":"ALTER DATABASE RECOVER MANAGED STANDBY DATABASE USING CURRENT LOGFILE DISCONNECT FROM SESSION;\nSELECT process, status, sequence# FROM v$managed_standby WHERE process LIKE 'MRP%';"}},{"title":"启用 Data Guard Broker","detail":"broker 集中管理配置，将故障转移/主备切换简化为一条命令。在两个实例上设置 DG_BROKER_START=TRUE，然后用 dgmgrl 创建并启用配置。在 12c+ 中，broker 负责管理 redo 传输：若 LOG_ARCHIVE_DEST_2 是手动设置的，ADD DATABASE 会因 ORA-16698 失败；请先用 ALTER SYSTEM SET LOG_ARCHIVE_DEST_2='' 将其清空。","engines":{"oracle":"ALTER SYSTEM SET DG_BROKER_START=TRUE;\n-- In dgmgrl:\nCREATE CONFIGURATION dgconf AS PRIMARY DATABASE IS orcl_prim CONNECT IDENTIFIER IS orcl_prim;\nADD DATABASE orcl_stby AS CONNECT IDENTIFIER IS orcl_stby;\nENABLE CONFIGURATION;"}},{"title":"监控同步差距并完成测试","detail":"必须持续监控主库与备库之间的 sequence 差异；差距不断扩大表明存在网络/应用瓶颈。dgmgrl SHOW CONFIGURATION 应显示 'SUCCESS'。可选地通过主备切换测试角色变更。","engines":{"oracle":"-- dgmgrl (SHOW DATABASE shows the Transport Lag / Apply Lag values):\nSHOW CONFIGURATION;\nSHOW DATABASE orcl_stby;\n-- SQL (standby): SELECT name, value FROM v$dataguard_stats WHERE name LIKE '%lag';"}}],"related":["replication-status","promote-failover","last-backup-status"]},{"id":"hadr-mssql-alwayson","title":"SQL Server Always On 可用性组搭建","icon":"🔁","symptom":"目标：使用 Always On 可用性组（AG）为 SQL Server 搭建自动故障转移和可读辅助副本。","steps":[{"title":"搭建 Windows Server 故障转移群集（WSFC）","detail":"Always On 依赖于底层的 WSFC。在所有节点上安装 Failover Clustering 功能，并配置网络和见证。用 PowerShell 创建群集。","engines":{"mssql":"-- PowerShell (the Failover-Clustering feature must be installed on every node):\nNew-Cluster -Name sqlclu -Node node1,node2 -StaticAddress 10.0.0.50\n-- Witness:\nSet-ClusterQuorum -NodeAndFileShareMajority \\\\fs01\\quorum"}},{"title":"在每个实例上启用 Always On","detail":"使用 SQL Server Configuration Manager 或 PowerShell 启用 Always On 可用性组功能。然后重启 SQL Server 服务。这会将群集成员身份绑定到实例。","engines":{"mssql":"-- PowerShell:\nEnable-SqlAlwaysOn -ServerInstance node1\\SQL01 -Force\nEnable-SqlAlwaysOn -ServerInstance node2\\SQL01 -Force"}},{"title":"备份数据库并在辅助副本上 WITH NORECOVERY 还原","detail":"要加入 AG 的数据库必须处于 FULL 恢复模式。在主副本上执行完整备份和日志备份；在辅助副本上 WITH NORECOVERY 还原（以便继续接收 redo）。这是手动初始化（manual seeding）；另一种方式是自动种子设定（automatic seeding）。","engines":{"mssql":"-- On the primary:\nBACKUP DATABASE app TO DISK='\\\\fs01\\bak\\app.bak';\nBACKUP LOG app TO DISK='\\\\fs01\\bak\\app.trn';\n-- On the secondary:\nRESTORE DATABASE app FROM DISK='\\\\fs01\\bak\\app.bak' WITH NORECOVERY;\nRESTORE LOG app FROM DISK='\\\\fs01\\bak\\app.trn' WITH NORECOVERY;"}},{"title":"创建端点并定义可用性组","detail":"在端口 5022 上创建 HADR 端点，用于节点之间的 redo 流量。同步模式（SYNCHRONOUS_COMMIT）提供零数据丢失的自动故障转移；异步模式（ASYNCHRONOUS_COMMIT）存在延迟，但对远程 DR 性能较好。","engines":{"mssql":"CREATE ENDPOINT hadr_endpoint STATE=STARTED\n  AS TCP (LISTENER_PORT=5022)\n  FOR DATA_MIRRORING (ROLE=ALL, ENCRYPTION=REQUIRED ALGORITHM AES);\n\nCREATE AVAILABILITY GROUP appag\n  FOR DATABASE app\n  REPLICA ON\n    'NODE1\\SQL01' WITH (ENDPOINT_URL='TCP://node1:5022', AVAILABILITY_MODE=SYNCHRONOUS_COMMIT, FAILOVER_MODE=AUTOMATIC, SECONDARY_ROLE(ALLOW_CONNECTIONS=ALL)),\n    'NODE2\\SQL01' WITH (ENDPOINT_URL='TCP://node2:5022', AVAILABILITY_MODE=SYNCHRONOUS_COMMIT, FAILOVER_MODE=AUTOMATIC, SECONDARY_ROLE(ALLOW_CONNECTIONS=ALL));"}},{"title":"将辅助副本加入 AG 并加入数据库","detail":"在辅助实例上 JOIN AG，然后将已还原的数据库加入 AG。若使用手动初始化则需要此步骤；使用自动种子设定时，则在主副本上通过 SET(SEEDING_MODE) 处理。","engines":{"mssql":"-- On the secondary:\nALTER AVAILABILITY GROUP appag JOIN;\nALTER DATABASE app SET HADR AVAILABILITY GROUP = appag;"}},{"title":"创建可用性组侦听器","detail":"侦听器是应用连接的虚拟名称和 IP；故障转移后，连接会自动重定向到新的主副本。在应用连接字符串中使用该名称和 MultiSubnetFailover=True。","engines":{"mssql":"ALTER AVAILABILITY GROUP appag\n  ADD LISTENER 'appag-listener' (WITH IP ((N'10.0.0.60', N'255.255.255.0')), PORT=1433);"}},{"title":"测试故障转移并验证同步状态","detail":"同步状态为 'HEALTHY' 时，通过计划内故障转移测试角色变更。要实现自动故障转移，模式必须为同步且 FAILOVER_MODE=AUTOMATIC。用 sys.dm_hadr_availability_replica_states 监控状态。","engines":{"mssql":"SELECT ag.name, ars.role_desc, ars.synchronization_health_desc\nFROM sys.dm_hadr_availability_replica_states ars\nJOIN sys.availability_groups ag ON ag.group_id=ars.group_id;\n-- Planned failover (on the node that will become the new primary):\nALTER AVAILABILITY GROUP appag FAILOVER;"}}],"related":["replication-status","promote-failover","list-replicas"]},{"id":"hadr-postgres-streaming","title":"PostgreSQL 流复制 + 自动故障转移","icon":"🐘","symptom":"目标：使用流复制为 PostgreSQL 搭建 hot standby，并加入 Patroni/repmgr 实现自动故障转移。","steps":[{"title":"在主库上设置 WAL 和连接参数","detail":"对于流复制，wal_level 至少须为 'replica'。max_wal_senders 必须覆盖副本数 + 备份连接数，并须通过 wal_keep_size 或复制槽防止 WAL 被过早删除。这些更改需要重启。","engines":{"postgres":"ALTER SYSTEM SET wal_level = 'replica';\nALTER SYSTEM SET max_wal_senders = 10;\nALTER SYSTEM SET max_replication_slots = 10;\nALTER SYSTEM SET hot_standby = on;\n-- To apply the postgresql.conf change:\n-- systemctl restart postgresql"}},{"title":"定义复制角色和 pg_hba 访问","detail":"创建一个带 REPLICATION 属性的角色，供备库连接使用。在 pg_hba.conf 中添加一行，允许从备库 IP 访问 'replication' 数据库，并用 pg_reload_conf() 重新加载。","engines":{"postgres":"CREATE ROLE replicator WITH REPLICATION LOGIN PASSWORD 'secret';\n-- Add to pg_hba.conf:\n-- host replication replicator 10.0.0.20/32 scram-sha-256\nSELECT pg_reload_conf();"}},{"title":"在主库上创建持久复制槽","detail":"物理复制槽可防止主库在备库落后时删除所需的 WAL；用复制槽管理比 wal_keep_size 更安全。我们将在备库配置中使用该槽名。","engines":{"postgres":"SELECT pg_create_physical_replication_slot('standby1_slot');\nSELECT slot_name, active, restart_lsn FROM pg_replication_slots;"}},{"title":"在备库服务器上用 pg_basebackup 做基础备份","detail":"在备库数据目录为空的情况下，从主库获取完整副本。-R 参数会自动写入 standby.signal 文件和 primary_conninfo；-C 还可以创建复制槽。槽名通过 -S 传入。","engines":{"postgres":"pg_basebackup -h 10.0.0.10 -U replicator -D /var/lib/pgsql/data \\\n  -R -X stream -S standby1_slot -P -v"}},{"title":"验证 primary_conninfo 和 hot_standby，启动备库","detail":"检查 -R 写入的 postgresql.auto.conf 中的 primary_conninfo（host、user、slot）；standby.signal 文件的存在决定备库模式。hot_standby=on 时备库接受只读查询。启动服务。","engines":{"postgres":"-- Expected in postgresql.auto.conf:\n-- primary_conninfo = 'host=10.0.0.10 user=replicator password=secret'\n-- primary_slot_name = 'standby1_slot'\n-- systemctl start postgresql\nSELECT pg_is_in_recovery();  -- should return true on the standby"}},{"title":"从主库监控复制延迟","detail":"在主库上，pg_stat_replication 显示每个备库的已发送/已重放 LSN 和延迟。write/flush/replay lag 应保持较小；若使用同步副本，sync_state 为 'sync'。","engines":{"postgres":"SELECT client_addr, state, sync_state,\n       pg_wal_lsn_diff(sent_lsn, replay_lsn) AS replay_lag_bytes,\n       write_lag, flush_lag, replay_lag\nFROM pg_stat_replication;"}},{"title":"加入 Patroni 或 repmgr 实现自动故障转移","detail":"单纯的流复制需要手动 promote。自动故障转移需要集群管理器：Patroni（基于 etcd/Consul 的领导者选举 + 自动 promote）或 repmgr（由 repmgrd 监控/故障转移）。在应用端，用 HAProxy/pgBouncer 提供单一端点。若需要手动 promote：pg_ctl promote。promote 之前，务必确认旧主库确实已停止且应用无法写入（脑裂风险）。将旧主库作为新主库的备库重新加入需要 pg_rewind；为此必须启用 wal_log_hints = on 或数据校验和。","engines":{"postgres":"-- Manual promote (if there is no cluster manager):\npg_ctl promote -D /var/lib/pgsql/data\n-- or via SQL:\nSELECT pg_promote();\n-- With Patroni: patronictl -c /etc/patroni.yml switchover mycluster"}}],"related":["replication-status","list-replicas","promote-failover"]},{"id":"hadr-mysql-innodb-cluster","title":"MySQL InnoDB Cluster（Group Replication）搭建","icon":"🐬","symptom":"目标：使用 InnoDB Cluster（Group Replication + MySQL Shell + MySQL Router）为 MySQL 搭建具备自动故障转移的高可用。","steps":[{"title":"满足前提条件","detail":"InnoDB Cluster 要求每个节点使用 MySQL 8.0+、唯一的 server_id、GTID 模式和 InnoDB 存储引擎。所有实例之间必须网络互通，且表必须有主键。准备一个用于管理的 clusterAdmin 用户。","engines":{"mysql":"-- my.cnf on each instance:\n-- server_id unique, gtid_mode=ON, enforce_gtid_consistency=ON\nCREATE USER 'icadmin'@'%' IDENTIFIED BY 'secret';\nGRANT ALL PRIVILEGES ON *.* TO 'icadmin'@'%' WITH GRANT OPTION;"}},{"title":"用 MySQL Shell 配置每个实例","detail":"在 mysqlsh 中，dba.configureInstance() 使每个节点满足 Group Replication 的要求；它会修正缺失的参数，并在需要时要求重启。在所有节点上重复执行。","engines":{"mysql":"-- mysqlsh:\ndba.configureInstance('icadmin@node1:3306')\ndba.configureInstance('icadmin@node2:3306')\ndba.configureInstance('icadmin@node3:3306')"}},{"title":"创建集群（种子节点）","detail":"连接到一个节点，用 dba.createCluster() 启动集群；该节点成为第一个主节点。返回的 cluster 对象用于后续添加节点。","engines":{"mysql":"-- mysqlsh, while connected to node1:\nvar cluster = dba.createCluster('appCluster')"}},{"title":"将其他实例加入集群","detail":"用 cluster.addInstance() 添加从节点。recoveryMethod 可选择 clone（物理克隆，对新节点速度快）或 incremental（基于 GTID）。至少 3 个节点才能形成奇数多数派并实现自动故障转移。","engines":{"mysql":"-- mysqlsh:\ncluster.addInstance('icadmin@node2:3306', {recoveryMethod: 'clone'})\ncluster.addInstance('icadmin@node3:3306', {recoveryMethod: 'clone'})"}},{"title":"引导（bootstrap）MySQL Router","detail":"MySQL Router 在应用与集群之间进行路由；它提供读写（6446）和只读（6447）端口，并在主节点变更时自动重定向流量。bootstrap 会根据集群元数据生成配置。","engines":{"mysql":"mysqlrouter --bootstrap icadmin@node1:3306 --user=mysqlrouter --directory /etc/mysqlrouter\n# Then: mysqlrouter -c /etc/mysqlrouter/mysqlrouter.conf &"}},{"title":"检查集群状态并验证故障转移","detail":"cluster.status() 显示每个节点的角色（R/W 主节点、R/O 从节点）和状态（ONLINE）。clusterName 和拓扑应处于健康状态。通过关闭主节点并观察自动选出新主节点来测试故障转移。","engines":{"mysql":"-- mysqlsh:\nvar cluster = dba.getCluster('appCluster')\ncluster.status()\n-- Members via SQL:\nSELECT member_host, member_role, member_state FROM performance_schema.replication_group_members;"}}],"related":["replication-status","list-replicas","promote-failover"]},{"id":"hadr-mongodb-replicaset","title":"MongoDB 副本集 + 分片搭建","icon":"🍃","symptom":"目标：为 MongoDB 搭建具备自动故障转移的副本集，并将其扩展为分片集群以实现横向扩展。","steps":[{"title":"以相同的 replSet 名称启动 mongod 进程","detail":"以相同的 --replSet 名称和可访问的 --bind_ip 启动每个副本集成员。生产环境中建议使用 keyFile 或 TLS 进行内部认证。投票成员数应为奇数，以形成明确的多数派。","engines":{"mongodb":"mongod --replSet rs0 --port 27017 --dbpath /data/n1 --bind_ip localhost,10.0.0.11\nmongod --replSet rs0 --port 27017 --dbpath /data/n2 --bind_ip localhost,10.0.0.12\nmongod --replSet rs0 --port 27017 --dbpath /data/n3 --bind_ip localhost,10.0.0.13"}},{"title":"初始化副本集","detail":"用 mongosh 连接到一个成员，调用 rs.initiate() 并定义所有成员。这会触发第一次选举并选出主节点。用 rs.status() 确认 PRIMARY/SECONDARY 角色。","engines":{"mongodb":"rs.initiate({\n  _id: 'rs0',\n  members: [\n    { _id: 0, host: '10.0.0.11:27017' },\n    { _id: 1, host: '10.0.0.12:27017' },\n    { _id: 2, host: '10.0.0.13:27017' }\n  ]\n})\nrs.status()"}},{"title":"添加成员、设置优先级，必要时添加 arbiter","detail":"之后用 rs.add() 添加成员。priority 决定哪个成员倾向于成为主节点（0 = 永远不会成为主节点）。arbiter 只投票、不保存数据；在数据节点数为偶数时用作决胜者，但出于数据安全考虑更推荐使用真正的成员。在基于 arbiter 的拓扑（尤其是 PSA）中，数据节点宕机时 majority write concern 无法满足，写入会等待；在 5.0+ 中，添加 arbiter 之前可能需要用 setDefaultRWConcern 显式设置集群级默认 write concern。","engines":{"mongodb":"rs.add({ host: '10.0.0.14:27017', priority: 0.5 })\n// Arbiter only if really needed (a data-bearing member is preferred):\nrs.addArb('10.0.0.15:27017')\n// change priority:\ncfg = rs.conf(); cfg.members[0].priority = 2; rs.reconfig(cfg)"}},{"title":"为分片搭建 config server 副本集","detail":"分片集群的元数据保存在 config server 副本集（--configsvr）中。它本身也以副本集方式初始化；为实现高可用，建议使用 3 个成员。","engines":{"mongodb":"mongod --configsvr --replSet cfgrs --port 27019 --dbpath /data/cfg1 --bind_ip localhost,10.0.0.21\n-- mongosh (on the config server):\nrs.initiate({ _id: 'cfgrs', configsvr: true, members: [\n  { _id: 0, host: '10.0.0.21:27019' },\n  { _id: 1, host: '10.0.0.22:27019' },\n  { _id: 2, host: '10.0.0.23:27019' } ] })"}},{"title":"启动分片副本集和 mongos 路由器","detail":"每个分片都是一个以 --shardsvr 启动的独立副本集（数据持久性来自分片内部的复制）。mongos 路由器启动时指向 config server；应用连接到 mongos。","engines":{"mongodb":"mongod --shardsvr --replSet shard1rs --port 27018 --dbpath /data/s1 --bind_ip localhost,10.0.0.31\n# initiate shard1rs with rs.initiate, then mongos:\nmongos --configdb cfgrs/10.0.0.21:27019,10.0.0.22:27019,10.0.0.23:27019 --port 27017 --bind_ip localhost,10.0.0.40"}},{"title":"添加分片并对集合分片","detail":"连接到 mongos，用 sh.addShard() 添加每个分片副本集。在数据库上启用分片，并用 sh.shardCollection() 按分片键分布集合。键的选择至关重要：应具有高基数且分布均匀。用 sh.status() 监控 chunk 分布。","engines":{"mongodb":"sh.addShard('shard1rs/10.0.0.31:27018')\nsh.addShard('shard2rs/10.0.0.32:27018')\nsh.enableSharding('appdb')\nsh.shardCollection('appdb.events', { customer_id: 'hashed' })\nsh.status()"}}],"related":["replication-status","list-replicas","promote-failover"]},{"id":"hadr-redis-sentinel","title":"使用 Redis Sentinel + Cluster 实现高可用","icon":"⚡","symptom":"目标：使用 Sentinel 为 Redis 搭建具备自动故障转移的 master-replica 部署；若需要横向扩展，则切换到 Redis Cluster 模式。","steps":[{"title":"配置 master 和副本","detail":"运行一个 master 和至少两个副本。副本通过 REPLICAOF 跟随 master（也可在 redis.conf 中写入 'replicaof <host> <port>'）。为实现持久化启用 AOF 或 RDB；建议使用 requirepass/masterauth 进行认证。","engines":{"redis":"# at runtime on the replica nodes:\nREPLICAOF 10.0.0.10 6379\n# to make it persistent, redis.conf:\n# replicaof 10.0.0.10 6379\n# masterauth secret\nINFO replication"}},{"title":"用 monitor 和 quorum 配置 Sentinel","detail":"在每个 Sentinel 节点的 sentinel.conf 中定义要监控的 master 和 quorum。quorum 是判定 master 为 'down' 所需的最少 Sentinel 数。生产环境中建议在 3 台独立服务器上部署 3 个 Sentinel（防止脑裂）。","engines":{"redis":"# sentinel.conf:\nsentinel monitor mymaster 10.0.0.10 6379 2\nsentinel down-after-milliseconds mymaster 5000\nsentinel failover-timeout mymaster 60000\nsentinel parallel-syncs mymaster 1"}},{"title":"启动 Sentinel 进程","detail":"在每个节点上启动 Sentinel。Sentinel 会自动发现彼此和副本；它们在运行时将配置写入文件。因此 sentinel.conf 必须可写。","engines":{"redis":"redis-sentinel /etc/redis/sentinel.conf\n# or:\nredis-server /etc/redis/sentinel.conf --sentinel"}},{"title":"通过 Sentinel 查询状态并测试故障转移","detail":"用 SENTINEL 命令查询当前的 master 和副本。应用应通过向 Sentinel 发送 'get-master-addr-by-name' 来获知当前 master。通过计划内故障转移验证自动切换。","engines":{"redis":"SENTINEL master mymaster\nSENTINEL get-master-addr-by-name mymaster\nSENTINEL replicas mymaster\n# planned switchover:\nSENTINEL failover mymaster"}},{"title":"替代方案：用 Redis Cluster 对数据分区","detail":"若单个 master 的内存/吞吐量不够，使用 Redis Cluster：数据被划分为 16384 个哈希槽并分布到多个 master 上；每个 master 都有自己的副本。应用必须使用支持集群的客户端。用 redis-cli --cluster create 搭建。","engines":{"redis":"redis-cli --cluster create \\\n  10.0.0.11:6379 10.0.0.12:6379 10.0.0.13:6379 \\\n  10.0.0.21:6379 10.0.0.22:6379 10.0.0.23:6379 \\\n  --cluster-replicas 1"}},{"title":"验证集群状态","detail":"集群创建后，确认所有槽都已分配且节点已互联。cluster_state 应为 'ok'，全部 16384 个槽都应显示为已分配。用 CLUSTER 命令监控节点和槽的分布。","engines":{"redis":"redis-cli --cluster check 10.0.0.11:6379\nredis-cli -c -h 10.0.0.11 CLUSTER INFO\nredis-cli -c -h 10.0.0.11 CLUSTER NODES"}}],"related":["replication-status","list-replicas","promote-failover"]},{"id":"hadr-elasticsearch-ha","title":"Elasticsearch 弹性：副本 + 快照 + CCR","icon":"🔎","symptom":"目标：通过副本分片、专用 master/data 节点、快照备份（SLM）和跨集群复制（CCR）为 Elasticsearch 构建弹性。","steps":[{"title":"分离角色：专用 master 节点和 data 节点","detail":"为保证集群稳定，将具备 master 资格的投票节点（最好 3 个，形成奇数多数派）与数据负载分离。在 elasticsearch.yml 中用 node.roles 分配角色，并为发现机制定义 seed hosts。3 个 master 可防止脑裂。","engines":{"elasticsearch":"# elasticsearch.yml on master nodes:\n# node.roles: [ master ]\n# on data nodes:\n# node.roles: [ data, ingest ]\n# discovery.seed_hosts: [ m1, m2, m3 ]\n# cluster.initial_master_nodes: [ m1, m2, m3 ]"}},{"title":"设置索引副本数","detail":"number_of_replicas 是为每个主分片保留的副本数；它至少应为 1，以便在丢失某个 data 节点时数据仍然可用。它可以在线上索引上动态更改；副本数必须小于 data 节点数。","engines":{"elasticsearch":"PUT /orders/_settings\n{\n  \"index\": { \"number_of_replicas\": 1 }\n}\n# default via template (composable template; the old _template API is legacy):\n# PUT _index_template/order-tpl { \"index_patterns\": [\"order*\"], \"template\": { \"settings\": { \"number_of_replicas\": 1 } } }"}},{"title":"定义快照仓库","detail":"备份保存到仓库中：共享文件系统（fs）或 S3/GCS/Azure 插件。为了即使集群被删除也能恢复数据，仓库必须位于集群之外的持久存储上。","engines":{"elasticsearch":"PUT /_snapshot/backup_repo\n{\n  \"type\": \"fs\",\n  \"settings\": { \"location\": \"/mnt/es_backups\", \"compress\": true }\n}"}},{"title":"用 SLM 安排自动快照","detail":"快照生命周期管理（SLM）定期生成快照并应用保留策略。cron 表达式定义频率、包含哪些索引以及保留多少个快照。","engines":{"elasticsearch":"PUT /_slm/policy/daily_backup\n{\n  \"schedule\": \"0 30 1 * * ?\",\n  \"name\": \"<daily-{now/d}>\",\n  \"repository\": \"backup_repo\",\n  \"config\": { \"indices\": [\"*\"] },\n  \"retention\": { \"expire_after\": \"30d\", \"min_count\": 5, \"max_count\": 50 }\n}"}},{"title":"验证快照和还原","detail":"手动触发 SLM 策略以验证其是否正常工作；快照状态应为 SUCCESS。定期进行还原测试，因为未经测试的备份不算备份。","engines":{"elasticsearch":"POST /_slm/policy/daily_backup/_execute\nGET /_snapshot/backup_repo/_all\n# restore:\nPOST /_snapshot/backup_repo/<snapshot_name>/_restore"}},{"title":"用跨集群复制（CCR）搭建远程 DR","detail":"为实现跨区域灾难恢复，第二个集群（follower）跟随远程主集群（leader）中的索引。先定义远程集群，再将 follower 索引关联到 leader；leader 上的变更会自动同步过来。CCR 是 Platinum（商业）功能。","engines":{"elasticsearch":"# define the remote cluster on the follower cluster:\nPUT /_cluster/settings\n{ \"persistent\": { \"cluster\": { \"remote\": { \"primary\": { \"seeds\": [\"leader1:9300\"] } } } } }\n# create the follower index:\nPUT /orders-follower/_ccr/follow\n{ \"remote_cluster\": \"primary\", \"leader_index\": \"orders\" }"}}],"related":["replication-status","list-replicas","last-backup-status"]}]},"playbooks/security.json":{"schemaVersion":1,"playbooks":[{"id":"harden-sql","title":"安全加固：SQL 数据库（Oracle、PostgreSQL、MySQL、SQL Server）","icon":"🔒","symptom":"目标：按 CIS 风格加固关系型数据库——默认账户、强密码策略、最小权限、网络限制、TLS、审计和加密（TDE）。","steps":[{"title":"删除或锁定默认/不需要的账户，删除示例 schema","detail":"安装时附带的默认账户和示例 schema 是众所周知的攻击面。锁定或删除不使用的账户；重命名顶级/服务账户。","engines":{"oracle":"-- Lock unused default accounts and expire their passwords:\nALTER USER SCOTT ACCOUNT LOCK PASSWORD EXPIRE;\nALTER USER HR ACCOUNT LOCK PASSWORD EXPIRE;\n-- Audit locked/open accounts:\nSELECT username, account_status FROM dba_users ORDER BY account_status;\n-- Remove sample schemas: $ORACLE_HOME/demo/schema/drop_sch.sql","postgres":"-- Keep the default superuser 'postgres' role; do NOT drop/rename it.\n-- Audit roles:\nSELECT rolname, rolsuper, rolcanlogin FROM pg_roles ORDER BY rolsuper DESC;\n-- Remove unneeded login roles:\nDROP ROLE IF EXISTS test_user;\n-- Drop sample/test databases: DROP DATABASE IF EXISTS test;","mysql":"# Interactive hardening wizard (removes anonymous users, the test DB, remote root):\nmysql_secure_installation\n-- Manual: remove anonymous and test accounts:\nDROP USER IF EXISTS ''@'localhost';\nDROP USER IF EXISTS ''@'%';\nDROP DATABASE IF EXISTS test;\nDELETE FROM mysql.db WHERE Db LIKE 'test%'; FLUSH PRIVILEGES;","mssql":"-- Disable and rename the 'sa' account:\nALTER LOGIN sa DISABLE;\nALTER LOGIN sa WITH NAME = [sa_disabled_acct];\n-- Restrict the guest user — only in USER databases\n-- (guest must stay enabled in master, tempdb and msdb; disabling it in tempdb/msdb causes problems):\nUSE appdb;\nREVOKE CONNECT FROM guest;\n-- Remove sample DBs: DROP DATABASE AdventureWorks;"}},{"title":"强制执行强密码策略和过期机制","detail":"弱密码或从不更改的密码是最常见的入侵入口。强制执行登录尝试次数限制、复杂度和密码有效期；使用强哈希存储密码。","engines":{"oracle":"-- Attempt limit, lifetime and complexity via a profile:\nCREATE PROFILE app_profile LIMIT\n  FAILED_LOGIN_ATTEMPTS 5\n  PASSWORD_LIFE_TIME 90\n  PASSWORD_REUSE_MAX 10\n  PASSWORD_VERIFY_FUNCTION ora12c_verify_function;\nALTER USER app_user PROFILE app_profile;","postgres":"-- Strong hash algorithm (SCRAM):\nSHOW password_encryption;              -- should be 'scram-sha-256'\nALTER SYSTEM SET password_encryption = 'scram-sha-256';\n-- Load the passwordcheck extension for complexity:\n-- shared_preload_libraries = 'passwordcheck'\n-- Lifetime: ALTER ROLE app_user VALID UNTIL '2026-12-31';","mysql":"-- Install the validate_password component and set the policy:\nINSTALL COMPONENT 'file://component_validate_password';\nSET PERSIST validate_password.policy = 'STRONG';\nSET PERSIST validate_password.length = 14;\n-- Attempt limit and lifetime:\nALTER USER app_user PASSWORD EXPIRE INTERVAL 90 DAY\n  FAILED_LOGIN_ATTEMPTS 5 PASSWORD_LOCK_TIME 1;","mssql":"-- Enforce the Windows password policy and expiration on the login:\nALTER LOGIN app_user WITH CHECK_POLICY = ON, CHECK_EXPIRATION = ON;\n-- Audit policy/expiration status:\nSELECT name, is_policy_checked, is_expiration_checked\nFROM sys.sql_logins;"}},{"title":"最小权限：收回 PUBLIC/过宽的权限，授予基于角色的权限","detail":"授予所有人（PUBLIC）的过宽权限会使横向移动更容易。收回这些权限，创建角色，只授予所需的权限。","engines":{"oracle":"-- Revoke dangerous PUBLIC privileges:\nREVOKE EXECUTE ON UTL_HTTP FROM PUBLIC;\nREVOKE EXECUTE ON UTL_FILE FROM PUBLIC;\nREVOKE EXECUTE ON DBMS_SCHEDULER FROM PUBLIC;\n-- Role-based privileges:\nCREATE ROLE app_read;\nGRANT SELECT ON app.orders TO app_read;\nGRANT app_read TO app_user;","postgres":"-- Revoke the default PUBLIC privileges on the schema:\nREVOKE ALL ON DATABASE appdb FROM PUBLIC;\nREVOKE CREATE ON SCHEMA public FROM PUBLIC;\n-- Role-based, read-only access:\nCREATE ROLE app_read NOLOGIN;\nGRANT USAGE ON SCHEMA app TO app_read;\nGRANT SELECT ON ALL TABLES IN SCHEMA app TO app_read;\nGRANT app_read TO app_user;","mysql":"-- Audit and revoke broad global privileges:\nSHOW GRANTS FOR app_user;\nREVOKE ALL PRIVILEGES, GRANT OPTION FROM app_user;\n-- Role-based, object-level privileges (MySQL 8+):\nCREATE ROLE app_read;\nGRANT SELECT ON appdb.* TO app_read;\nGRANT app_read TO app_user;\nSET DEFAULT ROLE app_read TO app_user;","mssql":"-- Revoke excessive privileges granted to the public role:\nREVOKE SELECT ON SCHEMA::dbo FROM public;\n-- Role-based privileges:\nCREATE ROLE app_read;\nGRANT SELECT ON SCHEMA::app TO app_read;\nALTER ROLE app_read ADD MEMBER app_user;\n-- At server level: limit fixed server roles (sysadmin)."}},{"title":"限制网络/访问——只允许已知客户端连接","detail":"避免数据库对全世界公开监听。限制监听地址，应用基于主机/IP 的访问规则，并用防火墙限制端口。","engines":{"oracle":"# listener.ora / sqlnet.ora — valid node checking:\nTCP.VALIDNODE_CHECKING = YES\nTCP.INVITED_NODES = (10.0.0.5, 10.0.0.6)\nTCP.EXCLUDED_NODES = (0.0.0.0/0)\n# After the change: lsnrctl reload","postgres":"# postgresql.conf — listen only on specific interfaces:\nlisten_addresses = '10.0.0.10,localhost'\n# pg_hba.conf — host/IP + strong authentication:\nhostssl appdb app_user 10.0.0.0/24 scram-sha-256\nhost    all      all       0.0.0.0/0      reject\n# Reload: SELECT pg_reload_conf();","mysql":"# my.cnf — bind only to a specific interface:\n[mysqld]\nbind-address = 10.0.0.10\n-- Host-based user (only from the application server):\nCREATE USER 'app_user'@'10.0.0.5'\n  IDENTIFIED BY '****' REQUIRE SSL;\n-- Never give remote root the '%' host.","mssql":"# Limit the TCP port with the firewall (application subnet only):\nNew-NetFirewallRule -DisplayName 'SQL-1433' -Direction Inbound `\n  -Protocol TCP -LocalPort 1433 -RemoteAddress 10.0.0.0/24 -Action Allow\n-- Audit listening addresses:\nSELECT * FROM sys.dm_tcp_listener_states;\n-- Disable the SQL Browser service if not used."}},{"title":"强制使用 TLS/加密连接","detail":"网络流量容易被被动嗅探。配置服务器证书，并要求客户端以加密方式连接。","engines":{"oracle":"# sqlnet.ora — TCPS/TLS (server certificate in the wallet):\nWALLET_LOCATION = (SOURCE=(METHOD=FILE)(METHOD_DATA=(DIRECTORY=/etc/oracle/wallet)))\nSSL_CLIENT_AUTHENTICATION = FALSE\nSSL_VERSION = 1.2\n# Listen only on TCPS (port 2484) in listener.ora. Note: SQLNET.ENCRYPTION_SERVER is Native Network Encryption, not TLS.","postgres":"# postgresql.conf:\nssl = on\nssl_cert_file = 'server.crt'\nssl_key_file  = 'server.key'\nssl_min_protocol_version = 'TLSv1.2'\n# Use 'hostssl' lines in pg_hba.conf; reject unencrypted connections over plain 'host'.","mysql":"# my.cnf:\n[mysqld]\nrequire_secure_transport = ON\nssl_cert = /etc/mysql/server-cert.pem\nssl_key  = /etc/mysql/server-key.pem\nssl_ca   = /etc/mysql/ca.pem\n-- At user level: ALTER USER app_user REQUIRE SSL;","mssql":"-- Force connection encryption (SQL Server Configuration Manager):\n-- Protocols for MSSQLSERVER > Properties > Force Encryption = Yes\n-- Assign a certificate; then restart the service.\n-- Check the status:\nSELECT session_id, encrypt_option FROM sys.dm_exec_connections;"}},{"title":"启用审计——让谁做了什么一目了然","detail":"审计跟踪对于监控特权访问和可疑活动至关重要。记录登录、权限变更以及对敏感对象的访问。","engines":{"oracle":"-- Create and enable a policy with Unified Auditing:\nCREATE AUDIT POLICY sec_pol\n  ACTIONS LOGON, LOGOFF, ALTER USER, GRANT, CREATE USER, DROP USER;\nAUDIT POLICY sec_pol;\n-- View the records:\nSELECT * FROM unified_audit_trail ORDER BY event_timestamp DESC;","postgres":"# With the pgaudit extension:\n# postgresql.conf:\nshared_preload_libraries = 'pgaudit'\npgaudit.log = 'ddl, role, write'\n-- Create the extension:\nCREATE EXTENSION pgaudit;\n-- log_connections = on and log_disconnections = on should also be enabled.","mysql":"-- Load the Enterprise/Percona audit_log plugin:\nINSTALL PLUGIN audit_log SONAME 'audit_log.so';\nSET PERSIST audit_log_policy = 'ALL';\n-- MariaDB: server_audit plugin\n-- INSTALL SONAME 'server_audit'; SET GLOBAL server_audit_logging=ON;","mssql":"-- Create and start a Server Audit:\nCREATE SERVER AUDIT sec_audit\n  TO FILE (FILEPATH = 'C:\\SQLAudit\\');\nALTER SERVER AUDIT sec_audit WITH (STATE = ON);\nCREATE SERVER AUDIT SPECIFICATION sec_spec\n  FOR SERVER AUDIT sec_audit\n  ADD (FAILED_LOGIN_GROUP), ADD (DATABASE_ROLE_MEMBER_CHANGE_GROUP)\n  WITH (STATE = ON);"}},{"title":"实施数据加密（静态加密 / TDE）","detail":"若磁盘、备份或文件被盗，数据必须无法读取。在列/表空间/数据库级别启用静态数据加密（TDE）。","engines":{"oracle":"-- Transparent Data Encryption (keystore + tablespace):\nADMINISTER KEY MANAGEMENT CREATE KEYSTORE '/etc/oracle/wallet'\n  IDENTIFIED BY \"WalletPwd1\";\nADMINISTER KEY MANAGEMENT SET KEY IDENTIFIED BY \"WalletPwd1\" WITH BACKUP;\nCREATE TABLESPACE sec_ts DATAFILE '/u01/sec01.dbf' SIZE 100M\n  ENCRYPTION USING 'AES256' DEFAULT STORAGE(ENCRYPT);","postgres":"-- There is no built-in block-level TDE; two common approaches:\n-- 1) Column-level pgcrypto:\nCREATE EXTENSION pgcrypto;\nINSERT INTO t(ssn) VALUES (pgp_sym_encrypt('123-45', 'my_key'));\nSELECT pgp_sym_decrypt(ssn, 'my_key') FROM t;\n-- 2) File system/block encryption (LUKS) or an EDB/Cybertec TDE build.","mysql":"# InnoDB tablespace encryption — keyring COMPONENT (8.0.24+; the keyring_file plugin was removed in 8.4)\n# 1) Global manifest in the mysqld binary directory: mysqld.my\n{ \"components\": \"file://component_keyring_file\" }\n# 2) In plugin_dir: component_keyring_file.cnf\n{ \"path\": \"/var/lib/mysql-keyring/component_keyring_file\", \"read_only\": false }\n# 3) Restart the server; verify:\n-- SELECT * FROM performance_schema.keyring_component_status;\n-- Encrypt the table:\nALTER TABLE app.orders ENCRYPTION = 'Y';\n-- For new tables: SET PERSIST default_table_encryption = ON;\n# Before 8.0.24 (8.0 only): early-plugin-load = keyring_file.so + keyring_file_data\n# WARNING: back up the keyring file SEPARATELY from the data directory and securely;\n# if the keyring is lost, encrypted tables and their backups become permanently unreadable.","mssql":"-- Transparent Data Encryption (DEK + certificate) — the key and certificate must be in master:\nUSE master;\nCREATE MASTER KEY ENCRYPTION BY PASSWORD = '<StrongPassword1>';\nCREATE CERTIFICATE tde_cert WITH SUBJECT = 'TDE Cert';\n-- MANDATORY: back up the certificate with its private key immediately and store it off the server.\n-- Without this backup, if the server is lost the encrypted DB and its backups CANNOT be restored.\nBACKUP CERTIFICATE tde_cert TO FILE = 'D:\\secure\\tde_cert.cer'\n  WITH PRIVATE KEY (FILE = 'D:\\secure\\tde_cert.pvk',\n    ENCRYPTION BY PASSWORD = '<CertBackupPassword1>');\nUSE appdb;\nCREATE DATABASE ENCRYPTION KEY\n  WITH ALGORITHM = AES_256 ENCRYPTION BY SERVER CERTIFICATE tde_cert;\nALTER DATABASE appdb SET ENCRYPTION ON;\n-- Progress: SELECT DB_NAME(database_id), encryption_state, percent_complete FROM sys.dm_database_encryption_keys;"}},{"title":"及时打补丁并禁用不需要的功能","detail":"已知漏洞是被利用最多的风险。定期应用安全补丁；禁用未使用的协议、插件和功能（缩小攻击面）。","engines":{"oracle":"-- Check the installed version and patches:\nSELECT version_full FROM v$instance;\n-- Apply the quarterly Critical Patch Update (CPU): OPatch/opatchauto\n-- Remove unused components (e.g. XML DB, APEX) and\n-- disable the external procedure (extproc) listener.","postgres":"# Check the version and minor patch level:\nSELECT version();\n# Upgrade to the current minor release with the package manager (e.g. latest 16.x).\n# Remove unused extensions: DROP EXTENSION IF EXISTS ...;\n# postgresql.conf: do not load unused language/PL extensions.","mysql":"-- Check the version: SELECT VERSION();\n-- Upgrade to the LTS/security release (8.0.x/8.4 LTS).\n-- Disable local file reading:\nSET PERSIST local_infile = OFF;\n-- Remove unused plugins: UNINSTALL PLUGIN ...;","mssql":"-- Check the product version/patch level:\nSELECT SERVERPROPERTY('ProductVersion'), SERVERPROPERTY('ProductLevel');\n-- Apply the latest Cumulative Update (CU).\n-- Disable unused features (with sp_configure):\nEXEC sp_configure 'xp_cmdshell', 0; RECONFIGURE;\nEXEC sp_configure 'Ole Automation Procedures', 0; RECONFIGURE;"}}],"related":["list-users","create-user-grant","enable-audit","change-password"]},{"id":"harden-nosql","title":"安全加固：MongoDB / Redis / Elasticsearch","icon":"🛡️","symptom":"目标：按 CIS 风格加固 NoSQL/数据存储——认证、网络限制、阻止危险命令、TLS、最小权限角色和审计。","steps":[{"title":"启用认证——切断未经认证的访问","detail":"这些产品往往默认禁用认证，一旦暴露在互联网上就会被攻破。启用认证并创建管理员用户。","engines":{"mongodb":"// mongod.conf:\nsecurity:\n  authorization: enabled\n// Create an administrator user (first time, via the localhost exception):\nuse admin\ndb.createUser({ user: \"admin\", pwd: passwordPrompt(),\n  roles: [ { role: \"userAdminAnyDatabase\", db: \"admin\" } ] })\n// Then connect with: mongosh -u admin --authenticationDatabase admin","redis":"# redis.conf — ACL (Redis 6+): disable the default user, define named users:\nuser default off\nuser admin on >AdminPassword789! ~* &* +@all\nuser app on >AppPassword456! ~app:* +@read +@write\n# Note: requirepass is a legacy compatibility setting that only sets the 'default' user's password;\n# do not confuse it with ACL users. You can also keep users in an aclfile (persisted with ACL SAVE).","elasticsearch":"# 8.x: security (authentication + TLS) is configured automatically on first start; elasticsearch.yml:\nxpack.security.enabled: true\n# Set/reset the password of the 'elastic' user:\nbin/elasticsearch-reset-password -u elastic\n# (elasticsearch-setup-passwords is deprecated since 8.0)\n# Create separate users with dedicated roles for Kibana/clients instead of 'elastic'."}},{"title":"限制网络——只开放所需的接口和端口","detail":"避免服务在 0.0.0.0 上对全世界监听。将绑定地址固定到内部接口/回环地址，并启用保护模式。","engines":{"mongodb":"# mongod.conf — internal interface + loopback only:\nnet:\n  bindIp: 127.0.0.1,10.0.0.10\n  port: 27017\n# Never use bindIpAll: true; limit the port with a firewall.","redis":"# redis.conf:\nbind 127.0.0.1 10.0.0.10\nprotected-mode yes\nport 6379\n# protected-mode blocks external access when no password is set;\n# still limit 6379 with a firewall.","elasticsearch":"# elasticsearch.yml — pin the listening interface:\nnetwork.host: 10.0.0.10\nhttp.port: 9200\ntransport.port: 9300\n# Open only the required ports in the firewall;\n# 9300 (transport) must never be exposed externally."}},{"title":"限制危险命令","detail":"一条命令就可能清除所有数据。重命名或移除破坏性/管理类命令，或阻止通配符目标。","engines":{"mongodb":"// Use role-based control instead of a separate command block:\n// Grant destructive privileges (dropDatabase, dropCollection) only to\n// dbAdmin/clusterAdmin roles; do not grant them to the application user.\ndb.grantRolesToUser(\"app\", [ { role: \"readWrite\", db: \"appdb\" } ])\n// Do NOT grant dbAdmin/root to the application user.","redis":"# For Redis 6+ the preferred way is ACLs (do not give the application user dangerous commands):\n# ACL SETUSER app ... -@dangerous\n# Legacy method — disable/rename commands in redis.conf:\nrename-command FLUSHALL \"\"\nrename-command FLUSHDB \"\"\nrename-command KEYS \"\"\nrename-command DEBUG \"\"\n# Caution: renaming CONFIG can break Sentinel, CONFIG REWRITE and management tools.","elasticsearch":"# elasticsearch.yml — block wildcard/all-index deletion:\naction.destructive_requires_name: true\n# This rejects DELETE /* or DELETE /_all;\n# bulk deletion is not possible without naming the index explicitly."}},{"title":"启用 TLS/加密连接","detail":"凭据和数据不得在开放网络上以未加密方式传输。为客户端流量和节点间流量启用 TLS。","engines":{"mongodb":"# mongod.conf — client TLS:\nnet:\n  tls:\n    mode: requireTLS\n    certificateKeyFile: /etc/ssl/mongodb.pem\n    CAFile: /etc/ssl/ca.pem\n# Client: mongosh --tls --tlsCAFile ca.pem","redis":"# redis.conf — enable TLS, close the plain port:\nport 0\ntls-port 6379\ntls-cert-file /etc/redis/redis.crt\ntls-key-file  /etc/redis/redis.key\ntls-ca-cert-file /etc/redis/ca.crt","elasticsearch":"# elasticsearch.yml — HTTP and transport TLS:\nxpack.security.http.ssl.enabled: true\nxpack.security.http.ssl.keystore.path: http.p12\nxpack.security.transport.ssl.enabled: true\nxpack.security.transport.ssl.verification_mode: certificate\nxpack.security.transport.ssl.keystore.path: transport.p12"}},{"title":"授予最小权限角色——避免过宽的角色","detail":"只给每个用户其工作所需的访问权限。使用限定于特定数据库、键模式或索引的细粒度角色，而不是过宽/管理类角色。","engines":{"mongodb":"// Instead of broad readWrite, grant only on the required DB;\n// define a custom role if needed:\ndb.createRole({ role: \"ordersRW\",\n  privileges: [ { resource: { db: \"appdb\", collection: \"orders\" },\n    actions: [ \"find\", \"insert\", \"update\" ] } ], roles: [] })\ndb.grantRolesToUser(\"app\", [ { role: \"ordersRW\", db: \"appdb\" } ])","redis":"# Limit by key pattern and command category with ACLs:\nACL SETUSER app on >AppPassword456! ~app:* +@read +@write -@dangerous\n# Only keys with the 'app:' prefix, read/write; dangerous commands forbidden.\nACL LIST\nACL WHOAMI","elasticsearch":"# Define a fine-grained role (read-only on specific indices):\nPOST /_security/role/orders_read\n{ \"indices\": [ { \"names\": [ \"orders-*\" ],\n    \"privileges\": [ \"read\" ] } ] }\n# Assign it to the user:\nPOST /_security/user/app\n{ \"password\": \"...\", \"roles\": [ \"orders_read\" ] }"}},{"title":"启用审计/日志——监控访问和变更","detail":"必须记录未经授权的访问尝试和管理操作。启用审计日志并将其发送到集中位置。","engines":{"mongodb":"# Auditing (requires MongoDB Enterprise) — mongod.conf:\nauditLog:\n  destination: file\n  format: JSON\n  path: /var/log/mongodb/audit.json\n# Filter: capture events such as authenticate, createUser, dropDatabase.","redis":"# Redis has no full audit; use ACL LOG to see rejected\n# authentication/command attempts:\nACL LOG\nACL LOG RESET\n# Also: route the loglevel notice and logfile settings to a central collector.","elasticsearch":"# elasticsearch.yml — enable security auditing:\nxpack.security.audit.enabled: true\nxpack.security.audit.logfile.events.include:\n  [ authentication_failed, access_denied, connection_denied ]\n# Records are written to the <cluster>_audit.json file."}}],"related":["list-users","create-user-grant","enable-audit","change-password"]}]},"playbooks/cloud.json":{"schemaVersion":1,"playbooks":[{"id":"cloud-managed-db","title":"云托管数据库（RDS/Aurora/Cloud SQL/Atlas/Azure SQL）","icon":"☁️","symptom":"目标：了解云托管数据库与自行管理安装之间的关键差异；理解备份、参数、故障转移、监控、升级和连接在云中如何运作。","steps":[{"title":"思维转变：你访问的是服务，而不是服务器","detail":"使用托管数据库时，你无法通过 shell 访问操作系统、文件系统或数据库进程；不能 SSH 登录、手动编辑配置文件或成为超级用户。你只能使用提供商的管理 API/控制台和普通的 SQL 连接。AWS RDS/Aurora：管理在资源（实例/集群）级别进行，服务器是隐藏的。GCP Cloud SQL：只有实例 API，没有操作系统。MongoDB Atlas：完全托管的集群，无法访问 mongod。Azure SQL Database：PaaS，底层的服务器和操作系统都不可见（Managed Instance 提供稍多的控制）。因此，大多数自行管理时的习惯做法，如“查看文件”、“终止进程”或“运行操作系统工具”，都不再适用。"},{"title":"备份：自动快照 + 时间点恢复（PITR）","detail":"你无需安排备份；提供商会自动执行。恢复通常是“还原到特定时间点”（PITR）或从快照创建新实例；现有实例不会被覆盖。AWS RDS/Aurora：自动备份 + 保留窗口；精确到秒的 PITR 还原会以新实例/集群的形式打开；也可以手动创建快照。GCP Cloud SQL：自动备份 + 基于 binary log 的 PITR；还原到新实例。MongoDB Atlas：持续云备份 + PITR（M10+）；可从快照还原。Azure SQL：内置自动备份，保留 1-35 天，可 PITR 到任意时间点并还原已删除的数据库。通用规则：核实备份配置（窗口、保留期），并定期进行还原测试。"},{"title":"参数：参数组 / 标志，而不是配置文件","detail":"你不再手动编辑 postgresql.conf 或 my.cnf，而是使用提供商的参数抽象；有些参数被锁定，有些需要重启（静态参数）。AWS RDS/Aurora：通过 DB parameter group（以及 cluster parameter group）设置；动态参数立即生效，静态参数在重启后生效。GCP Cloud SQL：通过控制台/gcloud 设置 'database flags'。MongoDB Atlas：有限的 'Additional Settings' + oplog 大小等；大多数内部参数由平台管理。Azure SQL：大多数服务器级设置由平台管理；通过 SCOPED CONFIGURATION 和服务层级的选择来改变行为。应用之前，先确认参数是动态的还是静态的。"},{"title":"高可用 / 故障转移：托管的 Multi-AZ 和副本","detail":"你无需自己构建故障转移；只需勾选一个选项，交给提供商即可。备库和自动故障转移由平台管理，连接通过稳定的端点重定向到新的主库。AWS RDS：Multi-AZ（同步备库，自动故障转移）；Aurora：多副本存储 + 自动故障转移，cluster endpoint 指向写入节点。GCP Cloud SQL：一键配置 HA（区域级，备库位于另一个可用区）；只读副本需单独添加。MongoDB Atlas：内置副本集，支持多区域/多云部署，自动故障转移。Azure SQL：内置 HA（Business Critical 中的本地副本）、区域冗余选项以及跨区域的活动异地复制 / 故障转移组。应用必须始终连接到端点，而不是固定 IP。"},{"title":"监控：提供商的指标和日志服务","detail":"由于无法运行操作系统工具（top、iostat），你需要通过提供商的指标平台进行监控；CPU、连接数、IOPS、复制延迟和存储指标都在那里。AWS：CloudWatch 指标 + Enhanced Monitoring + Performance Insights（查询级分析）。GCP Cloud SQL：Cloud Monitoring（原 Stackdriver）+ Query Insights。MongoDB Atlas：内置的 Atlas 指标/图表 + Performance Advisor + 实时面板。Azure SQL：Azure Monitor + SQL Analytics + Query Performance Insight。务必设置阈值告警（存储使用率、CPU、连接数上限、副本延迟）。"},{"title":"版本升级与维护：通过控制台，在维护窗口内进行","detail":"你无需手动更新二进制文件，而是选择目标版本；小版本补丁大多是自动的，而大版本升级需要规划，通常伴随停机/HA 故障转移。AWS RDS/Aurora：引擎版本选择 + 自动小版本升级选项 + 借助 Blue/Green Deployments 进行低风险的大版本升级。GCP Cloud SQL：维护窗口 + 自助式大版本升级。MongoDB Atlas：一键版本升级，以滚动方式实现近乎零停机。Azure SQL：由平台自动打补丁（版本的概念在很大程度上被抽象掉了）；通过兼容级别控制行为。务必先在测试/预发布副本上尝试，并将维护窗口安排在业务时间之外。"},{"title":"连接与安全：端点 + IAM/TLS","detail":"连接始终通过提供商提供的 DNS 端点进行，并且是加密的（TLS）；网络访问和身份可以与云身份系统关联。AWS RDS/Aurora：实例/集群端点 + 用于网络的 Security Group + 可选的 IAM 数据库认证 + TLS（rds-ca 证书）。GCP Cloud SQL：公有 IP + Authorized Networks 或私有 IP/VPC、Cloud SQL Auth Proxy、IAM 数据库认证、TLS。MongoDB Atlas：SRV 连接字符串（mongodb+srv://）、IP 访问列表 + private endpoint/peering、SCRAM 或 IAM/X.509、强制 TLS。Azure SQL：服务器 FQDN 端点 + 防火墙/Private Link + Microsoft Entra ID（原 Azure AD）认证 + 强制 TLS。规则：在连接字符串中使用端点而不是固定 IP，强制使用 TLS，并尽可能使用云 IAM 身份连接。"}],"related":["last-backup-status","promote-failover","connect-cli"]}]},"playbooks/automation.json":{"schemaVersion":1,"playbooks":[{"id":"auto-ansible","title":"使用 Ansible 实现数据库自动化","icon":"🤖","symptom":"目标：使用 Ansible 使数据库安装、配置、用户和备份任务可重复且幂等（重复运行同一个 playbook 不会破坏系统）。","steps":[{"title":"定义 inventory 和组","detail":"在 inventory 中按数据库角色对服务器分组；将主库、副本和备份服务器放在不同的组中。使用静态 INI/YAML inventory，或针对云环境使用动态 inventory（aws_ec2、gcp_compute）。组变量放在 group_vars/ 下，主机特定变量放在 host_vars/ 下。","engines":{"postgres":"[pg_primary]\ndb1.example.local\n\n[pg_replica]\ndb2.example.local\n\n[postgres:children]\npg_primary\npg_replica"}},{"title":"安装正确的 collection（community.postgresql / community.mysql / community.mongodb）","detail":"Ansible core 现在将数据库模块放在独立的 collection 中发布；通过 requirements.yml 安装对应引擎的 collection。PostgreSQL 使用 community.postgresql（postgresql_db、postgresql_user、postgresql_privs），MySQL/MariaDB 使用 community.mysql（mysql_db、mysql_user），MongoDB 使用 community.mongodb。目标主机上必须安装相应的 Python 客户端（psycopg2、PyMySQL、pymongo）。","engines":{"postgres":"# requirements.yml\ncollections:\n  - name: community.postgresql\n\n# install:\nansible-galaxy collection install -r requirements.yml","mysql":"collections:\n  - name: community.mysql","mongodb":"collections:\n  - name: community.mongodb"}},{"title":"用 Ansible Vault 加密机密信息","detail":"不要以明文保存数据库密码、复制密钥和超级用户凭据；用 ansible-vault 将它们保存在加密的变量文件中。运行时通过 --ask-vault-pass 或 --vault-password-file 解密。这样机密信息可以安全地存放在 Git 中，playbook 也保持幂等。","engines":{"postgres":"ansible-vault create group_vars/postgres/vault.yml\n# contents:\nvault_pg_admin_password: \"S3cret!\"\n\n# run:\nansible-playbook site.yml --ask-vault-pass"}},{"title":"建立角色结构（roles/postgres、roles/mysql）","detail":"为每种引擎创建一个角色，并将逻辑拆分到 tasks/、defaults/、templates/ 和 handlers/ 子目录中。这种结构提高了可重用性：defaults/main.yml 存放默认值，templates/ 存放 jinja2 配置模板，handlers/ 存放“仅在有变更时才重启”的逻辑。可以用 ansible-galaxy init roles/postgres 生成角色骨架。","engines":{"postgres":"ansible-galaxy init roles/postgres\n# roles/postgres/\n#   defaults/main.yml\n#   tasks/main.yml\n#   templates/postgresql.conf.j2\n#   handlers/main.yml"}},{"title":"以幂等方式编写安装和配置任务","detail":"安装软件包、初始化数据目录，并通过模板生成 postgresql.conf/my.cnf；配置变更时通过 handler 重启服务。模块是幂等的：已安装的软件包不会重复安装，文件未变化时 handler 不会被触发。用 template 模块渲染配置模板，并通过 'notify' 将其与 handler 关联。","engines":{"postgres":"- name: postgresql.conf template\n  ansible.builtin.template:\n    src: postgresql.conf.j2\n    dest: /etc/postgresql/16/main/postgresql.conf\n  notify: restart postgresql\n\n# handlers/main.yml\n- name: restart postgresql\n  ansible.builtin.service:\n    name: postgresql\n    state: restarted","mysql":"- name: my.cnf template\n  ansible.builtin.template:\n    src: my.cnf.j2\n    dest: /etc/mysql/my.cnf\n  notify: restart mysql"}},{"title":"定义数据库和用户任务","detail":"用模块创建数据库、用户和权限；密码取自 Vault 变量。community.postgresql 模块是幂等的：若用户/数据库已存在，不会重新创建，只应用差异部分。用 postgresql_privs 单独授予权限；在 MySQL 中使用 mysql_user 的 priv 字段。","engines":{"postgres":"- name: application database\n  community.postgresql.postgresql_db:\n    name: appdb\n    state: present\n\n- name: application user\n  community.postgresql.postgresql_user:\n    name: app_user\n    password: \"{{ vault_app_password }}\"\n    db: appdb\n    state: present","mysql":"- name: application user\n  community.mysql.mysql_user:\n    name: app_user\n    password: \"{{ vault_app_password }}\"\n    priv: 'appdb.*:ALL'\n    state: present"}},{"title":"添加备份任务和调度","detail":"通过模板部署备份脚本，并用 ansible.builtin.cron 进行调度；这样备份作业也作为代码进行管理。将备份目标目录、保留期和调度设为变量。cron 模块是幂等的，通过 'name' 识别条目，因此重复运行不会产生重复条目。","engines":{"postgres":"- name: nightly backup (cron)\n  ansible.builtin.cron:\n    name: \"pg dump nightly\"\n    minute: \"0\"\n    hour: \"2\"\n    job: \"pg_dump -Fc appdb > /backup/appdb_$(date +\\\\%F).dump\""}}],"related":["schedule-job","check-version","logical-dump"]},{"id":"auto-terraform","title":"使用 Terraform 配置数据库（IaC）","icon":"🏗️","symptom":"目标：使用 Terraform 将数据库基础设施（实例、参数组、网络）定义为代码（基础设施即代码）；建立 plan/apply 流程、远程 state 和漂移管理。","steps":[{"title":"选择并配置 provider（aws / google / azurerm）","detail":"为你所使用的云定义 provider 并固定其版本。在 required_providers 块中，AWS 锁定 hashicorp/aws provider，GCP 锁定 hashicorp/google，Azure 锁定 hashicorp/azurerm。固定版本可确保团队内行为一致。","engines":{"postgres":"terraform {\n  required_providers {\n    aws = {\n      source  = \"hashicorp/aws\"\n      version = \"~> 5.0\"\n    }\n  }\n}\n\nprovider \"aws\" {\n  region = \"eu-central-1\"\n}"}},{"title":"设置远程 state 和锁定","detail":"不要将 state 文件保存在本地；为团队协作和安全起见，使用远程 backend。常见选择是 AWS 上的 S3 backend + DynamoDB 锁表、GCP 上的 gcs backend 以及 Azure 上的 azurerm backend（Storage Account）。远程 state 通过锁定防止并发 apply，并使 state 成为唯一可信来源。由于 state 中可能以明文保存密码，backend 必须加密并限制访问。","engines":{"postgres":"terraform {\n  backend \"s3\" {\n    bucket         = \"company-tfstate\"\n    key            = \"db/terraform.tfstate\"\n    region         = \"eu-central-1\"\n    dynamodb_table = \"tf-lock\"\n    encrypt        = true\n  }\n}"}},{"title":"定义数据库资源","detail":"为你的引擎和云编写主资源：AWS 上为 aws_db_instance（Aurora 为 aws_rds_cluster），GCP 上为 google_sql_database_instance，Azure 上为 azurerm_mssql_database（对于 SQL Server，位于 azurerm_mssql_server 之下）。在此指定引擎、版本、规格、存储和备份窗口。","engines":{"postgres":"resource \"aws_db_instance\" \"pg\" {\n  identifier        = \"app-db\"\n  engine            = \"postgres\"\n  engine_version    = \"16\"\n  instance_class    = \"db.t3.medium\"\n  allocated_storage = 50\n  db_name           = \"appdb\"\n  username          = \"pgadmin\"\n  password          = var.db_password\n  backup_retention_period = 7\n}","mssql":"resource \"azurerm_mssql_database\" \"db\" {\n  name      = \"appdb\"\n  server_id = azurerm_mssql_server.srv.id\n  sku_name  = \"S1\"\n}"}},{"title":"将参数组写成代码","detail":"用参数资源而不是在控制台中手动定义数据库设置（max_connections、work_mem、字符集）。AWS 上使用 aws_db_parameter_group，GCP 上使用 google_sql_database_instance 中的 settings.database_flags，Azure 上使用服务器配置资源。这样设置就有了版本控制且可重现。","engines":{"postgres":"resource \"aws_db_parameter_group\" \"pg\" {\n  name   = \"app-pg16\"\n  family = \"postgres16\"\n\n  parameter {\n    name  = \"max_connections\"\n    value = \"200\"\n  }\n}\n# inside aws_db_instance:\n# parameter_group_name = aws_db_parameter_group.pg.name"}},{"title":"通过变量和密钥管理器传递机密信息","detail":"不要在 .tf 文件中硬编码密码；将其定义为敏感变量（variable ... sensitive = true），并通过 TF_VAR_ 环境变量、密钥管理器（AWS Secrets Manager、Vault）或 CI 机密提供其值。请记住：该值会写入 state 文件，因此远程 state 必须加密。最干净的做法是永远不将密码传入 Terraform，而是从密钥管理器资源中读取。","engines":{"postgres":"variable \"db_password\" {\n  type      = string\n  sensitive = true\n}\n\n# CI/shell:\nexport TF_VAR_db_password=\"$(vault kv get -field=pw secret/db)\""}},{"title":"执行 plan 和 apply 流程","detail":"先用 terraform init 准备 provider/backend，用 terraform plan 查看将要变更的内容，批准后用 terraform apply 应用。不要在未审查 plan 输出的情况下 apply；CI 中的安全流程是将 'plan' 作为 PR 评论发布，经人工批准后再 apply。通过 plan 输出中的 -/+ 标记识别破坏性变更（例如实例重建）。","engines":{"postgres":"terraform init\nterraform plan -out=plan.tfplan\nterraform apply plan.tfplan"}},{"title":"检测并修复漂移","detail":"若有人在控制台中手动更改，实际基础设施与 state 就会出现偏离（漂移）。在“本不应有任何变更”时 terraform plan 中出现的差异揭示了漂移；差异通过 terraform refresh/plan 读取。要么将变更反映回代码，要么用 apply 将基础设施拉回到代码定义的状态。原则上禁止手动更改；代码应是唯一可信来源。","engines":{"postgres":"# drift check (when no changes are expected):\nterraform plan\n# expect 'No changes'; any diff means drift\n# bring an existing resource into state with import:\nterraform import aws_db_instance.pg app-db"}}],"related":["check-version","apply-patch","schedule-job"]},{"id":"auto-k8s-operator","title":"Kubernetes 上的数据库（operator）","icon":"☸️","symptom":"目标：借助 operator 在 Kubernetes 上运行数据库；使用 operator 提供的自动故障转移、持久存储和备份功能，而不是单纯的 StatefulSet。","steps":[{"title":"为什么需要 operator：仅有 StatefulSet 是不够的","detail":"普通的 StatefulSet 为你提供具有稳定标识的 pod 和 PVC，但不包含故障转移、复制搭建、备份和升级的逻辑。operator 将这些数据库特有的运维知识打包为 controller + 自定义资源（CRD）。你编写一个名为 'Cluster' 的 CR，operator 就会管理其背后的 StatefulSet、Service、Secret 和备份作业。在生产环境中，通常首选用 operator 运行数据库。","engines":{"postgres":"# operators for PostgreSQL:\n#  - CloudNativePG (CNPG)\n#  - Zalando postgres-operator\n#  - Crunchy PGO"}},{"title":"选择并安装 operator","detail":"为你的引擎选择 operator：PostgreSQL 可选 CloudNativePG 或 Zalando postgres-operator；MySQL 和 MongoDB 常用 Percona 的 operator（Percona Operator for MySQL / for MongoDB）。用 Helm 或 bundle 清单安装 operator；这一步会向集群添加 CRD 和 controller pod。","engines":{"postgres":"# CloudNativePG installation\nkubectl apply --server-side -f \\\n  https://raw.githubusercontent.com/cloudnative-pg/cloudnative-pg/main/releases/cnpg-1.24.0.yaml","mysql":"# Percona Operator for MySQL (Helm)\nhelm install my-op percona/pxc-operator","mongodb":"# Percona Operator for MongoDB (Helm)\nhelm install psmdb-op percona/psmdb-operator"}},{"title":"规划存储（StorageClass / PVC）","detail":"数据库状态保存在 PVC 中；在 CR 中指定要使用的 StorageClass。选择提供低延迟块存储的 StorageClass，最好是接近本地的 SSD；大多数数据库不建议使用网络文件系统（NFS）。从一开始就正确设置 PVC 大小和可扩展性（allowVolumeExpansion），因为缩容很困难。","engines":{"postgres":"spec:\n  storage:\n    storageClass: fast-ssd\n    size: 50Gi"}},{"title":"应用 Cluster CR（副本与自动故障转移）","detail":"在 CR 中指定实例/副本数；operator 会搭建同步/异步复制，并在主库故障时通过提升副本执行自动故障转移。应用应连接到 operator 创建的 '-rw'（读写）Service；该服务始终指向当前主库。因此故障转移期间连接目标不会改变。","engines":{"postgres":"apiVersion: postgresql.cnpg.io/v1\nkind: Cluster\nmetadata:\n  name: app-db\nspec:\n  instances: 3\n  storage:\n    storageClass: fast-ssd\n    size: 50Gi\n# the application connects to: app-db-rw"}},{"title":"通过 CR 将备份写入 S3","detail":"operator 也以声明式方式管理备份：你在 CR 中定义主备份目标（对象存储/S3）和 WAL 归档。在 CloudNativePG 中，S3 信息通过 spec.backup.barmanObjectStore 指定（自 1.26 起已弃用；新部署请使用 Barman Cloud Plugin 及其 ObjectStore 资源）；持续的 WAL 归档 + 基础备份可实现时间点恢复。凭据从 Kubernetes Secret 中引用。","engines":{"postgres":"spec:\n  backup:\n    barmanObjectStore:\n      destinationPath: s3://company-backup/app-db\n      s3Credentials:\n        accessKeyId:\n          name: s3-creds\n          key: ACCESS_KEY_ID\n        secretAccessKey:\n          name: s3-creds\n          key: SECRET_ACCESS_KEY"}},{"title":"定时备份与还原 CR","detail":"一次性备份创建 Backup CR，定时备份创建 ScheduledBackup CR；用 cron 表达式定期执行基础备份。还原同样是声明式的：通过 'bootstrap.recovery' 从现有的对象存储备份引导一个新的 Cluster。定期进行还原演练；只做备份是不够的，要测试恢复。","engines":{"postgres":"apiVersion: postgresql.cnpg.io/v1\nkind: ScheduledBackup\nmetadata:\n  name: nightly-backup\nspec:\n  schedule: \"0 0 2 * * *\"\n  cluster:\n    name: app-db"}}],"related":["setup-cdc-replication","logical-dump","restore-dump"]},{"id":"auto-schema-migration","title":"Schema 迁移 CI/CD（Flyway/Liquibase）","icon":"🧬","symptom":"目标：用带版本号的迁移文件管理数据库 schema 变更；在 CI/CD 中使用 Flyway 或 Liquibase 自动但可控地应用这些变更。","steps":[{"title":"创建带版本号的迁移文件（Vxxx__name.sql）","detail":"将每个 schema 变更写入一个带递增版本号的命名文件；Flyway 的约定是 V<version>__<description>.sql（例如 V1__initial_schemas.sql、V2__orders_table.sql）。这些文件保存在 Git 中，并按版本顺序应用。已应用的迁移会记录在跟踪表中（Flyway 中为 flyway_schema_history），因此每个迁移只运行一次。","engines":{"postgres":"-- V2__orders_table.sql\nCREATE TABLE orders (\n  id       bigserial PRIMARY KEY,\n  amount    numeric(12,2) NOT NULL,\n  created_at   timestamptz DEFAULT now()\n);"}},{"title":"用校验和保证不可变性","detail":"Flyway/Liquibase 会存储每个已应用迁移的校验和。若之后编辑已应用的文件，校验和将不再匹配，工具会报错。规则：切勿编辑已应用的迁移；用新的版本文件进行修正。这保证所有环境（dev/staging/prod）拥有相同的历史。","engines":{"postgres":"# repair on checksum mismatch (use with care):\nflyway repair\n# the right way: add the change in a new Vx file"}},{"title":"为现有数据库设置基线（baseline）","detail":"将迁移工具接入已有数据但没有迁移历史的数据库时，使用 baseline；它将当前状态视为“起始版本”，不会尝试重新创建现有对象。baselineVersion 之后的迁移会被应用。在从零构建的环境中无需 baseline；迁移从 V1 开始运行。","engines":{"postgres":"flyway baseline -baselineVersion=1 -baselineDescription=\"existing schema\""}},{"title":"在 CI 流水线中自动迁移","detail":"在 CI 步骤中运行迁移工具，将变更自动应用到测试和预发布数据库；migrate 命令只运行新的（未应用的）文件，且是幂等的。先将迁移应用到临时/一次性数据库并进行验证，可在有问题的 SQL 到达生产环境之前将其拦截。schema 必须在部署应用代码之前完成迁移（采用向后兼容的变更）。","engines":{"postgres":"# CI step (Flyway)\nflyway -url=jdbc:postgresql://staging/appdb \\\n  -user=ci -password=$PW migrate","mysql":"flyway -url=jdbc:mysql://staging/appdb \\\n  -user=ci -password=$PW migrate"}},{"title":"为生产环境添加人工审批关卡","detail":"测试/预发布环境自动执行，但生产环境的 migrate 应要求人工审批；在 CI/CD 中添加“审批关卡”（人工审批）步骤。审批之前，用 'flyway info' 或 'liquibase status' 展示将要运行哪些迁移。应用到生产环境之前务必做一次新的备份，以便出问题时可以回退。","engines":{"postgres":"# what will run before approval?\nflyway info\n# liquibase equivalent:\nliquibase status --verbose"}},{"title":"定义可逆性和回退策略","detail":"Flyway Community 没有自动 undo；应采用只向前迁移的原则：不是撤销错误，而是编写一个新的迁移来修正它。Liquibase changeset 可以定义 rollback 块，并用 'liquibase rollback' 回退。最稳妥的策略：保持变更小而向后兼容，在高风险操作（删除列）之前做备份，并准备书面的回退方案。","engines":{"postgres":"-- Liquibase rollback example (inside a changeset)\n--changeset dev:2\nCREATE TABLE orders (...);\n--rollback DROP TABLE orders;"}}],"related":["apply-patch","logical-dump","check-version"]},{"id":"auto-backup","title":"自动化备份（pgBackRest/Barman/Xtrabackup/cron）","icon":"💾","symptom":"目标：自动化数据库备份；通过定时的完整+增量+WAL/binlog 归档、保留策略、验证和异地副本，构建可靠的恢复链。","steps":[{"title":"选择合适的备份工具","detail":"为你的引擎选择工具：PostgreSQL 可选 pgBackRest 或 Barman（物理备份 + WAL 归档 + PITR）；MySQL/MariaDB 可选 Percona XtraBackup（在线物理备份），小数据集可用 mysqldump/mydumper（逻辑备份）。物理工具在大型数据库上提供快速、一致的备份 + 时间点恢复；逻辑导出可移植，但在大数据量下较慢。","engines":{"postgres":"# pgBackRest or Barman","mysql":"# physical: xtrabackup\n# logical: mysqldump / mydumper"}},{"title":"构建完整 + 增量 + 持续归档链","detail":"通过每周/每天的完整备份、其间的增量/差异备份，以及持续的 WAL（PostgreSQL）或 binlog（MySQL）归档，实现时间点恢复。pgBackRest 管理 full/diff/incremental 备份类型和 WAL 归档；在 PostgreSQL 中，将 archive_command 指向 pgBackRest。增量备份 + 归档使你可以回到任意时间点。","engines":{"postgres":"# postgresql.conf\narchive_mode = on\narchive_command = 'pgbackrest --stanza=appdb archive-push %p'\n\n# full backup\npgbackrest --stanza=appdb --type=full backup\n# incremental\npgbackrest --stanza=appdb --type=incr backup","mysql":"# full\nxtrabackup --backup --target-dir=/backup/full\n# incremental\nxtrabackup --backup --target-dir=/backup/incr1 \\\n  --incremental-basedir=/backup/full"}},{"title":"用 cron/timer 调度备份","detail":"用 cron 或 systemd timer 定期运行备份命令：例如周日晚上做完整备份，其他晚上做增量备份。安排在业务时间之外，并确保 full 和 incr 作业不会重叠。将输出日志写到某处，并在失败时发出告警。","engines":{"postgres":"# crontab\n0 1 * * 0 pgbackrest --stanza=appdb --type=full backup\n0 1 * * 1-6 pgbackrest --stanza=appdb --type=incr backup"}},{"title":"定义保留策略","detail":"不要无限累积备份；设置要保留多少份完整备份和多少天的归档，并让工具自动使旧备份过期。在 pgBackRest 中通过 repo1-retention-full 和归档保留设置管理；在 Barman 中通过 retention_policy 管理。保留策略必须与恢复目标相匹配（需要回溯多少天）。","engines":{"postgres":"# pgbackrest.conf\n[global]\nrepo1-retention-full=4\nrepo1-retention-archive=8","mysql":"# purge old backups with cron (example)\nfind /backup -type d -mtime +14 -exec rm -rf {} +"}},{"title":"将备份复制到异地 / S3","detail":"备份不能与数据库位于同一台机器/同一区域；应将其复制到其他位置或对象存储（S3、GCS）。pgBackRest 可以直接写入兼容 S3 的存储；否则用 rsync/rclone 同步到异地目标。发生灾难（本地磁盘/区域丢失）时，只有异地副本才能救你。","engines":{"postgres":"# pgbackrest.conf S3 repository\n[global]\nrepo1-type=s3\nrepo1-s3-bucket=company-backup\nrepo1-s3-region=eu-central-1\nrepo1-s3-endpoint=s3.eu-central-1.amazonaws.com"}},{"title":"验证备份","detail":"备份存在并不意味着能够还原；应定期验证完整性。在 pgBackRest 中，'verify' 命令和归档一致性检查，以及在 XtraBackup 中成功执行 prepare（--prepare）步骤，都表明备份是完好的。每次备份后自动运行验证，失败时发出告警。","engines":{"postgres":"pgbackrest --stanza=appdb verify","mysql":"# make the backup consistent (verification)\nxtrabackup --prepare --target-dir=/backup/full"}},{"title":"定期进行还原演练","detail":"最关键的一步：定期将备份真正还原到另一台服务器上，并验证数据库能够打开且数据一致。这种演练让你能够衡量恢复时间（RTO），并在事故发生之外发现流程中的漏洞。未经测试的备份不算备份。","engines":{"postgres":"# restore + PITR on a separate server\npgbackrest --stanza=appdb \\\n  --type=time --target=\"2026-07-01 03:00:00\" restore","mysql":"# copy the prepared backup back to the data directory\nxtrabackup --copy-back --target-dir=/backup/full"}}],"related":["logical-dump","restore-dump","schedule-job"]},{"id":"auto-gitops-bluegreen","title":"GitOps 与蓝绿 / 零停机部署","icon":"🟦","symptom":"目标：在不停机的情况下部署 schema 和应用变更；通过 expand-contract 迁移、蓝绿环境、功能开关和自动回滚，接近零停机。","steps":[{"title":"GitOps：让 Git 成为唯一可信来源","detail":"在 Git 中定义基础设施、schema 迁移和应用版本；部署由合并到仓库触发（GitOps）。协调器（Argo CD/Flux 或 CI/CD）将期望状态应用到实际环境。这样每个变更都可审计、可重复，并在需要时可以回退到之前的提交。","engines":{"postgres":"# repo/\n#   migrations/  (Vxxx__*.sql)\n#   k8s/         (Deployment, Service)\n#   flags/       (feature flag config)"}},{"title":"以向后兼容的方式更改 schema（expand-contract）","detail":"为实现零停机，不要一步完成破坏性的 schema 变更；将其拆分为两个阶段。EXPAND：添加新结构（新列/新表），使旧代码和新代码可以同时运行（可空列、带默认值、双写）。在应用迁移到新结构之后，CONTRACT：在单独的迁移中删除不再使用的旧列/旧表。这样在部署期间，新旧应用版本可以并行运行而不出问题。","engines":{"postgres":"-- EXPAND (V5): backward-compatible addition\nALTER TABLE customer ADD COLUMN email text;\n\n-- for a while the code writes/reads both columns\n\n-- CONTRACT (V9): drop the old column\nALTER TABLE customer DROP COLUMN mail_old;"}},{"title":"将高风险行为放在功能开关之后","detail":"将新的代码路径（例如读取新列）放在功能开关之后有条件地运行；schema 就绪后打开开关，出现问题时无需改动部署即可关闭。开关将代码部署（deploy）与功能启用（release）分离开来。expand 阶段已发布，但行为变更以受控方式启用，并可快速回退。","engines":{"postgres":"-- application layer (pseudocode)\n-- if flag('new_email_field'):\n--     SELECT email FROM customer ...\n-- else:\n--     SELECT mail_old FROM customer ..."}},{"title":"搭建蓝绿环境","detail":"保持两套相同的环境：'blue'（线上）和 'green'（新版本）。将新的应用版本部署到 green，并在其接收真实流量之前进行验证；就绪后，将路由器/流量切换到 green。关键点：数据库通常是共享的，因此 schema 必须同时兼容两个版本（expand-contract 可确保这一点）。即使切换时连接目标发生变化，由于 schema 兼容，也不会停机。","engines":{"postgres":"# traffic is switched to green (new version)\n# shared DB: schema must be compatible with both blue and green"}},{"title":"通过监控验证部署（金丝雀/观察）","detail":"在切换全部流量之前，监控错误率、延迟、数据库错误/锁数量和复制延迟等指标；可选地将一小部分流量（金丝雀）发送到 green 并观察。若健康指标良好，逐步增加流量；若不佳，则不要继续。监控是自动回滚决策的输入。","engines":{"postgres":"-- live health queries\nSELECT count(*) FROM pg_stat_activity WHERE wait_event_type='Lock';\nSELECT * FROM pg_stat_replication;"}},{"title":"准备自动回滚","detail":"若指标超过阈值，部署必须自动回滚：在蓝绿部署中，迅速将流量切回 blue（旧版本）。由于 blue 仍在运行，应用回滚是即时的。在 schema 方面，通常不需要回滚：得益于 expand-contract，新 schema 也兼容旧代码，因此回滚应用就足够了。这就是为什么 schema 变更应独立于应用部署，并始终保持向后兼容。","engines":{"postgres":"# traffic instantly returns to blue (application rollback)\n# schema is not rolled back: expand-contract is compatible with old code\n# if truly needed: write a new corrective migration"}}],"related":["setup-cdc-replication","apply-patch","check-version"]}]},"install/index.json":{"schemaVersion":1,"categories":[{"id":"install","title":"安装"},{"id":"ha","title":"高可用与复制"},{"id":"backup","title":"备份与恢复"},{"id":"ops","title":"日常运维与故障"},{"id":"pooling","title":"连接与负载均衡"},{"id":"patch","title":"补丁与小版本更新"},{"id":"upgrade","title":"大版本升级与迁移"},{"id":"monitoring","title":"监控与管理"},{"id":"security","title":"安全与加固"}],"guides":[{"id":"pg-install","file":"install/guides/pg-install.json","group":"PostgreSQL","engineId":"postgres","category":"install","title":"PostgreSQL 单服务器安装（可用于生产）","summary":"从 PGDG 仓库在单台服务器上安装可用于生产的 PostgreSQL：独立的数据目录、data checksums、按 RAM/CPU 计算的参数、安全访问（peer + scram-sha-256）、日志、pg_stat_statements、防火墙和 SELinux。完成后您将得到一个运行中且已验证的集群，以及一份备份/监控检查清单。","level":"入门","duration":"30–45 分钟","topology":"1 台服务器","tags":["postgresql","postgres","安装","install","pgdg","initdb","tuning","rhel","ubuntu"],"order":1,"steps":23,"scenarios":[]},{"id":"pg-streaming","file":"install/guides/pg-streaming.json","group":"PostgreSQL","engineId":"postgres","category":"ha","title":"PostgreSQL 流复制（主库 + 备库）","summary":"使用 pg_basebackup 为已安装的主库添加一个或多个 hot standby（复制用户、pg_hba、复制槽、异步或同步 ANY/FIRST 模式、延迟监控），并生成这个无 Patroni 集群的运维手册：计划内 switchover、手动 failover（pg_promote）、用 pg_rewind 将旧主库重新加入、重建备库、级联备库和延迟备库、复制槽清理、修改同步配置。Failover 不是自动的，由 DBA 执行；","level":"中级","duration":"45–90 分钟","topology":"1 个主库 + 1..N 个备库","tags":["postgresql","流复制","复制","备库","hot standby","pg_basebackup","复制槽","synchronous","同步复制","failover","switchover","pg_promote","pg_rewind","高可用","级联","延迟备库","recovery_min_apply_delay","复制槽清理","pg_wal 满","synchronous_standby_names","重建备库"],"order":2,"steps":24,"scenarios":[{"k":"mode","v":"install","l":"部署：为主库添加备库","m":"添加 / 重建备库 搭建流复制 添加备库 hot standby pg_basebackup read replica 只读副本 physical replication 物理复制 standby.signal primary_conninfo 使用 pg_basebackup 为已安装的主库添加一个或多个 hot standby（复制槽、同步/异步、监控）。主库保持运行；初始复制会给主库带来磁盘和网络负载。"},{"k":"mode","v":"switchover","l":"计划内 switchover（角色切换，无数据丢失）","m":"角色切换 switchover planned switchover 角色切换 计划内切换 主库维护 将备库提升为主库 pg_promote pg_ctl promote 计划内维护：干净地关闭主库，提升第一个备库，旧主库作为备库重新加入。无数据丢失；写入中断通常不到 1 分钟。"},{"k":"mode","v":"failover","l":"手动 failover（主库宕机，提升备库）","m":"角色切换 could not connect to the primary server Connection refused primary down 主库宕机 主库无法访问 failover manual failover promote standby pg_promote split-brain could not receive data from WAL stream 主库丢失 主库宕机或无法访问：隔离旧主库（fencing），手动提升数据最新的备库。异步模式下最后的事务可能丢失。"},{"k":"mode","v":"rejoin","l":"用 pg_rewind 将旧主库作为备库重新加入","m":"角色切换 requested timeline is not a child of this server's history target server must be shut down cleanly could not find previous WAL record pg_rewind diverged timeline 旧主库重新加入 rejoin old primary Failover 之后，用 pg_rewind 将旧主库与新主库重新同步，并作为备库重新加入；比完整复制快得多。"},{"k":"mode","v":"cascade","l":"添加从备库获取 WAL 的新备库（级联）","m":"添加 / 重建备库 cascading replication cascade standby 级联复制 备库的备库 远程数据中心 容灾备库 新备库从第一个备库而不是主库接收 WAL：在远程数据中心或多备库架构中，可降低主库的网络/WAL sender 负载。主库不受影响。"},{"k":"mode","v":"delayed","l":"搭建延迟（delayed）备库","m":"添加 / 重建备库 recovery_min_apply_delay 延迟备库 延迟备库 误删 撤销 DROP TABLE accidental delete pg_wal_replay_pause 数小时后才应用 WAL 的备库（`recovery_min_apply_delay`）：用于在不从备份恢复的情况下找回误删的数据。它不是 failover 候选。"},{"k":"mode","v":"rebuild","l":"从头重建备库","m":"添加 / 重建备库 requested WAL segment has already been removed could not receive data from WAL stream database system identifier differs wal_status lost exceeds max_slot_wal_keep_size 重建备库 备库损坏 re-clone standby could not start WAL streaming wal_removed 备库落后 备库落后太多、所需 WAL 已被删除或备库已损坏：将数据目录移到一边，从主库重新复制。主库不受影响。"},{"k":"mode","v":"slotclean","l":"pg_wal 增长、磁盘写满（清理未使用的复制槽）","m":"维护与故障排查 No space left on device could not write to file \"pg_wal pg_wal 满 pg_wal 增长 WAL 膨胀 磁盘已满 replication slot inactive max_slot_wal_keep_size pg_wal 目录过大 WAL disk full 找出 WAL 堆积的原因（非活动复制槽、归档失败、wal_keep_size）；删除不再使用的复制槽，回收 WAL 空间。主库保持运行。"},{"k":"mode","v":"syncchg","l":"添加 / 移除同步备库（synchronous_standby_names）","m":"维护与故障排查 canceling wait for synchronous replication synchronous_standby_names COMMIT 挂起 commit hangs sync standby 同步备库 sync_state potential synchronous_commit 按表单中的模式（ANY/FIRST、k）重写同步列表，或改回异步；通过 reload 生效，无需重启。错误的配置可能使 commit 挂起；因此先做检查。"}]},{"id":"pg-patroni","file":"install/guides/pg-patroni.json","group":"PostgreSQL","engineId":"postgres","category":"ha","title":"使用 Patroni + etcd + HAProxy 构建自动 failover 集群","summary":"构建并运维具备自动 failover 的 PostgreSQL 集群：每个节点上的 PostgreSQL 由 Patroni 管理，leader 选举在 3（或 5）成员的 etcd 中进行，应用通过 HAProxy 的写（5000）和读（5001）端口连接，可选经由 keepalived VIP。除安装外，还提供针对已有集群的运维场景：健康检查、switchover、旧 leader 重新加入、reinit、添加/移除节点、edit-","level":"高级","duration":"2–3 小时","topology":"3 个 PostgreSQL + etcd 节点（共用或独立）+ HAProxy（+ VIP）","tags":["postgresql","patroni","etcd","haproxy","keepalived","vip","自动 failover","auto failover","switchover","ha","高可用","cluster","集群","patronictl","dcs","reinit","pg_rewind","edit-config","pause","resume","etcdctl member","quorum","failsafe","添加节点","移除节点","patroni 升级","集群关闭"],"order":3,"steps":40,"scenarios":[{"k":"mode","v":"install","l":"安装：新建 Patroni 集群","m":"安装 patroni 安装 patroni install etcd 安装 haproxy 自动 failover automatic failover 高可用 高可用性 HA 集群 从零构建 etcd + Patroni + HAProxy（+ VIP）集群。服务器上不得已有 PostgreSQL 集群：initdb 由 Patroni 执行（不执行 pg-install）。"},{"k":"mode","v":"ops","l":"运维 runbook：switchover、failover 测试、维护模式、参数修改","m":"检查与演练 failover 测试 failover test 演练 DR drill runbook 在新建集群上依次演练基本操作：状态、switchover、failover 测试（停止 leader，短暂写中断）、维护模式、参数修改。请在维护窗口内执行。"},{"k":"mode","v":"health","l":"集群是否健康？（健康检查）","m":"检查与演练 patronictl list Pending restart Maintenance mode: on start failed patroni health 集群状态 replica lag 复制延迟 timeline 不一致 只读：角色、延迟、timeline、REST API、etcd 健康、复制槽、待重启及维护模式。"},{"k":"mode","v":"switchover","l":"计划内 switchover（将 leader 切换到其他节点）","m":"角色切换 candidate name does not match with sync_standby Switchover failed does not exist in cluster or is tagged as nofailover switchover 切换 leader 角色切换 计划内维护 维护前将 leader 角色受控地切换到某个 replica；写入中断数秒，无数据丢失。"},{"k":"mode","v":"rejoin","l":"发生 failover 后旧 leader 无法重新加入（pg_rewind / reinit）","m":"角色切换 requested timeline is not a child of this server's history could not find previous WAL record pg_rewind: error target server needs to use either data checksums or wal_log_hints start failed diverged 旧 leader failover 之后 pg_rewind: fatal could not find common ancestor timeline history mismatch 崩溃的旧 leader 恢复后应作为 replica 加入；若 pg_rewind 失败，则从 leader 重建数据目录。"},{"k":"mode","v":"reinit","l":"从零重建 replica（reinit）","m":"成员维护 requested WAL segment has already been removed could not receive data from WAL stream invalid record length invalid page patronictl reinit replica 损坏 replica 落后 rebuild replica replica start failed creating replica replica 已损坏、严重落后或丢失了 WAL：删除其数据目录并从 leader（或备份）重建。leader 不受影响。"},{"k":"mode","v":"addmember","l":"向集群添加新节点","m":"成员维护 添加节点 add member 新 replica scale out 集群扩容 将新节点添加到表单列表末尾：安装软件，在现有节点上更新 pg_hba 和 HAProxy，新节点作为 replica 加入。"},{"k":"mode","v":"rmmember","l":"从集群移除节点","m":"成员维护 移除节点 remove member decommission 服务器下线 移除 replica 永久移除一个 replica 节点：停止 Patroni，清理 DCS 记录和复制槽，并从 HAProxy 中移除。"},{"k":"mode","v":"editcfg","l":"修改 PostgreSQL 或 Patroni 参数（edit-config）","m":"参数与维护 edit-config Pending restart postgresql.conf 参数丢失 ALTER SYSTEM 修改参数 max_connections shared_buffers change parameter work_mem 参数丢失 参数未生效 参数写入 DCS 并应用到所有节点；需要重启的参数执行 `restart --pending`（先重启 replica）。"},{"k":"mode","v":"pause","l":"维护期间停止自动 failover（pause / resume）","m":"参数与维护 Maintenance mode: on patronictl pause patronictl resume maintenance mode 维护模式 关闭自动 failover pause 状态下 Patroni 不操作 PostgreSQL，也不执行 failover；用于网络/etcd 维护或手动干预。完成后执行 resume。"},{"k":"mode","v":"upgrade","l":"升级 Patroni 软件（滚动）","m":"参数与维护 patroni 升级 patroni 更新 patroni 版本 dnf update patroni apt upgrade patroni patroni 3 to 4 集群进入 pause，逐个节点更新并重启 Patroni；PostgreSQL 不停止，不发生 failover。"},{"k":"mode","v":"stopstart","l":"停止 / 启动整个集群（正确顺序）","m":"参数与维护 关闭集群 启动集群 shutdown order 整体关闭 计划内关闭 数据中心维护 停电 计划内整体关闭（存储、数据中心维护）：先 replica，再 leader，最后 etcd；启动顺序相反。"},{"k":"mode","v":"etcdmember","l":"替换或添加 etcd 成员（etcdctl member）","m":"etcd (DCS) etcdserver: unhealthy cluster member has already been bootstrapped Peer URLs already exists member count is unequal etcdctl member etcd member add etcd member remove etcd 成员 unhealthy 移除故障 etcd 成员并添加新服务器（或增加成员数）；同步更新 Patroni 的 etcd 主机列表。"},{"k":"mode","v":"dcsloss","l":"etcd 多数派丢失：leader 变为只读","m":"etcd (DCS) Failed to get list of machines DCS is not accessible demoting self etcdserver: request timed out etcdserver: no leader cannot execute INSERT in a read-only transaction quorum failsafe etcd quorum lost 没有 leader no leader etcd 没有 quorum 时，Patroni 会将 leader 设为只读，也不会发生 failover。需恢复 etcd，或从单个成员重建。"}]},{"id":"pg-pgbouncer","file":"install/guides/pg-pgbouncer.json","group":"PostgreSQL","engineId":"postgres","category":"pooling","title":"PgBouncer 连接池","summary":"从 PGDG repo 安装 PgBouncer：transaction 或 session 池模式，通过 auth_query（SECURITY DEFINER 函数）或 userlist.txt 进行 scram-sha-256 认证，按数据库 max_connections 和 CPU 计算的池大小，systemd 以及可选的 so_reuseport 多进程。最后提供管理控制台 (SHOW POOLS/STATS)、应用连接字符","level":"中级","duration":"30–45 分钟","topology":"PgBouncer 部署在数据库服务器或独立服务器上；Patroni 时位于 HAProxy/VIP 前面","tags":["postgresql","pgbouncer","connection pool","连接池","pooling","transaction pooling","auth_query","userlist","scram-sha-256","so_reuseport","max_client_conn","default_pool_size","patroni","haproxy","vip","failover"],"order":4,"steps":15,"scenarios":[]},{"id":"pg-pgbackrest","file":"install/guides/pg-pgbackrest.json","group":"PostgreSQL","engineId":"postgres","category":"backup","title":"使用 pgBackRest 进行备份与 PITR","summary":"使用 pgBackRest 进行全量 / 差异 / 增量备份、持续 WAL 归档和基于时间点的恢复 (PITR)。适用于单服务器、流复制 (primary + standby) 或 Patroni 集群；仓库 (repo) 可以位于同一服务器、单独的备份服务器 (SSH) 或 S3 兼容的对象存储。包括加密、压缩、保留策略、cron/systemd 调度以及恢复到单独目录的测试 restore；同一指南还会生成灾难发生时的原地 rest","level":"中级","duration":"45–90 分钟","topology":"1 台服务器、primary + standby 或 Patroni 集群；可选备份服务器","tags":["postgresql","pgbackrest","备份","backup","restore","pitr","wal","archive","s3","minio","加密","retention","恢复","patroni","streaming","standby","cron","backup-standby","systemd timer","delta restore","db-include","target-action","restore point","xid","lsn","clone","克隆","灾难","disaster recovery","verify","expire","type=standby","演练"],"order":10,"steps":28,"scenarios":[{"k":"purpose","v":"kur","l":"安装 pgBackRest + 首次备份 + 测试 restore","m":"安装 stanza-create archive_command archive-push pgbackrest 安装 pgbackrest install 配置备份 backup setup 备份计划 full diff incr 定时备份 ERROR: [087] archive_mode must be enabled 配置 WAL 归档 首次备份 尚未部署 pgBackRest：软件、仓库、WAL 归档、首次备份、调度和一次测试 restore。"},{"k":"purpose","v":"geri","l":"灾难时刻：原地 restore（恢复到最新状态或某个时间点）","m":"恢复（同一服务器） recovery_target_time recovery_target_xid recovery_target_lsn pg_create_restore_point PITR point in time recovery 恢复到时间点 误删除 DROP TABLE DELETE without WHERE UPDATE without WHERE TRUNCATE restore point restore to time 找回数据 将数据库原地恢复到备份和归档所覆盖的某一时刻：最新时刻、某个时间、xid、LSN 或 restore point。集群会被关闭；目标之后的更改将丢失。"},{"k":"purpose","v":"delta","l":"数据目录中有损坏或被删除的文件（用 delta restore 修复）","m":"恢复（同一服务器） invalid page in block could not read block page verification failed checksum verification failed could not open file \"base/ could not access status of transaction missing chunk number No such file or directory could not read file 坏块 data corruption block corruption 数据文件被删除 ERROR:  could not read block PANIC:  could not open file PANIC:  could not read file \"global/pg_control\" 服务器完好，但部分数据"},{"k":"purpose","v":"dbinc","l":"只恢复单个数据库 (--db-include)","m":"恢复（同一服务器） db-include DROP DATABASE 单个数据库 single database restore 数据库被删除 恢复表 table restore 单表恢复 集群保持运行：单个数据库被恢复到一个临时实例（单独的目录和端口），用 pg_dump 导出后以新名称加载到生产服务器。单张表被删除时也用这张卡片（从临时实例执行 `pg_dump -t`）。"},{"k":"purpose","v":"clone","l":"将备份恢复到另一台服务器（克隆 / 测试 / 报表）","m":"新服务器：克隆、灾难、standby repo1-cipher-pass clone 克隆 测试环境 刷新测试环境 refresh test staging 恢复到新服务器 报表服务器 加密仓库 不触碰生产服务器。备份在新服务器上打开；WAL 归档被关闭，这样克隆不会写入生产仓库。加密仓库需要提供密钥。"},{"k":"purpose","v":"dr","l":"服务器完全丢失：在新服务器上打开备份并接替原服务器","m":"新服务器：克隆、灾难、standby PANIC: could not locate a valid checkpoint record 灾难 disaster recovery DR 服务器丢失 磁盘故障 server lost 数据目录丢失 新 primary 加密仓库 could not open file \"global/pg_control\" 服务器无法启动 PostgreSQL 无法启动 磁盘或服务器丢失：新服务器从备份打开并成为新的 primary；WAL 归档和备份在同一个 stanza 中继续。旧服务器必须保持关闭。"},{"k":"purpose","v":"standby","l":"从备份搭建新的 standby (--type=standby)","m":"新服务器：克隆、灾难、standby type=standby 搭建 standby 添加 replica 从备份搭建 standby standby rebuild 替代 pg_basebackup add standby 搭建新 standby 而不给 primary 增加复制负载：文件来自备份，缺少的 WAL 从归档和流复制补齐。Primary 保持运行。"},{"k":"purpose","v":"verify","l":"备份真的可用吗？（info / check / verify 与 restore 演练）","m":"检查与维护 ERROR: [082] ERROR: [028] ERROR: [056] pgbackrest verify pgbackrest check pgbackrest info 备份验证 backup verification restore test 演练 最近一次备份是什么时候 verify command 备份是否完好 restore drill 每月验证 只读检查，以及不触碰生产服务器、恢复到单独目录的 restore 演练。同时配置每月一次的验证任务 (cron)。"},{"k":"purpose","v":"retention","l":"修改保留期限并清理旧备份 (expire)","m":"检查与维护 No space left on device WARN: option 'repo1-retention-full' is not set repo1-retention-full expire retention 保留 仓库已满 备份磁盘已满 删除旧备份 repo 已满 bucket 已满 备份磁盘被占满 修改 repo1-retention-full/diff 设置，先用 --dry-run 显示将删除哪些内容，然后执行 expire。被删除的备份无法找回。"}]},{"id":"pg-pitr-basebackup","file":"install/guides/pg-pitr-basebackup.json","group":"PostgreSQL","engineId":"postgres","category":"backup","title":"使用 pg_basebackup + WAL 归档进行备份与 PITR（不使用 pgBackRest）","summary":"不安装额外工具，用 PostgreSQL 自带工具做物理备份：用 archive_command 持续归档 WAL，用 pg_basebackup 每晚做 base backup，用 pg_archivecleanup 管理保留期。恢复到指定时间 / 事务 / restore point（recovery.signal、restore_command、recovery_target_*），时间线以及定期恢复演练；最终得到已归档的 WAL","level":"中级","duration":"45–90 分钟","topology":"1 台服务器；流复制或 Patroni 环境下归档目录由所有节点共享 (NFS)","tags":["postgresql","pg_basebackup","archive_command","restore_command","pitr","point in time recovery","recovery.signal","recovery_target_time","pg_archivecleanup","wal 归档","timeline","base backup","备份","恢复","pg_verifybackup","cron","演练"],"order":12,"steps":14,"scenarios":[{"k":"op","v":"kur","l":"配置 WAL 归档和每晚 base backup","m":"部署 archive_mode archive_command archive_timeout pg_stat_archiver wal_level WAL 归档 WAL archiving continuous archiving 连续归档 pg_basebackup cron base backup 每晚备份 nightly backup 配置 wal 归档 首次部署：用 archive_command 持续归档 WAL、pg_basebackup 脚本、/etc/cron.d 调度、保留期和 WAL 清理。archive_mode 需要一次短暂重启。"},{"k":"op","v":"pitr","l":"恢复到指定时间点（PITR：时间、xid、LSN、restore point）","m":"恢复 recovery ended before configured recovery target was reached requested recovery stop point is before consistent recovery point PITR point in time recovery point in time recovery.signal restore_command recovery_target_time recovery_target_xid recovery_target_lsn recovery_target_name pg_create_restore_point DROP TABLE TRUNCATE 误删除 accidental delete 时间点恢复 服务器丢失 FATAL:  could not locate required ch"},{"k":"op","v":"drill","l":"恢复演练（在单独目录中，不触碰生产库）","m":"检查与维护 backup successfully verified pg_verifybackup pg_amcheck restore test restore drill 演练 备份校验 backup verify RTO 备份能否恢复 用于证明备份确实可以打开（建议每月一次）：校验最新的 base backup (pg_verifybackup)，解压到单独目录，配合归档在单独端口上启动；测量耗时 (RTO)。不触碰生产库。"},{"k":"op","v":"cleanup","l":"清理旧的 base backup 和 WAL 归档","m":"检查与维护 No space left on device could not write to file archive command failed pg_archivecleanup 归档盘已满 归档已满 disk full 删除旧备份 备份保留 retention pg_wal 增长 archive_status .ready 归档盘或备份盘快满了，或者保留策略发生变化：删除旧的 base backup，用 pg_archivecleanup 删除最旧备份之前的 WAL（先 dry-run）。数据库保持运行。"}]},{"id":"pg-logical-repl","file":"install/guides/pg-logical-repl.json","group":"PostgreSQL","engineId":"postgres","category":"ha","title":"PostgreSQL 逻辑复制（publication / subscription）","summary":"在两个 PostgreSQL 集群之间进行表级逻辑复制：wal_level=logical、replica identity 检查、publication 和 subscription、初始同步、监控、冲突（conflict）处理、手工同步 sequence。还包括跨版本迁移（大版本升级、更换服务器）时将停机控制在数秒内的最终切换（cutover）步骤。","level":"高级","duration":"45–90 分钟（+ 初始复制时间）","topology":"2 台服务器：发布端（publisher）+ 订阅端（subscriber）；发布端可以是单台服务器、流复制或 Patroni 集群","tags":["postgresql","logical replication","逻辑复制","publication","subscription","cdc","迁移","migration","upgrade","conflict","replica identity","sequence","patroni","failover slot","permanent slots","sync_replication_slots"],"order":15,"steps":25,"scenarios":[{"k":"purpose","v":"sync","l":"持续复制（报表、集成、部分副本）","m":"logical replication publication subscription CREATE SUBSCRIPTION 表复制 部分复制 选定的表持续流向另一个集群（报表服务器、数据仓库供数、服务间集成）。订阅端保持可写。"},{"k":"purpose","v":"migrate","l":"迁移：新服务器 / 新大版本 + 最终切换（cutover）","m":"零停机升级 near zero downtime cutover 服务器迁移 logical upgrade 以秒级停机迁移到新服务器或新大版本：初始复制和持续同步，然后短暂停止写入并进行最终切换（cutover）。"}]},{"id":"pg-dump-restore","file":"install/guides/pg-dump-restore.json","group":"PostgreSQL","engineId":"postgres","category":"backup","title":"PostgreSQL 逻辑备份与恢复 (pg_dump / pg_restore)","summary":"使用 pg_dump、pg_dumpall 和 pg_restore 在表、模式、数据库或整个集簇级别进行逻辑备份与恢复；仅结构 (DDL) 或仅数据、迁移到其他服务器/版本、从 dump 中恢复单个表，以及通过 systemd/cron 定时执行、带校验和保留期的备份。每条路径最后都会对比行数进行验证。在流复制和 Patroni 集群中，dump 从 replica（HAProxy 只读端口或 standby）获取，恢复只在 prim","level":"中级","duration":"20–90 分钟（取决于数据量）","topology":"1 台服务器、流复制 (primary + standby) 或 Patroni 集群；迁移时需要源端 + 目标端，远程定时备份另需一台备份服务器","tags":["postgresql","postgres","pg_dump","pg_restore","pg_dumpall","逻辑备份","logical backup","export","import","dump","restore","恢复 dump","数据库迁移","migration","upgrade","globals","systemd timer","cron","pgpass","备份","patroni","streaming replication","replica","standby","haproxy","hot standby","pg_wrapper","cron.d","flock"],"order":15,"steps":9,"scenarios":[{"k":"mode","v":"tables","l":"导出 / 导入单个（或多个）表","m":"导出 / 导入 pg_dump -t 导出表 导入表 table export 单表备份 no matching tables were found 导出所选表的结构和数据（包括索引、约束、触发器），并加载到另一个数据库。"},{"k":"mode","v":"schema","l":"导出 / 导入一个模式","m":"导出 / 导入 pg_dump -n 导出模式 schema export no matching schemas were found 导出模式中的所有对象（表、视图、函数、序列）。"},{"k":"mode","v":"db","l":"备份 / 恢复单个数据库","m":"导出 / 导入 pg_dump pg_restore 数据库备份 database backup database restore errors ignored on restore 将整个数据库（结构 + 数据）导出为一个 dump，并恢复到同一个或另一个数据库。不包括角色 (globals)。"},{"k":"mode","v":"cluster","l":"整个集簇（角色 + 表空间 + 每个数据库）","m":"导出 / 导入 pg_dumpall pg_dumpall -g globals 全集簇备份 角色备份 用 pg_dumpall -g 导出 globals，每个数据库单独执行 pg_dump；恢复先在临时测试集簇上演练。"},{"k":"mode","v":"ddl","l":"仅模式结构 (DDL)","m":"结构与数据分开 pg_dump -s --schema-only DDL 导出 仅结构 不含数据的表、索引、视图、函数定义；用于审查、版本控制或搭建空环境。"},{"k":"mode","v":"data","l":"仅数据","m":"结构与数据分开 pg_dump -a --data-only 仅数据 --disable-triggers circular foreign-key constraints 目标端结构已就绪时，只传输行数据 (COPY)。"},{"k":"mode","v":"migrate","l":"迁移到其他服务器 / 版本","m":"迁移与恢复 服务器迁移 migration dump restore upgrade aborting because of server version mismatch 迁移到新版本 在目标服务器上用新版本的 pg_dump 通过网络连接源端；通过管道或文件传输。"},{"k":"mode","v":"partial","l":"从 dump 中恢复单个表 / 对象","m":"迁移与恢复 恢复单个表 pg_restore -L pg_restore --list DROP TABLE 误删表 误删行 误删除 table restore 从现有的 custom/directory 格式 dump 中，借助列表文件 (-L) 只恢复一个表及其索引、约束和数据。"},{"k":"mode","v":"sched","l":"定时备份 (systemd timer / cron)","m":"定时备份 pg_dump cron systemd timer 自动备份 scheduled backup 每日备份 备份保留 每天备份 globals + 数据库，并做可读性校验、保留期管理、日志记录和失败通知。"}]},{"id":"pg-minor-update","file":"install/guides/pg-minor-update.json","group":"PostgreSQL","engineId":"postgres","category":"patch","title":"PostgreSQL 小版本更新（patch）","summary":"在同一主版本内进行小版本更新（例如 17.4 → 17.x）：检查 release notes、备份，并保留当前的 RPM/deb 包以便回退。单服务器上只需短暂停机；在 streaming replication 集群中先更新 standby，再通过 switchover 更新 primary（rolling）；在 Patroni 集群中，replica 通过 patronictl restart 更新，leader 在 patron","level":"中级","duration":"20–60 分钟","topology":"1 台服务器、primary + standby，或 Patroni 集群","tags":["postgresql","minor","patch","小版本更新","update","upgrade","rolling","switchover","dnf","apt","downgrade","rollback","extension","patroni","patronictl","failover"],"order":20,"steps":17,"scenarios":[{"k":"purpose","v":"upd","l":"小版本更新","m":"minor update 小版本升级 patch CVE 安全更新 dnf upgrade postgresql apt upgrade postgresql 在同一主版本内升级到较新的小版本（例如 17.4 → 17.6）：安全修复和错误修复。单服务器上短暂停机，集群中采用 rolling 方式。"},{"k":"purpose","v":"rb","l":"回退：恢复到上一个小版本（downgrade）","m":"downgrade 回退 rollback 恢复到上一版本 undefined symbol 新小版本中出现错误或回归：使用保存的上一版本 RPM/deb 包回退。数据目录不变。"}]},{"id":"pg-major-upgrade","file":"install/guides/pg-major-upgrade.json","group":"PostgreSQL","engineId":"postgres","category":"upgrade","title":"PostgreSQL 主版本升级 (pg_upgrade / 逻辑复制)","summary":"主版本升级（例如 16 → 17）：选择方法（pg_upgrade --link / --copy / --clone，或通过逻辑复制实现近乎零停机的迁移）、pg_upgrade --check、扩展兼容性、迁移配置、重新收集统计信息、通过 rsync 或 pg_basebackup 重建 standby、删除旧集群以及回退 runbook。涵盖单机、流复制（primary + standby）和 Patroni 集群（Patroni ","level":"高级","duration":"1–3 小时（+ 取决于数据量）","topology":"在同一服务器上原地升级（pg_upgrade；单机、primary + standby 或 Patroni 集群），或迁移到新服务器（逻辑复制）","tags":["postgresql","主版本","upgrade","版本升级","pg_upgrade","pg_upgradecluster","link","clone","逻辑复制","迁移","migration","rsync","回退","patroni","patronictl remove","replica","流复制"],"order":30,"steps":24,"scenarios":[{"k":"purpose","v":"upg","l":"主版本升级","m":"major upgrade pg_upgrade pg_upgradecluster 版本升级 主版本 升级 postgresql 17 升级 postgresql 18 主版本升级（例如 16 → 17）：预检查、备份、pg_upgrade 或逻辑复制、验证和清理。"},{"k":"purpose","v":"rb","l":"回退（升级失败 / 放弃升级）","m":"回退升级 回退 pg_control.old database files are incompatible with server 升级失败 升级中途停止，或新版本出现问题：回退到旧版本。使用 --link 时，如果新集群已经启动过，只能从备份恢复。"}]},{"id":"pg-security","file":"install/guides/pg-security.json","group":"PostgreSQL","engineId":"postgres","category":"security","title":"PostgreSQL 安全加固（状态审查、网络、TLS、认证、权限、审计、加密）","summary":"这不是安装指南：它用于发现并修复一台运行中 PostgreSQL 服务器的安全缺口。先运行只读的 状态审查，按严重程度列出发现项，并指出每一项对应哪张卡片；然后逐个或按顺序全部修复各个方面：监听地址、防火墙和 pg_hba；TLS 以及客户端的 sslmode=verify-full；scram-sha-256 以及禁止 postgres 通过网络登录；角色设计和 PUBLIC 权限；pgaudit 和集中日志；LUKS / pgcry","level":"中级","duration":"5 分钟（审查）– 3 小时（完整加固）","topology":"单台 PostgreSQL 服务器 + 应用服务器（客户端验证）；带 replica/Patroni 的环境见注意事项","tags":["postgresql","postgres","安全","security","hardening","安全加固","cis benchmark","pg_hba","tls","ssl","sslmode","verify-full","scram-sha-256","md5","pgaudit","audit","审计","public schema","权限","角色","luks","pgcrypto","加密","encryption","firewall"],"order":40,"steps":3,"scenarios":[{"k":"scenario","v":"scan","l":"状态审查：我有哪些安全缺口？（只读）","m":"从这里开始 安全审查 security audit hardening check CIS 安全缺口 漏洞 vulnerability 渗透测试发现 审计报告 审查查询 CIS 检查 安全扫描 security scan 首先运行的扫描：pg_hba、监听地址、TLS、md5 密码、超级用户、PUBLIC 权限、审计、文件权限和防火墙；不做任何更改。"},{"k":"scenario","v":"all","l":"完整加固（按顺序处理所有方面）","m":"从这里开始 hardening 安全加固 全面安全 full hardening 应用 CIS benchmark STIG 新安装或从未加固的服务器：按安全的顺序应用所有方面；PostgreSQL 需要重启一到两次（需要维护窗口）。"},{"k":"scenario","v":"net","l":"网络与连接：谁可以从哪里连接？（listen、防火墙、pg_hba）","m":"网络与身份 pg_hba.conf 0.0.0.0/0 trust listen_addresses firewall no pg_hba.conf entry pg_hba.conf rejects connection 5432 端口开放 FATAL: no pg_hba.conf entry for host 28000 Connection refused listen_addresses = * host all all 0.0.0.0/0 外部访问 限制访问 PostgreSQL 在所有网络接口上监听，pg_hba 中有 0.0.0.0/0 或 trust，端口对所有人开放。修改 listen_addresses 需要重启。"},{"k":"scenario","v":"tls","l":"加密连接（TLS、sslmode=verify-full）","m":"网络与身份 ssl tls sslmode verify-full hostssl certificate verify failed SSL off no encryption server.crt 证书过期 certificate expired SSL error: certificate verify failed does not match host name root certificate file does not exist server does not support SSL, but SSL was required self-signed certificate 证书链 certificate chain hostnossl ssl = off，或客户端不验证证书；服务器证书、hostssl 以及客户端的 verify-full。只需 reload，无停机。"},{"k":"scenario","v":"auth","l":"认证与密码（scram-sha-256、postgres 远程登录）","m":"网络与身份 md5 scram-sha-256 password_encryption password authentication failed authentication method 10 not supported postgres 远程登录 密码策略 password policy 28P01 FATAL: password authentication failed for user SCRAM authentication requires libpq version 10 or above pass-the-hash VALID UNTIL 密码有效期 md5/password 认证方法、md5 哈希的密码、postgres 用户可以通过网络登录；迁移到 scram-sha-256 并使用个人 DBA 账户。无停机，需要重新设置密码。"},{"k":"scenario","v":"priv","l":"最小权限：角色、PUBLIC 和应用账户","m":"权限与审计轨迹 public schema PUBLIC GRANT REVOKE superuser least privilege 最小权限 permission denied for schema 角色设计 DEFAULT PRIVILEGES 42501 permission denied for table must be owner of table pg_read_server_files pg_execute_server_program SECURITY DEFINER REASSIGN OWNED 权限报告 应用账户是表的属主或超级用户，任何人都能向 public 模式写入；读/写/属主角色以及权限报告。无停机。"},{"k":"scenario","v":"audit","l":"审计：谁做了什么？（pgaudit、集中日志）","m":"权限与审计轨迹 pgaudit audit 审计轨迹 audit log AUDIT: SESSION log_connections syslog SIEM 个人信息保护法 PCI shared_preload_libraries could not access file \"pgaudit\" pgaudit must be loaded via shared_preload_libraries log_statement 谁做了什么 who changed 审计人员或安全团队要求记录 DDL 和角色变更；安装 pgaudit 并将日志发送到集中日志服务器。需要重启一次。"},{"k":"scenario","v":"tde","l":"加密磁盘上的数据（LUKS / pgcrypto）","m":"数据与备份 TDE transparent data encryption 磁盘加密 encryption at rest LUKS dm-crypt pgcrypto pg_tde cryptsetup luksFormat pgp_sym_encrypt at-rest encryption 需要防范磁盘或备份被盗。PostgreSQL 没有内置 TDE：可以在磁盘层使用 LUKS（迁移需要停机），或在列级使用 pgcrypto。"},{"k":"scenario","v":"backup","l":"备份安全（加密备份、权限）","m":"数据与备份 备份加密 backup encryption repo-cipher-type pgbackrest cipher gpg .pgpass 备份权限 cipher: none repo-cipher-pass 未加密备份 unencrypted backup 备份文件未加密，或位于所有人可读的目录中；pgBackRest 仓库加密或 pg_dump + GPG、.pgpass 以及目录权限。无停机。"},{"k":"scenario","v":"os","l":"操作系统层面（服务账户、权限、core dump、SELinux）","m":"服务器与版本 SELinux permissive chmod 700 data directory has invalid permissions core dump postgres 账户 sudo avc: denied LimitCORE core dumped passwd -l postgres 账户的密码可用，数据目录权限过宽，SELinux 为 Permissive/Disabled。core dump 设置在下次重启后生效。"},{"k":"scenario","v":"patch","l":"补丁与版本：我用的是受支持的版本吗？","m":"服务器与版本 EOL end of life 支持已结束 小版本 CVE 安全补丁 security release out of support server_version 补丁计划 minor update pgsql-announce 检查版本、支持结束日期和待安装的小版本更新；实际应用请转到补丁指南。只读。"}]},{"id":"mysql-install","file":"install/guides/mysql-install.json","group":"MySQL","engineId":"mysql","category":"install","title":"MySQL 单机安装（可用于生产）","summary":"从官方仓库全新安装单台可用于生产的 MySQL 8.4 LTS（或 8.0）服务器：数据、binlog 和日志使用独立目录，my.cnf 按 RAM/CPU/磁盘类型计算，配置 GTID 与 binlog 保留期、slow query log、SELinux/AppArmor，限制 root 访问并创建应用用户。最终得到一个已验证并已完成首次备份的实例；复制、InnoDB Cluster、备份和安全指南都基于此安装。","level":"入门","duration":"30–45 分钟","topology":"1 台服务器","tags":["mysql","安装","install","8.4","lts","innodb","my.cnf","调优","gtid","binlog","rhel","ubuntu"],"order":1,"steps":24,"scenarios":[]},{"id":"mysql-replication","file":"install/guides/mysql-replication.json","group":"MySQL","engineId":"mysql","category":"ha","title":"MySQL GTID 复制（async / semi-sync）","summary":"用已安装的 MySQL 服务器搭建 1 个 source + N 个 replica 的基于 GTID 的复制：复制用户、用 clone plugin 或 mysqldump 传输现有数据、SOURCE_AUTO_POSITION、TLS、可选的 semi-sync、super_read_only 的 replica、延迟监控以及手动 failover 流程。最终得到一个受监控的拓扑：写入由 source 承担，读取可由 replica","level":"中级","duration":"45–90 分钟","topology":"1 个 source + 1..5 个 replica","tags":["mysql","主从复制","replication","gtid","semi-sync","semisync","replica","source","clone","failover","read_only"],"order":2,"steps":21,"scenarios":[]},{"id":"mysql-innodb-cluster","file":"install/guides/mysql-innodb-cluster.json","group":"MySQL","engineId":"mysql","category":"ha","title":"MySQL InnoDB Cluster（Group Replication + MySQL Router）","summary":"用同一数据中心内 3 台（或 5–9 台）已安装的 MySQL 服务器搭建 single-primary InnoDB Cluster：通过 MySQL Shell AdminAPI 执行 dba.configureInstance、createCluster 以及用 clone 方式 addInstance；MySQL Router bootstrap（6446 RW / 6447 RO），cluster.status() 监控，fa","level":"高级","duration":"90–120 分钟","topology":"3–9 个 MySQL 节点 + 1 台 Router 服务器","tags":["mysql","innodb cluster","group replication","mysql shell","mysqlsh","adminapi","mysql router","高可用","failover","quorum","clone","single-primary"],"order":3,"steps":39,"scenarios":[]},{"id":"mysql-xtrabackup","file":"install/guides/mysql-xtrabackup.json","group":"MySQL","engineId":"mysql","category":"backup","title":"使用 Percona XtraBackup 进行备份、恢复和 PITR","summary":"使用 Percona XtraBackup 进行物理备份：安装匹配版本的 XtraBackup、创建使用 auth_socket 的备份用户、编写压缩/加密的 full + incremental 备份脚本、用 systemd timer 或 cron 调度、复制 binlog 并进行恢复演练。场景卡片涵盖完整恢复、从 incremental 链恢复、恢复到指定时间点 (PITR)、单表恢复、从备份搭建 replica 以及恢复到另一台","level":"中级","duration":"45–90 分钟","topology":"1 台服务器（单机、source、replica 或 InnoDB Cluster secondary）；备份存储为本地磁盘或 NFS","tags":["mysql","xtrabackup","percona","备份","backup","restore","incremental","pitr","binlog","恢复","zstd","加密","副本","slave-info","safe-slave-backup","innodb cluster","cron","systemd timer","flock"],"order":4,"steps":26,"scenarios":[{"k":"scenario","v":"setup","l":"配置备份、调度并测试恢复（完整流程）","m":"安装与演练 xtrabackup 安装 备份脚本 backup script systemd timer cron 还原演练 xtrabackup 备份 物理备份 physical backup hot backup incremental backup full backup percona-xtrabackup 安装 XtraBackup、创建备份用户、编写 full + incremental 备份脚本、配置调度并进行一次恢复演练（含 PITR）。首次部署请选择此项。备份在服务器运行时进行；无停机，只增加磁盘/CPU 负载。"},{"k":"scenario","v":"r_full","l":"完整恢复：恢复到最新的 full 备份","m":"恢复 restore 恢复 copy-back datadir 丢失 InnoDB: corruption MY-011971 MY-012592 Database page corruption mysqld 无法启动 full restore ERROR 2002 Can't connect to local MySQL server through socket MY-012574 Unable to lock ./ibdata1 datadir 被删除 磁盘故障 disk failure 数据丢失 数据目录丢失/损坏或服务器无法启动；恢复到链中的 full 备份即可。恢复期间 MySQL 一直处于关闭状态。"},{"k":"scenario","v":"r_inc","l":"从 full + incremental 链恢复（到最新的 incremental）","m":"恢复 incremental restore apply-log-only incremental-dir to_lsn from_lsn This incremental backup seems not to be proper for the target incremental 链 incremental chain xtrabackup_checkpoints applying incremental 需要恢复到最新 incremental 备份的时间点（或您选择的某个 incremental）；incremental 按顺序用 `--apply-log-only` 应用到 full 之上。恢复期间 MySQL 一直处于关闭状态。"},{"k":"scenario","v":"r_pitr","l":"恢复到指定时间点 (PITR)：错误的 DELETE / DROP 之前","m":"时间点恢复 PITR 时间点恢复 误删 DROP TABLE DELETE 误操作 TRUNCATE mysqlbinlog stop-datetime stop-position exclude-gtids binlog replay 误 UPDATE 不带 WHERE 的 DELETE accidental delete accidental drop DROP DATABASE 撤销 undo delete 使用 binlog 恢复 误删除或被破坏的数据：恢复最新备份，然后应用 binlog，直到错误 transaction 之前（按时间、位置或 GTID）。恢复期间 MySQL 一直关闭，错误发生之后的写入也会被回退；如果只影响一张表，请先考虑单表场景。"},{"k":"scenario","v":"r_table","l":"恢复单表 (--export + IMPORT TABLESPACE)","m":"时间点恢复 单表 single table restore IMPORT TABLESPACE DISCARD TABLESPACE transportable tablespace --export .cfg ERROR 1808 ERROR 1812 Schema mismatch ERROR 1813 Tablespace exists ERROR 1146 Table doesn't exist tablespace is missing 恢复一张表 table restore ibd 文件 只需要某张表在备份中的版本；无需恢复整个服务器，从备份中将该表以 `t_restore` 名称导入，再从中修复数据。服务器保持运行。"},{"k":"scenario","v":"r_replica","l":"从备份搭建新的 replica","m":"迁移与克隆 新 replica 搭建 replica xtrabackup_binlog_info xtrabackup_slave_info gtid_purged slave-info replication from backup 1236 从备份搭建 replica seed replica Could not find first log file name in binary log index file purged required binary logs 1236 重建 replica 希望用现有的 XtraBackup 备份（而不是通过网络 clone）准备一个新的（或重建的）replica 并连接到 source（数据量大、source 负载）。source 不受影响；目标服务器的数据将被替换。"},{"k":"scenario","v":"r_other","l":"将备份恢复到另一台服务器（迁移、测试、克隆）","m":"迁移与克隆 其他服务器 测试服务器 restore test 迁移 migration 克隆 clone to test rsync server_uuid 加载到测试环境 备份验证 restore drill 打开备份 服务器迁移 需要在测试/开发服务器上打开生产备份、迁移服务器，或在另一台服务器上验证备份确实可以恢复。源服务器不受影响。"}]},{"id":"mysql-upgrade","file":"install/guides/mysql-upgrade.json","group":"MySQL","engineId":"mysql","category":"upgrade","title":"MySQL 8.0 → 8.4 LTS 升级（in-place）","summary":"在同一台服务器上把已安装的 MySQL 8.0 原地（in-place）升级到 8.4 LTS：单服务器、source + replica（先 replica，最后 source），或 InnoDB Cluster（Router → Shell → metadata → 逐个 secondary → 最后 primary）。包含每台服务器上的 util.checkForServerUpgrade 预检查、8.4 中已移除的变量和默认关闭","level":"中级","duration":"60–120 分钟（取决于数据量）","topology":"1 台服务器、source + replica，或 InnoDB Cluster（3+ 个成员 + MySQL Router 服务器）","tags":["mysql","upgrade","8.0","8.4","lts","升级","checkForServerUpgrade","mysql_native_password","authentication_policy","rollback","回退","innodb cluster","group replication","mysql router","upgradeMetadata","setPrimaryInstance","rolling upgrade"],"order":5,"steps":18,"scenarios":[]},{"id":"mysql-repl-ops","file":"install/guides/mysql-repl-ops.json","group":"MySQL","engineId":"mysql","category":"ops","title":"MySQL 复制日常运维与故障场景 (8.0 / 8.4)","summary":"适用于已部署的 source + replica 拓扑（async / semi-sync，GTID 或 binlog 位置）的日常运维与故障恢复 runbook：健康与延迟检查、计划内 switchover、failover、将旧 source 重新加入、重建 replica（clone / XtraBackup / mysqldump）、errant GTID、1062/1032 错误、利用延迟 replica 恢复数据、迁移 re","level":"高级","duration":"5 分钟（检查）– 2 小时（重建）","topology":"1 source + 1..5 replica (async / semi-sync)","tags":["mysql","复制","replication","gtid","errant gtid","errant transaction","switchover","failover","replica","source","super_read_only","semi-sync","show replica status","seconds_behind_source","复制延迟","lag","1062","1032","1236","duplicate entry","can't find record","gtid_next","sql_replica_skip_counter","source_delay","延迟 replica","delayed replica","clone","xtrabackup","mysqldump","purge binary logs","binlog_expire_logs_seconds","wait_for_executed_gtid_set","change replication source to","runbook","8.0","8.4"],"order":12,"steps":5,"scenarios":[{"k":"scenario","v":"health","l":"状态与延迟检查（复制是否健康？）","m":"监控与检查 SHOW REPLICA STATUS SHOW SLAVE STATUS Seconds_Behind_Source Seconds_Behind_Master Replica_IO_Running Slave_IO_Running Replica_SQL_Running: No 复制延迟 replication lag replica lag Rpl_semi_sync_source_status 1045 2003 2026 Error connecting to source 复制状态 Replica_IO_Running: No Replica_IO_Running: Connecting Slave_IO_Running: No Slave_SQL_Running: No Seconds_Behind_Source: NULL Last_IO_Error Last_"},{"k":"scenario","v":"switchover","l":"计划内 switchover（将 source 角色移交给某个 replica）","m":"角色切换 switchover 计划内切换 角色切换 role change promote replica 更换 source 维护 计划内维护 graceful takeover graceful-master-takeover 替换 source 更换 primary change primary 新 source source 和 replica 均健康；因维护、更换硬件或升级，需要按计划把 source 角色移交给某个 replica。写入会暂停几分钟，不丢数据；切回就是反向执行同一场景。"},{"k":"scenario","v":"failover","l":"source 丢失：failover 到最领先的 replica","m":"角色切换 failover source 崩溃 source down master down primary down Error connecting to source Can't connect to MySQL server 2003 2013 Lost connection 最领先的 replica most advanced replica Replica_IO_Running: Connecting Slave_IO_Running: Connecting 2005 Unknown MySQL server host error reconnecting to source source 无法访问 source 已关闭 primary 崩溃 服务器崩溃 server crashed source 已崩溃或无法访问，且短时间内不会恢复（replica 上 IO 线程为 `Co"},{"k":"scenario","v":"rejoin","l":"failover 后将旧 source 作为 replica 重新加入","m":"角色切换 旧 source old source old master rejoin 重新加入 errant split brain 1236 旧 primary old primary 旧 master 恢复 split-brain Replica has more GTIDs than the source has Slave has more GTIDs than the master has 重新加入 reintegrate 已完成 failover，旧 source 服务器恢复了。先禁止其写入并复制其 binlog，再检查 errant transaction；如果干净就直接作为 replica，否则从新 source 重建。新 source 无停机。"},{"k":"scenario","v":"rebuild","l":"从头重建 replica (clone / XtraBackup / mysqldump)","m":"恢复 1236 Cannot replicate because the source purged required binary logs purged required binary logs 重建 rebuild replica reseed clone instance 新 replica 数据偏差 data drift Could not find first log file name in binary log index file the source has purged binary logs containing GTIDs that the replica requires the master has purged binary logs containing GTIDs that the slave requires Got fatal error 1236 "},{"k":"scenario","v":"errant","l":"Errant GTID：replica 上有 source 没有的事务","m":"恢复 errant gtid errant transaction gtid_subtract extra gtid 多余 gtid Having extra GTID events incompatible GTID set 1236 GTID_SUBTRACT gtid_executed 差异 写入了 replica write on replica super_read_only 关闭 Replica has more GTIDs than the source has 存在直接写入 replica 的事务（super_read_only 关闭期间），或旧 source 遗留的事务；failover 时这些事务会扩散到其他 replica，或以 1236 使复制停止。解决办法：空事务、在 source 上应用，或重建。无停机。"},{"k":"scenario","v":"sqlerr","l":"复制停止：1062 Duplicate entry / 1032 Can't find record","m":"恢复 1062 1032 1452 1146 1007 1008 Duplicate entry Can't find record HA_ERR_KEY_NOT_FOUND HA_ERR_FOUND_DUPP_KEY Could not execute Write_rows event Could not execute Update_rows event Could not execute Delete_rows event Replica_SQL_Running: No Slave_SQL_Running: No Coordinator stopped because there were error(s) in the worker(s) sql_slave_skip_counter sql_replica_skip_counter gtid_next MY-010584 1058"},{"k":"scenario","v":"delayed","l":"延迟 replica (SOURCE_DELAY)：配置或恢复被删除的数据","m":"日常运维 SOURCE_DELAY MASTER_DELAY delayed replica 延迟 replica 误删 DROP TABLE DELETE 误操作 SQL_Delay SQL_BEFORE_GTIDS SQL_Remaining_Delay 误执行 DELETE 不带 WHERE 的 DELETE accidental delete accidental drop dropped table 表被删除 数据恢复 UNTIL SQL_BEFORE_GTIDS 需要让某个 replica 有意落后 N 秒（设置/取消延迟），或者在误删/误改的数据（DROP、不带 WHERE 的 DELETE/UPDATE）尚未在延迟 replica 上应用之前，从它那里找回数据。source 保持在线。"},{"k":"scenario","v":"repoint","l":"将 replica 迁移到另一个 source（级联 / 重新指向）","m":"日常运维 repoint change source 级联复制 chained replication intermediate 中间 replica relay replica CHANGE MASTER TO CHANGE REPLICATION SOURCE TO cascading replication 多级复制 将 replica 连接到其他 source 修改 SOURCE_HOST 修改 MASTER_HOST log_replica_updates 需要把某个 replica 挂到另一个 replica 之下（级联 / 中间 replica），或重新连回 source；不复制数据，只改变连接。被迁移的 replica 会停几秒钟。"},{"k":"scenario","v":"binlog","l":"binlog 清理与保留时间（磁盘将满）","m":"日常运维 PURGE BINARY LOGS PURGE MASTER LOGS binlog_expire_logs_seconds expire_logs_days binlog 磁盘已满 No space left on device errno 28 binlog 清理 binlog retention Disk is full writing OS errno 28 1867 was not purged because it was being read 1373 Target log not found in binlog index binlog 增长 binlog_expire_logs_auto_purge binlog_transaction_compression binlog 正在占满磁盘，或者需要设置保留时间；在不删除 replica 和 PITR 仍需要的文件"}]},{"id":"mysql-ic-ops","file":"install/guides/mysql-ic-ops.json","group":"MySQL","engineId":"mysql","category":"ops","title":"MySQL InnoDB Cluster 日常运维与故障场景 (MySQL Shell 8.4)","summary":"不是安装指南：针对已在运行的 InnoDB Cluster（Group Replication + MySQL Router，通过 MySQL Shell AdminAPI 搭建）的日常运维和故障恢复，按场景组织的 runbook。选择一个场景卡片：健康检查、添加成员（clone / incremental）和移除成员（含 force）、让掉线成员重新加入、整个集群停机后重新启动 (rebootClusterFromCompleteOu","level":"高级","duration":"5 分钟（检查）– 1 小时（添加成员）","topology":"3–9 个 MySQL 节点 (single-primary) + MySQL Router","tags":["mysql","innodb cluster","group replication","mysql shell","mysqlsh","adminapi","cluster.status","addinstance","removeinstance","rejoininstance","rebootclusterfromcompleteoutage","forcequorumusingpartitionof","setprimaryinstance","setoption","setinstanceoption","rescan","mysql router","bootstrap","quorum","no_quorum","split brain","expelTimeout","memberWeight","autoRejoinTries","exitStateAction","consistency","clone","MISSING","UNREACHABLE","RECOVERING","ERROR","runbook","8.4"],"order":13,"steps":5,"scenarios":[{"k":"scenario","v":"health","l":"集群状态与健康检查（一切正常吗？）","m":"监控与检查 cluster.status status() OK_PARTIAL OK_NO_TOLERANCE OK_NO_TOLERANCE_PARTIAL instanceErrors replicationLag flow control RECOVERING applier queue 集群状态 Cluster is ONLINE and can tolerate can tolerate up to ONE failure 健康检查 health check 队列增长 replication_group_members group_replication_member_stats 集群状态如何 只读：`cluster.status({extended: 1})`、成员的角色/状态/版本、应用队列、Router 列表和 error log；不做任何更改，不停机。如果不确定或收到告"},{"k":"scenario","v":"add","l":"添加新成员 (addInstance: clone / incremental)","m":"成员 addInstance 添加成员 添加节点 add instance recoveryMethod clone incremental 3092 Having extra GTID events MYSQLSH 51166 The target instance must be either cloned or fully provisioned Clone Donor Error 扩容 提高容错 OK_NO_TOLERANCE 您想向集群添加一个新的 MySQL 节点、提高容错能力 (3 → 5)，或重新加入一个已被移除的节点。集群保持可写；clone 期间 donor 上会产生磁盘/网络负载，新节点会重启。"},{"k":"scenario","v":"remove","l":"移除成员 (removeInstance，必要时 force)","m":"成员 removeInstance 移除成员 移除节点 remove instance force MISSING decommission Cluster.removeInstance is not reachable and cannot be safely removed 删除节点 缩容 服务器退还 您想把某个节点永久移出集群（硬件退还、缩容），或者节点已丢失，需要从元数据中删除 (`force`)。移除 secondary 不会停机；移除 primary 会有几秒钟的写入中断。"},{"k":"scenario","v":"rejoin","l":"让掉线成员重新加入 (MISSING / ERROR / OFFLINE)","m":"故障与恢复 rejoinInstance MISSING ERROR OFFLINE UNREACHABLE expelled 被逐出组 member was expelled MY-011735 MY-011526 incompatible GTID set (MISSING) This member has more executed transactions than those present in the group has an incompatible GTID set with the seed instance Error connecting to all peers Member join failed group_replication is stopped 成员掉线 成员 OFFLINE member expelled auto-rejoin 某个成员重启过、经历"},{"k":"scenario","v":"reboot","l":"整个集群已停机：重新启动 (rebootClusterFromCompleteOutage)","m":"故障与恢复 rebootClusterFromCompleteOutage complete outage 完全停机 所有节点都已关闭 断电 Group Replication is not active OFFLINE Unable to find a cluster PRIMARY member The cluster is offline Dba.rebootClusterFromCompleteOutage Dba.getCluster: This function is not available through a session to a standalone instance metadata exists, instance belongs to that metadata, but GR is not active group_replication_bootstrap"},{"k":"scenario","v":"quorum","l":"多数派丢失 (NO_QUORUM)：用剩余成员强制建立 quorum","m":"故障与恢复 NO_QUORUM forceQuorumUsingPartitionOf quorum 多数派丢失 split-brain split brain UNREACHABLE majority partition 3100 Cluster has no quorum cannot process write transactions split-brain! Instance is not part of the majority group 写入挂起 writes hang forceQuorum 大多数成员同时丢失（例如 3 个中的 2 个），剩余成员以 `NO_QUORUM` 停止写入（写入挂起）。如果确定丢失的成员确实已关闭，则用剩余分区重新运行集群。使用不当会造成 split-brain；只丢失一个成员（多数派仍在）不属于此场景。"},{"k":"scenario","v":"switch","l":"计划内切换 primary (setPrimaryInstance)","m":"角色与路由 setPrimaryInstance 切换 primary switchover 移交 primary The appointed primary member has a version that is greater group_replication_set_as_primary Cluster.setPrimaryInstance runningTransactionsTimeout 计划内 primary 切换 迁移 primary planned switchover 为了维护（OS 补丁、硬件）、负载均衡或 DR 演练，您想把 primary 角色计划内地移交给指定的 secondary。写入会等待几秒钟，已打开的事务会被完成；Router 会自动路由到新的 primary。"},{"k":"scenario","v":"router","l":"添加 Router 或重新 bootstrap","m":"角色与路由 mysqlrouter MySQL Router bootstrap --force routerErrors re-bootstrap Router listRouters removeRouterMetadata 6446 6447 previously configured in this host Unable to connect to the metadata server Unable to fetch all configuration options The provided server is currently not in a InnoDB cluster group with quorum mysqlrouter.service failed Router 无法连接 Router 安装 您想在新的应用服务器上安装 Router、重新 bootstrap"},{"k":"scenario","v":"options","l":"修改集群选项（consistency、expelTimeout、memberWeight…）","m":"配置 setOption setInstanceOption consistency BEFORE_ON_PRIMARY_FAILOVER expelTimeout group_replication_member_expel_timeout memberWeight autoRejoinTries exitStateAction OFFLINE_MODE READ_ONLY options() group_replication_consistency group_replication_member_weight group_replication_autorejoin_tries group_replication_exit_state_action 成员频繁被逐出 failover 优先级 您想调整 failover 一致性、网络抖动时逐出成员的时间、failover 优先级、自动"},{"k":"scenario","v":"rescan","l":"使元数据与实际组一致 (rescan)","m":"配置 rescan metadata is not managed not registered in the metadata newlyDiscoveredInstances unavailableInstances instanceErrors obsolete Use cluster.rescan() Instance is not managed by InnoDB cluster Use cluster.rescan() to repair 元数据不一致 元数据偏离 `cluster.status()` 提到一个不在元数据中的成员，或一条已不在组中的旧记录（手动执行过 `START GROUP_REPLICATION`、旧成员未删除、服务器被重装）。不停机；只更新元数据。"}]},{"id":"mysql-logical-backup","file":"install/guides/mysql-logical-backup.json","group":"MySQL","engineId":"mysql","category":"backup","title":"MySQL 逻辑备份与恢复（mysqldump、MySQL Shell）","summary":"使用 mysqldump 或 MySQL Shell（util.dumpInstance / dumpSchemas / dumpTables + util.loadDump）进行逻辑备份与恢复：单表、单个 schema 或整个实例；仅结构或仅数据；迁移到另一台服务器；从完整 dump 中提取单表；通过 systemd/cron 定时执行、借助 login-path 免密码、经过校验并带保留期限的备份。每条路径最后都会比对行数。迁移、单","level":"中级","duration":"20–90 分钟（取决于数据量）","topology":"1 台服务器（迁移和实例恢复时另需 1 台目标服务器）","tags":["mysql","mysqldump","mysql shell","mysqlsh","dumpInstance","dumpSchemas","dumpTables","loadDump","逻辑备份","logical backup","导出","导入","恢复","restore","迁移","migration","login-path","mysql_config_editor","systemd timer","cron","备份","replica","dump-replica","source-data","innodb cluster","cron","systemd timer","flock","login-path"],"order":15,"steps":8,"scenarios":[{"k":"mode","v":"table","l":"导出 / 导入表","m":"备份与恢复 表备份 table export 复制表 copy table mysqldump 表 备份一个或多个表、将其复制到其他 schema/服务器，或进行恢复。源库保持运行；InnoDB 表不加锁。"},{"k":"mode","v":"schema","l":"备份 / 恢复一个 schema（数据库）","m":"备份与恢复 schema 备份 数据库备份 database backup dumpSchemas mysqldump database 备份整个数据库（schema），或将其加载到其他 schema/服务器。源库保持运行；目标端的同名表会被删除后重建。"},{"k":"mode","v":"instance","l":"整个实例（用户 + 所有 schema）","m":"备份与恢复 所有数据库 all databases dumpInstance 迁移用户 full logical backup 在本服务器上备份，在一台空服务器上恢复：用户、权限和所有 schema。用于重建服务器或更换版本/平台。"},{"k":"mode","v":"ddl","l":"仅结构（DDL）","m":"结构和数据分开 仅结构 schema only no-data ddlOnly CREATE TABLE export 表、视图、触发器、存储过程和事件的定义；不含数据。用于比对 schema、搭建空环境或将结构纳入版本控制。"},{"k":"mode","v":"data","l":"仅数据","m":"结构和数据分开 仅数据 data only no-create-info dataOnly 数据传输 目标端结构已存在，只传输行数据（例如刷新测试数据）。加载期间目标端的触发器可能被触发；本指南会对此进行检查。"},{"k":"mode","v":"migrate","l":"迁移到另一台服务器","m":"迁移 迁移 migration 更换服务器 迁移到新服务器 8.0 to 8.4 migration dump and load 迁移到 HeatWave 在目标服务器上操作，通过网络用 login-path 连接源库。切换时应用停止写入；停机时间等于 dump + 加载的时间。用于更换服务器、操作系统或版本。"},{"k":"mode","v":"extract","l":"从完整 dump 中提取单表","m":"恢复 恢复单表 从 dump 中提取表 extract table from dump 撤销 DROP TABLE 误删的行 ERROR 1146 Table doesn't exist 手头有一个完整 dump，只需要其中一个表（误删的行、被 DROP 的表）。在不触碰原 schema 的情况下将其提取到单独的 schema，或将被删除的表放回原处。"},{"k":"mode","v":"sched","l":"定时备份（systemd timer / cron）","m":"定时备份 定时备份 scheduled backup cron mysqldump systemd timer 自动备份 夜间备份 每天在固定时间自动执行逻辑备份：独立的备份用户、login-path、脚本、systemd timer 或 cron、保留期限和出错通知。源库保持运行。"}]},{"id":"mysql-security","file":"install/guides/mysql-security.json","group":"MySQL","engineId":"mysql","category":"security","title":"MySQL 安全加固（状态审核、网络、TLS、身份验证、权限、审计、加密）","summary":"这不是安装指南：它用于发现并修补运行中的 MySQL 8.4 / 8.0 服务器的安全缺口。先由只读的状态审核按严重程度列出问题，并说明每条发现对应哪张卡片；然后逐项或按顺序全部修补：bind_address、防火墙和账户的 host 限制；TLS、require_secure_transport 和客户端 VERIFY_IDENTITY；caching_sha2_password、密码策略、远程 root 和匿名账户；角色和无 DDL","level":"中级","duration":"5 分钟（审核）– 3 小时（完整加固）","topology":"单台 MySQL 服务器 + 应用服务器（客户端验证）；replica/InnoDB Cluster 见注释","tags":["mysql","安全","security","hardening","安全加固","cis benchmark","tls","ssl","require_secure_transport","verify_identity","caching_sha2_password","mysql_native_password","validate_password","audit_log","audit","percona","tde","keyring","innodb encryption","root","匿名用户","权限","角色","login-path"],"order":40,"steps":2,"scenarios":[{"k":"scenario","v":"scan","l":"状态审核：我有哪些安全缺口？（只读）","m":"入门 安全检查 security audit hardening check CIS 安全缺口 安全漏洞 vulnerability 渗透测试发现 mysql_secure_installation 审核查询 安全评估 security scan 严重 首先运行的扫描：匿名、无密码和远程 root 账户，任意地址（%）访问，弱身份验证插件，全局权限，TLS，审计，加密，文件权限和防火墙；不做任何更改。"},{"k":"scenario","v":"all","l":"完整加固（全部按顺序）","m":"入门 hardening 安全加固 全面安全 full hardening 应用 CIS benchmark STIG 新安装或从未加固过的服务器：所有项按安全顺序执行；MySQL 会重启数次（需要维护窗口）。"},{"k":"scenario","v":"net","l":"网络和连接：谁能从哪里连接？（bind_address、防火墙、host 限制）","m":"网络与身份 bind_address 0.0.0.0 '%' host '%' firewall 3306 33060 Host is not allowed to connect ERROR 1130 ERROR 2003 Can't connect to MySQL server skip_name_resolve 外部访问 限制访问 MySQL 在所有接口上监听，账户 host 为 '%'，端口对所有人开放。修改 bind_address 需要重启。"},{"k":"scenario","v":"tls","l":"加密连接（TLS、require_secure_transport、VERIFY_IDENTITY）","m":"网络与身份 ssl tls require_secure_transport ssl-mode VERIFY_IDENTITY VERIFY_CA ERROR 3159 Connections using insecure transport are prohibited server-cert.pem 证书有效期 ERROR 2026 SSL connection error certificate verify failed ERROR 3888 证书链 certificate chain 正在使用自动生成的自签名证书、接受未加密连接，或客户端不验证证书。使用 ALTER INSTANCE RELOAD TLS 无需停机。"},{"k":"scenario","v":"auth","l":"身份验证和密码（caching_sha2、密码策略、root）","m":"网络与身份 mysql_native_password caching_sha2_password validate_password password_history FAILED_LOGIN_ATTEMPTS 远程 root anonymous user 匿名用户 ERROR 1045 Access denied Plugin 'mysql_native_password' is not loaded ERROR 1524 ERROR 1819 ERROR 3955 ERROR 3118 Account is locked ERROR 2061 Authentication requires secure connection auth_socket mysql_native_password 账户、没有密码策略、远程 root、匿名账户和 test 数据库。无需停机。"},{"k":"scenario","v":"priv","l":"最小权限：角色和无 DDL 的应用账户","m":"权限与审计跟踪 GRANT REVOKE ALL PRIVILEGES SUPER FILE least privilege 最小权限 CREATE ROLE ERROR 1142 command denied ERROR 3530 ERROR 1227 you need (at least one of) the SUPER privilege DEFAULT ROLE 权限报告 应用账户拥有 CREATE/ALTER/DROP 或 *.* 权限，存在拥有 FILE/SUPER 的账户；读/写/DDL 角色及权限报告。无需停机。"},{"k":"scenario","v":"audit","l":"审计：谁做了什么？","m":"权限与审计跟踪 audit audit_log audit_log_filter 审计跟踪 general_log Enterprise Audit Percona audit SIEM 《个人信息保护法》(PIPL) PCI audit_log_filter_set_filter ERROR 1126 MySQL Enterprise 许可 谁做了什么 审计人员要求记录会话和查询。MySQL Community 没有内置审计：可选 Enterprise Audit、Percona Server Audit Log Filter，或 Community 下有限的替代方案。**许可：** Enterprise Audit 仅随 MySQL Enterprise 订阅提供；Percona Audit Log Filter 免费。"},{"k":"scenario","v":"tde","l":"静态数据加密（InnoDB 表空间加密、keyring）","m":"数据与备份 TDE encryption at rest ENCRYPTION='Y' keyring component_keyring_file ERROR 3185 Can't find master key from keyring binlog_encryption innodb_redo_log_encrypt ALTER INSTANCE ROTATE INNODB MASTER KEY mysqld.my keyring_component_status master key 针对磁盘或备份副本被盗的表、redo、undo 和 binlog 加密；安装 keyring component 需重启一次，大表需要 ALTER 时间。在 Community 版中同样无需许可；加密/KMS keyring 属于 Enterprise。"},{"k":"scenario","v":"backup","l":"备份安全（加密备份、login-path、权限）","m":"数据与备份 备份加密 backup encryption mysqldump gpg xtrabackup --encrypt login-path mysql_config_editor .my.cnf 密码 xbcrypt --encrypt-key-file 未加密备份 unencrypted backup 备份未加密或位于所有人可读的目录，脚本中有明文密码；mysqldump + GPG 或 XtraBackup --encrypt、备份账户和 login-path。"},{"k":"scenario","v":"os","l":"操作系统层面（服务账户、权限、core dump、SELinux）","m":"服务器与版本 SELinux permissive secure_file_priv local_infile core dump mysql 账户 .mysql_history ERROR 1290 secure-file-priv option ERROR 3948 Loading local data is disabled avc: denied 数据目录或证书文件对其他用户开放、secure_file_priv 为空、local_infile 已开启、SELinux 已关闭。部分设置在重启后生效。"},{"k":"scenario","v":"patch","l":"补丁与版本：我用的是受支持的版本吗？","m":"服务器与版本 EOL end of life 支持已终止 MySQL 8.0 EOL CPU Critical Patch Update CVE 安全补丁 补丁计划 MySQL 8.0 end of life 8.4 LTS 版本、支持终止（8.0 已于 2026 年 4 月终止）和待安装的更新；执行升级请参阅升级指南。只读。"}]},{"id":"mssql-linux-install","file":"install/guides/mssql-linux-install.json","group":"SQL Server","engineId":"mssql","category":"install","title":"在 Linux 上安装 SQL Server（RHEL / Ubuntu，可用于生产）","summary":"从 Microsoft 仓库在单台 Linux 服务器（RHEL 系列或 Ubuntu）上安装 SQL Server 2022 或 2025：选择 edition、安全地设置 sa 密码、mssql-tools18 (sqlcmd/bcp)、独立的数据/日志/tempdb/备份目录、按内存和 CPU 计算的 memory limit、max server memory、MAXDOP 和 tempdb 文件数，以及可选的 TLS 和防火墙","level":"入门","duration":"40–60 分钟","topology":"1 台服务器","tags":["sql server","mssql","linux","安装","install","mssql-conf","sqlcmd","tempdb","maxdop","rhel","ubuntu","2022","2025"],"order":1,"steps":22,"scenarios":[]},{"id":"mssql-windows-install","file":"install/guides/mssql-windows-install.json","group":"SQL Server","engineId":"mssql","category":"install","title":"在 Windows 上安装 SQL Server 2022（无人参与，可用于生产）","summary":"使用根据您的选择生成的 ConfigurationFile.ini，在单台 Windows Server 上以静默（无人参与）方式安装 SQL Server 2022（功能、实例、服务账户/gMSA、排序规则、数据/日志/tempdb/备份目录、tempdb 文件数、内存、MAXDOP、TCP）。安装前：磁盘格式、电源计划、即时文件初始化和 Lock Pages in Memory；安装后：T-SQL 设置、防火墙、验证以及系统数据库的","level":"中级","duration":"45–75 分钟","topology":"1 台 Windows Server","tags":["sql server","mssql","windows","安装","install","unattended","configurationfile.ini","setup.exe","gmsa","tempdb","maxdop","ifi","lock pages in memory","2022"],"order":2,"steps":18,"scenarios":[]},{"id":"mssql-ag-windows","file":"install/guides/mssql-ag-windows.json","group":"SQL Server","engineId":"mssql","category":"ha","title":"SQL Server Always On Availability Group (Windows, WSFC)","summary":"在已安装 SQL Server 2022 的 Windows 服务器上搭建 Always On Availability Group：Windows Server Failover Cluster (WSFC) 及仲裁见证（文件共享 / cloud witness）、启用 Always On、mirroring endpoint 及权限、使用 automatic seeding 的 AG、listener、同步/异步 replica、可","level":"高级","duration":"90–150 分钟","topology":"2–9 个 Windows 节点 (WSFC) + 见证 (witness)","tags":["sql server","mssql","always on","availability group","ag","wsfc","failover cluster","listener","witness","automatic seeding","hadr","windows","高可用"],"order":3,"steps":25,"scenarios":[]},{"id":"mssql-ag-linux","file":"install/guides/mssql-ag-linux.json","group":"SQL Server","engineId":"mssql","category":"ha","title":"SQL Server Always On Availability Group (Linux, Pacemaker)","summary":"在三台已安装 SQL Server 2022 的 Linux 服务器（RHEL 9 或 Ubuntu 22.04）上搭建由 Pacemaker 管理的 Always On AG：mssql-server-ha、hadr.hadrenabled、使用证书认证的 mirroring endpoint、CLUSTER_TYPE = EXTERNAL 的 AG（3 个同步 replica，或 2 个同步 + configuration-only","level":"高级","duration":"120–180 分钟","topology":"3 个 Linux 节点：3 个同步 replica，或 2 个同步 + configuration-only replica","tags":["sql server","mssql","always on","availability group","ag","linux","pacemaker","corosync","pcs","stonith","fencing","mssql-server-ha","ocf:mssql:ag","cluster_type external","configuration only","listener","virtual ip","rhel","ubuntu","高可用"],"order":4,"steps":30,"scenarios":[]},{"id":"mssql-cu-patch","file":"install/guides/mssql-cu-patch.json","group":"SQL Server","engineId":"mssql","category":"patch","title":"应用 SQL Server 累积更新（CU）","summary":"在同一主版本内，为已安装的 SQL Server 2022/2025 实例应用累积更新（CU）：Linux 上用 dnf/apt，Windows 上用 CU 安装程序以静默（/quiet）方式安装。适用于单服务器，或在 Always On AG 中滚动更新（先 secondary，然后 failover，最后旧 primary；Linux 上通过 Pacemaker）。包含预检查、COPY_ONLY 备份、验证和回滚；最终所有 repl","level":"中级","duration":"30–90 分钟","topology":"1 台服务器或 AG（2+ 个 replica）","tags":["sql server","mssql","cu","cumulative update","patch","更新","rolling","availability group","always on","yum","apt","kb"],"order":4,"steps":12,"scenarios":[]},{"id":"mssql-ag-ops","file":"install/guides/mssql-ag-ops.json","group":"SQL Server","engineId":"mssql","category":"ops","title":"SQL Server Always On AG 运维（Windows 和 Linux）","summary":"这不是安装指南，而是已安装的 Always On 可用性组的日常运维与故障场景：健康检查、延迟和日志增长诊断、计划内故障转移、强制故障转移（FORCE_FAILOVER_ALLOW_DATA_LOSS）及其后续处理、挂起/恢复数据移动、同步/异步模式、添加数据库（自动种子设定或备份/还原）、添加/删除副本、只读路由和侦听器变更。Windows 上使用 WSFC 命令，Linux 上使用 Pacemaker（pcs）命令。","level":"高级","duration":"15–90 分钟（视场景而定）","topology":"2–9 个副本的 AG（Windows WSFC 或 Linux Pacemaker）","tags":["sql server","mssql","always on","availability group","ag","failover","forced failover","force_failover_allow_data_loss","suspend","resume","automatic seeding","read-only routing","listener","pacemaker","pcs","wsfc","secondary lag","log_send_queue","redo_queue","required_synchronized_secondaries_to_commit"],"order":5,"steps":3,"scenarios":[{"k":"op","v":"health","l":"AG 健康检查（仪表板查询）","m":"监视与诊断 NOT SYNCHRONIZING NOT_HEALTHY DISCONNECTED RESOLVING PARTIALLY_HEALTHY AG 健康状况 availability group health sys.dm_hadr dashboard 副本状态 Msg 35206 connection timeout AG dashboard Show Dashboard 一切是否正常？副本/数据库状态、故障转移就绪情况、群集以及最近的角色变更。不做任何更改。"},{"k":"op","v":"lag","l":"辅助副本落后 / 日志文件增长（延迟诊断）","m":"监视与诊断 AVAILABILITY_REPLICA log_reuse_wait log_send_queue redo_queue HADR_SYNC_COMMIT secondary lag secondary_lag_seconds Msg 9002 9002 transaction log is full 延迟 redo blocked 日志增长 send/redo 队列在增长、辅助副本上的数据陈旧、主副本上的日志即将写满（log_reuse_wait = AVAILABILITY_REPLICA），或者提交变慢（HADR_SYNC_COMMIT）。"},{"k":"op","v":"failover","l":"计划内故障转移（switchover，无数据丢失）","m":"角色变更 planned failover manual failover switchover ALTER AVAILABILITY GROUP FAILOVER pcs resource move Msg 41142 41142 Msg 19407 切换角色 切换主副本 Failover Cluster Manager Move 维护故障转移 为维护、打补丁或测试，把主副本切换到一个同步提交且处于 SYNCHRONIZED 状态的辅助副本；中断几秒钟，无数据丢失。"},{"k":"op","v":"forced","l":"主副本丢失：强制故障转移（FORCE_FAILOVER_ALLOW_DATA_LOSS）","m":"角色变更 FORCE_FAILOVER_ALLOW_DATA_LOSS forced failover 强制故障转移 quorum lost 仲裁丢失 FixQuorum forcequorum disaster recovery DR Msg 41131 Msg 35250 站点丢失 primary down Msg 47104 ForceQuorum Cluster is running in ForceQuorum state RESOLVING_NORMAL 主副本崩溃 数据丢失 主副本（或整个站点/群集仲裁）丢失且无法等待：接受数据丢失，把一个辅助副本提升为主副本；然后比对数据并恢复其他副本。"},{"k":"op","v":"suspend","l":"挂起 / 恢复数据移动（SUSPEND / RESUME）","m":"维护 SUSPENDED SET HADR SUSPEND SET HADR RESUME is_suspended suspend_reason data movement 数据移动已挂起 Msg 35264 35264 Msg 35265 SUSPEND_FROM_REDO SUSPEND_FROM_USER SUSPEND_FROM_PARTNER 已挂起 挂起数据库 暂时停止向辅助副本发送数据（网络维护、批量操作），或恢复卡在 SUSPENDED 状态的数据库。"},{"k":"op","v":"mode","l":"更改同步 / 异步模式","m":"维护 SYNCHRONOUS_COMMIT ASYNCHRONOUS_COMMIT availability mode REQUIRED_SYNCHRONIZED_SECONDARIES_TO_COMMIT required_synchronized_secondaries_to_commit 同步 异步 Msg 35211 35211 Msg 35212 35212 FAILOVER_MODE 将副本在 SYNCHRONOUS_COMMIT 和 ASYNCHRONOUS_COMMIT 之间切换（远程站点、慢速网络、DR 演练），并配置提交保护（REQUIRED_SYNCHRONIZED_SECONDARIES_TO_COMMIT）。"},{"k":"op","v":"adddb","l":"向 AG 添加数据库（自动种子设定或备份/还原）","m":"变更 ADD DATABASE automatic seeding SET HADR AVAILABILITY GROUP Msg 35250 Msg 1478 1478 seeding failed 添加数据库 Msg 1475 1475 might contain bulk logged changes manual seeding 把新的或现有的数据库加入 AG：中小型数据库使用自动种子设定，大型数据库或网络较慢时从备份初始化。"},{"k":"op","v":"addrep","l":"添加副本（节点）","m":"变更 ADD REPLICA Add-ClusterNode pcs cluster node add 新副本 添加副本 Msg 41158 41158 Failed to join local availability replica JOIN 向 AG 添加新的辅助副本：节点加入群集，准备端点，ADD REPLICA + JOIN，数据库通过种子设定同步过来。"},{"k":"op","v":"removerep","l":"删除副本（节点）","m":"变更 REMOVE REPLICA Remove-ClusterNode pcs cluster node remove 删除副本 decommission Msg 35223 35223 删除节点 从 AG 和群集中删除一个将要下线或重建的辅助副本。"},{"k":"op","v":"routing","l":"只读路由（read-only routing）","m":"连接 read-only routing READ_ONLY_ROUTING_URL READ_ONLY_ROUTING_LIST ApplicationIntent=ReadOnly readable secondary 可读辅助副本 Msg 978 978 Msg 976 976 not accessible for queries Msg 10786 ApplicationIntent 将 ApplicationIntent=ReadOnly 连接通过侦听器路由到可读辅助副本（把报表负载从主副本卸载）。"},{"k":"op","v":"listener","l":"创建或修改侦听器（IP、端口）","m":"连接 listener ADD LISTENER MODIFY LISTENER MultiSubnetFailover RegisterAllProvidersIP Msg 19471 19471 Msg 41009 virtualip Msg 19456 19456 HostRecordTTL 侦听器端口 DNS 记录 没有侦听器时创建它，修改其端口，或（Windows 上）为第二个子网添加 IP。"}]},{"id":"mssql-backup","file":"install/guides/mssql-backup.json","group":"SQL Server","engineId":"mssql","category":"backup","title":"SQL Server 备份策略、自动化与还原","summary":"为已安装的 SQL Server 实例（Linux 或 Windows；单实例、FCI 或 AG 副本）配置备份并从备份中恢复。备份： 选择恢复模式，每周完整 + 每日差异 + 每 n 分钟一次日志备份，COMPRESSION、CHECKSUM、证书加密，保留期与调度（SQL Server Agent T-SQL 作业或 Ola Hallengren；Express 版使用 Task Scheduler / cron），一次性 COPY","level":"中级","duration":"30–90 分钟","topology":"1 台服务器、故障转移群集实例或 Always On AG 副本（还原到其他服务器：源 + 目标）","tags":["sql server","mssql","backup","备份","备份数据库","restore","还原数据库","pitr","stopat","point in time","differential","transaction log","copy_only","compression","checksum","encryption","备份加密","sql server agent","job","ola hallengren","maintenance solution","retention","verifyonly","headeronly","filelistonly","norecovery","standby","with move","task scheduler","cron","availability group","always on","backup preference","automated_backup_preference","fn_hadr_backup_is_preferred_replica","copy_only","sql agent","sysjobhistory","cron","task scheduler","page restore","suspect_pages","824","823","33111","18456","orphaned user","sp_help_revlogin","master restore","single user","msdb restore","model restore","stopatmark","tail-log","no_truncate","recovery_pending","suspect","log chain","lsn"],"order":15,"steps":11,"scenarios":[{"k":"mode","v":"strategy","l":"建立备份策略（计划备份 + 保留期）","m":"备份 备份计划 backup strategy 日志文件增长 LOG_BACKUP Msg 9002 transaction log is full sql agent job Msg 3013 3013 BACKUP DATABASE is terminating abnormally 备份未执行 备份作业 新服务器或没有定期备份的数据库：配置完整 / 差异 / 日志作业、保留期和监控。"},{"k":"mode","v":"oneoff","l":"执行一次性备份（例如变更前的 COPY_ONLY）","m":"备份 copy_only 一次性备份 变更前备份 手动备份 Msg 4214 4214 Msg 4208 4208 BACKUP LOG is not allowed while the recovery model is SIMPLE 在变更、补丁或升级之前，或为复制到测试环境而做的单次备份；COPY_ONLY 不会破坏备份链。"},{"k":"mode","v":"verify","l":"验证备份（历史记录、VERIFYONLY、测试还原）","m":"演练与检查 VERIFYONLY HEADERONLY FILELISTONLY test restore 备份是否损坏 Msg 3241 Msg 3183 Msg 3203 用于证明备份确实可以还原：备份历史记录、VERIFYONLY、HEADERONLY / FILELISTONLY，以及还原到临时数据库进行测试 + CHECKDB。"},{"k":"mode","v":"chain","l":"备份链是否完整？（LSN 断档、缺失文件）","m":"演练与检查 Msg 4305 4305 too recent to apply LSN log chain 备份链断开 broken log chain Msg 3136 last_log_backup_lsn 备份链 在还原之前或作为定期审核：根据 msdb 历史记录重建备份链，找出 LSN 断档、磁盘上不存在的文件以及中间插入的备份。不做任何更改。"},{"k":"mode","v":"restore","l":"从备份还原数据库（备份链、STOPAT / STOPATMARK、MOVE / REPLACE、其他服务器）","m":"还原与时间点恢复 STOPAT STOPATMARK STOPBEFOREMARK point in time PITR 误删除 撤销 DELETE WITH MOVE REPLACE tail-log Msg 3154 Msg 3159 Msg 5133 Msg 1834 Msg 3156 The tail of the log for the database Msg 3101 3101 Exclusive access could not be obtained Msg 4335 4335 RESTORE DATABASE 数据被删除或被错误更新，需要数据库副本，或要迁移到其他服务器；实例正在运行。完整 + 差异 + 日志链，还原到某个时间点或标记事务，使用不同名称 / 目录，借助尾日志还原到最后时刻。"},{"k":"mode","v":"damaged","l":"数据库无法打开：数据文件丢失或损坏（尾日志 + 还原）","m":"丢失 / 损坏 Msg 945 945 RECOVERY_PENDING recovery pending SUSPECT Msg 5120 5120 Msg 17204 Msg 17207 Msg 5181 FCB::Open failed Operating system error 2 mdf 被删除 数据文件丢失 database cannot be opened Msg 823 NO_TRUNCATE CONTINUE_AFTER_ERROR EMERGENCY 数据库处于 RECOVERY_PENDING 或 SUSPECT 状态，.mdf/.ndf 无法读取，但实例正在运行：先使用 NO_TRUNCATE 进行尾日志备份，再还原到最后时刻。在操作完成之前数据库不可用。"},{"k":"mode","v":"page","l":"少数页损坏（Msg 823 / 824）：页还原","m":"丢失 / 损坏 Msg 824 824 Msg 823 823 Msg 829 829 suspect_pages torn page incorrect checksum 损坏页 page corruption page restore CHECKDB found Msg 8928 Msg 8939 Msg 8976 repair_allow_data_loss 查询或 CHECKDB 在特定页上报错（msdb.dbo.suspect_pages）：只从备份还原损坏的页。在 Enterprise/Developer 版中数据库保持联机，在 Standard/Express 版中会短时间脱机。"},{"k":"mode","v":"master","l":"还原 master 数据库（单用户模式）","m":"系统数据库 master Msg 3108 3108 single user mode single-user 单用户模式 -m Msg 3417 3417 Msg 17113 Msg 18461 master 损坏 force-setup REBUILDDATABASE master 损坏，或登录名 / 链接服务器 / 服务器设置丢失：以单用户模式（-m）启动实例，还原 master，SQL Server 随后自行关闭。需要停机；如果实例完全无法启动，先重新生成系统数据库。"},{"k":"mode","v":"sysdb","l":"还原 msdb 或 model 数据库","m":"系统数据库 msdb model Msg 3168 3168 作业丢失 msdb suspect Msg 1807 msdb 损坏 model 损坏 作业、备份历史记录、Database Mail（msdb）或新数据库模板（model）损坏 / 丢失：使用同一内部版本上做的备份 WITH REPLACE 还原；Agent 会短暂停止。"},{"k":"mode","v":"orphan","l":"还原后用户无法连接（孤立用户、登录名映射）","m":"迁移与访问 Msg 18456 18456 Login failed for user orphaned user orphan 孤立用户 sp_help_revlogin sp_change_users_login Msg 15023 15023 Msg 916 Msg 4060 登录名映射 SID 数据库被迁移或还原到其他服务器，应用程序收到 'Login failed'：从源服务器连同 SID 和密码哈希一起传输登录名，其余用户映射到登录名。数据库保持联机。"},{"k":"mode","v":"enc","l":"加密备份无法还原（缺少证书，Msg 33111）","m":"迁移与访问 Msg 33111 33111 Cannot find server certificate with thumbprint 加密备份 encrypted backup 证书 certificate TDE Msg 15581 15581 master key 备份（或启用 TDE 的数据库的备份）使用证书加密，而本服务器上没有该证书：找出 thumbprint，连同私钥加载证书，然后验证备份。不触及现有数据库。"}]},{"id":"mssql-bcp-export","file":"install/guides/mssql-bcp-export.json","group":"SQL Server","engineId":"mssql","category":"backup","title":"SQL Server 按表导出/导入 (bcp, sqlpackage)","summary":"这不是备份，而是逻辑数据迁移：用 bcp 导出表并加载到另一个数据库（native 或字符格式、格式文件、batch、TABLOCK、标识列、UTF-8），将查询结果导出为 CSV/文本文件 (queryout)，用 sqlpackage 迁移架构 + 数据（.bacpac Export/Import，例如迁移到 Azure SQL Database），以及仅迁移架构（.dacpac Extract、Script、Publish）。包括","level":"中级","duration":"20–60 分钟","topology":"1 台服务器（命令在 SQL Server 上或一台管理服务器上运行）","tags":["sql server","mssql","bcp","bulk copy","export","import","queryout","format file","native","csv","utf-8","sqlpackage","bacpac","dacpac","extract","publish","azure sql","迁移","migration","表复制"],"order":16,"steps":8,"scenarios":[{"k":"op","v":"table","l":"迁移表：bcp out → bcp in","m":"数据迁移 bcp bcp out bcp in bulk copy 复制表 迁移表 Unexpected EOF encountered in BCP data-file Unable to open BCP host data-file 将一个或多个表复制到另一个 SQL Server 数据库；目标端必须事先建好表。"},{"k":"op","v":"query","l":"将查询结果导出到文件：bcp queryout (CSV/文本)","m":"导出 queryout csv csv 导出 查询结果导出到文件 export to file 将查询结果写入 CSV/文本文件，提供给其他系统（Excel、ETL、报表）。"},{"k":"op","v":"bacpac","l":"架构 + 数据：sqlpackage Export / Import (.bacpac)","m":"数据迁移 bacpac sqlpackage SqlPackage Export SqlPackage Import Azure SQL Database 迁移 SQL71564 SQL71501 用一个文件迁移架构和数据；特别适用于迁移到 Azure SQL Database 或中小型数据库。"},{"k":"op","v":"dacpac","l":"仅架构：sqlpackage Extract / Script / Publish (.dacpac)","m":"架构 dacpac Extract Publish 架构比较 schema compare BlockOnPossibleDataLoss The schema update is terminating because data loss might occur 仅提取架构、与目标比较并生成变更脚本，或在新环境中部署空架构。"}]},{"id":"mssql-security","file":"install/guides/mssql-security.json","group":"SQL Server","engineId":"mssql","category":"security","title":"SQL Server 安全加固（Linux 与 Windows：检查、网络、TLS、身份验证、权限、审核、TDE）","summary":"这不是安装指南：它查找并修补正在运行的 SQL Server 2019 / 2022 / 2025 实例（Linux 或 Windows）的安全漏洞。首先由只读的安全检查按严重程度列出漏洞，并指出每项发现对应哪张卡片；然后逐项或按顺序全部修补：监听地址与防火墙、Force Encryption + 证书及客户端验证、禁用 sa 与密码策略（CHECK_POLICY）、角色与无 DDL 权限的应用用户、攻击面（xp_cmdshell、TR","level":"中级","duration":"5 分钟（检查）– 3 小时（完整加固）","topology":"单个 SQL Server 实例（Linux 或 Windows）+ 应用服务器；Always On AG 见说明","tags":["sql server","mssql","安全","security","hardening","安全加固","cis benchmark","stig","force encryption","tls","证书","sa","check_policy","xp_cmdshell","trustworthy","server audit","audit","tde","transparent data encryption","backup encryption","mssql-conf","windows","linux"],"order":40,"steps":2,"scenarios":[{"k":"scenario","v":"scan","l":"安全检查：有哪些漏洞？（只读）","m":"入门 安全检查 security audit hardening check CIS STIG 安全漏洞 弱点 vulnerability 渗透测试发现 SQL Vulnerability Assessment 检查查询 安全扫描 security scan 严重 首先运行的扫描：sa、密码策略、空密码、sysadmin 成员、public/guest 权限、xp_cmdshell、未加密连接、审核、TDE 与证书备份、端口与防火墙；不做任何更改。"},{"k":"scenario","v":"all","l":"完整加固（按顺序全部执行）","m":"入门 hardening 安全加固 全面安全 full hardening 执行 CIS benchmark STIG 新安装或从未加固的实例：所有方面按安全的顺序执行；SQL Server 会重启一到两次（需要维护窗口）。"},{"k":"scenario","v":"net","l":"网络与连接：谁能从哪里连接？（监听、防火墙、Browser）","m":"网络与身份 1433 1434 SQL Browser firewall ListenOnAllIPs network.ipaddress Error 53 Error 40 TCP/IP Error 26 Error locating Server/Instance Specified Login timeout expired A network-related or instance-specific error 外部访问 限制访问 SQL Server 在所有接口上监听，1433 对所有人开放，SQL Browser 在不需要时仍在运行。修改监听地址需要重启。"},{"k":"scenario","v":"tls","l":"加密连接（Force Encryption、证书、客户端验证）","m":"网络与身份 Force Encryption TrustServerCertificate Encrypt=Mandatory certificate chain was issued by an authority that is not trusted SSL Provider error 0x80090325 tlscert 证书过期 TDS 8.0 strict The target principal name is incorrect certificate verify failed Encrypt=Strict Force Strict Encryption Unable to load user-specified certificate 证书链 certificate chain 连接未加密或使用自签名的临时证书；客户端使用 TrustServerCertificate"},{"k":"scenario","v":"auth","l":"身份验证与密码（sa、CHECK_POLICY、Windows 身份验证）","m":"网络与身份 sa CHECK_POLICY CHECK_EXPIRATION mixed mode Login failed for user Error 18456 Error 18486 account is locked out 密码策略 password policy PWDCOMPARE Error 18452 Login is from an untrusted domain Error 18470 The account is disabled Msg 15118 Msg 33062 sa_disabled Windows Authentication sa 已启用，SQL 登录名没有密码策略，存在空密码/弱密码；个人 DBA 账户与禁用 sa。无停机（只有切换到 Windows 身份验证需要重启）。"},{"k":"scenario","v":"priv","l":"最小权限与攻击面（角色、public、guest、xp_cmdshell）","m":"权限与审核跟踪 db_owner sysadmin public guest xp_cmdshell TRUSTWORTHY CONTROL SERVER least privilege 最小权限 Msg 262 Msg 229 permission was denied Msg 15247 Msg 916 db_ddladmin EXECUTE AS 权限报告 应用用户是 db_owner 或 sysadmin，public/guest 有多余权限，xp_cmdshell/TRUSTWORTHY 已启用；角色设计与权限报告。无停机。"},{"k":"scenario","v":"audit","l":"审核：谁做了什么？（Server Audit、Database Audit Specification）","m":"权限与审核跟踪 SQL Server Audit server audit audit specification fn_get_audit_file C2 audit login auditing 审核跟踪 SIEM 《个人信息保护法》(PIPL) PCI sqlaudit Server Audit Specification Database Audit Specification Msg 33072 谁做了什么 审计人员要求记录登录、权限和架构变更；Server Audit 写入文件（或 Windows 事件日志），转发到集中系统并保留。无停机。所有版本均无需额外许可（2016 SP1+）。"},{"k":"scenario","v":"tde","l":"加密静态数据（TDE）","m":"数据与备份 TDE transparent data encryption database encryption key DEK service master key Msg 33111 Cannot find server certificate with thumbprint encryption at rest Msg 15208 ENCRYPTION_IN_PROGRESS percent_complete Msg 15581 证书备份 TDE 防止磁盘、数据文件或备份在其他服务器上被打开：DMK、证书、DEK。联机完成；证书备份是必需的（证书丢失后数据库及其备份都无法打开）。**许可：** SQL Server 2019+ Standard 和 Enterprise 版提供，Express 版没有。"},{"k":"scenario","v":"backup","l":"备份安全（加密备份、证书、权限）","m":"数据与备份 backup encryption 备份加密 WITH ENCRYPTION BACKUP CERTIFICATE 备份权限 Msg 33111 Msg 15151 RESTORE VERIFYONLY key_algorithm Msg 15240 备份未加密，备份目录对所有人开放，脚本中密码为明文；BACKUP … WITH ENCRYPTION、证书备份与目录权限。无停机。备份加密在 Standard 和 Enterprise 版中提供，Express 版没有。"},{"k":"scenario","v":"os","l":"操作系统层面（服务账户、文件权限、SELinux）","m":"服务器与版本 service account 服务账户 LocalSystem gMSA virtual account icacls SELinux mssql 用户 NT Service\\MSSQLSERVER mssql-server-selinux unconfined_service_t Configuration Manager Log On 服务以 LocalSystem 或管理员账户运行，数据/备份目录对所有人开放，SELinux 已关闭；更改服务账户需要重启。"},{"k":"scenario","v":"patch","l":"补丁与版本：是否在受支持的版本和最新 CU 上？","m":"服务器与版本 CU cumulative update GDR EOL end of support 停止支持 CVE 安全更新 ProductUpdateLevel ProductUpdateReference Latest updates 补丁计划 版本、CU 级别和支持结束时间（2016 已于 2026 年 7 月结束支持）；实际打补丁请参见 CU 补丁指南。只读。"}]},{"id":"mongo-replset","file":"install/guides/mongo-replset.json","group":"MongoDB","engineId":"mongodb","category":"ha","title":"MongoDB replica set 安装（3 个成员、keyfile 和授权）","summary":"从零搭建一个三成员的 MongoDB 8.0（或 7.0）replica set：官方软件源、XFS 数据目录、按内存设置的 WiredTiger cache、成员间 keyfile 认证、授权、管理员和应用用户、read/write concern、failover 测试和连接字符串；最终得到一个凭密码访问、可在丢失单个成员时不中断服务的 replica set。如果数据量或写入负载将超出单台服务器，请参阅 sharded clust","level":"中级","duration":"60–90 分钟","topology":"3 台服务器（1 primary + 2 secondary），承载数据的成员为奇数","tags":["mongodb","mongo","replica set","replset","rs.initiate","keyfile","failover","stepdown","write concern","wiredtiger","高可用","ha","rhel","ubuntu"],"order":1,"steps":36,"scenarios":[]},{"id":"mongo-sharded","file":"install/guides/mongo-sharded.json","group":"MongoDB","engineId":"mongodb","category":"ha","title":"安装 MongoDB 分片集群（配置服务器 + 分片 + mongos）","summary":"当数据量或写入负载超出单个副本集的承受能力时搭建的分片集群：三成员的配置服务器副本集、两个或三个分片副本集和两个 mongos 路由器；使用 keyfile 的内部身份验证、授权、sh.addShard、片键选择、对集合分片、均衡器以及数据分布验证。最终得到一个可水平扩展的集群，应用只通过 mongos 访问。如果数据能放在单台服务器上，安装副本集更简单，也足够。","level":"高级","duration":"2–3 小时","topology":"3 台配置服务器 + 2×3 个分片成员 + 2 个 mongos（至少 11 台服务器；测试时可更少）","tags":["mongodb","sharding","sharded cluster","mongos","config server","shard key","addShard","balancer","hashed","扩展","rhel","ubuntu"],"order":2,"steps":60,"scenarios":[]},{"id":"mongo-upgrade","file":"install/guides/mongo-upgrade.json","group":"MongoDB","engineId":"mongodb","category":"upgrade","title":"MongoDB rolling upgrade (7.0 → 8.0)：replica set 与 sharded cluster","summary":"将运行中的 MongoDB 7.0 replica set 或 sharded cluster 不停机（rolling）升级到 8.0：预检查（版本、FCV、成员状态、驱动兼容性）、备份、先升级 secondary，再 stepDown 并升级原 primary；sharded cluster 的顺序为停止 balancer、config server、各 shard、mongos。FCV 8.0 可选择推迟到 burn-in 之后；包","level":"高级","duration":"60–120 分钟（与数据量无关；每个成员几分钟）","topology":"Replica set（3 个以上成员）或 sharded cluster（config server + 各 shard + mongos）","tags":["mongodb","upgrade","版本升级","rolling upgrade","8.0","7.0","featureCompatibilityVersion","fcv","stepdown","replica set","sharded","balancer","config server","mongos","fcv","featureCompatibilityVersion","rolling upgrade"],"order":3,"steps":30,"scenarios":[]},{"id":"mongo-backup","file":"install/guides/mongo-backup.json","group":"MongoDB","engineId":"mongodb","category":"backup","title":"MongoDB 备份与恢复 (mongodump, mongorestore, snapshot)","summary":"为已安装的 MongoDB 建立备份机制，并按场景逐一说明如何从备份恢复。备份：用 mongodump 备份单个 collection、数据库或整个 deployment（--archive --gzip；在 replica set 上用 --oplog 取得一致时间点，并从 secondary 读取）、凭据文件、通过 systemd timer/cron 执行带保留期的定时备份、测试恢复，以及可选的文件系统 snapshot。恢复：恢","level":"中级","duration":"45–90 分钟","topology":"备份服务器或某个成员 + replica set / 单实例 / sharded cluster (mongos)","tags":["mongodb","mongo","备份","backup","restore","恢复","mongodump","mongorestore","oplog","oplogReplay","nsFrom","nsTo","mongoexport","mongoimport","fsyncLock","lvm snapshot","systemd timer","cron","retention","balancer","activeWindow","config server","cron","systemd timer","flock","oplog","恢复场景","point in time recovery","pitr","oplogLimit","nsInclude","dryRun","sharded restore","克隆","已删除的 collection","误删除"],"order":15,"steps":20,"scenarios":[{"k":"scenario","v":"setup","l":"配置备份：mongodump、定时与测试恢复","m":"备份 mongodump 配置备份 backup setup systemd timer cron retention mongorestore backup user backup.yaml Failed: can't create session oplog overflow mongodump was unable to capture all new oplog entries 备份 备份用户、凭据文件、手动和定时 mongodump、以新名称进行测试恢复；可选 snapshot 和 mongoexport。首次配置请从这里开始。"},{"k":"scenario","v":"r_dry","l":"备份真的能恢复吗？(--dryRun 及在临时实例上演练)","m":"演练与检查 dryRun --dryRun restore test 恢复演练 备份验证 backup verify gzip: invalid compressed data unexpected EOF archive is corrupt 归档损坏 gzip -t 备份能否恢复 不触碰生产数据：检查归档完整性、其中包含的 namespace 和 oplog，并可选择完整恢复到临时 mongod 并核对文档数。"},{"k":"scenario","v":"r_full","l":"从备份恢复全部数据 (完整恢复, --drop)","m":"恢复 full restore 完整恢复 全库恢复 --drop mongorestore --drop 数据丢失 data loss ransomware 勒索软件 数据全部被删除 dropDatabase no oplog file to replay oplogReplay applyOps not authorized … applyOps admin.tempusers 数据大量丢失或损坏，deployment 将回到备份时刻。备份之后的写入会丢失；恢复期间应用停机。"},{"k":"scenario","v":"r_ns","l":"恢复单个数据库或 collection (--nsInclude，以新名称或原位)","m":"恢复 --nsInclude --nsFrom --nsTo 单个 collection single collection restore collection 被删除 collection dropped drop collection 已删除的文档 deleted documents deleteMany namespace restore E11000 duplicate key error $merge orders_restored 恢复单个数据库 某个 collection/数据库被删除或损坏，其余数据完好：只从备份恢复该 namespace；其他数据不受影响，应用可继续运行。"},{"k":"scenario","v":"r_pitr","l":"恢复到指定时间点：备份 + oplog (误 drop、错误的 update/delete)","m":"时间点恢复 point in time PITR 时间点恢复 --oplogReplay --oplogLimit oplog replay oplog.rs 误删除 accidental drop 错误的 update wrong update drop 之前 Timestamp oplog 已覆盖 rollback to time 恢复到指定时间 错误操作发生在备份之后，且到该时刻为止的写入也需要保留：先恢复最近一次完整备份，再将生产 oplog 应用到错误操作之前 (--oplogReplay --oplogLimit)。仅适用于 replica set。"},{"k":"scenario","v":"r_other","l":"将备份恢复到其他 cluster / 服务器 (克隆、测试环境、迁移)","m":"迁移与克隆 clone 克隆 恢复到其他服务器 restore to another cluster migration 服务器迁移 测试环境 refresh nsExclude admin.system.users Invalid feature compatibility version cannot use --oplogReplay with excludes specified 复制到测试环境 不触碰源 deployment：备份恢复到另一个 replica set 或实例（测试/报表副本、服务器迁移、版本升级）；可选迁移用户。"},{"k":"scenario","v":"r_shard","l":"恢复 sharded cluster：mongodump 的局限与正确方法","m":"迁移与克隆 sharded restore sharded cluster restore mongos restore shard key shardCollection config server restore balancer inconsistent 备份不一致 PBM Percona Backup Ops Manager --oplog mode only supported on full dumps 通过 mongos 备份 备份的拓扑是 sharded cluster：通过 mongos 进行逻辑恢复（重建 shard key、balancer），或使用协调一致的 snapshot 以物理方式重建整个 cluster。"}]},{"id":"mongo-ops","file":"install/guides/mongo-ops.json","group":"MongoDB","engineId":"mongodb","category":"ops","title":"MongoDB 日常运维与故障场景（replica set 和 sharded cluster）","summary":"这不是安装指南：而是一份按场景组织的 runbook，用于日常管理已安装的 MongoDB replica set 和 sharded cluster，并在出现故障时恢复。内容包括健康检查、添加/删除成员、priority/hidden/delayed 成员、用数据成员替换 arbiter、重新同步落后的成员、多数派丢失时的 force reconfig、计划内 stepDown、oplog 大小、FCV；添加/删除 shard、bal","level":"高级","duration":"5 分钟（检查）– 数小时（initial sync、drain、reshard）","topology":"Replica set（3 个以上成员）或 sharded cluster（config server + shard replica set + mongos）","tags":["mongodb","mongo","replica set","sharded cluster","rs.status","rs.add","rs.remove","rs.reconfig","force reconfig","stepDown","priority","hidden","delayed","secondaryDelaySecs","arbiter","resync","initial sync","too stale","oplog","replSetResizeOplog","FCV","setFeatureCompatibilityVersion","addShard","removeShard","movePrimary","balancer","activeWindow","jumbo","chunk","reshardCollection","shard key","运维","故障"],"order":20,"steps":3,"scenarios":[{"k":"scenario","v":"health","l":"健康检查：成员、延迟、oplog 窗口（sharded 时还包括 shard 和 balancer）","m":"监控与检查 rs.status rs.status() replication lag 延迟 replLag printSecondaryReplicationInfo sh.status health 健康检查 NotYetInitialized RECOVERING STARTUP2 UNKNOWN (not reachable/healthy) DOWN ReplicaSetNoPrimary Server selection timed out lastHeartbeatMessage 只读：成员状态、复制延迟、oplog 窗口、配置；在 sharded cluster 上还包括 shard 列表、balancer 和 chunk 分布。不确定该做什么时，从这里开始。"},{"k":"scenario","v":"add","l":"向 replica set 添加新成员（initial sync）","m":"成员管理 rs.add 添加成员 add member initial sync 新 secondary newlyAdded STARTUP2 scale out rs.add() Quorum check failed Found two member configurations with same host field votes: 0 新服务器已准备好（相同版本、keyfile、replSetName）；先以 priority 0 / votes 0 加入，initial sync 完成并变为 SECONDARY 后再赋予投票权和 priority。无停机。"},{"k":"scenario","v":"remove","l":"从 replica set 删除成员","m":"成员管理 rs.remove 删除成员 remove member decommission 下线服务器 REMOVED Our replica set config is invalid or we are not a member of it rs.remove() 服务器即将下线或已永久丢失：从配置中删除该成员，并确认剩余投票仍构成多数。如果它是 primary，先执行 stepDown。"},{"k":"scenario","v":"member_cfg","l":"更改成员角色：priority、hidden、delayed（延迟）成员","m":"成员管理 priority hidden delayed secondaryDelaySecs slaveDelay 隐藏成员 延迟成员 votes rs.reconfig priority must be 0 when hidden=true only one voting member can be added or removed at a time rs.reconfig() 更改成员成为 primary 的优先级，将其设为用于报表/备份的隐藏（hidden）成员或防止误删的延迟（delayed）成员，或恢复为普通成员。"},{"k":"scenario","v":"arbiter","l":"用数据成员替换 arbiter（PSA → PSS）","m":"成员管理 arbiter arbiterOnly PSA rs.addArb majority write concern cache pressure w: majority 等待中 Rejecting reconfig where the new config has a PSA topology implicit default write concern majority commit point Replica set 为 primary + secondary + arbiter：一个数据成员宕机后，majority 写入和缓存压力会成为问题。添加一个新的数据成员，然后删除 arbiter。"},{"k":"scenario","v":"resync","l":"重新同步落后或损坏的成员（resync）","m":"故障与恢复 too stale too stale to catch up RS102 resync 重新同步 initial sync RECOVERING WiredTiger error WT_PANIC 数据文件损坏 data corruption OplogStartMissing maintenance mode InitialSyncFailure fassert checksum mismatch 成员卡在 RECOVERING 并报告 'too stale to catch up'，或其数据文件损坏：清空该成员的数据目录，通过 initial sync 从头同步。其他成员继续运行。"},{"k":"scenario","v":"force","l":"多数派丢失：用存活成员强制重新配置（force reconfig）","m":"故障与恢复 force reconfig force: true no primary 没有 primary 多数派丢失 majority lost NotWritablePrimary not master no replset config split brain DR site 站点丢失 ReplicaSetNoPrimary Server selection timed out rs.reconfig force 大多数成员已永久丢失（站点/服务器丢失），存活成员无法选出 primary：用存活成员执行 rs.reconfig(cfg, {force: true})。这是最后手段；使用不当会导致数据丢失和 split brain。"},{"k":"scenario","v":"stepdown","l":"计划内切换 primary（rs.stepDown，指定某成员成为 primary）","m":"维护 stepDown rs.stepDown replSetStepDown 切换 primary switchover 计划内维护 maintenance rs.freeze No electable secondaries caught up InterruptedDueToReplStateChange PrimarySteppedDown primary 切换 需要在 primary 服务器上做维护/打补丁：primary 降级，一个已追平的 secondary 成为 primary。应用可能有几秒钟出现写入错误。"},{"k":"scenario","v":"oplog","l":"更改 oplog 大小（replSetResizeOplog）","m":"维护 oplog oplog window oplog 窗口 replSetResizeOplog oplogSizeMB oplogMinRetentionHours too stale oplog overflow rs.printReplicationInfo getReplicationInfo log length start to end oplog 窗口过短：维护后 secondary 变为 'too stale'，备份无法捕获 oplog，或不足以做时间点恢复。可在每个成员上在线扩大。"},{"k":"scenario","v":"fcv","l":"检查并完成 Feature Compatibility Version（FCV）","m":"维护 FCV featureCompatibilityVersion setFeatureCompatibilityVersion Invalid feature compatibility version IncompatibleServerVersion 升级后 UPGRADE PROBLEM requires confirm: true getParameter featureCompatibilityVersion 升级后 FCV 仍停留在旧版本，或成员之间版本不一致：检查所有成员的二进制版本和 FCV，然后把 FCV 提升到新版本。"},{"k":"scenario","v":"addshard","l":"向集群添加 shard（sh.addShard）","m":"Sharded cluster addShard sh.addShard 添加 shard add shard scale out 新 shard shardAdded is not running with --shardsvr in use by another shard 容量不足：已搭建新的 shard replica set（shardsvr），将其加入集群；balancer 会逐步把 chunk 迁移过去。"},{"k":"scenario","v":"rmshard","l":"删除 shard（removeShard：drain 和 movePrimary）","m":"Sharded cluster removeShard remove shard 下线 shard drain draining movePrimary dbsToMove jumboChunks moveCollection collectionsToMove ShardNotFound draining started successfully 某个 shard 即将下线：把它的 chunk 迁空到其他 shard（drain），迁移以它为 primary shard 的数据库（movePrimary），然后从集群中删除。"},{"k":"scenario","v":"balancer","l":"Balancer：时间窗口、停止/启动、按 collection 禁用","m":"Sharded cluster balancer activeWindow stopBalancer startBalancer sh.stopBalancer disableBalancing balancer 窗口 chunk migration moveChunk isBalancerRunning getBalancerState Balancer timed out 高峰时段 chunk 迁移造成负载，维护/备份期间需要停止迁移，或某个 collection 不应参与均衡。"},{"k":"scenario","v":"chunks","l":"分布不均或存在 jumbo chunk（诊断与处理）","m":"Sharded cluster jumbo jumbo chunk chunk too big ChunkTooBig 分布不均 imbalance hot shard balancerCollectionStatus clearJumboFlag moveChunk failed getShardDistribution low cardinality splitFind Cannot split on the boundary jumboChunks 某个 shard 比其他 shard 满得多、balancer 不迁移、有标记为 'jumbo' 的 chunk，或 moveChunk 报错：检查分布、jumbo chunk 和 balancer 错误。"},{"k":"scenario","v":"reshard","l":"更改 shard key（reshardCollection）","m":"Sharded cluster reshardCollection reshard 更改 shard key change shard key refineCollectionShardKey abortReshardCollection commitReshardCollection forceRedistribution ReshardCollectionInProgress ReshardCollectionAborted unshardCollection 当前 shard key 导致热点 shard、jumbo chunk 或 scatter-gather 查询：在线以新 shard key 重新分布 collection（5.0+）。最后阶段写入会阻塞约 2 秒。"},{"k":"mcfg","v":"priority","l":"更改 priority（成为 primary 的优先级）","m":"更改成员成为 primary 的优先级（例如优先选择主数据中心的成员，把 DR 成员设为 0）。可能触发选举；写入可能短暂中断。"},{"k":"mcfg","v":"hidden","l":"隐藏（hidden）成员：应用不可见，用于备份/报表","m":"成员对应用不可见（不会路由读请求）；专用于报表、分析或备份。priority 变为 0，不能成为 primary。"},{"k":"mcfg","v":"delayed","l":"延迟（delayed）成员：防止误删","m":"成员按指定的延迟应用 oplog；误删的数据在这段时间内仍保留在它上面。它会成为 hidden、priority 0、votes 0。"},{"k":"mcfg","v":"normal","l":"恢复为普通成员（priority 1，可见，无延迟，参与投票）","m":"撤销之前的角色：priority 1，可见，无延迟，参与投票。"},{"k":"bal_op","v":"window","l":"定义时间窗口（activeWindow）","m":"迁移只在特定时段（例如夜间）进行；白天不产生负载。"},{"k":"bal_op","v":"unwindow","l":"删除时间窗口（任何时间都可运行）","m":"删除时间窗口；balancer 在需要时任何时段都会迁移（例如添加新 shard 后快速均衡）。"},{"k":"bal_op","v":"stop","l":"停止 balancer（维护/备份）","m":"在备份、升级或维护之前：正在进行的迁移会完成，不再启动新的迁移。工作结束后务必重新启动。"},{"k":"bal_op","v":"start","l":"启动 balancer","m":"启用已停止的 balancer；均衡从中断处继续。"},{"k":"bal_op","v":"coll_off","l":"对单个 collection 禁用均衡","m":"只有这一个 collection 的 chunk 不迁移（有意的数据放置、有问题的 collection）。其他 collection 继续均衡。"},{"k":"bal_op","v":"coll_on","l":"对单个 collection 启用均衡","m":"重新启用此前对该 collection 禁用的均衡。"}]},{"id":"mongo-security","file":"install/guides/mongo-security.json","group":"MongoDB","engineId":"mongodb","category":"security","title":"MongoDB 安全加固（授权、TLS、角色、审计）","summary":"按场景组织的 runbook，用于在不停机的情况下加固正在运行的 MongoDB（单服务器、replica set 或 sharded cluster）：只读安全审计、通过 keyFile + transitionToAuth 以 rolling 方式启用授权、最小权限的 custom role 和用户、SCRAM-SHA-256、bindIp 与防火墙、allowTLS → preferTLS → requireTLS 迁移、x.50","level":"高级","duration":"10 分钟（审计）– 2 小时（完整加固）","topology":"单服务器、3 个及以上成员的 replica set 或 sharded cluster","tags":["mongodb 安全","mongodb security","hardening","安全加固","CIS","authorization","keyFile","transitionToAuth","TLS","requireTLS","x.509","SCRAM-SHA-256","custom role","least privilege","bindIp","audit log","auditLog","encryption at rest","LUKS","Percona"],"order":20,"steps":8,"scenarios":[{"k":"op","v":"audit","l":"安全状态审计（只读）","m":"审计 安全审计 security audit security checklist CIS MongoDB hardening 安全加固 Access control is not enabled for the database 认证是否关闭 bindIp 0.0.0.0 exposed mongodb mongodb 暴露在互联网 ransom 勒索 getCmdLineOpts security checklist MongoDB 审计报告 不做任何修改。将匿名访问、监听地址、TLS、认证机制、高权限用户、审计和 encryption at rest 的状态报告为 OK / 警告 / 风险。如果不知道从哪里开始，请选择此项。"},{"k":"op","v":"all","l":"完整加固（按顺序全部执行）","m":"审计 完整加固 full hardening CIS benchmark 安全项目 渗透测试发现 审计发现 审计 → 内部认证 + 授权（通过 transitionToAuth 不停机）→ 最小权限用户 → SCRAM-SHA-256 → bindIp/防火墙 → TLS（allowTLS → preferTLS → requireTLS）→ 审计日志（Enterprise/Percona）→ 最终审计。在 replica set 中以 rolling 方式进行；每个有风险的步骤都需要确认。"},{"k":"op","v":"auth","l":"授权已关闭：启用 authorization（keyFile，不停机）","m":"身份与权限 Access control is not enabled for the database Read and write access to data and configuration is unrestricted authorization disabled authorization: enabled 认证关闭 no authentication transitionToAuth keyFile internal authentication 成员间认证 匿名访问 permissions on /etc/mongod.keyfile are too open Authentication failed __system localhost exception 任何人无需用户名/密码即可连接。在 replica set 和 sharded cluster 中通过 key"},{"k":"op","v":"users","l":"最小权限用户和 custom role（应用、备份、监控）","m":"身份与权限 least privilege 最小权限 custom role createRole root 用户 应用以 root 连接 not authorized on Unauthorized command find requires authentication requires authentication readWrite clusterMonitor backup 角色 createUser grantRolesToUser monitor_user backup_user 应用使用 root 或共享的管理员账号连接。为应用创建只授予其自身数据库所需操作的 custom role，并为备份和监控创建单独的用户；同时说明如何在 cron/应用侧安全保存密码。"},{"k":"op","v":"scram","l":"关闭 SCRAM-SHA-1，只用 SCRAM-SHA-256","m":"身份与权限 SCRAM-SHA-1 SCRAM-SHA-256 authenticationMechanisms Received authentication for mechanism SCRAM-SHA-1 which is not enabled Unable to use SCRAM-SHA-256 based authentication for user without any SCRAM-SHA-256 credentials registered MD5 mechanism 旧用户的凭据以 SHA-1 保存，或审计发现 SCRAM-SHA-1 处于启用状态。将用户迁移到 SHA-256，然后通过 rolling 重启收紧 `authenticationMechanisms`。"},{"k":"op","v":"bind","l":"收紧网络访问（bindIp 0.0.0.0、防火墙）","m":"网络与加密 bindIp 0.0.0.0 bindIpAll bind_ip_all 0.0.0.0:27017 端口 27017 开放 shodan 暴露在互联网 防火墙 javascriptEnabled noscripting net.bindIp ufw allow 27017 firewall-cmd 27017 mongod 在所有接口上监听（0.0.0.0、`bindIpAll`），或端口对所有人开放。将 bindIp 缩小到 localhost + 服务器内网 IP，防火墙只允许成员和应用网络访问。Rolling 方式，不停机。"},{"k":"op","v":"tls","l":"加密连接：TLS（allowTLS → preferTLS → requireTLS）","m":"网络与加密 TLS SSL net.tls.mode requireTLS preferTLS allowTLS 未加密连接 cleartext no SSL certificate provided by peer SSL peer certificate validation failed certificate verify failed x.509 x509 clusterAuthMode Hostname/IP does not match certificate's altnames self-signed certificate in certificate chain rotateCertificates allowConnectionsWithoutCertificates 应用与成员之间的流量未加密。分发证书后，先以 `allowTLS` 对成员进行 rolling 启"},{"k":"op","v":"auditlog","l":"启用审计日志（Enterprise / Percona）","m":"审计追踪与数据保护 audit log auditLog 审计日志 谁删除了 who dropped Unrecognized option: auditLog unrecognised option '--auditDestination' 《个人信息保护法》(PIPL) PCI DSS 日志记录 必须记录谁在何时登录、用户/角色变更以及 drop 操作。在 MongoDB Enterprise 和 Percona Server for MongoDB 中通过 `auditLog` 启用；Community 版没有此功能（给出替代方案）。"},{"k":"op","v":"ear","l":"Encryption at rest（加密磁盘上的数据）","m":"审计追踪与数据保护 encryption at rest 磁盘加密 enableEncryption encryptionKeyFile KMIP Vault LUKS dm-crypt 数据加密 Unrecognized option: security.enableEncryption Unable to retrieve key Unable to use encryption at rest cryptsetup luksFormat 即使磁盘或备份被盗，数据也无法读取。在 Enterprise/Percona 中，通过逐个以 initial sync 重建成员来启用 WiredTiger 加密；在 Community 中用 LUKS 加密数据盘。"}]},{"id":"redis-sentinel","file":"install/guides/redis-sentinel.json","group":"Redis","engineId":"redis","category":"ha","title":"使用 Redis Sentinel 实现高可用（master + replica + 3 个 Sentinel）","summary":"从零搭建三台服务器的 Redis 7.2 / 8.2 或 Valkey 高可用架构：一个 master、两个 replica，每台服务器各运行一个 Sentinel。按步骤配置按 RAM 计算的 maxmemory、持久化（RDB/AOF）、密码、内核参数、quorum 与超时、计划内 failover 测试以及支持 Sentinel 的客户端连接；最终得到一套受密码保护、master 宕机后数秒内自动选出新 master 的架构。如果","level":"中级","duration":"45–60 分钟","topology":"3 台服务器：每台运行 Redis（1 个 master + 2 个 replica）和 Sentinel","tags":["redis","valkey","哨兵","副本","主从复制","故障转移","quorum","高可用","ha","缓存","rhel","ubuntu"],"order":1,"steps":27,"scenarios":[]},{"id":"redis-cluster","file":"install/guides/redis-cluster.json","group":"Redis","engineId":"redis","category":"ha","title":"安装 Redis Cluster（3 master + 3 replica）","summary":"从零搭建 Redis Cluster（Redis 7.2 / 8.2 或 Valkey）：16384 个 hash slot 分配给三个（或更多）master，每个 master 在另一台服务器上有一个 replica。按步骤完成每台服务器一个 instance（6 台服务器）或两个 instance（3 台服务器）的布局、每个 instance 一个 systemd 服务、密码、创建集群、检查和 failover 测试；最终得到一个将","level":"中级","duration":"45–60 分钟","topology":"6 个 instance：6 台服务器 × 1 或 3 台服务器 × 2","tags":["redis","valkey","集群","redis 集群","分片","hash slot","reshard","failover","replica","扩容","rhel","ubuntu"],"order":2,"steps":33,"scenarios":[]},{"id":"redis-backup","file":"install/guides/redis-backup.json","group":"Redis","engineId":"redis","category":"backup","title":"Redis 备份与恢复（RDB、AOF、replica 和 cluster）","summary":"为已安装的 Redis 或 Valkey（单实例、带 Sentinel 的 master/replica 或 Redis Cluster）建立备份机制，并说明如何从备份恢复。逐步完成 RDB（save 规则、BGSAVE、LASTSAVE、复制 dump.rdb）和 AOF（BGREWRITEAOF，7.x 的多文件 appendonlydir）备份、从 replica 备份、在 Cluster 中备份所有 master、在临时实例上测","level":"中级","duration":"30–60 分钟","topology":"单实例、replica 或 Redis Cluster 服务器（Redis 7.2 / 8.x、Valkey）","tags":["redis","valkey","备份","backup","restore","恢复","rdb","dump.rdb","bgsave","lastsave","aof","appendonly","bgrewriteaof","appendonlydir","redis cluster","systemd timer","cron","retention","cron","systemd timer","flock","REDISCLI_AUTH","sentinel"],"order":15,"steps":19,"scenarios":[]},{"id":"redis-ops","file":"install/guides/redis-ops.json","group":"Redis","engineId":"redis","category":"ops","title":"Redis / Valkey 日常运维与故障场景（Cluster 和 Sentinel）","summary":"不是安装指南：这是一份按场景组织的运维手册，用于日常管理已安装的 Redis Cluster 或由 Sentinel 管理的 master/replica 架构，并在出故障时恢复（Redis 7.2、Redis 8 和 Valkey）。Cluster：健康检查、添加/移除节点、rebalance/reshard、手动 failover（FORCE 与 TAKEOVER 的区别）、替换故障节点以及 --cluster fix。Sentin","level":"高级","duration":"5 分钟（检查）– 1 小时（reshard）","topology":"Redis Cluster（3 个以上 master + replica）或 Sentinel（1 个 master + replica + 3 个 Sentinel）","tags":["redis","valkey","redis cluster","sentinel","redis-cli --cluster","cluster check","add-node","del-node","reshard","rebalance","CLUSTER FAILOVER","FORCE","TAKEOVER","CLUSTER FORGET","cluster fix","SENTINEL FAILOVER","SENTINEL RESET","quorum","CKQUORUM","INFO memory","MEMORY DOCTOR","eviction","maxmemory","bigkeys","memkeys","hotkeys","运维","故障"],"order":20,"steps":3,"scenarios":[{"k":"scenario","v":"c_health","l":"Cluster 健康检查（--cluster check、CLUSTER INFO / NODES）","m":"Redis Cluster：监控 cluster_state:fail CLUSTERDOWN CLUSTERDOWN Hash slot not served --cluster check CLUSTER INFO CLUSTER NODES fail? pfail slots not covered Not all 16384 slots are covered [ERR] Nodes don't agree about configuration MOVED ASK CLUSTERDOWN The cluster is down cluster_slots_fail 集群状态 cluster health cluster_state 只读：所有 slot 是否都已覆盖，各节点的配置是否一致，是否有失效（fail）节点或没有 replica 的 master。如果不确定该做什么，从这"},{"k":"scenario","v":"c_add","l":"向集群添加节点（master 或 replica）","m":"Redis Cluster：节点管理 add-node --cluster add-node 添加集群节点 add node scale out --cluster-slave --cluster-replica cluster-master-id 新 master 新 replica Node is not empty. Either the node already knows other nodes New node added correctly 扩容 新实例（cluster-enabled yes，空实例）已就绪：作为 master 加入集群并分配 slot，或成为某个 master 的 replica。无停机。"},{"k":"scenario","v":"c_remove","l":"从集群移除节点（清空 slot + del-node）","m":"Redis Cluster：节点管理 del-node --cluster del-node 移除集群节点 remove node scale in 清空 slot cluster-weight Node … is not empty is not empty! Reshard data away and try again CLUSTER RESET SOFT 节点下线 某个 master 或 replica 要下线：将 master 的 slot 迁移到其他 master，然后把节点从集群中移除。无停机；迁移期间会出现 ASK 重定向。"},{"k":"scenario","v":"c_reshard","l":"重新分配 slot（rebalance / reshard）","m":"Redis Cluster：节点管理 rebalance reshard --cluster rebalance --cluster reshard slot 分布 不均衡 imbalance use-empty-masters MIGRATE importing migrating BUSYKEY IOERR error or timeout reading to target instance has slots in importing state has slots in migrating state Calling MIGRATE No rebalancing needed 迁移 slot 各 master 之间的 slot 或内存不均衡、新 master 没有分到 slot，或需要把指定数量的 slot 从一个 master 迁移到另一个。"},{"k":"scenario","v":"c_failover","l":"手动 failover：将 replica 提升为 master（CLUSTER FAILOVER、FORCE、TAKEOVER）","m":"Redis Cluster：故障与维护 CLUSTER FAILOVER failover FORCE TAKEOVER manual failover master 维护 将 replica 提升为 master Manual failover timed out ERR Master is down or failed, please use CLUSTER FAILOVER FORCE ERR You should send CLUSTER FAILOVER to a replica 更换 master 要对 master 所在服务器做维护（计划内，无数据丢失），或 master 无法访问且没有发生自动 failover（FORCE / TAKEOVER；有数据丢失风险）。"},{"k":"scenario","v":"c_replace","l":"替换故障 master / 节点（FORGET、新 replica、--cluster fix）","m":"Redis Cluster：故障与维护 CLUSTER FORGET --cluster fix 节点丢失 failed node fail noaddr slots not covered CLUSTERDOWN replace node 故障 master handshake ERR Can't forget my master! CLUSTERDOWN Hash slot not served 服务器丢失 磁盘故障 某个节点的服务器永久丢失：如果其 replica 已成为 master，就让集群遗忘旧节点并加入新的 replica 顶替；如果它没有 replica，其 slot 会无主，需用 --cluster fix 补上（这些 slot 中的数据丢失）。"},{"k":"scenario","v":"s_health","l":"Sentinel 状态：master、replica、Sentinel、quorum","m":"Sentinel SENTINEL MASTERS SENTINEL master get-master-addr-by-name CKQUORUM +sdown +odown -odown +switch-master NOQUORUM NOGOODSLAVE sentinel 状态 +tilt -failover-abort-not-elected s_down o_down master_link_status:down sentinel health 只读：Sentinel 看到的 master 地址、replica、其他 Sentinel、CKQUORUM 以及最近的 failover 事件。在 Sentinel 架构中如果不确定该做什么，从这里开始。"},{"k":"scenario","v":"s_failover","l":"使用 Sentinel 手动 failover（SENTINEL FAILOVER）","m":"Sentinel SENTINEL FAILOVER sentinel failover switch-master 计划内 failover INPROG NOGOODSLAVE -failover-abort-no-good-slave NOGOODSLAVE No suitable replica to promote sentinel 维护 要对 master 所在服务器做维护：Sentinel 将最合适的 replica 提升为 master，并把其他 replica 和客户端指向新 master。中断 1–2 秒；master 运行期间执行无数据丢失。"},{"k":"scenario","v":"s_replace","l":"在 Sentinel 环境中添加 / 移除 / 替换节点（SENTINEL RESET）","m":"Sentinel SENTINEL RESET sentinel reset replicaof 新 replica 新 sentinel 添加 sentinel 移除 sentinel 旧 replica 仍在列表中 num-other-sentinels known-replica known-sentinel 旧 sentinel 仍在列表中 某个 replica 或 Sentinel 已永久移除或新加入：新 replica 通过 REPLICAOF 连接，Sentinel 中的过期记录用 SENTINEL RESET 清除。"},{"k":"scenario","v":"s_quorum","l":"修改 quorum 和 Sentinel 配置（SENTINEL SET）","m":"Sentinel SENTINEL SET quorum down-after-milliseconds failover-timeout parallel-syncs CKQUORUM NOQUORUM failover 太晚 不必要的 failover Sentinel 数量发生变化，或 failover 触发得太晚/太早：在所有 Sentinel 上修改 quorum、down-after-milliseconds、failover-timeout、parallel-syncs。"},{"k":"scenario","v":"memory","l":"内存压力：使用量、碎片、eviction 诊断","m":"内存与 key OOM command not allowed when used memory > 'maxmemory' OOM maxmemory used_memory evicted_keys eviction maxmemory-policy MEMORY DOCTOR mem_fragmentation_ratio fragmentation activedefrag client-output-buffer-limit 内存已满 Out of memory: Killed process oom-killer used_memory_rss mem_clients_normal omem used_memory 接近 maxmemory、出现 'OOM command not allowed' 错误、eviction 增加、RSS 比预期大，或 replica/AOF 缓冲"},{"k":"scenario","v":"bigkeys","l":"查找大 key 和热 key（--bigkeys、--memkeys、--hotkeys）","m":"内存与 key --bigkeys --memkeys --hotkeys big key 大 key hot key 热 key MEMORY USAGE UNLINK slowlog latency SCAN HGETALL KEYS * --keystats 延迟尖刺 latency spike 某个节点的内存或 CPU 高于其他节点、出现延迟尖刺、DEL 耗时很长：找出最大和访问最频繁的 key，并安全地清理（UNLINK）。"},{"k":"add_role","v":"master","l":"Master（分配 slot：添加后执行 rebalance）","m":"以空 master 身份加入，然后通过 rebalance 分配 slot。增加容量；还应为新 master 添加一个 replica。"},{"k":"add_role","v":"replica","l":"Replica（某个 master 的副本）","m":"成为某个 master 的副本：master 故障时接管。网络上会传输与 master 内存相当的数据量（全量同步）。"},{"k":"rs_mode","v":"rebalance","l":"Rebalance：在各 master 之间平均分配 slot（包括空 master）","m":"所有 master 获得相同份额（包括没有 slot 的新 master）。先显示模拟结果。这是添加新 master 后最常见的操作。"},{"k":"rs_mode","v":"move","l":"Reshard：把指定数量的 slot 从一个 master 迁移到另一个","m":"把指定数量的 slot 从一个 master 迁移到另一个：用于减轻热点/数据量大的 master 的压力，或逐步清空某个 master。"},{"k":"fo_mode","v":"default","l":"计划内（默认）：与 master 同步后接管，无数据丢失","m":"Master 必须在运行。"},{"k":"fo_mode","v":"force","l":"FORCE：master 无法访问，不等待同步直接接管","m":"Master 最后的写入可能丢失；仍需要多数 master 投票。"},{"k":"fo_mode","v":"takeover","l":"TAKEOVER：多数 master 也已不在，不经投票直接接管","m":"最后手段：有 split brain 和 epoch 冲突的风险。"},{"k":"srep_op","v":"add_replica","l":"添加新 replica","m":"新服务器上的 Redis 连接到当前 master；Sentinel 会自动发现它。"},{"k":"srep_op","v":"rm_replica","l":"永久移除 replica","m":"关闭该 replica，并在所有 Sentinel 上清除其记录。"},{"k":"srep_op","v":"add_sentinel","l":"添加新 Sentinel","m":"在新服务器上添加 Sentinel（Sentinel 总数应保持奇数：3 → 5）。"},{"k":"srep_op","v":"rm_sentinel","l":"永久移除 Sentinel","m":"关闭该 Sentinel，并在其他 Sentinel 上清除其记录；然后重新评估 quorum。"}]},{"id":"redis-security","file":"install/guides/redis-security.json","group":"Redis","engineId":"redis","category":"security","title":"Redis / Valkey 安全加固（ACL、TLS、危险命令）","summary":"不是安装或日常运维指南：用于在不停机的情况下加固运行中的 Redis 或 Valkey（单服务器、replica + Sentinel 或 Cluster）的场景化 runbook。内容包括：只读安全审计，ACL 用户与禁用 default 用户，requirepass/masterauth，bind 与 protected-mode，防范 CONFIG/DEBUG/MODULE 滥用的 protected config，在线迁移到 T","level":"高级","duration":"5 分钟（审计）– 90 分钟（完整加固）","topology":"单服务器、master + replica + Sentinel，或 Redis Cluster","tags":["redis 安全","redis security","valkey","hardening","安全加固","ACL","ACL SETUSER","aclfile","requirepass","masterauth","protected-mode","bind","rename-command","enable-protected-configs","TLS","tls-replication","sentinel 密码","CIS"],"order":20,"steps":7,"scenarios":[{"k":"op","v":"audit","l":"安全状态审计（只读）","m":"审计 redis 安全审计 redis security audit CIS Redis hardening 安全加固 redis 暴露 exposed redis unauthorized redis redis 无密码 -NOAUTH crackit redis cryptominer kinsing NOAUTH Authentication required redis 安全检查 redis pentest 不做任何修改。无密码访问、default 用户、监听地址、protected-mode、危险命令、TLS、Sentinel 密码和文件权限以 `OK` / `WARN` / `RISK` 形式报告。不确定从哪里开始时选择此项。"},{"k":"op","v":"all","l":"完整加固（按顺序执行全部）","m":"审计 完整加固 full hardening redis 安全项目 渗透测试发现 审计 → ACL 用户（管理员、应用、复制、Sentinel）与禁用 default 用户 → bind/protected-mode/防火墙 → 危险命令与 protected config → TLS → 文件权限 → 最终审计。replica/Sentinel 上滚动执行；有风险的步骤需要确认。"},{"k":"op","v":"acl","l":"ACL 用户与禁用 default 用户（Redis 6+）","m":"身份与权限 ACL ACL SETUSER aclfile default user default 用户 NOPERM WRONGPASS invalid username-password pair -NOAUTH Authentication required user default on nopass masteruser least privilege 最小权限 NOPERM this user has no permissions to run the NOPERM this user has no permissions to access one of the keys used as arguments ACL LOG 每用户密码 ACL SAVE 所有人都用同一个密码（或无密码）以 `default` 用户连接。为管理员、应用（只能访问自己的 key，不能执行危险命令"},{"k":"op","v":"auth","l":"Redis 无密码：requirepass / masterauth（快速处置）","m":"身份与权限 requirepass masterauth auth-pass redis 无密码 no password MASTER aborted replication with an error: NOAUTH Unable to AUTH to MASTER -NOAUTH ERR AUTH <password> called without any password configured for the default user Client sent AUTH, but no password is set 设置密码 完全没有密码，也没有时间迁移到 ACL。在线为所有节点设置共用密码（`requirepass`，复制用 `masterauth`，Sentinel 用 `auth-pass`）；应用将无法再无密码连接。"},{"k":"op","v":"net","l":"收紧网络访问（bind 0.0.0.0、protected-mode、防火墙）","m":"网络与加密 bind 0.0.0.0 protected-mode no DENIED Redis is running in protected mode 0.0.0.0:6379 6379 端口开放 shodan firewall DENIED Redis is running in protected mode because protected mode is enabled and no password is set for the default user *:6379 Could not create server TCP listening socket Redis 在所有网卡上监听，或以 `protected-mode no` 运行。将 bind 缩小到 localhost + 内网 IP，启用 protected-mode，防火墙只放行各节点和应用网络。在线执行，现有"},{"k":"op","v":"cmds","l":"危险命令与 CONFIG 滥用（FLUSHALL、CONFIG、DEBUG、MODULE）","m":"网络与加密 CONFIG SET dir dbfilename authorized_keys crontab FLUSHALL FLUSHDB DEBUG MODULE LOAD enable-protected-configs enable-debug-command enable-module-command rename-command @dangerous ERR CONFIG SET failed (possibly related to argument 'dir') - can't set protected config ERR DEBUG command not allowed CONFIG SET dbfilename rename-command CONFIG 恶意模块 SLAVEOF 漏洞利用 防范利用 `CONFIG SET dir/dbfilename` 向服"},{"k":"op","v":"tls","l":"加密连接：TLS（客户端、复制、Sentinel、cluster bus）","m":"网络与加密 TLS SSL tls-port tls-replication tls-cluster tls-auth-clients 未加密连接 Error accepting a client connection: error:0A00010B:SSL routines::wrong version number SSL_connect certificate verify failed rediss:// Connection reset by peer tls-cert-file 加密连接 encryption in transit 流量未加密。在现有端口旁在线开启 TLS 端口，将复制和 Sentinel 迁移到 TLS，应用切换完成后关闭明文端口（`port 0`）。"},{"k":"op","v":"perms","l":"文件权限与服务运行用户","m":"痕迹与文件 redis.conf 权限 world readable chmod dump.rdb appendonly 以 root 运行 running as root 文件权限 Permission denied 640 User=redis redis root redis.conf（含密码）、ACL 文件、RDB/AOF 和日志文件可被其他用户读取，或 Redis 以 root 运行。将权限收紧为 640/750，属主改为服务用户。"}]},{"id":"es-cluster","file":"install/guides/es-cluster.json","group":"Elasticsearch","engineId":"elasticsearch","category":"install","title":"Elasticsearch 集群安装（3+ 节点、TLS、Kibana）","summary":"从零搭建一个 3 个或更多节点的 Elasticsearch 8.x / 9.x 集群：Elastic 仓库、按 RAM 计算 heap、memory lock、vm.max_map_count、第一个节点上的自动安全配置（TLS + elastic 密码）、其他节点通过 enrollment token 加入、master/data 角色合并或分离、Kibana 安装与 enrollment、共享 snapshot 仓库和每晚的 SL","level":"中级","duration":"60–90 分钟","topology":"3+ 个 Elasticsearch 节点（+ 可选的 Kibana 服务器）","tags":["elasticsearch","elastic","kibana","cluster","集群","安装","install","enrollment","tls","heap","snapshot","rhel","ubuntu"],"order":1,"steps":54,"scenarios":[]},{"id":"es-rolling-upgrade","file":"install/guides/es-rolling-upgrade.json","group":"Elasticsearch","engineId":"elasticsearch","category":"upgrade","title":"Elasticsearch rolling upgrade（8.x minor 或 8.19 → 9.x）","summary":"不是安装指南：通过逐个升级节点，将运行中的 Elasticsearch 集群零停机迁移到新版本（8.x 内的 minor 升级，或 8.19 → 9.x 主版本升级）。逐步完成：预检查（版本、健康、deprecation API、system feature migration）、升级前快照、将分片分配限制为 primaries 并执行 flush、逐节点升级版本和插件、重新启用分配并等待集群恢复健康、升级 Kibana，必要时从快照回","level":"高级","duration":"每个节点 15–30 分钟","topology":"现有 3 个以上节点的集群（+ Kibana）","tags":["elasticsearch","upgrade","rolling upgrade","minor","major","9.x","8.19","kibana","upgrade assistant","deprecation","snapshot","rollback","rolling upgrade","node roles","master-eligible","deprecation api","versionlock"],"order":2,"steps":34,"scenarios":[{"k":"path","v":"minor","l":"同一主版本内（如 8.18 → 8.19、9.1 → 9.2）","m":"同一主版本内（8.18 → 8.19、9.1 → 9.2）：软件仓库不变，仍需执行 deprecation 检查。支持跳过中间的 minor 版本。"},{"k":"path","v":"major","l":"主版本：8.19.x → 9.x","m":"源集群必须先升级到 8.19.x（最新补丁）。预检查包括 Upgrade Assistant / deprecation API 和 system feature migration 步骤。"}]},{"id":"es-snapshot","file":"install/guides/es-snapshot.json","group":"Elasticsearch","engineId":"elasticsearch","category":"backup","title":"Elasticsearch snapshot 与 restore（fs / S3 repository、SLM）","summary":"为已安装的 Elasticsearch 8.x / 9.x 集群建立 snapshot（备份）机制并说明恢复方法：共享文件系统（NFS + path.repo）或 S3（keystore）repository，通过 rolling restart 生效并验证，手动 snapshot（indices、include_global_state、feature_states），用 SLM 定时 snapshot 并设置保留策略（expire_","level":"中级","duration":"45–90 分钟","topology":"Elasticsearch 8.x / 9.x 集群（1–N 个节点）+ 共享目录（NFS）或 S3 兼容对象存储","tags":["elasticsearch","snapshot","restore","备份","backup","恢复","repository","path.repo","nfs","s3","keystore","slm","snapshot lifecycle","保留策略","rename_pattern","feature_states","只读 repository","迁移","slm","snapshot lifecycle","quartz cron","utc","netrc"],"order":15,"steps":33,"scenarios":[]},{"id":"es-ops","file":"install/guides/es-ops.json","group":"Elasticsearch","engineId":"elasticsearch","category":"ops","title":"Elasticsearch 日常运维与故障场景 (8.x / 9.x)","summary":"不是安装指南：面向已安装的 Elasticsearch 8.x / 9.x 集群的场景化 runbook，用于日常管理以及出现故障时的恢复。内容包括：健康检查与 allocation explain，处理未分配分片（磁盘、filtering、retry_failed、副本，最后手段 stale/empty primary），磁盘 watermark，read_only_allow_delete 块，添加节点与安全下线节点，借助别名的零停","level":"高级","duration":"5 分钟（检查）– 数小时（reindex、腾空节点）","topology":"Elasticsearch 8.x / 9.x 集群（1–N 个节点），已启用 TLS 和身份验证","tags":["elasticsearch","elastic","cluster health","red","yellow","unassigned","allocation explain","reroute","retry_failed","allocate_stale_primary","watermark","flood stage","read_only_allow_delete","exclude","voting_config_exclusions","enrollment token","reindex","alias","ILM","rollover","shrink","split","snapshot restore","运维","故障排查"],"order":20,"steps":6,"scenarios":[{"k":"scenario","v":"health","l":"集群健康：状态、节点、未分配分片、磁盘","m":"监控与诊断 red yellow _cluster/health _cat/shards UNASSIGNED allocation explain _cluster/allocation/explain status red status yellow 集群健康 pending tasks master_not_discovered_exception NoNodeAvailableException circuit_breaking_exception heap 集群健康检查 只读：green/yellow/red、节点列表、未分配（UNASSIGNED）分片及其原因（allocation explain）、磁盘使用率和 watermark、挂起任务。不确定该做什么时从这里开始。"},{"k":"scenario","v":"unassigned","l":"处理未分配（UNASSIGNED）分片（依据 explain）","m":"监控与诊断 UNASSIGNED unassigned shards NODE_LEFT ALLOCATION_FAILED max_retry shard has exceeded the maximum number of retries retry_failed no_valid_shard_copy cannot allocate because a previous copy of the primary shard existed allocate_stale_primary allocate_empty_primary the node is above the high watermark same_shard awareness filter NO_VALID_SHARD_COPY CLUSTER_RECOVERED REPLICA_ADDED cannot alloca"},{"k":"scenario","v":"watermark","l":"查看 / 修改磁盘 watermark 设置","m":"磁盘与块 watermark disk.watermark low disk watermark high disk watermark flood stage disk watermark 磁盘已满 disk full cluster.routing.allocation.disk cluster.routing.allocation.disk.watermark.high disk usage exceeded 磁盘阈值 磁盘占用导致分片被迁移或无法分配；需要临时或永久修改 watermark 阈值（low / high / flood_stage），或恢复为默认值。"},{"k":"scenario","v":"readonly","l":"索引变为只读：移除 read_only_allow_delete 块","m":"磁盘与块 read_only_allow_delete FORBIDDEN/12/index read-only / allow delete (api) TOO_MANY_REQUESTS/12/disk usage exceeded flood-stage watermark cluster_block_exception index.blocks.read_only_allow_delete flood stage 只读 read-only blocked by: [FORBIDDEN/12/index read-only / allow delete (api)] index read-only 写入被拒绝 写入被拒绝，报错 'FORBIDDEN/12/index read-only / allow delete (api)' 或 'TOO_MANY_REQUESTS/12/dis"},{"k":"scenario","v":"add_node","l":"向集群添加节点（enrollment token）","m":"节点管理 添加节点 add node enrollment token elasticsearch-create-enrollment-token elasticsearch-reconfigure-node scale out join cluster master not discovered master not discovered yet, this node has not previously joined a bootstrapped cluster 扩容 新节点 需要扩容：新服务器上已安装 Elasticsearch，使用 enrollment token 加入集群；分片自动重新平衡。无停机。"},{"k":"scenario","v":"remove_node","l":"安全下线节点（exclude、腾空、关闭）","m":"节点管理 下线节点 remove node decommission cluster.routing.allocation.exclude._name exclude voting_config_exclusions relocating 腾空节点 voting configuration 节点退役 更换服务器 节点要退役或更换硬件：通过 allocation exclude 将其分片迁移到其他节点，确认节点已腾空；若为 master-eligible 节点则加入 voting exclusion，然后关闭。"},{"k":"scenario","v":"reindex","l":"Reindex：为修改 mapping/设置，借助新索引和别名零停机切换","m":"索引管理 _reindex reindex 修改 mapping mapper_parsing_exception illegal_argument_exception: mapper alias _aliases 无缝切换 zero downtime task version_conflict_engine_exception cannot be changed from type mapper [ 修改 analyzer 需要修改 mapping 字段类型、analyzer 或分片数：创建新索引，用 _reindex 复制数据，再将别名原子地切换到新索引。"},{"k":"scenario","v":"ilm","l":"配置 / 排查索引生命周期（ILM）与 rollover","m":"索引管理 ILM index lifecycle rollover _rollover _ilm/explain is_write_index rollover_alias ilm policy index.lifecycle.name ERROR step illegal_argument_exception: index.lifecycle.rollover_alias rollover_alias does not point to index ILM ERROR 索引未被删除 日志索引增长 日志/时间序列索引持续增长或未被删除：配置 ILM 策略（rollover + delete）、索引模板和初始索引（写别名）；用 explain 查看现有策略为何不推进。"},{"k":"scenario","v":"resize","l":"修正分片数：_shrink（减少）/ _split（增加）","m":"索引管理 _shrink _split shrink split 分片数 number_of_shards too many shards 分片过多 oversharding index.blocks.write must be read-only to resize index must have all shards allocated on the same node to shrink index number_of_routing_shards 索引的分片数选得不合适：过小的分片（分片过多）用 _shrink 合并，过大的分片用 _split 拆分；通过别名切换。"},{"k":"scenario","v":"snaprestore","l":"从快照以新名称恢复单个索引","m":"索引管理 snapshot restore _restore 单索引恢复 rename_pattern rename_replacement 索引被删除 deleted index index_not_found_exception cannot restore index because an open index with same name already exists snapshot_missing_exception 误删除 恢复索引 某个索引被删除或损坏：从快照存储库只恢复该索引，以新名称（rename）恢复，不影响在线索引；验证后通过别名投入使用。"},{"k":"un_fix","v":"retry","l":"超过重试上限：重试（_cluster/reroute?retry_failed）","m":"max_retry shard has exceeded the maximum number of retries ALLOCATION_FAILED explain：'shard has exceeded the maximum number of retries'。先排除根本错误（磁盘、mapping、内存）。"},{"k":"un_fix","v":"replicas","l":"副本数多于节点数：减少副本","m":"same_shard a copy of this shard is already allocated to this node explain：same_shard（副本不能放在同一节点上）；单节点集群副本数为 0。"},{"k":"un_fix","v":"filter","l":"Allocation filtering / exclude 阻止分配：移除过滤规则","m":"filter node does not match index setting [index.routing.allocation node matches cluster setting [cluster.routing.allocation.exclude explain：filter decider（'node does not match index setting [index.routing.allocation…]' 或集群级 exclude）。"},{"k":"un_fix","v":"disk","l":"磁盘 watermark 阻止分配","m":"disk_threshold the node is above the low watermark the node is above the high watermark explain：disk_threshold decider。转到 watermark 场景。"},{"k":"un_fix","v":"stale","l":"最后手段：从旧（stale）副本分配主分片","m":"no_valid_shard_copy all the copies of its data in the cluster are stale or corrupt allocate_stale_primary 主分片的最新副本已丢失，只剩旧副本：该副本中最近的写入会丢失。"},{"k":"un_fix","v":"empty","l":"万不得已：分配空主分片（分片的全部数据丢失）","m":"no_valid_shard_copy there are no copies of its data in the cluster allocate_empty_primary 没有任何副本，且索引无法从快照恢复时使用。"},{"k":"wm_op","v":"show","l":"仅查看（磁盘使用率和当前阈值）","m":"只读：各节点磁盘及当前阈值。"},{"k":"wm_op","v":"set","l":"修改阈值","m":"临时提高阈值（如大容量磁盘上用剩余空间 `50gb` 代替百分比）。问题解决后恢复默认值。"},{"k":"wm_op","v":"reset","l":"恢复默认值（85% / 90% / 95%）","m":"删除之前修改过的阈值，恢复为默认值（85% / 90% / 95%）。"},{"k":"ilm_op","v":"setup","l":"新策略 + 模板 + 初始索引（写别名）","m":"创建策略（rollover + 删除）、索引模板和初始索引（`-000001`，写别名）。用于新的日志流。"},{"k":"ilm_op","v":"debug","l":"现有索引为何不推进？（explain、retry）","m":"索引不 rollover 或不被删除：`_ilm/explain` 显示失败的步骤，排除原因后执行 retry。"},{"k":"ilm_op","v":"rollover","l":"手动 rollover","m":"不等待条件满足，直接创建新的写索引（mapping 修改后或索引过大时）。"},{"k":"rs_kind","v":"shrink","l":"_shrink：减少分片数（目标值必须是当前值的因数）","m":""},{"k":"rs_kind","v":"split","l":"_split：增加分片数（目标值必须是当前值的倍数）","m":""}]},{"id":"es-security","file":"install/guides/es-security.json","group":"Elasticsearch","engineId":"elasticsearch","category":"security","title":"Elasticsearch 安全加固（security、TLS、角色、audit）","summary":"不是安装或日常运维指南，而是对运行中的 Elasticsearch 集群进行安全加固的场景化 runbook。内容包括：只读安全审计、开启已关闭的 security 并启用 transport TLS（完整集群重启）、滚动切换到 HTTP TLS、DBA/应用角色、带有效期的 API key、应急 file realm 用户、让 elastic 退出日常使用、关闭 anonymous 访问、network.host 与 IP 过滤、au","level":"高级","duration":"10 分钟（审计）– 2 小时（完整加固）","topology":"1–12 个节点的 Elasticsearch 集群（+ Kibana）","tags":["elasticsearch 安全","elasticsearch security","hardening","安全加固","xpack.security","TLS","elasticsearch-certutil","elasticsearch-users","API key","role mapping","elastic superuser","anonymous","audit logging","IP filtering","kibana_system","service account token","CIS"],"order":20,"steps":4,"scenarios":[{"k":"op","v":"audit","l":"安全状态审计（只读）","m":"审计 elasticsearch 安全审计 elasticsearch security audit CIS Elasticsearch hardening 安全加固 暴露的 elasticsearch exposed elasticsearch xpack.security.enabled missing authentication credentials for REST request security_exception elasticsearch 安全检查 elasticsearch 渗透测试 RISK 不做任何更改。以 `OK` / `WARNING` / `RISK` 报告：security 是否开启、未认证访问、HTTP/transport TLS、anonymous 访问、用户及 elastic 的使用情况、无有效期的 API key、audit logging、IP "},{"k":"op","v":"all","l":"完整加固（按顺序全部执行）","m":"审计 完整加固 full hardening elastic 安全项目 渗透测试发现 审计 → security + transport/HTTP TLS（如未开启；完整集群重启）→ 用户/角色/API key、关闭 anonymous、让 elastic 退出日常使用 → 网络与 IP 过滤 → audit logging（许可证允许时）→ snapshot 仓库访问 → Kibana 连接 → 最终审计。"},{"k":"op","v":"enable","l":"Security 未开启：开启安全功能和 transport TLS","m":"身份与加密 xpack.security.enabled: false security disabled 未认证访问 Elasticsearch built-in security features are not enabled Transport SSL must be enabled if security is enabled invalid configuration for xpack.security.transport.ssl 7.x 升级 安全 Security is disabled xpack.security.transport.ssl 没有身份认证 `xpack.security.enabled: false`，或从 7.x 升级而来、可无认证访问的集群。生成证书，并在开启 transport TLS（以及可选的 HTTP TLS）的同时开启 security。"},{"k":"op","v":"httptls","l":"将 HTTP（REST）流量切换到 TLS","m":"身份与加密 http.ssl xpack.security.http.ssl.enabled received plaintext http traffic on an https channel plaintext http https 9200 elasticsearch-certutil http SSLHandshakeException curl: (52) Empty reply from server http:// 9200 密码在网络上明文传输 Security 已开启，但 9200 端口是明文 HTTP：密码可在网络上被读取。分发 HTTP 证书并逐个重启节点；客户端改用 `https://`。"},{"k":"op","v":"users","l":"用户、角色、API key；elastic 与 anonymous 访问","m":"身份与加密 elastic 用户 elastic superuser superuser role mapping API key api_key elasticsearch-users anonymous xpack.security.authc.anonymous action [indices:data/write/bulk[s]] is unauthorized is unauthorized for user unable to authenticate user action [indices:data/read/search] is unauthorized for user api key 有效期 无有效期的 api key break-glass 应用使用 `elastic` 超级用户连接、anonymous 访问已开启，或存在无有效期的 API key。为 DBA 和应"},{"k":"op","v":"auditlog","l":"开启 audit logging","m":"审计跟踪与访问 audit log xpack.security.audit.enabled audit logging _audit.json 审计记录 谁删除的 《个人信息保护法》(PIPL) PCI DSS xpack.security.audit.logfile.events.include 访问日志 谁访问了哪个 index、失败的登录尝试、授权拒绝以及安全设置变更都必须记录下来。`xpack.security.audit.enabled`（静态设置，滚动重启）— 需要许可证（该步骤会检查许可证）。"},{"k":"op","v":"net","l":"网络访问与 IP 过滤","m":"审计跟踪与访问 network.host 0.0.0.0 0.0.0.0:9200 9200 端口开放 xpack.security.transport.filter xpack.security.http.filter ip filtering shodan 防火墙 0.0.0.0:9300 network.host: 0.0.0.0 _site_ 公网 IP 9200/9300 监听所有网卡或对所有人开放。将 `network.host` 收窄到内网 IP，通过防火墙和 Elasticsearch IP 过滤只允许节点、Kibana 和应用网络访问。"},{"k":"op","v":"snapshot","l":"保护 snapshot 仓库访问","m":"审计跟踪与访问 snapshot 仓库 path.repo repository_exception s3.client access_key readonly repository snapshot 安全 snapshot_operator Object Lock 备份安全 snapshot 目录所有人可读、管理仓库的权限过宽，或云存储访问密钥写在 elasticsearch.yml 中。处理目录权限、snapshot 操作员角色、keystore，以及在第二个集群上以只读方式注册仓库。"},{"k":"op","v":"kibana","l":"保护 Kibana 连接","m":"审计跟踪与访问 kibana_system elasticsearch.username: elastic kibana.yml 密码 service account token elasticsearch.serviceAccountToken xpack.encryptedSavedObjects.encryptionKey Kibana server is not ready yet unable to retrieve version information from Elasticsearch nodes value of \"elastic\" is forbidden serviceAccountToken kibana.keystore Kibana 使用 `elastic` 用户或不加密连接，且密码以明文写在 kibana.yml 中。处理 service account t"}]},{"id":"mon-prometheus-grafana","file":"install/guides/mon-prometheus-grafana.json","group":"监控","engineId":null,"category":"monitoring","title":"使用 Prometheus + Grafana 监控数据库","summary":"搭建一台集中监控服务器（Prometheus、Alertmanager、Grafana），并在被监控服务器上部署 node_exporter 以及所选的数据库 exporter：PostgreSQL、MySQL、Oracle、SQL Server、MongoDB、Redis、Elasticsearch。内容包括最小权限的监控用户、systemd 服务、基础告警规则、邮件/webhook 通知、datasource provisionin","level":"中级","duration":"60–120 分钟","topology":"1 台监控服务器 + 被监控服务器","tags":["prometheus","grafana","alertmanager","node_exporter","postgres_exporter","mysqld_exporter","oracledb_exporter","sql_exporter","mongodb_exporter","redis_exporter","elasticsearch_exporter","数据库监控","monitoring","告警","dashboard"],"order":1,"steps":44,"scenarios":[]},{"id":"ora19-install","file":"install/guides/ora19-install.json","group":"Oracle","engineId":"oracle","category":"install","title":"Oracle Database 19c 单实例安装 (EE / SE2)","summary":"在运行 Oracle Linux / RHEL 7.9、8 或 9 的单台服务器上从零安装 Oracle Database 19c：操作系统准备（preinstall RPM 或手动）、OFA 目录、连同 Release Update 一起的静默 (silent) 软件安装、监听程序、CDB + PDB（AL32UTF8、按 RAM 计算 SGA/PGA、redo、FRA、ARCHIVELOG）、HugePages、自动启动、安全加固以","level":"中级","duration":"90–150 分钟","topology":"1 台服务器","tags":["oracle","19c","安装","install","runInstaller","dbca","netca","静默安装","release update","applyRU","opatch","cdb","pdb","hugepages","rman","oracle linux","rhel","asm","ol7","multipath","oracle restart"],"order":1,"steps":38,"scenarios":[]},{"id":"ora23-install","file":"install/guides/ora23-install.json","group":"Oracle","engineId":"oracle","category":"install","title":"安装 Oracle AI Database 26ai / 23ai (Linux)","summary":"在单台服务器上安装 Oracle AI Database 26ai（取代 23ai 的版本，版本号 23.26.x）；从三种官方方式中任选其一：生产环境使用 gold image ZIP 进行 OFA 安装（runInstaller -setupDBHome + dbca；文件系统，或已安装的 26ai Oracle Restart 的 ASM）、快速的 Enterprise Edition RPM，或免费的 Free RPM（开发/测","level":"中级","duration":"45–120 分钟","topology":"1 台服务器","tags":["oracle","26ai","23ai","ai database","安装","install","rpm","free","gold image","setupDBHome","dbca","cdb","pdb","oracle linux","rhel","asm","oracle restart"],"order":2,"steps":30,"scenarios":[]},{"id":"ora-storage-prep","file":"install/guides/ora-storage-prep.json","group":"Oracle","engineId":"oracle","category":"install","title":"Oracle 存储准备：multipath、持久设备名、ASM 磁盘","summary":"在安装 Oracle Grid Infrastructure、Oracle Restart 或数据库之前执行一次：按 WWID 识别 SAN LUN，按需配置 DM-Multipath 别名，并通过 udev 规则或 ASMLib 标签（若选择 AFD，则按安装程序打标签的方式准备）为 ASM 磁盘设置持久名称和 grid:asmadmin 属主；不使用 ASM 时，改为准备 LVM + XFS 文件系统和 fstab。RAC 中在所有","level":"中级","duration":"30–90 分钟","topology":"1 台服务器，或 RAC 的 2–8 个节点 + SAN/iSCSI/VM 磁盘","tags":["oracle","asm","multipath","device-mapper-multipath","dm-multipath","multipath.conf","wwid","udev","udev 规则","asmlib","oracleasm","afd","asm filter driver","lvm","xfs","fstab","san","lun","存储准备","storage","磁盘","rac","oracle restart"],"order":3,"steps":10,"scenarios":[]},{"id":"ora-grid-asm","file":"install/guides/ora-grid-asm.json","group":"Oracle","engineId":"oracle","category":"install","title":"Oracle Restart：Grid Infrastructure 19c + ASM（单服务器）","summary":"在单台服务器上安装 Oracle Restart（Grid Infrastructure 19c standalone，HA_CONFIG）和 ASM：grid 用户和 ASM 组、持久化磁盘名称（udev、ASMLib 或 ASM Filter Driver；单路径或多路径磁盘）、通过 gridSetup.sh -silent 同时应用 RU、DATA 和 FRA 磁盘组、用 srvctl/asmcmd 验证。适用于希望将数据库放在 ","level":"高级","duration":"90–150 分钟","topology":"1 台服务器 + 非共享 ASM 磁盘","tags":["oracle","19c","grid infrastructure","oracle restart","asm","gridSetup","HA_CONFIG","udev","asmlib","oracleasm","afd","asm filter driver","srvctl","asmcmd","安装","multipath","ol7"],"order":3,"steps":30,"scenarios":[]},{"id":"ora19-patch-ru","file":"install/guides/ora19-patch-ru.json","group":"Oracle","engineId":"oracle","category":"patch","title":"应用 Oracle Database 19c Release Update (RU)","summary":"为单实例或 Oracle Restart（Grid Infrastructure + ASM）19c 数据库应用季度 Release Update：OPatch 更新、冲突与空间预检查、ORACLE_HOME 备份和 guaranteed restore point、opatch apply（Oracle Restart 使用 opatchauto）、可选的 OJVM RU、datapatch、utlrp 及验证。包含缩短停机时间的 o","level":"中级","duration":"60–120 分钟（停机：in-place 30–60 分钟，out-of-place 10–20 分钟）","topology":"1 台服务器（单实例或 Oracle Restart）；RAC 请使用 rolling patch 指南","tags":["oracle","19c","补丁","release update","ru","opatch","opatchauto","datapatch","ojvm","out-of-place","回退","还原点","oracle restart","rac","rolling","standby-first","data guard","备库","1265700.1"],"order":10,"steps":25,"scenarios":[]},{"id":"ora19-rac","file":"install/guides/ora19-rac.json","group":"Oracle","engineId":"oracle","category":"ha","title":"安装 Oracle RAC 19c（Grid Infrastructure + ASM + RAC 数据库）","summary":"在两个或更多 node 上从零安装 Oracle Grid Infrastructure 19c（Flex ASM、SCAN）和 Oracle RAC 19c 数据库：网络与 DNS 规划、共享磁盘准备（udev 或 ASMLib；多路径或单路径）、用户与 SSH、cluvfy、在安装时应用 RU 的静默 gridSetup/runInstaller、用 dbca 创建 RAC CDB、应用服务及 failover 测试。最终得到每个 ","level":"高级","duration":"4–6 小时","topology":"2–8 个 node + 共享磁盘（SAN/iSCSI）+ DNS 中的 SCAN","tags":["oracle","rac","19c","grid infrastructure","clusterware","asm","scan","gridsetup","dbca","srvctl","crsctl","集群","高可用","udev","asmlib","多路径","ol7","scan","ocr","voting disk"],"order":10,"steps":68,"scenarios":[]},{"id":"ora-rac-patch","file":"install/guides/ora-rac-patch.json","group":"Oracle","engineId":"oracle","category":"patch","title":"Oracle RAC 19c rolling Release Update (opatchauto)","summary":"在运行中的 2–8 节点 RAC 19c 集群上以 rolling 方式不停机应用季度 GI Release Update：更新 OPatch，用 opatchauto analyze 检查冲突，备份 OCR 并建立回退点，迁移服务后逐节点应用，在单个节点上完成 datapatch，验证并在需要时 rollback。单实例和 Oracle Restart 请使用“应用 Oracle Database 19c Release Update","level":"高级","duration":"每节点 45–90 分钟","topology":"现有 2–8 节点 RAC 19c","tags":["oracle","rac","19c","补丁","release update","ru","opatchauto","opatch","datapatch","rolling","grid infrastructure","回退","scan","服务","standby-first","srvctl"],"order":11,"steps":38,"scenarios":[]},{"id":"ora-rac-ops","file":"install/guides/ora-rac-ops.json","group":"Oracle","engineId":"oracle","category":"ops","title":"Oracle RAC 日常运维与故障场景","summary":"已安装并运行的 Oracle RAC (Grid Infrastructure 19c) 集群的日常运维与故障处理：健康检查、节点驱逐诊断、节点停机维护、整个集群的计划内关闭与启动、添加和删除节点、服务管理 (preferred/available, AC/TAC)、SCAN/VIP/端口/interconnect 变更、OCR/OLR 备份与恢复、OCR 和 voting 文件迁移。本指南不是安装指南（先看 [RAC 安装](#ins","level":"高级","duration":"15 分钟 – 3 小时（视场景而定）","topology":"现有 2–8 节点 RAC（Grid Infrastructure 19c；23ai/26ai 的差异见注释）","tags":["oracle","rac","grid infrastructure","clusterware","crsctl","srvctl","olsnodes","cluvfy","addnode","添加节点","删除节点","delete node","ocr","olr","voting disk","votedisk","ocrconfig","ocrcheck","scan","vip","oifcfg","interconnect","eviction","ocssd","misscount","service","relocate","application continuity","taf","fan","日常运维","故障"],"order":12,"steps":10,"scenarios":[{"k":"scenario","v":"health","l":"集群健康检查（日常 / 维护前）","m":"检查与诊断 crsctl check cluster crsctl stat res -t cluvfy OFFLINE 资源 CRS-4535 CRS-4530 CRS-4639 health check 健康检查 INTERMEDIATE CRS-4537 CRS-2765 CRS-2878 CRS-4700 CRS-4701 crsctl check crs 集群状态 cluster status 资源已关闭 resource offline orachk tfactl 维护前检查 不做任何修改：检查 Clusterware 栈、资源、OCR/voting、ASM、interconnect、时间和最近的告警。日常检查、每次维护和打补丁之前，以及收到投诉时，首先选择此项。"},{"k":"scenario","v":"eviction","l":"节点被逐出集群或自行重启（驱逐诊断）","m":"检查与诊断 CRS-1609 CRS-1610 CRS-1611 CRS-1612 CRS-1615 CRS-1625 CRS-1632 CRS-1656 CRS-8011 node eviction evicted node reboot split brain ocssd.trc misscount disktimeout 节点被驱逐 CRS-1601 CRS-1606 CRS-1607 CRS-1608 CRS-1613 CRS-1614 CRS-8013 ORA-29740 ORA-29770 IPC Send timeout network heartbeat disk heartbeat 节点重启 服务器自动重启 rebootless restart instance eviction 只读：节点意外重启或被逐出集群时选择。通过 Clusterware alert log、oc"},{"k":"scenario","v":"stopnode","l":"停止节点进行维护并重新启动","m":"启动 / 停止 crsctl stop crs crsctl start crs 节点维护 OS patch reboot CRS-4133 CRS-4123 drain_timeout srvctl stop instance CRS-2673 CRS-2677 CRS-2675 crsctl disable crs crsctl enable crs 维护模式 maintenance kernel update 内核升级 firmware 要在某个节点上做操作系统补丁、内核或硬件维护时选择。其他节点继续运行；该节点实例上的服务被迁走，会话在 drain 时间结束后关闭。"},{"k":"scenario","v":"stopall","l":"计划内关闭并启动整个集群","m":"启动 / 停止 crsctl stop cluster -all crsctl start cluster -all 全部关闭 cluster shutdown 计划内停机 srvctl stop database CRS-2677 CRS-4133 CRS-4123 crsctl stop crs crsctl start crs SAN 维护 数据中心维护 datacenter maintenance power off 关闭所有服务器 SAN 或数据中心维护等需要所有节点同时关闭的情况下选择。完全停机：所有节点上的全部数据库、ASM 和 Clusterware 都会停止。"},{"k":"scenario","v":"addnode","l":"向集群添加新节点","m":"添加 / 删除节点 addnode.sh addNode gridSetup.sh -addNode 添加节点 add node cluvfy stage -pre nodeadd dbca -addInstance INS-06006 PRVG- PRVF- INS-13013 INS-40912 CLSRSC-184 PRVG-11050 PRVF-4664 PRVF-7617 cvuqdisk new node 添加新服务器 集群扩容 有新服务器要加入集群时选择（扩容、硬件更新）。不停机：预检查 (cluvfy)、Grid home (addnode.sh)、DB home 和新实例 (dbca -addInstance)。"},{"k":"scenario","v":"delnode","l":"从集群删除节点","m":"添加 / 删除节点 crsctl delete node deinstall -local dbca -deleteInstance 删除节点 delete node remove node cluvfy stage -post nodedel crsctl unpin css CRS-4658 CRS-4661 runInstaller -updateNodeList srvctl remove instance decommission 将服务器移出集群 下线旧服务器 某台服务器要永久移出集群时选择（退役、硬件故障）。删除实例，从该节点卸载 DB 和 Grid home，再将节点从集群中删除；其他节点保持运行。"},{"k":"scenario","v":"service","l":"添加、修改、迁移或删除服务","m":"服务与网络 srvctl add service srvctl modify service srvctl relocate service srvctl remove service ORA-12514 PRCD-1026 preferred available TAF Application Continuity TAC FAN 服务迁移 PRKO-2017 PRCR-1090 ORA-12514: TNS:listener does not currently know of service srvctl start service srvctl status service failback 服务没有迁回 drain_timeout 添加服务 create service 要定义应用连接的服务、修改其分布 (preferred/available) 或 failover 设置 (T"},{"k":"scenario","v":"net","l":"SCAN、VIP、listener 端口或 interconnect 变更","m":"服务与网络 srvctl modify scan srvctl modify scan_listener srvctl modify nodeapps srvctl modify listener oifcfg setif oifcfg delif oifcfg getif IP 变更 SCAN IP VIP 变更 interconnect private network ORA-12545 listener port PRCS-1034 PRKO-2310 PRCR-1079 oifcfg HAIP 169.254 端口变更 SCAN 变更 VIP change remote_listener ASMNET1LSNR_ASM SCAN 名称/IP、某个节点 VIP、listener 端口或私有网络 (interconnect) 要变更时选择。interconnect 变更需要停止整个集群"},{"k":"scenario","v":"ocr_backup","l":"备份 OCR 和 OLR，检查备份","m":"OCR、OLR 和 voting ocrconfig -manualbackup ocrconfig -showbackup ocrconfig -local -manualbackup OLR 备份 OCR 备份 OCR backup ocrcheck ocrdump PROT- ocrconfig -copy ocrconfig -showbackuploc OLR backup OCR 备份在哪里 手工备份 OCR 不停机：手工备份 OCR、在每个节点上备份 OLR，并把备份复制到集群磁盘之外。在打补丁、添加/删除节点、网络变更等任何高风险操作之前选择。"},{"k":"scenario","v":"ocr_restore","l":"OCR 损坏或 CRS disk group 丢失：从备份恢复","m":"OCR、OLR 和 voting PROT-602 PROC-26 CRS-4535 CRS-4000 CRS-1606 ocrconfig -restore crsctl start crs -excl -nocrs OCR corruption OCR 损坏 CRS disk group 丢失 voting disk 丢失 ORA-15017 ORA-15063 PROT-35 PROT-19 ORA-15018 ORA-15033 ocrdump OCR restore CRSD 无法启动 Logical corruption check failed `ocrcheck` 显示损坏、CRSD 无法启动（PROT-/PROC- 错误）或 CRS disk group 完全丢失时选择。整个集群停止；以 exclusive 模式从备份恢复 OCR，disk group 丢失时重建 dis"},{"k":"scenario","v":"olr_restore","l":"某个节点上 Clusterware 无法启动：OLR 损坏","m":"OCR、OLR 和 voting ocrcheck -local ocrconfig -local -restore OLR 损坏 OLR corrupt ohasd 无法启动 CRS-4124 CRS-4639 PROCL- CRS-4123 ohasd.trc olr.loc ocrconfig -local -showbackup Local registry integrity check failed OHASD 无法启动 单个节点无法启动 其他节点运行正常，只有一个节点上 `crsctl start crs` 无法启动 OHASD，且 `ocrcheck -local` 报 OLR 错误时选择。只影响该节点：从备份恢复 OLR。"},{"k":"scenario","v":"ocrvote_move","l":"将 OCR 和 voting 文件迁到其他 disk group","m":"OCR、OLR 和 voting crsctl replace votedisk crsctl query css votedisk ocrconfig -add ocrconfig -delete 迁移 voting disk votedisk 迁移 OCR storage migration ORA-15274 ocrconfig -backuploc asmcmd spmove asmcmd pwmove 迁移 CRS disk group 迁移 voting 文件 OCR move 存储更换 不停机：ocrconfig -add/-delete 和 crsctl replace votedisk。更换存储 (SAN)、提高 CRS disk group 冗余，或让 OCR 同时写入第二个 disk group 时选择。"}]},{"id":"ora-asm-ops","file":"install/guides/ora-asm-ops.json","group":"Oracle","engineId":"oracle","category":"ops","title":"Oracle ASM 日常运维与故障场景","summary":"已安装的 Oracle Restart 或 RAC 环境中 ASM 磁盘组的日常运维与故障处理：容量检查（usable_file_mb）、rebalance 监控、添加/替换/删除磁盘、offline 磁盘、新建磁盘组、mount/dismount、ASM 配置备份和发现字符串。本指南不安装 ASM（安装见 Oracle Restart 或 RAC 指南）；搜索错误代码（如 ORA-15041、ORA-15042）即可打开对应场景。每个","level":"高级","duration":"10 分钟 – 数小时（取决于 rebalance 时长）","topology":"Oracle Restart（单服务器）或 2–8 节点 RAC，Grid Infrastructure 19c（23ai/26ai 差异见注释）","tags":["oracle","asm","disk group","diskgroup","add disk","drop disk","replace disk","rebalance","v$asm_operation","usable_file_mb","disk_repair_time","online disk","offline disk","failgroup","compatible.asm","asmcmd","spbackup","md_backup","asm_diskstring","dsset","udev","asmlib","afd","多路径","rac","oracle restart","日常运维","故障"],"order":13,"steps":4,"scenarios":[{"k":"op","v":"capacity","l":"检查容量与磁盘组健康状况","m":"检查与监控 usable_file_mb required_mirror_free_mb asmcmd lsdg 磁盘组已满 ASM 使用率 free_mb 容量 ORA-15041 ORA-01653 ORA-17502 ORA-15041: diskgroup space exhausted diskgroup space exhausted disk group full ASM disk group full usable_file_mb 为负 negative usable_file_mb ASM 空间不足 asmcmd du 不做任何更改：磁盘组是否已满、剩余多少空间（按冗余级别正确解读 usable_file_mb）、offline 磁盘、磁盘不均衡以及兼容性属性。遇到空间问题、告警或例行检查时首选此卡片。"},{"k":"op","v":"rebal","l":"监控、加速或减速 rebalance","m":"检查与监控 v$asm_operation gv$asm_operation rebalance REBALANCE POWER asm_power_limit asmcmd lsop est_minutes resync compact rebalance 慢 rebalance slow rebalance 卡住 rebalance hung ERRS ARB0 rebalance 何时结束 正在进行的 rebalance/resync 的进度和剩余时间（v$asm_operation、est_minutes）以及修改 POWER。数据库保持打开；只改变 I/O 负载。"},{"k":"op","v":"add","l":"向磁盘组添加新磁盘","m":"磁盘操作 ADD DISK ALTER DISKGROUP ADD DISK 添加磁盘 释放空间 扩容 ORA-15041 ORA-15031 ORA-15033 ORA-15014 ORA-15410 ORA-15032 ORA-15075 ORA-17502 add disk 新 LUN new LUN 磁盘组扩容 extend diskgroup 磁盘组空间不足或需要扩容：在所有节点上准备新 LUN（multipath/udev/ASMLib/AFD），用 ADD DISK 添加，并监控 rebalance。数据库保持打开。"},{"k":"op","v":"replace","l":"用新磁盘替换磁盘（REPLACE DISK）","m":"磁盘操作 REPLACE DISK 更换磁盘 replace disk ORA-15145 storage migration 磁盘更新 ORA-15221 ORA-15033 replace failed disk 更换故障磁盘 disk replacement 存储迁移 storage migration ASM 一次操作即可用新磁盘替换故障（offline）或待更换的磁盘；只复制该磁盘的数据（12.1+，NORMAL/HIGH）。EXTERNAL 冗余下，添加 + 删除在一条命令中完成。数据库保持打开。"},{"k":"op","v":"drop","l":"从磁盘组删除磁盘","m":"磁盘操作 DROP DISK 删除磁盘 UNDROP DISKS FORMER ORA-15041 ORA-15067 归还 LUN drop disk DROPPING 摘除磁盘 remove disk from diskgroup 退还 LUN 用于归还 LUN 或从存储中移除：DROP DISK 会把数据迁移到其余磁盘；rebalance 完成且磁盘状态变为 FORMER 之前不得摘除磁盘。数据库保持打开。"},{"k":"op","v":"failed","l":"磁盘 offline 或故障（NORMAL/HIGH 冗余）","m":"磁盘操作 ONLINE DISK OFFLINE DISK disk_repair_time repair_timer mode_status OFFLINE ORA-15042 ORA-15040 ORA-15130 ORA-15063 missing disk disk offline fast mirror resync ORA-15080 ORA-15081 ORA-27061 ORA-27072 ORA-15066 WARNING: Write Failed WARNING: Read Failed remaining active paths: 0 磁盘变为 offline SYNCING 磁盘无法读取 I/O error ASM NORMAL/HIGH 磁盘组中某磁盘显示为 OFFLINE，或 alert log 中有读写错误：诊断、disk_repair_time 时限、路"},{"k":"op","v":"create","l":"新建磁盘组","m":"磁盘组管理 CREATE DISKGROUP 创建磁盘组 au_size compatible.asm compatible.rdbms failgroup ORA-15018 ORA-15221 ORA-15072 ORA-15031 ORA-15033 create diskgroup new disk group 新磁盘组 用于新建数据、FRA 或应用磁盘组：冗余级别、AU 大小、compatible.asm/rdbms 属性；RAC 中在所有节点上 mount 并注册 Clusterware 资源。现有磁盘组不受影响。"},{"k":"op","v":"mount","l":"Mount 或 dismount 磁盘组","m":"磁盘组管理 srvctl start diskgroup srvctl stop diskgroup ALTER DISKGROUP MOUNT DISMOUNT ORA-15027 ORA-15017 磁盘组无法 mount v$asm_client ORA-15063 ORA-15040 ORA-15130 ORA-15032 ORA-15042 PRCR-1065 CRS-2674 MOUNT FORCE diskgroup dismounted 磁盘组无法打开 insufficient number of disks 用于计划内关闭磁盘组（dismount），或打开已关闭/无法打开的磁盘组（mount）：在所有节点上使用 srvctl start/stop diskgroup。dismount 时不能有数据库正在使用该磁盘组；无法 mount 时按错误代码给出处理方向。"},{"k":"op","v":"spbackup","l":"备份 ASM spfile、口令文件和元数据","m":"配置 asmcmd spbackup asmcmd spget asmcmd pwget asmcmd pwcopy md_backup md_restore ASM spfile 备份 orapwASM asmcmd spcopy GPnP profile profile.xml ASM password file backup ASM metadata backup ASM 备份 无停机：将 ASM spfile、口令文件、磁盘组结构（md_backup）以及 RAC 中的 GPnP profile 备份到 ASM 之外，即使磁盘组丢失也能重建 ASM 配置。安装后以及每次结构变更后都要执行。"},{"k":"op","v":"dstring","l":"修改 ASM 磁盘发现字符串（asm_diskstring）","m":"配置 asm_diskstring asmcmd dsset asmcmd dsget discovery string 发现字符串 ORA-15014 kfod ORA-15020 discovered duplicate ASM disk afd_dsset asm_diskstring change 找不到磁盘 用于修改 ASM 查找磁盘的路径模式（如切换到 multipath、新的 udev 目录、ASMLib/AFD）：先用 kfod 测试，再执行 asmcmd dsset。所有现有磁盘（包括 OCR/voting）必须也能被新字符串发现。"}]},{"id":"ora-autoupgrade","file":"install/guides/ora-autoupgrade.json","group":"Oracle","engineId":"oracle","category":"upgrade","title":"19c → Oracle AI Database 26ai (23ai) 升级：AutoUpgrade","summary":"用于大版本升级 (19c → 26ai)；同一版本内的季度 RU 请使用“应用 Oracle Database 19c Release Update (RU)”指南。使用 Oracle 推荐的 AutoUpgrade 工具：最新的 autoupgrade.jar、配置文件、analyze → fixups → deploy、以 guaranteed restore point 作为自动回退点、19c CDB 的原地升级，或将 non-C","level":"高级","duration":"2–4 小时（停机：视数据库大小和组件而定，30–90 分钟）","topology":"1 台服务器、Oracle Restart 或 RAC（所有节点上均有 26ai home）","tags":["oracle","升级","autoupgrade","19c","26ai","23ai","noncdb","pdb","target_cdb","还原点","restoration","时区","compatible","回退","rac","oracle restart","cluster_database","srvctl"],"order":20,"steps":25,"scenarios":[]},{"id":"ora-dataguard","file":"install/guides/ora-dataguard.json","group":"Oracle","engineId":"oracle","category":"ha","title":"Oracle Data Guard 19c physical standby（使用 Broker；单实例、Oracle Restart、RAC）","summary":"为运行中的 19c primary 数据库，使用 RMAN DUPLICATE … FROM ACTIVE DATABASE 创建 physical standby，并将其加入 Data Guard Broker。最终得到：实时接收并应用 redo 的 standby、状态为 SUCCESS 的 broker 配置、经过验证的 switchover（可选 failover/reinstate 测试以及 Fast-Start Failov","level":"高级","duration":"2–4 小时（取决于数据量）","topology":"Primary（单实例 / Oracle Restart / RAC 2–8 节点）+ physical standby（单实例 / Oracle Restart / RAC）+ 可选 observer","tags":["oracle","data guard","dataguard","standby","physical standby","dgmgrl","broker","rman duplicate","switchover","failover","fast-start failover","observer","19c","容灾","rac","oracle restart","srvctl","standby redo log","apply instance"],"order":20,"steps":43,"scenarios":[]},{"id":"ora-datapump","file":"install/guides/ora-datapump.json","group":"Oracle","engineId":"oracle","category":"backup","title":"Oracle Data Pump：导出/导入、迁移、schema 刷新与 transportable","summary":"使用 expdp/impdp 按表、schema、表空间或全库范围导出、导入，在两个数据库之间迁移（dump 文件 + scp + checksum，或 NETWORK_LINK），以及从 dump 中提取 DDL（SQLFILE）。包括 directory、用户和 parfile 的准备、一致性、并行、remap、监控以及中断作业的清理。RAC 上通过 SCAN + 服务连接，共享 directory 时用 CLUSTER=YES，本","level":"中级","duration":"30–90 分钟（取决于数据量）","topology":"1 个数据库（单实例、Oracle Restart 或 RAC）；迁移时为源库 + 目标库","tags":["oracle","data pump","datapump","expdp","impdp","export","import","dump","parfile","remap_schema","network_link","sqlfile","table_exists_action","flashback_time","数据迁移","migration","logical backup","逻辑备份","rac","scan","cluster","acfs","cron","wallet","定时任务","refresh","schema 刷新","prod to test","drop user cascade","transportable tablespace","tts","xtts","full transportable","transportable=always","dbms_tts","transport_set_check","endian","rman convert","cross-platform","11g to 19c","non-cdb to pdb","encryption","encryption_password","compression","attach","kill_job","start_job","continue_client","orphan","dba_datapump_jobs","lob","securefile","data_options"],"order":20,"steps":16,"scenarios":[{"k":"op","v":"export","l":"导出：从数据库生成 dump 文件","m":"导出/导入 expdp export 导出 dump 逻辑备份 logical backup ORA-39001 ORA-39000 ORA-39070 ORA-31633 ORA-01555 ORA-39095 ORA-39094 ORA-00439 LRM-00123 UDE-00002 ORA-39127 将表、schema、表空间或整个数据库写入 dump 文件（expdp）。数据库保持打开，应用继续运行。这是逻辑副本，不能替代 RMAN 备份（时间点恢复和完全恢复需要 RMAN）。"},{"k":"op","v":"import","l":"导入：将 dump 文件加载到数据库","m":"导出/导入 impdp import 加载 dump REMAP_SCHEMA REMAP_TABLESPACE TABLE_EXISTS_ACTION ORA-39002 ORA-39087 ORA-39142 ORA-39143 ORA-39083 ORA-12899 ORA-39171 ORA-39082 ORA-31684 ORA-31640 ORA-39059 ORA-01917 ORA-39151 ORA-39111 ORA-01653 ORA-30036 使用 impdp 加载之前生成的 dump 文件；可以重映射 schema、表空间和表名。目标库保持打开；使用被加载对象的应用必须停止。"},{"k":"op","v":"move","l":"迁移：将数据从源库迁移到目标库","m":"迁移与刷新 数据迁移 migration NETWORK_LINK database link scp checksum ORA-31679 ORA-02085 ORA-39183 导出 → 复制文件 → 导入，或使用 NETWORK_LINK 直接迁移而不生成 dump 文件。如果目标库上还没有该 schema（首次加载），选择此卡片；如果目标库上已有同一 schema 且要用生产副本替换，选择“schema 刷新”卡片。源库保持打开。"},{"k":"op","v":"sqlfile","l":"从 dump 文件中仅提取 DDL（SQLFILE）","m":"导出/导入 SQLFILE 提取 DDL dump 内容 dump 里有什么 metadata DBMS_METADATA 不加载任何数据；对象的 CREATE 语句写入 .sql 文件。数据库不做任何更改。"},{"k":"op","v":"refresh","l":"从生产到测试的 schema 刷新（refresh）","m":"迁移与刷新 refresh schema 刷新 刷新测试库 prod to test 生产到测试 UAT 刷新 DROP USER CASCADE ORA-01940 ORA-02380 ORA-01917 ORA-31684 ORA-00031 TABLE_EXISTS_ACTION=REPLACE 用生产环境的当前版本替换测试/UAT 中已存在的 schema：备份目标 schema、断开会话、DROP USER … CASCADE 或 TABLE_EXISTS_ACTION=REPLACE、保留密码/profile 和权限。源库保持打开；作业期间目标 schema 不可用。"},{"k":"op","v":"tts","l":"通过复制数据文件迁移表空间（transportable tablespace）","m":"Transportable（复制数据文件） transportable tablespace TTS XTTS TRANSPORT_TABLESPACES TRANSPORT_DATAFILES DBMS_TTS TRANSPORT_SET_CHECK endian cross-platform RMAN CONVERT ORA-29335 ORA-29341 ORA-29342 ORA-29345 ORA-29349 ORA-19722 ORA-01647 大表空间（数百 GB 及以上）通过复制数据文件而不是逐行迁移；支持相同或不同平台/endian（RMAN CONVERT）。两端字符集必须相同。作业期间源库上的表空间为 READ ONLY（写入中断）。"},{"k":"op","v":"fulltts","l":"迁移整个数据库：full transportable（11g/12c → 19c，non-CDB → PDB）","m":"Transportable（复制数据文件） full transportable TRANSPORTABLE=ALWAYS FULL=Y VERSION=12 11g to 19c 11g 到 19c non-CDB to PDB 迁移到 PDB migration upgrade by migration ORA-39123 ORA-01647 ORA-29349 ORA-39127 使用 FULL=Y TRANSPORTABLE=ALWAYS，用户表空间以数据文件形式、其余内容（用户、PL/SQL、权限）以元数据形式迁移到新 PDB；11.2.0.3+ 源库上使用 VERSION=12。作业期间源库上所有用户表空间为 READ ONLY。"},{"k":"op","v":"jobfix","l":"中断/挂起的 Data Pump 作业：监控、恢复、终止","m":"监控与故障 ATTACH START_JOB CONTINUE_CLIENT KILL_JOB STOP_JOB dba_datapump_jobs orphan master table NOT RUNNING IDLING 挂起 resumable ORA-39171 ORA-31626 ORA-31633 ORA-39002 ORA-31634 ORA-39097 ORA-39126 某个 expdp/impdp 作业挂起（resumable wait）、已停止或中断；需要用 ATTACH 连接并恢复，用 KILL_JOB 终止，或清理孤立的 master 表。数据库保持打开。"}]},{"id":"ora-dataguard-ops","file":"install/guides/ora-dataguard-ops.json","group":"Oracle","engineId":"oracle","category":"ops","title":"Oracle Data Guard 日常运维与故障场景 (19c / 23ai)","summary":"已部署的 Data Guard (physical standby) 配置的日常运维与故障恢复 runbook；初始搭建请使用 Data Guard 安装指南。如果不确定该做什么，请从“状态与延迟检查”开始：只读诊断会把您引导到正确的场景（计划内 switchover、failover、reinstate 旧 primary、归档间隙、用 RECOVER … FROM SERVICE 前滚、重建 standby、snapshot sta","level":"高级","duration":"5 分钟（检查）– 4 小时（重建）","topology":"Primary（单实例 / Oracle Restart / RAC）+ physical standby（单实例 / Oracle Restart / RAC）+ 可选 observer","tags":["oracle","data guard","dataguard","standby","physical standby","dgmgrl","broker","switchover","failover","reinstate","archive gap","fal","recover from service","roll forward","snapshot standby","active data guard","redo apply","mrp","apply lag","transport lag","protection mode","fast-start failover","fsfo","observer","standby_file_management","standby redo log","unnamed","rman","runbook","dr","rac","19c","23ai"],"order":21,"steps":5,"scenarios":[{"k":"scenario","v":"health","l":"状态与延迟检查（一切正常吗？）","m":"监控与检查 ORA-16809 ORA-16810 ORA-16853 ORA-16855 ORA-16857 ORA-16766 ORA-16825 apply lag transport lag 延迟 lag SHOW CONFIGURATION WARNING Data Guard 状态 ORA-16854 ORA-16856 ORA-16191 ORA-16198 ORA-03135 ORA-16714 ORA-16792 ORA-16047 standby 是否落后 transport lag 高 Data Guard 健康检查 health check 只读：配置、transport/apply lag、归档间隙、进程和 alert log；不做任何更改。如果不确定该做什么，请从这里开始：最后一步会根据输出告诉您应选择的场景。"},{"k":"scenario","v":"switchover","l":"计划内 switchover（互换角色）","m":"角色转换 switchover 角色切换 role transition DR 演练 ORA-16475 ORA-16470 ORA-16467 Ready for Switchover ORA-16541 ORA-16516 switchover_status TO STANDBY SESSIONS ACTIVE switchback 切回原角色 计划内维护 站点切换 Primary 和 standby 都正常，您希望有计划地互换角色（维护、DR 演练、切回原角色）；不会丢失数据，应用中断几分钟。只要还能访问 primary，角色转换就始终选择此项。"},{"k":"scenario","v":"failover","l":"Primary 丢失：failover 到 standby","m":"故障与恢复 failover primary 崩溃 primary down 站点丢失 disaster ORA-16472 ORA-01034 ORA-12541 FAILOVER TO ACTIVATE PHYSICAL STANDBY ORA-12514 ORA-12170 ORA-03113 ORA-16625 ORA-16778 primary 无法访问 数据中心丢失 FAILOVER TO IMMEDIATE 紧急情况 Primary 无法访问且不会恢复（服务器、存储或站点丢失）；standby 成为 primary。不可逆：在 MaxPerformance 下最后几秒的数据可能丢失，旧 primary 之后需要 reinstate 或重建。如果仍能访问 primary，请选择 switchover。"},{"k":"scenario","v":"reinstate","l":"Failover 后将旧 primary 恢复为 standby (reinstate)","m":"故障与恢复 reinstate ORA-16661 ORA-16795 ORA-38729 ORA-16653 needs to be reinstated 旧 primary flashback ORA-16649 split-brain 旧 primary 无法打开 FastStartFailoverAutoReinstate REINSTATE DATABASE standby_became_primary_scn 已执行 failover，旧 primary 服务器已恢复；如果 Flashback Database 已启用，无需重建即可将其转换为新 primary 的 standby。新 primary 保持打开。如果 flashback 未启用或闪回窗口不够，诊断步骤会引导您去重建。"},{"k":"scenario","v":"gap","l":"归档间隙 (gap)：standby 在等待缺失的归档日志","m":"故障与恢复 archive gap 归档间隙 v$archive_gap FAL Fetching gap ORA-00308 ORA-16401 ORA-27037 ORA-16055 gap sequence waiting for log WAIT_FOR_GAP FAL[client] Media Recovery Waiting for thread 缺失归档 missing archive log ORA-16089 `v$archive_gap` 返回行，或 alert log 中有 FAL/gap 消息；apply 卡在某个 sequence。数据库保持打开。如果缺失的归档日志在任何地方都不存在，可在表单中选择转到前滚路径。"},{"k":"scenario","v":"rollfwd","l":"Standby 严重落后或归档已丢失：前滚 (RECOVER FROM SERVICE)","m":"故障与恢复 roll forward 前滚 standby RECOVER STANDBY DATABASE FROM SERVICE nologging ORA-01578 ORA-26040 standby 落后 resync 重新同步 ORA-10567 BACKUP INCREMENTAL FROM SCN standby 落后数天 NONLOGGED BLOCK incremental roll forward 间隙中的归档日志在任何地方都不存在、NOLOGGING 操作损坏了 standby，或 standby 已落后数天；无需重建 standby，即可从 primary 增量同步 (18c+)。"},{"k":"scenario","v":"rebuild","l":"从头重建 standby","m":"故障与恢复 rebuild 重建 standby re-create standby ORA-16795 DUPLICATE standby 丢失 控制文件丢失 ORA-00205 ORA-01157 standby 文件被删除 DROP DATABASE standby 磁盘丢失 无法 reinstate（没有 flashback），也无法前滚，或者 standby 的文件/控制文件已丢失；删除 standby 并用 DUPLICATE 重新创建。Primary 保持打开，但在重建完成前不受保护（除非还有其他 standby）。"},{"k":"scenario","v":"snapshot","l":"转换为 snapshot standby / 转换回来（用于测试的可写副本）","m":"测试与报表 snapshot standby CONVERT TO SNAPSHOT STANDBY 测试环境 read write standby ORA-16000 ORA-38784 ORA-16433 CONVERT TO PHYSICAL STANDBY SNAPSHOT STANDBY guaranteed restore point 打开 standby 用于测试 您希望临时以读写方式打开 standby 进行测试，然后丢弃更改并恢复为 physical standby；primary 不受影响，redo 继续接收，但作为 snapshot standby 期间，该 standby 无法用于 failover。"},{"k":"scenario","v":"apply","l":"停止 / 启动 Redo Apply，更换 apply 实例，以 read-only 打开","m":"日常运维 MRP redo apply APPLY-OFF APPLY-ON apply instance read only Active Data Guard ORA-16766 ORA-16136 ORA-01153 real-time query WAIT_FOR_LOG APPLYING_LOG READ ONLY WITH APPLY OPEN READ ONLY PreferredApplyInstance intended state apply 已停止 MRP 未运行 您希望为维护而停止或重启 MRP、在 RAC standby 上将 apply 移到另一个实例，或打开 standby 用于报表（Active Data Guard 许可警告）。"},{"k":"scenario","v":"prot","l":"保护模式、Fast-Start Failover 与 observer","m":"日常运维 protection mode MaxAvailability MaxPerformance MaxProtection SYNC ASYNC FSFO fast-start failover observer ORA-16627 ORA-16651 ORA-16820 ORA-16819 ORA-16072 ORA-16647 NetTimeout LogXptMode FASTSYNC protection_level 保护模式 您希望在 MaxPerformance / MaxAvailability / MaxProtection 之间切换、启用或禁用 FSFO，或管理 observer。"},{"k":"scenario","v":"files","l":"添加数据文件、redo log 和 standby redo log / UNNAMED 文件","m":"日常运维 standby_file_management UNNAMED ORA-01111 ORA-01110 ORA-01157 ORA-01274 添加数据文件 add datafile 添加 redo log standby redo log SRL ORA-16789 db_file_name_convert ADD STANDBY LOGFILE ORA-01136 standby 数据文件已添加 要在 primary 上添加数据文件或 redo log，standby 上出现了 UNNAMED 文件 (ORA-01111/ORA-01274)，或缺少 standby redo log。"},{"k":"scenario","v":"backup","l":"从 standby 做 RMAN 备份","m":"日常运维 rman standby backup 从 standby 备份 archivelog deletion policy RMAN-08137 recovery catalog offload backup RMAN-06820 BACKED UP 1 TIMES CONFIGURE DB_UNIQUE_NAME standby 备份 您希望在 standby 上运行 RMAN 备份以减轻 primary 的备份负载，并相应地设置归档日志删除策略。"}]},{"id":"ora-backup-strategy","file":"install/guides/ora-backup-strategy.json","group":"Oracle","engineId":"oracle","category":"backup","title":"Oracle 备份策略与自动化 (RMAN + Data Pump)","summary":"为已安装的数据库建立长期、自动化的备份方案（故障时的恢复见另一篇指南：\"使用 RMAN 进行 restore 与 recovery\"）：每周 level 0 + 每日 level 1（或每天全备）、频繁的归档日志备份、FRA 或独立目录、retention、压缩、加密、block change tracking 和并行。另外还有使用 wallet 的完整 expdp。完整的脚本（锁、日志、错误检查、电子邮件）通过 cron、system","level":"中级","duration":"60–120 分钟（不含首次 level 0 的时间）","topology":"1 台服务器（单实例或 Oracle Restart）或 RAC（2–8 个节点）","tags":["oracle","rman","备份","backup","策略","level 0","level 1","增量备份","archivelog","fra","retention","block change tracking","data pump","expdp","crontab","cron","systemd timer","dbms_scheduler","自动化","wallet","mkstore","rac","oracle restart","snapshot controlfile","asm","/etc/cron.d","ol7","recovery catalog","create catalog","register database","恢复演练","restore drill","restore validate","preview"],"order":21,"steps":33,"scenarios":[]},{"id":"ora-rman-restore","file":"install/guides/ora-rman-restore.json","group":"Oracle","engineId":"oracle","category":"backup","title":"使用 RMAN 进行还原和恢复","summary":"故障发生时逐步引导完成正确的 RMAN 恢复：19 个场景——数据文件、SYSTEM/UNDO、临时文件、坏块、联机重做日志、控制文件和 spfile 丢失；完全还原、还原到其他目录/ASM 以及还原到其他服务器；时间点恢复（CDB 或 PDB PITR）、Flashback Database、表空间 PITR、单表恢复（RECOVER TABLE）、已删除的 PDB；通过 Data Guard 经网络还原（FROM SERVICE）、","level":"高级","duration":"30 分钟 – 数小时（取决于数据库大小）","topology":"1 台服务器（单实例或 Oracle Restart）或 RAC；新主机场景需要源 + 目标","tags":["oracle","rman","还原","恢复","数据库恢复","pitr","时间点恢复","resetlogs","recover table","控制文件","spfile","autobackup","数据文件","preview","validate","dbid","新服务器","灾难恢复","rac","oracle restart","cluster_database","srvctl","asm","块损坏","坏块","ORA-01578","重做日志","ORA-00313","clear logfile","临时文件","tspitr","flashback database","还原点","已删除的 pdb","unplug","from service","data guard","还原归档日志","catalog start with","set newname","undo","system 表空间"],"order":22,"steps":10,"scenarios":[{"k":"scenario","v":"preview","l":"演练：RESTORE … PREVIEW / VALIDATE（不做任何更改）","m":"演练与备份记录 RESTORE PREVIEW RESTORE VALIDATE 还原演练 还原测试 备份是否可用 backup test restore drill 演练 RMAN VALIDATE 备份是否完好 没有任何损坏；您想在不写入数据的情况下确认备份确实足以完成恢复（数据库保持打开）。这也是每月还原演练以及不确定该选哪个场景时的第一步。"},{"k":"scenario","v":"catalog","l":"备份已移到其他目录 / RMAN 找不到备份（CATALOG）","m":"演练与备份记录 CATALOG START WITH RMAN-06023 RMAN-06025 RMAN-06026 ORA-19505 ORA-27037 EXPIRED 备份已移动 backup moved no backup found CROSSCHECK NFS 已更改 找不到备份 备份片存在，但 RMAN 不知道（已移动、NFS 已更改、来自其他服务器）：出现 RMAN-06023/06025。数据库保持打开。"},{"k":"scenario","v":"arch","l":"仅还原归档日志（RESTORE ARCHIVELOG）","m":"演练与备份记录 RESTORE ARCHIVELOG ORA-00308 还原归档日志 archive log restore archive gap LogMiner SET ARCHIVELOG DESTINATION LogMiner、备库间隔或手动恢复需要把特定序列号范围的归档日志放回磁盘；数据库保持打开，不覆盖任何文件。"},{"k":"scenario","v":"datafile","l":"单个数据文件或表空间丢失/损坏（数据库保持打开）","m":"丢失 / 损坏 ORA-01157 ORA-01110 ORA-01116 ORA-27041 ORA-00376 ORA-01113 数据文件丢失 数据文件被删除 missing datafile lost datafile 表空间丢失 ORA-27037 ORA-01135 ORA-01115 ORA-01114 数据文件损坏 数据库已打开，只有一个数据文件或表空间无法读取（ORA-01157/01110/01116）；其他一切正常。如果文件属于 SYSTEM/UNDO，选择“SYSTEM 或 UNDO”卡片；如果是临时文件，选择“临时文件”卡片；如果文件能打开但有几个块损坏（ORA-01578），选择“坏块”卡片。"},{"k":"scenario","v":"block","l":"存在坏块（ORA-01578，块介质恢复）","m":"丢失 / 损坏 ORA-01578 ORA-26040 ORA-01110 坏块 块损坏 corrupt block v$database_block_corruption RECOVER CORRUPTION LIST 块介质恢复 nologging ORA-19566 Marked Corrupt Corrupt block relative dba DBVERIFY 查询报 ORA-01578/ORA-26040，备份因 ORA-19566 中止，或 VALIDATE 发现了坏块；文件其余部分完好。数据库保持打开，只修复坏块（Enterprise Edition；SE2 上需还原整个文件）。如果整个文件都无法打开（ORA-01157），选择“单个数据文件”卡片。"},{"k":"scenario","v":"sysdf","l":"SYSTEM 或 UNDO 数据文件丢失（数据库无法打开）","m":"丢失 / 损坏 ORA-01157 ORA-01110 ORA-01116 ORA-01147 ORA-00376 ORA-01541 SYSTEM 数据文件 UNDO 数据文件 undo 表空间丢失 system tablespace lost 数据库无法打开 ORA-01113 system01.dbf undotbs 丢失或损坏的文件属于 SYSTEM 或 UNDO 表空间：无法脱机，数据库（如果是 PDB 的文件则仅该 PDB）需关闭并在 MOUNT 状态下还原。不会丢失数据。如果是其他表空间的文件，“单个数据文件”卡片可在不关闭数据库的情况下解决。"},{"k":"scenario","v":"temp","l":"临时文件丢失或损坏（TEMP 表空间）","m":"丢失 / 损坏 ORA-01157 ORA-01187 ORA-25153 ORA-01116 ORA-01122 ORA-01110 临时文件 temp tablespace 临时表空间 TEMP 丢失 temp01.dbf dba_temp_files 排序/哈希操作报 ORA-01157/ORA-01187/ORA-25153，且该文件是临时文件（错误消息中的编号大于 `db_files` 值，例如 `data file 201`）。无需还原；添加新的临时文件即可，数据库保持打开。"},{"k":"scenario","v":"redo","l":"联机重做日志丢失或损坏","m":"丢失 / 损坏 ORA-00313 ORA-00312 ORA-00327 ORA-00366 ORA-00367 ORA-00353 ORA-00354 ORA-00316 ORA-01624 重做日志丢失 联机重做 lost redo log CLEAR LOGFILE ORA-00314 ORA-00320 ORA-00321 ORA-00340 ORA-00350 ORA-00470 CLEAR UNARCHIVED 告警日志中出现 ORA-00313/00312（无法打开）或 ORA-00327/00366/00367（损坏）。如果只丢失一个成员或该组为 INACTIVE，数据库保持打开；如果丢失的是 CURRENT 组，则需要还原 + RESETLOGS，最近的事务会丢失。诊断步骤会显示组的状态；据此做出选择。"},{"k":"scenario","v":"controlfile","l":"所有控制文件副本均丢失","m":"丢失 / 损坏 ORA-00205 ORA-00202 ORA-00210 ORA-27037 控制文件丢失 control file lost RESTORE CONTROLFILE autobackup ORA-00206 ORA-00214 ORA-00221 ORA-00227 控制文件损坏 实例在 MOUNT 阶段找不到或无法读取控制文件（ORA-00205/00202/00210）；数据文件和重做日志完好。如果有一个副本完好则无需还原（诊断步骤会显示）；如果全部丢失，则从 autobackup 还原并以 RESETLOGS 打开，由于联机重做日志完好，不会丢失数据。"},{"k":"scenario","v":"spfile","l":"spfile 丢失","m":"丢失 / 损坏 ORA-01078 LRM-00109 ORA-01565 spfile 丢失 spfile lost 参数文件 RESTORE SPFILE CRS-5017 PRCR-1079 spfile 被删除 spfile 被删除或损坏；实例无法启动（ORA-01078/LRM-00109），或实例已打开但文件不存在。不涉及数据文件，不会丢失数据；如果实例仍在运行，也不会停机。"},{"k":"scenario","v":"full","l":"完全还原，同一服务器（数据文件丢失；控制文件和重做日志完好）","m":"丢失 / 损坏 所有数据文件丢失 all datafiles lost full restore RESTORE DATABASE 磁盘故障 存储故障 磁盘损坏 LUN 丢失 数据文件被删除 全部或大部分数据文件丢失，控制文件和联机重做日志完好；数据库需关闭，不会丢失数据。如果文件原来所在的磁盘/目录已不可用，选择“其他目录”卡片；如果控制文件也丢失，先选择“控制文件”卡片；如果服务器完全不可用，选择“还原到其他服务器”卡片。"},{"k":"scenario","v":"relocate","l":"磁盘损坏：将数据库还原到其他目录 / ASM（SET NEWNAME）","m":"丢失 / 损坏 SET NEWNAME SWITCH DATAFILE ALL 还原到其他目录 还原到 ASM new location 磁盘已更换 挂载点丢失 relocate ORA-15001 ORA-15040 disk group dismounted 数据文件所在的磁盘或磁盘组不可用；控制文件和重做日志完好。文件还原到新目录或 ASM 磁盘组，数据库需关闭，不会丢失数据。"},{"k":"scenario","v":"pitr","l":"恢复到某个时间 / SCN（PITR，RESETLOGS）","m":"时间点恢复与回退 PITR point in time recovery 时间点恢复 恢复到 SCN 误 DELETE 误 UPDATE TRUNCATE 逻辑错误 logical error UNTIL TIME 数据被删除 DROP USER UNTIL SCN RESETLOGS 错误的 DELETE/UPDATE/TRUNCATE 或有问题的应用版本：将数据库（或仅 PDB）恢复到过去的某个时刻；该时刻之后的所有更改都会丢失（RESETLOGS）。先考虑范围更小的方法：单表 → “单表恢复”，单个表空间 → TSPITR，已开启闪回 → “Flashback Database”（不需要还原，快得多）。"},{"k":"scenario","v":"flashback","l":"使用 Flashback Database 回退（还原点 / SCN / 时间）","m":"时间点恢复与回退 FLASHBACK DATABASE FLASHBACK PLUGGABLE DATABASE 还原点 guaranteed restore point 回退 升级回退 测试后回退 FLASHBACK_ON FLASHBACK TO RESTORE POINT 已开启闪回或存在担保还原点，且没有丢失任何文件：无需还原，几分钟内即可回退（Enterprise Edition）。需要 RESETLOGS。检查是否开启：`SELECT flashback_on FROM v$database;` → YES，或 `v$restore_point` 中有 GUARANTEE_FLASHBACK_DATABASE = YES 的行。"},{"k":"scenario","v":"tspitr","l":"将一个表空间恢复到过去的某个时刻（TSPITR）","m":"时间点恢复与回退 TSPITR tablespace point in time RECOVER TABLESPACE AUXILIARY DESTINATION DBMS_TTS 恢复表空间 ORA-29308 错误操作只影响了一个表空间中的对象；数据库其余部分必须保持在当前时刻（Enterprise Edition）。操作期间该表空间处于脱机状态，其中所有对象都回到目标时刻；如果只影响了一两张表，“单表恢复”回退的内容更少。"},{"k":"scenario","v":"table","l":"单表恢复（RECOVER TABLE，12c+ Enterprise Edition）","m":"时间点恢复与回退 RECOVER TABLE 表被删除 DROP TABLE 恢复表 table recovery ORA-00942 REMAP TABLE ORA-38305 FLASHBACK TABLE 回收站 PURGE 某张表被误删或损坏；数据库其余部分必须保持原样（12c+ Enterprise Edition）。如果是 DROP 且未 PURGE，先查看回收站：`FLASHBACK TABLE … TO BEFORE DROP` 只需几秒（诊断步骤会显示）。"},{"k":"scenario","v":"pdbdrop","l":"恢复已删除的 PDB（DROP PLUGGABLE DATABASE）","m":"时间点恢复与回退 DROP PLUGGABLE DATABASE PDB 被删除 dropped PDB RMAN-06813 ORA-65011 ORA-65179 KEEP DATAFILES 恢复 PDB unplug DBMS_PDB.RECOVER 某个 PDB 被 `DROP PLUGGABLE DATABASE` 删除。如果文件仍在，则用 XML 重新插入；如果文件已删除，则从备份恢复（21c+ 一条命令，19c 通过临时 CDB）。CDB 保持打开。"},{"k":"scenario","v":"newhost","l":"还原到其他服务器（新主机，从控制文件 autobackup）","m":"其他服务器与备库 新服务器 其他服务器 服务器丢失 灾难恢复 DR new host 克隆 clone 测试环境 服务器损毁 server lost restore to another host 服务器完全不可用，或要搭建副本/测试环境：将备份还原到另一台服务器（仅限单实例、文件系统）。恢复到最后一个已备份的归档日志，之后的内容会丢失；副本以 RESETLOGS 打开。"},{"k":"scenario","v":"fromsvc","l":"Data Guard：通过网络从备库/主库获取文件（FROM SERVICE）","m":"其他服务器与备库 RESTORE FROM SERVICE FROM SERVICE RECOVER STANDBY DATABASE 从备库还原 Data Guard 备库落后 archive gap 备库控制文件 ORA-01110 v$archive_gap apply lag 已部署 Data Guard：无需备份片，即可从备库获取主库上丢失的数据文件，或从主库前滚落后的备库、获取丢失的备库控制文件（12c+）。"}]},{"id":"ora-goldengate","file":"install/guides/ora-goldengate.json","group":"Oracle","engineId":"oracle","category":"ha","title":"Oracle GoldenGate Microservices：Oracle → Oracle 单向复制","summary":"使用 GoldenGate 21c 或 23ai/26ai Microservices，将一个 PDB schema 持续（延迟为秒级）复制到另一个数据库。完成后，源端运行 Integrated Extract 和 Distribution Path，目标端运行 Parallel Replicat；目标 schema 从一致的 SCN 通过初始加载填充，并用 heartbeat 测量端到端延迟。与 Data Guard 的区别：这是行级","level":"高级","duration":"3–5 小时","topology":"源 DB 服务器（OGG 源 deployment）+ 目标 DB 服务器（OGG 目标 deployment）；RAC 环境下 GoldenGate 位于节点 1（或通过 XAG 作为集群资源）","tags":["oracle","goldengate","ogg","microservices","复制","replication","cdc","integrated extract","parallel replicat","distribution path","adminclient","oggca","21c","23ai","26ai","rac","scan","xag","acfs","tranlogoptions"],"order":30,"steps":42,"scenarios":[]},{"id":"ora-goldengate-ops","file":"install/guides/ora-goldengate-ops.json","group":"Oracle","engineId":"oracle","category":"ops","title":"Oracle GoldenGate 日常运维与故障场景（Microservices 21c / 23ai）","summary":"已安装的 GoldenGate Microservices 复制的日常运维和故障恢复 runbook（安装请参阅 GoldenGate 安装指南）。如果不确定该做什么，请从“健康与延迟检查”开始：只读诊断会引导你找到正确的场景 —— Extract 和 Replicat ABEND（report、discard、OGG-xxxxx 错误）、将已有数据的表加入复制（一致性 SCN + AFTERCSN）、移除表、单表重新同步（resync","level":"高级","duration":"5 分钟（检查）– 2 小时（升级、添加表）","topology":"源服务器（源 deployment、Integrated Extract、Distribution Path）+ 目标服务器（目标 deployment、Parallel Replicat）","tags":["oracle","goldengate","ogg","microservices","adminclient","runbook","abend","extract","replicat","lag","stats","info all","heartbeat","report","discard","reperror","handlecollisions","cdr","conflict","add trandata","schematrandata","aftercsn","atcsn","mapexclude","tableexclude","resync","purge trail","purgeoldextracts","trail","alter extract scn","etrollover","upgrade","patch","opatch","oggHome","data guard","switchover","failover","handledlfailover","OGG-00664","OGG-01004","OGG-01296","OGG-02912","21c","23ai","26ai"],"order":31,"steps":8,"scenarios":[{"k":"scenario","v":"health","l":"健康与延迟检查（一切正常吗？）","m":"监控与检查 INFO ALL lag 延迟 STATS heartbeat GG_LAG Service Manager ggserr.log OGG-01027 OGG-01738 Long Running Transaction STOPPED RUNNING Distribution Path DISTPATH Receiver INFO DISTPATH 复制延迟 replication lag 只读：Service Manager 和各 microservice、INFO ALL、lag、STATS、heartbeat 延迟、数据库中的 capture 状态以及 ggserr.log；不做任何更改。如果不确定该做什么，请从这里开始。"},{"k":"scenario","v":"ext_abend","l":"Extract ABENDED（已停止，报错）","m":"故障（ABEND） Extract ABENDED ABEND OGG-00664 OGG-00446 OGG-02171 OGG-02912 OGG-02022 OGG-02091 OGG-01044 OGG-01668 ORA-01291 ORA-01017 ORA-28001 ORA-04031 ORA-01031 missing log archived log not found logmining OGG-00868 Extract STARTING Extract 停止 capture ABORTED 源端 Extract 处于 ABENDED 或不断重启：根据 report 文件中的 OGG-xxxxx 错误找出原因（连接、缺少归档日志、log mining、权限、磁盘）并排除，然后让 Extract 从中断处继续启动。源数据库不受影响；延迟会累积。"},{"k":"scenario","v":"rep_abend","l":"Replicat ABENDED（无法在目标端应用）","m":"故障（ABEND） Replicat ABENDED OGG-01004 OGG-01296 OGG-01154 OGG-00869 OGG-01161 OGG-01169 OGG-01431 OGG-00199 OGG-00423 ORA-01403 ORA-00001 ORA-02291 ORA-00942 ORA-01653 no data found unique constraint discard REPERROR HANDLECOLLISIONS CDR conflict Replicat 停止 ORA-01400 ORA-12899 value too large 目标端 Replicat 无法应用某个事务：从 report 和 discard 文件中找出表、操作和错误；修正数据或用临时 REPERROR 跳过，然后让 Replicat 从中断处继续启动。目标端保持打开；延"},{"k":"scenario","v":"add_table","l":"将已有数据的表加入复制（初始加载 + AFTERCSN）","m":"表管理 添加表 add table ADD TRANDATA ADD SCHEMATRANDATA AFTERCSN ATCSN initial load 初始加载 flashback_scn MAPEXCLUDE 新表 在不中断的情况下将一张已存在且包含数据的表加入正在运行的复制：ADD TRANDATA、基于一致性 SCN 的 expdp/impdp，以及一个只为该表应用此 SCN 之后变更的独立 Replicat（AFTERCSN）。其他表继续正常复制。"},{"k":"scenario","v":"remove_table","l":"将表从复制中移除","m":"表管理 移除表 remove table TABLEEXCLUDE MAPEXCLUDE DELETE TRANDATA exclude 该表将不再复制：在 Extract 中使用 TABLEEXCLUDE（或删除 TABLE 行），在 Replicat 中使用 MAPEXCLUDE，必要时执行 DELETE TRANDATA。进程会短暂重启。"},{"k":"scenario","v":"resync","l":"重新同步单张表（resync）","m":"表管理 resync 重新同步 out of sync 数据差异 数据不一致 Veridata OGG-01004 ORA-01403 ORA-00001 row count 行数不一致 某张表在目标端的数据与源端出现不一致（手动修改、跳过的错误、初始加载不完整）：从源端按一致性 SCN 重新加载该表，并由一个临时 Replicat 应用该 SCN 之后的变更。其他表不受影响。"},{"k":"scenario","v":"purge","l":"trail 文件占满磁盘：清理（Purge Trail）","m":"维护 trail purge PURGEOLDEXTRACTS Purge Trail 磁盘已满 No space left on device trail 清理 var/lib/data trail 目录已满 ENOSPC trail 目录不断增长或磁盘即将写满：查看哪些文件正在被读取，使用 USECHECKPOINTS 定义 Purge Trail 任务（相当于 Classic 中的 PURGEOLDEXTRACTS）；紧急情况下手动删除已读取的文件。"},{"k":"scenario","v":"reposition","l":"将 Extract 重新定位到指定 SCN","m":"维护 reposition ALTER EXTRACT SCN BEGIN SCN ETROLLOVER 从 SCN 启动 trail 丢失 first_scn 回退 需要让 Extract 从某个 SCN 重新开始（trail 丢失、起始点错误、目标端回退）：通过 ALTER EXTRACT … SCN 和 ETROLLOVER 从新的 trail 文件启动。相同的数据可能会再次到达目标端。"},{"k":"scenario","v":"upgrade","l":"GoldenGate 版本升级 / 补丁（Microservices）","m":"维护 upgrade patch 升级 OPatch out-of-place oggHome Service Manager upgrade bundle patch 21c 23ai 26ai release update GoldenGate 补丁 Service Manager PATCH 迁移到新的 GoldenGate 版本或补丁：out-of-place（安装新 home，通过 REST 将 Service Manager 和各 deployment 迁移到新 home）或使用 OPatch in-place。Extract/Replicat 会短暂停止；回退方式是切回旧 home。"},{"k":"scenario","v":"dg_role","l":"源端 Data Guard switchover/failover 后继续运行 Extract","m":"角色转换 Data Guard switchover failover role transition HANDLEDLFAILOVER ORA-01033 ORA-16000 ORA-01109 新 primary standby FSFO ORA-12514 角色转换 Extract 新 primary FAILOVERTARGETDESTID 源数据库发生了 switchover 或 failover；Extract 必须连接新的 primary 并从 checkpoint 继续：检查连接（credential alias/服务）、capture 状态、HANDLEDLFAILOVER 以及可能的数据不一致。"}]},{"id":"ora-security","file":"install/guides/ora-security.json","group":"Oracle","engineId":"oracle","category":"security","title":"Oracle Database 安全加固（审计、网络、TLS、身份、权限、Unified Auditing、TDE）","summary":"这不是安装指南：用于发现并修复运行中的 Oracle 19c / 23ai (26ai) 数据库（单实例、Oracle Restart 或 RAC；CDB/PDB）的安全漏洞。先用只读的状态审计按严重程度列出漏洞，并说明每项发现对应哪张卡片；然后逐个领域或按顺序全部修复：listener 和 TCP.VALIDNODE_CHECKING、防火墙、TCPS (wallet) 或 native network encryption、12C ","level":"高级","duration":"5 分钟（审计）– 4 小时（完整加固）","topology":"单实例 / Oracle Restart / RAC（2–8 节点），CDB + PDB；应用服务器（客户端验证）","tags":["oracle","安全","security","hardening","加固","cis benchmark","stig","tcps","wallet","orapki","sqlnet.ora","native network encryption","TCP.VALIDNODE_CHECKING","ALLOWED_LOGON_VERSION_SERVER","profile","ORA12C_STRONG_VERIFY_FUNCTION","unified auditing","AUDSYS","DBMS_AUDIT_MGMT","TDE","keystore","ADMINISTER KEY MANAGEMENT","UTL_FILE","PUBLIC","默认口令"],"order":40,"steps":2,"scenarios":[{"k":"scenario","v":"scan","l":"状态审计：我有哪些安全漏洞？（只读）","m":"从这里开始 安全审计 security audit hardening check CIS STIG DBSAT 安全漏洞 漏洞 vulnerability 渗透测试发现 dba_users_with_defpwd 审计查询 安全扫描 security scan 严重 首先运行的扫描：默认口令账户和未锁定账户、旧口令验证器、profile、DBA/ANY 权限、对 PUBLIC 开放的 UTL_* PL/SQL package、审计、TDE、未加密会话、listener 和文件权限；不做任何修改。"},{"k":"scenario","v":"all","l":"完整加固（按顺序全部执行）","m":"从这里开始 hardening 加固 全面安全 full hardening 应用 CIS benchmark STIG 新安装或从未加固过的数据库：按安全顺序应用所有领域；listener 和数据库会重启一两次（需要维护窗口）。"},{"k":"scenario","v":"net","l":"网络与连接：谁能从哪里连接？（listener、TCP.VALIDNODE_CHECKING、防火墙）","m":"网络与身份 TCP.VALIDNODE_CHECKING TCP.INVITED_NODES listener 1521 5500 ORA-12537 TNS-12537 ORA-12541 ADMIN_RESTRICTIONS 防火墙 TNS-12546 ORA-12170 invited nodes listener.log 外部访问 访问限制 listener 对所有地址开放，未定义 invited nodes，1521/5500 对所有人开放。listener 会重启（已有会话不受影响）。"},{"k":"scenario","v":"tls","l":"加密连接（TCPS wallet 或 native network encryption）","m":"网络与身份 TCPS SSL TLS wallet orapki SQLNET.ENCRYPTION_SERVER ORA-28860 ORA-29024 ORA-28759 ORA-29106 ORA-12696 ORA-12660 SSL_SERVER_DN_MATCH 证书有效期 ORA-29003 ORA-29002 TNS-12560 TLS 证书链 certificate chain native network encryption 网络流量未加密；使用 TLS（TCPS、wallet、证书、客户端 SSL_SERVER_DN_MATCH）或无需证书的 native encryption (SQLNET.ENCRYPTION_SERVER=REQUIRED)。两种方式都无需额外许可；listener 会重启，已有会话不受影响。"},{"k":"scenario","v":"auth","l":"身份验证与口令（12C 验证器、profile、默认账户）","m":"网络与身份 ALLOWED_LOGON_VERSION_SERVER ORA-28040 ORA-01017 ORA-28000 ORA-28001 ORA-28003 ORA-28007 password_versions 10G PROFILE ORA12C_STRONG_VERIFY_FUNCTION 默认口令 default password SCOTT ORA-65066 ORA-00988 password_versions 10G 口令 profile password policy FAILED_LOGIN_ATTEMPTS 10G/11G 口令验证器、没有口令 profile、默认口令账户或示例账户未锁定；个人 DBA 账户。无需停机。"},{"k":"scenario","v":"priv","l":"最小权限：角色、PUBLIC 和应用账户","m":"权限与审计跟踪 PUBLIC UTL_FILE UTL_TCP UTL_HTTP UTL_SMTP DBA role RESOURCE ANY privilege least privilege 最小权限 ORA-01031 ORA-00942 schema-only account PLS-00201 ORA-04021 GRANT ANY UNLIMITED TABLESPACE 权限报告 应用账户拥有 DBA/RESOURCE/ANY 权限，UTL_FILE/UTL_TCP/UTL_HTTP 对 PUBLIC 开放；schema owner、读/写角色和权限报告。无需停机。"},{"k":"scenario","v":"audit","l":"审计：谁做了什么？（Unified Auditing、AUDSYS 清理）","m":"权限与审计跟踪 unified auditing AUDIT POLICY AUDSYS DBMS_AUDIT_MGMT UNIFIED_AUDIT_TRAIL ORA_LOGON_FAILURES ORA_SECURECONFIG UNIFIED_AUDIT_SYSTEMLOG SYSAUX 已满 审计跟踪 SIEM ORA-46267 unified audit policy 谁做了什么 who changed 审计人员要求提供登录、账户和 schema 变更记录；unified audit 策略、按保留期清理 AUDSYS，以及把记录发送到 syslog/集中平台。Unified Auditing 无需额外许可。"},{"k":"scenario","v":"tde","l":"加密磁盘上的数据 (TDE)","m":"数据与备份 TDE transparent data encryption keystore wallet ADMINISTER KEY MANAGEMENT WALLET_ROOT ORA-28365 ORA-28374 ORA-46665 ENCRYPT_NEW_TABLESPACES encryption at rest ORA-28367 ORA-46658 ORA-28353 Advanced Security master key TDE_CONFIGURATION 使用 TDE 防止数据文件、备份或磁盘副本被读取：WALLET_ROOT、keystore、master key、表空间加密（online）。需要 Advanced Security 许可；设置 WALLET_ROOT 需重启一次。"},{"k":"scenario","v":"backup","l":"备份安全（RMAN 加密、免口令自动化、权限）","m":"数据与备份 RMAN encryption CONFIGURE ENCRYPTION ORA-19913 expdp ENCRYPTION secure external password store mkstore SEPS 备份权限 ORA-19914 Advanced Security 许可 未加密备份 unencrypted backup ENCRYPTION_PWD_PROMPT RMAN/Data Pump 备份未加密，脚本中有明文口令，备份目录对所有人开放；RMAN encryption、ENCRYPTION_PWD_PROMPT、wallet (SEPS) 和目录权限。RMAN 和 Data Pump 加密需要 Advanced Security 许可。"},{"k":"scenario","v":"os","l":"操作系统层面（oracle 账户、文件权限、core dump、SELinux）","m":"服务器与版本 SELinux orapw oracle 账户 文件权限 core dump ORACLE_HOME 权限 sudo avc: denied passwd -l oracle setuid 6751 limits.d oracle 账户可用口令登录，口令文件/network admin/wallet 权限过宽，SELinux 已关闭。core dump 限制在新会话/重启后生效。"},{"k":"scenario","v":"patch","l":"补丁与版本：我是否在最新 RU 上？","m":"服务器与版本 RU release update CPU Critical Patch Update opatch lspatches datapatch dba_registry_sqlpatch CVE 支持结束 opatch Doc ID 742060.1 补丁时间表 已安装的 RU、datapatch 状态和支持时间表；应用补丁时链接到 RU 补丁指南。只读。"}]},{"id":"ora-oem","file":"install/guides/ora-oem.json","group":"Oracle","engineId":"oracle","category":"monitoring","title":"安装 Oracle Enterprise Manager Cloud Control 13c Release 5 (13.5)","summary":"安装 Oracle Enterprise Manager Cloud Control 13.5：将现有 19c 数据库准备为 Management Repository（文档要求的参数、重置优化器设置），在 OMS 服务器上静默安装（OL9 上为仅软件 + RU + ConfigureGC），应用 Release Update，向目标服务器部署 agent，并将 Oracle 数据库（单实例、Oracle Restart 或 RAC）纳","level":"高级","duration":"3–5 小时","topology":"OMS 服务器 + 资料档案库数据库服务器（+ 要监控的目标服务器）","tags":["oracle","enterprise manager","oem","em13c","13.5","cloud control","oms","agent","emctl","emcli","monitoring","监控","rac","集群","discover_db","data guard","exportconfig"],"order":40,"steps":32,"scenarios":[]}]}}