数据库管理最热问答集锦,看完不再困惑 - 编号109327
许多 DBA 和开发者在实际运维中遇到的最棘手问题,并非高深的分布式理论,而是“索引失效”与“死锁排查”这两类日常高频陷阱。根据某云数据库社区 2024 年 7 月统计,超过 60% 的求助帖请求围绕这两个核心场景展开。下面直接拆解最常见的 3 个真实问答,帮你避开这些隐蔽的坑。
为什么我的查询走了全表扫描,明明建了索引?
典型场景:某电商后台团队在 order 表的 status 字段上建了索引,执行 `SELECT * FROM orders WHERE status = 'shipped'` 却仍触发全表扫描,导致大促期间查询超时。问题根源出在数据分布上:当 status='shipped' 的记录占全表 40% 以上时,数据库优化器认为走索引回表成本高于直接扫全表,所以主动抛弃索引。这不是索引“失效”,而是优化器基于统计信息的理性选择。解决方案并非加索引,而是改为覆盖索引或分区表:例如将索引改为 `(status, order_time, amount)`,让查询仅通过索引完成,无需回表。对应的,如果必须筛选大量数据,可以改用 `status IN ('shipped', 'pending')` 并配合 LIMIT 分页,强制优化器改变成本评估。
死锁日志看不懂,如何快速定位是哪两行数据在争抢?
真实案例:某支付系统一天内出现 30 次死锁回滚,业务方反馈用户支付页面卡死。DBA 抓到了死锁日志,但面对一堆十六进制的锁地址和事务编号时,直接懵圈。关键技巧在于:不要看日志里的“WAITING FOR THIS LOCK TO BE GRANTED”那一段,而是直接找 `*** (1) TRANSACTION:` 和 `*** (2) TRANSACTION:` 的起始位置,这两段分别对应两个互锁的事务。接着看每段最后的 `space id X page no Y` 和 `index name of table`,例如 `space id 99 page no 1234` 配合 `index PRIMARY`,就说明是在主键索引的第 1234 页上发生了行锁冲突。再用 `SELECT * FROM sys.innodb_buffer_page WHERE space_id = 99 AND page_no = 1234` 就能查出具体是哪行数据的 page 内容。实际排查发现,是两条 SQL 分别以不同顺序更新了用户余额表和订单表,把事务内的更新顺序统一后死锁消失。
MySQL 的 read_only 打开了,为什么还能写入数据?
运维人员常踩的坑:某团队在维护时设置 `SET GLOBAL read_only=1`,结果发现拥有 SUPER 权限的用户(如 root)依然能写入。这并非 Bug,而是 MySQL 的设计逻辑——read_only 默认只对普通用户生效,SUPER 用户不受限制。更隐蔽的是,复制场景下如果从库开了 `read_only`,但主库过来的 DDL 语句(如 ALTER TABLE)仍能执行成功,导致从库意外修改了表结构。正确的做法是同时设置 `super_read_only=ON`(MySQL 5.7.8+ 支持),这个参数会强制 SUPER 用户也无法写入。如果你还在用旧版本且无法升级,需要手动回收不需要的 SUPER 权限,例如 `REVOKE SUPER ON *.* FROM 'app_user'@'%'`。
三个最常踩的误区与具体建议
- 误区:加索引就能解决所有慢查询 —— 实际上,索引对高选择性列(如性别字段只有男女)几乎无效。建议:写 SQL 前先用 `EXPLAIN FORMAT=JSON` 查看 rows_examined_per_scan 与 filtered 比例,若 filtered 低于 20%,说明索引过滤性差,应优先考虑覆盖索引或业务层排序。
- 误区:死锁只发生在并发写入同一行 —— 常见于间隙锁导致,如 `SELECT ... FOR UPDATE` 配合范围查询。建议:将事务隔离级别从 REPEATABLE READ 降为 READ COMMITTED(业务允许的话),或把范围锁改为精确行锁,例如用 `IN (id1, id2)` 代替 `BETWEEN a AND b`。
- 误区:read_only 是安全的安全锁 —— 它阻止不了 DDL 和 SUPER 用户。建议:做维护操作时,先执行 `SELECT @@super_read_only` 确认是否开启,未开启则 `SET GLOBAL super_read_only=1`,并在脚本中增加 `--force-read-only` 检查,避免从库被意外写入。