事务就是用户定义的一系列数据库操作,这些操作可以视为一个完成的逻辑处理工作单元,要么全部执行,要么全部不执行,是不可分割的工作单元。
在数据库的课堂上,经常被拿来举例子的就是转账: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):
在 SQL-92 标准,定义了四种隔离级别:读未提交、读提交、可重复读和串行化,以下列出四种隔离级别下系统允许/禁止哪些类型的读数据现象。
分类 | 脏读 | 不可重复读 | 幻像读 |
---|---|---|---|
读未提交 | YES | YES | YES |
读提交 | NO | YES | YES |
可重复读 | NO | NO | YES |
串行化 | NO | NO | NO |
其中,DM数据库支持三种事务隔离级别:读未提交、读提交和串行化,(另外 DM 数据库还支持只读事务,只读事务只能访问数据,但不能修改数据)读提交是 DM 数据库默认使用的事务隔离级别。用户在事务开始时,使用以下语句可设定事务隔离级别。
select isolation from v$trx;--0:读未提交,1:读提交(默认)、2:可重复读、3:串行化
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; ---可以查看到乙未提交的数据
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;
串行化隔离级是最高的隔离级别,所有的事务依次执行完毕,各个事务之间完成不可能产生干扰。该级别可以防止脏读、不可重读以及幻读具体来说,当一个串行化事务试图更新或删除数据时,而这些数据在此事务开始后被其他事务修改并提交时,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 将可重复读作为默认的隔离级别。
--给表 "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 |
视图 | 描述 |
---|---|
V$SESSIONS | 显示会话的具体信息 |
V$TRX | 显示所有活动事务的信息 |
V$TRXWAIT | 显示事务等待信息 |
V$TRX_VIEW | 显示活动事务视图信息 |
V$LOCK | 显示当前系统中锁的状态 |
V$DEADLOCK_HISTORY | 显示死锁的历史信息 |
英国物理学家威廉·汤姆孙,在 1900 年 4 月 27 日的英国皇家学会新年庆祝会的演讲中说过:“物理学的大厦已经基本建立,未来的物理学家只需要做些修修补补的工作就行了”,随即爱因斯坦相对论的提出打破了这一结构。面对高并发,数据库是否有新的应对方式,值得期待。
文章
阅读量
获赞