Innodb锁

Innodb锁

Posted by CaiJiahe on August 14, 2017

0x01 用途

对共享资源进行并发访问的时候,为了保证数据安全,使用的一种互斥方法。

0x02 latch

innodb内部使用的锁,用来保证并发线程操作临界资源的正确性,并且通常没有死锁检测机制。

	show engine innodb mutex;

count:mutex被请求的次数。
spin_waits:自旋锁的次数。
spin_rounds:自旋内部循环的总次数,spin_rounds/spin_waits表示平均每次自选所需的内部循环次数。
os_waits:表示操作系统等待的次数。
os_yields:进行os_thread_yield的次数。
os_wait_time:操作系统等待的时间,单位ms。

0x03 lock

面向的是事务,用来锁定数据库中的对象(表、页、行),有死锁检测机制。

共享锁:允许事务读取数据。
排他锁:允许事务更新或删除数据。

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

innodb支持多粒度锁定,也就是支持意向锁。如果对某行记录进行加锁,需要先对其所在的表、页加上意向锁,才能对该行记录加共享/独占锁。

意向共享锁:事务想获得一张表中若干行的共享锁。
意向排它锁:事务想获得一张表中若干行的排他锁。

IS IX S X
IS 兼容 兼容 兼容 不兼容
IX 兼容 兼容 不兼容 不兼容
S 兼容 不兼容 兼容 不兼容
X 不兼容 不兼容 不兼容 不兼容
	show engine innodb status\G;

可以使用上面的sql语句查询锁请求的信息。

	select * from information_schema.INNODB_TRX\G;

查询结果的字段意义如下:
trx_id:事务的唯一id。
trx_state:当前事务的状态(RUNNING、LOCK WAIT)。
trx_started:事务的开始时间。
trx_requested_lock_id:事务等待锁的id。
trx_wait_started:事务等待开始的时间。
trx_weight:事务的权重(反应一个事务修改和锁住的行数)。
trx_mysql_thread_id:mysql中的线程id。
trx_query:事务运行的sql语句。

	select * from information_schema.INNODB_LOCKS\G;

查询结果的字段意义如下:
lock_id:锁的id。
lock_trx_id:事务id。
lock_mode:锁的模式(S、X)。
lock_type:锁的类型(表锁、行锁)。
lock_table:要锁住的表。
lock_index:锁住的索引。
lock_space:锁对象的space id。
lock_page:锁住的页数(表锁为NULL)。
lock_rec:锁住的记录数(表锁为NULL)。
lock_data:锁住的主键值(表锁为NULL)。

	select * from information_schema.INNODB_LOCK_WAITS\G;

查询结果的字段意义如下:
requesting_trx_id:申请锁资源的事务的id。
requested_lock_id:申请锁的id。
blocking_trx_id:阻塞的事务id。
blocking_lock_id:阻塞的锁。

一致性非锁定读

innodb通过MVCC的方式来读取当前执行数据库中的数据,不需要加锁,并发性很强,读取undo段来完成。READ COMMITED和REPEATABLE READ默认采用一致性非锁定读。
但是对于快照数据,在READ COMMITTED事务隔离级别下,一致性非锁定读总是读取锁定行的最新的一份快照数据。在REPEATABLE READ事务隔离级别下,一致性非锁定读总是读取到事务开始时的行数据版本。

一致性锁定读

有些逻辑需要读取的时候对数据行进行加锁。拿锁只能在事务中,事务提交锁才会释放。

	select ... for update; // 拿X锁
	select ... lock in share mode; // 拿S锁

自增长与锁

对于自增的主键,每张表里会存储一个自增长计数器,可以使用如下语句查看。

	select max(auto_inc_col) from t for update;

innodb会使用AUTO-INC Locking来实现,是一种特殊的表锁机制,为了提高性能,这个锁不是在事务结束释放,而是在完成对自增长值的sql插入后立即释放这个锁。而且自增长的键值如果是索引,必须是索引的第一列,否则会报错。
mysql 5.1.22后innodb提供一种轻量级互斥量的自增长实现机制,可以通过innodb_autoinc_lock_mode来控制自增长的模式,默认值是1。

插入类型

插入类型 说明
insert-like 所有的插入语句
simple inserts 预先能知道插入行数的语句
bulk inserts 在插入前不能确定插入行数的语句
mixed-mode inserts 插入中有一部分的值是自增长的,有一部分是确定的。

innodb_autoinc_lock_mode说明

说明
0 mysql 5.1.22之前的方式,采用表锁的auto-inc locking的方式。
1 默认方式,simple inserts会使用互斥量对内存中计数器进行累加,对于bulk inserts还是采用auto-inc locking的方式。已经使用auto-inc locking的simple inserts只能使用auto-inc locking。
2 对于所有的insert like都使用互斥量。(stmt replication会出问题,必须使用row-base replication)

阻塞

innodb_lock_wait_timeout:控制超时等待的时间,默认50秒,可动态调整。
innodb_rollback_on_timeout:超时事务是否回滚,默认OFF,不可动态调整。

锁的记录

innodb中的锁是以位图的方式存储在每个页上的,所以对同一个页来讲,锁一条记录和多条记录的开销是一致的。

锁算法

Record Lock:行锁,锁定自身。
Gap Lock:间隙锁,锁定一个范围,并不包含记录本身。
Next-Key Lock:Gap Lock + Record Lock。

唯一索引使用Record Lock(对于INSERT操作来说,若发生唯一约束冲突,则需要对冲突的唯一索引加上S Next-key Lock。从这里会发现,即使是RC事务隔离级别,也同样会存在Next-Key Lock锁,从而阻塞并发)。但是对辅助索引,innodb使用Next-Key Lock来锁住一个范围,这样可以防止Phantom Problem(幻读,举个例子,事务开始时读取了id=3的数据,一共两条,如果不加Next-Key Lock锁,可能锁住了这两条数据,但是并发插入了一条id=3的数据,下次再查询的时候就可能读取出3条数据。)。在RC读取3条新数据没毛病,但是在RR中就不行了,RR要求只能读取事务开始时的数据版本。所以Next-Key Lock用来实现RR。

关闭Gap Lock

1、将隔离级别设置为READ COMMITTED。
2、将innodb_locks_unsafe_for_binlog设置为1。

锁问题

脏读

事务读取到另一个事务未提交的数据。发生于READ UNCOMMITTED隔离级别中。

不可重复读

在一个事务内多次读取一行数据,得到的数据可能是不一致的。发生于READ COMMITTED隔离级别中。和脏读的区别是读取到的数据是其他事务已提交的,可以使用Next-Key Lock来避免不可重复读的问题。

丢失更新

一个事务的数据可能被另一个事务的更新操作覆盖。(类似缓存更新的套路)

0x04 拓展阅读

Innodb中的事务隔离级别和锁的关系-美团点评
MySQL 加锁处理分析-何登成
MySQL REPLACE死锁问题深入剖析-姜成尧