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';
无索引,表锁。
共享锁(Share Locks,记为S锁),用于表级,读取数据时加S锁。
排他锁(eXclusive Locks,记为X锁),用于行级,修改数据时加X锁。
共享锁之间不互斥,简记为:读读可以并行
排他锁与任何锁互斥,简记为:写读,写写不可以并行
select * from t where ... lock in share mode #共享锁
select * from t where ... for update #排他锁
可以看到,一旦写数据的任务没有完成,数据是不能被其他任务读取的,这对并发度有较大的影响。对应到数据库,可以理解为,写事务没有提交,读相关数据的 select 也会被阻塞。
InnoDB 支持多粒度锁(multiple granularity locking),它允许“行级锁”与“表级锁”共存,实际应用中,InnoDB 使用的是意向锁,意向锁是 InnoDB 自动加的,不需用户干预。
“意向锁”是指,未来的某个时刻,事务可能要加“共享/排它锁”了,先提前声明一个意向。
意向共享锁(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的与任何锁都不兼容
记录锁,它封锁索引记录,例如:
select * from t where id = 1 for update;
它会在 id=1 的索引记录上加锁,以阻止其他事务插入,更新,删除id=1的这一行。
需要说明的是:
select * from t where id = 1;
则是快照读(SnapShot Read),它并不加锁。
间隙锁,它封锁索引记录中的间隔,或者第一条索引记录之前的范围,又或者最后一条索引记录之后的范围,。
这个SQL语句
select * from t where id between 8 and 15 for update;
会封锁区间,以阻止其他事务 id=10 的记录插入。
如果把事务的隔离级别降级为读提交(Read Committed, RC),间隙锁则会自动失效。
临键锁,是“记录锁”与“间隙锁”的组合,它的封锁范围,既包含索引记录,又包含索引区间。
如果把事务的隔离级别降级为读提交(Read Committed, RC),临键锁也会失效。
对已有数据行的修改与删除,必须加强互斥锁X锁,而对于数据的插入,则使用“插入意向锁”。
插入意向锁,是间隙锁(Gap Locks)的一种(所以,也是实施在索引上的),它是专门针对 insert 操作的。多个事务,在同一个索引,同一个范围区间插入记录时,如果插入的位置不冲突,不会阻塞彼此。
“自增锁”是一种特殊的表级别锁(table-level lock),专门针对事务插入 AUTO_INCREMENT 类型的列。
最简单的情况,如果一个事务正在往表中插入记录,所有其他事务的插入必须等待,以便第一个事务插入的行,是连续的主键值。
1、InnoDB 使用共享锁,可以提高读读并发;
2、为了保证数据强一致,InnoDB 使用强互斥锁,保证同一行记录修改与删除的串行性;
3、InnoDB 使用插入意向锁,可以提高插入并发;
(1) 在读未提交(Read Uncommitted),读提交(Read Committed, RC),可重复读(Repeated Read, RR)这三种事务隔离级别下,普通select使用快照读(snpashot read),不加锁,并发非常高;
(2) 在串行化(Serializable)这种事务的隔离级别下,普通 select 会升级为 select … in share mode;
加锁select主要是指:
select ... for update
select ... in share mode
(1) 如果,在唯一索引(unique index)上使用唯一的查询条件(unique search condition),会使用记录锁(record lock),而不会封锁记录之间的间隔,即不会使用间“隙锁”与“临键锁”;
(2) 其他的查询条件和索引条件,InnoDB 会封锁被扫描的索引范围,并使用“间隙锁”与“临键锁”,避免索引范围区间插入记录;
(1) 和加锁 select 类似,如果在唯一索引上使用唯一的查询条件来 update/delete,例如:
update t set name = xxx where id = 1;
也只加记录锁;
(2) 否则,符合查询条件的索引记录之前,都会加排他“临键锁”,来封锁索引记录与之前的区间;
(3) 尤其需要特殊说明的是,如果 update 的是“聚集索引”记录,则对应的“普通索引”记录也会被隐式加锁,这是由 InnoDB 索引的实现机制决定的:普通索引存储PK的值,检索普通索引本质上要二次扫描聚集索引。
同样是写操作,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;