MySQL锁机制

InnoDB锁机制

MySQL 的 InnoDB 存储引擎支持行级锁,InnoDB的行锁是通过给索引项加锁实现的,这就意味着只有通过索引条件检索数据时,InnoDB才使用行锁,否则使用表锁。

例如:

t(uid, uname, age, sex) innodb;

uid PK
无其他索引

update t set age = 10 where uid = 1; 命中索引,行锁。

update t set age = 10 where uid != 1; 未命中索引,表锁。

update t_user set age = 10 where name = 'xxx'; 无索引,表锁。

InnoDB七种锁的概念
  1. 共享/排它锁(Shared and Exclusive Locks)
  2. 意向锁(Intention Locks)
  3. 记录锁(Record Locks)
  4. 间隙锁(Gap Locks)
  5. 临键锁(Next-key Locks)
  6. 插入意向锁(Insert Intention Locks)
  7. 自增锁(Auto-inc Locks)
1、共享/排它锁(Shared and Exclusive Locks)

共享锁(Share Locks,记为S锁),用于表级,读取数据时加S锁。

排他锁(eXclusive Locks,记为X锁),用于行级,修改数据时加X锁。

共享锁之间不互斥,简记为:读读可以并行
排他锁与任何锁互斥,简记为:写读,写写不可以并行

select * from t where ... lock in share mode  #共享锁
select * from t where ... for update          #排他锁

可以看到,一旦写数据的任务没有完成,数据是不能被其他任务读取的,这对并发度有较大的影响。对应到数据库,可以理解为,写事务没有提交,读相关数据的 select 也会被阻塞。

2、意向锁(Intention Locks)

InnoDB 支持多粒度锁(multiple granularity locking),它允许“行级锁”与“表级锁”共存,实际应用中,InnoDB 使用的是意向锁,意向锁是 InnoDB 自动加的,不需用户干预。

“意向锁”是指,未来的某个时刻,事务可能要加“共享/排它锁”了,先提前声明一个意向。

  1. 意向锁,是一个表级别的锁(table-level locking);
  2. 意向锁分为:
意向共享锁(intention shared lock, IS),事务打算给数据行加行共享锁,事务在给一个数据行加共享锁前必须先取得该表的IS锁。

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

举个例子:

select ... lock in share mode,要设置IS锁;

select ... for update,要设置IX锁;

兼容性:

请求锁模式
(是否兼容)
当前锁模式
X IX S IS
X 冲突 冲突 冲突 冲突
IX 冲突 兼容 冲突 兼容
S 冲突 冲突 兼容 兼容
IS 冲突 兼容 兼容 兼容

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

简记:

含I的锁与含I的锁是相互兼容的
含I的S锁与不含I的S锁兼容
不含I的锁,含S的与含S的兼容与含X的不兼容
不含I的锁,含X的与任何锁都不兼容
3、记录锁(行锁)(Record Locks):排它锁,解决脏读

记录锁,它封锁索引记录,例如:

select * from t where id = 1 for update;

它会在 id=1 的索引记录上加锁,以阻止其他事务插入,更新,删除id=1的这一行。

需要说明的是:

select * from t where id = 1;

则是快照读(SnapShot Read),它并不加锁。

4、间隙锁(Gap Locks):排它锁,解决不可重复读

间隙锁,它封锁索引记录中的间隔,或者第一条索引记录之前的范围,又或者最后一条索引记录之后的范围,。

这个SQL语句

select * from t where id between 8 and 15 for update;

会封锁区间,以阻止其他事务 id=10 的记录插入。

如果把事务的隔离级别降级为读提交(Read Committed, RC),间隙锁则会自动失效。

5、临键锁(Next-Key Locks):排它锁,解决幻读

临键锁,是“记录锁”与“间隙锁”的组合,它的封锁范围,既包含索引记录,又包含索引区间。

如果把事务的隔离级别降级为读提交(Read Committed, RC),临键锁也会失效。

6、插入意向锁(Insert Intention Locks)

对已有数据行的修改删除,必须加强互斥锁X锁,而对于数据的插入,则使用“插入意向锁”。

插入意向锁,是间隙锁(Gap Locks)的一种(所以,也是实施在索引上的),它是专门针对 insert 操作的。多个事务,在同一个索引,同一个范围区间插入记录时,如果插入的位置不冲突,不会阻塞彼此。

7、自增锁(Auto-inc Locks)

“自增锁”是一种特殊的表级别锁(table-level lock),专门针对事务插入 AUTO_INCREMENT 类型的列。

最简单的情况,如果一个事务正在往表中插入记录,所有其他事务的插入必须等待,以便第一个事务插入的行,是连续的主键值。

总结

1、InnoDB 使用共享锁,可以提高读读并发;

2、为了保证数据强一致,InnoDB 使用强互斥锁,保证同一行记录修改与删除的串行性;

3、InnoDB 使用插入意向锁,可以提高插入并发;

SQL相关场景下的锁

1、普通select

(1) 在读未提交(Read Uncommitted),读提交(Read Committed, RC),可重复读(Repeated Read, RR)这三种事务隔离级别下,普通select使用快照读(snpashot read),不加锁,并发非常高;

(2) 在串行化(Serializable)这种事务的隔离级别下,普通 select 会升级为 select … in share mode;

2、加锁select

加锁select主要是指:

select ... for update

select ... in share mode

(1) 如果,在唯一索引(unique index)上使用唯一的查询条件(unique search condition),会使用记录锁(record lock),而不会封锁记录之间的间隔,即不会使用间“隙锁”与“临键锁”;

(2) 其他的查询条件和索引条件,InnoDB 会封锁被扫描的索引范围,并使用“间隙锁”与“临键锁”,避免索引范围区间插入记录;

3、update与delete

(1) 和加锁 select 类似,如果在唯一索引上使用唯一的查询条件来 update/delete,例如:

update t set name = xxx where id = 1;

也只加记录锁;

(2) 否则,符合查询条件的索引记录之前,都会加排他“临键锁”,来封锁索引记录与之前的区间;

(3) 尤其需要特殊说明的是,如果 update 的是“聚集索引”记录,则对应的“普通索引”记录也会被隐式加锁,这是由 InnoDB 索引的实现机制决定的:普通索引存储PK的值,检索普通索引本质上要二次扫描聚集索引。

4、insert

同样是写操作,insert 和 update 与 delete 不同,它会用排它锁封锁被插入的索引记录,而不会封锁记录之前的范围。

同时,会在插入区间加“插入意向锁”,但这个并不会真正封锁区间,也不会阻止相同区间的不同KEY插入。

解决死锁状态

第一种:
查询是否锁表
show open tables where in_use > 0;

查询进程
show processlist;

杀死进程id
kill `id`;
第二种:
查看下在锁的事务
select * from information_schema.innodb_trx;

杀死进程id
kill `trx_mysql_thread_id`;
其它关于查看死锁的命令
查看当前的事务
select * from information_schema.innodb_trx;

查看当前锁定的事务
select * from information_schema.innodb_locks;

查看当前等锁的事务
select * from information_schema.innodb_lock_waits;

你可能感兴趣的:(MySQL)