MYSQL 的事务处理
事务是一个最小的不可再分的工作单元,通常一个事务对应一个完整的业务。以转账操作为例,常见的转账操作一般对应如下 SQL
语句:
1 | -- A: 500 B: 100, A 向 B 转 100 |
这段语句有一个隐患,当执行完第一条语句后突然出现了异常导致第二条语句无法执行,导致 B 没有收到 A 的转账,这是不可接受的。所以,这两条语句必须同时成功或者同时失败。开启一个事务,当 UPDATE
语句执行成功后,并不是把底层数据库中的数据修改,只是将操作记录了一下,这个记录是在内存中完成的。一直到执行完所有语句后,才把之前的所有改动全部同步到底层数据库。若期间遭遇异常导致执行失败,则清空所有的历史操作记录(即回滚)。
在 MySQL 命令行的默认设置下,事务都是自动提交的,即执行 SQL 语句后就会马上执行 COMMIT 操作(即同步到底层数据库)。因此要显式地开启一个事务须使用命令 BEGIN 或 START TRANSACTION,或者执行命令 SET AUTOCOMMIT=0 用来禁止使用当前会话的自动提交。
1 | SET AUTOCOMMIT=0 禁止自动提交 |
事务四大特性
谈到事务一般都是以下四点:
原子性(Atomicity)
事务是一个不可分割的工作单位,事务中的操作要么都发生,要么都不发生。
一致性(Consistency)
事务前后数据的完整性必须保持一致。比如 A 向 B 转账,不可能 A 扣了钱,B 却没收到。
隔离性(Isolation)
数据库允许多个并发事务同时对其数据进行读写和修改的能力,隔离性可以防止多个事务并发执行时由于交叉执行而导致数据的不一致。
持久性(Durability)
事务处理结束后,对数据的修改就是永久的,即便系统故障也不会丢失。
事务的隔离级别
事务的隔离级别一共有 4 个
读未提交(read uncommitted)
这是最低的隔离级别,指一个事务可以读取另外一个事务未提交的数据,这会导致脏读:
1 | -- A 给 B 转账,B 发货 |
如果 B 账户的隔离级别很低,它会读取到 A 还没有同步到底层数据库的数据(读内存),以为 A 已经转账便发货,然后 A 回滚了数据,B 就遭受了损失。一般数据库的默认隔离级别都高于此。
脏读的本质是直接读取未提交的数据会产生很大的风险,因为未提交的数据很可能不会同步到底层数据库。
读已提交(read committed)(Oracle 默认隔离级别)
指一个事务提交了的数据才可以被另外一个事务读取(读硬盘),这会导致不可重复读。
不可重复读指在一个事务内读取表中的某一行数据,多次读取结果不同。比如事务 A 读取了一个数据,在事务 A 还没结束时,事务 B 也访问了这个数据,修改了这个数据并提交。然后事务 A 又读这个数据。由于事务 B 的修改,导致事务 A 两次读到的的数据是不一样的。
这在某些场合可能导致错误,因为一般情况下,你只会做一次查询,并以这一次的查询数据,作为后续计算的基础。因为允许出现不可重复读。那么任何时候,查询到的数据,都有可能被其他事务更新,查询的结果将是不确定的。
可重复读(repeatable read)(MySQL 默认隔离级别)
拿不可重复读的例子而言,事务 A 不会读取到事务 B 修改后的数据,即保证了事务 A 读取的数据一直一样(加行级锁)。但这会导致幻读。
幻读,也叫虚读,是指在一个事务内读取到了别的事务插入的数据,导致前后读取不一致。比如事务 A 读取了数据库目前全部数据,在事务 A 还没结束时,事务 B 插入了一条数据并提交。然后事务 A 又读取了全部的数据,发现多了一条数据,导致前后读取数据不一致。
串行化(serializable)
这是最高的隔离级别,指一个事务在操作数据库时,其他事务只能排队等待。
因为事务操作数据库的时候不可能存在其他事务操作数据库,所以也就不可能出现任何读取问题。不过这会影响数据库的并发量,因此这种隔离级别很少使用。
不可重复读与幻读的区别
不可重复读针对的是事务运行期间其他事务提交的 UPDATE
与 DELETE
操作。幻读针对的是 INSERT
操作。
对于 UPDATE
与 DELETE
操作即不可重复读,因为其只操作某一行,所以只需要加行级锁
,锁住这一行的数据就可以防止。而对于 INSERT
操作即幻读,就得添加表级锁
,锁住整张表,那么其他事务只能排队即串行化。