事务存在的意义?
一个后台服务需要响应很多客户端的请求,在代码层面上一般都是无状态的,业务数据通常都需要保存到数据库中。数据库就是一个状态的载体、一个大的”共享区域“。
当存在共享区域时,我们在编程的过程中就需要考虑各种各样的并发问题。所以”事务“就出现了,将数据的管理全部放到数据库一层,大大简化了我们的编程模型。
例如在”转账业务“下,A给B转账100元时,需要先从A账户余额中扣除100元,再给B账户余额增加100元。在业务上,”先扣减再增加“这一行为是连贯的、不可分割的。如果A扣减100后,服务崩溃了,那这100元也不能凭空消失。在业务代码层面是很难保证的,需要通过数据库的”事务“来实现这一需求。
在mysql中,事务可以通过begin开启,通过 commit提交。
事务的ACID特征
原子性(Atomicity)
对于一个事务来说,事务内的所有操作都是一个整体,要么全部成功,要么全部失败。
begin;
update XXXX;
update XXXX;
commit;
如果第二个update执行失败,事务将进行”回滚“,回滚到这个事务执行前的状态,第一个update的影响将被消除。
一致性(Consistency)
”一致性“是事务的最终目的,同时也是一种手段。
AID三种特征的目的在于确保”系统能够从一个正确的状态到另一个正确的状态“,从而达到系统的”一致性“。
但同时”一致性“还指:事务在执行的过程中,如果不满足设定的约束,例如某个字段要求NOT NULL,而事务中插入了一个NULL值(属于数据库抛了一个正确的“异常”),数据库也应该进行回滚,回到该事务执行前的状态。
隔离性(Isolation)
数据库允许多个事务同时执行,事务与事务之间是隔离开的,互不影响。隔离性保证了多个事务并发执行时由于交叉执行而导致的数据库不一致性。
持久性(Durability)
事务处理完毕后,所做的修改会永久性更新到数据库中,即使发生故障,也不会丢失。
事物的隔离级别
读未提交(READ UNCOMMIT)
一个事务还没提交呢,另一个事务就能读到这个事务做的变更。
读提交(READ COMMIT)
一个事务在提交之后,所做的变更才能被其他事务看到。
可重复读(REPEATABLE READ)
一个事务开始后,所读到的数据都是同一版本的,即使此时其他事务修改了数据并提交了事务,对于当前事务来说,也是透明的。
RR是Mysql默认的隔离级别。
串行化(SERIALIZABLE)
对数据记录加上读写锁,多个事务进行读写操作时,如果有冲突,会一个一个执行。后一个事务必须等待前面的事务执行完毕后才能执行。
脏读、不可重复读、幻读
脏读
当事务级别为RU时,可能产生”脏读“。一个事务还未提交时,其所做的修改已经可以被其他事务读到,这就是”脏读“。
不可重复读
现如今有两个事务,A事务,B事务。
A事务执行了sql1,得到了查询结果result1,此时A事务还未提交。
B事务执行了sql2,更改了某些列得值,并提交了事务。
A事务再次执行sql1,得到了不同于result1得查询结果result2。
同一个事务中相同的sql,因为其他事务的影响(已提交,如果受到未提交事务的影响,叫脏读),得到了不同的查询结果,这属于“不可重复读”。
幻读
幻读和不可重复读类似,但不可重复读通常指的是读取一条记录,幻读指的是多次读取一个范围内的记录。(包含查询所有结果以及聚合统计)
RR隔离级别理论上是无法解决幻读问题的,但是InnoDB引擎通过MVCC解决了一部分的幻读问题。(有争议)
通过下面的示例来说明:
现有表a,只有一个字段:id,并且为主键
打开两个终端,分别开始事务A、事务B。
事务A执行查询SQL
mysql> select * from a;
+----+
| id |
+----+
| 1 |
| 2 |
+----+
2 rows in set (0.01 sec)
事务B插入一条数据,并提交。
mysql> begin;
Query OK, 0 rows affected (0.00 sec)
mysql> insert into a values(3);
Query OK, 1 row affected (0.00 sec)
mysql> commit;
Query OK, 0 rows affected (0.00 sec)
事务A再次执行查询语句
mysql> begin;
Query OK, 0 rows affected (0.00 sec)
mysql> select * from bin;
1146 - Table 'test.bin' doesn't exist
mysql> select * from a;
+----+
| id |
+----+
| 1 |
| 2 |
+----+
2 rows in set (0.01 sec)
mysql> select * from a;
+----+
| id |
+----+
| 1 |
| 2 |
+----+
2 rows in set (0.01 sec)
mysql>
结果依然是1、2,这是“可重复读”的体现。
当然,这是MVCC中的“快照读”,如果sql中指定“for update”或者是“lock in share mode”,则是“当前读”,可以读到最新的数据(注意事务B已提交,当前事务A还未提交)。
mysql> select * from a;
+----+
| id |
+----+
| 1 |
| 2 |
+----+
2 rows in set (0.01 sec)
mysql> select * from a for update;
+----+
| id |
+----+
| 1 |
| 2 |
| 3 |
+----+
3 rows in set (0.01 sec)
mysql> select * from a lock in share mode;
+----+
| id |
+----+
| 1 |
| 2 |
| 3 |
+----+
3 rows in set (0.01 sec)
在事务A还未提交前,执行更新操作
mysql> update a set id = 4 where id = 3;
Query OK, 1 row affected (0.00 sec)
Rows matched: 1 Changed: 1 Warnings: 0
再提交A事务。会发现3已经被改成4了。
对于A来说,快照读是读不到最新的数据,但是使用update操作时,却能连同“看不到”的数据一起改变了。
update、delete、insert等操作,在执行前需要检索范围,这个检索的过程是一种”当前读“,读取的是最新版本的值,而非事务所在版本的值。
如果把这一现象归结为”幻读“,那MySQL的确实并没有通过MVCC彻底解决幻读问题。
如有错误,欢迎指正。