MySQL的innoDB锁机制以及死锁处理

MySQL的nnoDB锁机制

InnoDB与MyISAM的最大不同有两点:一是支持事务(TRANSACTION);二是采用了行级锁。行级锁与表级锁本来就有许多不同之处,innodb正常的select ID from table where id=1;不会上任何锁,接下来详细讨论InnoDB的锁问题;

一:InnoDB行锁的介绍。

共享锁(S):允许一个事务去读一行,阻止其他事务获得相同数据集的排他锁,也就是我读取的行,你不能修改;

排他锁(X):允许获得排他锁的事务更新数据,阻止其他事务取得相同数据集的共享读锁和排他写锁。也就是我更新的行,不允许其他的事务读取和更新相同的行;

另外,为了允许行锁和表锁共存,实现多粒度锁机制,InnoDB还有两种内部使用的意向锁(Intention Locks),这两种意向锁都是表锁。

意向共享锁(IS):事务打算给数据行加行共享锁,事务在给一个数据行加共享锁前必须先取得该表的IS锁。

意向排他锁(IX):事务打算给数据行加行排他锁,事务在给一个数据行加排他锁前必须先取得该表的IX锁。

意向锁是InnoDB自动加的,不需用户干预。对于UPDATE、DELETE和INSERT语句,InnoDB会自动给涉及数据集加排他锁(X);对于普通SELECT语句,InnoDB不会加任何锁;事务可以通过以下语句显示给记录集加共享锁或排他锁。

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

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

InnoDB行锁模式兼容性列表:

MySQL的innoDB锁机制以及死锁处理

如果一个事务请求的锁模式与当前的锁兼容,InnoDB就将请求的锁授予该事务;反之,如果两者不兼容,该事务就要等待锁释放。

二:关于innodb锁机制,实现原理:

InnoDB行锁是通过给索引上的索引项加锁来实现的,这一点MySQL与Oracle不同,后者是通过在数据块中对相应数据行加锁来实现的。InnoDB这种行锁实现特点意味着:只有通过索引条件检索数据,InnoDB才使用行级锁,否则,InnoDB将使用表锁! 索引分为主键索引和二级索引两种,如果一条sql语句操作了主键索引,MySQL就会锁定这条主键索引;如果一条语句操作了二级索引,MySQL会先锁定该二级索引,再锁定相关的主键索引。

然后innodb行锁分为三种情形:

1)Record lock :对索引项加锁,即锁定一条记录。

2)Gap lock:对索引项之间的‘间隙’、对第一条记录前的间隙或最后一条记录后的间隙加锁,即锁定一个范围的记录,不包含记录本身

3)Next-key Lock:锁定一个范围的记录并包含记录本身(上面两者的结合)。

注意:InnoDB默认级别是repeatable-read级别,所以下面说的都是在RR级别中的。

Next-Key Lock是行锁与间隙锁的组合,这样,当InnoDB扫描索引记录的时候,会首先对选中的索引记录加上行锁(Record Lock),再对索引记录两边的间隙加上间隙锁(Gap Lock)。如果一个间隙被事务T1加了锁,其它事务是不能在这个间隙插入记录的

举例1:

假设我们有一张表:

+----+------+

| id | age |

+----+------+

| 1 | 3 |

| 2 | 6 |

| 3 | 9 |

+----+------+

表结构如下:

CREATE TABLE `liuhe` (

`id` int(11) NOT NULL AUTO_INCREMENT,

`age` int(11) DEFAULT NULL,

PRIMARY KEY (`id`),

KEY `keyname` (`age`)

) ENGINE=InnoDB AUTO_INCREMENT=302 DEFAULT CHARSET=gbk ;

这样我们age段的索引就分为

(negative infinity, 3],

(3,6],

(6,9],

(9,positive infinity);

我们来看一下几种情况:

1)当事务A执行以下语句:

mysql> select * from liuhe where age=6 for update ;

不仅使用行锁锁住了相应的数据行,同时也在两边的区间,(3,6]和(6,9] 都加入了gap锁。

这样事务B就无法在这两个区间insert进新数据,同时也不允许 update liuhe set age=5 where id=1(因为这也类似于在(3,6]范围新增),但是事务B可以在两个区间外的区间插入数据。

实验如下:

事务A:

mysql> set autocommit=0;

Query OK, 0 rows affected (0.00 sec)

mysql> select * from liuhe ;(age上有索引)

+----+------+

| id | age |

+----+------+

| 1 | 3 |

| 2 | 6 |

| 3 | 9 |

+----+------+

4 rows in set (0.00 sec)

mysql> select * from liuhe where age=6 for update ;

+----+------+

| id | age |

+----+------+

| 2 | 6 |

+----+------+

1 row in set (0.00 sec)

事务B,尝试insert age=5的数据, 确实有锁等待,说明确实(3,6]上区间锁,防止在这个区间插入;

mysql> insert into liuhe (id,age) values (5,5);

查看事务状态,发现确实是等待;

mysql> select * from INNODB_TRX\G;

*************************** 1. row ***************************

trx_id: 27162

trx_state:LOCK WAIT

trx_started: 2018-04-06 00:03:39

trx_requested_lock_id: 27162:529:4:3

trx_wait_started: 2018-04-06 00:03:39

trx_weight: 3

trx_mysql_thread_id: 46

trx_query: insert into liuhe (id,age) values (5,5)

trx_operation_state: inserting

trx_tables_in_use: 1

trx_tables_locked: 1

trx_lock_structs: 2

trx_lock_memory_bytes: 360

trx_rows_locked: 1

trx_rows_modified: 1

trx_concurrency_tickets: 0

trx_isolation_level: REPEATABLE READ

trx_unique_checks: 1

trx_foreign_key_checks: 1

trx_last_foreign_key_error: NULL

trx_adaptive_hash_latched: 0

trx_adaptive_hash_timeout: 10000

trx_is_read_only: 0

trx_autocommit_non_locking: 0

如上说明:(3,6]和(6,9] 都加入了gap锁。这样事务B就无法在这两个区间insert进新数据,但是事务B可以在两个区间外的区间插入数据

2)当事务A执行如下语句:

select * from fenye where age=7 for update ;

那么就会给(6,9]这个区间加锁,别的事务无法在此区间插入或更新数据。

3)当事务A执行:

select * from fenye where age=100 for update ;

内容版权声明:除非注明,否则皆为本站原创文章。

转载注明出处:https://www.heiqu.com/2ebc27f633bb4a9889eac9ae72f10830.html