本文探讨GBase 8s的事务处理特性,揭示它是如何确保ACID特性,并有效管理并发事务的。
事务的概念
将用户在数据库中的某些操作集合看作一个整体,在其有序执行的过程中不被其他操作影响,这样的操作集合就是事务。
事务(Transaction)是对数据库进行操作的集合。该集合是一个不可分割的具有逻辑功能的工作单元,集合中的操作在事务的一次执行中必须全部被执行或者全部。
事务的特性(ACID 特性)
Atomicity: 原子性,构成该事务的操作集合中的全部操作在事务的运行过程中全部执行或者全部不执行。
Consistency: 一致性,事务运行的结果应该使数据库保持数据一致性。
Isolation:隔离性,构成某个事务的全部操作与其他事务或操作是隔离的,同时执行的事务之间不能够互相影响,一个事务单独运行的结果应该与该事务和其他多个事务同时运行的结果一致。
Durability: 持久性,事务的持久性是指事务运行完毕并成功提交后,其对数据库全部操作的结果应该永久地保留在数据库中。
并发事务的调度
1、并发问题和并发操作
实际应用中,通常会有来自不同用户的多个事务并发执行,事务之间会有交叉,这个问题就是事务的并发问题。
并发事务之间相互影响,会破坏相关事务的正常运行,无法保证事务的 ACID 特性,产生数据错误。
并发操作(Simultaneous Concurrency)对于数据库的并发操作是由并发运行的多个事务引起的,是并发事务中包含的对数据库的操作。
并发操作是由多个事务并发进行而产生的,其最大问题是容易导致数据库的不一致性
举例说明
两个售票点同时售出同一天同一车次的车票,第一个售票点执行的数据库事务 T1 为:
(1)读出当前车票的剩余数量 A,假设 A=50;
(2)售出一张票,剩余数量变为 A=A-1,即 A=49。
第二个售票点执行的数据库事务 T2 为:
(1)读出当前车票的剩余数量 A,假设 A=50;
(2)售出一张票,剩余数量变为 A=A-1,即 A=49。
事务 T1 和 T2 按照如下顺序并发执行:
(1)读出当前车票的剩余数量 A,假设 A=50;
(2)读出当前车票的剩余数量 A,假设 A=50;
(3)售出一张票,剩余数量变为 A=A-1,即 A=49;
(4)售出一张票,剩余数量变为 A=A-1,即 A=49。
事务执行的结果是只卖了一张车票,而实际上售出了两张,这样就可能导致同一个座位售出两张车票,数据库出现了数据的不一致性错误。
并发操作导致数据库出现数据不一致性的问题,如下表所示。事务 T1 将数据对象,即火车票的总数 A 进行了修改,变为 49,而事务 T2 在 T1 修改 A 之前读取的 A 的值仍然
是 50,T2 将此值修改为 49 后写回数据库并覆盖了 T1 事务对 A 的修改,此时,A 的值为49,就会出现这种情况:卖出两张车票后车票总数应为 48,而数据库中车票的总数为 49,
显然出现了数据的不一致性错误。
2、调度
调度(Schedule)是指事务的执行次序。
如果多个事务依次执行,则称为事务的串行调度(Serial Schedule)。
如果多个事务同时执行,则称为事务的并发调度(Concurrent Schedule)
3、冲突
冲突(conflict):事务调度中的两个操作(读操作或写操作),如果交换顺序执行,它们所属的事务运行结果会改变,那么这两个操作就被称为冲突。
不同事务对于不同数据对象进行操作(无论是读操作还是写操作)不会产生冲突;不同事务对同一数据对象进行读操作也不会产生冲突。
4、封锁
并发控制的主要技术是封锁(Locking)
封锁的对象是数据库中的数据对象,如关系型数据库中的表、记录、属性、索引等
对数据对象加锁的时机是在事务对其进行操作之前,向系统发出加锁请求。
加锁后事务 T就取得了对该数据对象的控制,在事务 T 释放它的锁之前,其他事务不能对此数据对象进行任何操作。
封锁是一种排队机制,将并行任务按锁的先后顺序排队,把并行任务变成串行任务。
5、封锁产生的问题
封锁技术在一定程度上解决了数据库的并发事务处理过程中,并发操作给数据库带来的数据一致性问题,但同时产生了新的问题如活锁和死锁。
活锁问题
在处理并发事务的过程中,如果事务 T1 在数据对象 R 上加锁,事务 T2 又请求为数据 对象 R 加锁,则 T2 必须等待 T1 释放加在 R 上的锁。如果事务 T3 也请求为 R 加锁,当 T1 释放了 R 上的锁之后系统首先响应 T3 的请求,则允许 T3 对 R 加锁,此时 T2 仍需等 待 T3 释放加在 R 上的锁。然后事务 T4 又请求封锁 R,当 T3 释放了 R 上的封锁之后系统 又响应了 T4 的请求……T2 有可能永远等待而无法为 R 加锁,这就是活锁。
活锁也称饿死,由于事务永远得不到封锁而导致。避免活锁可以采用先来先服务的策略。当多个事务请求封锁同一个数据对象时,可按照请求封锁的先后次序对事务排队,数据对象上的锁一旦释放就批准申请队列的第一个事务获得封锁。
T1
T2
T3
T4
LOCK (R)
……
……
UNLOCK (R)
LOCK (R)
等待
等待
等待
等待
LOCK (R)
等待
LOCK (R)
……
UNLOCK (R)
LOCK (R)
等待
等待
等待
LOCK (R)
……
死锁问题
如果事务 T1 对数据对象 R1 进行封锁,T2 对数据对象 R2 进行封锁,然后 T1 又请求对 R2 进行封锁,由于 T2 已封锁了 R2,则 T1 必须等待 T2 释放 R2 上的锁。接着 T2 又申请对 R1 进行封锁,由于 T1 已封锁 R1,因此,T2 也只能等待 T1 释放 R1 上的锁。这样就出现了事务 T1 等待另一个事务 T2 释放相关数据对象上的锁,而 T2 又在等待 T1 释放相关对象上的锁,T1 和 T2 两个事务由于相互等待对方释放数据对象上的锁而永远不能继续执行,这样就出现了死锁。
死锁(Dead Lock):系统中有两个或两个以上的事务都处于等待状态,并且每个事务都在等待其中另一个事务释放封锁,这样才能继续执行下去,结果造成任何一个事务都无法继续执行,这种现象称数据库系统进入了“死锁”(Dead Lock)状态
用例简单复现死锁产生
GBase模式下(会话1):
Create database if not exists testdb with buffered log;
Database testdb;
Create table if not exists a(c1 int,c2 int);
Create table if not exists b(c1 int,c2 int);
Insert into a values(1,1);
Insert into a values(2,2);
Insert into b values(1,1);
Insert into b values(1,1);
!sh /tmp/rd129/onmode_I143_rd129
Set isolation to repeatable read;
Set lock mode to wait;
Begin;
Select *from b; --读b表,对b表加上S锁
!echo “sql start sleep 5”
!sleep 15
!echo “sql sleep 5 done”
Update a set c1=6 where c2=2; -更新a表,想对a表加上X锁
Commit;
Close database;
!cat /tmp/rd129/out.log
Drop database testdb;
onmode_I143_rd129内容如下(会话2):
Onmode -I 143
(
Dbaccess -e-m testdb - <<!
Set isolation to repeatable read;
Set lock mode to wait;
Begin;
Select * from a; --读a表,对a表加上S锁
!echo “sql start sleep 5”
!sleep 10
!echo “sql sleep 5 done”
Update b set c1=6 where c2=2; --更新b表,想对b表加上X锁
Commit;
!
) >/tmp/rd129/out.log 2>&1 &
那么两个会话分别在同一个库中,对a表和b表加上共享锁S,当想进行更新时,会话1在等待会话2释放a的S锁,而会话2在等待会话1释放b的S锁,从而导致死锁产生。
那么在oracle模式下,该用例却不能产生死锁。
因为在GBase模式下,开启事务后,可以保证两个会话在执行更新后才提交保证两个会话同时进行。
而在oracle模式下,只能开启多语句模式,DML语句会自动开启事务,通过commit/rollback结束事务,DDL会提交事务。从而导致两个会话可能无法同时进行,因此并行变成串行,导致死锁无法产生。
南大通用GBase 8s数据库的事务处理能力和并发控制机制,为企业提供了一个可靠、高效和安全的数据处理平台。无论是金融交易还是在线事务处理,GBase 8s都能确保数据的完整性和一致性,满足企业对数据库系统的要求。