MySQL锁
本文非小马原创,为学习总结笔记,作为日后复盘回顾,感谢原作者分享,文末已注明出处,侵删。
1、锁的颗粒
行级锁
表锁
间隙锁
间隙锁是一个在索引记录之间的间隙上的锁。
间隙锁的作用
保证某个间隙内的数据在锁定情况下不会发生任何变化。比如mysql默认隔离级别下的可重复读(RR)。
当使用唯一索引来搜索唯一行的语句时,不需要间隙锁定。如下面语句的id列有唯一索引,此时只会对id值为10的行使用记录锁。
select * from t where id = 10 for update;// 注意:普通查询是快照读,不需要加锁
如果,上面语句中id列没有建立索引或者是非唯一索引时,则语句会产生间隙锁。
2、锁的类型
读锁(共享锁 / S锁) : 一个事务加了s锁后, 其他事务只能读不能写, 其他事务可以再加读锁
select ... from table lock in share mode ;
写锁(排他锁 / X锁 ) : 一个事务加了x锁后, 其他事务只能读不能写,不能再加锁
update ...
insert ...
delete ...
select ... for update ;
无锁 : select 语句直接查完全不受锁影响, 既不触发加锁也不被已有的锁限制。但如果select ... from table lock in share mode 或者select ... for update 就是手动加读锁和写锁了。
select ... from table ;
2.1、读锁总结 :
select ... lock in share mode 加的是行级读锁
多个读锁是可以重复加的
读锁是可读不可写的
事务结束(commit / rollback)后锁自动解除
2.2、写锁总结:
for update 加的是行级写锁
加上写锁后不能再加读锁 / 写锁
写锁是可读不可写的
事务结束(commit / rollback)后锁自动解除

锁冲突结论: 只有读锁之间不冲突
3、sql 语句手动加锁
加读锁 select ... lock in share mode;
加写锁 select ... for update ;
注意 : 如果select 的条件中未使用到索引, for update 将会变成表锁 ; 不仅是select...for update ,update 也是一样。
案例:
转账操作中需要判断余额是否足够
begin;
select balance from account where id=1; ①
(对余额进行判断,余额不做的不更新操作)
update account set balance=balance-100 where id=1; ②
update account set balance=balance+100 where id=2;
commit;
多线程下,会有多个事务并发访问数据库。
假设余额150,事务a可以执行到①判断余额充足,切换线程执行事务b到①并判断余额充足,最后都提交后,id为1的账户余额为-50;所以必须让整个事务操作保持原子性。
修改①为:select balance from account where id=1 for update;对该记录加行锁。当事务a执行完提交释放行锁时事务b才能获得行锁 再查询判断。
4、死锁的排查和解除
小马曾经遇到自建MySQL使用innodb存储引擎的写操作导致死锁问题,排查后杀死进程可以解决,但一旦写操作又会出现死锁,后来直接换成MYISAM后死锁不再出现,至今未知原因。

5、MyISAM 和InnoDB 比较
是否支持行级锁: MyISAM 只有表级锁(table-level locking),而InnoDB 支持行级锁(row-level locking)和表级锁,默认为行级锁。
是否支持外键:MyISAM不支持,而InnoDB支持。
是否支持MVCC:仅InnoDB支持。应对高并发事务, MVCC比单纯的加锁更高效;MVCC只在READ COMMITTED和REPEATABLE READ两个隔离级别下工作;MVCC可以使用乐观(optimistic)锁和悲观(pessimistic)锁来实现;各数据库中MVCC实现并不统一。
参考文献: