事务是一种用于将多个SQL语句划分在一起的机制,这些语句作为整体,要么执行成功,要么执行失败
多用户数据库
锁定
锁是数据库服务器用来控制数据资源被同时使用的一种机制
锁的粒度
确定如何锁定资源时也有一些不同的策略可供选择,服务器可以在以下三个不同级别(或者粒度)上应用锁
数据表锁
避免多个用户同时修改同一数据表中的数据
页锁
避免多个用户同时修改某数据表中同一页的数据(一页通常是大小为2~16KB的内存段)
行锁
避免多个用户同时修改某数据表中同一行的数据
什么是事务
如果数据库服务器始终能够正常运行,如果用户总是允许程序执行完毕,如果应用程序总能顺利结束而不会遇到致命错误,就没必要讨论数据库并发访问,然而,上述情况无法发挥作用,因此另一个必要因素就是允许多个用户访问相同的数据
有关并发的难题中的额外部分就是事务,它是一种将多个SQL语句组合在一起的机制,所有的语句要么全部成功,要么全部失败(即原子性),试想从储蓄账户划转500美元到支票账户时,如果钱已从储蓄账户成功支取却没有成功存入支票账户,你肯定会有点不满意,不管失败的原因是什么(服务器因维护而关闭、account数据表的页锁定请求超时等),你都希望拿回你的500美元。为了避免产生这种错误,处理转账请求的程序将首先启动一个事务,然后发起SQL语句将钱从储蓄账户转到支票账户,如果一切顺利,则发出commit命令结束事务,如果发生意外情况,则发出rollback命令撤销服务器自事务开始依赖所做出的所有变更
通过使用事务,程序可以确保这500美元要么留在储蓄账户,要么转移到支票账户,不存在出现意外的可能,不管事务被提交还是回滚,在事务执行期间所获得的资源(比如写入锁)在事务完成后都会被释放
当然,如果程序完成了执行两个update语句后尚未来得及执行命令commit或rollback时服务器突然宕机了,那么该事务会在服务器重新上线后被回滚。(数据库服务器上线前必须完成的任务之一就是查找服务器宕机前正在运行但未完成的事务并将其回滚。)此外,如果程序完成了事务并发出了commit命令,但还没有将变更应用于永久性存储(也就是说,修改的数据仍然位于内存,尚未被写入磁盘)时服务器就宕机了,那么当服务器重启时,数据库服务器必须重新应用来自事务的变更(这种属性称为持久性)
启动事务
数据库服务器采用下列两种方式之一创建事务
- 一个活跃的事务总是和数据库会话相关联,所以没有必要也没有方法显示地开始一个事务,当前事务结束后,服务器会自动为会话启动一个新的事务
- 除非显式地开始一个事务,否则各个SQL语句会被自动提交,彼此独立,要想启动事务,必须先发出一个命令
Oracle Database采用第一种方法,而 Microsoft SQL Server和 MySQL采用第二种方法,Oracle的事务处理方法的优点之一是,哪怕只发出了一个SQL命令,如果对结果不满意或是改变了注意,也能够回滚变更。因此,如果忘记在delete语句中添加where子句,还是有机会挽回损失的(假设你清晨喝过咖啡后意识到自己并不是要删除数据表中所有的125000行),然而,对于MySQL和SQL Server来说,一旦你按下Enter键,你的SQL语句所带来的变更将是永久性的(除非数据库管理员可以通过备份或其他方法恢复原始数据)
SQL:2003标准提供了start transaction命令,可用于显式地启动事务,MySQL遵守该标准,但SQL Server用户必须使用替代命令begin transaction。对于这两种服务器,除非显式地启动事务,否则将一直处于自动提交模式,这意味着单个语句会被服务器自动提交。因此,你可以自己发出事务启动命令或是简单地让服务器提交单独的语句。
MySQL和SQL Server都允许关闭单个会话的自动提交模式,在这种情况下,对事务而言,这些服务器的行为就像Oracle Database一样,可以使用下列命令关闭SQL Server的自动提交模式
bash
SET IMPLICIT_TRANSACTIONS ON
对于MySQL,可以通过下列命令关闭自动提交模式
c
SET AUTOCOMMIT=0
一旦离开了自动提交模式,所有的SQL命令都会作用在同一个事务的范围内,并且必须显式地提交或者回滚事务
建议每次登录时关闭自动提交模式,养成在事务内运行所有SQL语句的习惯。即使没有其他好处,至少能避免请求数据库管理员重建被自己无意中删除的数据的窘境
结束事务
一旦事务启动,不管是通过start transaction命令显式地启动还是由数据库服务器隐式地启动,为了使变更持久生效,都必须显式地结束事务,可以通过commit命令来实现,该命令指示服务器将此变更标记为永久性的并释放事务中使用的任何资源(页锁或者行锁)
如果打算撤销自事务启动后所做的所有变更,必须发出rollback命令,该命令指示服务器将数据恢复到事务之前的状态。rollback命令完成后,会话所使用的资源全都会被释放
除了发出commit或rollback命令,其他场景也会导致事务结束,要么作为操作的间接结果,要么作为意外结果,如下所示。
- 服务器宕机,在这种情况下,事务会在服务器重启时被自动回滚
- 发出SQL模式语句,比如alter table,这会使当前事务被提交并启动一个新的事务
- 发出另一个start transaction命令,这会造成前一个事务被提交
- 如果服务器检测到死锁并认定当前事务就是导致死锁的根源,服务器就会提前结束当前事务,在这种情况下,该事务会被回滚,同时也会接收到错误消息
事务保存点
在某些情况下,你可能会遇到一些问题需要回滚事务,但是又并不想撤销所有完成的工作,此时,可以在事务内创建一个或多个保存点,利用其回滚到事务内的特定位置,而不必一路回滚到事务的启动点
所有保存点都必须给定名称,这样就可以在单个事务中设置多个保存点,使用下列命令创建名为my_savepoint的保存点
bash
SAVEPOINT my_savepoint;
要想回滚到特定的保存点,只需发出rollback命令,后跟关键字 to savepoint和保存点名称
bash
ROLLBACK TO SAVEPOINT my_savepoint;
使用保存点时,记住以下两点:
- 创建保存点时,除了名称,什么都没有保存,要想使事务具有持久性,最终必须发出commit命令
- 如果发出rollback命令的时候没有指定保存点名称,事务中的所有保存点都会被忽略,整个事务都会被撤销