这篇分两部分:索引结构(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
  • 索引列上使用函数
  • HAVING
  • ORDER BY 不同字段

小结

索引的本质是“用空间换时间”。锁的本质是“用并发换一致性”。理解两者,SQL 性能与正确性才会稳定。