[{"data":1,"prerenderedAt":-1},["ShallowReactive",2],{"article:Q18bNJ8x:guest":3,"article:Q18bNJ8x:related":32},{"id":4,"created_at":5,"updated_at":6,"code":7,"author_id":8,"title":9,"slug":10,"content":11,"content_html":12,"summary":13,"category_slug":14,"visibility":15,"is_pinned":16,"is_recommended":16,"is_approved":17,"view_count":18,"like_count":19,"favorite_count":19,"comment_count":19,"hot_score":20,"recommended_score":19,"status":21,"publish_at":6,"author":22,"is_liked":16,"is_favorite":16},1198,"2026-10-20T20:39:07.914+08:00","2026-10-20T20:40:30.191+08:00","Q18bNJ8x",4,"ClickHouse 实战：从列式存储到海量数据分析的完整指南","clickhouse-实战：从列式存储到海量数据分析的完整指南","# ClickHouse 实战：从列式存储到海量数据分析的完整指南\n\n当业务数据量达到亿级、查询要求秒级返回时，传统 OLTP 数据库开始力不从心。ClickHouse 以列式存储 + 向量化执行引擎，专为 OLAP 分析场景而生，单机即可支撑每秒数亿行的扫描吞吐。本文从核心原理讲起，完整走一遍落地路径。\n\n## 一、为什么 ClickHouse 这么快\n\n### 1.1 列式存储\n\n传统行式存储按行读写，分析查询（只取几列）也要读整行；ClickHouse 按列存储，查询只读取涉及的列，IO 量降低一个数量级：\n\n```\n行式: [id, name, time, value] [id, name, time, value] ...\n列式: [id,id,id] [name,name,name] [time,time,time] [value,value,value]\n```\n\n### 1.2 向量化执行\n\nCPU 一次指令处理一整个数据块（SIMD），而非逐行循环，配合极致的压缩率（列同质性高），磁盘 IO 与 CPU 双双受益。\n\n### 1.3 数据压缩\n\n列式存储让同列数据高度相似，压缩比可达 10:1 甚至更高，进一步减少 IO。\n\n## 二、核心表引擎：MergeTree 家族\n\nMergeTree 是 ClickHouse 的基石，理解它的机制就理解了 CH 的用法：\n\n```sql\nCREATE TABLE events (\n    event_date Date,\n    event_type String,\n    user_id    UInt64,\n    amount     Float64\n) ENGINE = MergeTree\nPARTITION BY toYYYYMM(event_date)   -- 按月分区\nORDER BY (event_date, user_id)      -- 排序键，决定存储顺序\n```\n\n**核心机制**：\n- **分区**：按分区存储，查询可裁剪跳过无关分区；\n- **排序键**：数据按 ORDER BY 列有序存储，等于天然索引；\n- **后台合并**：小片段后台异步合并成大片段，写入即可查。\n\n## 三、实战查询：从基础到进阶\n\n### 3.1 基础聚合\n\n```sql\n-- 按日统计营收\nSELECT event_date, sum(amount) AS revenue\nFROM events\nWHERE event_date >= '2026-10-01'\nGROUP BY event_date\nORDER BY event_date;\n```\n\n### 3.2 常用函数\n\n```sql\n-- 去重计数（近似，速度快）\nSELECT uniqExact(user_id) FROM events WHERE event_date = '2026-10-20';\n\n-- 分位数（P95 延迟）\nSELECT quantile(0.95)(latency_ms) FROM requests WHERE date = today();\n\n-- 漏斗\u002F路径分析\nSELECT\n    level,\n    count() AS users\nFROM (\n    SELECT user_id,\n           multiIf(\n               has(groupArray(event_type), 'view') AND has(groupArray(event_type), 'click'), 2,\n               has(groupArray(event_type), 'view'), 1, 0) AS level\n    FROM events GROUP BY user_id\n)\nGROUP BY level;\n```\n\n### 3.3 物化视图：预聚合加速\n\n```sql\nCREATE MATERIALIZED VIEW daily_stats\nENGINE = SummingMergeTree\nORDER BY (event_date, event_type)\nAS\nSELECT\n    toDate(time) AS event_date,\n    event_type,\n    count() AS cnt,\n    sum(amount) AS total\nFROM events\nGROUP BY event_date, event_type;\n```\n\n**价值**：写入时实时增量聚合，查询直接读预聚合结果，亿级明细上的秒级统计不再依赖全量扫描。\n\n## 四、写入与更新：理解 CH 的数据模型\n\n### 4.1 批量写入\n\n```sql\n-- 批量 INSERT 是推荐方式\nINSERT INTO events VALUES\n  ('2026-10-20', 'view', 1, 10.0),\n  ('2026-10-20', 'click', 1, 0.0);\n```\n\n```go\n\u002F\u002F Go 侧批量写入\nbatch := ch.Conn.Batch(\"INSERT INTO events VALUES (?, ?, ?, ?)\")\nfor _, e := range events {\n    batch.Append(e.Date, e.Type, e.UserID, e.Amount)\n}\nbatch.Send()\n```\n\n### 4.2 更新与删除（Mutation）\n\nCH 的 UPDATE\u002FDELETE 是\"异步重写\"操作，开销大：\n\n```sql\nALTER TABLE events DELETE WHERE event_date \u003C '2026-01-01';\nALTER TABLE events UPDATE amount = 0 WHERE event_type = 'refund';\n```\n\n**实践**：高频更新场景不适合 CH；数据修正用\"插入新版本 + 查询取最新\"的幂等思路更合适。\n\n## 五、分布式表：从单机到集群\n\n### 5.1 集群与分布式表\n\n```xml\n\u003C!-- config.xml 配置集群 -->\n\u003Cremote_servers>\n    \u003Cmy_cluster>\n        \u003Cshard>\u003Creplica>\u003Chost>ch-1\u003C\u002Fhost>\u003Cport>9000\u003C\u002Fport>\u003C\u002Freplica>\u003C\u002Fshard>\n        \u003Cshard>\u003Creplica>\u003Chost>ch-2\u003C\u002Fhost>\u003Cport>9000\u003C\u002Fport>\u003C\u002Freplica>\u003C\u002Fshard>\n    \u003C\u002Fmy_cluster>\n\u003C\u002Fremote_servers>\n```\n\n```sql\n-- 本地表 + 分布式表\nCREATE TABLE events_local ... ENGINE = MergeTree ...;\nCREATE TABLE events AS events_local\nENGINE = Distributed(my_cluster, default, events_local, rand());\n```\n\n应用只写分布式表，数据自动按分片路由，查询自动聚合各分片结果。\n\n### 5.2 副本：ReplicatedMergeTree\n\n```sql\nCREATE TABLE events\nENGINE = ReplicatedMergeTree('\u002Fclickhouse\u002Ftables\u002F{shard}\u002Fevents', '{replica}')\nPARTITION BY toYYYYMM(event_date)\nORDER BY (event_date, user_id);\n```\n\nReplicated 引擎基于 ZooKeeper\u002FClickHouse Keeper 自动同步副本，任一节点故障数据不丢。\n\n## 六、常用运维与监控\n\n```bash\nclickhouse-client --query \"SELECT * FROM system.parts WHERE active=1\"   # 查看分区\nclickhouse-client --query \"SELECT * FROM system.mutations\"              # 查看后台任务\nclickhouse-client --query \"SHOW PROCESSLIST\"                            # 查看运行中查询\n```\n\n**监控指标**：查询延迟 P99、每秒查询数、磁盘 IO、后台 merge 队列长度。\n\n## 七、适用边界与选型\n\n| 场景 | 推荐 |\n|------|------|\n| 亿级日志、埋点分析、漏斗转化 | ClickHouse |\n| 在线交易、强一致性 OLTP | MySQL\u002FPostgreSQL（不是 CH） |\n| 多维报表、即席查询 | ClickHouse |\n| 秒级实时流计算 | 配合 Kafka + 物化视图 |\n\n**边界**：CH 不适合高频点查（按主键随机查一行）与小事务更新，那是 KV\u002FOLTP 的领域。\n\n## 结语\n\nClickHouse 的价值一句话概括：**把\"等不起的分析\"变成\"等得起的分析\"**。列式存储 + 向量化执行 + 预聚合物化视图，三板斧下来，亿级数据的聚合统计可以从分钟级压到毫秒级。落地建议：先从最痛的报表\u002F日志场景开始，用 MergeTree + 物化视图跑通一条真实链路，再逐步把分析查询从 MySQL 迁移过来——你会发现，分析型需求终于不用再\"砍需求\"了。\n","\u003Ch1>ClickHouse 实战：从列式存储到海量数据分析的完整指南\u003C\u002Fh1>\n\u003Cp>当业务数据量达到亿级、查询要求秒级返回时，传统 OLTP 数据库开始力不从心。ClickHouse 以列式存储 + 向量化执行引擎，专为 OLAP 分析场景而生，单机即可支撑每秒数亿行的扫描吞吐。本文从核心原理讲起，完整走一遍落地路径。\u003C\u002Fp>\n\u003Ch2>一、为什么 ClickHouse 这么快\u003C\u002Fh2>\n\u003Ch3>1.1 列式存储\u003C\u002Fh3>\n\u003Cp>传统行式存储按行读写，分析查询（只取几列）也要读整行；ClickHouse 按列存储，查询只读取涉及的列，IO 量降低一个数量级：\u003C\u002Fp>\n\u003Cpre>\u003Ccode>行式: [id, name, time, value] [id, name, time, value] ...\n列式: [id,id,id] [name,name,name] [time,time,time] [value,value,value]\n\u003C\u002Fcode>\u003C\u002Fpre>\n\u003Ch3>1.2 向量化执行\u003C\u002Fh3>\n\u003Cp>CPU 一次指令处理一整个数据块（SIMD），而非逐行循环，配合极致的压缩率（列同质性高），磁盘 IO 与 CPU 双双受益。\u003C\u002Fp>\n\u003Ch3>1.3 数据压缩\u003C\u002Fh3>\n\u003Cp>列式存储让同列数据高度相似，压缩比可达 10:1 甚至更高，进一步减少 IO。\u003C\u002Fp>\n\u003Ch2>二、核心表引擎：MergeTree 家族\u003C\u002Fh2>\n\u003Cp>MergeTree 是 ClickHouse 的基石，理解它的机制就理解了 CH 的用法：\u003C\u002Fp>\n\u003Cpre>\u003Ccode class=\"language-sql\">CREATE TABLE events (\n    event_date Date,\n    event_type String,\n    user_id    UInt64,\n    amount     Float64\n) ENGINE = MergeTree\nPARTITION BY toYYYYMM(event_date)   -- 按月分区\nORDER BY (event_date, user_id)      -- 排序键，决定存储顺序\n\u003C\u002Fcode>\u003C\u002Fpre>\n\u003Cp>\u003Cstrong>核心机制\u003C\u002Fstrong>：\u003C\u002Fp>\n\u003Cul>\n\u003Cli>\u003Cstrong>分区\u003C\u002Fstrong>：按分区存储，查询可裁剪跳过无关分区；\u003C\u002Fli>\n\u003Cli>\u003Cstrong>排序键\u003C\u002Fstrong>：数据按 ORDER BY 列有序存储，等于天然索引；\u003C\u002Fli>\n\u003Cli>\u003Cstrong>后台合并\u003C\u002Fstrong>：小片段后台异步合并成大片段，写入即可查。\u003C\u002Fli>\n\u003C\u002Ful>\n\u003Ch2>三、实战查询：从基础到进阶\u003C\u002Fh2>\n\u003Ch3>3.1 基础聚合\u003C\u002Fh3>\n\u003Cpre>\u003Ccode class=\"language-sql\">-- 按日统计营收\nSELECT event_date, sum(amount) AS revenue\nFROM events\nWHERE event_date &gt;= '2026-10-01'\nGROUP BY event_date\nORDER BY event_date;\n\u003C\u002Fcode>\u003C\u002Fpre>\n\u003Ch3>3.2 常用函数\u003C\u002Fh3>\n\u003Cpre>\u003Ccode class=\"language-sql\">-- 去重计数（近似，速度快）\nSELECT uniqExact(user_id) FROM events WHERE event_date = '2026-10-20';\n\n-- 分位数（P95 延迟）\nSELECT quantile(0.95)(latency_ms) FROM requests WHERE date = today();\n\n-- 漏斗\u002F路径分析\nSELECT\n    level,\n    count() AS users\nFROM (\n    SELECT user_id,\n           multiIf(\n               has(groupArray(event_type), 'view') AND has(groupArray(event_type), 'click'), 2,\n               has(groupArray(event_type), 'view'), 1, 0) AS level\n    FROM events GROUP BY user_id\n)\nGROUP BY level;\n\u003C\u002Fcode>\u003C\u002Fpre>\n\u003Ch3>3.3 物化视图：预聚合加速\u003C\u002Fh3>\n\u003Cpre>\u003Ccode class=\"language-sql\">CREATE MATERIALIZED VIEW daily_stats\nENGINE = SummingMergeTree\nORDER BY (event_date, event_type)\nAS\nSELECT\n    toDate(time) AS event_date,\n    event_type,\n    count() AS cnt,\n    sum(amount) AS total\nFROM events\nGROUP BY event_date, event_type;\n\u003C\u002Fcode>\u003C\u002Fpre>\n\u003Cp>\u003Cstrong>价值\u003C\u002Fstrong>：写入时实时增量聚合，查询直接读预聚合结果，亿级明细上的秒级统计不再依赖全量扫描。\u003C\u002Fp>\n\u003Ch2>四、写入与更新：理解 CH 的数据模型\u003C\u002Fh2>\n\u003Ch3>4.1 批量写入\u003C\u002Fh3>\n\u003Cpre>\u003Ccode class=\"language-sql\">-- 批量 INSERT 是推荐方式\nINSERT INTO events VALUES\n  ('2026-10-20', 'view', 1, 10.0),\n  ('2026-10-20', 'click', 1, 0.0);\n\u003C\u002Fcode>\u003C\u002Fpre>\n\u003Cpre>\u003Ccode class=\"language-go\">\u002F\u002F Go 侧批量写入\nbatch := ch.Conn.Batch(&quot;INSERT INTO events VALUES (?, ?, ?, ?)&quot;)\nfor _, e := range events {\n    batch.Append(e.Date, e.Type, e.UserID, e.Amount)\n}\nbatch.Send()\n\u003C\u002Fcode>\u003C\u002Fpre>\n\u003Ch3>4.2 更新与删除（Mutation）\u003C\u002Fh3>\n\u003Cp>CH 的 UPDATE\u002FDELETE 是&quot;异步重写&quot;操作，开销大：\u003C\u002Fp>\n\u003Cpre>\u003Ccode class=\"language-sql\">ALTER TABLE events DELETE WHERE event_date &lt; '2026-01-01';\nALTER TABLE events UPDATE amount = 0 WHERE event_type = 'refund';\n\u003C\u002Fcode>\u003C\u002Fpre>\n\u003Cp>\u003Cstrong>实践\u003C\u002Fstrong>：高频更新场景不适合 CH；数据修正用&quot;插入新版本 + 查询取最新&quot;的幂等思路更合适。\u003C\u002Fp>\n\u003Ch2>五、分布式表：从单机到集群\u003C\u002Fh2>\n\u003Ch3>5.1 集群与分布式表\u003C\u002Fh3>\n\u003Cpre>\u003Ccode class=\"language-xml\">&lt;!-- config.xml 配置集群 --&gt;\n&lt;remote_servers&gt;\n    &lt;my_cluster&gt;\n        &lt;shard&gt;&lt;replica&gt;&lt;host&gt;ch-1&lt;\u002Fhost&gt;&lt;port&gt;9000&lt;\u002Fport&gt;&lt;\u002Freplica&gt;&lt;\u002Fshard&gt;\n        &lt;shard&gt;&lt;replica&gt;&lt;host&gt;ch-2&lt;\u002Fhost&gt;&lt;port&gt;9000&lt;\u002Fport&gt;&lt;\u002Freplica&gt;&lt;\u002Fshard&gt;\n    &lt;\u002Fmy_cluster&gt;\n&lt;\u002Fremote_servers&gt;\n\u003C\u002Fcode>\u003C\u002Fpre>\n\u003Cpre>\u003Ccode class=\"language-sql\">-- 本地表 + 分布式表\nCREATE TABLE events_local ... ENGINE = MergeTree ...;\nCREATE TABLE events AS events_local\nENGINE = Distributed(my_cluster, default, events_local, rand());\n\u003C\u002Fcode>\u003C\u002Fpre>\n\u003Cp>应用只写分布式表，数据自动按分片路由，查询自动聚合各分片结果。\u003C\u002Fp>\n\u003Ch3>5.2 副本：ReplicatedMergeTree\u003C\u002Fh3>\n\u003Cpre>\u003Ccode class=\"language-sql\">CREATE TABLE events\nENGINE = ReplicatedMergeTree('\u002Fclickhouse\u002Ftables\u002F{shard}\u002Fevents', '{replica}')\nPARTITION BY toYYYYMM(event_date)\nORDER BY (event_date, user_id);\n\u003C\u002Fcode>\u003C\u002Fpre>\n\u003Cp>Replicated 引擎基于 ZooKeeper\u002FClickHouse Keeper 自动同步副本，任一节点故障数据不丢。\u003C\u002Fp>\n\u003Ch2>六、常用运维与监控\u003C\u002Fh2>\n\u003Cpre>\u003Ccode class=\"language-bash\">clickhouse-client --query &quot;SELECT * FROM system.parts WHERE active=1&quot;   # 查看分区\nclickhouse-client --query &quot;SELECT * FROM system.mutations&quot;              # 查看后台任务\nclickhouse-client --query &quot;SHOW PROCESSLIST&quot;                            # 查看运行中查询\n\u003C\u002Fcode>\u003C\u002Fpre>\n\u003Cp>\u003Cstrong>监控指标\u003C\u002Fstrong>：查询延迟 P99、每秒查询数、磁盘 IO、后台 merge 队列长度。\u003C\u002Fp>\n\u003Ch2>七、适用边界与选型\u003C\u002Fh2>\n\u003Cp>| 场景 | 推荐 |\n|------|------|\n| 亿级日志、埋点分析、漏斗转化 | ClickHouse |\n| 在线交易、强一致性 OLTP | MySQL\u002FPostgreSQL（不是 CH） |\n| 多维报表、即席查询 | ClickHouse |\n| 秒级实时流计算 | 配合 Kafka + 物化视图 |\u003C\u002Fp>\n\u003Cp>\u003Cstrong>边界\u003C\u002Fstrong>：CH 不适合高频点查（按主键随机查一行）与小事务更新，那是 KV\u002FOLTP 的领域。\u003C\u002Fp>\n\u003Ch2>结语\u003C\u002Fh2>\n\u003Cp>ClickHouse 的价值一句话概括：\u003Cstrong>把&quot;等不起的分析&quot;变成&quot;等得起的分析&quot;\u003C\u002Fstrong>。列式存储 + 向量化执行 + 预聚合物化视图，三板斧下来，亿级数据的聚合统计可以从分钟级压到毫秒级。落地建议：先从最痛的报表\u002F日志场景开始，用 MergeTree + 物化视图跑通一条真实链路，再逐步把分析查询从 MySQL 迁移过来——你会发现，分析型需求终于不用再&quot;砍需求&quot;了。\u003C\u002Fp>\n","从列式存储原理、MergeTree 表引擎到 SQL 分析实战与集群高可用，掌握海量数据实时分析的 ClickHouse 方案。","数据库","public",false,true,2,0,39834.28824250944,"published",{"id":8,"created_at":23,"updated_at":24,"code":25,"username":26,"display_name":27,"role":28,"avatar_url":29,"is_active":17,"is_banned":16,"follower_count":30,"following_count":19,"last_login_at":31},"2023-12-07T23:03:24.891+08:00","2026-10-20T20:39:07.491+08:00","zJXMbKwb","1701961404829","今晚打老虎","user","\u002Fcuts\u002F1702121810380island.jpg",1,"2026-10-20T20:39:07+08:00",[33,54,73],{"id":34,"created_at":35,"updated_at":36,"code":37,"author_id":38,"title":39,"slug":40,"content":41,"content_html":42,"summary":43,"category_slug":14,"visibility":15,"is_pinned":16,"is_recommended":16,"is_approved":17,"view_count":44,"like_count":30,"favorite_count":30,"comment_count":19,"hot_score":45,"recommended_score":19,"status":21,"publish_at":36,"author":46,"is_liked":16,"is_favorite":16},1191,"2026-10-20T20:39:07.253+08:00","2026-10-20T20:40:34.248+08:00","r2Xr32KO",11,"MySQL 备份与容灾实战：从逻辑备份到主从复制的完整体系","mysql-备份与容灾实战：从逻辑备份到主从复制的完整体系","# MySQL 备份与容灾实战：从逻辑备份到主从复制的完整体系\n\n数据库备份是运维的最后一道防线。99% 的数据事故都不可怕，可怕的是\"没有备份\"或\"备份不可用\"。本文从备份类型讲起，到自动化备份、恢复演练与主从容灾，给出生产可用的完整方案。\n\n## 一、备份类型的选择\n\n| 类型 | 工具 | 特点 | 适用 |\n|------|------|------|------|\n| 逻辑备份 | mysqldump | 跨版本\u002F跨平台恢复，慢 | 小库（\u003C10GB）、迁移 |\n| 物理备份 | Percona XtraBackup | 快、在线热备 | 大库、生产环境 |\n| 二进制日志 | binlog | 增量、按时间点恢复 | 所有生产库 |\n\n**黄金组合**：定期全量（XtraBackup）+ 持续增量（binlog）+ 定期演练恢复。\n\n## 二、逻辑备份：mysqldump 的正确姿势\n\n```bash\n# 单库备份（带表结构）\nmysqldump -u backup -p \\\n  --single-transaction --set-gtid-purged=OFF \\\n  --databases app_db > app_db_$(date +%F).sql\n\n# 只备份数据（不含建库语句）\nmysqldump -u backup -p --no-create-info \\\n  --single-transaction app_db > app_db_data_$(date +%F).sql\n```\n\n关键参数解读：\n\n- `--single-transaction`：InnoDB 下基于事务快照，**备份期间不锁表**，是热备的核心；\n- `--set-gtid-purged=OFF`：避免恢复时 GTID 冲突；\n- 备份账号只授予 `SELECT, LOCK TABLES, SHOW VIEW` 等最小权限。\n\n## 三、物理备份：Percona XtraBackup\n\n```bash\n# 全量备份\nxtrabackup --backup --target-dir=\u002Fbackup\u002Ffull \\\n  --host=127.0.0.1 --user=backup --password=xxx\n\n# 准备阶段（应用日志，使备份一致）\nxtrabackup --prepare --target-dir=\u002Fbackup\u002Ffull\n\n# 恢复\nxtrabackup --copy-back --target-dir=\u002Fbackup\u002Ffull\n```\n\n**为什么大库用物理备份**：mysqldump 对 100GB 库可能要跑几小时，XtraBackup 通过物理复制数据文件 + redo log 增量，速度提升一个数量级。\n\n## 四、增量与时间点恢复：binlog 的妙用\n\n### 4.1 开启 binlog\n\n```ini\n# my.cnf\n[mysqld]\nserver-id = 1\nlog-bin = \u002Fvar\u002Flib\u002Fmysql\u002Fmysql-bin\nbinlog_format = ROW\nexpire_logs_days = 7\n```\n\n### 4.2 按时间点恢复\n\n```bash\n# 1. 恢复最近一次全量备份\nmysql -u root -p \u003C full_backup.sql\n\n# 2. 应用备份点之后的 binlog（到误操作前的时刻）\nmysqlbinlog --stop-datetime=\"2026-10-20 03:00:00\" \\\n  mysql-bin.000012 mysql-bin.000013 | mysql -u root -p app_db\n```\n\n**演练价值**：这一整套命令只有在定期演练时才能确保手到擒来。\n\n## 五、自动化备份脚本\n\n```bash\n#!\u002Fusr\u002Fbin\u002Fenv bash\nset -euo pipefail\n\nBACKUP_DIR=\"\u002Fbackup\u002Fdb\"\nRETENTION_DAYS=14\nDB_NAME=\"app_db\"\nDATE=$(date +%F)\n\n# 全量备份\nmysqldump --single-transaction --databases \"$DB_NAME\" \\\n  > \"$BACKUP_DIR\u002F${DB_NAME}_${DATE}.sql\"\n\n# 压缩（gzip 或 zstd）\ngzip -9 \"$BACKUP_DIR\u002F${DB_NAME}_${DATE}.sql\"\n\n# 清理过期备份\nfind \"$BACKUP_DIR\" -name \"*.sql.gz\" -mtime +\"$RETENTION_DAYS\" -delete\n\n# 上传异地（对象存储\u002FSFTP），防本地磁盘一起损坏\nrclone copy \"$BACKUP_DIR\u002F${DB_NAME}_${DATE}.sql.gz\" remote:backup\u002Fdb\u002F\n```\n\n```bash\n# crontab 每日凌晨 2 点执行\n0 2 * * * \u002Fopt\u002Fscripts\u002Fbackup_db.sh >> \u002Fvar\u002Flog\u002Fbackup_db.log 2>&1\n```\n\n**异地备份是底线**：本地磁盘故障、机房火灾都会同时摧毁主库与备份，必须异地\u002F异机一份。\n\n## 六、主从复制：读写分离与容灾\n\n### 6.1 搭建一主一从\n\n```sql\n-- 主库：创建复制账号并记录位点\nCREATE USER 'repl'@'%' IDENTIFIED BY 'repl_pass';\nGRANT REPLICATION SLAVE ON *.* TO 'repl'@'%';\nFLUSH TABLES WITH READ LOCK;\nSHOW MASTER STATUS;   -- 记录 File 与 Position\n```\n\n```sql\n-- 从库：先导入主库备份，再启动复制\nCHANGE MASTER TO\n  MASTER_HOST='10.0.1.11',\n  MASTER_USER='repl',\n  MASTER_PASSWORD='repl_pass',\n  MASTER_LOG_FILE='mysql-bin.000012',\n  MASTER_LOG_POS=123456;\nSTART SLAVE;\nSHOW SLAVE STATUS\\G   -- Slave_IO_Running \u002F Slave_SQL_Running 均为 Yes\n```\n\n### 6.2 主从延迟的监控\n\n```sql\n-- 从库执行，Seconds_Behind_Master 是延迟秒数\nSHOW SLAVE STATUS\\G\n```\n\n延迟持续增长时排查：从库 IO 压力、大事务、DDL 锁、单线程应用（可用 MTS 并行复制优化）。\n\n### 6.3 故障切换（MHA \u002F Orchestrator）\n\n生产环境用 Orchestrator 等工具管理拓扑：主库宕机自动提升从库、迁移 VIP、通知告警。**注意**：自动切换只解决\"可用性\"，数据丢失（半同步复制可缓解）取决于复制延迟。\n\n## 七、恢复演练：备份的灵魂\n\n```\n演练场景：误删 app_db 核心表，恢复到 30 分钟前。\n演练步骤：\n1. 恢复最近全量备份到临时实例\n2. 应用 binlog 到误删前时刻\n3. 导出误删表数据，导回生产\n4. 对比行数\u002F关键业务校验\n```\n\n**结论**：没有演练过的备份等于没有备份。建议每季度全流程演练一次，并把演练时长计入 SLO。\n\n## 结语\n\n备份体系的完备度不取决于工具多先进，而取决于**恢复路径是否被验证过**。全量 + binlog 增量 + 异地 + 定期演练，这套组合能覆盖绝大多数数据事故。把备份脚本、恢复手册、演练记录沉淀为团队的运维资产，你才能真正在深夜收到\"误删数据\"的电话时，保持冷静、按章操作、快速恢复。\n","\u003Ch1>MySQL 备份与容灾实战：从逻辑备份到主从复制的完整体系\u003C\u002Fh1>\n\u003Cp>数据库备份是运维的最后一道防线。99% 的数据事故都不可怕，可怕的是&quot;没有备份&quot;或&quot;备份不可用&quot;。本文从备份类型讲起，到自动化备份、恢复演练与主从容灾，给出生产可用的完整方案。\u003C\u002Fp>\n\u003Ch2>一、备份类型的选择\u003C\u002Fh2>\n\u003Cp>| 类型 | 工具 | 特点 | 适用 |\n|------|------|------|------|\n| 逻辑备份 | mysqldump | 跨版本\u002F跨平台恢复，慢 | 小库（&lt;10GB）、迁移 |\n| 物理备份 | Percona XtraBackup | 快、在线热备 | 大库、生产环境 |\n| 二进制日志 | binlog | 增量、按时间点恢复 | 所有生产库 |\u003C\u002Fp>\n\u003Cp>\u003Cstrong>黄金组合\u003C\u002Fstrong>：定期全量（XtraBackup）+ 持续增量（binlog）+ 定期演练恢复。\u003C\u002Fp>\n\u003Ch2>二、逻辑备份：mysqldump 的正确姿势\u003C\u002Fh2>\n\u003Cpre>\u003Ccode class=\"language-bash\"># 单库备份（带表结构）\nmysqldump -u backup -p \\\n  --single-transaction --set-gtid-purged=OFF \\\n  --databases app_db &gt; app_db_$(date +%F).sql\n\n# 只备份数据（不含建库语句）\nmysqldump -u backup -p --no-create-info \\\n  --single-transaction app_db &gt; app_db_data_$(date +%F).sql\n\u003C\u002Fcode>\u003C\u002Fpre>\n\u003Cp>关键参数解读：\u003C\u002Fp>\n\u003Cul>\n\u003Cli>\u003Ccode>--single-transaction\u003C\u002Fcode>：InnoDB 下基于事务快照，\u003Cstrong>备份期间不锁表\u003C\u002Fstrong>，是热备的核心；\u003C\u002Fli>\n\u003Cli>\u003Ccode>--set-gtid-purged=OFF\u003C\u002Fcode>：避免恢复时 GTID 冲突；\u003C\u002Fli>\n\u003Cli>备份账号只授予 \u003Ccode>SELECT, LOCK TABLES, SHOW VIEW\u003C\u002Fcode> 等最小权限。\u003C\u002Fli>\n\u003C\u002Ful>\n\u003Ch2>三、物理备份：Percona XtraBackup\u003C\u002Fh2>\n\u003Cpre>\u003Ccode class=\"language-bash\"># 全量备份\nxtrabackup --backup --target-dir=\u002Fbackup\u002Ffull \\\n  --host=127.0.0.1 --user=backup --password=xxx\n\n# 准备阶段（应用日志，使备份一致）\nxtrabackup --prepare --target-dir=\u002Fbackup\u002Ffull\n\n# 恢复\nxtrabackup --copy-back --target-dir=\u002Fbackup\u002Ffull\n\u003C\u002Fcode>\u003C\u002Fpre>\n\u003Cp>\u003Cstrong>为什么大库用物理备份\u003C\u002Fstrong>：mysqldump 对 100GB 库可能要跑几小时，XtraBackup 通过物理复制数据文件 + redo log 增量，速度提升一个数量级。\u003C\u002Fp>\n\u003Ch2>四、增量与时间点恢复：binlog 的妙用\u003C\u002Fh2>\n\u003Ch3>4.1 开启 binlog\u003C\u002Fh3>\n\u003Cpre>\u003Ccode class=\"language-ini\"># my.cnf\n[mysqld]\nserver-id = 1\nlog-bin = \u002Fvar\u002Flib\u002Fmysql\u002Fmysql-bin\nbinlog_format = ROW\nexpire_logs_days = 7\n\u003C\u002Fcode>\u003C\u002Fpre>\n\u003Ch3>4.2 按时间点恢复\u003C\u002Fh3>\n\u003Cpre>\u003Ccode class=\"language-bash\"># 1. 恢复最近一次全量备份\nmysql -u root -p &lt; full_backup.sql\n\n# 2. 应用备份点之后的 binlog（到误操作前的时刻）\nmysqlbinlog --stop-datetime=&quot;2026-10-20 03:00:00&quot; \\\n  mysql-bin.000012 mysql-bin.000013 | mysql -u root -p app_db\n\u003C\u002Fcode>\u003C\u002Fpre>\n\u003Cp>\u003Cstrong>演练价值\u003C\u002Fstrong>：这一整套命令只有在定期演练时才能确保手到擒来。\u003C\u002Fp>\n\u003Ch2>五、自动化备份脚本\u003C\u002Fh2>\n\u003Cpre>\u003Ccode class=\"language-bash\">#!\u002Fusr\u002Fbin\u002Fenv bash\nset -euo pipefail\n\nBACKUP_DIR=&quot;\u002Fbackup\u002Fdb&quot;\nRETENTION_DAYS=14\nDB_NAME=&quot;app_db&quot;\nDATE=$(date +%F)\n\n# 全量备份\nmysqldump --single-transaction --databases &quot;$DB_NAME&quot; \\\n  &gt; &quot;$BACKUP_DIR\u002F${DB_NAME}_${DATE}.sql&quot;\n\n# 压缩（gzip 或 zstd）\ngzip -9 &quot;$BACKUP_DIR\u002F${DB_NAME}_${DATE}.sql&quot;\n\n# 清理过期备份\nfind &quot;$BACKUP_DIR&quot; -name &quot;*.sql.gz&quot; -mtime +&quot;$RETENTION_DAYS&quot; -delete\n\n# 上传异地（对象存储\u002FSFTP），防本地磁盘一起损坏\nrclone copy &quot;$BACKUP_DIR\u002F${DB_NAME}_${DATE}.sql.gz&quot; remote:backup\u002Fdb\u002F\n\u003C\u002Fcode>\u003C\u002Fpre>\n\u003Cpre>\u003Ccode class=\"language-bash\"># crontab 每日凌晨 2 点执行\n0 2 * * * \u002Fopt\u002Fscripts\u002Fbackup_db.sh &gt;&gt; \u002Fvar\u002Flog\u002Fbackup_db.log 2&gt;&amp;1\n\u003C\u002Fcode>\u003C\u002Fpre>\n\u003Cp>\u003Cstrong>异地备份是底线\u003C\u002Fstrong>：本地磁盘故障、机房火灾都会同时摧毁主库与备份，必须异地\u002F异机一份。\u003C\u002Fp>\n\u003Ch2>六、主从复制：读写分离与容灾\u003C\u002Fh2>\n\u003Ch3>6.1 搭建一主一从\u003C\u002Fh3>\n\u003Cpre>\u003Ccode class=\"language-sql\">-- 主库：创建复制账号并记录位点\nCREATE USER 'repl'@'%' IDENTIFIED BY 'repl_pass';\nGRANT REPLICATION SLAVE ON *.* TO 'repl'@'%';\nFLUSH TABLES WITH READ LOCK;\nSHOW MASTER STATUS;   -- 记录 File 与 Position\n\u003C\u002Fcode>\u003C\u002Fpre>\n\u003Cpre>\u003Ccode class=\"language-sql\">-- 从库：先导入主库备份，再启动复制\nCHANGE MASTER TO\n  MASTER_HOST='10.0.1.11',\n  MASTER_USER='repl',\n  MASTER_PASSWORD='repl_pass',\n  MASTER_LOG_FILE='mysql-bin.000012',\n  MASTER_LOG_POS=123456;\nSTART SLAVE;\nSHOW SLAVE STATUS\\G   -- Slave_IO_Running \u002F Slave_SQL_Running 均为 Yes\n\u003C\u002Fcode>\u003C\u002Fpre>\n\u003Ch3>6.2 主从延迟的监控\u003C\u002Fh3>\n\u003Cpre>\u003Ccode class=\"language-sql\">-- 从库执行，Seconds_Behind_Master 是延迟秒数\nSHOW SLAVE STATUS\\G\n\u003C\u002Fcode>\u003C\u002Fpre>\n\u003Cp>延迟持续增长时排查：从库 IO 压力、大事务、DDL 锁、单线程应用（可用 MTS 并行复制优化）。\u003C\u002Fp>\n\u003Ch3>6.3 故障切换（MHA \u002F Orchestrator）\u003C\u002Fh3>\n\u003Cp>生产环境用 Orchestrator 等工具管理拓扑：主库宕机自动提升从库、迁移 VIP、通知告警。\u003Cstrong>注意\u003C\u002Fstrong>：自动切换只解决&quot;可用性&quot;，数据丢失（半同步复制可缓解）取决于复制延迟。\u003C\u002Fp>\n\u003Ch2>七、恢复演练：备份的灵魂\u003C\u002Fh2>\n\u003Cpre>\u003Ccode>演练场景：误删 app_db 核心表，恢复到 30 分钟前。\n演练步骤：\n1. 恢复最近全量备份到临时实例\n2. 应用 binlog 到误删前时刻\n3. 导出误删表数据，导回生产\n4. 对比行数\u002F关键业务校验\n\u003C\u002Fcode>\u003C\u002Fpre>\n\u003Cp>\u003Cstrong>结论\u003C\u002Fstrong>：没有演练过的备份等于没有备份。建议每季度全流程演练一次，并把演练时长计入 SLO。\u003C\u002Fp>\n\u003Ch2>结语\u003C\u002Fh2>\n\u003Cp>备份体系的完备度不取决于工具多先进，而取决于\u003Cstrong>恢复路径是否被验证过\u003C\u002Fstrong>。全量 + binlog 增量 + 异地 + 定期演练，这套组合能覆盖绝大多数数据事故。把备份脚本、恢复手册、演练记录沉淀为团队的运维资产，你才能真正在深夜收到&quot;误删数据&quot;的电话时，保持冷静、按章操作、快速恢复。\u003C\u002Fp>\n","从逻辑备份、物理备份、Binlog 增量备份到主从复制与半同步，构建 MySQL 可恢复、可验证的容灾备份体系。",3,39841.53820887155,{"id":38,"created_at":47,"updated_at":48,"code":49,"username":50,"display_name":51,"role":28,"avatar_url":52,"is_active":17,"is_banned":16,"follower_count":30,"following_count":19,"last_login_at":53},"2024-09-12T11:58:24.365+08:00","2026-10-20T20:39:06.889+08:00","kbXvOKMJ","1726113504301","打工人","\u002Fcuts\u002F1726370661979da4b1661617b527c821952001.jpg","2026-10-20T20:39:06+08:00",{"id":55,"created_at":56,"updated_at":57,"code":58,"author_id":44,"title":59,"slug":60,"content":61,"content_html":62,"summary":63,"category_slug":14,"visibility":15,"is_pinned":16,"is_recommended":16,"is_approved":17,"view_count":8,"like_count":30,"favorite_count":19,"comment_count":19,"hot_score":64,"recommended_score":19,"status":21,"publish_at":57,"author":65,"is_liked":16,"is_favorite":16},1175,"2026-10-20T18:08:48.9+08:00","2026-10-20T18:09:32.69+08:00","xD81zVXg","MySQL 索引优化实战：从 EXPLAIN 到查询提速的完整指南","mysql-索引优化实战：从-explain-到查询提速的完整指南","# MySQL 索引优化实战：从 EXPLAIN 到查询提速的完整指南\n\n索引是关系型数据库性能的基石。同样一条 SQL，在索引缺失与索引合理的情况下，查询耗时可能相差几个数量级。本文从执行计划入手，系统梳理 MySQL 索引的设计原则、失效场景与调优方法论。\n\n## 一、先看懂 EXPLAIN\n\n任何索引优化都从读懂执行计划开始。`EXPLAIN SELECT ...` 输出每一行代表一个执行步骤，核心列的含义如下：\n\n| 列名 | 含义 | 需要警惕的值 |\n|------|------|--------------|\n| type | 访问类型，从好到差依次为 | system > const > eq_ref > ref > range > index > ALL |\n| key | 实际使用的索引 | NULL 表示没走索引 |\n| rows | 预估扫描行数 | 与实际行数偏差过大说明统计信息陈旧 |\n| Extra | 附加信息 | Using filesort \u002F Using temporary 是需要优化的信号 |\n\n最需要记住的一条经验：**type 为 ALL（全表扫描）或 index（扫描整个索引）时，先停下来分析**。只要命中 range 及以上，SQL 通常还有救。\n\n## 二、索引失效的十个高频场景\n\n### 2.1 对索引列做函数运算\n\n```sql\n-- 反例：对 created_at 取函数，索引失效\nSELECT * FROM orders WHERE DATE(created_at) = '2026-10-01';\n\n-- 正解：改成范围查询，走索引\nSELECT * FROM orders\nWHERE created_at >= '2026-10-01 00:00:00'\n  AND created_at \u003C '2026-10-02 00:00:00';\n```\n\n### 2.2 隐式类型转换\n\n```sql\n-- 反例：phone 是 varchar，传入数字导致隐式转换，索引失效\nSELECT * FROM users WHERE phone = 13800138000;\n\n-- 正解：加引号，保持类型一致\nSELECT * FROM users WHERE phone = '13800138000';\n```\n\n### 2.3 前导模糊查询\n\n```sql\n-- '%xx' 开头无法利用索引，但 'xx%' 可以\nSELECT * FROM users WHERE name LIKE '%小明%';  -- 索引失效\nSELECT * FROM users WHERE name LIKE '小明%';    -- 走 range\n```\n\n### 2.4 联合索引违反最左前缀\n\n联合索引 `(a, b, c)` 遵循最左前缀原则：`WHERE b=1 AND c=2` 用不上，`WHERE a=1 AND c=2` 只能用到 a。\n\n### 2.5 索引列参与计算或比较\n\n```sql\n-- 反例\nSELECT * FROM products WHERE price * 0.9 \u003C 100;\n-- 正解：把计算移到常量侧\nSELECT * FROM products WHERE price \u003C 100 \u002F 0.9;\n```\n\n## 三、索引设计五原则\n\n1. **区分度优先**：选择性 = 去重值数 \u002F 总行数，低于 20% 的列单独建索引意义不大。\n2. **联合索引按\"等值在前、排序在后\"排列**：`WHERE a=? AND b>? ORDER BY c`，索引设计为 `(a, b, c)` 可同时满足过滤与排序。\n3. **覆盖索引是查询提速神器**：`(a, b, c)` 索引让 `SELECT a,b,c` 直接从索引树返回，避免回表。\n4. **控制索引数量**：单表索引控制在 5 个以内，每个索引都要考虑写入成本。\n5. **字符串前缀索引**：大字段用 `INDEX idx_name(name(20))`，用长度换空间。\n\n## 四、order by \u002F group by 的索引优化\n\n```sql\n-- 反例：产生 Using filesort\nSELECT id, title FROM articles WHERE category_id = 5 ORDER BY created_at DESC;\n\n-- 正解：联合索引 (category_id, created_at)，排序免 filesort\nALTER TABLE articles ADD INDEX idx_cat_time (category_id, created_at);\n```\n\n`GROUP BY` 同理，本质是先排序再分组。给 group by 列建联合索引能显著提升性能。\n\n## 五、分页深翻页的优化\n\n`LIMIT 100000, 20` 需要扫描 100020 行再丢弃前 10 万行，这是经典性能陷阱：\n\n```sql\n-- 反例：深翻页\nSELECT * FROM orders ORDER BY id LIMIT 100000, 20;\n\n-- 方案一：延迟关联（先取主键，再回表）\nSELECT o.* FROM orders o\nINNER JOIN (SELECT id FROM orders ORDER BY id LIMIT 100000, 20) t\nON o.id = t.id;\n\n-- 方案二：游标\u002F键集分页（推荐）\nSELECT * FROM orders\nWHERE id > :last_seen_id\nORDER BY id LIMIT 20;\n```\n\n## 六、count 的优化思路\n\n- `COUNT(*)` 和 `COUNT(1)` 在 MySQL 8 中性能无差异，别纠结；\n- 大表精确 count 无法避免全表扫描（InnoDB 不缓存行数），业务上可用缓存计数、抽样估算替代；\n- 条件 count 走覆盖索引：`SELECT COUNT(*) FROM t WHERE status=1`，若 `(status)` 有索引则走 Index Only Scan。\n\n## 七、调优方法论：先测量再动手\n\n1. `EXPLAIN ANALYZE`（MySQL 8.0+）给出真实执行耗时与行数，比估算更可靠；\n2. 开启慢查询日志，把 `long_query_time` 设为 1 秒，找到真正需要优化的 SQL；\n3. 每次只改一个变量（索引\u002F写法\u002F参数），用同样的数据集对比；\n4. 上线前用 `EXPLAIN` 复查，确认 type 和 rows 符合预期。\n\n## 结语\n\n索引优化的本质是**减少磁盘随机 IO**。理解 B+ 树、覆盖索引、最左前缀这三件事，90% 的慢查询都能自己分析出原因。记住：先 EXPLAIN，再动手，用数据说话，而不是靠感觉。\n","\u003Ch1>MySQL 索引优化实战：从 EXPLAIN 到查询提速的完整指南\u003C\u002Fh1>\n\u003Cp>索引是关系型数据库性能的基石。同样一条 SQL，在索引缺失与索引合理的情况下，查询耗时可能相差几个数量级。本文从执行计划入手，系统梳理 MySQL 索引的设计原则、失效场景与调优方法论。\u003C\u002Fp>\n\u003Ch2>一、先看懂 EXPLAIN\u003C\u002Fh2>\n\u003Cp>任何索引优化都从读懂执行计划开始。\u003Ccode>EXPLAIN SELECT ...\u003C\u002Fcode> 输出每一行代表一个执行步骤，核心列的含义如下：\u003C\u002Fp>\n\u003Cp>| 列名 | 含义 | 需要警惕的值 |\n|------|------|--------------|\n| type | 访问类型，从好到差依次为 | system &gt; const &gt; eq_ref &gt; ref &gt; range &gt; index &gt; ALL |\n| key | 实际使用的索引 | NULL 表示没走索引 |\n| rows | 预估扫描行数 | 与实际行数偏差过大说明统计信息陈旧 |\n| Extra | 附加信息 | Using filesort \u002F Using temporary 是需要优化的信号 |\u003C\u002Fp>\n\u003Cp>最需要记住的一条经验：\u003Cstrong>type 为 ALL（全表扫描）或 index（扫描整个索引）时，先停下来分析\u003C\u002Fstrong>。只要命中 range 及以上，SQL 通常还有救。\u003C\u002Fp>\n\u003Ch2>二、索引失效的十个高频场景\u003C\u002Fh2>\n\u003Ch3>2.1 对索引列做函数运算\u003C\u002Fh3>\n\u003Cpre>\u003Ccode class=\"language-sql\">-- 反例：对 created_at 取函数，索引失效\nSELECT * FROM orders WHERE DATE(created_at) = '2026-10-01';\n\n-- 正解：改成范围查询，走索引\nSELECT * FROM orders\nWHERE created_at &gt;= '2026-10-01 00:00:00'\n  AND created_at &lt; '2026-10-02 00:00:00';\n\u003C\u002Fcode>\u003C\u002Fpre>\n\u003Ch3>2.2 隐式类型转换\u003C\u002Fh3>\n\u003Cpre>\u003Ccode class=\"language-sql\">-- 反例：phone 是 varchar，传入数字导致隐式转换，索引失效\nSELECT * FROM users WHERE phone = 13800138000;\n\n-- 正解：加引号，保持类型一致\nSELECT * FROM users WHERE phone = '13800138000';\n\u003C\u002Fcode>\u003C\u002Fpre>\n\u003Ch3>2.3 前导模糊查询\u003C\u002Fh3>\n\u003Cpre>\u003Ccode class=\"language-sql\">-- '%xx' 开头无法利用索引，但 'xx%' 可以\nSELECT * FROM users WHERE name LIKE '%小明%';  -- 索引失效\nSELECT * FROM users WHERE name LIKE '小明%';    -- 走 range\n\u003C\u002Fcode>\u003C\u002Fpre>\n\u003Ch3>2.4 联合索引违反最左前缀\u003C\u002Fh3>\n\u003Cp>联合索引 \u003Ccode>(a, b, c)\u003C\u002Fcode> 遵循最左前缀原则：\u003Ccode>WHERE b=1 AND c=2\u003C\u002Fcode> 用不上，\u003Ccode>WHERE a=1 AND c=2\u003C\u002Fcode> 只能用到 a。\u003C\u002Fp>\n\u003Ch3>2.5 索引列参与计算或比较\u003C\u002Fh3>\n\u003Cpre>\u003Ccode class=\"language-sql\">-- 反例\nSELECT * FROM products WHERE price * 0.9 &lt; 100;\n-- 正解：把计算移到常量侧\nSELECT * FROM products WHERE price &lt; 100 \u002F 0.9;\n\u003C\u002Fcode>\u003C\u002Fpre>\n\u003Ch2>三、索引设计五原则\u003C\u002Fh2>\n\u003Col>\n\u003Cli>\u003Cstrong>区分度优先\u003C\u002Fstrong>：选择性 = 去重值数 \u002F 总行数，低于 20% 的列单独建索引意义不大。\u003C\u002Fli>\n\u003Cli>\u003Cstrong>联合索引按&quot;等值在前、排序在后&quot;排列\u003C\u002Fstrong>：\u003Ccode>WHERE a=? AND b&gt;? ORDER BY c\u003C\u002Fcode>，索引设计为 \u003Ccode>(a, b, c)\u003C\u002Fcode> 可同时满足过滤与排序。\u003C\u002Fli>\n\u003Cli>\u003Cstrong>覆盖索引是查询提速神器\u003C\u002Fstrong>：\u003Ccode>(a, b, c)\u003C\u002Fcode> 索引让 \u003Ccode>SELECT a,b,c\u003C\u002Fcode> 直接从索引树返回，避免回表。\u003C\u002Fli>\n\u003Cli>\u003Cstrong>控制索引数量\u003C\u002Fstrong>：单表索引控制在 5 个以内，每个索引都要考虑写入成本。\u003C\u002Fli>\n\u003Cli>\u003Cstrong>字符串前缀索引\u003C\u002Fstrong>：大字段用 \u003Ccode>INDEX idx_name(name(20))\u003C\u002Fcode>，用长度换空间。\u003C\u002Fli>\n\u003C\u002Fol>\n\u003Ch2>四、order by \u002F group by 的索引优化\u003C\u002Fh2>\n\u003Cpre>\u003Ccode class=\"language-sql\">-- 反例：产生 Using filesort\nSELECT id, title FROM articles WHERE category_id = 5 ORDER BY created_at DESC;\n\n-- 正解：联合索引 (category_id, created_at)，排序免 filesort\nALTER TABLE articles ADD INDEX idx_cat_time (category_id, created_at);\n\u003C\u002Fcode>\u003C\u002Fpre>\n\u003Cp>\u003Ccode>GROUP BY\u003C\u002Fcode> 同理，本质是先排序再分组。给 group by 列建联合索引能显著提升性能。\u003C\u002Fp>\n\u003Ch2>五、分页深翻页的优化\u003C\u002Fh2>\n\u003Cp>\u003Ccode>LIMIT 100000, 20\u003C\u002Fcode> 需要扫描 100020 行再丢弃前 10 万行，这是经典性能陷阱：\u003C\u002Fp>\n\u003Cpre>\u003Ccode class=\"language-sql\">-- 反例：深翻页\nSELECT * FROM orders ORDER BY id LIMIT 100000, 20;\n\n-- 方案一：延迟关联（先取主键，再回表）\nSELECT o.* FROM orders o\nINNER JOIN (SELECT id FROM orders ORDER BY id LIMIT 100000, 20) t\nON o.id = t.id;\n\n-- 方案二：游标\u002F键集分页（推荐）\nSELECT * FROM orders\nWHERE id &gt; :last_seen_id\nORDER BY id LIMIT 20;\n\u003C\u002Fcode>\u003C\u002Fpre>\n\u003Ch2>六、count 的优化思路\u003C\u002Fh2>\n\u003Cul>\n\u003Cli>\u003Ccode>COUNT(*)\u003C\u002Fcode> 和 \u003Ccode>COUNT(1)\u003C\u002Fcode> 在 MySQL 8 中性能无差异，别纠结；\u003C\u002Fli>\n\u003Cli>大表精确 count 无法避免全表扫描（InnoDB 不缓存行数），业务上可用缓存计数、抽样估算替代；\u003C\u002Fli>\n\u003Cli>条件 count 走覆盖索引：\u003Ccode>SELECT COUNT(*) FROM t WHERE status=1\u003C\u002Fcode>，若 \u003Ccode>(status)\u003C\u002Fcode> 有索引则走 Index Only Scan。\u003C\u002Fli>\n\u003C\u002Ful>\n\u003Ch2>七、调优方法论：先测量再动手\u003C\u002Fh2>\n\u003Col>\n\u003Cli>\u003Ccode>EXPLAIN ANALYZE\u003C\u002Fcode>（MySQL 8.0+）给出真实执行耗时与行数，比估算更可靠；\u003C\u002Fli>\n\u003Cli>开启慢查询日志，把 \u003Ccode>long_query_time\u003C\u002Fcode> 设为 1 秒，找到真正需要优化的 SQL；\u003C\u002Fli>\n\u003Cli>每次只改一个变量（索引\u002F写法\u002F参数），用同样的数据集对比；\u003C\u002Fli>\n\u003Cli>上线前用 \u003Ccode>EXPLAIN\u003C\u002Fcode> 复查，确认 type 和 rows 符合预期。\u003C\u002Fli>\n\u003C\u002Fol>\n\u003Ch2>结语\u003C\u002Fh2>\n\u003Cp>索引优化的本质是\u003Cstrong>减少磁盘随机 IO\u003C\u002Fstrong>。理解 B+ 树、覆盖索引、最左前缀这三件事，90% 的慢查询都能自己分析出原因。记住：先 EXPLAIN，再动手，用数据说话，而不是靠感觉。\u003C\u002Fp>\n","从执行计划 EXPLAIN 入手，系统梳理 MySQL 索引的设计原则、十个失效场景与调优方法论，含 order by、深翻页、count 优化等实战技巧。",39837.530651119785,{"id":44,"created_at":66,"updated_at":67,"code":68,"username":69,"display_name":70,"role":28,"avatar_url":71,"is_active":17,"is_banned":16,"follower_count":30,"following_count":19,"last_login_at":72},"2023-12-05T17:38:27.845+08:00","2026-10-21T00:01:41.406+08:00","r2Xr2XOR","1701769107783","小卒子","\u002Fcuts\u002F17021221850442010-06-29_233540.jpg","2026-10-21T00:01:41+08:00",{"id":74,"created_at":75,"updated_at":76,"code":77,"author_id":8,"title":78,"slug":79,"content":80,"content_html":81,"summary":82,"category_slug":14,"visibility":15,"is_pinned":16,"is_recommended":16,"is_approved":17,"view_count":18,"like_count":19,"favorite_count":19,"comment_count":19,"hot_score":83,"recommended_score":19,"status":21,"publish_at":76,"author":84,"is_liked":16,"is_favorite":16},1193,"2026-10-20T20:39:07.566+08:00","2026-10-20T20:40:21.371+08:00","bn8ooM8e","PostgreSQL 实战：从入门到高可用的完整指南","postgresql-实战：从入门到高可用的完整指南","# PostgreSQL 实战：从入门到高可用的完整指南\n\nPostgreSQL 以功能全面、扩展性强、严格的数据一致性著称，被业界誉为\"最先进的开源关系数据库\"。从 JSON 支持到全文检索，从物化视图到逻辑复制，PG 几乎覆盖了所有数据需求。本文从核心特性讲起，完整走一遍从部署到高可用的实战路径。\n\n## 一、为什么选择 PostgreSQL\n\n相比其他关系型数据库，PG 的核心优势：\n\n1. **功能纵深**：CTE、窗口函数、部分索引、数组\u002FJSONB 类型、GIN\u002FGiST 索引；\n2. **扩展生态**：PostGIS（地理空间）、TimescaleDB（时序）、pgvector（向量检索）；\n3. **数据完整性**：ACID 严格实现、外键、检查约束、排他约束；\n4. **高可用方案成熟**：流复制 + Patroni 是生产标配。\n\n## 二、基础使用：表、索引与 JSONB\n\n### 2.1 常用 DDL 与数据类型\n\n```sql\nCREATE TABLE articles (\n    id          BIGSERIAL PRIMARY KEY,          -- 自增\n    title       TEXT NOT NULL,\n    content     TEXT,\n    category    VARCHAR(50),\n    tags        TEXT[] DEFAULT '{}',            -- 数组类型\n    meta        JSONB DEFAULT '{}'::jsonb,      -- 半结构化\n    view_count  INTEGER NOT NULL DEFAULT 0,\n    created_at  TIMESTAMPTZ NOT NULL DEFAULT now()\n);\n\n-- 部分索引：只对满足条件的行为建索引，节省空间\nCREATE INDEX idx_articles_published ON articles (created_at)\nWHERE category = 'backend';\n```\n\n### 2.2 JSONB 的妙用\n\n```sql\n-- 半结构化数据直接查询\nSELECT * FROM articles WHERE meta->>'is_top' = 'true';\n\n-- GIN 索引加速 JSONB 查询\nCREATE INDEX idx_articles_meta ON articles USING GIN (meta);\n```\n\n**JSONB vs 关系表**：字段不确定、频繁增删的元数据适合 JSONB；有稳定查询模式、需要强约束的业务字段应该用列。\n\n## 三、高级查询能力\n\n### 3.1 CTE 与递归查询\n\n```sql\n-- 树形结构递归查询（分类层级）\nWITH RECURSIVE tree AS (\n    SELECT id, name, parent_id FROM categories WHERE parent_id IS NULL\n    UNION ALL\n    SELECT c.id, c.name, c.parent_id\n    FROM categories c\n    JOIN tree t ON c.parent_id = t.id\n)\nSELECT * FROM tree;\n```\n\n### 3.2 窗口函数\n\n```sql\n-- 每篇文章在其分类内的阅读量排名\nSELECT title, category, view_count,\n       RANK() OVER (PARTITION BY category ORDER BY view_count DESC) AS rank\nFROM articles;\n```\n\n窗口函数在排行榜、同比环比、去重取 TopN 场景中不可或缺。\n\n### 3.3 UPSERT\n\n```sql\nINSERT INTO user_stats (user_id, view_count)\nVALUES (1, 1)\nON CONFLICT (user_id)\nDO UPDATE SET view_count = user_stats.view_count + EXCLUDED.view_count;\n```\n\n`ON CONFLICT` 是并发安全的高频更新利器，比\"先查再改\"少一个竞态窗口。\n\n## 四、并发控制：MVCC 与事务隔离\n\nPG 通过 MVCC 让读写互不阻塞：读事务看到的是快照，写事务不阻塞读。\n\n```sql\n-- 四种隔离级别\nBEGIN ISOLATION LEVEL READ COMMITTED;      -- 默认\nBEGIN ISOLATION LEVEL REPEATABLE READ;\nBEGIN ISOLATION LEVEL SERIALIZABLE;\n\n-- 事务级锁（行锁）\nSELECT * FROM inventory WHERE id = 1 FOR UPDATE;\n```\n\n**实践提醒**：长事务会放大 MVCC 开销与膨胀（bloat），生产环境务必监控 `long-running queries` 并设置 `idle_in_transaction_session_timeout`。\n\n## 五、备份与恢复\n\n```bash\n# 逻辑备份（支持选表\u002F选库）\npg_dump -U app -h localhost app_db -Fc -f app_db.dump\n\n# 并行物理备份\npg_basebackup -D \u002Fbackup\u002Fbase -h db-primary -U replica -Xs -P\n\n# 恢复\npg_restore -U app -d app_db app_db.dump\n```\n\n**备份要点**：物理备份（pg_basebackup）用于快速恢复全库，逻辑备份（pg_dump）灵活选表；WAL 归档提供按时间点恢复（PITR）能力。\n\n## 六、主从复制与高可用：Patroni 方案\n\n### 6.1 流复制搭建\n\n```yaml\n# 主库 postgresql.conf\nwal_level = replica\nmax_wal_senders = 5\nhot_standby = on\n```\n\n```bash\n# 从库：从主库 basebackup 后配置\npg_basebackup -h db-primary -U replica -D \u002Fvar\u002Flib\u002Fpgsql\u002Fdata -Xs -P\necho \"primary_conninfo = 'host=db-primary user=replica'\" >> data\u002Fpostgresql.conf\ntouch data\u002Fstandby.signal\n```\n\n### 6.2 Patroni：自动化故障转移\n\nPatroni 基于 etcd\u002FConsul 维护集群状态，实现\"主库故障自动提升从库\"：\n\n```yaml\n# patroni.yml\nscope: pg-cluster\nname: pg-01\nrestapi:\n  listen: 0.0.0.0:8008\netcd:\n  hosts: etcd-1:2379,etcd-2:2379\nbootstrap:\n  dcs:\n    ttl: 30\n    postgresql:\n      parameters:\n        wal_level: replica\npostgresql:\n  listen: 0.0.0.0:5432\n  data_dir: \u002Fdata\u002Fpg\n```\n\n`patronictl list` 查看集群状态，`patronictl switchover` 手动切换。配合 HAProxy 或服务发现，应用无感知地完成主从切换。\n\n## 七、性能调优速查\n\n| 配置项 | 建议值 | 说明 |\n|--------|--------|------|\n| shared_buffers | 内存的 25% | 共享缓存池 |\n| effective_cache_size | 内存的 60~70% | 供优化器估算 |\n| work_mem | 4~16MB | 排序\u002F哈希内存，过高易爆内存 |\n| max_connections | 按需，配合连接池 | 每个连接有内存开销 |\n| checkpoint 相关 | 适度放大 | 减少写放大与 IO 尖刺 |\n\n```sql\n-- 常用慢查询定位\nSELECT query, calls, total_exec_time, mean_exec_time\nFROM pg_stat_statements\nORDER BY total_exec_time DESC LIMIT 10;\n```\n\n## 结语\n\nPostgreSQL 的魅力在于\"用一个库解决绝大多数问题\"：关系数据、JSON、全文检索、地理信息、时序数据都能在一套体系内优雅共存。从 JSONB 到窗口函数，从流复制到 Patroni，PG 的能力纵深让它在功能与性能之间提供了难得的平衡。建议新项目认真评估 PG——它很可能比你想象得更能打。\n","\u003Ch1>PostgreSQL 实战：从入门到高可用的完整指南\u003C\u002Fh1>\n\u003Cp>PostgreSQL 以功能全面、扩展性强、严格的数据一致性著称，被业界誉为&quot;最先进的开源关系数据库&quot;。从 JSON 支持到全文检索，从物化视图到逻辑复制，PG 几乎覆盖了所有数据需求。本文从核心特性讲起，完整走一遍从部署到高可用的实战路径。\u003C\u002Fp>\n\u003Ch2>一、为什么选择 PostgreSQL\u003C\u002Fh2>\n\u003Cp>相比其他关系型数据库，PG 的核心优势：\u003C\u002Fp>\n\u003Col>\n\u003Cli>\u003Cstrong>功能纵深\u003C\u002Fstrong>：CTE、窗口函数、部分索引、数组\u002FJSONB 类型、GIN\u002FGiST 索引；\u003C\u002Fli>\n\u003Cli>\u003Cstrong>扩展生态\u003C\u002Fstrong>：PostGIS（地理空间）、TimescaleDB（时序）、pgvector（向量检索）；\u003C\u002Fli>\n\u003Cli>\u003Cstrong>数据完整性\u003C\u002Fstrong>：ACID 严格实现、外键、检查约束、排他约束；\u003C\u002Fli>\n\u003Cli>\u003Cstrong>高可用方案成熟\u003C\u002Fstrong>：流复制 + Patroni 是生产标配。\u003C\u002Fli>\n\u003C\u002Fol>\n\u003Ch2>二、基础使用：表、索引与 JSONB\u003C\u002Fh2>\n\u003Ch3>2.1 常用 DDL 与数据类型\u003C\u002Fh3>\n\u003Cpre>\u003Ccode class=\"language-sql\">CREATE TABLE articles (\n    id          BIGSERIAL PRIMARY KEY,          -- 自增\n    title       TEXT NOT NULL,\n    content     TEXT,\n    category    VARCHAR(50),\n    tags        TEXT[] DEFAULT '{}',            -- 数组类型\n    meta        JSONB DEFAULT '{}'::jsonb,      -- 半结构化\n    view_count  INTEGER NOT NULL DEFAULT 0,\n    created_at  TIMESTAMPTZ NOT NULL DEFAULT now()\n);\n\n-- 部分索引：只对满足条件的行为建索引，节省空间\nCREATE INDEX idx_articles_published ON articles (created_at)\nWHERE category = 'backend';\n\u003C\u002Fcode>\u003C\u002Fpre>\n\u003Ch3>2.2 JSONB 的妙用\u003C\u002Fh3>\n\u003Cpre>\u003Ccode class=\"language-sql\">-- 半结构化数据直接查询\nSELECT * FROM articles WHERE meta-&gt;&gt;'is_top' = 'true';\n\n-- GIN 索引加速 JSONB 查询\nCREATE INDEX idx_articles_meta ON articles USING GIN (meta);\n\u003C\u002Fcode>\u003C\u002Fpre>\n\u003Cp>\u003Cstrong>JSONB vs 关系表\u003C\u002Fstrong>：字段不确定、频繁增删的元数据适合 JSONB；有稳定查询模式、需要强约束的业务字段应该用列。\u003C\u002Fp>\n\u003Ch2>三、高级查询能力\u003C\u002Fh2>\n\u003Ch3>3.1 CTE 与递归查询\u003C\u002Fh3>\n\u003Cpre>\u003Ccode class=\"language-sql\">-- 树形结构递归查询（分类层级）\nWITH RECURSIVE tree AS (\n    SELECT id, name, parent_id FROM categories WHERE parent_id IS NULL\n    UNION ALL\n    SELECT c.id, c.name, c.parent_id\n    FROM categories c\n    JOIN tree t ON c.parent_id = t.id\n)\nSELECT * FROM tree;\n\u003C\u002Fcode>\u003C\u002Fpre>\n\u003Ch3>3.2 窗口函数\u003C\u002Fh3>\n\u003Cpre>\u003Ccode class=\"language-sql\">-- 每篇文章在其分类内的阅读量排名\nSELECT title, category, view_count,\n       RANK() OVER (PARTITION BY category ORDER BY view_count DESC) AS rank\nFROM articles;\n\u003C\u002Fcode>\u003C\u002Fpre>\n\u003Cp>窗口函数在排行榜、同比环比、去重取 TopN 场景中不可或缺。\u003C\u002Fp>\n\u003Ch3>3.3 UPSERT\u003C\u002Fh3>\n\u003Cpre>\u003Ccode class=\"language-sql\">INSERT INTO user_stats (user_id, view_count)\nVALUES (1, 1)\nON CONFLICT (user_id)\nDO UPDATE SET view_count = user_stats.view_count + EXCLUDED.view_count;\n\u003C\u002Fcode>\u003C\u002Fpre>\n\u003Cp>\u003Ccode>ON CONFLICT\u003C\u002Fcode> 是并发安全的高频更新利器，比&quot;先查再改&quot;少一个竞态窗口。\u003C\u002Fp>\n\u003Ch2>四、并发控制：MVCC 与事务隔离\u003C\u002Fh2>\n\u003Cp>PG 通过 MVCC 让读写互不阻塞：读事务看到的是快照，写事务不阻塞读。\u003C\u002Fp>\n\u003Cpre>\u003Ccode class=\"language-sql\">-- 四种隔离级别\nBEGIN ISOLATION LEVEL READ COMMITTED;      -- 默认\nBEGIN ISOLATION LEVEL REPEATABLE READ;\nBEGIN ISOLATION LEVEL SERIALIZABLE;\n\n-- 事务级锁（行锁）\nSELECT * FROM inventory WHERE id = 1 FOR UPDATE;\n\u003C\u002Fcode>\u003C\u002Fpre>\n\u003Cp>\u003Cstrong>实践提醒\u003C\u002Fstrong>：长事务会放大 MVCC 开销与膨胀（bloat），生产环境务必监控 \u003Ccode>long-running queries\u003C\u002Fcode> 并设置 \u003Ccode>idle_in_transaction_session_timeout\u003C\u002Fcode>。\u003C\u002Fp>\n\u003Ch2>五、备份与恢复\u003C\u002Fh2>\n\u003Cpre>\u003Ccode class=\"language-bash\"># 逻辑备份（支持选表\u002F选库）\npg_dump -U app -h localhost app_db -Fc -f app_db.dump\n\n# 并行物理备份\npg_basebackup -D \u002Fbackup\u002Fbase -h db-primary -U replica -Xs -P\n\n# 恢复\npg_restore -U app -d app_db app_db.dump\n\u003C\u002Fcode>\u003C\u002Fpre>\n\u003Cp>\u003Cstrong>备份要点\u003C\u002Fstrong>：物理备份（pg_basebackup）用于快速恢复全库，逻辑备份（pg_dump）灵活选表；WAL 归档提供按时间点恢复（PITR）能力。\u003C\u002Fp>\n\u003Ch2>六、主从复制与高可用：Patroni 方案\u003C\u002Fh2>\n\u003Ch3>6.1 流复制搭建\u003C\u002Fh3>\n\u003Cpre>\u003Ccode class=\"language-yaml\"># 主库 postgresql.conf\nwal_level = replica\nmax_wal_senders = 5\nhot_standby = on\n\u003C\u002Fcode>\u003C\u002Fpre>\n\u003Cpre>\u003Ccode class=\"language-bash\"># 从库：从主库 basebackup 后配置\npg_basebackup -h db-primary -U replica -D \u002Fvar\u002Flib\u002Fpgsql\u002Fdata -Xs -P\necho &quot;primary_conninfo = 'host=db-primary user=replica'&quot; &gt;&gt; data\u002Fpostgresql.conf\ntouch data\u002Fstandby.signal\n\u003C\u002Fcode>\u003C\u002Fpre>\n\u003Ch3>6.2 Patroni：自动化故障转移\u003C\u002Fh3>\n\u003Cp>Patroni 基于 etcd\u002FConsul 维护集群状态，实现&quot;主库故障自动提升从库&quot;：\u003C\u002Fp>\n\u003Cpre>\u003Ccode class=\"language-yaml\"># patroni.yml\nscope: pg-cluster\nname: pg-01\nrestapi:\n  listen: 0.0.0.0:8008\netcd:\n  hosts: etcd-1:2379,etcd-2:2379\nbootstrap:\n  dcs:\n    ttl: 30\n    postgresql:\n      parameters:\n        wal_level: replica\npostgresql:\n  listen: 0.0.0.0:5432\n  data_dir: \u002Fdata\u002Fpg\n\u003C\u002Fcode>\u003C\u002Fpre>\n\u003Cp>\u003Ccode>patronictl list\u003C\u002Fcode> 查看集群状态，\u003Ccode>patronictl switchover\u003C\u002Fcode> 手动切换。配合 HAProxy 或服务发现，应用无感知地完成主从切换。\u003C\u002Fp>\n\u003Ch2>七、性能调优速查\u003C\u002Fh2>\n\u003Cp>| 配置项 | 建议值 | 说明 |\n|--------|--------|------|\n| shared_buffers | 内存的 25% | 共享缓存池 |\n| effective_cache_size | 内存的 60~70% | 供优化器估算 |\n| work_mem | 4~16MB | 排序\u002F哈希内存，过高易爆内存 |\n| max_connections | 按需，配合连接池 | 每个连接有内存开销 |\n| checkpoint 相关 | 适度放大 | 减少写放大与 IO 尖刺 |\u003C\u002Fp>\n\u003Cpre>\u003Ccode class=\"language-sql\">-- 常用慢查询定位\nSELECT query, calls, total_exec_time, mean_exec_time\nFROM pg_stat_statements\nORDER BY total_exec_time DESC LIMIT 10;\n\u003C\u002Fcode>\u003C\u002Fpre>\n\u003Ch2>结语\u003C\u002Fh2>\n\u003Cp>PostgreSQL 的魅力在于&quot;用一个库解决绝大多数问题&quot;：关系数据、JSON、全文检索、地理信息、时序数据都能在一套体系内优雅共存。从 JSONB 到窗口函数，从流复制到 Patroni，PG 的能力纵深让它在功能与性能之间提供了难得的平衡。建议新项目认真评估 PG——它很可能比你想象得更能打。\u003C\u002Fp>\n","从数据类型、事务与 MVCC 原理到索引优化、流复制高可用，给出 PostgreSQL 从入门到生产的完整实战指南。",39834.28804250944,{"id":8,"created_at":23,"updated_at":24,"code":25,"username":26,"display_name":27,"role":28,"avatar_url":29,"is_active":17,"is_banned":16,"follower_count":30,"following_count":19,"last_login_at":31}]