切换语言为:繁体

开发易忽视的问题:InnoDB 行锁设计与实现

  • 爱糖宝
  • 2024-09-08
  • 2056
  • 0
  • 0

InnoDB 的行锁是通过其存储模型和锁机制来实现的。下面是有关其具体实现和存储结构的深入分析:

存储结构

  1. 数据页

    • InnoDB 将表的数据存储在数据页中,每个页默认大小为 16KB。

    • 数据页中存储多个行记录,行记录按照主键顺序存放。

  2. 行格式

    • InnoDB 支持多种行格式,包括 Compact、Redundant、Dynamic 和 Compressed。这些格式影响数据的存储方式,但对行锁机制影响不大。

  3. 索引组织表

    • InnoDB 是一种索引组织表(Index-Organized Table),即数据按主键聚集存储,这意味着行数据实际上存储在 B+ 树的叶子节点上。

    • 非主键索引以主键作为叶子节点指向的数据行的指针。

行锁实现

  1. 基于索引的锁定

    • 行级锁是基于索引实现的,具体来说,InnoDB 锁定的是索引上的记录,而不是实际的数据行。

    • 当事务要锁定一行时,它实际上是在锁定该行所在索引的一个“间隙”或确切的索引键。

  2. 锁的类型

    • Record Lock:锁定单个索引记录。

    • Gap Lock:锁定索引记录之间的间隙,用于防止幻影读,通常用于范围查询。

    • Next-Key Lock:结合了 Record Lock 和 Gap Lock,在索引记录及其前面的间隙上加锁。这样可以防止其他事务插入新的索引记录。

  3. 锁的存储

    • 锁信息并不是直接存储在行本身,而是存储在内存中的锁表中。

    • 锁表包含有关每个锁的信息,如锁的类型、被锁定的事务等。

  4. 压缩行锁

    • 为了减少锁的开销和提高效率,InnoDB 使用了一种称为锁压缩(Lock Compression)的技术,使得对于连续范围内的锁,仅需要维护较少数量的锁对象。

  5. 意向锁

    • 在表级别实现的一种锁,它表示某个事务想要在行级别获得锁。这种设计使得 InnoDB 能够快速判断是否可以安全地对整个表进行锁定。

实际操作

  • 锁定机制依赖于索引,因此合理设计索引可以有效地利用行级锁。

  • 当没有适当的索引来支持查询时,InnoDB 可能会退化到锁定更多的行甚至整个表。

锁表分析

InnoDB 的锁表是用来管理和维护所有活动事务的锁信息的关键组件。锁表并不是一个物理存储的表,而是一种内存数据结构,其设计与实现旨在高效地处理并发事务,确保数据一致性和完整性。

锁表的设计和实现

  1. 锁结构

    • 每个锁都有一个锁结构,用于表示特定事务对某个资源(比如行或间隙)的锁定。

    • 锁结构包含的信息包括:被锁定的资源、锁类型(如共享锁、排他锁)、拥有锁的事务ID等。

  2. 锁的存储

    • 锁信息保存在内存中,具体来说,是通过一个全局的哈希表来维护所有当前活动的锁。

    • 这个哈希表以被锁定的资源为键,以相应的锁结构链表为值。这样可以快速查找某个资源上的锁。

  3. 锁类型

    • InnoDB 支持多种类型的锁,如前面提到的 Record Lock、Gap Lock 和 Next-Key Lock。每种锁类型在锁表中都有不同的表现形式和处理逻辑。

    • 锁的类型决定了如何对其他事务进行阻塞或允许并发访问。

  4. 锁兼容性

    • 锁表需要处理锁的兼容性问题,即检测新请求的锁是否与现有锁冲突。共享锁与共享锁兼容,但排他锁则与其他任何锁冲突。

    • 通过锁兼容矩阵,InnoDB 可以有效地决定是否授予新的锁请求。

  5. 死锁检测

    • InnoDB 实现了自动死锁检测机制,通过分析锁表中的锁依赖图来判断是否存在死锁循环

    • 一旦发现死锁,InnoDB 会主动回滚其中一个事务,以解除死锁状态。

  6. 锁等待队列

    • 对于因锁冲突而无法立即获得的锁请求,InnoDB 将其放入锁等待队列中。

    • 当锁释放时,InnoDB 会检查锁等待队列,并尝试授予队列中合适的锁请求。

  7. 性能优化

    • 为了减少锁开销,InnoDB 使用了一些优化技术,比如锁压缩。这种技术在可能的情况下将多个相邻范围的锁合并成一个,从而减少锁对象的数量。

案例分析

假设我们有一个名为 employees 的表,其中包含如下字段:id(主键),namesalary。该表的数据如下:

id name salary
1 Alice 5000
2 Bob 6000
3 Carol 7000

现在我们有两个事务对这个表进行操作:

  • 事务A:将 Bob 的薪水增加 1000。

  • 事务B:尝试读取 Bob 和 Carol 的信息。

操作步骤及锁机制

  1. 事务A开始

    • 执行 START TRANSACTION;

    • 执行 UPDATE employees SET salary = salary + 1000 WHERE name = 'Bob';

    • 假如name字段没有添加索引,InnoDB 使用主键索引进行全表扫描,来定位 Bob 的行并获取行级锁(Record Lock)在 id=2 上,因为这是精确的行更新。

  2. 事务B开始

    • 执行 START TRANSACTION;

    • 执行 SELECT * FROM employees WHERE name IN ('Bob', 'Carol') FOR UPDATE;

    • 因为事务A已经持有了 id=2 的排他锁,事务B将在此处被阻塞,等待事务A释放对 Bob 的锁。

  3. 锁表存储与管理

    • 当事务A执行更新时,InnoDB 会在内存中的锁表中创建一个记录锁条目,标记该行(id=2)已被锁定,并且事务A拥有一个排他锁。

    • 锁表的哈希结构会以 id=2 为键,指向一个链表,该链表包含事务A的锁信息。

    • 当事务B尝试获取锁时,会检查锁表,发现冲突,因此会将其请求放入等待队列中,关联到相同的哈希键(即 id=2)。

  4. 事务A提交或回滚

    • 事务A完成后,执行 COMMIT; 或 ROLLBACK;

    • InnoDB 释放 id=2 的排他锁,从锁表中移除相应的锁条目。

    • 此时,事务B会从等待队列中唤醒,重新尝试获取锁,现在它能够获取到 id=2 的共享锁或排他锁(视具体操作而定)。

  5. 事务B继续

    • 事务B成功获得锁后,可以读取 Bob 和 Carol 的信息。

    • 完成后执行 COMMIT; 释放所有锁。

结论

通过这个案例,我们可以看到 InnoDB 如何使用行级锁和锁表来管理并发访问。锁表是一个中间存储,用于跟踪当前所有活动事务的锁状态

思考题:查询条件没有索引,一定会进行表锁嘛

如果在执行 SELECT * FROM employees WHERE name = 'Alice' FOR UPDATE; 时,name 字段上没有索引,那么 InnoDB 会执行全表扫描来查找满足条件的记录。这时,InnoDB 的锁机制会有所不同:

  1. 全表扫描:由于缺少索引,InnoDB 必须扫描整张表来找到所有符合条件的行。

  2. 行锁应用:即使是在全表扫描的情况下,InnoDB 仍会尝试对每一行进行行级锁(Record Lock)。不过,由于需要检查每一行,效率会降低。

  3. 锁定性能影响:在没有索引的情况下,锁定的范围可能会更广泛,因为必须访问每个数据页和其中的每一行来判断是否符合条件。这可能导致更多的锁竞争和降低并发性能。

  4. 优化建议

    • 为避免这种低效的操作,建议为常用的查询条件列添加适当的索引。

    • 添加索引不仅可以提高查询的性能,还能使锁更精确,从而提高事务的并发处理能力。

0条评论

您的电子邮件等信息不会被公开,以下所有项均必填

OK! You can skip this field.