关于事务原理可以参考之前的 文章.
并发异常产生的前提
加行锁并且事务是并发进行的。
读异常
读异常分类
脏读
事务(A)可以读到另外一个事务(B)中未提交的数据;也就是事务A读到脏数据;在读写分离的场景下,可以将slave节点设置为 READ UNCOMMITTED;此时脏读不影响,在slave上查询并不需要特别精准的返回值。
不可重复读
事务(A) 可以读到另外一个事务(B)中提交的数据;通常发生在一个事务中两次读到的数据是不一样的情况;不可重复读在隔离级别 READ COMMITTED 存在。一般而言,不可重复读的问题是可以接受的,因为读到已经提交的数据,一般不会带来很大的问题,所以很多厂商(如Oracle、SQL Server)默认隔离级别就是READ COMMITTED;
幻读
两次读取同一个范围内的记录得到的结果集不一样;例如:以 name 为唯一键的表,一个事务中查询 select * from t where name = ‘mark’; 不存在,接下来 insert into t(name) values (‘mark’); 出现错误,此时另外一个事务也执行了 insert 操作;幻读在隔离级别REPEATABLE READ 及以下存在;但是可以在 REPEATABLE READ 级别下通过读加锁(使用nextkey locking)解决;
解决:
在 select的时候加上S锁或X锁,让拥有X锁的事务无法修改,加S锁时就类似于SERIALIZABLE;
加X锁的话就在SQL语句后面加上for updata;
丢失更新
脏读、不可重复读、幻读都是一个事务写,一个事务读,由于一个事务的写导致另一个事务读到了不该读的数据;丢失更新是两个事务都是写;丢失更新分为提交覆盖和回滚覆盖;回滚覆盖数据库拒绝不可能产生,重点关注提交覆盖;
解决方法与幻读类似,通过加S/X锁,但依旧会有提交覆盖,需要由业务逻辑判断,最后应该提交哪一个。
区别
脏读和不可重复读的区别在于,脏读是读取了另一个事务未提交的数据,而不可重复读是读取了另一个事务提交之后的修改;本质上都是其他事务的修改影响了本事务的读取;
不可重复读和幻读比较类似;不可重复读是两次读取同一条记录,得到不一样的结果;而幻读是两次读取同一个范围内的记录得到的结果集不一样(可能不同个数,也可能相同个数内容不一样,比如删除一行后又添加新行);不可重复读是因为其他事务进行了update 操作,幻读是因为其他事务进行了 insert 或者 delete 操作。
并发死锁
死锁:两个或两个以上的事务在执行过程中,因争夺锁资源而造成的一种互相等待的现象;
MySQL 中采用 wait-for graph (等待图-采用非递归深度优先的图算法实现)的方式来进行死锁检测;
异常报错:deadlock found when trying to get lock;
相反加锁顺序死锁
不同表的加锁顺序相反或者相同表不同行加锁顺序相反造成死锁;其中相同表不同行加锁顺序相反造成死锁有很多变种,其中容易忽略的是给辅助索引行加锁的时候,同时会给聚集索引行加锁;同时还可能出现在外键索引时,给父表加锁,同时隐含给子表加锁;触发器同样如此,这些都需要视情况分析;
调整加锁顺序;
锁冲突死锁
innodb 在 RR 隔离级别下,最常见的是插入意向锁与 gap 锁冲突造成死锁;主要原理为:一个事务想要获取插入意向锁,如果有其他事务已经加了 gap lock 或 Next-key lock 则会阻塞;
最后
推荐一个零声学院免费公开课程,个人觉得老师讲得不错,分享给大家:Linux,Nginx,ZeroMQ,MySQL,Redis,fastdfs,MongoDB,ZK,流媒体,CDN,P2P,K8S,Docker,TCP/IP,协程,DPDK等技术内容,立即学习