这篇分两部分:索引结构(B+ 树)与 InnoDB 锁模型。
先讲索引,后讲锁,最后补一个索引失效清单。
为什么是 B+ 树
核心原因是 磁盘 IO 成本:索引要尽量减少 IO 次数。
B+ 树的优势:
- 内部节点不存数据,扇出更大,树更矮
- 叶子节点有序链表,范围查询快
- 查询路径长度稳定
B+ 树 vs B 树
B+ 树更适合数据库索引,因为:
- 查询性能更稳定
- 范围查询更高效
- 磁盘 IO 次数更少
InnoDB 锁类型
表锁
LOCK TABLE payment READ;
UNLOCK TABLES;
LOCK TABLE payment WRITE;
UNLOCK TABLES;
共享锁(S)
SELECT * FROM payment WHERE id=1 LOCK IN SHARE MODE;
排他锁(X)
SELECT * FROM payment WHERE id=1 FOR UPDATE;
意向锁
意向锁由 InnoDB 自动管理,用于表级锁和行级锁的协调。
InnoDB 行锁原理
行锁是“锁索引”而不是“锁行”。无索引时会退化成全表锁。
三种锁:
- Record Lock
- Gap Lock
- Next-Key Lock(Record + Gap)
Gap Lock 示例
SELECT * FROM t1 WHERE key1 > 10 AND key1 < 30 FOR UPDATE;
会锁住索引区间,防止插入幻读。
Next-Key Lock 示例
SELECT * FROM t1 WHERE key1 > 10 AND key1 < 30;
范围内记录与间隙都会被锁住。
索引失效常见情况
表结构示例:
CREATE TABLE payment_tab (
`id` bigint unsigned auto_increment,
`payment_id` bigint unsigned not null,
`user_id` bigint unsigned not null,
`create_time` int unsigned not null,
`update_time` int unsigned not null default 0,
`amount` bigint not null default 0,
`payment_status` tinyint not null default 0,
`linked_order_no` varchar(64) default NULL,
`payment_type` tinyint not null default 0,
PRIMARY KEY (`id`),
KEY `idx_user_id` (`user_id`),
UNIQUE KEY `uk_payment_id` (`payment_id`),
KEY `idx_create_time` (`create_time`),
KEY `idx_update_time` (`update_time`),
KEY `idx_linked_order_no_payment_type` (`linked_order_no`, `payment_type`)
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4;
常见失效原因:
NULL判断OR两侧字段无法同时命中索引!=/<>/NOT IN- 前缀模糊匹配
%xxx - 索引列上使用函数
HAVINGORDER BY不同字段
小结
索引的本质是“用空间换时间”。锁的本质是“用并发换一致性”。理解两者,SQL 性能与正确性才会稳定。