登录
首页 >  数据库 >  MySQL

带你了解什么是mysql事务(附代码演示)

来源:SegmentFault

时间:2023-01-11 09:53:00 276浏览 收藏

知识点掌握了,还需要不断练习才能熟练运用。下面golang学习网给大家带来一个数据库开发实战,手把手教大家学习《带你了解什么是mysql事务(附代码演示)》,在实现功能的过程中也带大家重新温习相关知识点,温故而知新,回头看看说不定又有不一样的感悟!

事务

数据准备

-- 创建数据表
CREATE TABLE account (
    id INT PRIMARY KEY AUTO_INCREMENT,
    name VARCHAR(10),
    balance DOUBLE
);
-- 添加数据
INSERT INTO account (name, balance) VALUES ('张三', 1000), ('李四', 1000);

概述

在实际的开发过程中,一个业务操作如:转账,往往是要多次访问数据库才能完成的。转账是一个用户扣钱,另一个用户加钱。如果其中有一条 SQL 语句出现异常,这条 SQL 就可能执行失败。

事务执行是一个整体,所有的 SQL 语句都必须执行成功。如果其中有 1 条 SQL 语句出现异常,则所有的 SQL 语句都要回滚,整个业务执行失败。

代码演示

/* 模拟转账的操作 */

-- 张三账号-500
update account set balance = balance - 500 where name = '张三';
-- 李四账号+500
update account set balance = balance + 500 where name = '李四';

假设当张三账号上-500 元,服务器崩溃了。李四的账号并没有+500 元,数据就出现问题了。我们需要保证其中 一条 SQL 语句出现问题,整个转账就算失败。只有两条 SQL 都成功了转账才算成功。这个时候就需要用到事务.

分类

start transaction;
  • 提交事务

    commit;

  • 回滚事务

    rollback;

代码演示

-- 重置表数据
update account set balance = 1000;

-- 开启事务
start transaction;

-- 张三账户-500
update account set balance = balance - 500 where name = '张三';

-- 李四账号+500
update account set balance = balance + 500 where name = '李四';

-- 提交
commit;

-- 查看表数据,数据已发现改变
select * from account;

-- 重置表数据
update account set balance = 1000;

-- 开启事务
start transaction;

-- 张三账户-500
update account set balance = balance - 500 where name = '张三';

-- 李四账号+500
update account set balance = balance + 500 where name = '李四';

-- 回滚
rollback;

-- 查看表数据,数据未发现改变
select * from account;
如果事务中 SQL 语句没有问题,commit 提交事务,会对数据库数据的数据进行改变。 如果事务中 SQL 语句有问题,rollback 回滚事务,会回退到开启事务时的状态。

自动提交

MySQL 默认每一条 DML(增删改)语句都是一个单独的事务,每条语句都会自动开启一个事务,语句执行完毕 自动提交事务,MySQL 默认开始自动提交事务。

  • 查看是否了开启自动提交事务

    SELECT @@AUTOCOMMIT;-- @@表示全局变量,1 表示开启,0 表示关闭

  • 取消自动提交事务

    SET @@AUTOCOMMIT = 0;

  • 开启自动提交事务

    SET @@AUTOCOMMIT = 1;

代码演示

-- 重置表数据
update account set balance = 1000;

-- 查看是否开启了自动提交事务
SELECT @@AUTOCOMMIT;

-- 取消自动提交事务
SET @@AUTOCOMMIT = 0;

-- 张三账户-500
update account set balance = balance - 500 where name = '张三';

-- 李四账号+500
update account set balance = balance + 500 where name = '李四';

-- 回滚
rollback;

-- 查询表数据,数据未发生改变(自动提交事务已关闭,回滚,会导致前面的SQL失效)
select * from account;

-- 重置表数据
update account set balance = 1000;

-- 查看是否开启了自动提交事务
SELECT @@AUTOCOMMIT;

-- 张三账户-500
update account set balance = balance - 500 where name = '张三';

-- 李四账号+500
update account set balance = balance + 500 where name = '李四';

-- 提交
commit;

-- 查询表数据,数据已发生改变(自动提交事务已关闭,只有commit,才能提交数据)
select * from account;

-- 重置表数据
update account set balance = 1000;

-- 查看是否开启了自动提交事务
SELECT @@AUTOCOMMIT;

-- 开启自动提交事务
SET @@AUTOCOMMIT = 1;

-- 张三账户-500
update account set balance = balance - 500 where name = '张三';

-- 李四账号+500
update account set balance = balance + 500 where name = '李四';

-- 回滚
rollback;

-- 查询表数据,数据已发生改变(自动提交事务已开启,回滚无效)
select * from account;

事务原理

事务开启之后, 所有的操作都会临时保存到事务日志中, 事务日志只有在得到

SAVEPOINT 回滚点名字;

返回回滚点

ROLLBACK TO 回滚点名字;

代码演示

-- 重置表数据
update account set balance = 1000;

-- 开启事务
start transaction;

-- 李四账号-100
update account set balance = balance - 100 where name = '李四';

-- 设置回滚点
savepoint one_time;

-- 李四账号再-100
update account set balance = balance - 100 where name = '李四';

-- 返回回滚点
rollback to one_time;

-- 提交
commit;

-- 查询表数据,发现李四账号只减少了100
select * from account;

事务特征

  1. 原子性(Atomicity)

    每个事务都是一个整体,不可再拆分,事务中所有的 SQL 语句要么都执行成功, 要么都失败。

  2. 一致性(Consistency)

    事务在执行前数据库的状态与执行后数据库的状态保持一致。如:转账前2个人的总金额是2000,转账后2 个人总金额也是2000

  3. 隔离性(Isolation)

    事务与事务之间不应该相互影响,执行时保持隔离的状态。

  4. 持久性(Durability)

    一旦事务执行成功,对数据库的修改是持久的。就算关机,也是保存下来的。

事务隔离级别

事务在操作时的理想状态是:所有的事务之间保持隔离,互不影响。

但是因为并发操作,多个用户同时访问同一个数据。就可能引发并发访问的问题,如下:

  • 脏读

    一个事务读取到了另一个事务中尚未提交的数据。

  • 不可重复读

    一个事务中两次读取的数据内容不一致(update时引发的问题)

  • 幻读

    一个事务中两次读取的数据的数量不一致(insert或delete引发的问题)

下表为

SELECT @@TX_ISOLATION;
  • 设置事务隔离级别(需重登MySQL,才能看出隔离级别变化)

    SET GLOBAL TRANSACTION ISOLATION LEVEL 级别字符串;

代码演示

/* 脏读演示 */

-- 打开 A 窗口登录 MySQL,重置表格
update account set balance = 1000;

-- A 窗口设置全局的隔离级别为最低(read uncommitted——读未提交)
set global transaction isolation level read uncommitted;

-- 再打开 B 窗口登录MySQL,进入指定数据库,然后 A B 窗口都开启事务
start transaction;

-- A 窗口更新 2 个人的账户数据,不提交
update account set balance = balance - 500 where name = '张三';
update account set balance = balance + 500 where name = '李四';

-- B 窗口查询账户, 结果读取了 A 窗口尚未提交的事务,这就叫作脏读
select * from account;

-- A 窗口回滚
rollback;

-- B 窗口查询,表数据又变为原来的样子
select * from account;

脏读非常危险的,比如张三向李四购买商品,张三开启事务,向李四账号转入 500 块,然后打电话给李四说钱 已经转了。李四一查询钱到账了,发货给张三。张三收到货后回滚事务,李四的再查看钱没了。

解决脏读的办法:将全局的隔离级别进行提升为

/* 解决脏读演示 */

-- 打开 A 窗口登录 MySQL,重置表格
update account set balance = 1000;

-- A 窗口设置全局的隔离级别为read committed-读已提交
set global transaction isolation level read committed;

-- B 窗口重登MySQL,A B窗口都开启事务
start transaction;

-- A 窗口更新2人账号,不提交
update account set balance = balance - 500 where name = '张三';
update account set balance = balance + 500 where name = '李四';

-- B 窗口查询账户,并没有读取到 A 窗口尚未提交的数据(脏读已被解决)
select * from account;

-- A 窗口提交事务
commit;

-- B 窗口查看账户,才读取到 A 窗口已经提交的数据(脏读已被解决)
select * from account;
隔离级别为
/* 不可重复读演示 */

-- 打开 A 窗口登录 MySQL,重置表格
update account set balance = 1000;

-- A 窗口设置全局的隔离级别为read committed-读已提交
set global transaction isolation level read committed;

-- B 窗口重登MySQL,并开启事务
start transaction;

-- B 窗口查询表数据
select * from account;

-- A 窗口也开启事务
start transaction;

-- A 窗口更新2人账号,并提交
update account set balance = balance - 500 where name = '张三';
update account set balance = balance + 500 where name = '李四';
commit;

-- B 窗口查询,发现查询到的数据与前一次不同
select * from account;

两次查询输出的结果不同,这也是一种危险情况!我们可以考虑这样一种情况,比如银行程序需要将查询结果分别输出到电脑屏幕和发短信给客户,结果在一个事务中针对不同的输出目的地进行的两次查询不一致,导致文件和屏幕中的结果不一致,这样就可能会导致账务错误。

解决不可重复读的办法:将全局的隔离级别提升为

/* 解决不可重复读演示 */

-- 打开 A 窗口登录 MySQL,重置表格
update account set balance = 1000;

-- A 窗口设置全局的隔离级别为repeatable read-可重复读
set global transaction isolation level repeatable read;

-- B 窗口重登MySQL,并开启事务
start transaction;

-- B 窗口都查询表数据
select * from account;

-- A 窗口也开启事务

-- A 窗口更新2人账号,并提交
update account set balance = balance - 500 where name = '张三';
update account set balance = balance + 500 where name = '李四';
commit;

-- B 窗口查询,发现查询到的数据与前一次一致
select * from account;

隔离级别为

/* 幻读演示 */

-- 打开 A 窗口登录 MySQL,重置表格
update account set balance = 1000;

-- A 窗口设置全局的隔离级别为最高serializable-串行化
set global transaction isolation level serializable;

-- A 窗口重登MySQL, 并开启事务
start transaction;

-- A 窗口查询表记录总数
select count(*) from account;

-- B 窗口登录 MySQL,并开启事务
start transaction;

-- B 窗口添加一条记录(此时回车确认时,会发现操作没有继续进行,光标仍在闪烁)
insert into account (name, balance) values ('Jason', 500);

-- A 窗口中执行commit提交事务后,B 窗口中的insert语句就立即执行完毕
commit;

-- A 窗口中查询表记录总数,发现总数未改变,B 窗口的insert没起作用
select count(*) from account;

-- B 窗口中执行commit提交事务
commit;

-- A 窗口查询表记录总数,发现总数已经改变,B 窗口的insert起作用了
select count(*) from account;
使用
  • 默认情况下autocommit(自动提交)=1时:
    执行sql修改语句(insert into,update…set,delete from)后,是立即生效的;

  • 若手动修改autocommit(自动提交)=0,即手动提交时:
    执行sql修改语句(insert into,update,delete from)后,不会生效;

    在当前终端查看到的数据是存在缓存里的数据;

    重新打开一个终端查看,会发现数据没有生效;

    需要输入commit命令,提交数据到数据库,这样才会生效;

  • 不管是autocommit=1或=0时;开启事务使用了begin命令,执行了sql修改语句后,都必须要使用commit命令提交数据
    给数据库,不然不会生效;
    如果使用了begin,没有使用commit,而是用begin另外开启了一个事务,之前的修改会被提交,隐式提交;
    即:使用begin开启事务->后面必须使用commit/begin(其他命令不行),sql修改语句才能生效;

  • 以上要使用到commit的,在commit之前,使用命令rollback,都可以使数据回滚

  • 事务开启到结束:begin+commit/rollback

  • 对于set autocommit=0的设置的有效范围():

    • 只在当前操作的mysql数据库有效,临时有效

    • 退出当前mysql数据库(未退出终端),失效

    • 退出当前终端,再重新使用mysql,失效

  • 事务的开启,与是否选择了特定的数据库无关。即:在执行

    use database_name
    命令前,就可以通过begin/start transaction来开启事务,然后在选择你需要使用的数据库,进行数据操作。
  • begin
    start transaction
    等价,都是显式开启一个事务;
    commit
    commit work
    等价,都是对事物进行提交;
    rollback
    rollback work
    等价,都是对事物进行回滚;
  • 文中关于mysql的知识介绍,希望对你的学习有所帮助!若是受益匪浅,那就动动鼠标收藏这篇《带你了解什么是mysql事务(附代码演示)》文章吧,也可关注golang学习网公众号了解相关技术文章。

    声明:本文转载于:SegmentFault 如有侵犯,请联系study_golang@163.com删除
    相关阅读
    更多>
    最新阅读
    更多>
    课程推荐
    更多>
    评论列表