PS:现在orders表中存在idx_user_id(user_id),以及idx_user_status(user_id, status)两个索引
1-是否走索引
并不是符合条件的sql就一定要走索引,数据库内部有优化器,优化器会选择成本最低的执行计划
运行:
sql
EXPLAIN SELECT * FROM orders WHERE user_id > 1 AND status = 1;
结果为:
| id | select_type | table | partitions | type | possible_keys | key | key_len | ref | rows | filtered | Extra |
|---|---|---|---|---|---|---|---|---|---|---|---|
| 1 | SIMPLE | orders | ALL | idx_user_id,idx_user_status | 99520 | 5 | Using where |
优化器选择了全表扫描 ,没有使用(user_id)或(user_id, status)索引
数据是随机生成的,user_id 是0 ~ 10000,递增的,条件user_id > 1几乎匹配:99% 数据,走索引会大量回表,此时不如走全表扫描,所以优化器选择全表扫描
2-索引失效
如果针对sql的字段有计算操作,那么索引就会失效
运行:
sql
EXPLAIN SELECT * FROM orders WHERE user_id + 1 = 5;
结果为:
| id | select_type | table | partitions | type | possible_keys | key | key_len | ref | rows | filtered | Extra |
|---|---|---|---|---|---|---|---|---|---|---|---|
| 1 | SIMPLE | orders | ALL | 99520 | 100 | Using where |
分析:
根本没有用到索引,因为user_id + 1,需要进行运算,这意味着数据库必须:
bash
读取每一行
计算 user_id + 1
再判断是否等于5
#-------------------------
扫描整表
↓
计算 user_id + 1
↓
判断 = 5
不要在索引列上做运算
2-2.常见索引失效的写法
- 运算:
sql
user_id + 1 = 5
price * 10 > 100
- 函数:
sql
DATE(create_time) = '2025-01-01'
- 类型转换:
sql
#字符串与数字比较可能触发转换
user_id = '123'
2-3.索引失效优化
原则:保持索引列"原样参与比较"
- 例如时间查询:
sql
错误:
WHERE DATE(create_time) = '2025-01-01'
正确:
WHERE create_time >= '2025-01-01'
AND create_time < '2025-01-02'
2-4.索引的隐式转换
orders表中的user_id是BIGINT类型,这里查询不是用的数字,运行:
sql
EXPLAIN SELECT * FROM orders WHERE user_id = '1';
结果为:
| id | select_type | table | partitions | type | possible_keys | key | key_len | ref | rows | filtered | Extra |
|---|---|---|---|---|---|---|---|---|---|---|---|
| 1 | SIMPLE | orders | ref | idx_user_id,idx_user_status | idx_user_id | 9 | const | 9 | 100 | Using index condition |
分析:
发现还是用到了索引,MySQL进行隐式类型转换 ,将'1' → 1,执行逻辑等价于:WHERE user_id = 1
本质上确实发生了类型转换,但最佳实践还是保持类型一致