数据库隔离级别

数据库隔离级别

  1. 读未提交 Read uncommitted
    就是可以读到未提交的内容。因此,在这种隔离级别下,查询是不会加锁的,也由于查询的不加锁,所以这种隔离级别的一致性是最差的,可能会产生“脏读”、“不可重复读”、“幻读”。如无特殊情况,基本是不会使用这种隔离级别的

  2. 读已提交 Read committed 就是只能读到已经提交了的内容。这是各种系统中最常用的一种隔离级别

  3. 可重复度 Repeatable read
    在同一个事务中,前后两次读到的数据是一致的;就是专门针对“不可重复读”这种情况而制定的隔离级别,在这个级别下,普通的查询同样是使用的“快照读”,但是,和“读提交”不同的是,当事务启动时,就不允许进行“修改操作(Update)”了,而“不可重复读”恰恰是因为两次读取之间进行了数据的修改

  4. 序列化 Serializable 这是数据库最高的隔离级别,这种级别下,事务“串行化顺序执行”,也就是一个一个排队执行。

    脏读 不可重复读 幻读
    Read uncommitted √ √ √
    Read committed × √ √
    Repeatable read × × √
    Serializable × × ×

脏读:脏读就是指当一个事务正在访问数据,并且对数据进行了修改,而这种修改还没有提交到数据库中,这时,另外一个事务也访问这个数据,然后使用了这个数据。

不可重复读:是指在一个事务内,多次读同一数据。在这个事务还没有结束时,另外一个事务也访问该同一数据。那么,在第一个事务中的两次读数据之间,由于第二个事务的修改,那么第一个事务两次读到的的数据可能是不一样的。这样就发生了在一个事务内两次读到的数据是不一样的,因此称为是不可重复读。重点在于修改。

幻读:是值在一个事务中,相同的条件第一次和第二次读出来的记录数不一致,另外一个事务新增或删除了一条记录,这条记录属于第一个事务读取的数据行内。重点在于新增或删除。

MVCC

思考一个问题,当两个事务对同一数据修改的时候,是如何保证不冲突的呢,保证更新不丢失的呢?第一个方法就是加锁,比如LBBC(Lock Based Concurrency Control),这个方案比较简单粗暴但是效率很低。还有个方案就是MVVC(Multi Version Concurrency Control),基于多版本的数据库并发控制。
InnoDB中的MVCC是依靠uodo log 和 ReadView 来实现的。

Undo Log
  • insert undo log
    由insert操作产生的日志,因为insert操作只对事务本身可见,对其他事务是不可见的,所以insert undo log在事务提交后可以直接删除。
  • update undo log
    由delete和update操作中产生的日志,该日志后续会被用于MVCC中,因此不能在事务提交的时候删除,提交后会被放入undo log 链表中,等待purge线程进行最后的删除。
  • 修改数据的过程
    每次修改一条记录的时候,会将该行当前的值复制到undo log中,然后修改当前行的值,同时会添加一个事务ID,还会有一个回滚指针指向undo log 中修改之前的行(第一次修改的时候会先复制一个当前行到undo log),事务ID是单调递增的。如下截图显示了两次修改后的undo log 链表中的记录情况。
    在这里插入图片描述
ReadView

所谓的ReadView主要包含当前系统中活跃的读写事务,并把它们的事务ID放到一个列表中称之为 m_ids。这样我们在访问某条记录的时候只需要按照如下的步骤去判断记录的某个版本是否可见,可见的版本就是我们读取到的数据,注意:当某个事务被提交了,则它就不属于活跃事务了。

  • 如果被访问版本的trx_id 属性值小于m_ids 列表中最小的事务id,表明生成该版本的事务在生成ReadView 前已经提交,所以该版本可以被当前事务访问。
  • 如果被访问版本的trx_id 属性值大于m_ids 列表中最大的事务id,表明生成该版本的事务在生成ReadView 后才生成,所以该版本不可以被当前事务访问。
  • 如果被访问版本的trx_id 属性值在m_ids 列表中最大的事务id和最小事务id之间,那就需要判断一下trx_id 属性值是不是在m_ids 列表中,如果在,说明创建ReadView 时生成该版本的事务还是活跃的,该版本不可以被访问;如果不在,说明创建ReadView 时生成该版本的事务已经被提交,该版本可以被访问。
  • 如果某个版本的数据对当前事务不可见,则顺着版本链找到下一个版本,直到版本链中的最后一个版本,如果最后一个也不可见则查询不到结果。
  • 总结来说就是:如果当前事务ID在m_ids中则不可见,不在的话则是可见的。
再谈RC和RR

在Mysql中RC和RR一个非常大的区别就是生成ReadView的时机不同

  • READ COMMITTED每次读取数据前都生成一个ReadView,这样就保证了每次读之前拿到都是最新的活跃事务ID,所以只要有事务提交了,就不在m_ids中,这样的话对于查询操作的事务而言修改过的那个版本记录就是可见的,所以能读到已经提交的数据。

  • REPEATABLE READ :只会在事务第一次读取数据的时候生成一个ReadView,之后的查询都不会重复生成了。举个例子看下:
    比如现在系统中有两个id为100和200的事务在执行
    在这里插入图片描述那么t表中id=1的记录得到的版本链如下
    在这里插入图片描述此时有个RR隔离级别下的查询事务在执行
    在这里插入图片描述
    该查询过程如下:
    1)在执行SELECT 语句时会先生成一个ReadView , ReadView 的m_ids 列表的内容就是[100,200] 。
    2)然后从版本链中挑选可见的记录,从图中可以看出,最新版本的列c 的内容是’张飞’ ,该版本的trx_id 值为100 ,在m_ids 列表内,所以不符合可见性要求,根据roll_pointer 跳到下一个版本。
    3)下一个版本的列c 的内容是’关羽’ ,该版本的trx_id 值也为100 ,也在m_ids 列表内,所以也不符合要求,继续跳到下一个版本。
    4)下一个版本的列c 的内容是’刘备’ ,该版本的trx_id 值为80 ,小于m_ids 列表中最小的事务id 100 ,所以这个版本是符合要求的,最后返回给用户的版本就是这条列c 为’刘备’ 的记录。

    然后我们把事务id为100的事务提交下,事务200的中更新下t表,如下:
    在这里插入图片描述在这里插入图片描述
    此时版本链如下图:
    在这里插入图片描述
    然后继续回到刚才的查询事务中查找id=1的记录,查到的还是 c=‘刘备’的记录,分析过程如下:
    1)因为之前已经生成过ReadView 了,所以此时直接复用之前的ReadView ,之前的ReadView 中的m_ids 列表就是[100, 200] 。虽然此时事务为100的一句提交了。
    2)然后从版本链中挑选可见的记录,从图中可以看出,最新版本的列c 的内容是’诸葛亮’ ,该版本的trx_id 值为200 ,在m_ids 列表内,所以不符合可见性要求,根据roll_pointer 跳到下一个版本。
    3)下一个版本的列c 的内容是’张飞’ ,该版本的trx_id 值为100 ,而m_ids 列表中是包含值为
    100 的事务id的,所以该版本也不符合要求,同理下一个列c 的内容是’关羽’ 的版本也不符合要求。继续跳到下一个版本。
    4)下一个版本的列c 的内容是’刘备’ ,该版本的trx_id 值为80 , 80 小于m_ids 列表中最小的事务id 100 ,所以这个版本是符合要求的,最后返回给用户的版本就是这条列c 为’刘备’ 的记录。

    可以看到,同一个事务中我们前后两次查询的结果是相同的,这就是可重复读,如果把事务id为200的提交了,查询到的还是‘刘备’。

这就是MVCC通过多版本来控制并发,达到数据库不同隔离级别的效果,但是在InnoDB中只支持RC和RR两种隔离级别。

其他

Sql Server , Oracle 默认隔离级别是 Read committed
Mysql 默认隔离级别是 Repeatable read ;但是通过next-key lock 解决了幻读,保证了ACID

查看数据库隔离级别:show variables like ‘tx_isolation’;

遇到的问题:
mysql中开启事务,先查询,再做update操作,会报错java.sql.SQLException: Could not retrieve transaction read-only status from
百度后有很多答案,我是把update操作单独拿出来作为一个方法,给这个单独的方法加上事务@Transactional 就可以了。
但是在别的数据库上有时候又没有这个问题,很奇怪。

记录一些问题:
Expression #2 of SELECT list is not in GROUP BY clause and contains nonaggregated column ‘user.t_emp.name’ which is not functionally dependent on columns in GROUP BY clause; this is incompatible with sql_mode=only_full_group_by。
原因:mysql的group by规则,select @@global.sql_mode,查看结果如下:
ONLY_FULL_GROUP_BY,STRICT_TRANS_TABLES,NO_ZERO_IN_DATE,NO_ZERO_DATE,ERROR_FOR_DIVISION_BY_ZERO,NO_AUTO_CREATE_USER,NO_ENGINE_SUBSTITUTION。
去掉ONLY_FULL_GROUP_BY就行了,不然的话查询列必须都是group by 的列。

  • 0
    点赞
  • 2
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值