目录
事务要允许并发执行就会导致数据的安全性&一致性和并发效率问题,导致脏读、不可重复读、幻读。
表级锁&行级锁
表级锁:对整张表加锁。开销小,加锁快,不会出现死锁;锁粒度大,发生锁冲突的概率高,并发度 低。
行级锁:对某行记录加锁。开销大,加锁慢,会出现死锁;锁定粒度最小,发生锁冲突的概率最低,并 发度高。
排他锁&共享锁
排它锁(Exclusive),X 锁,写锁。
共享锁(Shared),S 锁,读锁。
InnoDB行级锁
行级锁(record lock):
InnoDB存储引擎支持事务处理,表支持行级锁定,并发能力更好;
1. InnoDB的行锁是加在索引项上面的,是给索引在加索,并不是给表的行记录加锁;只有通过索引条件检索数据,InnoDB使用的才是行级锁,否则使用的是表锁;
2、由于InnoDB的行锁实现是针对索引字段添加的锁,不是针对行记录加的锁,因此虽然访问的是 InnoDB引擎下表的不同行,但是如果使用相同的索引字段作为过滤条件,依然会发生锁冲突,只能串行进行,不能并发进行。
3、即使SQL中使用了索引,但是经过MySQL的优化器后,如果认为全表扫描比使用索引效率更高,此 时会放弃使用索引,因此也不会使用行锁,而是使用表锁,比如对一些很小的表,MySQL就不会去使用 索引。
间隙锁(gap lock):
InnoDB串行化隔离级别 如何解决幻读? 间隙锁
1. 当使用范围查询条件,并请求共享锁或者排他锁时,InnoDB会给符合条件的已有数据记录的索引项加索;对于键值在条件范围内但并不存在的记录,叫做“间隙(GAP)” ,InnoDB 也会对 这个“间隙”加锁,这种锁机制就是所谓的间隙锁。
InnoDB使用间隙锁的目的,为了防止幻读,以满足串行化隔离级别的要求,对于上面的例子,要是不 使用间隙锁,如果其他事务插入了 userid 大于 100 的任何记录,那么本事务如果再次执行上述语句, 就会发生幻读。
2. 当使用等值查询时,再次插入age=18时会被阻塞住,会在等值18的两边加上间隙锁
(辅助索引值相等的情况下(辅助索引是可以重复的),主键按升序排列)比如说插入一个age=15的,会阻塞,因为他的主键id会升序排列24,会在已有的15和18之间,由于有间隙锁,所以无法加入。
意向锁
意向锁的存在是为了协调行锁和表锁的关系,支持多粒度(表锁与行锁)的锁并存。
1)意向共享锁(IS锁):事务在给一行记录加共享锁前,必须先取得该表的 IS 锁。
2)意向排他锁(IX锁):事务在给一行记录加排他锁前,必须先取得该表的 IX 锁。
意向锁和共享锁、排他锁的兼容关系:
意向锁如何解决表锁涉及的效率问题?
需要在大量数据中检验没有被其他事务获取过X锁,有了意向锁之后,事务在给行加锁之前,都活获取表的IS或者IX锁,所以当事务要获取表的X锁时,不需要在检查表中那些行锁被(X或者S)占用,只需要快速检查IX和IS锁即可。效率提升 了
InnoDB表级锁
在绝大部分情况下应该使用行锁,因为事务和行锁往往是选择InnoDB的理由,但个别情况下也使用表级锁;
使用表锁的时候涉及到一个效率的问题:
要获取一张表的共享锁S或者排他锁X,最起码得确定,这张表没有被其他事务获取过X锁!比如说一张表(一千万个数据)没有被其他事务获取过行锁X锁!
MVCC(多版本并发控制)
MVCC是多版本并发控制(Multi-Version Concurrency Control,简称MVCC),是MySQL中基于乐观锁理论实现隔离级别的方式,用于实现已提交读和可重复读隔离级别的实现,也经常称为多版本数据库。
MVCC机制会生成一个数据请求时间点的一致性数据快照 (Snapshot), 并用这个快照来提供一定 级别 (语句级或事务级) 的一致性读取。从用户的角度来看,好象是数据库可以提供同一数据的多个 版本(系统版本号和事务版本号)。
已经提交读和可重复读 =》底层实现原理是 MVCC(多版本并发控制)=> 并发的读取方式:快照读
InnoDB提供了两个读取操作:锁定读(SX)和非锁定读(MVCC提供的快照读)=》 依赖底层的一个技术 =》 undo log 回滚日志
MVCC:每一行记录实际上有多个版本,每个版本的记录除了数据本身之外,增加了其他字段
已提交读的MVCC:
(每次执行语句的时候都重新生成一次快照(Read View),每次select查询时。)
1. 已提交读底层如何解决脏读问题?
已提交读使用的是一种非锁定读,也就是MVCC多版本并发控制提供的一种快照读来解决的,事务每一次select都会把符合当前查询条件的数据拍个照片,相当于生成一次当前数据快照的一个数据版本,生成这个数据版本的时候有一个前提就是数据的状态必须是commit提交状态,当我们对一个数据进行修改但未提交,数据处于prepare状态,所以在生成数据快照的时候用的还是旧数据来生成的,也就是在undo log里面的旧数据,所以不会出现脏读。
2. 但为什么无法解决 不可重复读?
因为每一次select都会重新产生一次数据快照,其他事务更新后而且已提交数据,符合快照读的条件,可以实时反馈到当前事务的select结果中。
3. 为什么无法解决幻读?
因为每一次select都会重新产生一次数据快照,其它事务增加了和当前事务查询条件相同的新的数据并且成功commit提交,导致当前事务再次以同样的条件查询时,数据多了。
可重复读的MVCC:
(同一个事务开始的时候生成一个当前事务全局性的快照(Read View),第一次select查询时)
1. 解决了“脏读问题”
都是生成数据快照,生成数据快照的前提就是数据状态必须是commit
2. 解决了 不可重复读问题
第一次select产生数据快照,而且只产生一次!!!!!
第一次select产生数据快照,其它事务随谈修改了最新的数据,但是当前事务select时,依然查看的是最初的快照数据。
3. 部分解决了幻读
对于select操解决了幻读,原理同上,只在第一次select产生快照;但是对于updata等使用当前读方法的操作,并没有解决幻读问题。
死锁
MyISAM 表锁是 deadlock free 的, 这是因为 MyISAM 总是一次获得所需的全部锁,要么全部满足, 要么等待,因此不会出现死锁。
但在 InnoDB 中,除单个 SQL 组成的事务外,锁是逐步获得的,即锁的粒度比较小,这就决定了在 InnoDB 中发生死锁是可能的。
死锁问题一般都是我们自己的应用造成的,和多线程的死锁情况相似,大部分都是由于我们多个线程在获取多个锁资源的时候,获取的顺序不同导致的死锁问题。因此我们应用子啊对数据库的多个表坐更新的时候,不同的代码段应对这些表按相同的顺序进行更新操作,以防止锁冲突导致死锁问题。