事务概念
数据库中由用户自定义发起的一些列操作集合简称事务。只要可以连接数据库中进行一组操作序列都可以称为事务。
COMMIT 操作会将该语句所对应事务对数据库的所有更新持久化(即写入磁盘),数据库此时进入一个新的一致性状态,同时该事务成功地结束。ROLLBACK 操作将该语句所对应事务对数据库的所有更新全部撤销,把数据库恢复到该事务初启动前的一致性状态。
事务特性
原子性
这一组操作要么一起生效,要么都不生效,事务执行过程中如遇错误,已经执行的操作要全部撤回,这就是事务的原子性。
一致性
数据一致性是指表示客观世界同一事务状态的数据,不管出现在何时何处都是一致的、正确的、完整的。换句话说,数据一致性是任何点上保证数据以及内部数据结构的完整性,
比如账户之间无论怎么转账,总额不会变等现实约束;年龄不能为负值。
隔离性
事务是隔离的,意味着每个事务的执行效果与系统中只有该事务的执行效果一样,也就是说,某个并发事务所做的修改必须与任何其他的并发事务所做的修改相互隔离。
数据库为了提高资源利用率和事务执行效率、降低响应时间,允许事务并发执行。但是多个事务同时操作同一对象,必然存在冲突,事务的中间状态可能暴露给其它事务,导致一些事务依据其它事务中间状态,把错误的值写到数据库里。需要提供一种机制,保证事务执行不受并发事务的影响,让用户感觉,当前仿佛只有自己发起的事务在执行,这就是隔离性。
持久性
持久性是指一个事务一旦被提交,它对数据库中数据的改变就是永久性的,接下来的其他操作和数据库故障不应该对其有任何影响。即一旦一个事务提交,DBMS 保证它对数据库中数据的改变应该是永久性的。如果 DM 数据库或者操作系统出现故障,那么在 DM 数据库重启的时候,数据库会自动恢复。如果某个数据驱动器出现故障,并且数据丢失或者被损坏,可以通过备份和联机重做日志来恢复数据库。需要注意的是,如果备份驱动器也出现故障,且系统没有准备其他的可靠性解决措施,备份就会丢失,那么就无法恢复数据库了。
事务隔离级别
脏读(DirtyRead)所谓脏读就是对脏数据的读取,而脏数据所指的就是未提交的已修改数据;
不可重复度(Non-RepeatableRead)一个事务先后读取同一条记录,但两次读取的数据不同,我们称之为不可重复读;
幻像读(PhantomRead)一个事务按相同的查询条件重新读取以前检索过的数据,却发现其他事务插入了满足其查询条件的新数据,这种现象就称为幻像读。
达梦数据库支持读未提交、读提交和序列化(串行化)三种隔离级。其中,读提交是 DM 数据库默认使用的事务隔离级别。可重复读升级为更严格的串行化隔离级。
读未提交:
<会话1> SQL> SET TRANSACTION ISOLATION LEVEL READ UNCOMMITTED; 操作已执行 已用时间: 1.097(毫秒). 执行号:800. SQL> CREATE TABLE T1 (C1 INT); 操作已执行 已用时间: 74.113(毫秒). 执行号:801. SQL> INSERT INTO T1 VALUES (1); 影响行数 1 已用时间: 4.845(毫秒). 执行号:802. SQL> INSERT INTO T1 VALUES (2); 影响行数 1 已用时间: 0.408(毫秒). 执行号:812. SQL> SELECT * FROM T1; 行号 C1 ---------- ----------- 1 1 2 2 已用时间: 0.251(毫秒). 执行号:813. <会话2> SQL> SET TRANSACTION ISOLATION LEVEL READ UNCOMMITTED; 操作已执行 已用时间: 0.210(毫秒). 执行号:909. SQL> SELECT * FROM T1; 行号 C1 ---------- ----------- 1 1 2 2 已用时间: 0.292(毫秒). 执行号:910. 事务级别设置为读未提交时,会话2可以读取到会话1中未提交的数据。 |
读已提交:
<会话1> SQL> SET TRANSACTION ISOLATION LEVEL READ COMMITTED; 操作已执行 已用时间: 0.264(毫秒). 执行号:823. SQL> INSERT INTO T1 VALUES (1); 影响行数 1 已用时间: 0.474(毫秒). 执行号:824. SQL> INSERT INTO T1 VALUES (2); 影响行数 1 已用时间: 0.518(毫秒). 执行号:825. SQL> SELECT * FROM T1; 行号 C1 ---------- ----------- 1 1 2 2 已用时间: 0.383(毫秒). 执行号:826. <会话2> SQL> SET TRANSACTION ISOLATION LEVEL READ COMMITTED; 操作已执行 已用时间: 0.213(毫秒). 执行号:916. SQL> SELECT * FROM T1; 未选定行 已用时间: 0.688(毫秒). 执行号:917. SQL> SELECT * FROM T1 WITH UR; 行号 C1 ---------- ----------- 1 1 2 2 已用时间: 0.286(毫秒). 执行号:918. 事务级别设置为读已提交时,会话2只能读取到会话1中提交的数据。要想读取未提交的数据,可以加上WITH UR 。 |
串行化隔离级
在要求消除不可重复读取或幻像读的情况下,我们可以设置事务隔离级为串行化。跟读提交隔离级相比,串行化事务的查询本身不会增加任何代价,但修改数据可能引发“串行化事务被打断”错误。具体来说,当一个串行化事务试图更新或删除数据时,而这些数据在此事务开始后被其他事务修改并提交时,DM 数据库将报“串行化事务被打断”错误。应用开发者应该充分考虑串行化事务带来的回滚及重做事务的开销,从应用逻辑上避免对相同数据行的激烈竞争导致产生大量事务回滚。并结合应用逻辑,捕获“串行化事务被打断”错误,进行事务重做等相应处理。如果系统中存在长时间运行的写事务,并且该长事务所操作的数据还会被其他短事务频繁更新的话,最好避免使用串行化事务。
用户可以在事务开始时使用以下语句设定事务为串行化隔离级:
SET TRANSACTION ISOLATION LEVEL SERIALIZABLE;
只读事务
DM 数据库还支持只读事务,只读事务只能访问数据,但不能修改数据。并且只读事务不会改变事务原有的隔离级。 SQL> SET TRANSACTION READ ONLY; 操作已执行 已用时间: 0.318(毫秒). 执行号:939. SQL> UPDATE T1 SET C1=6 WHERE C1=4; UPDATE T1 SET C1=6 WHERE C1=4; [-6506]:试图在只读事务中修改数据. 已用时间: 0.277(毫秒). 执行号:0. |
锁等待与死锁检测
阻塞和死锁是会与并发事务一起发生的两个事件,它们都与锁相关。当一个事务正在占用某个资源的锁,此时另一个事务正在请求这个资源上与第一个锁相冲突的锁类型时,就会发生阻塞。被阻塞的事务将一直挂起,直到持有锁的事务放弃锁定的资源为止。死锁与阻塞的不同之处在于死锁包括两个或者多个已阻塞事务,它们之间形成了等待环,每个都等待其他事务释放锁。例如事务 1 给表 T1 上了排他锁,第二个事务给表 T2 上了排他锁,此时事务 1 请求 T2 的排他锁,就会处于等待状态,被阻塞。若此时 T2 再请求表 T1 的排他锁,则 T2 也处于阻塞状态。此时这两个事务发生死锁,DM 数据库会选择牺牲掉其中一个事务。
在 DM 数据库中,INSERT、UPDATE、DELETE 是最常见的会产生阻塞和死锁的语句。INSERT 发生阻塞的唯一情况是,当多个事务同时试图向有主键或 UNIQUE 约束的表中插入相同的数据时,其中的一个事务将被阻塞,直到另外一个事务提交或回滚。一个事务提交时,另一个事务将收到唯一性冲突的错误;一个事务回滚时,被阻塞的事务可以继续执行。
当 UPDATE 和 DELETE 修改的记录,已经被另外的事务修改过,将会发生阻塞,直到另一个事务提交或回滚。
<会话1> SQL> SELECT * FROM T1; 行号 C1 ---------- ----------- 1 1 2 3 3 2 4 5 已用时间: 0.318(毫秒). 执行号:854. SQL> UPDATE T1 SET C1=6 WHERE C1=5; 影响行数 1 已用时间: 0.739(毫秒). 执行号:945. <会话2> SQL> SELECT * FROM T1; 行号 C1 ---------- ----------- 1 1 2 3 3 2 4 5 已用时间: 0.353(毫秒). 执行号:944. SQL> UPDATE T1 SET C1=7 WHERE C1=5; 此时会话2出现阻塞。 <会话1> SQL> COMMIT; 操作已执行 已用时间: 4.858(毫秒). 执行号:946. <会话2> SQL> UPDATE T1 SET C1=7 WHERE C1=5; 影响行数 0 已用时间: 00:00:38.886. 执行号:855. 当会话1中COMMIT后,会话2阻塞消失。 <会话2> SQL> SELECT * FROM T1; 行号 C1 ---------- ----------- 1 1 2 3 3 2 4 6 已用时间: 0.287(毫秒). 执行号:856. <会话1> SQL> SELECT * FROM T1; 行号 C1 ---------- ----------- 1 1 2 3 3 2 4 6 已用时间: 0.332(毫秒). 执行号:947. |
闪回功能
闪回技术主要是通过回滚段存储的 UNDO 记录来完成历史记录的还原。
设置ENABLE_FLASHBACK 为 1 后,开启闪回功能。DM 会保留回滚段一段时间,回滚段保留的时间代表着可以闪回的时间长度。由 UNDO_RETENTION 参数指定。
开启闪回功能后,DM 会在内存中记录下每个事务的起始时间和提交时间。通过用户指定的时刻,查询到该时刻的 LSN,结合当前记录和回滚段中的 UNDO 记录,就可以还原出特定 LSN 的记录。即指定时刻的记录状态。从而完成闪回查询。闪回查询功能完全依赖于回滚段管理,对于 DROP 等误操作不能恢复。闪回特性可应用在以下方面:
- 自我维护过程中的修复:当一些重要的记录被意外删除,用户可以向后移动到一个时间点,查看丢失的行并把它们重新插入现在的表内恢复;
- 用于分析数据变化:可以对同一张表的不同闪回时刻进行链接查询,以此查看变化的数据。
SQL> select sf_get_para_value(1,'ENABLE_FLASHBACK'); 行号 SF_GET_PARA_VALUE(1,'ENABLE_FLASHBACK') ---------- --------------------------------------- 1 0 已用时间: 6.268(毫秒). 执行号:857. SQL> sp_set_para_value(1,'ENABLE_FLASHBACK','1'); DMSQL 过程已成功完成 已用时间: 20.931(毫秒). 执行号:858. SQL> select sf_get_para_value(1,'ENABLE_FLASHBACK'); 行号 SF_GET_PARA_VALUE(1,'ENABLE_FLASHBACK') ---------- --------------------------------------- 1 1 已用时间: 5.479(毫秒). 执行号:859. SQL> select sf_get_para_double_value(1,'undo_retention'); 行号 SF_GET_PARA_DOUBLE_VALUE(1,'undo_retention') ---------- -------------------------------------------- 1 9.000000000000000E+01 已用时间: 6.245(毫秒). 执行号:860. SQL> sp_set_para_double_value(1,'undo_retention','3600'); DMSQL 过程已成功完成 已用时间: 5.635(毫秒). 执行号:861. SQL> CREATE TABLE T2 ( ID INT, EMAIL VARCHAR(50), NAME VARCHAR(20)); 2 3 4 操作已执行 已用时间: 13.303(毫秒). 执行号:862. SQL> insert into T2 values(1,'123131412@qq.com','张毅'); 影响行数 1 已用时间: 0.674(毫秒). 执行号:863. SQL> insert into T2 values(2,'zhanglong@126.com','张龙'); 影响行数 1 已用时间: 0.476(毫秒). 执行号:864. SQL> insert into T2 values(3,'lihong@163.com','李红'); 影响行数 1 已用时间: 0.421(毫秒). 执行号:865. SQL> insert into T2 values(4,'zhaohu@163.com','赵虎'); 影响行数 1 已用时间: 0.475(毫秒). 执行号:866. SQL> commit; 操作已执行 已用时间: 2.009(毫秒). 执行号:867. SQL> select * from T2; 行号 ID EMAIL NAME ---------- ----------- ----------------- ------ 1 1 123131412@qq.com 张毅 2 2 zhanglong@126.com 张龙 3 3 lihong@163.com 李红 4 4 zhaohu@163.com 赵虎 已用时间: 0.528(毫秒). 执行号:868. SQL> select sysdate; 行号 SYSDATE ---------- ------------------- 1 2023-09-18 12:02:51 已用时间: 0.561(毫秒). 执行号:869. SQL> delete from T2; 影响行数 4 已用时间: 0.550(毫秒). 执行号:870. SQL> commit; 操作已执行 已用时间: 4.716(毫秒). 执行号:871. SQL> select * from T2 ; 未选定行 已用时间: 0.581(毫秒). 执行号:872. SQL> select * from T2 when timestamp '2023-09-18 12:02:51'; 行号 ID EMAIL NAME ---------- ----------- ----------------- ------ 1 1 123131412@qq.com 张毅 2 2 zhanglong@126.com 张龙 3 3 lihong@163.com 李红 4 4 zhaohu@163.com 赵虎 已用时间: 0.595(毫秒). 执行号:873. SQL> select * from T2 when timestamp '2023-09-18 12:02:51' WHERE ID=1; 行号 ID EMAIL NAME ---------- ----------- ---------------- ------ 1 1 123131412@qq.com 张毅 已用时间: 0.612(毫秒). 执行号:874. |
总结
事务常用于需要保证数据一致性和完整性的场景,如金融交易、订单处理等。
事务的设计应遵循ACID原则,包括原子性、一致性、隔离性和持久性,同时应考虑事务的粒度和并发控制的方式。
事务中可能出现异常和错误,需要合理处理和进行错误恢复,如回滚和日志记录等。
不同的隔离级别会对数据库的并发控制和性能产生不同的影响,需要根据实际需求进行选择。