-----------------------------------事务--------------------------
#学习记录
#学习笔记
1什么是事务?
事务说白了就是把一组sql语句打包在一起,同生共死,要么一起全部执行完,要么就都不执行,都是一条绳上蚂蚱。
先来看一个转账例子,创建账户表,张三向李四转账100
# ================账⼾表====================
use test_1;
DROP TABLE bank_account;
CREATE TABLE bank_account (
id bigint PRIMARY KEY AUTO_INCREMENT,
name varchar(255) NOT NULL, # 姓名
balance decimal(10, 2) NOT NULL # 余额
);
INSERT INTO bank_account(`name`, balance) VALUES('张三', 1000);
INSERT INTO bank_account(`name`, balance) VALUES('李四', 1000);
SELECT * from bank_account;
#
update bank_account set balance = balance -100 where name = '张三';update bank_account set balance = balance + 100 where name = '李四';
针对这个问题,我们需要确保如下几点
1张三和李四的账户应该都发生改变,张三账户-100成900,李四账户+100成1100,不能张三的账户余额-100,而李四的不变,或者张三的不变,李四的增加。
2张三和李四转账前后的的金额总数应该保持不变,转账钱2000,转账后900+1100应该也是两千
3转账后的修改后悔余额结果数据应该落盘永久保存起来,不能重启一下服务器余额就丢失了
4在转账的处理过程中张三和李四的余额不能因为其他转账事件而干扰。不能说张三之前转出100,而拿着之前转出的那100视为转给李四的100,然后保持1000不变,这样显然是不对的,再者有一个人和张三同时给李四转了100,你不能讲拿着别人转的100视为张三转的100,那这样就不对账了。
事务则能保证以上4个问题,得益于事务的四大特性
2事务的ACID特性
1Atomicity(原子性):原子性说白了就是某个东西(操作)不能再分了就叫具备原子性,要么发生那么不发生,不存在发生一半这种情况,事务具备原子性,意思是事务里面的sql全被绑定在一起了不能分开,要么一股脑的全部执行了,要么全都老实的不执行,如果执行中途出现异常中断了执行,会有rollback 回滚机制撤回之前的sql操作,恢复到啥也没干的状态。
2Consistency(一致性): 事务开始前和事务结束后,数据库的完整性不会被破坏。这表示写入的数据必须符合所有的预设规则,包括数据的精度,关联性以及关于事务执行过程中服务器崩溃后如何恢复;这个就确保了转账前和转账后金额总和为2000不变
3Isolation(隔离性): 数据库允许多个并发事务同时对数据进行修改和读写,隔离性可以防止多个事务并发执行时由于交叉执行而导致数据的不一致。事务可以指定不同的隔离级别,以权衡在不同的应用场景下数据库性能和安全。
4Durability(持久性): 事务处理后,对数据库的修改将永久的写入存储介质,即便系统故障也不会丢失。
3事务的使用
3.1事务的语法
# 开启事务
start transaction;
# 或
begincommit # 提交事务,并对更改持久化保存
rollback # 回滚事务,取消之前的修改,恢复原样
3.2使用事务
start transaction;
update bank_account set balance = balance -100 where name = '张三';update bank_account set balance = balance + 100 where name = '李四';
SELECT * FROM bank_account;
若回滚再查发现数据恢复了,开启事务后便会生产日志来记事务的有关操作,在修改之后,修改的数据其实早已经存储到硬盘上了,如果回滚,就会根据日志记录下来的操作,一对一的,一个也不落下的将所有的操作复原回去,复原一条删除一条日志,知道将所有日志信息都删除完,然后回滚完成。
也可以直接正式提交保存了,提交之后就相当于落盘了,存储到硬盘上去了,并且一旦提交,那么日志也随之删除,就无法再去回滚了。
3.3保存点Savepoint
可以在事务中设定保存点,那么回滚的时候,可以指定回滚到某个位置,不必直接回到最初的状态,当然你直接commit 或者总体rollback那么savepoint 就无了,或者当rollback的途中将某个savepoint点也roll了的话,那么该保持点也失效。
start transaction;
update bank_account set balance = balance -100 where name = '张三';
savepoint savepoint1;
SELECT * FROM bank_account;
开启事务,设置第一个保存点后查询一下
然后跟新李四数据,设置第二个保存点
update bank_account set balance = balance + 100 where name = '李四';
SAVEPOINT savepoint2;
SELECT * FROM bank_account;
若rollback到第一个保存点的话savepoint2也会被消去
4事务的隔离级别
4.1查询和设置事务的隔离级别
select @@global.transaction_isolation;----->查询全局作用域(对所有的客户端都有效)的隔离级别
select @@session.transaction_isolation;---->查询当前会话(当前访问的某个客户端)的隔离级别
设置隔离级别
方法一:
set @@global.transaction_isolation = 'repeatable-read';
set global transaction_isolation = 'serializable';
4.2事务的四种隔离级别
READ-UNCOMMITTED(读未提交)------>问题:脏读
就是可以读到事务进行了相应的操作并且还未提交的数据, 一个事务修改了数据库中的数据,还没提交之前切忌轻举妄动,因为你如果直接去用了还未提交的数据,保不住前一个sql改了,下一个sql又改回来了,反复拉扯,而你正好读到改了的数据,你就误以为真的改了,实则不然,这种情况就叫做脏读,读到的数据不准确.
CREATE TABLE bank_account (
id bigint PRIMARY KEY AUTO_INCREMENT,
name varchar(255) NOT NULL, # 姓名
balance decimal(10, 2) NOT NULL # 余额
);INSERT INTO bank_account(`name`, balance) VALUES('张三', 1000);
INSERT INTO bank_account(`name`, balance) VALUES('李四', 1000);INSERT INTO bank_account(`name`, balance) VALUES('钱七', 1000);
INSERT INTO bank_account(`name`, balance) VALUES('王麻子', 1000);set global transaction_isolation = 'read-uncommitted';
START TRANSACTION;
update bank_account set balance = 1000 where name = '张三';对于navicat, 我们新建一个连接来模拟另外一个客户端
在两客户端内分别建立查询窗口,然后每次设置完隔离级别后让另一个连接断开然后重新连接,如果不这样,一个客户端设置好的隔离级别在另外一个客户端不起作用.
问题分析:
正是因为隔离级别太低极易发生两个事务操作同一数据的情况,对于一个数据如果有一个事务正在操作另外一个事物不是想插手操作就插手操作的,两事务之间应该有适当的隔离,如果你很容易就能够插手,那么势必发生问题,所以为了解决脏读问题,我们可以提高隔离级别,对于还未提交的数据,其他事务不能插手操作
READ-COMMITTED(读已提交)------->出现的问题:不可重复读
只能读到已经提交后的数据,对于还有提交但已经被修改了的数据,另外一个事务读取不到,仍然读取到的是原先的数据
我们先将隔离级别设置为READ-COMMITTED;
然后继续进行刚才的修改
此时我们再去查发现既是''111'' 客户端已经将张三的balance修改为了10000,但在客户端2的事务里面查不出来.
问题分析:如果在客户端1commit提交了修改操作,那么客户端2就可以插到1000的数据了,但是对于同一个事务两次查询到的数据尽然不一样,这是无法接受的,为了解决不可重复读问题, 继续提升隔离级别到''可重复读''
REPEATABLE-READ(可重复读)------->问题:幻读
设置李四balance为10000commit提交后,客户端2查询应该能查到10000,但是这里没查到,这是因为设置可重复读后,提交前 和 提交后 读取到的数据都是一样 == 提交前的数据1000, 这里前后读到的数据就都是1000, 是可重复读的
问题分析:当我们插入一条新数据,然后再去客户端2查询
如果客户端2也查询到新插入的数据,这就叫幻读问题,但是为什么没查询到,没有出现幻读问题呢?可重复度会发生幻读问题,对于一部分幻读问题可重复度还是可以解决的,如上述,直接的select语句,直接就将新插入的数据给过滤掉,避免出现幻读现象.对于那种情况无法解决幻读问题呢?
我们尝试在客户端2内插入一条数据,数据id列设置为和王五一样的3,看看现象
这是为何呢? 我们插入一条数据是,数据库会先校验我们插入数据的合理性,比如id列, 会先到表中找看看3 这个值存在与否, 因为id列是主键列, 不可重复, 如果这个时候规避了幻读问题, 也就是新插入的王五被忽视的话, 那么3 就被认为不存在,所以应该可以正常插入, 但是事实就是发生幻读了,新插入的王五的id=3被查出来,然后你再新加一条id=3的数据显然不合理, 此处就很明显的发生了幻读问题,并且REPEATABLE-READ并没有解决这个问题
SERIALIZABLE(串行化):
串行化的隔离级别最高,就是一个事务一个事务按班就部依次执行,只有等待一个事务执行完毕后,才能去执行下一个事务
为了解决上述我们,提高隔离级别为串行化
这是因为我们现在客户端2开启了一个事务,但是没有提交啊,也就是那个事务还没完成呢,你这里又开一个事务,那这个事务就得老实的等着前一个事务完事之后才轮到你这个事务去操作数据库,这就是严格的将事务和事务之间进行了隔离,是串行化这一最高隔离级别的体现,并且开启的事务等待的时机也是有限制的,超出一定时机就直接保持,表明该事务作废,就相当于没写
客户端2就不会读到王五的有关数据,这样就很好的解决了之前幻读的问题