mysql原理(九) 锁,你更新一条记录真的不会造成死锁吗?

锁是数据库区别于文件系统的一个关键特性。锁机制用于管理共享资源的并发访问。InnoDB除了在表上面进行上锁之外,在其他层面也会进行上锁,如操作缓冲池当中的LRU列表,删除、添加和移动都需要有锁的介入。本文只讨论在InnoDB中的锁。

一、InnoDB存储引擎中的锁

1.1 锁的类型

1)共享锁(s lock):允许多个事务读一行数据。
2)排它锁(x lock):允许一个事务修改或删除数据。

锁的兼容锁的不兼容

X S
X 不兼容 不兼容
S 不兼容 兼容

例:
T1获取了行r的共享锁,T2仍然可以获取r的共享锁,这称为锁的兼容
如果此时T3想要获取r的排他锁,则需要等到T1和T2释放r的共享锁之后,这称为锁的不兼容

InnoBD允许行级锁和表级锁同时存在,为了支持在不同粒度上执行加锁操作,支持了额外的锁方式,称之为意向锁(Intention Lock)。InnoDB设计的意向锁比较简单,因为其支持行级锁,所以意向锁仅设计在表锁上。其主要目的是为了揭示下一行将要被请求的锁类型。
1)意向共享锁(IS Lock)想要获得一张表中某几行的共享锁。
2)意向排它锁(IX Lock)想要获得一张表中某几行的排它锁。

意向锁不会阻塞除全表扫描以外的请求。

在information_schema当中有以下三张表可以让我们分析锁的情况:INNODB_TRX、INNODB_LOCKS、INNODB_LOCK_WAITS

INNODB_TRX
首先查看INNODB_TRX中的主要段含义:

字段名 说明
trx_id 唯一事务id
trx_state 事务状态
trx_started 事务开始时间
trx_requested_lock_id 等待事务的锁id,如trx_wait为lock_wait的时候;如果不是trx_wait,则是null
trx_wait_started 事务等待开始时间
trx_weight 权重,该值反映修改和锁住的行数。当发生死锁需要回滚时,会回滚该值最小的记录
trx_mysql_thread_id mysql中的线程id
trx_query 事务运行的sql语句

INNODB_LOCKS
首先查看INNODB_LOCKS中的主要段含义:
|字段名|说明|
|lock_id|锁的id|
|lock_trx_id|事务的id|
|lock_mode|锁模式|
|lock_type|锁的类型,表锁和行锁|
|lock_table|要加锁的表|
|lock_index|锁住的索引|
|lock_space|锁对象的space id|
|lock_page|事务锁定页的数量。表锁该值为null|
|lock_rec|事务锁定记录的数量,表锁该值为null|
|lock_data|事务锁定记录的主键值,表锁该值为null|

INNODB_LOCK_WAITS
首先查看INNODB_LOCK_WAITS中的主要段含义:

字段名 说明
requesting_trx_id 申请锁资源的事务id
requested_lock_id 申请锁的id
blocking_trx_id 阻塞的事务id
blocking_lock_id 阻塞锁的id

1.2 一致性非锁定读

一致性非锁定读是指InnoDB通过多版本并发控制MVCC(Multi-Version Concurrency Control)来读取当前执行时间数据库中行的数据。

如果一行或多行数据正在进行update或者delete操作,这时候另外的事务去读取这些数据并不会等待其释放锁,可以直接进行读取。读取的是其快照数据。

之所以称之为一致性非锁定读,因为不需要等到行上面的X锁进行释放即可进行读取。如下图所示:

快照数据是指行之前版本的数据,通过undo log实现。

如上图所示,一个行的版本可能有多个,一般称之为多版本技术,由此带来的并发控制称之为多版本并发控制(MVCC)

在事务隔离级别read commited(oracle默认)和repeatable read(innoDB的默认隔离级别)中,使用非锁定一致性读,然而其对于快照读的定义却不相同。

隔离级别 读取版本
rc 读取最新版本数据
rr 读取最开始版本数据

下面看个例子:

时间 会话1 会话2
1 begin
2 select * from student where id = 1
3 begin
4 update student set id =3 where id = 1
5 select * from student where id = 1
6 commit
7 select * from student where id = 1
8 commit

如上表格所示:
在rr级别下,会话1读取到的结果分别是:1,1,1
在rc级别下,会话1读取到的结果分别是:1,1,empty

1.3 一致性锁定读

在某些特定情况下,用户需要显示的对数据库读取进行加锁操作以保证数据的一致性。InnoDB对select支持两种一致性锁定读:

  1. select * from table where id = X for update 对数据加X锁,其他事物不能对该行数据加任何锁。
  2. select * from table where id = X lock in share model 对数据加S锁,其他事务可以对其加S锁,但是加X锁会被阻塞。

以上加锁操作必须保证在一个事务当中,一旦事务提交后,锁即被释放掉。所以使用时不需使用begin、start transaction、set autocommit = 0。

1.4 自增长和锁

自增长是一种常见的使用方式,也是数据主键的首选。InnoDB对于每一个含有自增长属性的表都维护一个自增长计数器。会依据该计数器的值自动加1赋予自增长列。这种方式称作Auto Inc Locking,这是一种特殊的表锁机制,而这种方式存在一定的性能问题。

在mysql 5.1.22开始,提供了一种轻量级互斥量的自增长方式,这种机制大大提高了自增长值插入的性能。并且提供一个innodb_autoinc_lock_mode来控制自增长,默认为1。另外还有0和2两种方式。

0:并发性能不好,在5.1.22版本前使用。
1:默认值。
2:性能最高,并发插入会带来一定的问题,导致不连续。

二、锁的算法

2.1 三种行锁的算法:

1)Record Lock:单条记录锁。
2)Gap Lock:区间锁或间隙锁,锁定一个范围但不包含本身。
3)Next-key Lock:临键,Record Lock + Gap Lock,锁定范围区间的同时并且锁定记录本身。

如下索引值有1,5,9,11,name在三种锁的不同区间如下所示:

Next-key Lock算法是结合了record lock 和gap lock,其出现的原因是为了解决幻读(snapshot read),利用这种技术,锁定的不再是单个值,而是一个范围。

如果查询的索引含有唯一索引时,Next-key Lock将会降级为Record lock,仅锁住索引本身。

如果查询的索引是辅助索引,使用Next-key Lock进行加锁。按照上图给出下面一个sql,key是辅助索引,本例子不写唯一索引了:

select * from table where key = 5 for update;添加了X锁

那么会对5这个值增加(1,5]的前区间,还会使用gap lock对其增加一个后区间(5,9),因此,运行一下sql都会被阻塞:

select * from table where key = 6 lock in share mode;无法对X锁添加S锁
insert into table select 2;2在锁定区间,被阻塞
insert into table select 7;7在锁定区间,被阻塞

Gap Lock的作用是为了解决幻读问题,只在rr级别存在,如果想要关闭可以修改默认隔离界别为rc,但是这违反了隔离性。

在默认的隔离级别repeatable read下,Innodb使用next-key lock来解决幻读问题。幻读问题是指在同一事务的两次查询中,可能得到不同的结果,即第二次可能查询到之前不存在的行。

發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章