事务的特性(ACID)
- Atomicity 原子性
- 事务中的一组操作,要么全部成功,要么全部失败。
- 事务的原子性通过
undo日志
实现
- Consistency 一致性
- 事务开始前和结束后,数据库的完整性没有被破坏。比如:A向B转账,不能A扣款成功,而B没有收到。
- Isolation 隔离性
- 多个事务并发执行时,事务之间不会互相影响。
- 事务的隔离行由
锁
和MVCC机制
保证。
- Durability 持久性
- 事务一旦提交,对数据库的变更就是永久的。
- 事务的永久性通过
redo日志
实现
MySQL事务隔离级别
- 读未提交
最低的隔离级别,允许事务读取其他事务
已修改但尚未提交
的数据
- 读已提交=>
常用的隔离级别
允许事务读取其他事务已提交的数据,可以
避免脏读
正常来讲,事务之间应该互不干扰,该级别允许在当前事务中读取其他事务已经修改并提交的数据
- 可重复读
InnoDB 存储引擎的默认支持的隔离级别
=>常用的隔离级别
事务中对
同一字段在不同时刻读取的结果都一致
,可以避免脏读和不可重复读
事务开始后第一条查询语句执行时,数据库生成所有表数据
的快照数据,后续该事务中所有查询都查询的快照数据
,而非数据库最新数据。
除非该事务中对某条数据做了更新操作,那么后续查询到的就是数据库更新后的值
不能解决幻读问题:如果在事务A中对事务B提交的新增数据进行更新操作,那么下次查询就可以查询到该数据
- 可串行化
最高的隔离级别,所有事务都依次执行,可以
避免脏读、不可重复读和幻读
对于同一行记录,读会加读锁
,写会加写锁
,当出现读写冲突时,后一个事务必须等前一个事务结束
如何选择隔离级别
并发要求高的,选择 读已提交
对数据统一要求高的,选择 可重复读
因为隔离级别越高,并发性能越低
并发事务带来的问题
- 脏读
事务A读取到了事务B已修改未提交
的数据
事务B中修改了数据,但事务还未提交,此时事务A就查询到了修改后的数据 - 不可重复读
主要是针对更新和删除操作
事务内部相同语句在不同时刻读取到的值不同
例:
隔离级别为读已提交
时,
①事务A第一次查询到的数值为5000,
②事务B修改数值为4500,
③事务A查询到的数值为5000(没有出现脏读),
④事务B提交事务,
⑤事务A再次查询到的数值就是4500,与前两次查询到的数值不同 - 幻读
主要是针对插入操作
事务A读取到了事务B已提交的数据
首先来看看 MySQL 文档是怎么定义幻读(Phantom Read)的:
The so-called phantom problem occurs within a transaction when the same query produces different sets of rows at different times. For example, if a SELECT is executed twice, but returns a row the second time that was not returned the first time, the row is a “phantom” row.
翻译:当同一个查询在不同的时间产生不同的结果集
时,事务中就会出现所谓的幻象问题。例如,如果 SELECT 执行了两次,但第二次返回了第一次没有返回的行,则该行是“幻像”行。
例:
隔离级别设置为 读已提交
①事务A按条件查询到一条数据
②事务B插入一条数据,并提交
③事务A再次查询,查到两条数据
若此时事务B回滚,那么之前事务A查到的两条数据就像幻觉一样
SQL 脚本 1 在第一次查询工资为 500 的记录时只有一条,SQL 脚本 2 插入了一条工资为 500 的记录,提交之后;SQL 脚本 1 在同一个事务中再次使用当前读查询发现出现了两条工资为 500 的记录这种就是幻读。
解决幻读的方法
解决幻读的方式有很多,但是它们的核心思想就是一个事务在操作某张表数据的时候,另外一个事务不允许新增或者删除这张表中的数据了。
解决幻读的方式主要有以下几种:
1. 将事务隔离级别调整为 SERIALIZABLE
。
2. 在可重复读的事务级别下,给事务操作的这张表添加表锁。
3. 在可重复读的事务级别下,给事务操作的这张表添加 Next-key Lock(Record Lock+Gap Lock)。
- 脏写(更新丢失)
读未提交、读已提交、可重复读 这3种隔离级别都不能解决脏写问题
串行化可以解决吗??
事务B修改了事务A已修改未提交的数据
多个事务更新同一条数据,可能发生更新丢失
①事务A更新了一条数据
②事务B也更新了这条数据
③事务A回滚,把该数据回滚到之前的值,导致事务B的更新丢失
**解决脏写的主要方式:**不要在java代码中对数据进行计算,而是通过sql语句执行计算
MySQL 命令行的默认配置中事务都是自动提交的,即执行 SQL 语句后就会马上执行 COMMIT 操作。如果要显式地开启一个事务
需要使用命令:
START TRANSACTION。
我们可以通过下面的命令来设置隔离级别
。
SET [SESSION|GLOBAL] TRANSACTION ISOLATION LEVEL [READ UNCOMMITTED|READ COMMITTED|REPEATABLE READ|SERIALIZABLE]
我们再来看一下我们在下面实际操作中使用到的一些并发控制语句:
- START TRANSACTION |BEGIN:显式地开启一个事务。
- COMMIT:提交事务,使得对数据库做的所有修改成为永久性。
- ROLLBACK:回滚会结束用户的事务,并撤销正在进行的所有未提交的修改。