[{"data":1,"prerenderedAt":69},["ShallowReactive",2],{"article-148":3},{"code":4,"msg":5,"data":6,"count":14},200,"查询成功",{"id":7,"title":8,"keywords":9,"description":10,"category_id":11,"content":12,"body_html":13,"thumb_up":14,"clicks":15,"sort":14,"remark":13,"status":16,"is_open":16,"is_deleted":14,"is_top":16,"is_recommend":16,"create_time":17,"update_time":18,"image_id":19,"url":13,"member_id":14,"cate_name":20,"prev":21,"next":24,"tags":27,"words":40,"read_time":41,"comments":14,"cover":42,"relevant":43},148,"SQL 跑得慢？别慌，这里有份排查手册","SQL优化, 慢查询排查, 执行计划, 索引失效, 数据库锁","线上一条 SQL 突然变慢，往往是开发与 DBA 最头疼的问题。本文不讲晦涩理论，直接梳理一套落地的排查路径：从快速定位问题 SQL，到读懂执行计划、揪出索引与锁的毛病，再到系统资源与配置调优。下次再遇慢查询，照着清单查一遍，多半能找到病根。",6,"![](https:\u002F\u002Ftp.myong.top\u002Fstorage\u002Fmarkdown\u002F20260529\u002F5238183ef6a90661b912adff3f56233f.png)\n这大概是每个后端开发都逃不掉的噩梦：大半夜正刷着手机，报警群里突然弹出一句，“接口超时了，数据库 CPU 100%”。心里咯噔一下，打开监控一看，又是几条熟悉的慢 SQL 在捣鬼。\n\n干过几年活，踩坑无数之后，我慢慢攒出了一套排查慢 SQL 的思路。它不靠玄学，也不需要你把《高性能MySQL》倒背如流，只需要按“从外到内、从大到小”的次序一层层往下捋，问题基本藏不住。这份清单更偏向实战，MySQL 和 PostgreSQL 都会兼顾着说，原理相通。\n\n### 1. 先别急着优化，搞清“它是什么慢”\n\n![](https:\u002F\u002Ftp.myong.top\u002Fstorage\u002Fmarkdown\u002F20260529\u002F23eeaa784c56741cf30ad1fe88695f2d.png)\n第一步往往最容易被跳过：你到底在应付哪种慢？\n\n- **一直慢**：只要执行这条 SQL 就慢。这种最好查，大概率是没索引或者 SQL 写得烂。\n- **时而快时而慢**：有时候秒出，有时候卡住。这种最磨人，多半和缓存失效、锁竞争、统计信息不准有关。\n- **突然变慢**：昨天好好的，今天挂了。十有八九是数据量暴增、表结构被人改过、索引被误删，或者统计信息过期导致执行计划跳变。\n\n先把这个大方向判断清楚，能省下不少走弯路的时间。比如一直慢的 SQL 你非要去查锁，那不是瞎忙活吗。\n\n### 2. 在慢日志里“捞”出肇事者\n\n生产环境上百条查询在跑，怎么知道哪条是罪魁祸首？两个渠道：\n![](https:\u002F\u002Ftp.myong.top\u002Fstorage\u002Fmarkdown\u002F20260529\u002Fb9f68786d4f21a31aeb2ea1fe8ea7534.png)\n\n第一，直接看“现场”。连上数据库看当前谁在跑，谁跑了很久。\n\n- MySQL 里跑 `SHOW FULL PROCESSLIST;`，或者查 `information_schema.PROCESSLIST`。看到状态是 `Sending data`、`Sorting result`、`Copying to tmp table` 甚至 `Waiting for lock` 的，就要特别留意了。\n- PostgreSQL 里查 `pg_stat_activity`，把 `state = 'active'` 且排除自己会话的行捞出来。如果一堆会话都在等锁，先跳到锁排查那步。\n\n第二，从慢日志里翻旧账。建议把慢查询阈值设到你能容忍的上限，比如 1 秒，然后把慢日志打开。MySQL 可以用 `pt-query-digest` 工具分析，它会帮你按总耗时、执行次数排序，谁拖后腿一目了然。PostgreSQL 则强烈推荐开启 `pg_stat_statements` 扩展，直接查询这个视图，按 `mean_time` 排序，一条 SQL 平均跑多慢清清楚楚。\n\n这一步完成后，你应该能拿到三五条具体的、有问题的 SQL 文本了。\n\n### 3. 执行计划，你得读懂它\n![](https:\u002F\u002Ftp.myong.top\u002Fstorage\u002Fmarkdown\u002F20260529\u002F97960322b774d118fd1f30e9ebb7f8df.png)\n\n拿到 SQL 之后，别上来就加索引，先看执行计划。这是整个排查的心脏。\n\n用 `EXPLAIN` 看预估路径，用 `EXPLAIN ANALYZE`（PostgreSQL）或者 `EXPLAIN` 跟上实际执行（MySQL 8.0 的 `EXPLAIN ANALYZE` 也 OK）看真实统计。盯住这几个地方：\n\n- **访问方式**：是不是走了 `ALL` 全表扫描，或者 `Seq Scan`？几百万行的表全表扫，神仙都救不了。如果用了索引，看是 `ref`、`range` 还是 `index`，扫描行数（`rows` 列）是否合理。\n- **估算行数和实际行数差多少**：这是个大坑。`EXPLAIN` 告诉你要扫 1 行，实际执行却扫了 10 万行，那大概率是统计信息过时了，优化器瞎选路径。这时赶紧 `ANALYZE TABLE`（MySQL）或 `ANALYZE`（PG）更新一下。\n- **额外的排序和临时表**：执行计划里出现 `Using filesort`、`Using temporary`（MySQL）或者显式的 Sort、Hash 节点（PG），说明你的 `ORDER BY`、`GROUP BY` 没能利用好索引，数据被迫在内存甚至磁盘上重新排队。\n- **连接顺序和算法**：多表关联时，驱动表应当是小表，被驱动表的连接列要有索引。如果看到嵌套循环扫描了大量行，基本就是该有的索引没建。\n\n把执行计划当成地图，它告诉你数据库是怎么找到数据的。哪段路堵车，一眼便知。\n\n### 4. 索引，最容易背锅也最容易救火\n\n90% 的慢查询，最后都归到索引上。常见的问题无非这几种：\n\n**缺索引**。`WHERE` 条件、`JOIN` 连接的列、`ORDER BY` 和 `GROUP BY` 后的字段，如果没有索引，只能全表扫。看执行计划里没有 `key`，直接补上复合索引往往能解决问题。\n![](https:\u002F\u002Ftp.myong.top\u002Fstorage\u002Fmarkdown\u002F20260529\u002F163f8755cff58242d19a6be1742cf465.png)\n\n**索引失效**。这才是让人挠头的地方，明明有索引，优化器就是不用。几种典型的“作死”操作：\n\n- 在索引列上用函数或计算：`WHERE DATE(create_time) = '2025-01-01'`，这个 `DATE()` 一上，索引直接废。\n- 隐式类型转换：字段是字符串，你给个数字 `WHERE phone = 13800138000`，数据库得把所有手机号转成数字再比，没法用索引。\n- `LIKE '%关键词'`，前面带百分号，索引失效。\n- 联合索引没遵循“最左前缀”：建了 `(a,b,c)` 的索引，但你查询只有 `b` 和 `c` 条件，那对不起，用不上。\n\n**索引选择性太差**。如果一个字段只有“是\u002F否”两个值，即便有索引，返回一半数据时优化器会直接全表扫，因为这比来回跳转索引再回表要快。这不是 bug，是它更聪明的选择。\n\n遇到这些情况，改写法、调整索引字段顺序、或者用覆盖索引来避免回表，都是常规手段。\n\n### 5. 别忘了锁和长事务\n![](https:\u002F\u002Ftp.myong.top\u002Fstorage\u002Fmarkdown\u002F20260529\u002F774ee204c93249206b1b608a2f5c0439.png)\n\n有时一条 SQL 执行计划很完美，索引也都齐了，但偏偏卡着不动。这时候得往锁的方向看。\n\nMySQL InnoDB 下，可以查 `information_schema.INNODB_TRX` 看当前事务，如果一个事务 `trx_started` 显示已经在 10 分钟前就开始了，还一直没提交，它很可能攥着一堆锁没放。再配合 `performance_schema.data_locks` 和 `data_lock_waits`（8.0 版本）就能看到谁在等谁的锁。`SHOW ENGINE INNODB STATUS` 里的锁信息也非常详细。\n\nPostgreSQL 就更直接，查 `pg_locks` 视图，结合 `pg_stat_activity` 看 `wait_event_type = 'Lock'` 的会话，然后用 `pg_blocking_pids(pid)` 一把找出阻塞源。\n\n发现长事务或锁等待，策略通常是 kill 掉阻塞源头（比如那个忘记提交的只读事务），或者优化应用代码，把大事务拆小，尽快提交。\n\n### 6. 往外看：系统资源和配置\n![](https:\u002F\u002Ftp.myong.top\u002Fstorage\u002Fmarkdown\u002F20260529\u002F425f38b1868e8fe53a270bf3d9421b6d.png)\n\n如果 SQL 和索引都挑不出毛病，问题很可能在数据库外面一圈，或者在配置上。\n\n- **CPU 飙高**：大量排序、计算，或者全表扫描把 CPU 吃满，查询自然排队。看 `top` 谁在干活。\n- **磁盘 IO 告急**：`iowait` 高，说明缓冲池不够大，数据没法全部缓存到内存，频繁读磁盘。`innodb_buffer_pool_size`（MySQL）或 `shared_buffers`（PG）要是只设了几百 MB，而数据几百 GB，那每次查询都相当于去硬盘上刨地。\n- **内存配置抠搜**：排序缓冲区（`sort_buffer_size`）、临时表大小（`tmp_table_size`）、PG 的 `work_mem` 设得太小，会导致稍大一点的排序或 Hash 操作就溢出到磁盘，慢得离谱。\n- **连接数爆炸**：每个连接都吃内存，几百个连接上下文切来切去，CPU 光忙活调度就饱了。\n- **网络延迟**：如果数据库和应用程序不在一个机房，而 SQL 又要返回几万行结果，网络传输本身就会成为瓶颈。\n\n调整这些参数需要结合服务器的实际内存来，不是越大越好，但默认值往往偏保守。让热数据尽量待在内存里，是性价比最高的优化。\n\n### 7. SQL 写法，顺手治治“代码洁癖”\n\n有些 SQL 天生就慢，换种写法可能就跑得飞快。\n\n- **别贪图方便写 `SELECT *`**：只要三五列，却把整行全拖出来，白白增加网络传输和回表开销。\n![](https:\u002F\u002Ftp.myong.top\u002Fstorage\u002Fmarkdown\u002F20260529\u002F3444a64af6138c65c23054cf40820376.png)\n\n- **深分页老老实实延迟关联**：`LIMIT 100000, 10` 这样的翻页，数据库需要查出前 100010 行再扔掉 10 万行。不如先用覆盖索引取到主键，再回表拿数据，也就是常说的“延迟关联”。\n- **相关子查询能改写就改写**：`WHERE` 条件里每行都执行一次子查询，数据一大就是灾难，换成 `JOIN` 或 `EXISTS` 往往效果拔群。\n- **避免无谓的排序**：`UNION` 会默认去重排序，如果不需要去重，用 `UNION ALL`；`GROUP BY` 如果已经利用索引有序了，就没必要再额外 `ORDER BY`。\n\n### 8. 定期“保养”：统计信息与表碎片\n\n数据库跑久了，表会膨胀，统计信息会过时，这也是慢查询的慢性病。\n\nMySQL 的 `OPTIMIZE TABLE` 或 PG 的 `VACUUM`、`VACUUM FULL` 能回收空间，让扫描更紧凑。统计信息则需要定期或按需执行 `ANALYZE`，尤其在大批量写入或删除之后。优化器只有看得清数据分布，才能选对路。\n\n当单表数据量真到了几千万甚至上亿，再怎么优化索引都会吃力，这时候就要考虑分区、分库分表，或者冷热数据分离了，但那属于架构演进的范畴，不是单条 SQL 能全解决的。\n\n### 9. 把排查变成常态：监控与工具\n![](https:\u002F\u002Ftp.myong.top\u002Fstorage\u002Fmarkdown\u002F20260529\u002Fef2b3702be15b3d2c0dbf93ed3f87012.png)\n\n别每次出问题都火急火燎地手动敲命令。花点时间把监控搭起来，能省下半条命。\n\nMySQL 生态里，`pt-query-digest` + 慢日志是最基础的，进一步可以用 PMM（Percona Monitoring and Management），慢查询、锁、资源占用全看得见。PostgreSQL 则用 `pg_stat_statements` 搭配 `pgBadger` 分析日志，或者接上 pgwatch2。应用层挂上 APM（像 Pinpoint、SkyWalking），能直接看到哪条 SQL 在拖慢哪个接口，把锅精准分配给对应的人。\n\n---\n\n排查慢 SQL 这套活儿，说到底就一句话：**先定位是哪条 SQL，再用执行计划看它怎么跑，最后从索引、锁、资源、写法几个方向对症下药。** 多数问题，走到第三步就已经露出马脚了。下次再碰见数据库变慢，希望这个清单能帮你稳住心态，找到病灶，而不是对着满屏告警干瞪眼。",null,0,29,1,"2026-05-23 00:43:43","2026-05-29 11:26:33",126,"Mysql",{"id":22,"title":23},147,"数据库索引的定海神针：B+ 树凭什么这么能打？",{"id":25,"title":26},149,"被误解的 Redis 单线程：为什么它依然是快如闪电的秘密",[28,31,34,37],{"id":29,"name":30},28,"数据库",{"id":32,"name":33},118,"性能调优",{"id":35,"name":36},138,"后端开发",{"id":38,"name":39},129,"运维",4009,10,"https:\u002F\u002Ftp.myong.top\u002Fstorage\u002Farticle\u002F47\u002F99a9dc36e27ae31744abfc94176cef.jpg",[44,49,54,59,64],{"id":45,"title":46,"create_time":47,"description":48},90,"Redis常见使用场景","2020-06-17 16:09:42","Redis是一个开源的使用ANSI C语言编写、支持网络、可基于内存亦可持久化的日志型、Key-Value数据库，并提供多种语言的API。本篇文章，主要介绍利用PHP使用Redis，主要的应用场景。",{"id":50,"title":51,"create_time":52,"description":53},94,"客户端连接MySQL8提示 caching-sha2-password 问题","2020-10-19 10:55:26","在安装mysql8的时候如果选择了密码加密，之后用客户端连接比如navicate，会提示客户端连接caching-sha2-password,是由于客户端不支持这种插件，可以通过如下方式进行修改：",{"id":55,"title":56,"create_time":57,"description":58},87,"MySQL中InnoDB和MyISAM区别","2020-06-12 17:11:21","InnoDB具有事务，支持4个事务隔离级别，回滚，崩溃修复能力和多版本并发的事务安全，包括ACID.如果应用中需要执行大量的INSERT或UPDATE操作，则应该使用InnoDB,这样可以提高多用户并发操作的性能。MyISAM管理非事务表，提供高速存储和检索，以及全文搜索能力，如果应用中需要执行大量的SELECT查询，那么MyISAM是更好的选择。",{"id":60,"title":61,"create_time":62,"description":63},88,"MySQL实现循环插入千万级数据","2020-06-12 17:56:22","对于一些数据量较大的系统，数据库面临的问题除了查询效率低下，还有就是数据入库时间长。特别像报表系统，可能每天花费在数据导入上的时间就会长达几个小时之久。因此，优化数据库插入性能是很有意义的。",{"id":65,"title":66,"create_time":67,"description":68},86,"MySQL查询表结构命令","2020-06-09 11:30:00","###### MySQL查询表结构命令\n\n###### 1、查询表结构\n\n主要显示字段类型主键是否允许为空等\n\n```mysql\nDESC 表名;\n```\n\n结果显示\n\n| Field | Type         | Null | Key  | Default | Extra          |\n| :---- | ------------ | ---- | ---- | ------- | -------------- |\n| id    | int(11)      | NO   | P",1783431645694]