目录

雪月书韵茶香

昨夜西风凋碧树,独上高楼,望尽天涯路


X

数据库并发控制的基本单位--事务

事务(并发控制的基本单位)

  • 事务(Transaction)是一个操作序列。这些操作要么都做,要么都不做,是一个不可分割的工作单位,是数据库环境中的逻辑工作单位。

  • 事务是为了保证数据库的完整性

  • 事务不能嵌套

  • 在oracle中,没有事务开始的语句。一个Transaction 起始于一条DML(Insert、Update和Delete)语句,结束于以下的几种情况:

    • 用户显示执行Commit语句提交操作或Rollback语句回退
    • 当执行DDL(Create、Alter、Drop)语句事务自动提交
    • 用户正常断开连接时,Translation自动提交
    • 系统崩溃或断电时事务自动回退。

    示例DDL语句执行自动提交事务

    insert into test02;
    select * from emp where emp.deptno = 10;
    
    create table test04 as
    select * from emp where 1=2;
    

事务是并发控制的基本单位

Commit & Rollback

  • Commit
    • Commit表示事务成功的结束,此时告诉系统,数据库要进入一个新的正确状态,该事务对数据库的所有更新都已经交付实施。每个commit语句都可以看成是一个事务成功的结束,同时也是另一个事务的开始。
  • Rollback
    • Rollback表示事务不成功的结束,此时告诉系统,已经发生错误,数据库可能处在不正确的状态,该事务对数据库的更新必须被撤销,数据库应恢复该事务到初始状态。每个Rollback语句同时也是另一个事务的开始

一旦执行了Commit语句,将目前对数据库的操作提交给数据库(实际写入DB),以后就不能用rollback进行撤销

执行一个DDL,DCL语句或从SQL*Plus正常退出,都会自动执行commit命令

当然也可以在事务执行过程中设置存档点, savepoint test01 然后事务 通过rollback to test01 可以恢复到存档点

insert into test02(ename,empno,deptno ) values('cai10',1010,10);
insert into test02(ename,empno,deptno ) values('cai20',1010,10);
select * from test02;
savepoint sp01
insert into test02(ename,empno,deptno ) values('cai30',1010,10);
insert into test02(ename,empno,deptno ) values('cai40',1010,10);
select * from test02;
rollback to sp01
commit;

提交或回滚前数据的状态

  • 以前的数据可恢复
  • 当前的可以看到DML操作的结果
  • 其他用户不能看到DML操作的结果
  • 被操作的数据被锁住,其它用户不能修改这些数据

提交后的数据的状态

  • 数据的修改被永久写在数据库中
  • 数据以前的状态永久性丢失
  • 所有的用户都能看到操作后的结果
  • 记录锁被释放,其他用户可操作这些记录

回滚后的数据的状态

  • 语句将放弃所有的数据修改

    • 修改的数据被回退
    • 恢复数据到以前的状态
    • 行级锁被释放

    锁的机制:
    为了解决在并发访问的时候,数据不一致的问题,需要给数据加锁,加锁的同同时需要考虑加锁的“粒度”问题:
    操作的对象可以是:

    • 数据库

    • 一般情况下,锁的粒度越小,效率越高,粒度越大,效率越低,在实际工作环境中,大部分操作都是行级锁。

事务的ACID属性

事务四大特征如下:

  • 原子性(Atomicity)
  • 一致性(Consistency)
  • 隔离性(Isolation)
  • 持久性(Durability)

原子性(Atomicity)

一个原子事务要么完整执行,要么干脆不执行。这意味着,工作单元中每项任务都必须被正确执行。如果有任一任务执行失败,则整个工作单元或事务就会被终止。

即此前对数据所做的任何修改都将被撤销。如果所有任务都被成功执行,事务就回被提交,即对数据所做的修改将会是永久性的。

如何理解上面这句话呢?

原子性表达的含义是 不可再分的意思,把一个事务当成一个原子,要么存在要么不存在。绝不可能存在一个事务开始后执行一半,然后就去写入数据库文件这种事情发生。

如果你非要抬杠的话:我们可以设置savepoint存档点!

那你不得先提交然后再回滚到savepoint么? 提交表示一个事务执行完成,如果你设置savepoint的那个事务都没执行成功,你设置的存档点根本就不存在。

有了原子性我们能干啥事儿呢?

转账之类的是最能贴近生活的例子了。

有一天小六去买最喜欢吃的台湾卤肉饭,招牌卤肉饭是12块钱,微信上零钱刚好有12块钱,然后去通过扫码给商家转账过去了,这时候微信支付系统会开始执行事务了。

这个事务我们叫它“支付事务” 事务先从数据库中把小六的支付钱数扣除,然后再把这个12块钱加到卤肉饭老板的微信余额里面。

事务是具有原子性,从小六的微信零钱中的扣钱和向老板微信余额中加钱的操作是一气呵成的如果中间流程执行失败就会回滚到事务开始执行的初始状态。最后扣钱和加钱的金额肯定是相等的。这是非常符合实际情况的。

可是此时假设事务没有原子性的特性,这时候就会小六就会出现以下尴尬的情况。

“支付事务”在先执行了对小六的扣款操作,然后嘞 由于不知名的因素事务在向老板的余额中执行加钱操作的时候,失败了,由于没有原子性保障,小六的扣款操作是已经写入数据库文件的了,不可逆。这时候卤肉饭老板也没收到钱,就发飙了,你这个年轻人都不讲5的,明明没付钱,还说自己付过了。

此时小六一脸委屈,从此之后卸载微信,掏出支付宝(ding~您已到账五百万元)。

一致性(Consistency)

一致性代表了底层数据存储的完整性。它必须由事务系统和应用开发人员共同保证。事务系统通过保证事务的原子性,隔离性和持久性来满足这一要求;应用开发人员则需要保证数据库有适当的约束(主键,引用完整性等),并且工作单元中所实现的业务逻辑不会导致数据的不一致(即,数据预期所表达的现实业务情况不一致)。

例如,在一次转账过程中,从某一账户中扣除的金额必须与另一账户中存入的金额相等,支付宝账户有100块,你读到余额要取,有人向你转100但是事务没提交(这时候你读到的余额应该还是100,而不是200)这就是一致性。

隔离性(Isolation)

隔离性意味着事务必须在不干扰其他进程或事务的前提下独立进行。换言之,在事务或工作单元执行完毕之前,其访问的数据不能收其他部分的影响。

如何理解隔离性嘞?

假设,快过年了,你想要给你家Leader买新的羽绒服,然后你支付宝上没钱,微信支付宝都绑定的同一张银行卡,然后你从微信上提现400到银行卡,是有延迟的,然后你这时候是在抢购中眼看你想要买的那件就剩没多少库存了。你这时候在不晓得你微信提现到银行卡的钱到底到没到帐的时候,你就直接去支付了,是然后支付宝拉起付款页面输入密码之后开始转圈圈了,(这时候你要支付的钱是899 ,你点击确认支付时候你卡里实际只有500块)这时候事务开始执行了,你说巧不巧就在你点了确认,然后事务刚开始执行,这时候你微信提现到银行卡的钱实际到账了还没给你发短信,结果是最终显示余额不足,支付失败。然后你不信邪,再提交了一次,支付成功了。

为啥会有这种情况嘞,就是因为事务的隔离性,事务开始执行时候你的余额是500 ,执行过程中你实际金额已经是900的时候,你依然会提交失败。当第二次重试时候,事务再次开始执行你的余额已经被刷新成实际的900了,所以第二次会成功。

持久性(Durability)

持久性表示在某个事务的执行过程中,对数据所作的所有改动都必须在事务成功结束前保存至某种物理存储设备。

这样可以保证,所做的修改在任何系统瘫痪时不至于丢失。

持久性这个就很好理解了,完全是为了应对突发情况嘛。

假设,双十一凌晨0点,你在抢购超便宜switch游戏机,然后你支付过程中,负责执行你支付事务的服务器扛不住压力宕机了。你的钱已经扣掉了,但你没有支付成功,这就很mmp了。年轻人莫慌,事务执行过程中对数据所有改动都会在结束前保存到物理设备。也就是即使服务器临时宕机,当服务器启动时候也会恢复到事务开始执行前的状态的。你的钱会回来了,如果钱回不来,就去找 客服让 她陪你。


总结

事务的原子性、隔离性、持久性最终的目的就是为了保证数据的一致性。

四个特性中最关键的特性就是一致性

Mysql事务测试

--事务包含四个隔离级别:从上往下,隔离级别越来越高,意味着数据越来越安全
read uncommitted; 	--读未提交
read commited;		--读已提交
repeatable read;	--可重复读
(seariable)			--序列化执行,串行执行
--产生数据不一致的情况:
脏读
不可重复读
幻读

定义数据库事务并发操作引起的数据不一致情况

在写这篇博客时候我参考了其他博文,发现部分定义不明确,下面我结合《数据库系统概论》-第五版 高等教育出版社这本书中的定义来做一下区分,帮助理解。

在进行测试之前我们重新定义一下并发操作导致

书中第十一章节并发控制中原文如下

并发操作带来数据不一致包括修改、不可重复读和读“脏数据”

1.丢失修改(lost update)

两个事务T1和T2读入同一数据并修改,T2提交的结果破坏了T1提交的结果,导致T1的数据修改被丢失。

  	两个事务T1和T2读入同一数据并修改,T2提交的结果破坏了T1提交的结果,导致T1的数据修改被丢失。
  	两个事务T1和T2读入同一数据并修改,T2提交的结果破坏了T1提交的结果,导致T1的数据修改被丢失。

2.不可重复读(non-repeatable read)

不可重复读是指事务T1 读取数据后,事务T2执行更新操作,使T1无法再现前一次读取结果。

  	不可重复读是指事务T1 读取数据后,事务T2执行更新操作,使T1无法再现前一次读取结果。
  	不可重复读是指事务T1 读取数据后,事务T2执行更新操作,使T1无法再现前一次读取结果。

具体的讲,不可重复读包括三种情况:

(1)事务T1 读取某一数据后,事务T2对其进行了修改,当事务T1再次读取该数据时,得到与前一次不同的值。

(2)事务T1按一定条件从数据库中读取某些数据记录后,事务T2删除了其中部分记录,当T1再次按照相同条件读取数据时,发现某些记录神秘的消失了。

(3)事务T1按一定的条件从数据库中读取某些数据记录后,事务T2插入了一些记录,当T1再次按相同条件读取数据时,发现多了一些记录。

(1)事务T1 读取某一数据后,事务T2对其进行了修改,当事务T1再次读取该数据时,得到与前一次不同的值。

(2)事务T1按一定条件从数据库中读取某些数据记录后,事务T2删除了其中部分记录,当T1再次按照相同条件读取数据时,发现某些记录神秘的消失了。

(3)事务T1按一定的条件从数据库中读取某些数据记录后,事务T2插入了一些记录,当T1再次按相同条件读取数据时,发现多了一些记录。
(1)事务T1 读取某一数据后,事务T2对其进行了修改,当事务T1再次读取该数据时,得到与前一次不同的值。

(2)事务T1按一定条件从数据库中读取某些数据记录后,事务T2删除了其中部分记录,当T1再次按照相同条件读取数据时,发现某些记录神秘的消失了。

(3)事务T1按一定的条件从数据库中读取某些数据记录后,事务T2插入了一些记录,当T1再次按相同条件读取数据时,发现多了一些记录。

(2)(3)这两种情况有时也称为幻影(phantom row)现象。

3.脏读(dirty read)

读“脏”数据是指事务T1修改某一数据并将其写回磁盘,事务T2读取同一数据后,T1由于某种原因被撤销,这时被T1修改的过数据恢复原值,T2读到的数据就与数据库中的数据不一致,则T2读到的数据就是“脏”数据,即不正确的数据。

结合书中的定义和大多数人理解的数据库事务引起的数据不一致情况来重新整理定义如下:

脏读

当多个事务并发执行时候,假设事务B读取到了事务A执行回滚之前的数据,此时事务B读取到的数据与数据库中的数据不一致,我们称事务B读取到了脏数据,这个就叫脏读。

不可重复读

当多个事务并发执行时候,事务A执行过程中读取某一数据后,事务B 执行过程中对数据进行了修改,事务A再次读取该数据时,得到的数据与前一次的结果不同。

反例:当事务B执行完成后,事务A执行过程中读取数据与上一次执行结果不一致的情况叫幻读

幻读

当多个事务并发执行时候,事务B执行中对数据进行了修改,事务B提交后事务A再次读取该数据时,得到的数据与前一次的结果不同。 这个时候就叫做幻读。


产生以上三种数据不一致的情况主要原因是并发操作破坏了事务的隔离性,并发控制机制就是要用正确的方式调度并发操作,使一个用户事务的执行不受其他事务的干扰,从而避免造成数据的不一致性。

并发控制的的主要技术有

  • locking、
  • timestamp、
  • optimistic schedule、
  • multi-version concurrency control(MVCC)

我知道你在不可重复读和幻读的概念上很纠结,到底哪个是哪个。。。

不可重复读的定义和幻读定义区别在于,当事务B读取数据时候,修改此条数据的事务A是否提交,如果事务A已经提交(commit)那么,此时事务B先后两次读取到不一致的情况就是幻读。

如果当事务B读取数据时候,修改此条数据的事务A还未提交,那么事务B先后读取到两条数据不一致的情况就叫做不可重复读。

如果你理解了我定义的概念那么按照下表来记忆 事务隔离级别所对应的异常情况就很高效了。

这种表必须结合我上面的定义才有效,不然你会把自己搞混的。

隔离级别异常情况异常情况异常情况
读未提交脏读不可重复读幻读
读已提交 不可重复读幻读
可重复读 幻读
序列化

请以我的概念为准,如果有细节问题,欢迎邮件来讨论。

测试sql文件

已经用文件比对工具比对过,应该是可以放心执行的

截图录屏_选择区域_20201127154712

/*
我是A会话
*/
-- 查看是否是自动提交 1表示开启,0表示关闭
select @@autocommit;
-- 设置关闭
set autocommit = 0;
-- 创建数据库
create database tran;

-- 切换数据库 两个窗口都执行
use tran;
-- 准备数据
create table psn(id int primary key,name varchar(10)) engine=innodb;
-- 插入数据
insert into psn values(1,'zhangsan');
insert into psn values(2,'lisi');
insert into psn values(3,'wangwu');
commit;

select * from psn;
-- ----------------此线以下是读未提交----------------------
-- 此处只是为了测试才只设置session 的当前会话的事务隔离级别,实际一般设置是全局的
set session transaction isolation level read uncommitted;
-- 设置当前会话事务隔离级别为读未提交 完成
-- 开始事务
start transaction;
-- 查询psn表的所有数据
select * from psn;
-- 更新name列所有值为xysycx
update psn set name='xysycx';
-- 再次查询psn表 查询结果 name列已经全部更改为xysycx
select * from psn;
/*
id  name
1  xysycx
2  xysycx
3  xysycx

*/
-- 执行回滚操作
rollback;
-- 再次查询psn表
select * from psn;
/*
结果显示已经回滚初始状态
id     name
1   zhangsan
2   lisi
3   wangwu
此时在B会话中查看结果
*/
update psn set name='xysycx';
-- 再次查询psn表 查询结果 name列已经全部更改为xysycx
select * from psn;
/*
id   name
1   xysycx
2   xysycx
3   xysycx
再次转到B会话查询
*/
-- ----------------此线以上是读未提交----------------------

-- ----------------此线以下是读已提交----------------------

set session transaction isolation level read committed;

start transaction;
select * from psn;
-- 执行到此处的时候发现,两个窗口读取的数据是一致的
update psn set name ='zhangsan' where id = 1;

select * from psn;
-- 执行到此处发现两个窗口读取的数据不一致,B窗口中读取不到更新的数据
/*
结果如下
id   name
1   zhangsan
2   xysycx
3   xysycx

*/
commit;
select * from psn;-- 读取到更新的数据
/*
id   name
1   zhangsan
2   xysycx
3   xysycx
*/

-- 发现同一个事务中多次读取数据出现不一致的情况,没有出现脏读的请款,但是还是出现了不可重复读和幻读的情况
-- ----------------此线以上是读已提交----------------------
-- ----------------此线以下是可重复读----------------------
set session transaction isolation level repeatable read;
start transaction;
select * from psn;
-- 执行到此处的时候发现,两个窗口读取的数据是一致的
/*
id   name
1   zhangsan
2   xysycx
3   xysycx
*/
update psn set name ='lisi' where id = 2;
select * from psn;

/*
id   name
1   zhangsan
2   lisi
3   xysycx
*/
commit;
select * from psn;-- 读取到更新的数据
-- 此时会话A已经修改数据并提交 ,会话B 读取到A的操作结果,但是与前两次查询结果不一致 ,出现了幻读现象
/*
id   name
1   zhangsan
2   lisi
3   xysycx
*/
```sql
/*
我是B会话
*/

use tran;

select * from psn;
-- ----------------此线以下是读未提交----------------------
-- 此处只是为了测试才只设置session 的当前会话的事务隔离级别,实际一般设置是全局的
set session transaction isolation level read uncommitted;
-- 设置当前会话事务隔离级别为读未提交 完成
-- 开始事务
start transaction;
-- 查询psn表的所有数据
select * from psn;
-- 在A窗口执行update语句后未commit之前就已经可以查询到A窗口执行的结果了

select * from psn;
/*
id  name
1  xysycx
2  xysycx
3  xysycx

*/

-- 在A会话执行回滚操作之后再次查询psn表

select * from psn;
/*
结果显示已经回到初始状态
id     name
1   zhangsan
2   lisi
3   wangwu
执行了两次查询,结果不一致
*/
-- 在A会话再次提交update语句之后 再次查询psn表

select * from psn;
/*
id   name
1   xysycx
2   xysycx
3   xysycx

*/
-- ----------------此线以上是读未提交----------------------

-- ----------------此线以下是读已提交----------------------

set session transaction isolation level read committed;

start transaction;
select * from psn;
-- 执行到此处的时候发现,两个窗口读取的数据是一致的

select * from psn;
-- 执行到此处发现两个窗口读取的数据不一致,B窗口中读取不到更新的数据
/*
结果如下
id   name
1   xysycx
2   xysycx
3   xysycx

*/

select * from psn; -- 也读取到更新的数据
/*
id   name
1   zhangsan
2   xysycx
3   xysycx
*/
-- 发现同一个事务中多次读取数据出现不一致的情况,没有出现脏读的请款,但是还是出现了不可重复读和幻读的情况
-- ----------------此线以上是读已提交----------------------
-- ----------------此线以下是可重复读----------------------
set session transaction isolation level repeatable read;
start transaction;
select * from psn;
-- 执行到此处的时候发现,两个窗口读取的数据是一致的
/*
id   name
1   zhangsan
2   xysycx
3   xysycx
*/

select * from psn;
-- 此时会话A已经修改数据但是未提交,会话B 未读取到会话A的操作结果 没有出现脏读的情况,没有出现可重复读
/*
结果
id   name
1   zhangsan
2   xysycx
3   xysycx
*/
select * from psn;
-- 此时会话A已经修改数据并提交 ,会话B 读取到A的操作结果,但是与前两次查询结果不一致 ,出现了幻读现象
/*
结果
id   name
1   zhangsan
2   lisi
3   xysycx
*/

标题:数据库并发控制的基本单位--事务
作者:shuaibing90
版权声明:本站所有文章除特别声明外,均采用 CC BY-SA 4.0转载请于文章明显位置附上原文出处链接和本声明
地址:https://xysycx.cn/articles/2020/11/27/1606441855642.html
欢迎加入博主QQ群点击加入群聊:验证www.xysycx.cn