1.什么是事务
数据库事务(Database Transaction) ,是指作为单个逻辑工作单元执行的一系列操作,要么完全地执行,要么完全地不执行。 事务处理可以确保除非事务性单元内的所有操作都成功完成,否则不会永久更新面向数据的资源。通过将一组相关操作组合为一个要么全部成功要么全部失败的单元,可以简化错误恢复并使应用程序更加可靠。一个逻辑工作单元要成为事务,必须满足所谓的ACID(原子性、一致性、隔离性和持久性)属性。事务是数据库运行中的逻辑工作单位,由DBMS中的事务管理子系统负责事务的处理。
2.事务的ACID属性
2-1.原子性(Atomicity)
一个事务(Transaction)中的所有操作,要么全部完成,要么全部不完成,不会结束在中间某个环节。事务在执行过程中发生错误,会被回滚(Rollback)到事务开始前的状态,就像这个事务从来没有执行过一样。
2-2.一致性(Consistency)
一致性是指事务必须使数据库从一个一致性状态变换到另一个一致性状态,也就是说一个事务执行之前和执行之后都必须处于一致性状态。
2-3.隔离性(Isolation)
数据库允许多个并发事务同时对其数据进行读写和修改的能力,隔离性可以防止多个事务并发执行时由于交叉执行而导致数据的不一致。事务隔离分为不同级别,包括读未提交(Read uncommitted)、读提交(Read committed)、可重复读(Repeatable read)和串行化(Serializable)。
2-4.持久性(Durability)
事务处理结束后,对数据的修改就是永久的,即便系统故障也不会丢失。
在 MySQL 命令行的默认设置下,事务都是自动提交的,即执行 SQL 语句后就会马上执行 COMMIT 操作。因此要显式地开启一个事务务须使用命令 BEGIN 或 START TRANSACTION,或者执行命令 SET AUTOCOMMIT=0,用来禁止使用当前会话的自动提交。
3.事务控制语句
- BEGIN(START TRANSACTION): 显式地开启一个事务。
- ROLLBACK(ROLLBACK WORK): 回滚会结束用户的事务,并撤销正在进行的所有未提交的修改。
- COMMIT(COMMIT WORK): 提交事务,并使已对数据库进行的所有修改成为永久性的。
- SET AUTOCOMMIT=0/1: 禁止自动提交/开启自动提交(默认)
4.隔离级别
4-1.操作隔离级别SQL
mysql> select @@tx_isolation; --查看当前数据库的隔离级别
mysql> set [gloabl | session] trasaction isoslation level 隔离级别名称; -- 设置隔离级别
-- 或
mysql> set tx_isolation='read-uncommitted'; -- 设置隔离级别
4-2.四种隔离级别
隔离级别名称 | 脏读 | 不可重复读 | 幻读 |
---|---|---|---|
READ-UNCOMMITTED | 是 | 是 | 是 |
READ-COMMITTED | 否 | 是 | 是 |
REPEATABLE-READ | 否 | 否 | 是 |
SERIALIZABLE | 否 | 否 | 否 |
MySQL默认的隔离级别是: REPEATABLE-READ
4-3.脏读、不可重复读、幻读
4-3-1.脏读
(针对未提交数据)如果事务A中对数据进行了更新,但事务还没有提交,事务B可以"看到"该事务没有提交的更新结果,这样造成的问题就是,如果事务A回滚,那么,事务B在此之前所"看到"的数据就是一笔脏数据。
4-3-2.不可重复读
(针对其他提交前后,读取数据本身的对比)不可重复读取是指同一个事务在整个事务过程中对同一笔数据进行读取,每次读取结果都不同。如果事务A在事务B的更新操作之前读取一次数据,在事务B的更新操作之后再读取同一笔数据一次,两次结果是不同的,所以,Read Uncommitted也无法避免不可重复读取的问题。
流程描述
- 事务A开启事务
- 事务B开启事务
- 事务A读取一条数据
- 事务B更新事务A读取的数据
- 事务B提交事务
- 事务A读取到了事务B更新的数据
- 事务A提交事务
简单说就是在一个事务中两次读取的数据不一样。
4-3-3.可重复读
可重复读(Repeatable Read),当使用可重复读隔离级别时,在事务执行期间会锁定该事务以任何方式引用的所有行。因此,如果在同一个事务中发出同一个SELECT语句两次或更多次,那么产生的结果数据集总是相同的。因此,使用可重复读隔离级别的事务可以多次检索同一行集,并对它们执行任意操作,直到提交或回滚操作终止该事务。
简单说就是在一个事务中两次读取的数据一定相同。
4-3-4.幻读
多次读取一个范围内的记录(包括直接查询所有记录结果或者做聚合统计), 发现结果不一致(标准档案一般指记录增多, 记录的减少应该也算是幻读)。
与不可重复读的区别是:
- 不可重复读:针对的是某条数据的某个列的改变
- 幻读: 针对的是新增或删除行数的改变
其实对于幻读, MySQL的InnoDB引擎默认的RR级别已经通过MVCC自动帮我们解决了, 所以该级别下, 你也模拟不出幻读的场景; 退回到RC隔离级别的话, 你又容易把幻读和不可重复读搞混淆, 所以这可能就是比较头痛的点吧!
具体可以参考《高性能MySQL》对RR隔离级别的描述, 理论上RR级别是无法解决幻读的问题, 但是由于InnoDB引擎的RR级别还使用了MVCC, 所以也就避免了幻读的出现!
MVCC虽然解决了幻读问题, 但严格来说只是解决了部分幻读问题。
复现问题步骤(数据库隔离级别为RR):
- 开启事务A
- 开启事务B
- 在事务B中查询表A中的所有数据
- 在事务A中向表A中插入一条数据(
insert into category(category_id, name, last_update) value(20, 'Admin', CURRENT_TIMESTAMP);
) - 在事务B中查询表A的所有数据,并没有列出事务B刚刚插入的数据(避免了幻读)
- 提交事务A
- 在事务B中继续查询表A的所有数据,还是没有列出事务B刚刚插入的数据(避免了幻读)
- 在事务B中向表A中插入一条数据(
insert into category(category_id, name, last_update) value(20, 'Admin', CURRENT_TIMESTAMP);
) - 事务B会报错:
ERROR 1062 (23000): Duplicate entry '20' for key 'PRIMARY'
- 提交事务B
这说明什么?
The snapshot of the database state applies to SELECT statements within a transaction, not necessarily to DML statements. If you insert or modify some rows and then commit that transaction, a DELETE or UPDATE statement issued from another concurrent REPEATABLE READ transaction could affect those just-committed rows, even though the session could not query them. If a transaction does update or delete rows committed by a different transaction, those changes do become visible to the current transaction.
个人认为应该翻译为: 数据库状态的快照适用于事务中的SELECT语句, 而不一定适用于所有DML语句。 如果您插入或修改某些行, 然后提交该事务, 则从另一个并发REPEATABLE READ事务发出的DELETE或UPDATE语句就可能会影响那些刚刚提交的行, 即使该事务无法查询它们。 如果事务更新或删除由不同事务提交的行, 则这些更改对当前事务变得可见。