本文地址:https://www.askmac.cn/archives/mysql-transactions.html
7.4 事务
将多个SQL语句组成为一个具有原子性操作的机制,我们称之为事务。事务中其中所有语句要么执行成功,要么都失败。一旦所有语句执行成功,整个事务对记录数据的操作就会被永久保存下来。如果事务中发生错误,其中进行的所有操作会被回滚并取消,数据库数据将回到其开始事务之前的状态。
例如,如果你尝试将1000元钱从一张A银行行转到B卡中,但你发现A卡中扣去了钱,而B卡中1000元未到账,你一定不会高兴。为了避免此类问题,程序中在处理此种需求时,首先会启用一个事务,然后在事务中发送进行欠款转账的命令。只有当所有命令都成功执行后才结束事务。如果在事务中所执行的命令报错,那么事务中所做的所有数据修改就会被回滚到启动事务之前的样子。整个过程看起来像这样:
什么是ACID?
我们知道事务系统一般都声称其遵循ACID,那什么是ACID呢?
Atomic | 原子性。所有语句被作为一个整体单元,执行成功或全部取消。 |
Consistent | 一致性。从事务开始至事务结束,其中数据库的状态始终保持一致。 |
Isolated | 隔离性。一个事务不会对另一个事务产生影响。 |
Durable | 持久性。由事务所产生的数据变更都会被成功记录在数据库中。这些改变不会丢失。 |
事务处理为数据库操作提供了更有力的保证,但这需要花费更多的CPU,内存以及磁盘空间资源。事务的这些特点对某些应用是必要的,有些则不需要,因此你需要为你的应用选择有实际意义的特点。一般财务操作都需要事务控制,来保证数据的完整性要求超过了其它相应所带来的成本。另一方面,对于那些记录网页对数据库访问的应用,对于服务端奔溃导致的部分数据丢失则是可以容忍的。在这种情况下,你可以使用非事务存储引擎来避免事务处理所产生的额外的开销。MySQL提供有一些支持事务的存储引擎,也提供不支持事务的存储引擎。
你可以使用以下语句来了解当前MySQL中所提供的存储引擎:
mysql> show engines\G
关于存储引擎的内容会在下一章中提到。在建立表时,你可以选择使用哪种存储引擎,因此在使用事务语法前,你需要了解你的表是否使用了支持事务的存储引擎。你可以使用show create table语句来查看:
7.4.1 事务命令
为了使用事务,你必须使用一种事务存储引擎(如InnoDB)。以下命令被用于启用事务:
- START TRANSACTION (或BEGIN [WORK]) — 开启一个新事物
- COMMIT — 确认当前事务,将事务中所做的数据修改永久化
- ROLLBACK — 回滚当前事务,取消其中的修改
- SET AUTOCOMMIT — 对当前会话连接关闭或启用默认的autocommit模式
默认情况下,MySQL的autocommit模式是开启的。这以为着一旦你执行语句进行表修改,MySQL会自动将其修改保存到磁盘。在autocommit模式中,每个语句都被认为是一个事务(除非你使用START TRANSACTION语句)。在一个事务开启后,直到显式地使用COMMIT或ROLLBACK或其它中止事务命令(如开启一个新事物START TRANSACTION)才会关闭事务。当关闭了autocommit模式,由于新事物会自动在旧事物结束后产生,因此就没用必要使用START TRANSACTION语句了。
设置AUTOCOMMIT:
如果你正使用一种事务型存储引擎(如InnDB, Falcon或NDB Cluster),你可以使用以下语句来在关闭autocommit模式:
mysql> set autocommit=0;
设置AUTOCOMMIT变量值为0会关闭autocommit模式,你必须使用COMMIT来将修改保存到磁盘或ROLLBACK来回滚你之前的事务操作。
在设置好后,你可以使用以下SELECT语句来检查当前设置:
7.4.2 隔离级别(Isolation Level)
之前提到,多个事务可能在服务端并行执行,一个客户端对应一个事务。这可能会引发潜在问题:如果一个客户端的事务修改了数据,那些其它客户端的事务是否应该看到这些改变或它们应该被隔离开呢?事务的隔离级别定义了事务(那些同时访问相同数据)之间的可视级别。这节讨论事务存储引擎(如InnoDB)如何实现隔离级别。注意,对于隔离级别的选择在不同数据库系统会有不同,因此InnoDB中实现的隔离级别并不和其他数据库系统中的级别相对应。
当多个客户端并行运行事务时,可能会产生如导致脏读(dirty reads),非重复读(non-repeatable reads)和幻读(phantom reads)这三种问题。这些会发生在以下情况下:
- 脏读:读取到了由其它客户端修改的但还被commit的数据。假如事务T1修改了一行,而事务T2读取了这行并看到T1的修改,尽管T1还未commit,这就是所谓的脏读。这之所以是一个问题,是因为如果T1 rollback了此修改,T2并不知道。
- 非重复读:其发生在事务执行了两次相同的读取但却每次获得了不同的返回结果。假设事务T1读取了一些行,而T2之后对这些行进行了修改并commit。当T1第二次读取时,它会发现这些改变;最初的读取和第二次读取为非重复读。这样的问题就是事务T1并未能从相同的查询中获取到一致的结果。
- 幻读:指一行数据在之前不可见,而又突然出现了。假设事务T1和T2都开启,且T1读取了一些行。如果T2插入了一个新行且当T1再次读的时候看到此行,那么此行为幻读。
四种隔离级别:
InnoDB实现了四种产业标准隔离级别来控制事务修改对其它并行事务的可视性。
- READ UNCOMMITED 允许事务可以看到由其它事务所做的还未commit的修改更新。此隔离级允许发生脏读,非重复读和幻读。
- READ COMMITTED 允许事务仅可以看到其它事物已commit的更改。未commit的修改仍旧不可见。此隔离级别允许发生非重复读和幻读。
- REPEATABLE READ 则保证当事务进行同样的SELECT查询多次,其得到相同的结果而无视其它事物所做的commit或未commit的事务。换句话说,这保证了同样的查询语句多次查询的数据一致性结果。在某些数据库系统中,REPEATABLE READ隔离级别允许幻读,即当其它事务在两个select查询语句之间插入了新行这样的情况。但对于InnoDB来说,幻读不会发生在REPEATABLE READ级别。
- SERIALIZABLE则完全隔离了事务之间的影响。它类似于REPEATABLE READ,但还增加了其他一些限制,如一个事务查询的行不能被其它事务所修改直到第一个事务结束。
隔离级别仅和同时执行事务中涉及的数据内容有关。在一个事务已经commit后,它所做的更新将对之后启动的事务可见。
使用多版本(multi-versioning)机制,每个事务将会看到适合其隔离级的数据库内容。例如,在REPEATABLE READ级,一个事务看到的数据库快照即为在其第一次读取时的数据库状态。这种隔离级的一大特点是它提供了都一致性:事务中相同的select查询多次所得的结果相同。唯一事务可见的改变是其自己进行的修改,而不是由其它事物产生的。对于READ COMMITED,则表现得稍有不同。事务所能看到的数据库视图在每次读取时,考虑到其它事务从之前读取之后进行的修改和commit,会不断进行更新。
事务隔离级别特征
脏读 | 非重复读 | 幻读 | |
Read Uncommitted | 可能 | 可能 | 可能 |
Read Committed | 不可能 | 可能 | 可能 |
Repeatable Read | 不可能 | 不可能 | 可能 (分系统而定) |
Serializable | 不可能 | 不可能 | 不可能 |
为了在MySQL启动时设置服务端的默认事务隔离级别,可使用 –transaction-isolation项。命令项的值可以为READ-UNCOMMITTED,READ-COMMITED,REPEATABLE-READ或SERIALIZABLE。例如,可以在服务端默认设置其为READ COMMITTED模式,在配置文件中设置:
[mysqld] transaction-isolation=READ-COMMITTED
你也可以在一个运行着的服务端上动态设置隔离级别,有以下三种语法:
SET GLOBAL TRANSACTION ISOLATION LEVEL isolation_level; SET SESSION TRANSACTION ISOLATION LEVEL isolation_level; SET TRANSACTION ISOLATION LEVEL isolation_level;
而isolation_level值可以是READ UNCOMMITTED, READ COMMITTED, REPEATABLE READ或SERIALIZABLE。第一种语句形式设置的是服务端全局隔离级别,它应用于所有从设置的时点起的新的客户端建立的连接。已存的连接则不受影响。第二种语法则仅对当前客户端连接设置其隔离级别,并针对应用于从设置的时点起客户端中的事务。第三种语法形式仅是对当前客户端下一次开始的事务设置隔离级。
仅那些有SUPER权限的用户可以使用第一种语句。那些可以使用第二种和第三种语法的客户端仅会对自己的事务产生影响,不需要额外其它的权限。
Leave a Reply