目录
总结表
what-事务定义
why-为什么需要事务,事务的必要性
what-数据库事务ACID特性
原子性(atomicity)
一致性(consistency)
隔离性(isolation)——指不同事务之间
持久性(durability)
A(Atomicity),原子性。
C(consistency),一致性。
I(isolation),隔离性。
D(durability),持久性。
丢失更新、脏读、不可重复度、幻读和事务隔离级别(针对不同事务之间)
第一类丢失更新(回滚丢失,Lost update)
第二类丢失更新(覆盖丢失/两次更新问题,Second lost update)
应用层面丢失更新(多个客户端)及解决方案
脏读:
不可重复度:
幻读:
READ UNCOMMITTED(未提交读)
READ COMMITTED(提交读)
REPEATABLE READ(可重复读)
SERIALIZABLE(可串行化)
事务控制
数据库面试知识点整理:
总结表
序号 | 现象→ | 第一类 | 第二类 | 脏读 | 不可 | 幻读 | 客户端层丢失更新,解决方式: | SQL92标准是否满足ACID | MYSQLSQL92 | 发生死锁? | 单纯锁实现机制 | 高并发实现机制 | DB默认 | DB支持 |
1 | 读未提交 READ_UNCOMMITTED | | | ● | ● | ● | ● | 不满足CI | 不满足CI | yes | 1、修改时行锁X锁,避免两类丢失更新。 | 1、修改时行锁X锁,避免两类丢失更新。 | | Mysql支持 ORACLE不支持 |
2 | 读已提交 READ_COMMITTED | | | | ● | ● | ● | 不满足I | 不满足I | yes | 1、修改时行锁X锁,避免两类丢失更新。 2、禁止读取缓冲池内存中未提交的脏数据 | MVVC,快照读和当前读是一样的,或者说,select也是当前读,没有快照读 | Oracle | Mysql支持 ORACLE支持 |
3 | 可重复读 REPEATABLE_READ | | | | | ● | ● | 不满足I | 满足ACID mysql这个级别解决了部分幻读(两次select内容一样,但是一旦update再select还是会幻读) | yes | 1、修改时行锁X锁,避免两类丢失更新。 2、禁止读取缓冲池内存中未提交的脏数据 3、读取时在where行上上读锁,禁止其它事务修改删除 | MVVC,普通的SELECT就是快照读,而UPDATE、DELETE、INSERT、SELECT ... LOCK IN SHARE MODE、SELECT ... FOR UPDATE是当前读 注意!!单纯的select是不会添加任何锁的 | mysql | Mysql支持 ORACLE不支持 |
4 | 序列化 SERIALIZABLE | | | | | | ● | 满足ACID | 满足ACID | yes | 1、修改时行锁X锁,避免两类丢失更新。 2、禁止读取缓冲池内存中未提交的脏数据 3、加锁读,默认给select加锁,即SELECT ... LOCK IN SHARE MODE | 无主键时X锁锁表,禁止其他事务增删改任何记录。有主键时间隙锁锁where条件行,禁止其它事务增删改where行(mysql在rr级别就用了间隙锁) | | Mysql支持 ORACLE支持 |
what-事务定义
《高性能mysql》
在理解事务的概念之前,接触数据库系统的其他高级特性还言之过早。事务就是一组原子性的SQL查询,或者说一个独立的工作单元。如果数据库引擎能够成功地对数据库应用该组查询的全部语句,那么就执行该组查询。如果其中有任何一条语句因为崩溃或其他原因无法执行,那么所有的语句都不会执行。也就是说,事务内的语句,要么全部执行成功,要么全部执行失败。
《mysql技术内幕》
事务可由一条非常简单的SQL语句组成,也可以由一组复杂的SQL语句组成。事务是访问并更新数据库中各种数据项的一个程序执行单元。在事务中的操作,要么都做修改,要么都不做,这就是事务的目的,也是事务模型区别与文件系统的重要特征之一。
why-为什么需要事务,事务的必要性
《高性能mysql》
银行应用是解释事务必要性的一个经典例子。假设一个银行的数据库有两张表:支票(checking)表和储蓄(savings)表。现在要从用户Jane的支票账户转移200美元到她的储蓄账户,那么需要至少三个步骤:
检查支票账户的余额高于200美元。
从支票账户余额中减去200美元。
在储蓄账户余额中增加200美元。
上述三个步骤的操作必须打包在一个事务中,任何一个步骤失败,则必须回滚所有的步骤。
可以用START TRANSACTION语句开始一个事务,然后要么使用COMMIT提交事务将修改的数据持久保留,要么使用ROLLBACK撤销所有的修改。事务SQL的样本如下:
1 START TRANSACTION; 2 SELECT balance FROM checking WHERE customer_id = 10233276; 3 UPDATE checking SET balance = balance - 200.00 WHERE customer_id = 10233276; 4 UPDATE savings SET balance = balance + 200.00 WHERE customer_id = 10233276; 5 COMMIT;
单纯的事务概念并不是故事的全部。试想一下,如果执行到第四条语句时服务器崩溃了,会发生什么?天知道,用户可能会损失200美元。再假如,在执行到第三条语句和第四条语句之间时,另外一个进程要删除支票账户的所有余额,那么结果可能就是银行在不知道这个逻辑的情况下白白给了Jane 200美元。
除非系统通过严格的ACID测试,否则空谈事务的概念是不够的。ACID表示原子性(atomicity)、一致性(consistency)、隔离性(isolation)和持久性(durability)。一个运行良好的事务处理系统,必须具备这些标准特征。
《mysql技术内幕》
理论上说,事务有着极其严格的定义,它必须同时满足四个特性,即通常所说的事务的ACID特性。值得注意的是,虽然理论上定义了严格的事务要求,但是数据库厂商出于各种目的,并没有严格去满足事务的ACID标准。例如,对于MySQL的NDB Cluster引擎来说,虽然其支持事务,但是不满足D的要求,即持久性的要求。对于Oracle数据库来说,其默认的事务隔离级别为READ COMMITTED,不满足I的要求,即隔离性的要求。虽然在大多数的情况下,这并不会导致严重的结果,甚至可能还会带来性能的提升,但是用户首先需要知道严谨的事务标准,并在实际的生产应用中避免可能存在的潜在问题。对于mysql的InnoDB存储引擎而言,其默认的事务隔离级别为READ REPEATABLE(标准的可重复读级别是不满足I要求的,因为会出现幻读行),完全遵循和满足事务的ACID特性。这里,具体介绍事务的ACID特性,并给出相关概念。
what-数据库事务ACID特性
《高性能mysql》
原子性(atomicity)
一个事务必须被视为一个不可分割的最小工作单元,整个事务中的所有操作要么全部提交成功,要么全部失败回滚,对于一个事务来说,不可能只执行其中的一部分操作,这就是事务的原子性。
一致性(consistency)
数据库总是从一个一致性的状态转换到另外一个一致性的状态。在前面的例子中,一致性确保了,即使在执行第三、四条语句之间时系统崩溃,支票账户中也不会损失200美元,因为事务最终没有提交,所以事务中所做的修改也不会保存到数据库中。
隔离性(isolation)——指不同事务之间
通常来说,一个事务所做的修改在最终提交以前,对其他事务是不可见的。在前面的例子中,当执行完第三条语句、第四条语句还未开始时,此时有另外一个账户汇总程序开始运行,则其看到的支票账户的余额并没有被减去200美元。后面我们讨论隔离级别(Isolation level)的时候,会发现为什么我们要说“通常来说”是不可见的。
持久性(durability)
一旦事务提交,则其所做的修改就会永久保存到数据库中。此时即使系统崩溃,修改的数据也不会丢失。持久性是个有点模糊的概念,因为实际上持久性也分很多不同的级别。有些持久性策略能够提供非常强的安全保障,而有些则未必。而且不可能有能做到100%的持久性保证的策略(如果数据库本身就能做到真正的持久性,那么备份又怎么能增加持久性呢?)。在后面的一些章节中,我们会继续讨论MySQL中持久性的真正含义。
《mysql技术内幕》
A(Atomicity),原子性。
在计算机系统中,每个人都将原子性视为理所当然。例如在C语言中调用SQRT函数,其要么返回正确的平方根值,要么返回错误的代码,而不会在不可预知的情况下改变任何的数据结构和参数。如果SQRT函数被许多个程序调用,一个程序的返回值也不会是其他程序要计算的平方根。然而在数据的事务中实现调用操作的原子性,就不是那么理所当然了。例如一个用户在ATM机前取款,假设取款的流程为:
1)登录ATM机平台,验证密码。 2)从远程银行的数据库中,取得账户的信息。 3)用户在ATM机上输入欲提取的金额。 4)从远程银行的数据库中,更新账户信息。 5)ATM机出款。 6)用户取钱。
整个取款的操作过程应该视为原子操作,即要么都做,要么都不做。不能用户钱未从ATM机上取得,但是银行卡上的钱已经被扣除了,相信这是任何人都不能接受的一种情况。而通过事物模型,可以保证该操作的原子性。
原子性指整个数据库事务是不可分割的工作单位。只有使事务中所有的数据库操作都执行成功,才算整个事务成功。事务中任何一个SQL语句执行失败,已经执行成功的SQL语句也必须撤销,数据库状态应该退回到执行事务前的状态。
如果事务中的操作都是只读的,要保持原子性是很简单的。一旦发生任何错误,要么重试,要么返回错误代码。因为只读操作不会改变系统中的任何相关部分。但是,当事务中的操作需要改变系统中的状态时,例如插入记录或更新记录,那么情况可能就不像只读操作那么简单了。如果操作失败,很有可能引起状态的变化,因此必须要保护系统中并发用户访问受影响的部分数据
C(consistency),一致性。
一致性指事务将数据库从一种状态转变为下一种一致的状态。在事务开始之前和事务结束以后,数据库的完整性约束没有被破坏。例如,在表中有一个字段为姓名,为唯一约束,即在表中姓名不能重复。如果一个事务对姓名字段进行了修改,但是在事务提交或事务操作发生回滚后,表中的姓名变得非唯一了,这就破坏了事务的一致性要求,即事务将数据库从一种状态变为了一种不一致的状态。因此,事务是一致性的单位,如果事务中某个动作失败了,系统可以自动撤销事务——返回初始化的状态。
I(isolation),隔离性。
隔离性还有其他的称呼,如并发控制(concurrency control)、可串行化(serializability)、锁(locking)等。事务的隔离性要求每个读写事务的对象对其他事务的操作对象能相互分离,即该事务提交前对其他事务都不可见,通常这使用锁来实现。当前数据库系统中都提供了一种粒度锁(granular lock)的策略,允许事务仅锁住一个实体对象的子集,以此来提高事务之间的并发度。
D(durability),持久性。
事务一旦提交,其结果就是永久性的。即使发生宕机等故障,数据库也能将数据恢复。需要注意的是,只能从事务本身的角度来保证结果的永久性。例如,在事务提交后,所有的变化都是永久的。即使当数据库因为崩溃而需要恢复时,也能保证恢复后提交的数据都不会丢失。但若不是数据库本身发生故障,而是一些外部的原因,如RAID卡损坏、自然灾害等原因导致数据库发生问题,那么所有提交的数据可能都会丢失。因此持久性保证事务系统的高可靠性(High Reliability),而不是高可用性(High Availability)。对于高可用性的实现,事务本身并不能保证,需要一些系统共同配合来完成。
《大石头》
C,一致性:指的是同一个事务内,也就是begin transaction和commit/rollback之中,多次查询看到的数据都是一样的
I,隔离性:指不同事务之间
《高性能mysql》
事务的ACID特性可以确保银行不会弄丢你的钱。而在应用逻辑中,要实现这一点非常难,甚至可以说是不可能完成的任务。一个兼容ACID的数据库系统,需要做很多复杂但可能用户并没有觉察到的工作,才能确保ACID的实现。
就像锁粒度的升级会增加系统开销一样,这种事务处理过程中额外的安全性,也会需要数据库系统做更多的额外工作。一个实现了ACID的数据库,相比没有实现ACID的数据库,通常会需要更强的CPU处理能力、更大的内存和更多的磁盘空间。正如本章不断重复的,这也正是MySQL的存储引擎架构可以发挥优势的地方。用户可以根据业务是否需要事务处理,来选择合适的存储引擎。对于一些不需要事务的查询类应用,选择一个非事务型的存储引擎,可以获得更高的性能。即使存储引擎不支持事务,也可以通过LOCK TABLES语句为应用提供一定程度的保护,这些选择用户都可以自主决定。
丢失更新、脏读、不可重复度、幻读和事务隔离级别(针对不同事务之间)
事务隔离级别↓ ▋ 现象→ | 丢失更新 | 脏读 | 不可重读 | 幻读 | 实现机制 |
无事务 NONE | ● | ● | ● | ● | |
读未提交 READ_UNCOMMITTED | | ● | ● | ● | |
读已提交 READ_COMMITTED | | | ● | ● | 记录版本 |
可重复读 REPEATABLE_READ | | | | ● | 锁行 |
序列化 SERIALIZABLE | | | | | 锁表 |
《大石头》
大部分博客、书籍关于丢失更新和幻读的解释都是不对的。我翻阅了大量资料,找到了正确的说法
第一类丢失更新(回滚丢失,Lost update)
A事务撤销时,把已经提交的B事务的更新数据覆盖了。这种错误可能造成很严重的问题,通过下面的账户取款转账就可以看出来:
场景1:时间取款事务A转账事务B开始事务后真是情况T1开始事务 T2 开始事务 T3查询账户余额为1000元 T4 查询账户余额为1000元 T5 汇入100元把余额改为1100元 T6 提交事务 T7取出100元把余额改为900元 T8撤销事务 T9余额恢复为1000元(丢失更新) 场景2时间取款事务A转账事务B开始事务后真是情况T1开始事务 T2 开始事务 T3查询账户余额为1000元 T4 查询账户余额为1000元 T5 汇入100元把余额改为1100元事务B通过X锁锁记录r1T6取出100元把余额改为900元 阻塞,事务A等待获取记录R1的X锁T7 提交事务 T8撤销事务 T9余额恢复为1000元(丢失更新) 场景3时间取款事务A转账事务B开始事务后真是情况T1开始事务 T2 开始事务 T3查询账户余额为1000元 T4 查询账户余额为1000元 T5取出100元把余额改为900元 事务A通过X锁锁记录r1T6 汇入100元把余额改为1100元阻塞,事务B等待获取记录R1的X锁T7 提交事务阻塞,根本提交不动,T7不可能在T8前T8撤销事务 T9余额恢复为1000元(丢失更新)
A事务在撤销时,“不小心”将B事务已经转入账户的金额给抹去了。
SQL92没有定义这种现象,标准定义的所有隔离界别都不允许第一类丢失更新发生。所幸的是大部分数据库(包括 MySQL 和 Oracle)基本都已经消灭了这类丢失更新,也无法复现,所以笔者就不对这类丢失更新展开讨论了。
这其实就是数据库的bug,根本不应该出现。 ——lishuoboy
第二类丢失更新(覆盖丢失/两次更新问题,Second lost update)
A事务覆盖B事务已经提交的数据,造成B事务所做操作丢失:
场景1:时间取款事务A转账事务B开始事务后真是情况T1开始事务 T2 开始事务 T3查询账户余额为1000元 T4 查询账户余额为1000元 T5 汇入100元把余额改为1100元 T6 提交事务 T7取出100元把余额改为900元 T8提交事务 T9把余额改为900元(丢失更新) 场景2时间取款事务A转账事务B开始事务后真是情况T1开始事务 T2 开始事务 T3查询账户余额为1000元 T4 查询账户余额为1000元 T5 汇入100元把余额改为1100元事务B通过X锁锁记录r1T6取出100元把余额改为900元 阻塞,事务A等待获取记录R1的X锁T7 提交事务 T8提交事务 T9把余额改为900元(丢失更新) 场景3时间取款事务A转账事务B开始事务后真是情况T1开始事务 T2 开始事务 T3查询账户余额为1000元 T4 查询账户余额为1000元 T5取出100元把余额改为900元 事务A通过X锁锁记录r1T6 汇入100元把余额改为1100元阻塞,事务B等待获取记录R1的X锁T7 提交事务阻塞,根本提交不动,T7不可能在T8前T8提交事务 T9把余额改为900元(丢失更新)
丢失更新是另一个锁导致的问题,简单来说其就是一个事务的更新操作会被另一个事务的更新操作所覆盖,从而导致数据的不一致。例如:
1)事务T1将行记录r更新为v1,但是事务T1并未提交。
2)与此同时,事务T2将行记录r更新为v2,事务T2未提交。
3)事务T1提交。
4)事务T2提交。
但是,在当前数据库的任何隔离级别下,都不会导致数据库理论意义上的丢失更新问题。这是因为,即使是READ UNCOMMITTED的事务隔离级别,对于行的DML操作,需要对行或其他粗粒度级别的对象加锁。因此在上述步骤2)中,事务T2并不能对行记录r进行更新操作,其会被阻塞,直到事务T1提交。
应用层面丢失更新(多个客户端)及解决方案
虽然数据库能阻止丢失更新问题的产生,但是在生产应用中还有另一个逻辑意义的丢失更新问题,而导致该问题的并不是因为数据库本身的问题。实际上,在所有多用户计算机系统环境下都有可能产生这个问题。简单地说来,出现下面的情况时,就会发生丢失更新:
1)事务T1查询一行数据,放入本地内存(比如java变量中),并显示给一个终端用户User1。
2)事务T2也查询该行数据,并将取得的数据显示给终端用户User2。
3)User1修改这行记录,更新数据库并提交。
4)User2修改这行记录,更新数据库并提交。显然,这个过程中用户User1的修改更新操作“丢失”了,而这可能会导致一个“恐怖”的结果。设想银行发生丢失更新现象,例如一个用户账号中有10 000元人民币,他用两个网上银行的客户端分别进行转账操作。第一次转账9000人民币,因为网络和数据的关系,这时需要等待。但是这时用户操作另一个网上银行客户端,转账1元,如果最终两笔操作都成功了,用户的账号余款是9999人民币,第一次转的9000人民币并没有得到更新,但是在转账的另一个账号却会收到这9000元,这导致的结果就是钱变多,而账不平。也许有读者会说,不对,我的网银是绑定USB Key的,不会发生这种情况。是的,通过USB Key登录也许可以解决这个问题,但是更重要的是在数据库层解决这个问题,避免任何可能发生丢失更新的情况。
要避免丢失更新发生,需要让事务在这种情况下的操作变成串行化,而不是并行的操作。即在上述四个步骤的1)中,对用户读取的记录加上一个排他X锁。同样,在步骤2)用户读取时也需要加一个排他X锁。通过这种方式,步骤2)就必须等待一步骤1)和步骤3)完成,最后完成步骤4)。表6-17所示的过程演示了如何避免这种逻辑上丢失更新问题的产生。
这类丢失更新不是调整事务级别能解决的,任何级别数据库都解决不了,因为数据库在读的时候都是加的共享S锁。解决方法是用户主动加排它锁。
有读者可能会问,在上述的例子中为什么不直接允许UPDATE语句,而首先要进行SELECT…FOR UPDATE的操作。的确,直接使用UPDATE可以避免丢失更新问题的产生。然而在实际应用中,应用程序可能需要首先检测用户的余额信息,查看是否可以进行转账操作,然后再进行最后的UPDATE操作,因此在SELECT与UPDATE操作之间可能还存在一些其他的SQL操作。
我发现,程序员可能在了解如何使用SELECT、INSERT、UPDATE、DELETE语句后就开始编写应用程序。因此,丢失更新是程序员最容易犯的错误,也是最不易发现的一个错误,因为这种现象只是随机的、零星出现的,不过其可能造成的后果却十分严重。
脏读:
事务A读取到了事务B已经修改但是尚未提交的数据,如果B事务回滚,A读取的数据无效,不符合一致性。
不可重复度:
事务A读取到了事务B提交修改(包括insert、update、delete)的数据,不符合隔离性。
幻读:
事务A读取到了事务B提交新增的数据,不符合隔离性。
《高性能mysql》
隔离性其实比想象的要复杂。在SQL标准中定义了四种隔离级别,每一种级别都规定了一个事务中所做的修改,哪些在事务内和事务间是可见的,哪些是不可见的。较低级别的隔离通常可以执行更高的并发,系统的开销也更低。每种存储引擎实现的隔离级别不尽相同。如果熟悉其他的数据库产品,可能会发现某些特性和你期望的会有些不一样(但本节不打算讨论更详细的内容)。读者可以根据所选择的存储引擎,查阅相关的手册。
下面简单地介绍一下四种隔离级别。
READ UNCOMMITTED(未提交读)
在READ UNCOMMITTED级别,事务中的修改,即使没有提交,对其他事务也都是可见的。事务可以读取未提交的数据,这也被称为脏读(Dirty Read)。这个级别会导致很多问题,从性能上来说,READ UNCOMMITTED不会比其他的级别好太多,但却缺乏其他级别的很多好处,除非真的有非常必要的理由,在实际应用中一般很少使用。
READ COMMITTED(提交读)
大多数数据库系统的默认隔离级别都是READ COMMITTED(但MySQL不是)。READ COMMITTED满足前面提到的隔离性的简单定义:一个事务开始时,只能“看见”已经提交的事务所做的修改。换句话说,一个事务从开始直到提交之前,所做的任何修改对其他事务都是不可见的。这个级别有时候也叫做不可重复读(nonrepeatable read),因为两次执行同样的查询,可能会得到不一样的结果。
REPEATABLE READ(可重复读)
EPEATABLE READ解决了脏读的问题。该级别保证了在同一个事务中多次读取同样记录的结果是一致的。但是理论上,可重复读隔离级别还是无法解决另外一个幻读(Phantom Read)的问题。所谓幻读,指的是当某个事务在读取某个范围内的记录时,另外一个事务又在该范围内插入了新的记录,当之前的事务再次读取该范围的记录时,会产生幻行(Phantom Row)。InnoDB和XtraDB存储引擎通过多版本并发控制(MVCC,Multiversion Concurrency Control)解决了幻读的问题。本章稍后会做进一步的讨论。
可重复读是MySQL的默认事务隔离级别。SERIALIZABLE(可串行化)
ERIALIZABLE是最高的隔离级别。它通过强制事务串行执行,避免了前面说的幻读的问题。简单来说,SERIALIZABLE会在读取的每一行数据上都加锁,所以可能导致大量的超时和锁争用的问题。实际应用中也很少用到这个隔离级别,只有在非常需要确保数据的一致性而且可以接受没有并发的情况下,才考虑采用该级别。
表1-1:ANSI SQL隔离级别
《mysql技术内幕》
令人惊讶的是,大部分数据库系统都没有提供真正的隔离性,最初或许是因为系统实现者并没有真正理解这些问题。如今这些问题已经弄清楚了,但是数据库实现者在正确性和性能之间做了妥协。ISO和ANIS SQL标准制定了四种事务隔离级别的标准,但是很少有数据库厂商遵循这些标准。比如Oracle数据库就不支持READ UNCOMMITTED和REPEATABLE READ的事务隔离级别。
SQL标准定义的四个隔离级别为:
❑READ UNCOMMITTED
❑READ COMMITTED
❑REPEATABLE READ
❑SERIALIZABLE
READ UNCOMMITTED称为浏览访问(browse access),仅仅针对事务而言的。READ COMMITTED称为游标稳定(cursor stability)。REPEATABLE READ是2.9999°的隔离,没有幻读的保护。SERIALIZABLE称为隔离,或3°的隔离。SQL和SQL2标准的默认事务隔离级别是SERIALIZABLE。
InnoDB存储引擎默认支持的隔离级别是REPEATABLE READ,但是与标准SQL不同的是,InnoDB存储引擎在REPEATABLE READ事务隔离级别下,使用Next-Key Lock锁的算法,因此避免幻读的产生。这与其他数据库系统(如Microsoft SQL Server数据库)是不同的。所以说,InnoDB存储引擎在默认的REPEATABLE READ的事务隔离级别下已经能完全保证事务的隔离性要求,即达到SQL标准的SERIALIZABLE隔离级别。
隔离级别越低,事务请求的锁越少或保持锁的时间就越短。这也是为什么大多数数据库系统默认的事务隔离级别是READ COMMITTED。
据了解,大部分的用户质疑SERIALIZABLE隔离级别带来的性能问题,但是根据Jim Gray在《Transaction Processing》一书中指出,两者的开销几乎是一样的,甚至SERIALIZABLE可能更优!!!因此在InnoDB存储引擎中选择REPEATABLE READ的事务隔离级别并不会有任何性能的损失。同样地,即使使用READ COMMITTED的隔离级别,用户也不会得到性能的大幅度提升。
Mysql中MVCC(多版本并发控制)的使用及原理详解
事务控制
使用下面的命令来控制事务:
- BEGIN TRANSACTION:开始事务处理。
- COMMIT:保存更改,或者可以使用 END TRANSACTION 命令。
- ROLLBACK:回滚所做的更改。
事务控制命令只与 DML 命令 INSERT、UPDATE 和 DELETE 一起使用。他们不能在创建表或删除表时使用,因为这些操作在数据库中是自动提交的。