索引查询并非很快

全表扫描相信大家都知道,一般情况下都要尽量不要使用。索引扫描是先在索引B树中查找满足条件的行,找到后根据索引中行ROWID信息,直接定位表中相应的行。即使表中的数据有千万级,效率是很高的。这种情况只是针对返回的行比较少的情况,如果很多的话,效率很可能就低了。

原因:


[img]/upload/attachment/102034/17c79f53-fb9a-39ab-b538-089168ad9411.jpg[/img]


上图中,第1个叶块中的第2个索引条目和第2个叶块中的第8个索引条目,指向表中同一块:第5号块。索引是按顺序访问的,当访问到第1个索引条目时,Oracle将表中5号块读进Buffer cache,读后面的索引条目时,因为后面的索引条目并不一定也指向表中5号块,在此例中,后面的索引条目都指向其他的块,Oracle必须将其他的块读进Buffer cache,而这后读进Buffer cache中的块,可能会使表的第5号块老化(也就是被移出缓存),当读到第8个索引条目时,又必须重新物理读取一次表的第5号块。而全表扫描则不同,它每读取一个块,必将此块中所有满足条件的行全部取出,才会物理读取下一个块。因此,如果是取表中大量行,通过索引扫描反而会使物理读增加,因而造成性能降低。

所以从表中取出大量行时,通常行数占表中总行数的10%以上时,再通过索引取行效率就不很好了。
### 问题:数据库表数据量较大,执行添加索引后,业务反馈查询变慢,是什么原因?如何优化添加索引的操作? --- ### ✅ 回答: 在数据量较大的表上**在线添加索引**(如 `ALTER TABLE ... ADD INDEX`)时,虽然最终目的是为了**提升查询性能**,但**在加索引的过程中甚至之后的一段时间内,反而可能导致查询变慢**。这是非常常见的现象。 下面我们从 **原因分析 → 影响机制 → 优化方案** 三个层面详细解答。 --- ## 🔍 一、为什么添加索引会导致查询变慢?(根本原因) ### 1. **加索引是“写操作”,会锁表或阻塞查询** - 在 MySQL(InnoDB)中: - 添加普通二级索引(非主键)默认使用 **`INPLACE` 算法**(MySQL 5.6+),不重建表,但仍需获取元数据锁(MDL) - 虽然不像老版本那样完全锁表,但在某些阶段仍会短暂阻塞 DML 操作(INSERT/UPDATE/DELETE) - 如果有长事务或未提交事务,MDL 锁可能被长时间持有,导致后续所有查询排队 > 📌 示例场景: > 正在加索引 → 一个 SELECT 查询来了 → 它需要获取 MDL 共享锁 → 但 DDL 正在持有排他锁 → 查询被阻塞! --- ### 2. **构建索引过程消耗大量 I/O 和 CPU 资源** - 添加索引的本质是:扫描整张表的数据,提取字段值,排序并写入 `.ibd` 文件中的 B+ 树结构 - 对于大表(比如千万级),这个过程会产生: - 大量磁盘读(全表扫描) - 大量磁盘写(构建索引页) - 内存压力(buffer pool 被污染) - 导致数据库整体负载升高,**其他正常查询响应变慢** > ⚠️ 即使没有锁冲突,系统资源争抢也会让查询“卡顿” --- ### 3. **缓冲池(Buffer Pool)被新索引页“污染”** - InnoDB 使用 Buffer Pool 缓存数据页和索引页 - 加索引过程中会将大量新的索引页加载进内存 - 原本缓存的热点数据页可能因此被淘汰 - 导致原本很快查询突然变成“磁盘 IO”,性能下降 --- ### 4. **统计信息未更新,执行计划不准** - 新建索引后,MySQL 不会立即更新统计信息(`SHOW INDEX FROM table` 中的 Cardinality) - 查询优化器仍然认为该列不适合走索引,可能继续选择全表扫描 - 或者错误地选择了新建的低效索引(比如选择性差),反而更慢 --- ### 5. **客户端连接堆积 & 超时连锁反应** - 查询被阻塞 → 连接不释放 → 新请求不断进来 → 连接池耗尽 - 最终出现大量超时、熔断、服务雪崩 --- ## ✅ 二、如何安全高效地为大表添加索引?(优化方案) 以下是生产环境推荐的最佳实践: --- ### ✅ 方案 1:选择低峰期 + 控制并发 ```sql -- 在业务低峰期执行,并控制并发线程数 ALTER TABLE large_table ADD INDEX idx_user_id (user_id); ``` - 避免在白天高峰时段操作 - 提前通知运维和开发团队 - 监控 CPU、IOPS、连接数等指标 --- ### ✅ 方案 2:使用 `ALGORITHM=INPLACE, LOCK=NONE`(MySQL 5.6+) 明确指定不锁表(仅适用于支持的 DDL 类型): ```sql ALTER TABLE large_table ADD INDEX idx_status_created (status, created_at) ALGORITHM=INPLACE, LOCK=NONE; ``` > 注意:并非所有情况都能做到 `LOCK=NONE`,例如涉及列类型变更就不行。 --- ### ✅ 方案 3:使用 Online DDL 工具(强烈推荐) #### 推荐工具:**pt-online-schema-change(Percona Toolkit)** 它通过以下方式实现“零停机”加索引: ```bash # 示例命令 pt-online-schema-change \ --host=localhost \ --user=root \ --alter "ADD INDEX idx_email (email)" \ --execute \ D=your_db,t=users ``` ##### 原理简述: 1. 创建一个影子表(_users_new) 2. 在影子表上加索引(不影响原表) 3. 创建触发器同步原表的增删改到影子表 4. 逐步将原表数据拷贝过去 5. 最后原子性 rename 替换原表 ✅ 优点: - 几乎不影响线上查询 - 支持暂停、限速、回滚 ❌ 缺点: - 时间较长(尤其是大表) - 触发器带来额外开销 --- #### 替代方案:**GitHub 的 gh-ost**(基于 binlog 同步,更轻量) 适合超大表、对稳定性要求极高的场景。 --- ### ✅ 方案 4:先创建再启用(部分支持) 某些存储引擎(如 MyRocks)支持“不可见索引”或“准备状态索引”,但 InnoDB 不直接支持。 替代做法: - 先用 pt-osc 创建索引 - 等待完成后再在应用层逐步引导流量使用新索引 --- ### ✅ 方案 5:监控与验证 加完索引后要检查: ```sql -- 查看索引是否生效 SHOW INDEX FROM large_table; -- 强制刷新统计信息(可选) ANALYZE TABLE large_table; -- 检查执行计划是否用了新索引 EXPLAIN SELECT * FROM large_table WHERE user_id = 123; ``` --- ## ✅ 三、总结:关键原则 | 原则 | 说明 | |------|------| | ❌ 不要在高峰期直接 `ALTER TABLE` | 极易引发故障 | | ✅ 使用 `pt-online-schema-change` | 生产环境首选 | | ✅ 加索引前评估必要性 | 是否真的能提升查询?选择性够高吗? | | ✅ 监控资源使用 | CPU、IO、内存、连接数 | | ✅ 提前压测 | 在测试环境模拟大表加索引的影响 | ---
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值