相关文章推荐
冷静的签字笔  ·  [python] ...·  1 年前    · 

MySQL事务

一、事务简介

首先给大家举一个例子:我们有如下的销售业务,一个销售业务可能包含很多步骤,比如记录订单、添加积分、管理库存、扣减金额等等,每一个操作都可能对应一条或多条sql语句,但是这个业务却是不可分割的,不能下了订单,不扣减库存。此时我们就需要事务来统一管理这个业务当中的一系列sql语句了。

(1)在 MySQL 中只有使用了 Innodb 数据库引擎的数据库或表才支持事务。

(2)事务处理可以用来维护数据库的完整性,保证成批的 SQL 语句要么全部执行,要么全部不执行。

二、事务分类

1、显式事务和隐式事务

(1)mysql的事务可以分为【显式事务】和【隐式事务】。默认的事务是隐式事务,由变量【autocommit】控制。隐式事务的环境下,我们每执行一条sql都会【自动开启和关闭】事务,变量如下:

SHOW VARIABLES LIKE 'autocommit';

(2)显式事务由我们【自己控制】事务的【开启,提交,回滚】等操作,我们创建一个表,同时展示事务的基础语法,如下:

create database ydlTrx;
use ydlTrx;
-- UNSIGNED代表无符号数,不能是负数
create table user(
id int primary key auto_increment,
name VARCHAR(20),
balance DECIMAL(10,2) UNSIGNED
insert into user VALUES (1,'楠哥',200);
insert into user VALUES (2,'楠哥老婆',50000);
-- 转账业务,必须都成功,或者都失败,所以不能一句一句执行,万一执行了一半,断电了咋办
-- 所以要编程一个整体
-- 都成功
-- 开启事务;
start transaction;
UPDATE user set balance = balance - 200 where id = 1;
UPDATE user set balance = balance + 200 where id = 2;
-- 提交事务
commit;
-- 都失败
start transaction;
UPDATE user set balance = balance - 200 where id = 1;
UPDATE user set balance = balance + 200 where id = 2;
-- 回滚事务
rollback;

我们可以使用begin或start transaction开启一个事务,使用commit提交事务,使用rollback回滚当前事务。

2、只读事务和读写事务

我们可以使用read only开启只读事务,开启只读事务模式之后,事务执行期间任何【insert】或者【update】语句都是不允许的,具体语法如下:

start transaction read only
select * from ....
select * from ....
commit;

有人可能会问,这样和不开事务有什么区别呢?这个在下边学了隔离级别就知道了。

3、保存点

我们可以使用savepoint 关键字在事务执行中新建【保存点】,之后可以使用rollback向任意保存点回滚。

start transaction;
UPDATE user set balance = balance - 200 where id = 1;
savepoint a;
UPDATE user set balance = balance + 200 where id = 2;
rollback to a;

**注意:**Mysql是不支持嵌套事务的,开启一个事务的情况下,若再开启一个事务,会隐式的提交上一个事务:

start transaction;
UPDATE user set balance = balance - 200 where id = 1;
start transaction;    -- 这里会自动将第一个事务提交
UPDATE user set balance = balance + 200 where id = 2;
commit;
-- 回滚事务
rollback;

三、事务四大特征(ACID)

1、原子性(Atomicity)

一个事务(transaction)中的所有操作,要么全部完成,要么全部不完成,不会结束在中间某个环节。如果事务在执行过程中发生错误,会被回滚(Rollback)到事务开始前的状态,就像这个事务从来没有执行过一样,这个很好理解。

2、一致性(Consistency)

在事务【开始之前和结束以后】,数据库的完整性没有被破坏,数据库状态应该与业务规则保持一致。举一个例子:A向B转账,不可能A扣了钱,B却没有收到,也不可能A和B的总金额,在事务前后发生变化,产生数据不一致。其他的三个特性都在为他服务。

3、隔离性(Isolation)

数据库【允许多个并发事务同时对其数据进行读取和修改】,隔离性可以防止多个事务在并发修改共享数据时产生【数据不一致】的现象,这里要联想到我们学习过的多线程。

事务隔离级别分为不同等级,包括读未提交(Read uncommitted)、读提交(read committed)、可重复读(repeatable read)和串行化(Serializable),后续会详细讲。

4、持久性(Durability)

事务处理结束后,对数据的修改就是永久的,即便系统故障也不会丢失。

四、事务隔离级别

对于数据库的四大特性中的【隔离级别】是比较难理解的,我们在本小结中详细介绍。

在多个事务【并发操作】相同的表数据时,为了让多个事务都可以得到正确的结果,不会因为互相的交叉操作产生干扰,同时还要保证一定的执行效率,故而提出了不同的隔离级别。

隔离级别分类如下,在不同的隔离级别下可能产生不同的问题,如脏读、不可重复度、幻读等,我们会在后边的课程中一一讲解:

隔离级别脏读不可重复读幻读解决方案
Read uncommitted(读未提交)
Read committed(读已提交)×undo log
Repeatable read(可重复读)××MVCC版本控制+间隙锁(mysql的rr不存在幻读)
Serializable(串行化)×××

注意:传统意义上的rr级别是存在幻读问题的,但是mysql的rr级别不存在。

在mysql中查看和设置【事务的隔离级别】,语法如下:

-- 查看全局和当前事务的隔离级别
SELECT @@global.transaction_isolation, @@transaction_isolation_isolation;
show variables like 'transaction_isolation';
--5.7   tx_isolation
--8.0   transaction_isolation
-- 设置下一个事务的隔离级别
SET transaction isolation level read uncommitted;
SET transaction isolation level read committed;
set transaction isolation level repeatable read;
SET transaction isolation level serializable;
-- 设置当前会话的隔离级别
SET session transaction isolation level read uncommitted;
SET session transaction isolation level read committed;
set session transaction isolation level repeatable read;
SET session transaction isolation level serializable;
-- 设置全局事务的隔离级别
SET GLOBAL transaction isolation level read uncommitted;
SET GLOBAL transaction isolation level read committed;
set GLOBAL transaction isolation level repeatable read;
SET GLOBAL transaction isolation level serializable;
其中,SESSION 和 GLOBAL 关键字用来指定修改的事务隔离级别的范围:
SESSION:表示修改的事务隔离级别将应用于当前 session(当前 cmd 窗口)内的所有事务;
GLOBAL:表示修改的事务隔离级别将应用于所有 session(全局)中的所有事务,且当前已经存在的 session 不受影响;
如果省略 SESSION 和 GLOBAL,表示修改的事务隔离级别将应用于当前 session 内的下一个还未开始的事务。

1、读未提交(RU)

【ru隔离级别】说的简单一点就是,一个事务可以读取其他【未提交的事务】修改的数据,这种隔离级别最低,一般情况下,数据库隔离级别都要高于该级别,该隔离级别下,可能会存在脏读、不可重复度,幻读的问题。

**脏读:**指的是一个事务读到了其他事务未提交的数据,未提交意味着这些数据可能会回滚,读到的数据不一定准确。

A发工资了,B让A把工资打到他B的账号上,但是该事务并未提交,就让B去查看,B一看真的打了钱了,高高兴兴关了网页,此时A急中生智进行回滚,钱瞬间回来,一次蒙混了一个月工资。所以B看到的数据我们称之为“脏数据”。

第一步:使用两个窗口,开启两个事务,且将隔离级别设置为RU。

use ydlTrx;
SET transaction isolation level read uncommitted;

第二步:A给B转账10000元,但是不提交。

start transaction;
UPDATE user set balance = balance - 10000 where id = 1;
UPDATE user set balance = balance + 10000 where id = 2;

第三部步:A通知B进行查账,确实读取到了A未提交事务修改的数据。

start transaction;
select * from user where id = 2;
commit;

第四步:B查账结束,A来一个回马枪,对自己的事务进行回滚操作。

rollback;

第五步:B某天查账,突然发现,哎,怎么少了一万。

start transaction;
select * from user where id = 2;
commit;

2、读已提交(RC)

【RC读已提交】说的是当前事务只能读到别的事物已经提交的数据,该隔离级别可能会产生不可重复读和幻读。

【不可重复读】的官方解释是:【一个事务】(A事务)修改了【另一个未提交事务】(B事务)读取过的数据。那么B事务【再次读取】,会发现两次读取的数据不一致。也就是说在一个原子性的操作中一个事务两次读取相同的数据,却不一致,一行数据不能重复被读取。主要是【update】语句,会导致不可重复读。

A拿着工资卡去消费,系统读取到卡里确实有10200元,而此时B也正好在网上转账,把A工资卡的2000元转到另一账户,并在A之前提交了事务,当楠哥扣款时,系统检查到A的工资卡和上次读取的不一样了,A十分纳闷,明明卡里有钱,为何......

第一步:将事务的隔离级别设置为RC。

SET  transaction isolation level read committed;

第二步:A准备去消费了,检查存款显示有余额,贼高兴。

start transaction;
select * from user where id = 1;

第三步:B在A没有提交事务的时候,进行了一笔转账,并且提交了事务。

start transaction;
UPDATE user set balance = balance + 500 where id = 2;
UPDATE user set balance = balance - 500 where id = 1;
commit;

第四步:A再次查账,同一个事务里,发现钱少了。

select * from user where id = 1;

当隔离级别设置为Read committed 时,避免了脏读,但是可能会造成不可重复读。

大多数数据库的默认级别就是Read committed,比如Sql Server , Oracle。如何解决不可重复读这一问题,请看下一个隔离级别。

#3、可重复读(RR)

学习完不可重复读,理解【可重复读】就简单多了,他的意思是,同一个事务中发出同一个SELECT语句【两次或更多次】,那么产生的结果数据集总是相同的,在RR隔离级别中可能出现幻读。

**幻读:**一个事务按照某些条件进行查询,事务提交前,有另一个事务插入了满足条件的其他数据,再次使用相同条件查询,却发现多了一些数据,就像出现了幻觉一样。幻读主要针对针对delete和insert语句。

不可重复读强调的是两次读取的数据【内容不同】,幻读前调的是两次读取的【行数不同】。

B在银行部门工作,B时常通过银行内部系统查看A的账户A信息。有一天,B正在查询到楠哥账户信息时发现A只有一个账户,心想这家伙应该没有私房钱。此时A在另外一家分行又开了一个账户,准备存私房钱。同时,B在同一个事务中又一次查询,结果显示出的A账户居然多了一个,真实奇怪。

第一步:将数据库的隔离级别设置为RR。B

set  transaction isolation level repeatable read;

第二步:A开启事务。

start transaction;

第三步:B查账户。

start transaction;
select * from user where name = '楠哥';

第四步:A趁机开户,并提交事务。

insert into user values(3,'楠哥',10000);
commit;

第五步:B再查询并打印,应该发现A多了一个账户,但是没有,并没有出现幻读。

select * from user where name = '楠哥';

**注意:**在mysql中的RR隔离级别中,innodb使用mvcc+锁帮我们解决了绝大部分的幻读情况,

上边的例子稍微修改一下,我们就能看到幻读现象了。

第一步:将数据库的隔离级别设置为RR。

set  transaction isolation level repeatable read;

第二步:A开启事务。

start transaction;

第三步:B查账户。

start transaction;
select * from user where name = '楠哥';

第四步:A趁机开户,并提交事务。

insert into user values(3,'楠哥',10000);
commit;

第五步:B好心给所有的账户充值100,再查询并打印,结果发现A多了一个账户,出现幻读。

-- 先给所有的账户钱充值一百
UPDATE user set balance = balance + 100;
select * from user where name = '楠哥';

这里边的原理过于复杂,目前我们先卖个关子,等我们学完【锁和mvcc】以后回来在看,就能明白了。

4、串行化

  • 事务A和事务B,事务A在操作数据库时,事务B只能排队等待
  • 这种隔离级别很少使用,吞吐量太低,用户体验差
  • 这种级别可以避免“幻读”,每一次读取的都是数据库中真实存在数据,事务A与事务B串行,而不并发。
  • 第一步:修改数据库的隔离级别

    SET  transaction isolation level serializable;
    

    第一步:A执行查询操作

    begin;
    select * from user;
    

    第二步:B执行查询操作

    begin;
    select * from user;
    

    第三部:B想执行一个删除操作,发现事务被阻塞了,需要等待

    delete from user where id = 9;
    

    第四部:A这边一提交,V那边就能操作了

    commit;
    复制代码
    分类:
    后端
    标签: