热门标签 | HotTags
当前位置:  开发笔记 > 编程语言 > 正文

数据库事务的四种隔离性及Oracle\MySQL默认隔离级别和原因分析|运维进阶

【摘要】本文将围绕事务的特性、并发问题、隔离级别进行集中学习,加深对相关问题的理解和提升应对能力。【作者】杨磊,某金融公司、数据库架构师;一直在数据库领域坚持耕耘10+年,从事

【摘要】本文将围绕事务的特性、并发问题、隔离级别进行集中学习,加深对相关问题的理解和提升应对能力。
【作者】杨磊,某金融公司、数据库架构师;一直在数据库领域坚持耕耘10+年,从事数据架构设计和部署,对开源数据库、国产数据库、NoSQL、NewSQL、大数据、云计算、云原生等新技术有持续关注和极大兴趣。擅长领域:数据库、架构设计、信创改造。


大家都知道数据库事务四特性:原子性、一致性、隔离性和持久性,也就是人们熟知的 ACID 特性。在实际生产应用中,数据库中的数据是要被多用户共享/访问,而在多个用户同时操作相同数据时,可能会出现一些事务的并发问题,这就有了事务隔离性的四种不同级别。今天,本文将围绕事务的特性、并发问题、隔离级别进行集中学习。

首先回顾下事务的概念:

一个事务中的一系列的处理操作操作要么全部成功,要么一个都不做。在数据库操作中,一项事务(Transaction)是由一条或多条操作数据库的 SQL 语句组成的一个不可分割的工作单元。

那么,事务的处理结束就会有两种:1. 当事务中的所有步骤全部成功执行时,事务提交,成功;2. 如果其中任何一个步骤失败,该事务都将发生回滚操作,撤消已执行的所有操作。

再来看下事务的四个特性:

1)原子性(Atomic)

表示将事务中所进行的操作捆绑成一个不可分割的单元,即对事务所进行的数据修改等操作,要么全部执行,要么全不执行;如果失败,就回滚到事务开始前的状态

2)一致性(Consistency)

事务必须使数据库从一个一致性状态变换到另一个一致性状态,也就是说一个事务执行之前和执行之后都必须处于一致性状态。

3)隔离性(Isolation)

指一个事务的执行不能被其他事务干扰,即一个事务内部的操作及使用的数据对并发的其他事务是隔离的,并发执行的各个事务之间不能互相干扰。

4)持久性(Durability)

持久性也称永久性(permanence),持久性就是指如果事务一旦被提交,数据库中数据的改变就是永久性的,即使断电或者宕机的情况下,也不会丢失提交的事务操作。

本文主讲隔离性,这里再把事务的隔离性(Isolation)单拉出来说明下:

隔离性是指,多个用户的并发事务访问同一个数据库时,一个用户的事务不应该被其他用户的事务干扰,多个并发事务之间要相互隔离。

事务隔离性的重要性:

事务的隔离性主要是从提升数据库的数据处理速度,即并发角度考虑的;换句话说,事务隔离性和整体数据库的性能/并发执行,有直接决定性作用。

一个思维模式:想要理解一个知识点,就假设如果没有该点会有什么发生;事务隔离性也是,如果事务中没有隔离性这个概念,会发生点啥事?

在实际应用中,数据库中性能的好坏标准之一就是数据能被尽可能多的用户共同访问,当多个用户同时处理同一数据时,可能就会出现一些事务的并发问题,导致如下四种情况出现:

1)脏读

指一个事务读取到另一个事务未提交的数据。

2)不可重复读

指一个事务对同一行数据重复读取两次,但得到的结果不同。

举例:事务1读取表的一条数据期间,事务2更新了该条记录并提交,事务1再次读取该表该条记录时,发现和第一次内容不一致。

3)幻读

指一个事务执行两次查询,但第二次查询的结果包含了第一次查询中未出现的数据。

举例:事务1读取一个表期间,事务2对表做了delete/update/insert操作并提交,事务1再次读取表时,此时读取到事务2操作的记录,两次操作结果不一致。

这里,有朋友会问,不可重复读和幻读这不一样吗?

答案为:不一样。有啥区别?

幻读和不可重复读都是读取了另一条已经提交的事务;但不可重复读查询的都是同一个数据项,而幻读针对的是一批数据整体(比如数据的个数)。

不可重复读和幻读是初学者不易分清的概念;简单来说,解决不可重复读的方法是大家常说的加行锁,解决幻读方式是加表锁

4)丢失更新

指两个事务同时更新一行数据,后提交(或撤销)的事务将之前事务提交的数据覆盖了。

注意:丢失更新可分为两类,分别为第一类丢失更新和第二类丢失更新。

第一类丢失更新:两个事务同时操作同一个数据时,当第一个事务撤销时,把已经提交的第二个事务的更新数据覆盖了,第二个事务就造成了数据丢失。

第二类丢失更新:当两个事务同时操作同一个数据时,第一个事务将修改结果成功提交后,对第二个事务已经提交的修改结果进行了覆盖,对第二个事务造成了数据丢失。

可以看出,上述问题,均是在并发情况下发生的,并发度越高,上述出现的情况也普遍。

怎么办?

为了避免上述事务并发问题的出现,标准 SQL 规范定义了四种事务隔离级别,不同的隔离级别对事务的处理有所不同。这四种事务的隔离级别如下:

1)Read Uncommitted(读未提交)

一个事务在执行过程中,既读取其他事务未提交的数据,又可以读取本事务未提交的修改数据。一个事务已经开始写数据,则另外一个事务不允许同时进行写操作,但允许其他事务读此行数据。此隔离级别可防止丢失更新。

但因为能读取到其他事务未修改的数据,即不能防止“脏读”。这种事务隔离级别下,select语句不加锁。此时,可能读取到不一致的数据。

读未提交是并发最高,但一致性也最差的隔离级别。

2)Read Committed(读已提交)

此隔离级别可有效防止脏读。

在该隔离级别下,不允许2个未提交的事务之间并行执行,但它允许在一个事务执行的过程中,另外一个事务得到执行并提交。读取数据的一个事务不会禁止其他写事务,读取数据的事务允许其他事务继续访问该行数据,但是未提交的写事务将会禁止其他事务访问该行。此隔离级别不能解决不可重复读问题。

该方式是oracle数据库默认的隔离级别,事务提交需手动进行。

注意,在互联网大数据量,高并发量的场景下,几乎不会使用上述两种隔离级别。

原因如下:

“读未提交”虽说有最高的并行执行度,但大量的“脏读”是不被用户认可的;互联网场景下,经常会有大量的读写操作,当有大量写操作未提交时,会限制其他事务对数据的任何访问,这对互联网需要访问热点数据的需求下显得极为不够友好。

3)Repeatable Read(可重复读取)

该隔离级别可解决不可重复读的问题。在该隔离级别下,在一个事务使用某行的数据的过程中,不允许别的事务再对该行数据进行操作。可重复读是给数据库的行加上了锁。

读取数据的事务将会禁止写事务(但允许读事务),写事务则禁止任何其他事务。这里的事务读数据时禁止其他进程写,就保障了一个事务的可重复读性。

但是不能解决幻读,为啥?看个场景

可重复读取隔离级别下,因为只是对一个事务写操作的行加了行锁,但依旧允许别的事务在该表其他行插入和删除数据,于是就会出现,在事务1执行的过程中,如果先后两次select出符合某个条件的行,如果在这两次select之间另一个事务得到了执行,insert或delete了某些行,就会出现先后两次select出来的符合同一个条件的结果不一样,第一次select好像出现了幻觉一样,因此,这个问题也被成为幻读。要想解决幻读问题,需要将数据库的隔离级别设置为串行化。

4)Serializable(可串行化)

提供严格的事务隔离。它要求事务序列化执行,事务只能一个接着一个地执行,不能并发执行。此隔离级别可有效防止脏读、不可重复读和幻读。

但这个级别可能导致大量的超时现象和锁竞争,在实际应用中很少使用。

Serializable(可串行化)隔离级别可避免脏读、不可重复读、幻读 的发生,但失去了并发效率。

介绍完四种隔离级别,这里小结下:


Oracle数据库只支持Serializable (串行化) 级别和 Read committed (读已提交) 两种级别,默认隔离级别为 Read committed(读已提交)级别;

MySQL数据库中支持上面四种隔离级别,默认隔离级别为Repeatable read (可重复读)


这里可以反问一个问题:

为啥Oracle默认隔离级别是Read committed(读已提交)?

为啥MySQL默认隔离级别是Repeatable read (可重复读)?

个人推断原因如下:

1.时代背景分析:

Oracle为商业数据库,服务对象面临的是传统行业。传统场景下,事务的增删改并不是很频繁,通常读和写比较均衡,且写操作是比较常见的一种DML方式,故在读写选择时做了折中处理,在一个事务在读操作时,允许其他事务做写操作。

Oracle数据库只支持Serializable (串行化) 和Read committed (读已提交) 两种隔离级别,串行化不支持并发,为了保障较好的服务体验,必须保障一定的并发性,于是便默认选择Read committed (读已提交) 隔离模式;但这种模式确实会发生不可重复读和幻读的现风险。这是为什么在Oracle中更新一张大表时,常规操作时会依据一定条件做批量写提交,减少其他读事务的不可重复读现象。

2.场景原因分析:

随着硬件工艺和制作成本的降低,互联网大并发读访问需求下,越来越多的开源库架构开始使用share-nothing的MPP架构,MySQL隔离级别之所以选定为Repeatable read (可重复读) ;原因为要能最大限度的满足互联网场景下的高并发访问多次读的需求;且往往在大并发读场景下,分布式架构能有效把读操作进行分库分表式的方法访问,无形中增加了读操作的并行处理能力;

MySQL在面临大量的写操作时,Repeatable read (可重复读) 隔离级别就显得很不友好;一是一个读事务在读取一些行数据时会禁止对这些行的写事务(但允许读事务);二是一个写事务会禁止其他任何其他事务操作。

MySQL事务提交方式为默认提交,即MySQL执行每一条增删改DML语句后会默认自动提交,对行锁的获取和释放均很快操作结束,最大程度降低了读事务和写事物的冲突;因MySQL是share-nothing设计架构,自动提交事务的方式也能最大限度的保障从节点能和主库保持数据一致性。

MySQL分布式架构也把读和写的操作分散在不同的节点上,也从另一方面降低了读事务和写事务的冲突,从而保障了前段业务的可用性。

但如下场景会是一个头疼的事:当读事务访问的某些数据行和写事务访问的某些行均落在分布式架构的一个节点上,会引起资源争用,这个时候只能等待事务完成,释放资源;或者杀掉某个事务会话,迫使其释放资源,让另一个事务完成后再行执行。

3.底层设计思维的不同:

先说oracle架构设计思维:

大家都知道oracle是基于share-disk的设计思维,存储节点只有一份,控制文件、redo日志、归档日志、数据文件均在共享盘阵上。RAC架构的数据一致性在计算节点间的内存层buffer cache进行保证,然后落盘redo日志。因为是在内存级别保证数据一致性,且redo是顺序化的写入,故处理速度会非常快,Oracle在一个事务提交后,会依据如下条生成redo日志,redo日志记录的是发生变化的数据块,包含已经提交和未提交的(The redo log records all changes made to data, including both uncommitted and committed changes.)。Redo的功能主要通过3个组件来实现:Redo Log Buffer、LGWR后台进程和Redo Log File。

因为redo日志的刷新机制速度快且较为频繁(见下文),故Oracle虽然采用Read committed (读已提交) ,也能最大限度的减少其他读事务的不可重复读现象。

Redo Log Buffer:如果数据需要写到在线重做日志中,则在写至磁盘之前要在Redo Buffer中临时缓存这些数据。由于内存到内存的传输比内存到磁盘的传输快得多,因此使用重做日志Buffer可以加快数据库的操作。数据在重做缓冲区的停留时间不会太长。实际上LGWR会在以下某个情况发生时启动对这个区的刷新输出(flush):

  • 每3秒一次

  • 无论何时有人提交请求

  • 要求LGWR切换日志文件

  • 重做缓冲区1/3满,或者包含了1MB的缓存重做日志数据

再谈MySQL设计思维:

MySQL是基于share-nothing的设计思维,所有的计算节点和存储节点为自身独享,通过网络来保持主从间的同步。不像oracle的share-disk共享存储的设计架构(所有数据共享一份数据存储);MySQL主从节点为保持数据的一致性,须尽快将主库事务落盘,通过网络把主库的bin-log日志传送至从库,从库根据中继日志relay-log中抽取的sql重新执行,达到主从库数据一致性,然后才能满足业务对从库的数据读取,实现读写分离。为此,MySQL主库在执行完一个增删改的DML操作时,默认进行提交,有助于主库尽快将该事务通过网络同步至从库;也有助于降低读事务和写事务的冲突。

通常,MySQL主从架构在通过半同步方式强化主从库的数据一致性;Innodb cluster使用paxos协议(二阶段提交)来保证集群环境的数据一致性。

总结:

1.四种隔离级别最高的是 Serializable 级别,最低的是 Read uncommitted 级别,当然级别越高,执行效率就越低。像 Serializable 这样的级别,就是以 锁表 的方式(类似于Java多线程中的锁)使得其他的线程只能在锁外等待,所以平时选用何种隔离级别应该根据实际情况。在MySQL数据库中默认的隔离级别为Repeatable read (可重复读) ;

2.在MySQL数据库中,支持上面四种隔离级别,默认的为Repeatable read (可重复读) ;而在 Oracle数据库 中,只支持Serializable (串行化) 级别和 Read committed (读已提交) 这两种级别,其中默认的为 Read committed(读已提交) 级别;

3.通常来说,事务的隔离级别越高,越能保证数据库的完整性和一致性,但相对来说,隔离级别越高,对并发性能的影响也越大。因此,Oracle通常将数据库的隔离级别设置为 Read Committed,即读已提交数据,它既能防止脏读,又能有较好的并发性能。虽然这种隔离级别会导致不可重复读、幻读和第二类丢失更新这些并发问题,但可通过在应用程序中采用悲观锁和乐观锁加以控制;

4.最后,对Oracle默认隔离级别为什么是Read committed(读已提交)和MySQL默认隔离级别为什么是Repeatable read (可重复读)的原因分析在时代背景、场景演变、底层数据库设计逻辑的三个角度进行了个人解读;大家如有疑问和新的看法,欢迎来交流。


参考:

http://c.biancheng.net/view/4220.html

https://blog.csdn.net/li_canhui/article/details/83058068

https://blog.csdn.net/u010960184/article/details/82557978

原题:今天来聊一聊数据库事务的四种隔离性及Oracle和MySQL各自的默认隔离级别及原因分析
有任何问题可点击阅读原文到社区原文下评论交流
觉得本文有用,请转发、点赞或点击“在看”,让更多同行看到


 资料/文章推荐:

  • MySQL数据库设计开发规范(汇总篇)


欢迎关注社区  “数据库”技术主题 ,将会不断更新优质资料、文章。地址:https://www.talkwithtrend.com/Topic/597


下载 twt 社区客户端 APP


长按识别二维码即可下载

或到应用商店搜索“twt”


长按二维码关注公众号

*本公众号所发布内容仅代表作者观点,不代表社区立场



推荐阅读
  • Oracle优化新常态的五大禁止及其性能隐患
    本文介绍了Oracle优化新常态中的五大禁止措施,包括禁止外键、禁止视图、禁止触发器、禁止存储过程和禁止JOB,并分析了这些禁止措施可能带来的性能隐患。文章还讨论了这些禁止措施在C/S架构和B/S架构中的不同应用情况,并提出了解决方案。 ... [详细]
  • 本文讨论了在数据库打开和关闭状态下,重新命名或移动数据文件和日志文件的情况。针对性能和维护原因,需要将数据库文件移动到不同的磁盘上或重新分配到新的磁盘上的情况,以及在操作系统级别移动或重命名数据文件但未在数据库层进行重命名导致报错的情况。通过三个方面进行讨论。 ... [详细]
  • 在Oracle11g以前版本中的的DataGuard物理备用数据库,可以以只读的方式打开数据库,但此时MediaRecovery利用日志进行数据同步的过 ... [详细]
  • MySQL中的MVVC多版本并发控制机制的应用及实现
    本文介绍了MySQL中MVCC的应用及实现机制。MVCC是一种提高并发性能的技术,通过对事务内读取的内存进行处理,避免写操作堵塞读操作的并发问题。与其他数据库系统的MVCC实现机制不尽相同,MySQL的MVCC是在undolog中实现的。通过undolog可以找回数据的历史版本,提供给用户读取或在回滚时覆盖数据页上的数据。MySQL的大多数事务型存储引擎都实现了MVCC,但各自的实现机制有所不同。 ... [详细]
  • Android中高级面试必知必会,积累总结
    本文介绍了Android中高级面试的必知必会内容,并总结了相关经验。文章指出,如今的Android市场对开发人员的要求更高,需要更专业的人才。同时,文章还给出了针对Android岗位的职责和要求,并提供了简历突出的建议。 ... [详细]
  • 本文介绍了如何使用php限制数据库插入的条数并显示每次插入数据库之间的数据数目,以及避免重复提交的方法。同时还介绍了如何限制某一个数据库用户的并发连接数,以及设置数据库的连接数和连接超时时间的方法。最后提供了一些关于浏览器在线用户数和数据库连接数量比例的参考值。 ... [详细]
  • t-io 2.0.0发布-法网天眼第一版的回顾和更新说明
    本文回顾了t-io 1.x版本的工程结构和性能数据,并介绍了t-io在码云上的成绩和用户反馈。同时,还提到了@openSeLi同学发布的t-io 30W长连接并发压力测试报告。最后,详细介绍了t-io 2.0.0版本的更新内容,包括更简洁的使用方式和内置的httpsession功能。 ... [详细]
  • eclipse学习(第三章:ssh中的Hibernate)——11.Hibernate的缓存(2级缓存,get和load)
    本文介绍了eclipse学习中的第三章内容,主要讲解了ssh中的Hibernate的缓存,包括2级缓存和get方法、load方法的区别。文章还涉及了项目实践和相关知识点的讲解。 ... [详细]
  • Java String与StringBuffer的区别及其应用场景
    本文主要介绍了Java中String和StringBuffer的区别,String是不可变的,而StringBuffer是可变的。StringBuffer在进行字符串处理时不生成新的对象,内存使用上要优于String类。因此,在需要频繁对字符串进行修改的情况下,使用StringBuffer更加适合。同时,文章还介绍了String和StringBuffer的应用场景。 ... [详细]
  • 本文介绍了在Oracle数据库中创建序列时如何选择cache或nocache参数。cache参数可以提高序列的存取速度,但可能会导致序列丢失;nocache参数可以避免序列丢失,但在高并发访问时可能导致性能问题。文章详细解释了两者的区别和使用场景。 ... [详细]
  • Python SQLAlchemy库的使用方法详解
    本文详细介绍了Python中使用SQLAlchemy库的方法。首先对SQLAlchemy进行了简介,包括其定义、适用的数据库类型等。然后讨论了SQLAlchemy提供的两种主要使用模式,即SQL表达式语言和ORM。针对不同的需求,给出了选择哪种模式的建议。最后,介绍了连接数据库的方法,包括创建SQLAlchemy引擎和执行SQL语句的接口。 ... [详细]
  • 一句话解决高并发的核心原则
    本文介绍了解决高并发的核心原则,即将用户访问请求尽量往前推,避免访问CDN、静态服务器、动态服务器、数据库和存储,从而实现高性能、高并发、高可扩展的网站架构。同时提到了Google的成功案例,以及适用于千万级别PV站和亿级PV网站的架构层次。 ... [详细]
  • 本文介绍了操作系统的定义和功能,包括操作系统的本质、用户界面以及系统调用的分类。同时还介绍了进程和线程的区别,包括进程和线程的定义和作用。 ... [详细]
  • 从Oracle安全移植到国产达梦数据库的DBA实践与攻略
    随着我国对信息安全和自主可控技术的重视,国产数据库在党政机关、军队和大型央企等行业中得到了快速应用。本文介绍了如何降低从Oracle到国产达梦数据库的技术门槛,保障用户现有业务系统投资。具体包括分析待移植系统、确定移植对象、数据迁移、PL/SQL移植、校验移植结果以及应用系统的测试和优化等步骤。同时提供了移植攻略,包括待移植系统分析和准备移植环境的方法。通过本文的实践与攻略,DBA可以更好地完成Oracle安全移植到国产达梦数据库的工作。 ... [详细]
  • 背景应用安全领域,各类攻击长久以来都危害着互联网上的应用,在web应用安全风险中,各类注入、跨站等攻击仍然占据着较前的位置。WAF(Web应用防火墙)正是为防御和阻断这类攻击而存在 ... [详细]
author-avatar
钟z4z萍
这个家伙很懒,什么也没留下!
PHP1.CN | 中国最专业的PHP中文社区 | DevBox开发工具箱 | json解析格式化 |PHP资讯 | PHP教程 | 数据库技术 | 服务器技术 | 前端开发技术 | PHP框架 | 开发工具 | 在线工具
Copyright © 1998 - 2020 PHP1.CN. All Rights Reserved | 京公网安备 11010802041100号 | 京ICP备19059560号-4 | PHP1.CN 第一PHP社区 版权所有