三种锁问题
1.丢失修改:火车票问题,让第一个人丢失了修改
2.不可重复读(幻象):第二次读会读到不正确的数据(一个事务读到另一个已提交的事务)
3.读“脏”数据:A修改值后又回滚,B读到修改后的值,即脏数据;或者是一个事务读到了另一个事务未提交的数据
阻塞
定义:一个事务等待另一个事务开锁(释放占用的资源)
控制等待的时间:set @@nnodb_lock_wait_timeout=60;
设定事务在等待超时时是否回滚:set @@innodb_rollback_on_timeout=on;
(默认是off,代表不回滚)
死锁
定义:A等B开锁,B等A开锁,两个在互相等待
解决方法:1、超时后对事务回滚,参数innodb_lock_wait_timeout设置超时的时间
2、用wait-for graph进行死锁检测
解决三种问题的方案
1、读操作使用多版本并发控制**(MVCC)**,写操作加锁:生成readview,查询语句只能读生成readview之前已经提交的更改(read committed和repeatable read就是用这个方法),mvcc也称为一致性非锁定读
2、读、写操作都加锁
隔离级别
1、read uncommitted:脏读,不可重复读,幻读都可能发生
2、read committed:不可重复读,幻读可能发生,脏读不可能发生
3、repeatable read:幻读可能发生,脏读和不可重复读不可能发生
4、serializable:上述现象都不可能发生
(以上都是不加锁的情况)
锁定读
1、共享锁:S锁 select…lock in share mode(对读取的数据加S锁)
2、独占锁:X锁 select…for update(对读取的数据加X锁)
S锁和S锁是兼容的,其他都不兼容,会阻塞
加锁语句只有这两三句,但是根据事务的隔离级别,索引类型等条件会产生不同的锁。在隔离级别不大于read committed(read uncommitted、read committed)会为当前记录加正经记录锁。在隔离级别不小于repeatable read(repeatable read、serializable)会为当前记录加next-key锁
写操作
即delete,update,insert
delete:先在B+树中定位到这条记录的位置,然后获取这条记录的X锁,最后再执行delete mark操作
update:同理
insert:新插入的一条记录受隐式锁保护,不需要在内存中生成对应的锁结构
多粒度锁
分几个层次:库,表,页,行
表加锁就是粒度比较粗,占用资源少
行加锁就是粒度最细的,可以实现更精准的并发控制,但是占用资源大
意向共享锁:IS锁,事务给某条记录加S锁前,要在表级别加个IS锁
意向独占锁:IX锁,事务给某条记录加X锁前,要在表级别加个IX锁
作用:方便判断表中的记录是否有被上锁,避免用遍历的方式查看表中是否有被上锁
IX和S不兼容,X都不兼容
行级锁类型
1、record lock
名称:lock_rec_not_gap
记录锁:把一条记录锁上
即上述的S锁和X锁
2、gap lock
名称:lock_gap
gap锁:不允许别的事务在一条记录前的间隙插入新记录
作用:防止插入幻影记录
3、next_key lock
名称:lock_ordinary
next-key锁:是record锁和gap锁的合体,即保护该条记录,又防止别的事务对被保护记录前的间隙插入记录
4、隐式锁
insert语句是不需要在内存中生成锁结构
解释:一个事务对新插入的记录不加锁,别的事务要对这条记录加锁时,由于有隐式锁的存在,会给当前事务生成一个锁结构,在给另外一个事务生成一个锁结构,然后“另外一个事务”进入等待状态
投机取巧:不用时可以不加,它自己用时自己加
查看事务加锁情况
使用innodb_trx表
select * from information_schema.innodb_trx\G
使用innodb_locks表
select * from information_schema.innodb_locks;
使用innodb_lock_waits表
select * from information_schema.innodb_lock_waits;
使用show engine innodb status