一、事务简介
事务就是用户定义的一系列数据库操作,这些操作可以视为一个完成的逻辑处理工作单元,要么全部执行,要么全部不执行,是不可分割的工作单元。
在数据库的课堂上,经常被拿来举例子的就是转账:A转账给B100块,这个转账会涉及到两个关键的步骤,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;
数据库事务,具有四大特性(ACID)
1.原子性:A 事务是最小的执行单位,不允许单独分开。
举例:转账业务必须由上面两个步骤组成,任何一个步骤失败,业务必须回滚。
2.一致性:C 执行事务前后,数据保持一致。
举例:转账前后,两者所在金额总和是恒定值,不会因为转赠操作而导致金额丢失。
3.隔离性:I 并发访问数据库时,一个用户的事务不被其他事务所干扰,各并发事务之间数据库是独立的。
举例:用户A、B转账和用户C、D转账没有任何影响,隔离开。
4.持久性:D
一个事务被提交之后。它对数据库中数据的改变是持久的,即使数据库发生故障也不应该对其有影响。
举例: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:串行化
2.设定读未提交隔离级:
SET TRANSACTION ISOLATION LEVEL READ UNCOMMITTED;
--事务甲
select "monkey" from "TEST1"
where ID=001;
--事务乙
update "TEST1" set "monkey"="monkey"+100000 where ID=001;
--事务甲
select "monkey" from "TEST1"
where ID=001; ---可以查看到乙未提交的数据
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";
SET TRANSACTION ISOLATION LEVEL SERIALIZABLE;
---事务甲
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中,还存在共享意向排他锁。
共享锁(简称S锁)用于只读操作。这种模式允许并发事务同时读取相同的资源,但是不允许任何事务修改这个资源。
排它锁(简称X锁)用于修改数据的操作,如插入、更新和删除。当某个事务占用某个资源上的排他锁时,其他事务就不能修改此数据,这种锁模式防止并发用户同时更新相同的数据,否则会造成数据不一致或者不正确的现象。
共享锁和排他锁的相容性是比较差的。一般锁定的粒度越大,需要锁定的对象就越少,可选择性就越小,并发度就越低,开销就越小;反之,锁定的粒度越小,需要锁定的对象就越多,可选择性就越大,并发度就越大,开销就越大。意向锁解决了共享锁和排他锁相容性差的问题。
意向锁的含义:如果对一个结点加意向锁,则说明该结点的下层节点正在被加锁;对任一结点加锁时,必须先对它的上层结点加意向锁。如下图:如果对行1~行n中的某一行上锁时,必须先对表1 加意向锁,然后再对改行加锁。这样一来,事务对表加锁时,就不用再检查表中每行记录的锁标志了,提高了系统效率(图摘自Oracle)
意向共享锁(Intent Shared Lock,简称IS锁):一般在只读访问对象时使用。
意向排他锁(Intent Exclusive Lock,简称IX锁):一般在修改对象数据时使用
LOCK TABLE "TEST1" IN SHARE MODE;
LOCK TABLE "TEST1" IN EXCLUSIVE MODE;
LOCK TABLE "TEST1" IN INTENT SHARE MODE;
LOCK TABLE "TEST1" IN INTENT EXCLUSIVE MODE;
前面说到共享锁和排他锁的相容性是比较差的,关于锁的兼容性如下(“Y"代表相容,”N”代表不相容),可以从以下图锁的兼容性看到,当表TEST1上了排他锁(X锁),其他事务将无法查询(IS),插入、删除和更新(IX)表TEST1。
2.锁粒度
锁定对象的大小被称为封锁的粒度,Oracle中重点关注行级锁(TM锁)和列级锁(TX锁),根据锁定对象的不同,DM锁可以分为 TID 锁和对象锁。
显示会话的具体信息:V$SESSIONS
显示所有活动事务的信息:V$TRX
显示事务等待信息:V$TRXWAIT
显示活动事务视图信息:V$TRX_VIEW
显示当前系统中锁的状态:V$LOCK
显示死锁的历史信息:V$DEADLOCK_HISTORY
英国物理学家威廉·汤姆孙,在1900年4月27日的英国皇家学会新年庆祝会的演讲中说过:“物理学的大厦已经基本建立,未来的物理学家只需要做些修修补补的工作就行了”,随即爱因斯坦相对论的提出打破了这一结构。面对高并发,数据库是否有新的应对方式,值得期待。
(如有错误,欢迎指正)
(更多内容可查看达梦技术文档 (dameng.com):https://eco.dameng.com/document/dm/zh-cn/start/index.html)