首先,我们来看一个很常见的操作——转账。 假设现在用户A要给用户B转账100元。这个过程可以分为两个步骤: 首先,A用户的账户数据首先减去100元 然后,B用户的账户数据在增加100元 当这两个步骤都完成的时候,转账的这个过程才算完成。
但是我们知道,现实世界有着各种突发情况。假如当用户A的账户减少100元之后,由于某些突发故障,比如系统崩坏,网络故障等,转账操作未能完成。此时,我们会发现,A账户少了100元,但是B账户的余额没变,也就是说用户A莫名其妙的少了100元。
1.概念 从用户的观点来看,数据库中一些操作的集合被认为是一个独立单元,比如转账操作。 这些操作要么全部发生,要么由于出错而全不发生。
构成单一逻辑工作单元的操作集合,我们称为事务 (transaction)。 即使有故障,数据库系统也必须保证事务的正确执行——要么执行整个事务,要么属于该事务的操作一个也不执行。
2.事务的基本操作 START TRANSACTION / BEGIN:开启一个事务,标记事务的起点 COMMIT:提交事务,表示事务成功被执行。 ROLL BACK:回滚事务,回滚到初始状态或者回滚点。 SAVEPOINT:回滚点 RELEASE SAVEPOINT:删除回滚点 SET TRANSACTION: 设置隔离级别
注意事项: (1)START TRANSACTION 标志事务的开始,在 MySQL 中可以用 set autocommit = 0 替代。 (2)结束事务的情况只有两种: a. COMMIT:表示事务成功被执行,结束事务。 b. 发生故障:结束事务, 不管有没有设置回滚点,都会回到事务开启前的状态。 (3)ROLLBACK:不表示发生故障, 它也是一个数据库操作,属于事务的一部分。表示回滚事务,回滚到事务开启前的状态,或者中间的某个回滚点。要想 rollback 生效,必须要 commit。
1.并发执行事务可能会引发的问题——脏写 最终,A 账户有1100元。
这个现象,我们称之为脏写。 脏写是指当多个事务并发写同一数据时,先执行的事务所写的数据会被后写的数据覆盖。 脏写会导致更新丢失。就好像先提交的事务根本没有执行一样。
2.并发执行事务可能会引发的问题——脏读 你会发现 T2 计算的结果是 1900,这肯定是不正确的,数据不一致了!
这种现象我们称之为脏读。 如果一个事务A向数据库写数据,但该事务还没提交或终止,另一个事务B就看到了事务A写入数据库的数据,这个现象我们称为脏读。 发生场景:两个事务并发执行,一个事务写数据,另一个事务读数据,但是可以读取未提交的数据。
3.并发执行事务可能会引发的问题——不可重复读 不可重复读是指:一个事务有对同一个数据项的多次读取,但是在某前后两次读取之间,另一个事务更新该数据项,并且提交了。在后一次读取时,感知到了提交的更新。这个现象我们称为不可重复读。
一个事务至少读两次数据,一个事务写数据。在前后两次读取之间,感知到了另一个事务提交的更新。
可能有同学觉得不可重复读,读取的是已提交的数据,那么就是“正确的”数据,不应该是一个问题。 这时,我们回顾以下隔离性的定义,你会发现写数据的事务对读数据的事务,确实产生了影响!也就是说没有做到完全隔离。
有些情况下,不可重复读确实不是什么大的问题。但是,在有些业务场景下,我们应该避免不可重复读。
4.并发执行事务可能会引发的问题——幻读 一个事务需要进行前后两次统计,在这两次统计期间,另一个事务插入了新的符合统计条件的记录,并且提交了。导致前后两次统计的数据不一致。这种现象,我们称之为幻读。 从这个事务的视角来看,平白无故多了几条数据。就像产生了幻觉一样。
既然并发执行事务可能会引发这么多问题,那么我们怎么应对呢? 数据库提供了不同的隔离级别来应对不同的问题。
SQL 标准规定了四种隔离级别,隔离性依次增高,分别为
读未提交(read uncommitted) 允许读取未提交的数据。读已提交(read committed) 只允许读取已提交数据,但不要求可重复读。比如,在事务两次读取一个数据项期 间,另一个事务更新了该数据项并提交。可重复读(repeatable read) 只允许读取已提交数据,而且在一个事务两次读取一个数据项期间,(在该事物看来,也就是感知不到)其他事务不得更新该数据。但该事务不要求与其他事务可串行化。比如,在两次统计查询中,另一个事务可以插入一些记录,当这些记录中有符合查询条件的,那么就会产生幻读。可串行化(serializable) 看起来事务就好像是串行执行的一样。一个事务结束后,另一个事务才开始执行。 以上所有隔离级别都不允许脏写(dirty write),即如果有一个数据项已经被另一个尚未提交或中止的事务写入,则该事务不能对该数据项执行写操作。我们可以通过下面命令,查看和设置 MySQL 的隔离级别。 select @@[session|global.]transaction_isolation; set [session|global] transaction isolation level read uncommitted.
注意: MySQL 支持4种隔离级别,默认为 RR (repeatable read); Oracle 只支持 read committed 和 serializable 两种隔离级别,默认为 read committed.
#个人学习记录,如发现有错误之处,欢迎与我交流