数据库基础问答(二) —— 为什么like不会像范围查询那样使索引失效

age<5

先看例子:

建立了name_age_address组合索引:

最普通的全匹配索引查询:

最左匹配不用多说;

name_age_address相当于建立了三个索引:

name索引,name_age索引,name_age_address索引。

可以分别看一下长度:

name —— 377:

name_age —— 381:

name_age_address —— 533:

where的限制条件只能只用这“三个”(其实还是一个)索引中的一个,要么就完全没有使用(没有符合最左匹配原则),如:

没有name,只有age_address,索引失效:

漏掉age,只有name_address:

可以看到,由于age缺失,索引到age列即失效,即使用where name = "5"和where name = "5" and address = “nihao”是一样的。这也是了解了机制之后,我们才能更好地利用MySQL的例证之一。

 

那么除了缺失,其他什么情况会使索引匹配终结呢?

一个重要的一项是:范围查询

可以看到,name_age_address索引的age使用范围限制条件时,利用的索引长度只有381而不是533,即使用的是name_age索引,到age就失效了。原因很好理解,考虑B+树的结构,数据的相对大小是像“字典序”一样,从左往右定义的,举个例子,B+树的最后一层(叶子层),(name , age, address)的三元组,("a", 11, "上海")必然是在 ("b", 1, "北京")左侧的。但是(XXX, 11, "上海")和(YYY, 1, "北京")(注意name是未知)的相对顺序是不定的。

不过应该注意到,MySQL的查询是寻找代价最小的执行计划,不是说理论上可以利用的索引就一定利用了,用上面的理论,看下面的例子:

按说age > 1 and age < 5 和 age > 1没有本质区别,这里可以看到根本就没有使用任何索引,退一步讲,就算使用不了name_age索引,至少也可以使用name索引,在name_age_address B+树的叶子层拿到一个范围的数据后,带着该id去主表中拿数据,再使用age > 1 and age < 5 and address = "天津"进行普通的where筛选。现实是,一个索引都没有用,直接走全表扫描。目前不知道是MySQL在编译阶段就确定了不走任何索引(看见闭区间直接不走blablabla),还是查询优化器计算得出走索引带来的优化太小(name的筛选条件过滤得太少),这样得到的成本还不如直接走全表扫描(省去了查找name_age_address  索引树的过程)。

 

当然,这里都不是本文要讲的重点 —— like不像范围查询,竟然可以继续进行索引匹配。

看演示:

按说like就像查字典一样,name like "a%"只会匹配到所有name以a开头的记录,接下来索引匹配就终结了,就像上面 age > 1就不能继续匹配address字段一样,在叶子层取了n个小区间,每个小区间内都满足name = "5" and age > 1,但是内部的address没有进行判断了,直接拿着这n个小区间内的id去主表中取记录,再对这些记录进行过滤(address = "天津")。更不要说age > 1 and age < 5时,直接完全不走扫描。

但这里的name like "a%"竟然一直匹配到索引结束,即 name like "a%"会直接找到当前节点的name首字母为a的节点,以该节点为根的B+树必然全部满足条件,此时对该B+树使用age_address索引即可。

那么按这个理论,name = "5" and age > 1可以完全匹配索引吗?

不能,注意,name like "a%",这里的like "a%"其实表现在B+树上并不是一个范围值,而是一个确定值,建立B+树时,一定以单个字符为单位建分支节点,如ab和ac一定是同层不同节点。

按说age也可以啊。。。不然age > 5是怎么实现利用的。。。。

 

 

另外注意,name like “%5”,“%5%”都是不能利用索引的,而name like 5可以

### 数据库索引失效场景 数据库索引失效通常发生在以下几种常见场景中: 1. **使用函数或表达式覆盖列名** 如果在 `WHERE` 子句中对索引字段应用了函数或其他运算符,则可能导致索引失效。例如,在查询中使用 `LOWER(column_name)` 或 `column_name + 1` 等操作时,MySQL 将无法利用该字段上的索引[^3]。 2. **隐式类型转换** 当查询条件中的数据类型与索引字段的数据类型匹配时,可能会发生隐式类型转换,从而导致索引失效。例如,当字符串类型的索引字段被用于数值比较时,可能引发此问题[^5]。 3. **模糊查询前缀通配符** 使用 `LIKE '%keyword'` 进行模糊查询时,由于 `%` 出现在开头位置,MySQL 需要扫描整个表来查找符合条件的结果,因此索引将起作用[^4]。 4. **未命中索引范围** 查询条件未能有效利用复合索引的最左前缀原则(Leftmost Prefix Rule)。如果复合索引定义为 `(col1, col2, col3)`,而查询仅限于 `col2` 而跳过了 `col1`,则不会触发索引优化[^1]。 --- ### 行锁升级为表锁的条件 行级锁升至表级锁的情况主要取决于以下几个因素: 1. **高并发下的频繁冲突** 若大量事务同时尝试访问并修改同一张表内的同行资源,但由于这些请求之间存在高度重叠区域或者顺序当等原因引起持续性的等待队列形成,则系统为了减少开销有可能决定提升整体封锁力度到整表层面以便更高效管理权限分配过程[^2]。 2. **缺乏适当索引来支持特定操作需求** 假设某次更新动作因缺少必要的辅助结构而导致引擎执行全盘检索才能定位目标对象集合——这种情形下很容易促使原本设计好的细颗粒度控制机制退化成为全局性限制措施之一即转变为表格级别的独占使用权声明方式[^5]。 3. **特殊SQL语句的影响** 显式的调用诸如 `LOCK TABLES` 可以直接指定采用何种形式的安全保障策略;另外一些复杂的多步处理流程也可能间接促成此类转变的发生几率增加,尤其是涉及到外部参照完整性维护方面的工作任务时更是如此[^1]。 4. **死锁预防机制的作用效果** 在检测到潜在循环依赖关系之后采取相应的规避手段过程中有时也会伴随出现类似的调整行为模式改变现象[^3]。 以下是实现一个简单的测试脚本验证上述理论的例子: ```sql -- 创建测试表并插入初始数据 CREATE TABLE test_table ( id INT PRIMARY KEY, name VARCHAR(50), age INT ); INSERT INTO test_table (id, name, age) VALUES (1,'Alice',28), (2,'Bob',22); -- 场景模拟:无索引条件下进行排他锁操作 START TRANSACTION; SELECT * FROM test_table WHERE age = 22 FOR UPDATE; -- 此处age未建索引 -- 新连接开启另一个事务试图获取另一条记录的锁 START TRANSACTION; SELECT * FROM test_table WHERE age = 28 FOR UPDATE; -- 应会出现锁等待 ``` 通过观察第个事务的行为可以判断第一个事务是否发生了锁升级。 ---
评论 3
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值