在绝大多数情况下,为了有效地利用数据库资源,多个应用程序会并发地访问数据库,这就 是数据库的并发操作。
1、事务
数据库事务是指作为单个逻辑工作单元的一系列操作的集合。
一个典型的事务由应用程序中的一组操作序列组成,对于 DM 数据库来说,第一次执行SQL 语句时,隐式地启动一个事务,以COMMIT 或ROLLBACK 语句/方法显式地结束事务。
在执行DDL 前,DM数据库会自动把前面的操作进行提交,DDL 前面的操作作为一个完整的事务结束,DDL 语句本身所属事务则根据“DDL_AUTO_COMMIT”配置参数决定是否隐式地提交(注意,无论DDL_AUTO_COMMIT参数如何设置,ALTER TABLESPACE 和ALTER USER操作总是自动提交的)。
COMMIT操作会将该语句所对应事务对数据库的所有更新持久化(即写入磁盘),数据
库此时进入一个新的一致性状态,同时该事务成功地结束。
ROLLBACK 操作将该语句所对应事务对数据库的所有更新全部撤销,把数据库恢复到该事务初启动前的一致性状态。
为了提高事务管理的灵活性,DM 数据库还提供了设置保存点(SAVEPOINT)和回滚到保存点的功能。保存点提供了一种灵活的回滚,事务在执行中可以回滚到某个保存点,在该保存点以前的操作有效,而以后的操作被回滚掉。可以使用 SAVEPOINT SAVEPOINT_NAME 命令创建保存点,使用 ROLLBACK TO SAVEPOINT SAVEPOINT_NAME命令来回滚到保存点SAVEPOINT_NAME。
2、事务特性
原子性
事务的原子性保证事务包含的一组更新操作是原子不可分的,也就是说这些更新操作是
一个整体,对数据库而言全做或者全不做,不能部分地完成。
一致性
数据一致性是指表示客观世界同一事务状态的数据,不管出现在何时何处都是一致的、正确的、完整的。例如在转账的操作中,各账户金额必须平衡。
事务的一致性属性要求事务在并发执行的情况下事务的一致性仍然满足。
隔离性
事务是隔离的,某个并发事务所做的修改必须与任何其他的并发事务所做的修改相互隔离。事务只会识别另一并发事务修改之前或者修改完成之后的数据,不会识别处于这中间状态的数据。事务的隔离行为依赖于指定的隔离级别。
持久性
持久性是指一个事务一旦被提交,它对数据库中数据的改变就是永久性的。
3、事务提交
提交事务就是提交事务对数据库所做的修改,将从事务开始的所有更新保存到数据库
中,任何更改的记录都被写入日志文件并最终写入到数据文件,同时提交事务还会释放由事
务占用的资源,如锁。如果提交时数据还没有写入到数据文件,DM 数据库后台线程会在适
当时机(如检查点、缓冲区满)将它们写入。
已提交事务中对数据的修改被存储在数据库的缓冲区中,它们不一定被立即写入数据文件内。DM 数据库自动选择适当的时机进行写操作以保证系统的效率。因此写操作既可能发生在事务提交之前,也可能在提交之后。
在 DM 数据库中还存在有3 种事务模式:自动提交模式、手动提交模式和隐式提交模式。
自动提交
除了命令行交互式工具 DISQL外,DM数据库(如果不手动/编程设置提交模式)缺省都采用自动提交模式。
在 DISQL中,用户也可以通过执行如下语句来设置当前会话为自动提交模式:
SQL> SET AUTOCOMMIT ON;
手动提交
在手动提交模式下,DM 数据库用户或者应用开发人员明确定义事务的开始和结束,这
些事务也被称为显式事务。
在 DISQL 中,DISQL连接到服务器后第一条SQL语句或者事务结束后的第一条语句就标记着事务的开始,可以执行COMMIT或者ROLLBACK来提交或者回滚事务。
隐式提交
在手动提交模式下,当遇到 DDL语句时,DM数据库会自动提交前面的事务,然后开始一个新的事务执行 DDL 语句。这种事务提交被称为隐式提交。
DM 数据库在遇到以下 SQL语句时自动提交前面的事务:
- CREATE;
- ALTER;
- TRUNCATE;
- DROP;
- GRANT;
- REVOKE;
- 审计设置语句。
4、事务回滚
回滚事务是撤消该事务所做的任何更改。
回滚有两种形式:DM数据库自动回滚,或者通过程序/ROLLBACK命令手动回滚。
除此之外,与回滚相关的还有回滚到保存点和语句级回滚,
自动回滚
若事务运行期间出现连接断开,DM 数据库都会自动回滚该连接所产生的事务。
回滚会撤消事务执行的所有数据库更改,并释放此事务使用的所有数据库资源。
DM 数据库在恢复时也会使用自动回滚。例如在运行事务时服务器突然断电,接着系统重新启动,DM 数据库就会在重启时执行自动恢复。自动恢复要从事务重做日志中读取信息以重新执行没有写入磁盘的已提交事务,或者回滚断电时还没有来得及提交的事务。
手动回滚
一般来说,在实际应用中,用户可使用 ROLLBACK语句或者编程接口提供的回滚函数来回滚整个事务。
回滚到保存点
除了回滚整个事务之外,DM 数据库的用户还可以部分回滚未提交事务,即从事务的最末端回滚到事务中任意一个被称为保存点的标记处。用户在事务内可以声明多个被称为保存点的标记,将一个大事务划分为几个较小的片断。之后用户在对事务进行回滚操作时,就可以选择从当前执行位置回滚到事务内的任意一个保存点。
将事务回滚到某个保存点的过程如下:
- 只回滚保存点之后的语句;
- 保留该保存点,其后创建的保存点都被清除;
- 释放此保存点之后获得的所有锁,保留该保存点之前的锁。
DM数据库用户可以使用SAVEPOINT SAVEPOINT_NAME 命令创建保存点,使用 ROLLBACK TO SAVEPOINT SAVEPOINT_NAME 命令来回滚到保存点SAVEPOINT_NAME。
语句级回滚
如果在一个 SQL 语句执行过程中发生了错误,此语句对数据库产生的影响将被回滚,回滚后就如同此语句从未执行过,这种操作被称为语句级回滚。语句级回滚只会使此语句所
做的数据修改无效,不会影响此语句之前所做的数据修改。
当 INI参数ROLL_ON_ERR 为缺省值0时,在 SQL语句执行过程中发生的错误,将会
导致语句级回滚,例如违反唯一性、死锁(访问相同数据而产生的竞争)、运算溢出等。
在SQL 语句解析的过程中发生错误(例如语法错误),由于未对数据产生任何影响,因此不会产生语句级回滚。
5、事务锁
DM 数据库支持多用户并发访问、修改数据,有可能出现多个事务同时访问、修改相同数据的情况。若对并发操作不加控制,就可能会访问到不正确的数据,破坏数据的一致性和正确性。
封锁机制是实现数据库并发控制的一个非常重要的技术。当事务在对某个数据库对象进行操作前,需要先对其封锁。封锁后事务就对该数据库对象有了一定的控制,在该事务释放锁之前,其他的事务不能对此数据库对象进行相应操作。
锁模式
锁模式指定并发用户如何访问锁定资源。
DM 数据库使用四种不同的锁模式:共享锁、排他锁、意向共享锁和意向排他锁。
共享锁
共享锁(Share Lock,简称S 锁)用于读操作,防止其他事务修改正在访问的对象。这种封锁模式允许多个事务同时并发读取相同的资源,但是不允许任何事务修改这个资源。
排他锁
排他锁(Exclusive Lock,简称 X 锁)用于写操作,以独占的方式访问对象,不允许任何其他事务访问被封锁对象;防止多个事务同时修改相同的数据,避免引发数据错误;防止访问一个正在被修改的对象,避免引发数据不一致。一般在修改对象定义时使用。
意向锁
意向锁(Intent Lock)用于读取或修改被访问对象数据时使用,多个事务可以同时对相同对象上意向锁。
DM支持两种意向锁:
- 意向共享锁(Intent Share Lock,简称 IS锁):一般在只读访问对象时使用;
- 意向排他锁(Intent Exclusive Lock,简称 IX 锁):一般在修改对象数据时使用。
四种锁模式的相容矩阵如下表所示,其中Y表示相容;N表示不相容。如表中第二行第二列为Y,表示如果某个事务已经加了IS锁时,其他事务还可以继续添加IS锁,第二行第五列为N,表示如果某个事务已经加IS锁时,其他事务不能添加X锁。
表1 不同事务之间锁的相容矩阵
锁粒度
按照封锁对象的不同,锁可以分为TID 锁和对象锁。
TID锁
TID 锁以事务号为封锁对象,为每个活动事务生成一把 TID 锁,代替了其他数据库行
锁的功能,防止多个事务同时修改同一行记录。DM 实现的是行级多版本,每一行记录隐含一个TID 字段,用于事务可见性判断。
执行INSERT、DELETE、UPDATE操作时,设置事务号到 TID字段。相当于隐式地对记录上了一把 TID 锁,INSERT、DELETE、UPDATE 操作不再需要额外的行锁,避免了大量行锁对系统资源的消耗。
只有多个事务同时修改同一行记录时,才会产生新的 TID锁。例如,当事务T1(事务号为 TID1)试图修改某行数据,而该行数据正在被另一个事务T2(事务号为TID2)修改,此时事务T1会生成一个新的TID 锁,其锁对象为事务号 TID2,而非事务T2。
同时多版本写不阻塞读的特性,SELECT操作已经消除了行锁,因此DM中不再有行锁的概念。
对象锁
对象锁是 DM 新引入的一种锁,通过统一的对象ID进行封锁,将对数据字典的封锁和表锁合并为对象锁,以达到减少封锁冲突、提升系统并发性能的目的。
通常数据字典锁和表锁各自应承担的功能:
- 数据字典锁:用来保护数据字典对象的并发访问,解决DDL 并发和DDL/DML 并发问题,防止多个事务同时修改同一个对象的字典定义,确保对同一个对象的 DDL操作是串行执行的。并防止一个事务在修改字典定义的同时,另外一个事务修改对应表的数据。
- 表锁:表锁用来保护表数据的完整性,防止多个事务同时采用批量方式插入、更新一张表,防止向正在使用 FAST LOADER 工具装载数据的表中插入数据等,保证这些优化后数据操作的正确性。此外,表锁还有一个作用,避免对存在未提交修改的表执行ALTER TABLE、TRUNCATE TABLE操作。
为了实现与数据字典锁和表锁相同的封锁效果,从逻辑上将对象锁的封锁动作分为四类:
a) 独占访问(EXCLUSIVE ACCESS),不允许其他事务修改对象,不允许其他事务访问对象,使用 X方式封锁
b) 独占修改(EXCLUSIVE MODIFY),不允许其他事务修改对象,允许其他事务共享访问对象,使用S + IX方式封锁
c) 共享修改(SHARE MODIFY),允许其他事务共享修改对象,允许其他事务共享访问对象,使用 IX方式封锁
d) 共享访问(SHARE ACCESS),允许其他事务共享修改对象,允许其他事务共享访问对象,使用 IS方式封锁
显式锁定表
用户可以根据自己的需要显式的对表对象进行封锁。
显式锁定表的语法如下:
LOCK TABLE <table_name> IN <lock_mode> MODE [NOWAIT];
lock_mode 是锁定的模式,可以选择的模式有INTENT SHARE(意向共享)、INTENT EXCLUSIVE(意向排他)、SHARE(共享)和EXCLUSIVE(排他),其含义分别如下:
- 意向共享:不允许其他事务独占修改该表。意向共享锁定后,不同事务可以同时增、删、改、查该表的数据,也支持在该表上创建索引,但不支持修改该表的定义;
- 意向排他:不允许其他事务独占访问和独占修改该表。被意向排他后,不同事务可以同时增、删、改、查该表的数据,不支持在该表上创建索引,也不支持修改该表定义;
- 共享:只允许其他事务共享访问该表,仅允许其他事务查询表中的数据,但不允许增、删、改该表的数据;
- 排他:以独占访问方式锁定整个表,不允许其他事务访问该表,是封锁力度最大的一种封锁方式。
当使用NOWAIT 时,若不能立即上锁成功则立刻返回报错信息,不再等待。
锁查看
为了方便用户查看当前系统中锁的状态,DM数据库专门提供了一个 V$LOCK动态视图。通过该视图,用户可以查看到系统当前所有锁的详细信息,如锁的内存地址、所属事务ID、锁类型、锁模式等。
用户可以通过执行如下语句查看锁信息:
SQL> SELECT * FROM V$LOCK;
行号 ADDR TRX_ID LTYPE LMODE BLOCKED TABLE_ID ROW_IDX TID
1 572815360 9173 TID X 0 -1 -1 9173
2 572815480 9172 TID X 0 -1 -1 9172
其中,
ADDR 列表示锁的内存地址;
TRX_ID 列表示锁所属的事务 ID;
LTYPE 列表示锁的类型,可能是 OBJECT(对象锁)或者 TID(TID 锁);
LMODE 表示锁的模式,可能的取值有 S(共享锁)、X(排他锁)、IS(意向共享锁)、IX(意向排他锁);
BLOCKED列表示锁是否处于上锁等待状态,0表示已上锁成功,1 表示处于上锁等待状态;
TABLE_ID列对于对象锁,表示表对象或字典对象的 ID,对于 TID 锁,表示封锁记录对应的表 ID;
ROW_IDX 列为 TID锁封锁记录的行信息;
TID 列为 TID锁对象事务ID。
6、多版本控制
在多版本控制以前,数据库仅通过锁机制来实现并发控制。数据库对读操作上共享锁,写操作上排他锁,这种锁机制虽然解决了并发问题,但影响了并发性。例如,当对一个事务对表进行查询时,另一个对表更新的事务就必须等待。DM 数据库的多版本实现完全消除了行锁对系统资源的消耗,查询永远不会被阻塞也不需要上行锁,并通过 TID 锁机制消除了插入、删除、更新操作的行锁。数据库的读操作与写操作不会相互阻塞,并发度大幅度提高。
DM 数据库基于物理记录和回滚记录实现行级多版本支持,数据页中只保留物理记录的最新版本,通过回滚记录维护历史版本,所有事务针对特定的版本进行操作。
物理记录格式
为了适应多版本机制,高效地获取历史记录,每一条物理记录中包含了两个字段:TID和 RPTR。TID 保存修改记录的事务号,RPTR 保存回滚段中上一个版本回滚记录的物理地址。插入、删除和更新物理记录时,RPTR 指向操作生成的回滚记录的物理地址。
物理记录格式如下:
新物理记录的RPTR指向当前回滚记录的物理地址。
回滚记录格式
回滚记录与物理记录一样,增加了两个字段:TID和RPTR。
TID保存回滚记录对应的事务号,RPTR保存回滚段中上一个版本回滚记录的物理地址。
插入物理记录时,由于没有更老的版本数据,回滚记录的 RPTR 值为NULL;更新和删
除物理记录时,RPTR 指向原始物理记录的RPTR。
可见性原则
实现多版本控制的关键是可见性判断,找到对当前事务可见的特定版本数据。
DM 通过活动事务表,确定事务的可见性。根据事务隔离级的不同,在事务启动时(串行化),或者语句执行时(读提交),收集这一时刻所有活动事务,并记录系统中即将产生的事务号NEXT_TID。
DM多版本可见性原则:
- 物理记录的 TRXID 等于当前事务号,说明是本事务修改的物理记录,物理记录可见;
- 物理记录的TRXID不在活动事务表中,并且 TRXID小于NEXT_TID,物理记录可见;
- 物理记录的TRXID包含在活动事务表中,或者TRXID 大于等于NEXT_TID,物理记录不可见。
在DM数据库运行过程中,由于DM多版本并发控制实现策略的缘故,可能会出现“-6527记录可见性无法确定”报错。出现这个报错说明操作的执行时间过长了,报错相关操作执行前启动的某个事务 A 在这个操作的执行过程中进行了提交,且系统提交状态事务数组中记录这个事务 A 的位置已被新的事务覆盖。为了避免发生这种情况,应该尽量避免系统中的过长事务,同时可以放大TRX_CMTARR_SIZE参数。
历史数据获取
当物理记录对当前事务不可见时,根据物理记录和回滚记录的 RPTR 指针,向前回溯一个历史版本记录,通过此历史版本记录的 TID 字段,依据事务可见性原则判断此版本的记录对当前事务是否可见。如可见即获取到了满足当前事务的历史版本数据;如不可见则根据RPTR指针继续向前回溯。如果一直不能找到对当前事务的可见版本(例如此记录是一个活动事务插入的新记录),则此记录将不会添加到查询结果集中。
下面以UPDATE 为例描述多版本的实现;依次执行事务T1和 T2:
事务T1
SQL> CREATE TABLE TEST_UPDATE (COL_1 INT PRIMARY KEY, COL_2 VARCHAR(10));
SQL> INSERT INTO TEST_UPDATE VALUES(1, ‘ABCD’);
事务T2
SQL> UPDATE test_update set col_2=‘xyz’ where col_1 = 1;
执行以上两个事务以后,表 TEST_UPDATE的记录了两个版本
物理记录:
回滚记录:
回滚段自动清理
由于需要根据回滚记录回溯、还原物理记录的历史版本信息,因此不能在事务提交时立即清除当前事务产生的回滚记录。但是,如果不及时清理回滚段,可能造成回滚段空间的不断膨胀,占用大量磁盘空间。
DM提供了自动清理、回收回滚段空间的机制。系统定时(缺省是每间隔 1秒)扫描回滚段,根据回滚记录的 TID,判断是否需要保留回滚记录,清除那些对所有活动事务可见的回滚记录空间。
7、事务隔离级别
在关系型数据库中,事务的隔离性分为四个隔离级别,在解读这四个级别前先介绍几个
关于读数据的概念。
- 脏读(DirtyRead)
脏读就是对脏数据的读取,而脏数据指的是未提交的已修改数据。即一个事务在提交操作结果之前,另一个事务可以看到该结果,就会发生脏读。 - 不可重复读(Non-RepeatableRead)
一个事务先后读取同一条记录,但两次读取的数据不同,称之为不可重复读。如果一个事务在读取了一条记录后,另一个事务修改了这条记录并且提交了事务,再次读取记录时如果获取到的是修改后的数据,这就发生了不可重复读情况。 - 幻像读(PhantomRead)
一个事务按相同的查询条件重新读取以前检索过的数据,却发现其他事务插入或删除了满足其查询条件的新数据,这种现象就称为幻像读。
在 SQL-92 标准中,定义了四种隔离级别:读未提交、读已提交、可重复读和串行化。每种隔离级别下对于读数据有不同的要求,表1 中列出四种隔离级别下系统允许/禁止哪些类型的读数据现象。其中Y表示允许,N表示禁止。
表1 隔离级别表
在只有单一用户的数据库中,用户可以任意修改数据,而无需考虑同时有其他用户正在修改相同的数据。但在一个多用户数据库中,多个并发事务中包含的语句可能会修改相同的数据。数据库中并发执行的事务最终应产生有意义且具备一致性的结果。因此在多用户数据库中,对数据并发访问及数据一致性进行控制是两项极为重要的工作。
DM数据库支持三种事务隔离级别:读未提交、读已提交和串行化。其中,读提交是 DM 数据库默认使用的事务隔离级别。可重复读升级为更严格的串行化隔离级。
串行化
在要求消除不可重复读取或幻像读的情况下,可设置事务隔离级为串行化。和读已提交隔离级相比,串行化事务的查询本身不会增加任何代价,但修改数据可能引发“串行化事务被打断”错误。
具体来说,当一个串行化事务试图更新或删除数据时,而这些数据在此事务开始后被其
他事务修改并提交时,DM数据库将报―串行化事务被打断‖错误。应用开发者应该充分考虑串行化事务带来的回滚及重做事务的开销,从应用逻辑上避免对相同数据行的激烈竞争导致产生大量事务回滚。并结合应用逻辑,捕获―串行化事务被打断‖错误,进行事务重做等相应处理。如果系统中存在长时间运行的写事务,并且该长事务所操作的数据还会被其他短事务频繁更新的话,最好避免使用串行化事务。
用户可以在事务开始时使用以下语句设定事务为串行化隔离级:
SQL> SET TRANSACTION ISOLATION LEVEL SERIALIZABLE;
读已提交
DM 数据库的读提交隔离可以确保只访问到已提交事务修改的数据,保证数据处于一致性状态,能够满足大多数应用的要求,并最大限度的保证系统并发性能,但可能会出现不可重复读取和幻像读。
用户可以在事务开始时使用以下语句设定事务为读提交隔离级:
SQL> SET TRANSACTION ISOLATION LEVEL READ COMMITTED;
读未提交
DM数据库除了支持读提交、串行化两种隔离级之外,还支持读未提交这种隔离级。
读未提交隔离级别是最不严格的隔离级别。实际上,在使用这个隔离级别时,有可能发
生脏读、不可重复读和幻像。一般来说,读未提交隔离级别通常只用于访问只读表和只读视
图,以消除可见性判断带来的系统开销,提升查询性能。
用户可以在事务开始时使用以下语句,设定事务为读未提交隔离级:
SQL> SET TRANSACTION ISOLATION LEVEL READ UNCOMMITTED;
此外,DM 还允许用户在 SELECT 语句的末尾加上WITH UR或 ? 以指定当前查询语句的隔离级为读未提交,即允许脏读,并在该语句结束时自动恢复为原来的隔离级。
只读事务
除了前面所述的各种标准特性外,DM数据库还支持只读事务,只读事务只能访问数据,但不能修改数据。并且只读事务不会改变事务原有的隔离级。
用户可以在事务开始时使用以下语句,设定事务为只读事务:
SQL> SET TRANSACTION READ ONLY;
8、锁等待与死锁检测
阻塞和死锁是会与并发事务一起发生的两个事件,它们都与锁相关。
当一个事务正在占用某个资源的锁,此时另一个事务正在请求这个资源上与第一个锁相冲突的锁类型时,就会发生阻塞。被阻塞的事务将一直挂起,直到持有锁的事务放弃锁定的资源为止。
死锁与阻塞的不同之处在于死锁包括两个或者多个已阻塞事务,它们之间形成了等待环,每个都等待其他事务释放锁。例如事务 1 给表T1 上了排他锁,第二个事务给表T2 上了排他锁,此时事务 1 请求 T2 的排他锁,就会处于等待状态,被阻塞。若此时 T2 再请求表 T1 的排他锁,则T2 也处于阻塞状态。此时这两个事务发生死锁,DM 数据库会选择牺牲掉其中一个事务。
在 DM 数据库中,INSERT、UPDATE、DELETE 是最常见的会产生阻塞和死锁的语句。
INSERT发生阻塞的唯一情况是,当多个事务同时试图向有主键或UNIQUE约束的表中插入相同的数据时,其中的一个事务将被阻塞,直到另外一个事务提交或回滚。一个事务提交时,另一个事务将收到唯一性冲突的错误;一个事务回滚时,被阻塞的事务可以继续执行。
当 UPDATE 和 DELETE 修改的记录,已经被另外的事务修改过,将会发生阻塞,直到
另一个事务提交或回滚。
9、闪回
闪回技术,是为了用户可以迅速处理数据逻辑损坏(误删改)的情况而产生的。
闪回技术主要是通过回滚段存储的 UNDO 记录来完成历史记录的还原。设置ENABLE_FLASHBACK 为 1 后,开启闪回功能。DM 会保留回滚段一段时间,回滚段保留的时间代表着可以闪回的时间长度。由UNDO_RETENTION参数指定。
开启闪回功能后,DM 会在内存中记录下每个事务的起始时间和提交时间。通过用户指
定的时刻,查询到该时刻的 LSN,结合当前记录和回滚段中的UNDO 记录,就可以还原出特定 LSN 的记录。即指定时刻的记录状态。从而完成闪回查询。
闪回查询功能完全依赖于回滚段管理,对于DROP 等误操作不能恢复。
闪回特性可应用在以下方面:
- 自我维护过程中的修复:当一些重要的记录被意外删除,用户可以向后移动到一个时间点,查看丢失的行并把它们重新插入现在的表内恢复;
- 用于分析数据变化:可以对同一张表的不同闪回时刻进行链接查询,以此查看变化的数据。
社区地址:https://eco.dameng.com