哇靠,死锁了之innodb锁解读
Posted 破产DBA
tags:
篇首语:本文由小常识网(cha138.com)小编为大家整理,主要介绍了哇靠,死锁了之innodb锁解读相关的知识,希望对你有一定的参考价值。
为什么要有锁?
如果我回答锁的出现大大提高了数据库的并发性,维护了数据的一致性,你一定会认为这也太套路了,理论性的东西谁都可以照着念,所以我想从另一个角度来解答这个问题。
“如果数据库没有锁会怎么样?”
我们假设一个场景
京东电商场景,最新款iPhoneX以1500元的价格参与秒杀,但参与秒杀的不可能有那么多,只有5部,在秒杀前做足了宣传,总共大概有3000被吸引到了秒杀页面等待最后的时刻。
假设在秒杀的那一刻有1000个网速比较块,动作敏捷的幸运儿提交了订单,那么这1000个幸运儿将会执行:
Update product set stock= stock-1 where id=’1’ and stock>0;
由于没锁,1000个进程连接执行成功,实际上到最后stock仅仅是减了1,后面还有2000个慢一点连接还可以继续。
如果没有锁,京东的这场活动就是一场自残秀了。
(上面之所以没提1000个事务,是因为事务的基础是锁,没有锁也就无法建立事务了)
(本来是想按照以下思维导图的逻辑写,但写了一半才发现,这个主题太大了,单锁的类别,加锁方式,MVCC都可以单独写一篇长文了,能力有限,所以还是着重挑了一些,后续将补充相关主题.)
ACID的需要
数据库事务正确执行的四个基本要素: 原子性(Atomicity)、一致性(Consistency)、隔离性(Isolation)、持久性(Durability)
其中隔离性需要使用锁来实现
INNODB常见的几种锁
共享锁(Share Lock)
也叫S锁,若事务A对表记录1加了共享锁,则其他事务只能加共享锁,不能加排他锁,通俗的讲,别的事务只能读,不能做修改操作。
显示的指定锁定方法:Select … lock in share mode
举例:
事务A对记录id=5添加S锁:
begin;
select * from tb where id=5 lock in share mode;
..
在事务A未提交的情况下事务B也想添加S锁,然后再修改此记录:
begin;
select * from tb where id=5 lock in share mode; --OK,没问题。
update tb set name=’xxx’ where id=5; ---此时无法继续, 必须等到事务A提交或回滚才能执行
排他锁(Exclusive Lock)
也叫X锁,写锁,独占锁,事务对数据A加了X锁后,其他事务不能对数据A加任何锁。
Update,delete,insert都会对相应记录添加X锁;
也可以显示的给指定数据添加X锁:select ... for update
举例:
举例:
事务A对id=5添加排他锁(使用for update或直接执行update、delete语句效果类似):
begin;
select * from tb where id=5 for update;
…
事务B需要对数据做select,delete操作
begin;
select * from tb where id=5 for update; -无法继续,执行lock in share mode也不支持;
…
间隙锁(GAP LOCK)
Gap锁,中文名间隙锁,锁住的不是记录,而是范围,比如:(negative infinity, 10),(10, 11)区间,主要作用是用来防止insert的,从而防止出现幻读,是“可重复读”隔离级别的基础。
实验;
test表结构:(id int 自增主键,code int 普通索引)
包含数据:
现在事务A删除code=4的记录,经过实验我们发现,处理code=4记录本身会添加X锁外,还会对【2,4),(4,6)之间添加gap锁,其中包括前一条记录的边界值2、
NEXT-KEY LOCK
Next-Key Locks = Gap Locks + Record Locks 的结合, 不仅仅锁住记录,还会锁住间隙,比如上个例子中记录code=4的x锁,也有周边的gap锁;
意向锁
意向锁是表级锁,作用是告诉下一个事务,我即将添加什么锁,你就不要排队了。
InnoDB 中的两个表锁:
意向共享锁(IS):事务准备对表中数据加共享锁,提示别的事务此时不要添加表级X锁
意向排他锁(IX):类似于IS,只是即将对表中数据添加X锁。
锁的兼容性
S锁只与S锁兼容,X锁与什么锁都不兼容
IS锁除了X锁外都兼容,IX锁只与IS,IX兼容
隔离级别
常见的4种隔离级别
Read Uncommited
可以读取未提交记录。此隔离级别,不会使用,忽略。
Read Committed (RC)
快照读忽略,本文不考虑。
针对当前读,RC隔离级别保证对读取到的记录加锁 (记录锁),存在幻读现象。
Repeatable Read (RR)
快照读忽略,本文不考虑。
针对当前读,RR隔离级别保证对读取到的记录加锁 (记录锁),同时保证对读取的范围加锁,新的满足查询条件的记录不能够插入 (间隙锁),不存在幻读现象。
Serializable
从MVCC并发控制退化为基于锁的并发控制。不区别快照读与当前读,所有的读操作均为当前读,读加读锁 (S锁),写加写锁 (X锁)。
Serializable隔离级别下,读写冲突,因此并发度急剧下降,在mysql/InnoDB下不建议使用。
隔离级别能解决的问题
脏读:读取到了别的事务未提交的数据
不可重复读:第一次读取数据与第二次读取的不同,因为其他事务对数据做了修改且已提交;
幻读:连续两次读取的数据行数不一样;
分析锁的方法
工欲善其事必先利其器
查看最后一次死锁信息 show innodb engine status;
查看LATEST DETECTED DEADLOCK这一栏的相关信息。
查看正在使用的锁
SELECT r.trx_id waiting_trx_id,
r.trx_query waiting_query,
b.trx_id blocking_trx_id,
b.trx_query blocking_query,
b.trx_mysql_thread_id blocking_thread,
b.trx_started,
b.trx_wait_started
FROM information_schema.innodb_lock_waits w
INNER JOIN information_schema.innodb_trx b
ON b.trx_id = w.blocking_trx_id
INNER JOIN information_schema.innodb_trx r
ON r.trx_id = w.requesting_trx_id
查看事务的隔离级别
show variables like 'tx_isolation';
典型场景下锁解读
select做insert或create table添加S锁
insert into target_tab select * from source_tab where ...
create table new_tab as select ... From source_tab where ...
在RR隔离级别下,会对source_tab上锁,防止出现幻读;RC隔离级别下,不上锁。
以下是来自何登成老师的经典加锁分析
http://hedengcheng.com/?p=771 MySQL加锁处理分析
以下语句怎么加锁?
delete from t1 where id = 10;
这个问题没有答案。如果让我来回答这个问题,我必须还要知道以下的一些前提,前提不同,我能给出的答案也就不同。要回答这个问题,还缺少哪些前提条件?
前提一:id列是不是主键?
前提二:当前系统的隔离级别是什么?
前提三:id列如果不是主键,那么id列上有索引吗?
前提四:id列上如果有二级索引,那么这个索引是唯一索引吗?
前提五:两个SQL的执行计划是什么?索引扫描?全表扫描?
下面来一一分析:
组合一:id列是主键,RC隔离级别
id是主键,Read Committed隔离级别,给定SQL:delete from t1 where id = 10; 只需要将主键上,id = 10的记录加上X锁即可。
组合二:id列是二级唯一索引,RC隔离级别
id不是主键,而是一个Unique的二级索引键值。那么在RC隔离级别下,首先会将unique索引上的id=10索引记录加上X锁,同时还会对对应主键上加X锁
组合三:id列是二级非唯一索引,RC隔离级别
与组合二唯一的区别在于,组合二最多只有一个满足等值查询的记录,而组合三会将所有满足查询条件的记录都加锁。
组合四:id列上没有索引,RC隔离级别
由于id列上没有索引,因此只能走聚簇索引,进行全部扫描。从图中可以看到,满足删除条件的记录有两条,但是,聚簇索引上所有的记录,都被加上了X锁。
组合五:id列是主键,RR隔离级别
与组合一加锁相同
组合六:id列是二级唯一索引,RR隔离级别
与组合二一致
组合七:id列是二级非唯一索引,RR隔离级别
通过id索引定位到第一条满足查询条件的记录,加记录上的X锁,加GAP上的GAP锁,然后加主键聚簇索引上的记录X锁,然后返回;
组合八:id列上没有索引,RR隔离级别
只能进行全表扫描。最终的加锁情况,如下图所示:
以上是关于哇靠,死锁了之innodb锁解读的主要内容,如果未能解决你的问题,请参考以下文章