InnoDB锁机制

 2023-09-13 阅读 16 评论 0

摘要:2019独角兽企业重金招聘Python工程师标准>>> InnoDB中的锁 数据库系统使用锁是为了支持对共享资源的并发访问,提供数据的完整性和一致性。数据库中的锁主要有lock和latch之分。其中latch成为闩锁(轻量级锁),latch又分为mutex(互

2019独角兽企业重金招聘Python工程师标准>>> hot3.png

InnoDB中的锁

数据库系统使用锁是为了支持对共享资源的并发访问,提供数据的完整性和一致性。数据库中的锁主要有lock和latch之分。其中latch成为闩锁(轻量级锁),latch又分为mutex(互斥量)和rwlock(读写锁),其目的是用来保证并发线程操作共享资源的正确性,通常无死锁检测机制;lock的对象是事务,用来锁定数据库中的对象,如表、页、行,一般在commit或rollback后释放,且通常有死锁检测机制。lock和latch的区别如下:

 

innodb原理?Lock

latch

对象

事务

常见锁机制?线程

保护

数据库内容

内存数据结构

innodb支持行锁和表锁。持续时间

整个事务过程

临界资源

模式

锁 原理,行锁、表锁、意向锁

读写锁、互斥量

死锁

通过waits-for graph、time out等机制进行死锁检测和处理

数据库锁机制,无死锁检测与处理机制。仅通过应用程序加锁的顺序保证无死锁的情况发生

存在于

Lock Manager的哈希表中

每个数据结构的对象中

线程安全与锁机制?查看innoDB中的latch信息:show engine innodb mutex

输出说明:

名称

说明

Redis锁。count

mutex被请求的次数

spin_waits

spin lock(自旋锁)的次数,innoDB在不能获取锁时首先进行自旋,若自旋后仍不能获取锁,则进入等待状态

MySQL 锁?spin_rounds

自旋内部循环的总次数,每次自旋的内部循环是一个随机数。spin_rounds/ spin_waits表示平均每次自旋所需的内部循环次数

os_waits

表示os等待的次数。当spin lock通过自旋还不能获得latch时,则进入os等待状态,等待被唤醒

java锁机制。os_yields

进行os_thread_yield唤醒操作的次数

os_wait_times

os等待的时间,单位ms

查看lock信息:show full processlist、show engine innodb status或查询information_schema下表innodb_trx、innodb_locks、innodb_lock_waits。

lock类型

行级锁

Innodb实现了两种标准的行级锁,共享(S)锁和排他(X)锁:

共享(S)锁:允许事务读一行数据。

排他(X)锁:允许事务删除或更新一行数据。

如果事务T1持有行Row上的一个S锁,那么其他事务仍然可以持有行Row上的S锁,但不能持有行Row上的X锁;如果事务T1持有行Row上的一个X锁,则其他事务不能再持有行Row上的任何锁。也就是说,X锁与任何锁都不兼容,S锁与S锁兼容。S和X都是行锁,因此兼容均指对相同行(Row)锁的兼容情况。

意向锁

InnoDB存储引擎支持多粒度锁定,这种锁定允许事务在行级上的锁定和表级上的锁定同时存在。这种锁称为意向锁,意向锁将锁定的对象分为多个层次。

将加锁的对象看成一棵树,那么对最下层的对象上锁,也即最细粒度的对象,那么首先需对粗粒度的对象上锁。如:若需要对页上的记录Row上X锁,那么分别需要对数据库A、表、页上意向锁IX,最后才对记录Row上X锁。其中任何一个部分导致等待,那么该操作需要等待粗粒度锁的完成。

152227_uCmM_2663573.png

Innodb存储引擎的意向锁设计比较简练,其意向锁为表级别的锁,因此意向锁不会阻塞除全表扫描外的任何请求。主要有两种意向锁:

意向共享锁(IS lock):事务想要获得一张表中某几行的共享锁,必须先获取该表的IS锁。

意向排他锁(IX Lock):事务想要获得一张表中某几行的排他锁,必须先获得该表的IX锁。

Innodb存储引擎中锁的兼容性

 

IS

IX

S

X

IS

兼容

兼容

兼容

不兼容

IX

兼容

兼容

不兼容

不兼容

S

兼容

不兼容

兼容

不兼容

X

不兼容

不兼容

不兼容

不兼容

注意:

IX,IS是表级锁,不会和行级的X,S锁发生冲突,只会和表级的X,S发生冲突。考虑如下场景:

  • SessionA申请IX,同时申请行级X
  • SessionB申请IX,同时申请行级X

由于IX锁与IX锁兼容,因此SessionA和SessionB都能成功申请IX锁,对于行级X锁的申请,取决于锁定的行。如果SessionA和SessionB分别对不同的行申请行级X锁,它们都会同时申请成功,也就是说,IX锁不会和行级的X锁发生冲突,这大大提高了InnoDB的并发写能力。

那么,意向锁的作用是什么呢?

首先我们看看显示添加表锁的方法:

  • LOCK TABLE table_name READ:用读锁锁表,会阻塞其他事务修改表数据
  • LOCK TABLE table_name WRITE:用写锁锁表,会阻塞其他事务读和写

考虑一个问题,当事务A持有行Row的行级S锁时,若事务B想要申请表级写锁,由于表级写锁与事务A持有的S锁是冲突的,因此,事务B只能阻塞直到事务A释放S锁。那么,数据库如何判断这个冲突呢?

  • step1:判断表是否已被其他事务用表锁锁表
  • step2:判断表中的每一行是否已被行锁锁住

注意step2,为了判断表是否具有行级锁,需要遍历整个表,这样的判断方法效率实在不高,于是就有了意向锁。

在意向锁存在的情况下,事务A必须先申请表的意向共享锁,成功后再申请一行的行锁。因此,存在S锁,必然存在IS锁。再看step2,有IS锁的存在,只需要判断表上是否有意向锁即可判断冲突。

意向锁由InnoDB自动添加,不需要用户干预。对于UPDATE、DELETE和INSERT语句,InnoDB会自动给涉及数据集加排他锁(X);对于普通SELECT语句,InnoDB不会加任何锁,对于alter table类型操作,mysql会自动添加表锁。

事务可以通过以下语句显示给记录集加共享锁或排他锁:

共享锁(S):SELECT * FROM table_name WHERE ... LOCK IN SHARE MODE。

排他锁(X):SELECT * FROM table_name WHERE ... FOR UPDATE。

记录锁

记录锁是索引记录上的锁。 例如,SELECT c1 FROM t WHERE c1 = 10 FOR UPDATE; 阻止任何其他事务插入,更新或删除t.c1的值为10的行。

记录锁总是锁定索引记录,当定义表时,若没有指定主键,InnoDB创建一个隐藏的主键索引,并使用此索引进行记录锁定。

间隙锁

间隙锁是在索引记录之间的间隙上的锁定,或在最后一个索引记录之前或之后的间隙上的锁定。 例如:SELECT c1 FROM t WHERE c1 BETWEEN 10和20 FOR UPDATE;

它将阻止其他事务将值15插入到列t.c1中,无论列中是否有任何此类值,因为该范围中所有现有值之间的间隙被锁定。

间隙锁是性能和并发性之间权衡的一部分,并且只在一些事务隔离级别中使用。当使用唯一索引来锁定行时,不会使用间隙锁,当没有使用索引或使用非唯一索引进行锁定行时,才有可能使用间隙锁。

Next-Key锁

Next-Key锁是索引记录上的记录锁和索引记录之间的间隙上的间隙锁的组合。

关于记录锁,间隙锁,Next-Key锁更详细的描述请移步Innodb行锁算法

多版本并发控制(MVCC)

一致性非锁定读是指InnoDB存储引擎通过多版本控制的方法来读取当前执行时间数据库中行的数据。如果读取的行正在执行delete或update操作,这时读取操作不会因此去等待行上锁的释放。InnoDB会选择去读取一个快照数据。

 快照数据其实就是当前行数据之前的历史版本,每行记录可能有多个版本,一般称为行多版本技术,由此带来的并发控制,称之为多版本并发控制(MVCC)。

非锁定读机制大大地提高了数据库的并发性。在InnoDB存储引擎默认设置下,这是默认的读取方式,即读取不会占用和等待表上的锁。但在不同的事务隔离级别下,读取的方式不同。READ COMMITTED和REPEATABLE READ都使用非锁定读机制。区别在于,READ COMMITTED下,对于快照数据,总是读取被锁定行的最新一份快照数据,因此会发生不可重复读;而在REPEATABLE READ下,总是读取事务开始时的行数据版本,因此可能会发生幻读。

若需要显示地对数据库的读取操作进行加锁以保证数据逻辑的一致性,那么可通过以下方式实现:

Select ... for update;

Select  ... lock in share mode;

Select ... for update对读取的行记录加一个X锁,Select  ... lock in share mode则是对读取的行记录加一个S锁。

锁带来的问题

InnoDB使用锁机制来实现事务的隔离性要求,使得事务可以并发地工作。锁提高了并发,但也带来了几种问题:脏读,不可重复读,幻读,丢失更新

脏读

脏数据:指事务对缓冲池中行记录的修改,并且没有被提交。

脏页:指在缓冲池中已经被修改的页,但还没有被刷到磁盘中,即数据库实例内存中的页和磁盘中的页数据不一致。脏页并不影响数据的一致性。

脏读指在不同的事务下,当前事务可以读到另外事务未提交的数据,即读到脏数据。脏读的发生条件是事务的隔离级别为未提交读(read uncommitted)。

不可重复读

不可重复读指在不同的事务下,当前事务可以读到另外事务已经提交的数据,但是这违反了数据库事务的一致性要求。

查询事务隔离级别:select @@tx_isolation;

修改事务隔离级别:

SET [SESSION | GLOBAL] TRANSACTION ISOLATION LEVEL {READ UNCOMMITTED | READ COMMITTED | REPEATABLE READ | SERIALIZABLE}

举例说明:

time

Session A

Session B

1

set session transaction isolation level  READ COMMITTED;

 

2

 

set session transaction isolation level  READ COMMITTED;

3

begin

begin

4

Select * from amg_order

 

5

 

Insert into amg_order(id,brand_id,name,amount,type) values(5,1,’test’,1,1)

6

 

Commit;

7

Select * from amg_order

 

8

Commit;

 

在会话A开始一个事务,第一次读取到的记录不包含id为5的记录;在会话B执行插入一条记录,未提交时A读到的数据仍然不包含id为5的记录。当会话B提交事务后,在会话A中再次查询就能读取到id为5的记录。

幻读

幻读是指当事务不是独立执行时发生的一种现象。幻读保证了同一个事务里,查询的结果都是事务开始时的状态(一致性),但是,如果另一个事务同时更新了数据,当前事务再进行更新时,发现数据已存在。

举例说明:

time

Session A

Session B

1

begin

begin

2

Select * from amg_order where name=’ test’;

-- 1 row in set

 

3

 

Insert into amg_order(id,brand_id,name,amount,type) values(6,1,'test',1,1);

4

Select * from amg_order where name=’ test’;

-- 1 row in set

 

5

 

Commit;

6

Select * from amg_order where name=’ test’;

-- 1 row in set

 

7

 

 

8

Update amg_order set brand_id=10014 where name=’ test’;

--Query OK, 2 rows affected

Rows matched:2 Changed: 2 Warnings: 0

 

9

Commit;

 

在会话A开始一个事务,查询name为test的记录,返回一行;开始事务B,插入id为6,name为test的新纪录,事务B提交前后在事务A中执行查询name为test的记录,都只返回一行,也就是说避免了不可重复读的问题。但是当事务B提交后,事务A修改name为test的记录,结果返回受影响的行为2,也就是说更新时实际读到了事务B的提交,发生了幻读。

疑问:Innodb使用了Next-Key Lock行锁算法来避免幻读,为什么Session A中还是发生了幻读?

Innodb提供的Next-Key Lock算法确实是为了避免幻读,而Session A中也确实发生了幻读。主要原因在于,Innodb使用了MVCC技术来避免读取时加锁,即Session A中select是非加锁读,Next-Key Lock算法自然也没有生效,因此发生了幻读。若想要避免发生幻读,可以显示地对select加锁,使得Next-Key Lock算法生效。

总结:InnoDB在隔离级别REPEATABLE READ下并不保证避免幻读,有时需要应用使用加锁读来保证

丢失更新

丢失更新指一个事务的更新操作会被另一个事务的更新操作所覆盖,从而导致数据不一致。

在当前数据库的任何隔离级别下,都不会导致数据库层面的丢失更新问题。因为DML操作,都会对行或更粗粒度的对象加X锁。导致丢失更新更多表现在应用代码业务逻辑中,如出现下面情况将会导致丢失更新问题:

1         事务T1查询一行数据,并展示给终端用户User1;

2         事务T2也查询该数据,并展示给终端用户User2;

3         User1修改这行记录,更新数据库并提交;

4         User2修改这行记录,更新数据库并提交。

很显然,User2提交后,将会导致User1的更新丢失。主要原因在于User2修改的记录是过期的数据。当然,要解决该问题的方案很多,如:每行记录加版本号,每次修改,都将比较版本号,并将版本号+1;也可以使用select ... for update对查询显示加X锁。

欢迎指出本文有误的地方,转载请注明原文出处https://my.oschina.net/7001/blog/880328

转载于:https://my.oschina.net/7001/blog/880328

版权声明:本站所有资料均为网友推荐收集整理而来,仅供学习和研究交流使用。

原文链接:https://hbdhgg.com/1/55023.html

发表评论:

本站为非赢利网站,部分文章来源或改编自互联网及其他公众平台,主要目的在于分享信息,版权归原作者所有,内容仅供读者参考,如有侵权请联系我们删除!

Copyright © 2022 匯編語言學習筆記 Inc. 保留所有权利。

底部版权信息