事务是由一系列对数据的访问与更新操作组成的程序执行逻辑单元,以便服务器保证数据完整性
事务是数据库系统区别于其他一切文件系统的重要特性之一
事务是用户定义的一个数据库操作序列,这些操作要么全做,要么全不做,是一个不可分割的工作单位,一个事务可以是一条或多条SQL语句组成,如果其中有任意一条语句不能完成或者产生错误,那么这个单元里所有的sql语句都要放弃执行,所以只有事务中所有的语句都成功地执行了,才可以说这个事务被成功地执行
一般情况下,一个事务对应着一个完整的业务,一段程序也可能包含多个事务,比如说,工资转账,银行转账、商品购物等业务
mysql事务主要用于处理操作量大、复杂度高的数据
总结
事务就是用户定义的一个数据库操作序列,这些操作要么全做完要么全都不做,如果发生错误,会被回滚是一个不可分割的工作单位,只有事务中所有的语句都成功地执行了,才可以说这个事务被成功地执行!使用事务以便服务器保证数据完整性,MySQL事务主要用于处理操作量大,复杂度高的数据
ACID是事务的四大特点
单词也记
原子性(Atomicity)/ˌætəˈmɪsəti/
整个事务中的所有操作,要么全部完成,要么全部不完成,不可能停滞在中间某个环节。事务在执行过程中发生错误,会被回滚(ROLLBACK)到事务开始前的状态,好像这个事务从来没有执行过一样。
补充:
undo log 回滚日志**,每条数据的变化(insert/update/delete)都会产生一条记录,并且日志持久化到磁盘**,undolog用来记录数据修改前的信息,
就是说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万),这就是脏钱,脏数据。
是指在一个事务内,多次读同一数据。在这个事务也访问该同一数据。那么,在第一个事务中的两次读数据之间,由于第二个事务的修改,那么第一个事务两次读到的数据可能是不一样的。这样就发生了在一个事务内两次读到的数据是不一样的,因此称为是不可重复读。
实例
一个事务按相同的查询条件查询之间检索过的数据,却发现检索出来的结果集条数变多或者减少(由其他事务插入、删除的),类似产生幻觉。两次相同的查询条件读出来的数据集条数不一样,因为其他事务插入了满足其查询条件的新数据,这种现象就称为“幻读”。
一句话:事务A 读取到了事务B提交的新增数据,不符合隔离性。
实例
更新丢失:最后的更新覆盖了其他事物之前的更新,而事务之间并不知道,发生更新丢失。更新丢失,完全可以避免,应用对访问的数据加锁即可
脏读(针对未提交的数据):一个事务在更新一条记录,未提交前,第二个事务读到了第一个事务更新后的记录,那么第二个事务读到了脏数据,会产生对第一个未提交数据的依赖。一旦第一个事务回滚,那么第二个事务读到的数据就是错误的脏数据。
不可重复读(读取数据本身的对比):一个事务在读取某些数据后的一段时间内,再次读取这个数据发现其读取出来的数据内容已经发生了改变,就是不可重复读。两次读取同一数据,数据不一致
幻读(读取结果集条数的对比):一个事务按相同的查询条件查询之间检索过的数据,却发现检索出来的结果集条数变多或者减少(由其他事务插入、删除的),类似产生幻觉。两次相同的查询条件读出来的数据集条数不一样
不可重复读的重点是修改:在同一事务中,同样的条件,第一次读的数据和第二次读的【数据不一样】(因为中间有其他事务提交了修改)
幻读的重点在于新增或者删除:在同一个事务中,同样的条件,第一次和第二次读取的【记录条数不一样】(因为中间有其他事务提交了插入/删除)
从如何通过锁机制来解决他们产生的问题的角度看:
避免不可重复读需要锁行就锁行(update会影响行中数据)
避免幻读则需要锁表(insert/delete会影响表的记录数)
指在一个事务读取一个数据时,另外一个事务也访问了该数据,那么在第一个事务中修改了这个数据后,第二个事务也修改了这个数据。这样第一个事务内的修改结果就被丢失,因此称为丢失修改。 例如:事务 1 读取某表中的数据 A=20,事务 2 也读取 A=20,事务 1 修改 A=A-1,事务 2 也修改 A=A-1,最终结果 A=19,事务 1 的修改被丢失。
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;
(Serializable)
读的时候加共享锁,也就是其他事务可以并发读,但是不能写,写的时候加排它锁,其他事务不能并发写也不能并发读
串行化是4种事务隔离级别中隔离效果最好的,解决了脏读、不可重复读、幻读的问题,但是性能最差,它将事务的执行变为顺序执行,与其他三个隔离级别相比,它就相当于单线程,后一个事务的执行必须等待前一个事务结束。
(repeatable read)
可重复读是对比不可重复而言的,不可重复读是指同一事务不同时刻读到的数据值可能不一致。而可重复读是指,事务不会读到其他事务对已有数据的修改,即使其他事务已提交,也就是说,==事务开始时读到的已有数据是什么,在事务提交前的任意时刻,这些数据的值都是一样的。==但是,对于其他事务新插入的数据是可以读到的,这就引发了幻读问题
采用MVVC(多版本并发控制)的方式
同样的,需要改全局隔离级别为可重复读级别。
set global transaction isolation level repeatable read;
例子
在这个隔离级别下,启动两个事务,两个事务同时开启。
首先看一下可重复读的效果,事务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) | 没解决 | 没解决 | 没解决 |