21 InnoDB数据库锁

InnoDB数据库锁

锁的类型

InnoDB数据库中所得类型主要包括共享锁和排它锁两种

共享锁和排它锁是数据库中用于控制数据并发访问的机制。

共享锁是一种读取锁,它允许多个事务同时读取同一份数据,但是不允许修改。共享锁的存在不会阻止其他事务获得共享锁,但会阻止其他事务获得排它锁。共享锁适用于读取频繁,写入较少的场景。

排它锁是一种写入锁,它不允许其他事务读取或修改被锁定的数据,只允许持有锁的事务进行操作。排它锁适用于写入频繁,读取较少的场景。

举个例子,假设有一个银行账户表,其中包含每个账户的余额。现在有两个事务分别要对同一个账户进行操作,一个事务要查询余额,另一个事务要进行存款操作。 如果这两个事务都使用共享锁,那么它们可以同时读取同一个账户的余额,但是存款操作会失败,因为它不能修改数据。 如果这两个事务都使用排它锁,那么查询余额和存款操作都会被阻塞,直到持有锁的事务释放锁。 如果查询余额的事务使用共享锁,而存款操作的事务使用排它锁,那么查询余额的事务可以读取余额,而存款操作会被阻塞,直到共享锁被释放。

锁兼容:如果一个事务T1已经获得了行 r 的共享锁,那么另外的事务 T2 可以立即获得行 r 的共享锁(S Lock),因为读取并没有改变行 r 的数据,称这种情况为锁兼容。排它锁(X Lock)和任意锁的组合都不兼容。

image.png

InnoDB中行级锁是怎么实现的?锁的粒度?锁的升级?

InnoDB中行级锁是怎么实现的?

InnoDB存储引擎使用位图来管理锁状态,这种锁管理方式被称为"Record Locks"。在InnoDB中,每个页都有一个对应的锁定信息,称为“锁定字”,它包含了对页中每个记录的锁定状态。当一个事务需要锁定某个记录时,InnoDB就会将该记录所在的页的锁定字读入内存,并在内存中对该记录的锁定状态进行修改,然后将修改后的锁定字写回磁盘。 举个例子,假设有一个包含1000条记录的表,现在有两个事务T1和T2需要对其中的记录进行操作。如果T1需要对表中的100条记录进行排它锁定,而T2需要对其中的200条记录进行排它锁定,那么InnoDB存储引擎会将表中对应的页锁定,并使用位图来管理锁状态。 假设表中的记录按照以下方式分布在不同的页中:

plaintextCopy code
第1页:1, 2, 3, ..., 100
第2页:101, 102, 103, ..., 200
第3页:201, 202, 203, ..., 300
...
第10页:901, 902, 903, ..., 1000

当T1需要锁定第1页中的1-100条记录时,InnoDB会将第1页的锁定字读入内存,并在内存中修改第1页中1-100条记录的锁定状态。假设第1页的锁定字为"1010101010",其中1表示该记录已经被锁定,0表示该记录未被锁定。在T1锁定第1页中的记录时,InnoDB会将该锁定字修改为"1111111111"。此时,T2需要锁定第2页中的101-200条记录,InnoDB会将第2页的锁定字读入内存,并在内存中修改第2页中101-200条记录的锁定状态。假设第2页的锁定字为"0101010101",在T2锁定第2页中的记录时,InnoDB会将该锁定字修改为"1111111111"。 这样,InnoDB通过位图来管理锁状态,避免了按照每个记录来产生行锁的开销,提高了系统的并发性能。

锁的粒度,意向锁?

InnoDB支持多粒度锁机制,即允许行级锁和表级锁共存

意向锁是一种表级锁,用于表示事务将来可能要在某个数据行上加共享锁或排它锁

意向锁是一种不与行级锁冲突的表级锁,它可以提高并发性能,避免不必要的表扫描。

当事务想要在某个数据行上加共享锁或排它锁时,必须先在表上加相应的意向锁(意向共享锁或意向排它锁),然后才能在数据行上加实际的行级锁。

当事务想要在整个表上加共享锁或排它锁时必须等待所有已经存在的意向排它锁释放,然后才能在表上加实际的表级锁。

锁的升级?

锁升级(Lock Escalation)是指将当前锁的粒度降低。 举例来说,数据库可以把一个表的1000个行锁升级为一个页锁,或者将页锁升级为表锁。

InnoDB存储引擎不存在锁升级的问题。因为其不是根据每个记录来产生行锁的,相反,其根据每个事务访问的每个页对锁进行管理的,采用的是位图的方式。因此不管一个事务锁住页中一个记录还是多个记录,其开销通常都是一致的。

锁的算法

InnoDB存储引擎有3种行锁的算法,其分别是:

  • Record Lock:单个行记录上的锁。
  • Gap Lock:间隙锁,锁定一个范围,但不包含记录本身。
  • Next-Key Lock:Gap Lock+Record Lock,锁定一个范围,并且锁定记录本身。

InnoDB存储引擎有3种行锁的算法,间隙锁(Gap Lock)是其中之一。间隙锁用于锁定一个范围,但不包含记录本身。它的作用是为了阻止多个事务将记录插入到同一范围内,而这会导致幻读问题的产生。

Record Lock总是会去锁住索引记录,如果InnoDB存储引擎表在建立的时候没有设置任何一个索引,那么这时InnoDB存储引擎会使用隐式的主键来进行锁定。Next-Key Lock是结合了Gap Lock和Record Lock的一种锁定算法,在Next-Key Lock算法下,InnoDB对于行的查询都是采用这种锁定算法。采用Next-Key Lock的锁定技术称为Next-Key Locking,其设计的目的是为了解决Phantom Problem(幻读)。

幻读和脏读,举个例子说明他们的区别

幻读和脏读都是并发控制中的问题,但它们的原因和表现形式有所不同。

幻读是指在同一个事务中,第二次查询时出现了新增或删除的记录,导致第二次查询返回的结果和第一次查询不一致。幻读通常发生在读取范围内的数据时,比如使用SELECT … WHERE语句查询某个范围内的数据,如果该范围内的数据在事务执行期间发生了变化,就可能出现幻读。

脏读是指在一个事务中读取到了另一个未提交的事务中的修改,导致读取到的数据与实际数据不一致。脏读通常发生在一个事务在修改数据时,另一个事务同时读取了未提交的修改结果。 举个例子,假设有一个表T,其中有两条记录:

plaintextCopy code
id  | name
----|-----
 1  |  Tom
 2  |  Jerry

如果事务T1执行以下操作:

plaintextCopy code
BEGIN;
SELECT * FROM T;

此时T1会读取到表T中的全部记录,即Tom和Jerry。 如果在T1执行SELECT语句期间,事务T2执行以下操作:

plaintextCopy code
BEGIN;
INSERT INTO T VALUES (3, 'Lucy');
COMMIT;

此时T2向表T中插入了一条记录。 如果此时T1再次执行以下操作:

plaintextCopy code
SELECT * FROM T;
COMMIT;

此时T1会发现表T中出现了一条新增的记录,即Lucy,这就是幻读的表现形式。如果在T1执行第二次SELECT语句之前,T2进行了ROLLBACK操作,那么T1只会读取到之前的两条记录,不会出现幻读的问题。

如果在上述例子中,T2执行INSERT操作后不进行COMMIT,而是在T1执行第二次SELECT操作之后进行ROLLBACK操作,那么T1就会读取到未提交的修改结果,即Lucy记录,这就是脏读的表现形式。

InnoDB死锁在什么情况下出现

死锁的情况通常是在多个事务并发操作时出现的,当一个事务持有某个资源的锁而另外一个事务也需要获得该资源的锁时,就可能会发生死锁。如果两个或多个事务相互等待对方释放锁资源,就会形成死锁。这种情况下,所有事务都无法继续执行,系统会陷入僵局,只能通过人工干预或者自动死锁检测和回滚来解除死锁。

举个例子,假设有两个事务T1和T2,T1需要对表A中的某一行数据进行排它锁定,T2需要对表B中的某一行数据进行排它锁定。如果T1持有了表A的排它锁且T2持有了表B的排它锁,但是T1又想要对表B的同一行数据进行排它锁定,而T2又想要对表A的某一行数据进行排它锁定,那么就会形成死锁。此时,T1在等待T2释放表B的锁,而T2在等待T1释放表A的锁,两个事务互相等待,导致系统无法继续执行。如果不进行干预,这个死锁会一直存在,直到有人为干预或者系统自动检测到死锁并回滚其中一个事务为止。

InnoDB死锁的解决方法

InnoDB中的死锁检测机制是基于wait-for graph(等待图)实现的。当一个事务被阻塞时,InnoDB会检查等待图来判断是否发生了死锁。如果发现了死锁,InnoDB会自动回滚其中一个事务(通常来说InnoDB存储引擎选择回滚undo量最小的事务),以解除死锁。

等待图是一个有向图,图中的节点表示事务,边表示锁依赖关系。如果事务T1请求锁定了事务T2持有的锁,那么就会在图中从T1到T2连一条有向边。如果图中存在一个环路,那么就表示发生了死锁

猜你喜欢

转载自blog.csdn.net/weixin_68930048/article/details/130113745
21
21)
今日推荐