注册
【与达梦同行】达梦数据库事务管理
技术分享/ 文章详情 /

【与达梦同行】达梦数据库事务管理

DM_sms 2022/12/28 3789 4 1

一、事务简介

事务就是用户定义的一系列数据库操作,这些操作可以视为一个完成的逻辑处理工作单元,要么全部执行,要么全部不执行,是不可分割的工作单元。
在数据库的课堂上,经常被拿来举例子的就是转账:A 转账给 B 一百块,这个转账会涉及到两个关键的步骤,A 的余额减少 100 块,B 的余额增加 100 块。事务要保证这关键的步骤,要么成功,要么失败 。

--创建一个测试表 create table "TEST1" ( "ID" INT not null , "monkey" NUMBER(10, 4), "name" VARCHAR2(50), primary key("ID") ) ---插入测试数据 insert into "TEST1"("ID", "monkey", "name") VALUES(001, 5000,'A'); insert into "TEST1"("ID", "monkey", "name") VALUES(002, 50000,'B'); insert into "TEST1"("ID", "monkey", "name") VALUES(003, 100000,'C'); insert into "TEST1"("ID", "monkey", "name") VALUES(004, 6000,'D'); insert into "TEST1"("ID", "monkey", "name") VALUES(005, 7000,'E'); commit; --1.A给B用户转账100块 update "TEST1" set "monkey"="monkey"-100 where ID=001; --2.B的余额增加100 update "TEST1" set "monkey"="monkey"+100 where ID=002; --3.提交事务 commit;

在上面的例子中,需要考虑两种情况:如果两条 SQL 语句全部正常执行,使账户间的平衡得以保证,那么此事务中对数据的修改就可以应用到数据库中;如果发生诸如资金不足、 账号错误、硬件故障等问题,导致事务中一条或多条 SQL 语句不能执行,那么整个事务必须被回滚掉才能保证账户间的平衡。DM 数据库提供了足够的事务管理机制来保证上面的事务要么成功执行,所有的更新都会写入磁盘,要么所有的更新都被回滚,数据恢复到执行该事务前的状态。无论是提交还是回滚,DM 保证数据库在每个事务开始前、结束后是一致的。
数据库事务具有四大特性(ACID):

  1. 原子性(Atomicity):事务是最小的执行单位,不允许单独分开。
    举例:转账业务必须由上面两个步骤组成,任何一个步骤失败,业务必须回滚。
  2. 一致性(Consistency):执行事务前后,数据保持一致。
    举例:转账前后,两者所在金额总和是恒定值,不会因为转赠操作而导致金额丢失。
  3. 隔离性(Isolation): 并发访问数据库时,一个用户的事务不被其他事务所干扰,各并发事务之间数据库是独立的。
    举例:用户 A、B 转账和用户 C、D 转账没有任何影响,隔离开。
  4. 持久性(Durability):一个事务被提交之后。它对数据库中数据的改变是持久的,即使数据库发生故障也不应该对其有影响。
    举例:A 和 B 转账成功后,没有其他操作的情况下,A 的资金会一直是原先的金额-转账的金额,B 的资金是原先的金额+收到的金额。

二、事务带来的并发问题

  1. 脏读(DirtyRead)
    所谓脏读就是对脏数据的读取,而脏数据所指的就是未提交的已修改数据。也就是说,一个事务正在对一条记录做修改,在这个事务完成并提交之前,这条数据是处于待定状态的(可能提交也可能回滚),这时,第二个事务来读取这条没有提交的数据,并据此做进一步的处理,就会产生未提交的数据依赖关系,这种现象被称为脏读。如果一个事务在提交操作结果之前,另一个事务可以看到该结果,就会发生脏读。
    举例:小张本月奖金 500 块,财务错误地加在小王工资上,还未提交,小王通过事务查询自己的工资多了 500,后续财务核算进行修改,将事务进行回滚,这样员工 B 通过事务看到的是脏读。
  2. 不可重复读(Non-RepeatableRead)
    一个事务先后读取同一条记录,但两次读取的数据不同,我们称之为不可重复读。如果一个事务在读取了一条记录后,另一个事务修改了这条记录并且提交了事务,再次读取记录时如果获取到的是修改后的数据,这就发生了不可重复读情况。
    举例:在事务A中,读取到张三的工资为 5000,操作没有完成,事务还没提交。与此同时,事务 B 把张三的工资改为 8000,并提交了事务。 随后,在事务 A 中,再次读取张三的工资,此时工资变为 8000,两次读取到的结果不一致。
  3. 幻像读(PhantomRead)
    一个事务按相同的查询条件重新读取以前检索过的数据,却发现其他事务插入了满足其查询条件的新数据,这种现象就称为幻像读。
    举例:目前公司员工有 10 人,老板通过事务 A 读取公司所有人数为 10 人。此时招聘部通过事务B插入一条新员工的记录。这时,事务 A 再次查看公司的员工人数记录,记录为 11 人。此时产生了幻读。

三、事务隔离级

在 SQL-92 标准,定义了四种隔离级别:读未提交、读提交、可重复读和串行化,以下列出四种隔离级别下系统允许/禁止哪些类型的读数据现象。

分类 脏读 不可重复读 幻像读
读未提交 YES YES YES
读提交 NO YES YES
可重复读 NO NO YES
串行化 NO NO NO

其中,DM数据库支持三种事务隔离级别:读未提交、读提交和串行化,(另外 DM 数据库还支持只读事务,只读事务只能访问数据,但不能修改数据)读提交是 DM 数据库默认使用的事务隔离级别。用户在事务开始时,使用以下语句可设定事务隔离级别。

  1. 查看事务的隔离级别
select isolation from v$trx;--0:读未提交,1:读提交(默认)、2:可重复读、3:串行化
  1. 设定读未提交隔离级:
SET TRANSACTION ISOLATION LEVEL READ UNCOMMITTED;

假设:事务甲读,事务乙写,两个事务互不干扰,互不等待,这样事务乙还没提交的事务,事务甲就读到,这样虽然并发性最高,但是会造成脏读。会造成用户A在半夜的时候,发现自己的账号莫名多了巨款,一整夜想着跑车洋楼,却是美梦一场。

--事务甲 select "monkey" from "TEST1" where ID=001; --事务乙 update "TEST1" set "monkey"="monkey"+100000 where ID=001; --事务甲 select "monkey" from "TEST1" where ID=001; ---可以查看到乙未提交的数据
  1. 设定读提交隔离级(达梦数据库的默认级别)
SET TRANSACTION ISOLATION LEVEL READ COMMITTED;

假设.事务甲读,事务乙写的时候,只要未提交,甲再读的时候,数据还是不会变。但是只要事务乙写完提交后,事务甲读的时候,获取的记录会读到该表,这就是不可重复读。

---事务乙: update "TEST1" set "monkey"="monkey"+100000 where ID=001; update "TEST1" set "monkey"="monkey"+100 where ID=002; ---事务甲: select * from "TEST1"; ---事务乙: commit; ---事务甲: select * from "TEST1";
  1. 设定串行化隔离级
SET TRANSACTION ISOLATION LEVEL SERIALIZABLE;

串行化隔离级是最高的隔离级别,所有的事务依次执行完毕,各个事务之间完成不可能产生干扰。该级别可以防止脏读、不可重读以及幻读具体来说,当一个串行化事务试图更新或删除数据时,而这些数据在此事务开始后被其他事务修改并提交时,DM 数据库将报“串行化事务被打断”错误。
假设:事务甲读,事务乙写的时候,不管事务是否提交,甲再读的时候,数据还是不会变。

--事务甲 select * from "TEST1"; --事务乙 insert into "TEST1"("ID", "monkey", "name") VALUES(025, 7000,'V'); commit--事务甲 select * from "TEST1"; --此时查询到的记录和之前一致,并没有因为乙添加了记录发生改变 假设:甲和乙修改同一条数据 --事务甲 update "TEST1" set "monkey"="monkey"+100 where "name"='B'; commit; --事务乙 update "TEST1" set "monkey"="monkey"+100 where "name"='B'; --执行失败,提示"串行化事务被打断" --设定事务为只读事务: SET TRANSACTION READ ONLY;

在实际的生产业务中,DM 和 Oracle、SqlServer 相同,大多数数据库选择读提交作为默认的隔离级别,使用读提交隔离级别可以满足大多数应用需要。如:在网上购买余数不多的高铁票时,首页显示为数不少的票数,在付款时,却提示余票 0,虽然有不可重读的情况,考虑到节假日一票难求,这也是符合实际。读未提交,对于数据的严谨性,明显不符合逻辑,在访问只读表和视图的事务,以及某些执行 SELECT 语句的事务(只要其他事务的未提交数据对这些语句没有 负面效果)时,可以使用读未提交隔离级。既然串行化隔离级是最高的隔离级别,如果选择串行化隔离级别,效率会非常低,需要充分考虑到并发性。如果银行和高铁使用串行化隔离级,那几乎所有的人都要到窗口办理业务。
至于 Mysql 为什么选择可重复读作为默认的隔离级别?MySQL默认隔离级别是可重复读,主要还是因为历史原因,5.1 版本之前,Mysql 的binlog(二进制)类型 Statement 是默认格式,即依次记录系统接受的 SQL 请求;5.2 版本之后,MySql 提供了 Row,Mixed,Statement 三种 Binlog 格式,使用读已提交隔离级别,会出现 bug,因此 MySql 将可重复读作为默认的隔离级别。

四、事务的封锁机制

  1. 锁概念
    在高并发的情况下,数据库多个事务同时存取同一数据时,若对并发操作不加控制就可能会读取和存储不正确的数据,破坏数据库的一致性。DM采用了多版本并发控制(MVCC) 和封锁机制,防止并发事务修改同一数据,保护数据一致性。
  2. 锁模式
    锁模式指定并发用户如何访问锁定资源。DM8 数据库使用 4 种不同的锁模式:共享锁、排他锁、意向锁(意向共享锁和意向排他锁),值得注意的是,在 ORACLE 中,还存在共享意向排他锁。
    (1)共享锁(简称 S 锁)用于只读操作。这种模式允许并发事务同时读取相同的资源,但是不允许任何事务修改这个资源。
    (2)排它锁(简称 X 锁)用于修改数据的操作,如插入、更新和删除。当某个事务占用某个资源上的排他锁时,其他事务就不能修改此数据,这种锁模式防止并发用户同时更新相同的数据,否则会造成数据不一致或者不正确的现象。
    共享锁和排他锁的相容性是比较差的。一般锁定的粒度越大,需要锁定的对象就越少,可选择性就越小,并发度就越低,开销就越小;反之,锁定的粒度越小,需要锁定的对象就越多,可选择性就越大,并发度就越大,开销就越大。意向锁解决了共享锁和排他锁相容性差的问题。
    (3)意向锁:如果对一个结点加意向锁,则说明该结点的下层节点正在被加锁;对任一结点加锁时,必须先对它的上层结点加意向锁。
    意向共享锁(Intent Shared Lock,简称 IS 锁):一般在只读访问对象时使用。
    意向排他锁(Intent Exclusive Lock,简称 IX 锁):一般在修改对象数据时使用
  3. 锁的兼容性
    在执行 SELECT、INSERT、DELETE、UPDATE 等 DML 语句时, 隐式上意向锁,查询上 IS 锁,插入、删除和更新行存储表上 IX 锁,列存储表上 X 锁。DM 的隐式封锁足以保证数据的一致性,但用户可以根据自己的需要手工显式锁定表。
--给表 "TEST1" 加共享锁 LOCK TABLE "TEST1" IN SHARE MODE; --给表 "TEST1" 加排他锁 LOCK TABLE "TEST1" IN EXCLUSIVE MODE; --给表"TEST1"加意向共享锁 LOCK TABLE "TEST1" IN INTENT SHARE MODE; --给表"TEST1"加意向排他锁 LOCK TABLE "TEST1" IN INTENT EXCLUSIVE MODE;

前面说到共享锁和排他锁的相容性是比较差的,关于锁的兼容性如下(“Y"代表相容,”N”代表不相容),可以从以下表锁的兼容性看到,当表TEST1 上了排他锁(X 锁),其他事务将无法查询(IS),插入、删除和更新(IX)表 TEST1。

分类 IS IX S X
IS Y Y Y N
IX Y Y N N
S Y N Y N
X N N N N
  1. 锁粒度
    锁定对象的大小被称为封锁的粒度,Oracle中重点关注行级锁(TM锁)和列级锁(TX锁),根据锁定对象的不同,DM锁可以分为 TID 锁和对象锁。
    TID 锁:以事务号为封锁对象,为每个活动事务生成一把 TID 锁, 代替了其他数据库行锁的功能。
    防止多个事务同时修改同一行记录。 每个事务启动时创建一把独占的TID锁,锁模式为X锁,并持有到事务结束。 DM8实现的是行级多版本,每一行记录隐含一个 TID 字段,用于事务可见性判断,协调行级的多版本并发控制 。
    对象锁:DM 新引入的一种锁,通过统一的对象 ID 进行封锁,将对数据字典的封锁和表锁合并为对象锁,以达到减少封锁冲突、提升系统并发性能的目的。
  2. 多版本控制(MVCC)
    多版本控制,对数据的每次更新形成数据的新版本,老版本保存在回滚段中。 读操作将不需要上锁,读操作与写操作不会相互阻塞,并发度大幅度提高。 如果数据库仅通过锁机制来实现并发控制,数据库对读操作上共享锁,写操作上排他锁,这种锁机制虽然解决了并发问题,但影响了并发性。例如,当对一个事务对表进行查询时,另一个对表更新的事务就必须等待。DM 数据库的多版本实现完全消除了行锁对系统资源的消耗,查询永远不会被阻塞也不需要上行锁,并通过 TID 锁机制消除了插入、删除、更新操作的行锁。数据库的读操作与写操作不会相互阻塞,并发度大幅度提高。
  3. 相关动态视图
视图 描述
V$SESSIONS 显示会话的具体信息
V$TRX 显示所有活动事务的信息
V$TRXWAIT 显示事务等待信息
V$TRX_VIEW 显示活动事务视图信息
V$LOCK 显示当前系统中锁的状态
V$DEADLOCK_HISTORY 显示死锁的历史信息

英国物理学家威廉·汤姆孙,在 1900 年 4 月 27 日的英国皇家学会新年庆祝会的演讲中说过:“物理学的大厦已经基本建立,未来的物理学家只需要做些修修补补的工作就行了”,随即爱因斯坦相对论的提出打破了这一结构。面对高并发,数据库是否有新的应对方式,值得期待。

评论
后发表回复

作者

文章

阅读量

获赞

扫一扫
联系客服