结构化查询语言/事务
一个事务是一组一个或多个SQL语句 - 包含写入数据库的语句,例如INSERT、UPDATE或DELETE,SELECT命令也可以是事务的一部分。所有写入语句必须是事务的一部分。事务的目的是保证数据库的变化只从一个一致的状态到另一个一致的状态。它将所有中间状态组合成一个更改事件。这在并行处理、磁盘崩溃、电源故障等关键情况下也适用。事务确保数据库完整性。
为此,它们支持四个基本属性,这些属性统称为ACID 范式。
Atomic(原子性) 事务中的所有SQL语句要么全部执行,要么都不执行。 Consistent(一致性) 事务所有数据更改的总和将数据库从一个一致状态转换为另一个一致状态。 Isolated(隔离性) 隔离级别定义了未提交事务的哪些部分对其他会话可见。 Durable(持久性) 即使系统随后崩溃,数据库也会保留已提交的更改。
由于每个写入数据库的SQL语句都是事务的一部分,因此如果之前没有语句启动未完成的事务,DBMS会为每个语句隐式地启动一个事务。或者,应用程序/会话可以通过命令START TRANSACTION
显式地启动事务。
所有后续的SQL命令都是此事务的一部分。事务保持到确认或拒绝为止。确认通过命令COMMIT
进行,拒绝通过命令ROLLBACK
进行。在提交COMMIT或ROLLBACK命令之前,DBMS将每个写入语句的结果存储到一个中间区域,该区域对其他会话不可见(参见:隔离级别)。同时使用COMMIT命令,此事务的所有更改最终进入公共数据库,对每个其他会话可见,并且事务终止。如果COMMIT由于任何原因失败,则与会话提交ROLLBACK命令时相同:此事务的所有更改都被丢弃,并且事务终止。可选地,会话可以通过提交单个命令ROLLBACK来撤消其属于活动事务的所有写入操作。
一个示例
-- Begin the transaction with an explicit command (In general not necessary. Not supported by Oracle.)
START TRANSACTION;
-- Insert some rows
INSERT ... ;
-- Modify those rows or some other rows
UPDATE ... ;
-- Delete some rows
DELETE ... ;
-- If the COMMIT succeeds, the results of the above 3 commands have been transferred to the 'common'
-- database and thus 'published' to all other sessions.
COMMIT;
--
--
START TRANSACTION;
INSERT ... ;
UPDATE ... ;
DELETE ... ;
-- Discard INSERT, UPDATE and DELETE
ROLLBACK;
由于事务可以包含许多语句,因此很可能出现运行时错误或逻辑错误。在某些此类情况下,应用程序希望仅回滚实际事务的一部分并提交其余部分,或第二次恢复处理。为此,可以定义内部事务边界,它反映了从事务开始到此时的所有处理。这些中间边界称为保存点。COMMIT和ROLLBACK语句终止完整的事务,包括其保存点。
-- Begin the transaction with an explicit command
START TRANSACTION;
--
INSERT ... ;
-- Define a savepoint
SAVEPOINT step_1;
--
UPDATE ... ;
-- Discard only the UPDATE. The INSERT remains.
ROLLBACK TO SAVEPOINT step_1;
-- try again (or do any other action)
UPDATE ... ;
-- confirm INSERT and the second UPDATE
COMMIT;
在事务的生命周期内,如果保存点不再需要,则可以释放它。(它在事务结束时会被隐式释放。)
-- ...
-- ...
RELEASE SAVEPOINT <savepoint_name>;
-- This has no effect on the results of the previous INSERT, UPDATE or DELETE commands. It only eliminates the
-- possibility to ROLLBACK TO SAVEPOINT <savepoint_name>.
事务保证其所有语句的结果在逻辑级别上被视为单个操作。所有写入语句都具有临时性质,直到COMMIT命令成功终止。
此行为有助于确保业务逻辑的逻辑完整性。例如:如果要将一定数量的钱从一个账户转到另一个账户,则必须修改数据库中的至少两行。第一个修改减少一行中的金额,第二个修改在另一行中增加金额。如果在这两个写操作之间发生磁盘崩溃或电源故障,则应用程序会出现问题。但是,事务的原子性属性保证写入操作都不会到达数据库(在任何故障或ROLLBACK的情况下)或所有操作都到达数据库(在成功COMMIT的情况下)。
在维基百科上可以找到有关原子性属性的更多详细信息。
事务保证数据库在终止后处于一致状态。这种一致性发生在不同的级别
在大多数情况下,许多会话同时在DBMS上工作。它们竞争资源,尤其是数据。只要数据没有被修改,这不成问题。DBMS可以将数据传递给所有会话。
但是,如果多个会话尝试在同一时间修改数据,则冲突是不可避免的。这是一个在航班预订系统上工作的两个会话的示例时间线。会话S1读取航班的空余座位数:1个空余座位。S2读取同一航班的空余座位数:1个空余座位。S1预订最后一个座位。S2预订最后一个座位。
对这类冲突进行分析的核心结果是,如果所有事务(涉及相同数据的事务)按顺序执行:一个接一个,那么所有冲突都是可以避免的。但很明显,这种行为效率较低。如果DBMS尽可能多地并行工作,则整体性能会提高。SQL标准提供了对这类冲突的系统分类,并提供了SET TRANSACTION ...
命令来解决这些冲突,目的是尽可能地允许并行操作。
标准确定了三种问题情景
- P1(**脏读**):"SQL事务T1修改一行。然后SQL事务T2在T1执行COMMIT之前读取该行。如果T1随后执行ROLLBACK,则T2将读取一行从未提交过的数据,因此可以认为该行从未存在过。" [1]
- P2(**不可重复读**):"SQL事务T1读取一行。然后SQL事务T2修改或删除该行并执行COMMIT。如果T1随后尝试重新读取该行,则它可能会收到修改后的值或发现该行已被删除。" [1]不可重复读涉及单个行。
- P3(**幻读**):"SQL事务T1读取满足某个搜索条件的行集N。然后SQL事务T2执行生成一个或多个满足SQL事务T1使用的搜索条件的行SQL语句。如果SQL事务T1随后使用相同的搜索条件重复初始读取,则它将获得不同的行集合。" [1]幻读涉及结果集。
根据应用程序的要求和访问策略,上述某些问题可能是可以容忍的,而另一些则不可容忍。标准提供了SET TRANSACTION ...
命令来定义在事务内允许发生哪些问题,以及不允许发生哪些问题。SET TRANSACTION ...
命令必须是事务中的第一个语句。
-- define (un)tolerable conflict situations (Oracle does not support all of them)
SET TRANSACTION ISOLATION LEVEL [READ UNCOMMITTED |
READ COMMITTED |
REPEATABLE READ |
SERIALIZABLE];
下表显示了每个级别可能发生哪些问题。
隔离级别 | 脏读 | 不可重复读 | 幻读 |
---|---|---|---|
读取未提交 | 可能发生 | 可能发生 | 可能发生 |
读取提交 | - | 可能发生 | 可能发生 |
可重复读 | - | - | 可能发生 |
串行化 | - | - | - |
在维基百科上,有更多关于隔离级别和并发控制的详细信息和示例。
事务保证每个确认的写操作都将(几乎)在所有后续灾难中幸存下来。为此,在大多数情况下,DBMS 不仅将更改写入数据库,还额外写入日志文件,这些日志文件应位于不同的设备上。因此,在磁盘崩溃后,可以从数据库备份和这些日志文件中恢复所有更改。
在维基百科上可以找到关于持久性属性的更多详细信息。
一些DBMS在标准之外提供了AUTOCOMMIT功能。如果激活此功能,则该功能会在每个写入语句后自动提交COMMIT命令,从而导致无法回滚由大量SQL语句组成的逻辑工作单元。此外,无法使用SAVEPOINT功能。
在许多情况下,此功能默认处于激活状态。