什么是数据库事务
事务是以一种可靠、一致的方式,访问和操作数据库中数据的程序单元。
换句话说:用户定义的一个数据库操作序列,要么全部成功,要么全部失败,不存在中间状态(部分成功,部分失败),并且操作的结果和程序的预设相符合。这里的数据库操作序列通常指的是sql语句。
示例
定义一张用户表account。
id,账户id。
name,姓名。
amount,金额。
有两个用户张三、李四,初始状态张三账户有100元,李四账户有50元。张三希望转50元给李四。实现此需求需要两个update语句,一个是张三扣除50元,另一个是李四增加50元。将这两个update语句使用事物进行包裹(begin transaction和commint),就不会出现一个命令执行成功、一个命令执行失败的情况,要么全部成功,要么全部失败。
![](https://i-blog.csdnimg.cn/blog_migrate/7b88b22f1d24e5024020b693421ba15d.jpeg)
数据库事务的ACID特性
更为具体的描述了满足哪些特性的数据库才可以被称为支持事务的数据库。
原子性(Atomicity)
一个事务(transaction)所有操作,要么全部成功,要么全部失败,不存在中间状态(部分成功,部分失败)。
上述例子中,张三扣50元、李四加50元这两个操作要么全部成功要么全部失败,不会出现(张三扣50元、李四没加50元)和(张三没扣50元、李四加50元)的情况。
一致性(Consistency)
在事务开始之前和事务结束以后,数据库的完整性没有被破坏。这表示写入的资料必须完全符合所有的预设规则,这包含资料的精确度、串联性以及后续数据库可以自发性地完成预定的工作。
一致性与原子性看起来比较类似,大白话:原子性关心的命令要么全部执行、要么全部不执行,不关心执行结果对错。一致性关心的是结果也要是对的。
举个例子:你预设的逻辑是张三扣50元、李四加50元。实际的执行结果张三扣50元、李四仅加了40元,这个两操作都成功了满足原子性,但是由于某些原因李四少加了10元,这就和一致性不相符合了。
隔离性(Isolation)
数据库允许多个事务并发的对相同的数据进行读写操作,一个事务修改了数据没有提交,另外一个事务能不能看见没有提交的数据,以及其他更为复杂的情况,因此引出了隔离性。
隔离级别包括:
读未提交(Read uncommitted)
读已提交(read committed)
可重复读(repeatable read),mysql默认的隔离级别。
串行化(Serializable)
上述的隔离级别是低到依次增加的,串行化(Serializable)的隔离级别最高。后续针对不同的隔离级别进行详细说明。
mysql查看对应的隔离级别
select @@GLOBAL.tx_isolation,@@tx_isolation;
![](https://i-blog.csdnimg.cn/blog_migrate/eacb53320dc40044417a8fcc3c43236d.jpeg)
mysql设置针对单个session的隔离级别
-- 读未提交
set transaction isolation level read uncommitted;
-- 读已提交
set transaction isolation level read committed;
-- 可重复读
set transaction isolation level repeatable read;
-- 序列化读
set transaction isolation level serializable;
持久性(Durability)
事务处理结束后,对数据的修改就是永久的,即便系统故障也不会丢失。
这个特性比较容易理解,实际的操作就是将操作的结果已经写入到磁盘。
隔离性(Isolation)- 读未提交(Read uncommitted)
一个事务中修改的数据还没有提交,另一个事务就能读取到已修改,但是没有提交的结果。
以张三给李四转50块钱为例
初始状态
![](https://i-blog.csdnimg.cn/blog_migrate/4dcc7807e3daf465e7796bfc3aacb395.jpeg)
Navicat开两个查询窗口,并将隔离级别都设置为读未提交(Read uncommitted)。
先执行事务A,仅执行选中的语句。
![](https://i-blog.csdnimg.cn/blog_migrate/52bd681decbc3e65c6399025c36f6628.jpeg)
在执行事务B,仅执行选中的语句,可以看见查询的结果张三的金额已经变成50元。读取到这种没有提交的数据也叫脏读。
![](https://i-blog.csdnimg.cn/blog_migrate/d91f161c05ad7b8f986802d6d68bd8af.jpeg)
隔离性(Isolation)- 读已提交(read committed)
读已提交相对于读未提交而言,事务中只能读取到已提交的数据,不能读取到中间状态。
仍以张三给李四转50块钱为例
初始状态
![](https://i-blog.csdnimg.cn/blog_migrate/4dcc7807e3daf465e7796bfc3aacb395.jpeg)
Navicat开两个查询窗口,并将隔离级别都设置为读未提交(Read committed)。
先执行事务A,仅执行选中的语句。
![](https://i-blog.csdnimg.cn/blog_migrate/6d2da6593a9b500d676a9fef55fb7553.jpeg)
在执行事务B,仅执行选中的语句,可以看见查询的结果张三的金额仍是100元。读取不到没有提交的结果。
![](https://i-blog.csdnimg.cn/blog_migrate/c03a84779f0c4e632e5b428ee619edf4.jpeg)
在此增加一个幻读的概念
先执行事务A,查询id>1的用户,仅执行选中的语句。查询结果只有李四。
![](https://i-blog.csdnimg.cn/blog_migrate/51ceab8db4770757642ed1346d9f16d7.jpeg)
在执行事务B,插入一条王五,id为3。
![](https://i-blog.csdnimg.cn/blog_migrate/c474c1bab282a094bf74a2e336701490.jpeg)
此时在事务A中,第二次执行查询id>1的用户。同一个事务中,两次查询的结果不相同,这个就称为幻读。在读已提交(read committed)这个隔离级别下,会发生幻读的情况。
![](https://i-blog.csdnimg.cn/blog_migrate/57681f0e6f348bf7c4a9c16f8b51b8e3.jpeg)
隔离性(Isolation)- 可重复读(repeatable read)
可重复读相对于读已提交解决了大部分的幻读情况,有小部分的情况是解决不了的。
解决在读已提交(read committed)隔离级别下发生幻读的情况
初始状态
![](https://i-blog.csdnimg.cn/blog_migrate/4dcc7807e3daf465e7796bfc3aacb395.jpeg)
Navicat开两个查询窗口,并将隔离级别都设置为可重复读(repeatable read)。
可重复读(repeatable read)隔离级别下,执行在读已提交(read committed)发生幻读的例子。
先执行事务A,查询id>1的用户,仅执行选中的语句。查询结果只有李四。
![](https://i-blog.csdnimg.cn/blog_migrate/021ccfb2d429e88baea2e253c51cb418.jpeg)
在执行事务B,插入一条王五,id为3。
![](https://i-blog.csdnimg.cn/blog_migrate/982e3f6a133f1206b55ec0bef27768f0.jpeg)
此时在事务A中,第二次执行查询id>1的用户。同一个事务中,两次查询的结果相同,没有发生在读已提交(read committed)隔离级别下发生的幻读问题。
![](https://i-blog.csdnimg.cn/blog_migrate/d4130a98b9c2690f892f588f9d5e8094.jpeg)
在看下可重复读这个隔离级别下解决不了的幻读问题。
初始状态
![](https://i-blog.csdnimg.cn/blog_migrate/4dcc7807e3daf465e7796bfc3aacb395.jpeg)
Navicat开两个查询窗口,并将隔离级别都设置为可重复读(repeatable read)。
先执行事务A,查询id=3的账户,查询结果为空。
![](https://i-blog.csdnimg.cn/blog_migrate/f7b187d69c62c9103495b4d734f35a19.jpeg)
在行事务B,插入一条id=3的王五。
![](https://i-blog.csdnimg.cn/blog_migrate/ee94cb1fa449ae282b337e327b9c7254.jpeg)
再次执行事务A中的查询,查询id=3的账户,查询结果仍为空。因为隔离级别是可重复读(repeatable read),这样是没有问题的。接着往下看。
![](https://i-blog.csdnimg.cn/blog_migrate/4122c7bf3a546cbd602360cd9232dc4c.jpeg)
再次执行事务A中的更新语句,更新账户id=3的账户金额为100元,此时更新成功了,有一条数据被更改,这就发生了幻读。
![](https://i-blog.csdnimg.cn/blog_migrate/216eccf1b6460c4670a629cb113be45d.jpeg)
发生上述情况的原因是,在可重复读(repeatable read)这个级别下,普通的查询采用的是快照读的方式来实现可重复读。但是向select ... for update、update、insert、delete采用的是当前读,也就是读取最新的已提交的数据。
综上所述,在可重复读(repeatable read)这个隔离级别下不能完全的解决幻读的问题。换句话说:在可重复读(repeatable read)这个隔离级别下,在一个事务中即使用快照读又使用当前读极有可能发生幻读。但是在实际生产与开发过程中,在一个事务中进行二次查询的场景非常少见,或者说即使是有这种场景,也需要用其他的方案来替代,事务里的操作应尽可能的简短,更不应该有类似于查询列表的操作。
隔离性(Isolation)- 串行化(Serializable)
串行化的实现采用的是读写都加锁的原理。
串行化的情况下,对于同一行事务,写会加写锁,读会加读锁。当出现读写锁冲突的时候,后访问的事务必须等前一个事务执行完成,才能继续执行。