务(transaction)是一个或多个接连在一起作为一个逻辑单位运行的SQL语句。DBMS认为事务是不可分割的,要么全部执行,要么全不执行。
用银行的例子来说明事务的重要性是很经典的。假定某个客户从他的储蓄账户向支票账户转账500美元。这个操作包括连续执行的两个独立步骤。
(1) 储蓄账户减500美元。
(2) 支票账户增500美元。
图14-1显示了这个事务的两条SQL语句。设想,如果DBMS在执行了第一条语句,但没有执行第二条时突然发生故障——断电、系统崩溃、硬件出问题,账户在神不知鬼不觉的情况下出现了不平衡。渎职控告和牢狱之灾就会接踵而至。
图14-1 当银行客户从储蓄账户向支票账户转账时,必须有两条SQL语句
为了避免违法记录,应该使用事务来保证两条SQL语句都被执行,以维持账户平衡。如果事务中的一条语句无法执行时,DBMS将撤销(回滚)事务中其他语句。如果一切顺利,变化将被持久化(提交)。
执行事务
要了解事务如何工作,就要了解一些术语。
提交。提交(committing)事务是使自事务开始后修改的所有数据持久化在数据库中。在事务提交后,即使发生崩溃或其他故障,事务带来的所有变化仍然对其他用户可见并能够保证持久化。
回滚。回滚(rolling back)事务是撤销事务中SQL语句带来的所有变化。事务回滚后,此前影响到的数据回到原状,就好像SQL语句从未执行一样。
事务日志。事务日志文件(transaction logfile)或日志(log)是有关事务对数据库进行修改的一系列记录。事务日志记录了每个事务开始、数据的变化以及撤销或重新执行事务(如果将来需要)的足够信息。日志随着数据库事务的执行不断增长。
尽管保证每个事务本身的完整性是DBMS的职责,但依据组织或公司规章来开始和结束事务以保证数据逻辑的一致性则是数据库开发人员的责任。事务应该仅包含能做出一致修改的必要的SQL语句——不多不少。所有引用表中的数据在事务开始前和事务结束后必须保持一致。
在设计和执行事务时,要重点考虑以下方面。
l 事务相关的SQL语句会修改数据,所以执行事务要得到数据库管理员的授权。
l 事务过程应用于那些改变数据和数据库对象的语句(INSERT、UPDATE、DELETE、CREATE、ALTER、DROP——因不同DBMS而异)。对于工作中用到的数据库,每一条这样的语句都应该作为事务的一部分执行。
l 提交了的事务被称作持久化,意味着永久性改变,即便系统发生故障仍能保持。
l DBMS的数据恢复机制依赖于事务。当DBMS在故障之后被在线复原,DBMS检查事务日志确认是否所有事务都提交给了数据库。如发现没有提交(部分执行)的事务,依据日志将它们回滚。必须重新提交回滚的事务(尽管一些DBMS能够自动完成没有结束的事务)。
l DBMS的备份/恢复设备依赖于事务。备份设备获得例行的数据库快照并将它们和随后的事务日志存储在备份盘上。假定使用的硬盘发生故障使得数据和事务日志不可读。可以借助于恢复设备,它将采用最近的数据库备份并执行,或前滚所有从快照到故障前最后执行并在日志中提交的事务。这个恢复操作使数据库恢复到故障发生前的正确状态(注意,要再次提交没有提交的事务)。
l 显然,应该将数据库和它的事务日志存储于不同的物理硬盘。
对人来说,计算机好像在同一时间运行着两个或更多进程。实际上,计算机操作并非同时发生,而是连续的。同时发生的印象是因为微处理器在人们难以察觉的很短的时间段内工作。在DBMS里,并发控制是在两个或更多用户同时访问或修改相同的数据时为防止数据失去完整性的一组策略 。
DBMS使用锁定策略来保证事务完整性和数据库的一致性。在读写操作时,锁定限制数据的访问;于是,它阻止用户读那些正在被其他用户修改的数据,并防止多用户同时对同一数据修改。如果没有锁定,数据可能发生逻辑错误,针对这些数据执行的语句将返回不可预料的结果。偶尔会出现两个用户都锁定了对方事务所需的数据并尝试去得到对方的解锁,这时发生死锁问题。大多数DBMS能够侦测和解决死锁问题,通过回滚一个用户的事务让另一个事务可以运行(否则,两个用户都要永远等对方解锁)。锁定机制非常复杂,请查阅DBMS文档了解锁定。
并发透明性是从事务的角度看数据库上运行唯一事务的现象。DBMS分离事务变化与任何其他并发事务的变化。当然,事务永远见不到数据的中间状态;或在其他并发事务之前访问,或在其他并发事务结束以后访问。分离的事务允许重载开始数据并再次执行(前滚)一系列事务来达到它们在最初的事务被执行之后的状态。
因为事务按照要么全部,要么全不方式被执行,事务的边界(开始点和结束点)必须清晰。边界使DBMS作为一个原子单元来执行这些语句。事务隐式开始于第一个可执行的SQL语句或显式使用 START TRANSACTION语句。事务显式结束于COMMIT或ROLLBACK语句(无法隐式结束),且无法在提交之后回滚事务。
Oracle和DB2的事务总是隐式开始,这些DBMS没有用来开始事务的语句。在Microsoft Access、Microsoft SQL Server、MySQL和PostgreSQL中,可以使用BEGIN语句显式开始事务。SQL:1999引入START TRANSACTION语句——由于这发生在DBMS使用BEGIN开始事务很久以后,因此不同DBMS扩展BEGIN的语法也各不相同。MySQL和PostgreSQL支持START TRANSACTION(作为BEGIN同义词)。
在Microsoft Access或Microsoft SQL Server中,输入:
BEGIN TRANSACTION;
或
在MySQL or PostgreSQL,输入:
START TRANSACTION;
输入:
COMMIT;
输入:
ROLLBACK;
代码14-1中的SELECT语句显示UPDATE操作被DBMS执行后又被ROLLBACK语句取消。结果见图14-2。
图14-2 运行代码14-1的结果。SELECT语
句的结果显示DBMS取消了操作
代码14-1 在一个事务内,更新操作(像插入和删除操作那样)永远不是在最后出现。结果见图14-2
SELECT SUM(pages), AVG(price) FROM titles;
BEGIN TRANSACTION;
UPDATE titles SET pages = 0;
UPDATE titles SET price = price * 2;
SELECT SUM(pages), AVG(price) FROM titles;
ROLLBACK;
SELECT SUM(pages), AVG(price) FROM titles;
代码14-2显示更实用的事务例子。要从表publi- shers删除出版社P04而不产生引用完整性错误。因为表titles的有些外键值指向表publishers的出版社P04,所以要先删除表titles、titles_authors、和royalties中相关的行。应该使用事务保证所有DELETE语句都被执行。如果只有一些语句执行成功,数据将无法保持一致(要了解更多有关引用完整性检查的信息,参见11.7节)。
代码14-2 使用事务从表publishers中删除出版社P04,及删除其他表中与P04相关的行
BEGIN TRANSACTION;
DELETE FROM title_authors
WHERE title_id IN
(SELECT title_id
FROM titles
WHERE pub_id = 'P04');
DELETE FROM royalties
WHERE title_id IN
(SELECT title_id
FROM titles
WHERE pub_id = 'P04');
DELETE FROM titles
WHERE pub_id = 'P04';
DELETE FROM publishers
WHERE pub_id = 'P04';
COMMIT;
ACID是首字母缩写,它概括了事务的特点:
原子性(Atomicity)。要么事务中所有的数据修改都执行,要么都撤销。
一致性(Consistency)。完全的事务应让数据保持一致来保证数据完整。一致状态要保证满足所有数据约束。(注意,并不要求在任何事务的中间点保持一致性)。
隔离性(Isolation)。事务的影响独立(或隐藏)于其他事务,参见14.1节“并发控制”提要栏。
持久性(Durakility)。在事务完成后,它的影响是永久和持续的——即便是系统崩溃。
事务理论是独立于关系模型的重大问题。由Jim Gray和Andreas Reuter所著的Transaction Processing: Concepts and Techniques(Morgan Kaufmann)是一本很好的参考书。
ü提示
l 不要忘记使用COMMIT或ROLLBACK显式结束事务。没有结束点将导致回滚的最后未提交事务巨大,可能带来意外的数据变化或程序异常中止。因为事务在生存期锁定行、整个表、索引和其他资源,所以要让事务尽可能小。COMMIT或ROLLBACK为其他事务释放资源。
l 可以嵌套事务。最大嵌套数因DBMS而异。
使用一条SET语句的UPDATE更新多个列比使用多个UPDATE快。例如,查询:
UPDATE mytable
SET col1 = 1
col2 = 2
col3 = 3
WHERE col1 <> 1
OR col2 <> 2
OR col3 <> 3;
比3个UPDATE语句好,因为它减少了日志记录(尽管带来锁定)。
l 默认情况下,DBMS运行在自动提交模式(autocommit mode),除非被其他显式或隐式事务重写(或被系统设置关闭)。在这种模式下,每一条语句作为一个事务执行。如果语句执行成功,DBMS就将它提交;如果DBMS遇到错误,就回滚这条语句。
l 对于长的事务,可以设置称为存储点(savepoints)的中间标志,将事务分割为小段。存储点允许回滚从事务当前点到事务靠前的时间点之间的变化(假定事务还没有提交)。如果在一系列复杂的插入、更新、删除操作未提交时,意识到最后的变化是不正确的或不必要的,使用存储点就可以避免重新提交所有语句。Microsoft Access不支持存储点。在Oracle、DB2、MySQL和PostgreSQL中,使用语句:
SAVEPOINT savepoint_name
对于Microsoft SQL Server,使用:
SAVE TRANSACTION savepoint_name;
查阅DBMS文档来了解有关存储点锁定的细节知识及如何COMMIT或ROLLBACK到特定的存储点。
Microsoft Access中,通过SQL视图窗口或DAO无法执行事务,必须使用Microsoft Jet OLE DB Provider和ADO。
Oracle和DB2是隐式开始事务的。为了在Oracle 和 DB2中运行代码14-1和代码14-2,要删除语句:BEGIN TRANSACTION;
为了在MySQL中运行代码14-1和代码14-2,将语句BEGIN TRANSACTION;变为START TRANSACTION;(或BEGIN)。
MySQL通过InnoDB和BDB表支持事务,请查阅MySQL文档了解事务。Microsoft SQL Server、 Oracle、MySQL和PostgreSQL支持SET TRANSACTION语句设置事务特征。DB2通过服务器层和连接初始化设置控制事务特征。