0


MySQL-事务详解

事务

事务是由一系列对数据的访问与更新操作组成的程序执行逻辑单元,以便服务器保证数据完整性

事务是数据库系统区别于其他一切文件系统的重要特性之一

  • 事务是用户定义的一个数据库操作序列,这些操作要么全做,要么全不做,是一个不可分割的工作单位,一个事务可以是一条或多条SQL语句组成,如果其中有任意一条语句不能完成或者产生错误,那么这个单元里所有的sql语句都要放弃执行,所以只有事务中所有的语句都成功地执行了,才可以说这个事务被成功地执行
  • 一般情况下,一个事务对应着一个完整的业务,一段程序也可能包含多个事务,比如说,工资转账,银行转账、商品购物等业务
  • mysql事务主要用于处理操作量大、复杂度高的数据

总结

事务就是用户定义的一个数据库操作序列,这些操作要么全做完要么全都不做,如果发生错误,会被回滚是一个不可分割的工作单位,只有事务中所有的语句都成功地执行了,才可以说这个事务被成功地执行!使用事务以便服务器保证数据完整性,MySQL事务主要用于处理操作量大,复杂度高的数据

ACID

ACID是事务的四大特点

单词也记

原子性(Atomicity)/ˌætəˈmɪsəti/

整个事务中的所有操作,要么全部完成,要么全部不完成,不可能停滞在中间某个环节。事务在执行过程中发生错误,会被回滚(ROLLBACK)到事务开始前的状态,好像这个事务从来没有执行过一样。

补充:

undo log 回滚日志,每条数据的变化(insert/update/delete)都会产生一条记录,并且日志持久化到磁盘,undolog用来记录数据修改前的信息,

  • 比如说要插入一条记录,那么undo log就会记录一条删除该信息的语句,这样你需要回滚的时候,undo log就会执行删除你之前插入的那条记录,达到没有修改前的状态,
  • 更新一条记录也会生成一条sql,记录你更新之前的字段状态,从而实现了原子性。

就是说undo log会生成能让数据库恢复到没修改之前的sql语句,insert就会生成delete,更新值,就会生成更新会原值,相反的sql语句

一致性(Consistency)

事务开始前和结束后,数据的完整性约束没有被破坏,比如A向B转账,不可能A扣了钱,B却没有收到。其实一致性也是因为原子性的一种表现。

一个事务可以封装状态改变(除非它是一个只读的)。事务必须始终保持系统处于一致的状态,不管在任何给定的时间并发事务有多少

也就是说:如果事务是并发多个,系统也必须如同串行事务一样操作。其主要特征是保护性和不变性(Preserving an Invariant),以转账案例为例,假设有五个账户,每个账户余额是100元,那么五个账户总额是500元,如果在这个5个账户之间同时发生多个转账,无论并发多少个,比如在A与B账户之间转账5元,在C与D账户之间转账10元,在B与E之间转账15元,五个账户总额也应该还是500元,这就是保护性和不变性。

原子性,隔离性,持久性保证了一致性

隔离性(Isolation)

同一时间,只允许一个事务请求同一数据

事务和事务之间彼此没有任何干扰。

====比如A正在从一张银行卡里取钱,取钱的过程没有结束前,B不能向这张卡转账。=》串行化(感觉有点像单线程 将两个任务 按顺序执行)

事务是并发控制机制,他们交错使用时也能提供一致性,隔离让我们隐藏来自外部世界未提交的状态变化,一个失败的事务不应该破坏系统的状态。隔离是通过用悲观锁或乐观锁机制实现的。

隔离的实现:

每次修改对应一个事务ID:row trx id,还对应一个undo log,因此undo log和row trx id 是一一对应的。

持久性(Durability)

在事务完成以后,该事务对数据库所作的更改会持久的保存在数据库之中,并不会被回滚。

一个成功的事务将永久性地改变系统的状态,所以在它结束之前,所有导致状态的变化都记录在一个持久的事务日志中。如果我们的系统突然受到系统崩溃或断电,那么所有未完成已提交的事务可能会重演(不会导致数据丢失) (两次提交,应该是先写入持久日志文件算一次提交,后面再提交给系统)

保证了MySQL数据库的高可靠性(High Reliability),而不是高可用(High Availability)

事务的回滚错误理解:只要把事务写出来,最后用commit提交一下,数据库会自动判断这些语句是否全执行陈宫,如果成功则把所有的数据插入到数据库,如果有一条失败就自动回滚到原始状态!

事务的回滚正确的理解:如果事务中所有sql语句执行正确则需要自己手动提交commit;否则有任何一条执行错误,需要自己提交一条rollback,这时会回滚所有操作,而不是commit给你自动判断和回滚

MySQL的innoDB存储引擎,用Redo log保证了事务的持久性。当事务提交时,必须先将事务的所有日志写入日志文件进行持久化,就是我们常说的WAL(write ahead log)机制。这样才能保证断电或宕机等情况发生后,已提交的事务不会丢失,这个能力称为crash-safe

Redo log包括两部分:重做日志缓冲(redo log buffer)和==重做日志文件==(redo log file),前者是易失去的缓存,后者是持久化的文件。

脏读、不可重复读、幻读

脏读

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

实例

比如我给你转了100万,但是我还有提交,此时你查询自己账户,多了100万,很开心,然后发现转错人了,回滚了事务,然后你100万就没了,但是过程中你查到了没有提交事务的数据(多出的100万),这就是脏钱,脏数据。

不可重复读

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

实例

img

幻读

一个事务按相同的查询条件查询之间检索过的数据,却发现检索出来的结果集条数变多或者减少(由其他事务插入、删除的),类似产生幻觉。两次相同的查询条件读出来的数据集条数不一样因为其他事务插入了满足其查询条件的新数据,这种现象就称为“幻读”。

一句话:事务A 读取到了事务B提交的新增数据,不符合隔离性。

实例

img

脏读、不可重复读,幻读的区别

更新丢失:最后的更新覆盖了其他事物之前的更新,而事务之间并不知道,发生更新丢失。更新丢失,完全可以避免,应用对访问的数据加锁即可

脏读(针对未提交的数据):一个事务在更新一条记录,未提交前,第二个事务读到了第一个事务更新后的记录,那么第二个事务读到了脏数据,会产生对第一个未提交数据的依赖。一旦第一个事务回滚,那么第二个事务读到的数据就是错误的脏数据。

不可重复读(读取数据本身的对比):一个事务在读取某些数据后的一段时间内,再次读取这个数据发现其读取出来的数据内容已经发生了改变,就是不可重复读。两次读取同一数据,数据不一致

幻读(读取结果集条数的对比):一个事务按相同的查询条件查询之间检索过的数据,却发现检索出来的结果集条数变多或者减少(由其他事务插入、删除的),类似产生幻觉。两次相同的查询条件读出来的数据集条数不一样

不可重复读和幻读的区别?

不可重复读的重点是修改:在同一事务中,同样的条件,第一次读的数据和第二次读的【数据不一样】(因为中间有其他事务提交了修改)

幻读的重点在于新增或者删除:在同一个事务中,同样的条件,第一次和第二次读取的【记录条数不一样】(因为中间有其他事务提交了插入/删除)

从如何通过锁机制来解决他们产生的问题的角度看:

避免不可重复读需要锁行就锁行(update会影响行中数据)

避免幻读则需要锁表(insert/delete会影响表的记录数)

丢失修改

指在一个事务读取一个数据时,另外一个事务也访问了该数据,那么在第一个事务中修改了这个数据后,第二个事务也修改了这个数据。这样第一个事务内的修改结果就被丢失,因此称为丢失修改。 例如:事务 1 读取某表中的数据 A=20,事务 2 也读取 A=20,事务 1 修改 A=A-1,事务 2 也修改 A=A-1,最终结果 A=19,事务 1 的修改被丢失。

MySQL和Oracle中的默认隔离级别?

MySQL默认事务处理级别是REPEATABLE-READ,也就是可重复读

Oracle默认系统事务隔离级别是READ COMMITTED,也就是读已提交

//查看当前事物级别:
SELECT @@tx_isolation;

//设置read uncommitted级别:
set session transaction isolation level read uncommitted;

//设置read committed级别:
set session transaction isolation level read committed;

//设置repeatable read级别:
set session transaction isolation level repeatable read;

//设置serializable级别:
set session transaction isolation level serializable;

MySQL的事务隔离级别

串行化

(Serializable)

读的时候加共享锁,也就是其他事务可以并发读,但是不能写,写的时候加排它锁,其他事务不能并发写也不能并发读

串行化是4种事务隔离级别中隔离效果最好的解决了脏读、不可重复读、幻读的问题,但是性能最差,它将事务的执行变为顺序执行,与其他三个隔离级别相比,它就相当于单线程,后一个事务的执行必须等待前一个事务结束。

可重复读

(repeatable read)

可重复读是对比不可重复而言的,不可重复读是指同一事务不同时刻读到的数据值可能不一致。而可重复读是指,事务不会读到其他事务对已有数据的修改,即使其他事务已提交,也就是说,==事务开始时读到的已有数据是什么,在事务提交前的任意时刻,这些数据的值都是一样的。==但是,对于其他事务新插入的数据是可以读到的,这就引发了幻读问题

采用MVVC(多版本并发控制)的方式

同样的,需要改全局隔离级别为可重复读级别。

setglobaltransactionisolationlevelrepeatableread;

例子

在这个隔离级别下,启动两个事务,两个事务同时开启。

首先看一下可重复读的效果,事务A启动后修改了数据,并且在事务B之前提交,事务B在事务开始和事务A提交之后两个时间节点都读取的数据相同,已经可以看出可重复读的效果。

读提交

( read committed)

既然读未提交没办法解决脏数据问题,那么就有了读提交,读提交就是一个事务只能读到其他事务已经提交过的数据,也就是其他事务用了commit命令之后的数据,那脏数据问题迎刃而解了。

读提交事务隔离级别是大多数流行数据库的默认事务隔离级别,比如Oracle,但不是MySQL的默认隔离级别

把事务隔离级别改为读提交级别之后同样需要重新打开新的session窗口,也就是新的shell窗口才可以。

例子

同样开启事务A和事务B两个事务,在事务A使用Update语句将id=1的记录行,age字段改为10.此时,事务B使用select语句进行查询,我们发现事务A提交之前,事务B查询到的age一直是1,直到事务A提交,此时在事务B中select查询,发现age的值已经是10了

读未提交

( read uncommitted)

MySQL事务隔离其实是依靠锁来实现的,加锁自然会带来性能的损失。而读未提交隔离级别是不加锁,所以性能是最好的。没有加锁、解锁带来的性能开销。但有利就有弊,这基本上就是裸奔,所以连脏读的问题都没法解决

它是性能最好的,也可以说它是最野蛮的方式,因为它压根儿就不加锁,所以根本谈不上什么隔离效果,可以理解为没有隔离

任何事物对数据的修改都会第一时间暴露给其他业务,即使业务还没有提交。

设置为读未提交后,只对之后新起的session才起作用,对已经启动session无效。如果用shell客户端那就要重新连接MySQL,如果Navicat就要创建新的查询窗口

例子

启动两个事务,分别为事务A和事务B,在事务A中使用update语句,修改age为10,初始是1,在执行完update语句之后,在事务B中查询user表,会看到age的值已经是10了,这时候事务A还没有提交,而此时事务B可能已经拿着已经修改过的age=10去进行其他操作了。在事务B进行操作的过程中,很有可能事务A由于某些原因,进行了事务回滚操作,那事务B拿到的数据就是脏数据了,拿着脏数据去进行其他的计算,那结果肯定也是有问题的。

顺着时间轴表示两事务中操作的执行顺序,重点看图中age字段的值

总结

隔离级别脏读不可重复读幻读串行化(Serializable)解决解决解决可重复读(Repeatable read)默认解决****解决没解决读已提交(Read commited)解决没解决没解决读未提交(Read uncommited)没解决没解决没解决

  • 数据库的事务隔离越严格,并发性能就越低,安全性越高,例如串行化相当于将事务按顺序执行,完全失去了并发性,读未提交相当于没有事务隔离能力,但是并发性能却是最高的。
  • 根据需求选择事务隔离级别,对于一些只读不改的表,我们可以设置读未提交的级别,因为不修改数据的话就不会产生脏读,不可重复读,幻读的问题
标签: mysql 数据库 sql

本文转载自: https://blog.csdn.net/qq_50596778/article/details/122764665
版权归原作者 NeverOW 所有, 如有侵权,请联系我们删除。

“MySQL-事务详解”的评论:

还没有评论