事务(Transaction)是一个操作序列。这些操作要么都做,要么都不做,是一个不可分割的工作单位,是数据库环境中的逻辑工作单位。
事务是为了保证数据库的完整性
事务不能嵌套
在 oracle 中,没有事务开始的语句。一个 Transaction 起始于一条 DML(Insert、Update 和 Delete)语句,结束于以下的几种情况:
示例 DDL 语句执行自动提交事务
insert into test02;
select * from emp where emp.deptno = 10;
create table test04 as
select * from emp where 1=2;
事务是并发控制的基本单位。
一旦执行了 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;
语句将放弃所有的数据修改
锁的机制:
为了解决在并发访问的时候,数据不一致的问题,需要给数据加锁,加锁的同同时需要考虑加锁的“粒度”问题:
操作的对象可以是:
事务四大特征如下:
一个原子事务要么完整执行,要么干脆不执行。这意味着,工作单元中每项任务都必须被正确执行。如果有任一任务执行失败,则整个工作单元或事务就会被终止。
即此前对数据所做的任何修改都将被撤销。如果所有任务都被成功执行,事务就回被提交,即对数据所做的修改将会是永久性的。
如何理解上面这句话呢?
原子性表达的含义是 不可再分的意思,把一个事务当成一个原子,要么存在要么不存在。绝不可能存在一个事务开始后执行一半,然后就去写入数据库文件这种事情发生。
如果你非要抬杠的话:我们可以设置 savepoint 存档点!
那你不得先提交然后再回滚到 savepoint 么? 提交表示一个事务执行完成,如果你设置 savepoint 的那个事务都没执行成功,你设置的存档点根本就不存在。
有了原子性我们能干啥事儿呢?
转账之类的是最能贴近生活的例子了。
有一天小六去买最喜欢吃的台湾卤肉饭,招牌卤肉饭是 12 块钱,微信上零钱刚好有 12 块钱,然后去通过扫码给商家转账过去了,这时候微信支付系统会开始执行事务了。
这个事务我们叫它“支付事务” 事务先从数据库中把小六的支付钱数扣除,然后再把这个 12 块钱加到卤肉饭老板的微信余额里面。
事务是具有原子性,从小六的微信零钱中的扣钱和向老板微信余额中加钱的操作是一气呵成的如果中间流程执行失败就会回滚到事务开始执行的初始状态。最后扣钱和加钱的金额肯定是相等的。这是非常符合实际情况的。
可是此时假设事务没有原子性的特性,这时候就会小六就会出现以下尴尬的情况。
“支付事务”在先执行了对小六的扣款操作,然后嘞 由于不知名的因素事务在向老板的余额中执行加钱操作的时候,失败了,由于没有原子性保障,小六的扣款操作是已经写入数据库文件的了,不可逆。这时候卤肉饭老板也没收到钱,就发飙了,你这个年轻人都不讲 5 的,明明没付钱,还说自己付过了。
此时小六一脸委屈,从此之后卸载微信,掏出支付宝(ding~ 您已到账五百万元)。
一致性代表了底层数据存储的完整性。它必须由事务系统和应用开发人员共同保证。事务系统通过保证事务的原子性,隔离性和持久性来满足这一要求;应用开发人员则需要保证数据库有适当的约束(主键,引用完整性等),并且工作单元中所实现的业务逻辑不会导致数据的不一致(即,数据预期所表达的现实业务情况不一致)。
例如,在一次转账过程中,从某一账户中扣除的金额必须与另一账户中存入的金额相等,支付宝账户有 100 块,你读到余额要取,有人向你转 100 但是事务没提交(这时候你读到的余额应该还是 100,而不是 200)这就是一致性。
隔离性意味着事务必须在不干扰其他进程或事务的前提下独立进行。换言之,在事务或工作单元执行完毕之前,其访问的数据不能收其他部分的影响。
如何理解隔离性嘞?
假设,快过年了,你想要给你家 Leader 买新的羽绒服,然后你支付宝上没钱,微信支付宝都绑定的同一张银行卡,然后你从微信上提现 400 到银行卡,是有延迟的,然后你这时候是在抢购中眼看你想要买的那件就剩没多少库存了。你这时候在不晓得你微信提现到银行卡的钱到底到没到帐的时候,你就直接去支付了,是然后支付宝拉起付款页面输入密码之后开始转圈圈了,(这时候你要支付的钱是 899 ,你点击确认支付时候你卡里实际只有 500 块)这时候事务开始执行了,你说巧不巧就在你点了确认,然后事务刚开始执行,这时候你微信提现到银行卡的钱实际到账了还没给你发短信,结果是最终显示余额不足,支付失败。然后你不信邪,再提交了一次,支付成功了。
为啥会有这种情况嘞,就是因为事务的隔离性,事务开始执行时候你的余额是 500 ,执行过程中你实际金额已经是 900 的时候,你依然会提交失败。当第二次重试时候,事务再次开始执行你的余额已经被刷新成实际的 900 了,所以第二次会成功。
持久性表示在某个事务的执行过程中,对数据所作的所有改动都必须在事务成功结束前保存至某种物理存储设备。
这样可以保证,所做的修改在任何系统瘫痪时不至于丢失。
持久性这个就很好理解了,完全是为了应对突发情况嘛。
假设,双十一凌晨 0 点,你在抢购超便宜 switch 游戏机,然后你支付过程中,负责执行你支付事务的服务器扛不住压力宕机了。你的钱已经扣掉了,但你没有支付成功,这就很 mmp 了。年轻人莫慌,事务执行过程中对数据所有改动都会在结束前保存到物理设备。也就是即使服务器临时宕机,当服务器启动时候也会恢复到事务开始执行前的状态的。你的钱会回来了,如果钱回不来,就去找 客服让 她陪你。
事务的原子性、隔离性、持久性最终的目的就是为了保证数据的一致性。
四个特性中最关键的特性就是一致性
--事务包含四个隔离级别:从上往下,隔离级别越来越高,意味着数据越来越安全
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 再次读取该数据时,得到的数据与前一次的结果不同。 这个时候就叫做幻读。
产生以上三种数据不一致的情况主要原因是并发操作破坏了事务的隔离性,并发控制机制就是要用正确的方式调度并发操作,使一个用户事务的执行不受其他事务的干扰,从而避免造成数据的不一致性。
并发控制的的主要技术有
我知道你在不可重复读和幻读的概念上很纠结,到底哪个是哪个。。。
不可重复读的定义和幻读定义区别在于,当事务 B 读取数据时候,修改此条数据的事务 A 是否提交,如果事务 A 已经提交(commit)那么,此时事务 B 先后两次读取到不一致的情况就是幻读。
如果当事务 B 读取数据时候,修改此条数据的事务 A 还未提交,那么事务 B 先后读取到两条数据不一致的情况就叫做不可重复读。
如果你理解了我定义的概念那么按照下表来记忆 事务隔离级别所对应的异常情况就很高效了。
这种表必须结合我上面的定义才有效,不然你会把自己搞混的。
隔离级别 | 异常情况 | 异常情况 | 异常情况 |
---|---|---|---|
读未提交 | 脏读 | 不可重复读 | 幻读 |
读已提交 | 不可重复读 | 幻读 | |
可重复读 | 幻读 | ||
序列化 |
请以我的概念为准,如果有细节问题,欢迎邮件来讨论。
已经用文件比对工具比对过,应该是可以放心执行的
/*
我是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
*/