数据库隔离级别和锁
背景:关于数据库中的事务ACID以及隔离级别在面试笔试中经常被考。如果这种题再不会实在太可惜了。所以要深入总结
脏读和不可重复读的区别:脏读是某一事务读取了另一个事务未提交的脏数据,而不可重复读则是读取了前一事务提交的数据。
不可重复读和幻读的区别:都是读取了另一条已经提交的事务(这点就脏读不同),所不同的是不可重复读查询的都是同一个数据项,而幻读针对的是一批数据整体(比如数据的个数)。
不可重复读和幻读的区别
很多人容易搞混不可重复读和幻读,确实这两者有些相似。但不可重复读重点在于update和delete,而幻读的重点在于insert。
如果使用锁机制来实现这两种隔离级别,在可重复读中,该sql第一次读取到数据后,就将这些数据加锁,其它事务无法修改这些数据,就可以实现可重复读了。但这种方法却无法锁住insert的数据,所以当事务A先前读取了数据,或者修改了全部数据,事务B还是可以insert数据提交,这时事务A就会发现莫名其妙多了一条之前没有的数据,这就是幻读,不能通过行锁来避免。需要Serializable隔离级别 ,读用读锁,写用写锁,读锁和写锁互斥,这么做可以有效的避免幻读、不可重复读、脏读等问题,但会极大的降低数据库的并发能力。
所以说不可重复读和幻读最大的区别,就在于如何通过锁机制来解决他们产生的问题。
上文说的,是使用悲观锁机制来处理这两种问题,但是MySQL、ORACLE、PostgreSQL等成熟的数据库,出于性能考虑,都是使用了以乐观锁为理论基础的MVCC(多版本并发控制)来避免这两种问题。
行锁防止别的事务修改或删除,GAP锁防止别的事务新增,行锁和GAP锁结合形成的的Next-Key锁共同解决了RR级别在写数据时的幻读问题。
总结到很到位,还要继续梳理
ps:数据库中的两段锁,行锁,表锁,悲观乐观锁。GAP(间隙锁,也就是间隔锁) 隔离级别和锁的对应关系。
mysql 幻读的详解、实例及解决办法
ps:上面的文章讲的很透彻。理解不同的隔离级别,加锁机制,幻读解决办法(隔离级别调整为序列化,或者是RR时候手动加X锁)
一直有个疑问,间隙锁可以用来防止幻读,间隙锁需要在RR级别才会生效,RR为什么又会发生幻读?正确的理解是,在RR级别下快照读通过MVCC实现,通过加间隙锁来防止出现幻读,此时可以加X锁或者for update-当前读解决
mysql 有四级事务隔离级别 每个级别都有字符或数字编号
级别 | symbol | 值 | 描述 |
---|---|---|---|
读未提交 | READ-UNCOMMITTED | 0 | 存在脏读、不可重复读、幻读的问题 |
读已提交 | READ-COMMITTED | 1 | 解决脏读的问题,存在不可重复读、幻读的问题 |
可重复读 | REPEATABLE-READ | 2 | mysql 默认级别,解决脏读、不可重复读的问题,存在幻读的问题。使用 MMVC机制 实现可重复读 |
序列化 | SERIALIZABLE | 3 | 解决脏读、不可重复读、幻读,可保证事务安全,但完全串行执行,性能最低 |
幻读会在 RU / RC / RR
级别下出现,SERIALIZABLE
则杜绝了 幻读
,但 RU / RC
下还会存在脏读、不可重复读
,故我们就以 RR
级别来研究 幻读
,排除其他干扰。
注意:RR
级别下存在幻读的可能,但也是可以使用对记录手动加 X锁
的方法消除幻读
。SERIALIZABLE
正是对所有事务都加 X锁
才杜绝了 幻读
,但很多场景下我们的业务 sql
并不会存在 幻读
的风险。SERIALIZABLE
的一刀切虽然事务绝对安全,但性能会有很多不必要的损失。故可以在 RR
下根据业务需求决定是否加锁,存在幻读风险我们加锁,不存在就不加锁,事务安全与性能兼备,这也是 RR
作为 mysql
默认隔是个事务离级别的原因,所以需要正确的理解 幻读
。
幻读错误的理解:说幻读是 事务A 执行两次 select 操作得到不同的数据集,即 select 1 得到 10 条记录,select 2 得到 11 条记录。这其实并不是幻读,这是不可重复读的一种,只会在 R-U R-C 级别下出现,而在 mysql 默认的 RR 隔离级别是不会出现的。
这里给出我对幻读的比较白话的理解:
幻读,并不是说两次读取获取的结果集不同,幻读侧重的方面是某一次的 select 操作得到的结果所表征的数据状态无法支撑后续的业务操作。更为具体一些:select 某记录是否存在,不存在,准备插入此记录,但执行 insert 时发现此记录已存在,无法插入,此时就发生了幻读。
rr级别下,mvcc机制,不会出现上面说的两次select,发现insert的数据。如果select用的是当前读会出现,还有就是上面说的,先select后insert时候出现幻读。
RR
级别作为 mysql
事务默认隔离级别,是事务安全与性能的折中,可能也符合二八定律(20%的事务存在幻读的可能,80%的事务没有幻读的风险),我们在正确认识幻读后,便可以根据场景灵活的防止幻读的发生。
SERIALIZABLE
级别则是悲观的认为幻读时刻都会发生,故会自动的隐式的对事务所需资源加排它锁,其他事务访问此资源会被阻塞等待,故事务是安全的,但需要认真考虑性能。
InnoDB
的行锁锁定的是索引,而不是记录本身,这一点也需要有清晰的认识,故某索引相同的记录都会被加锁,会造成索引竞争,这就需要我们严格设计业务 sql
,尽可能的使用主键或唯一索引对记录加锁。索引映射的记录如果存在,加行锁,如果不存在,则会加 next-key lock / gap 锁 / 间隙锁
,故 InnoDB
可以实现事务对某记录的预先占用,如果记录存在,它就是本事务的,如果记录不存在,那它也将是本是无的,只要本是无还在,其他事务就别想占有它。
ps:可以作为参考
- R隔离级别下间隙锁才有效,RC隔离级别下没有间隙锁;
- RR隔离级别下为了解决“幻读”问题:“快照读”依靠MVCC控制,“当前读”通过间隙锁解决;
- 间隙锁和行锁合称next-key lock,每个next-key lock是前开后闭区间;
- 间隙锁的引入,可能会导致同样语句锁住更大的范围,影响并发度。
Mysql锁一般使用
ps:介绍了mysql中的各种锁,行锁里面的共享锁和独占锁,值得好好研究
行锁是比较理想的锁,把需要操作的记录行给锁定不影响其它行的操作,可以增加更多的并行操作空间。MySQL的InnoDB引擎支持的行锁是在索引的基础上加锁,所以也可以说InnoDB的锁是索引锁,如果加锁时没有索引或索引没有命中那就会进入到表锁。索引锁是通过查询SQL来加锁所以在加索前一定要分析下SQL语句。同时索引的命中并不是通过desc或explain就可以绝对确定的,MySQL查询优化处理会判断使用索引与不使用索引的最佳方案,一般通过SQL解析中不会命令索引的就不会使用索引,会使用到索引的需要人为的再去判断下所对应的数据量在整表中占比数(唯一或主键索引一般不需要判断),占比量越小越好(参考 < 30% 以内)并且查询的数量越小越好(参考 < 5000)。InnoDB的行锁在事务内有效,释放锁与事务提交、回滚同时进行,如果没有开启事务加锁则会在执行完SQL后自动释放锁(这个自动释放受autocommit配置影响,autocommit是自动提交事务开关,当打开时所有InnoDB引擎的SQL在没有开启事务时会默认给要执行的SQL开启一个事务并在执行完后提交这个事务,如果是手动开启事务则这个选项无效)。行级锁加锁慢。
共享锁(S)共享锁也称为读锁,读锁允许多个连接可以同一时刻并发的读取同一资源,互不干扰;
排他锁(X)排他锁也称为写锁,一个写锁会阻塞其他的写锁或读锁,保证同一时刻只有一个连接可以写入数据,同时防止其他用户对这个数据的读写。
对于锁可以参考:深入理解 MySQL ——锁、事务与并发控制