文章目录
事务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 序列化的时候可以避免
其他都凉
总结
这节内容有点多 但相信把事务 这个装逼 的概念理清楚了应该