数据库学习之MySQL (二十七)—— 事务 transaction 事务控制语言 TCL commit rollback

MySQL学习专栏 正在持续更新中:)

事务Transaction 事务控制语言 TCL

Transaction control language TCL 事务控制语言

一组sql语句 作为一个不可分割的执行单元 就是事务
必须全部执行 或者全部失败

举例:淘宝买东西
你给商家交钱 有两步sql语句
一句是 你的钱扣了
一句是 商家的钱加了

假设第一句执行 第二句执行 凉了 反之也是如此

事务的执行 只要有一部分失败了 所有语句都会撤销 因为数据不是我们想过要的最终数据
那么就会 回滚到所有语句都没有执行过的状态

详细的往后看 这里大致有个印象

ACID

事务具备的ACID属性
A - atomicity 原子性 不可分割
C - consistency 从一个一致性状态变换到另一个一致性状态 个人认为个不可分割性是相辅相成的
由于系统异常或数据库系统出现故障,导致只有部分数据更新成功,但是这不是我们需要的最终数据, 这就是数据的不一致
简而言之 永福同享 有难同当 共同进退 没人掉队
I - isolation 隔离性 一个事务执行不被其他事务干扰
D - Durability 持久性 也就是永久改变数据库(commit)之后 接下来的其他操作 或者数据库故障
不应该对上一个已经提交了的事务 有任何影响

autocommit

autocommit属性 默认会把每句sql语句都作为一个事务
当然我们经常需要多个句子合为一个事务
那就需要关上这个功能 然后多选多句sql一次执行

SHOW VARIBLE LIKE 'autocommit';
SET autocommit=0;
start transaction;#可以省略
语句1;
语句2;
# 事务语句 增删改查 select insert update delete
commit; #github里面的 commit 还记得不?不是指“评论”,而是指提交更改 

在commit之前数据只在内存 没有应用更改到(apply commits)磁盘中
commit之后 假设前面的语句发生执行错误 这这个事务执行就结束了 完美收官

commit与rollback;

事务执行语言
美国国家标准学会(ANSI)制定了管理 SQL 数据库事务的标准。
从事务执行开始(start transaction),后面的sql语句顺序执行,内容我们来定,直到如下 4 个事件之一发生:

1️⃣遇到(reach)COMMIT 语句,所有的,之前事务在内存执行过的修改,都永久的记录(或者说 正式应用于 officially apply to)在数据库中。此时 commit语句也标志着事务结束
2️⃣遇到(reach)ROLLBACK 语句,所有的,之前事务在内存执行过的修改,都取消,数据库回滚到以前的状态(一致性的);
3️⃣所有sql语句成功执行到程序末尾 没有错误,那么效果就等同于遇到了COMMIT;
4️⃣sql语句执行中,有错误出现,那么就等同于遇到了一句ROLLBACK,数据库回滚到以前一致性的状态。

存储引擎

数据存储(应用在)在文件过程中采用的技术
我们提到过:事务在内存执行过的修改,都永久的记录(或者说 正式应用于 officially apply to)在数据库中
记录的过程由存储引擎完成 不过遗憾的是

MySQL常用的三个存储引擎中 innodb myisam memory
只有innodb支持事务

查看自己数据库所用的引擎 代码:

SHOW engine; 

事务并发问题

前面提到的 隔离性(isolation)其实是理想的
一个事务还是会或多或少的受另一个事务干扰
那么事务并发(简单理解为 同时执行)
出现以下三种常见的并发问题:
脏读
不可重复读
幻读

我们在下一段和事务隔离等级一并解释清楚

事务隔离级别

出现并发问题 我们就得加强控制了,那就得设定策略 设定事务隔离的等级问题

隔离级别有4种,由低(控制、抑制干扰的程度较宽松)到高(控制、抑制干扰的程度较严格)
分别为
Read uncommitted
Read committed
Repeatable read
Serializable

oracle仅支持两种隔离级别(read uncommit & serializable)
mySQL 支持四种

下面通过事例一一阐述隔离级别,并详细阐述,
为啥设定隔离级别能够解决上述的 事务并发问题 脏读 不可重复读 幻读
设定隔离等级 怎么解决上述问题的

1️⃣Read uncommitted

读未提交,顾名思义,就是一个事务可以读取另一个未提交事务的数据

事例
阮菜鸡工资是1万/月。
发工资时,会计不小心按错了数字,按成1.2万,该钱已经打到程序员的户口,但是我们知道银行有个一天反悔的机制 也就是数据库的事务还没有提交
在这时,阮菜鸡去看自己的工资,发现多了2千,以为涨工资了,正准备去消费(剁手!)呢。
但是会计对账,发现账目不对,马上撤回转账(数据库里就是:回滚),
阮菜鸡去shopping mall的时候 结账 傻眼了
咋多的两千没了QAQ…

分析:阮菜鸡看到的是 会计还没提交事务时的数据。这就是脏读

那怎么解决脏读呢?Read committed!

2️⃣Read committed

读提交,顾名思义,就是一个事务要等另一个事务提交才能读取数据。

事例
阮菜鸡工资到了 1w,他拿着信用卡去享受生活,
当他埋单时(阮菜鸡事务开启),收费系统事先检测到他的卡里有1万,
在这个时候!!阮菜鸡的老爸把钱全部转出作为股票投资,
没提交的时候,当然 查询余额还是1w的啦 这就避免了脏读
但是 又出来了bug:
阮菜鸡老爸提交购买股票操作 扣钱确认了
当阮菜鸡结账的时候,也就是收费系统准备扣款时,再检测卡里的金额,
发现:没钱了

也就是 阮菜鸡事务在操作表的时候,没法觉察到别的事务已经偷梁换柱,把数据改了
这就是不可重复读 也就阮菜鸡读到的卡内余额 是不可重复(不稳定的)

那怎么解决可能的不可重复读问题?Repeatable read !

3️⃣Repeatable read

重复读,就是在 开始读取数据(事务开启)时,不再允许修改操作
联系前面的学习 我们知道 对于更新Update操作,事务运行时阻塞的,事务同步调用 也就一个事务对一个表执行中,另一个事务不能过去搞搞震

这时 阮菜鸡消费的时候(阮菜鸡事务开启),再也不用担心别人把钱用了

分析:重复读可以解决不可重复读问题。
写到这里,应该明白的一点就是,不可重复读对应的是修改,即UPDATE操作。

但是可能还会有幻读问题。因为幻读问题对应的是插入INSERT操作,而不是UPDATE操作。

什么时候会出现幻读?

注意我们说 对于更新Update操作,事务运行时阻塞的,事务同步调用 也即是串行执行的 但是插入操作还是非阻塞执行的,还是被异步调用并行执行的

实例1:
一个事务T1正在查看 或者修改数据的时候(比如一张表),另一个事务T2悄咪咪往那个表加了一点东西,导致,T1再次执行查看的时候发现咋多了一条 就好像是幻觉一般 这就是幻读

实例2:
如果 T1想执行的是修改操作,可能就会有严重的后果,
比如,老师(老师事务)想给一个全是男生的班级,每个人都改成性别男(修改所有行对象实例的数据),结果还没提交,
教学处(教学处事务)说你们班来了个女生,INSERT 插入了一个女生数据 却没有告诉老师
老师就这么一改 提交
然后一个妹子就被改成汉子了:)

那怎么解决幻读问题?Serializable!

4️⃣Serializable 串行化(即 序列化)

Serializable 是最高的事务隔离级别,在该级别下,事务串行化顺序执行,所有操作都是阻塞被同步调用
可以避免脏读、不可重复读与幻读
但是~这种事务隔离级别效率低下(串行你懂得),比较耗数据库性能,一般不使用。

5️⃣大多数数据库默认的事务隔离级别是Read committed,比如Sql Server , Oracle。
Mysql的默认隔离级别是Repeatable read。

实践 read uncommitted

我们做个小案例感受一下 当隔离等级设置不当 为啥会导致脏读 幻读 不可重复读

1️⃣我们采用MySQL的控制台黑框子 cmd 登录一下 因为SQLyog是单个用户的
不知道方法的请看第二节 传送:数据库学习之MySQL (二)——MySQL的安装 环境配置 登录 操作方法

2️⃣我们用两个cmd框子 模拟两个访问数据库的用户 用户操作就是事务
也就是 两个事务同一张表的操作

基本思路是 employees表 阮菜鸡的部门号从90更改成60了
这是 负责更改的事务1 T1

想要查询我部门号的事务2 T2 之间
会有啥有趣的事情发生呢?我们拭目以待:

3️⃣我们首先得把事务隔离等级了 这样 会造成脏读幻读不可重复读的等级
这里教两句话
一个是查询当前的事务隔离级别:

select @@tx_isolation;

在这里插入图片描述
可见不是最低级,

二个是 我们设置一个最低的级别

set session transaction isolation level read uncommitted

在这里插入图片描述
然后再查一下:
在这里插入图片描述

ok 两个cmd框都这么干一遍 记得
也就是两个事务(左边T1 右边T2)的隔离等级都降低:

在这里插入图片描述
因为用到了中文 记得两个事务都执行:

set names gbk;

更新前 我的部门号
我是这么查询的:

use data1;
select concat_ws('-',last_name,first_name) as 名字,department_id as 部门号 from employees where last_name='阮';

在这里插入图片描述
然后 T1 执行录入(更新 Update)我的新部门编号 一句一句打哈

use data1;
set autocommit=0; #这里开始 就默认有一句start transaction; 了 所以这里开始就可以输语句了
update employees set department_id=60 where last_name='阮';

这是更新后
在这里插入图片描述
可见 已经"更改"了
真的更改了吗???
我说过 这不过是内存的值 还没有应用commit呢!

这时 事务2 T2 悄咪咪的想查询阮菜鸡的部门号:
在这里插入图片描述
诶?阮菜鸡已经是60号部门了啊?
这就是 脏读现象
此时看到了的也就是不可重复读(不算是幻读 幻读针对的是 当T1 添加新行 时的现象 我这个是 更改

此时 T1并没有commit 也就 我可以把T2当猴耍一下:
在这里插入图片描述
这时 T2再查一波 懵逼了:
在这里插入图片描述
这时T2已经不敢相信自己的眼睛 于是搞不清楚阮菜鸡到底是哪个部门
这时 我们
在这里插入图片描述
最后还是改成60 而且这次 盖棺定论 commit以后 理论上你服务器硬件爆炸 这个操作也不得rollback了,你可以试试:
在这里插入图片描述

实践 read committed

还是上面的实例 阮菜鸡部门号为60
我们把T1 T2的隔离等级改成read committed
然后 T1 更改id = 90 结果如下:
在这里插入图片描述
这时 T2想查
注意加一句commit; 后再查询 查询语句也属于事务的范畴!!!
前面的查询语句会对后面产生影响

然后我们就能看到 虽然T1改了(60->90) T2显示的还是60
在这里插入图片描述
这说明 T1没有commit的更新内容 T2 并没有不可重复读或者脏读

其他隔离等级的实践

其他的隔离等级 聪明的你可以自己测试一下
测试Serializable 序列化的时候 注意
幻读是 T1修改之前(修改内容可以是 把所有行的名字改成abc) T2插入了新的一行 导致T1修改的对象更多了(就是多了一行)
其实T1修改的原意是 T2没添加的时候 之前所有行
这就造成了幻读 这种情况只有 当隔离等级设置成Serializable 序列化的时候可以避免
其他都凉

总结

这节内容有点多 但相信把事务 这个装逼 的概念理清楚了应该

发布了27 篇原创文章 · 获赞 22 · 访问量 8765

猜你喜欢

转载自blog.csdn.net/weixin_43178828/article/details/104213671