vlambda博客
学习文章列表

必须懂的 MySQL 的事务与隔离级别

重磅资讯、干货,第一时间送达

今日推荐:

        
          
          
        

个人原创+1博客:


出处:哈基石
链接:https://segmentfault.com/a/1190000022610363

事务的特性 ACID

  • 原子性(atomicity) 一个事务为不可分割的最小工作单元,要么全部提交成功,要么全部回滚,不可能只执行一部分,这就是事务的原子性
  • 一致性(consistency) 数据库从一个一致性状态切换到另一个一致性状态
  • 隔离性(isolation) 一个事务在提交之前,对其他事务是不可见的。
  • 永久性(durability) 一旦事务提交,那么所做的修改将会永久存储在数据库中

事务的隔离级别

READ UNCOMMITED(未提交读)脏读

事务中的修改即使没有提交,对其他事务都是可见的。会出现脏读(Dirty Read)的情况。

READ COMMITTED (提交读)不可重复读

一个事务从开始直到提交之前,所做的任何修改对其他事务都是不可见的。也叫做不可重复读(nonrepeatable read),因为两次同样的查询,可能会存在不同的结果举例说明:比如事务A正在读数据库内容,而事务 B 修改了数据后提交了。此时事务A又读了一次数据库内容,这时出现两个内容不同的结果,称为不可重复读。

REPEATABLE READ(可重复读)幻读 MySQL 默认的事务隔离级别

当某个事务在读取某个范围内的记录时,另外一个事务在这个范围内插入了新的记录,当之前的事务再次读取这个范围内的数据时,会产生幻行(Phantom Row)称为幻读举例说明:比如事务 A 正在读数据库内容,而事务 B 插入一条到数据库后提交了。此时事务 A 又读了一次数据库内容,这时数据库现了多一条数据称为幻读

SERIALIZABLE (可串行化)最高的隔离级别

在读取的每一行数据都上锁。导致导量的超时和等待锁的问题。

隔离级别 脏读 不可重复度 幻读 加锁读
未提交读 x
提交读 x x
可重复读 x x x
可串行化 x x x

选择哪种模式其实是一种博弈的过程,数据库的事务隔离越严格,并发副作用越小,但付出的代价也就越大,因为事务隔离实质上就是使事务在一定程度上“串行化”进行,这显然与“并发”是矛盾的。


自动提交(AUTOCOMMIT)

MySQL 默认采用自动提交(AUTOCOMMIT)模式。如果不显式地开始一个事务,则每个事务都被当做一个事务进行提交操作。MyISAM 在执行查询语句(select)前,会自动给涉及的所有表加读锁,在执行更新操作(update、delete、insert等)前,会自动给涉及的表加写锁,这个过程并不需要直接用 lock table 命令给 MyISAM 表显示加锁。MyISAM 在自动加锁的情况下,总是一次获得 sql 语句所需要的全部锁,所以显示锁表的时候,必须同时取得所有涉及表的锁,这也正是 MyISAM 表不会出现死锁(deadlock)的原因。

查看自动提交模式类型show variables like 'autocommit'

设置自动提交模式set autocommit = 1在事务的执行过程中,随时都可以执行锁定,锁只有 COMMIT 或 ROLLBACK 才会释放

MySQL 可以使用 SET TRANSACTION ISOLATION LEVEL 命令设置隔离级别

SET SESSION TRANSACTION ISOLATION LEVEL READ COMMITTED

死锁

死锁是指两个或者多个事务在同一个资源上互相占用,并请求锁定对方占用的资源,从而出现恶性循环的现象。

死锁产生的原因

  1. 因为系统资源不足。
  2. 进程运行推进的顺序不合适。
  3. 资源分配不当等。

如果系统资源充足,进程的资源请求都能够得到满足,死锁出现的可能性就很低,否则 就会因争夺有限的资源而陷入死锁。其次,进程运行推进顺序与速度不同,也可能产生死锁。

死锁产生的条件

  1. 互斥条件:一个资源每次只能被一个进程使用。
  2. 占有且等待:一个进程因请求资源而阻塞时,对已获得的资源保持不放。
  3. 不可强行占有:进程已获得的资源,在末使用完之前,不能强行剥夺。
  4. 循环等待条件:若干进程之间形成一种头尾相接的循环等待资源关系。这四个条件是死锁的必要条件,只要系统发生死锁,这些条件必然成立。

死锁的预防

  1. 破幻死锁产生的必要条件
  2. 检测到死锁的循环依赖,放弃锁的请求
  3. 等待锁超市,放弃锁

乐观锁

乐观锁(Optimistic Lock),顾名思义,就是很乐观,每次去拿数据的时候都认为别人不会修改,所以不会上锁,但是在提交更新的时候会判断一下在此期间别人有没有去更新这个数据。乐观锁适用于读多写少的应用场景,这样可以提高吞吐量。

乐观锁:假设不会发生并发冲突,只在提交操作时检查是否违反数据完整性。

乐观锁的实现方式

  1. 使用数据版本(Version)记录机制实现,这是乐观锁最常用的一种实现方式。何谓数据版本?即为数据增加一个版本标识,一般是通过为数据库表增加一个数字类型的 “version” 字段来实现。当读取数据时,将version字段的值一同读出,数据每更新一次,对此version值加一。当我们提交更新的时候,判断数据库表对应记录的当前版本信息与第一次取出来的version值进行比对,如果数据库表当前版本号与第一次取出来的version值相等,则予以更新,否则认为是过期数据。
  2. 使用时间戳(timestamp)。乐观锁定的第二种实现方式和第一种差不多,同样是在需要乐观锁控制的table中增加一个字段,名称无所谓,字段类型使用时间戳(timestamp), 和上面的version类似,也是在更新提交的时候检查当前数据库中数据的时间戳和自己更新前取到的时间戳进行对比,如果一致则OK,否则就是版本冲突。

应用场景乐观锁适用于写比较少的情况下(多读场景) ,这样可以省去了锁的开销,加大了系统的整个吞吐量。

悲观锁

悲观锁(Pessimistic Lock),顾名思义,就是很悲观,每次去拿数据的时候都认为别人会修改,所以每次在拿数据的时候都会上锁,这样别人想拿这个数据就会block直到它拿到锁。

悲观锁:假定会发生并发冲突,屏蔽一切可能违反数据完整性的操作。

应用场景一般多写的场景下用悲观锁就比较合适

最后,再附上我历时三个月总结的 Java 面试 + Java 后端技术学习指南,这是本人这几年及春招的总结,目前,已经拿到了大厂offer,拿去不谢!

下载方式

1. 首先扫描下方二维码

2. 后台回复「Java面试」即可获取