• MySQL基础——事务


    目录

    事务

    事务操作

    手动提交事务

    自动提交事务中手动提交

    并发事务问题

    事务隔离级别

    Read uncommitted

    Read committed

    Repeatable Read

    Serializable


    上篇文章学习了MySQL基础——多表查询,这篇文章学习MySQL基础——事务。

    事务

    事务是一组操作的集合,它是一个不可分割的工作单位,事务会把所有的操作作为一个整体一起向系统提交或撤销操作请求,所以这些操作要么同时成功,要么同时失败。

    事务的四大特性:

    • 原子性(Atomicity):事务是不可分割的最小操作单元,要么全部成功,要么全部失败;

    • 一致性(Consistency):事务完成时,必须使所有的数据都保持一致状态;

    • 隔离性(Isolation):数据库系统提供的隔离机制,保证事务在不受外部并发操作影响的独立环境下运行;

    • 持久性(Durability):事务一旦提交或回滚,它对数据库中的数据的改变是永久的。

    MySQL的事务默认是自动提交的,当执行一条DML语句,MySQL会隐式的提交事务。

    默认自动提交事务可能会导致数据出错,例如,有如下数据表:

    接下来我们模拟收付款,同时执行如下代码:

    1. update account set money=money - 1000 where name='买家'; # 买家付款
    2. update account set dasa money=money + 1000 where name='商家'; # 商家收款

    执行结果如下图所示:

    由于第二条MySQL语句是错误的,MySQL自动提交事务错误,导致商家没有收到款。

    事务操作

    既然自动提交事务可能会导致数据错误,那么我们把自动提交事务改为手动提交事务。

    手动提交事务

    在修改事务提交方式之前,执行如下代码查看事务的提交方式:

    select @@autocommit;

    如下图所示:

    其中1为自动提交事务,0为手动提交事务。

    查看事务提交方式后,执行如下代码把事务提交方式改为手动提交:

    set @@autocommit=0;

    如下图所示:

    把事务提交方式改为手动提交后,需要执行如下代码提交事务和回滚事务:

    1. commit; #提交事务
    2. rollback; #回滚事务

    接下来我们还是模拟收付款来演示手动提交事务,同时执行如下代码:

    1. update account set money=money - 1000 where name='买家'; # 买家付款
    2. update account set money=money + 1000 where name='商家'; # 商家收款

    发现数据没有发生改变,如下图所示:

    这是因为还没提交事务,此时我们只需要执行如下代码数据会发生改变:

    commit;

    如下图所示:

    接下来我们同时执行如下代码演示事务执行出错的情况:

    1. update account set money=money - 1000 where name='买家';
    2. update account set dasa money=money + 1000 where name='商家';
    3. rollback; # 回滚事务

    如下图所示:

    通过回滚事务把正确和错误的事务修改值恢复到修改前的值。

    自动提交事务中手动提交

    当我们不想把自动提交事务改为手动提交事务,而防止自动提交事务出错时,可以在自动提交事务中,手动开始事务,代码如下:

    start transaction 或 begin;

    示例代码如下:

    1. set @@autocommit=1; # 把事务提交方式改为自动提交
    2. start transaction; # 开始事务
    3. update account set money=money - 1000 where name='买家'; # 买家付款
    4. update account set money=money + 1000 where name='商家'; # 商家收款

    如下图所示:

    发现数据没有发生改变,这时只需要执行如下代码数据就会发生改变:

    commit;

    并发事务问题

    当有两个或两个以上的事务同时执行、提交事务时,可能会引发并发事务问题,并发事务常见的问题有:

    • 脏读:一个事务读取了另一个事务还没提交的数据;

    • 不可重复读:一个事务先后读取同一条记录,但两次读取的数据不同,这种称为不可重复读;

    • 幻读:一个事务按照条件查询数据时,没有对应的数据行,但在插入数据时,这行数据已经存在了,好像出现了“幻影”。

    事务隔离级别

    为了解决事务并发问题,我们设置事务的隔离级别,不同的隔离级别可以解决不同的事务并发问题,如下表:

    隔离级别脏读不可重复读幻读
    Read uncommitted不可解决不可解决不可解决
    Read committed可解决不可解决不可解决
    Repeatable Read(默认)可解决可解决不可解决
    Serializable可解决可解决可解决

    在设置事务的隔离级别之前,我们首先执行如下代码看看事务的隔离级别:

    select @@transaction_isolation;

    设置事务隔离级别语法格式如下:

    set [session | global] transaction isolation level {read uncommitted | read committed | repeatable read | serializable}

    其中session是当前会话窗口有效,global是对当前客户端的所有会话窗口有效。

    接下来我们来演示隔离级别解决事务并发问题。

    Read uncommitted

    首先依次执行如下代码查看和设置隔离级别:

    1. select @@transaction_isolation;
    2. set session transaction isolation level read uncommitted;

    如下图所示:

    接下来通过两个cmd窗口通过start transaction手动开启两个事务,并依次执行如下代码:

    1. # 窗口1
    2. use mytest; # 选择mytest数据库
    3. set session transaction isolation level read uncommitted; # 设置事务级别为read uncommitted
    4. start transaction; # 开始事务
    5. select * from account; # 查看数据
    6. #窗口2
    7. use mytest; # 选择mytest数据库
    8. start transaction; # 开始事务
    9. update account set money=money - 1000 where name='买家'; #更新数据
    10. # 窗口1
    11. select * from account; # 查看数据

    如下图所示:

    在窗口2更新数据事务后,没有提交事务,但在窗口1查看数据发现数据已经更新了,这就属于脏读了。

    Read committed

    接下来我们通过设置Read committed事务隔离级别解决脏读问题,同样打开两个cmd窗口通过start transaction手动开启两个事务,并依次执行如下代码:

    1. # 窗口1
    2. use mytest; # 选择mytest数据库
    3. set session transaction isolation level read committed; # 设置事务级别为read committed
    4. start transaction; # 开始事务
    5. select * from account; # 查看数据
    6. #窗口2
    7. use mytest; # 选择mytest数据库
    8. start transaction; # 开始事务
    9. update account set money=money - 1000 where name='买家'; #更新数据
    10. # 窗口1
    11. select * from account; # 查看数据
    12. #窗口2
    13. commit;

    如下图所示:

    这样就成功解决了脏读问题。虽然Read committed隔离级别可以解决脏读问题,解决不了不可重复读和幻读。

    接下来演示不可重复读,示例代码如下:

    1. # 窗口1
    2. use mytest; # 选择mytest数据库
    3. set session transaction isolation level read committed; # 设置事务级别为read committed
    4. start transaction; # 开始事务
    5. select * from account; # 查看数据
    6. #窗口2
    7. use mytest; # 选择mytest数据库
    8. start transaction; # 开始事务
    9. update account set money=money - 1000 where name='买家'; #更新数据
    10. # 窗口1
    11. select * from account; # 查看数据
    12. #窗口2
    13. commit; # 提交事务
    14. #窗口1
    15. select * from account; # 查看数据

    如下图所示:

    在窗口1中一次事务执行了3次查询数据表操作,前面两次结果相同,第三次查询的结果不同,这就叫不可重复读。

    Repeatable Read

    为了解决不可重复读,把事务隔离级别改为Repeatable Read,示例代码如下 :

    1. # 窗口1
    2. use mytest; # 选择mytest数据库
    3. set session transaction isolation level repeatable read; # 设置事务级别为repeatable read
    4. start transaction; # 开始事务
    5. select * from account; # 查看数据
    6. #窗口2
    7. use mytest; # 选择mytest数据库
    8. start transaction; # 开始事务
    9. update account set money=money + 1000 where name='买家'; #更新数据
    10. commit;
    11. # 窗口1
    12. select * from account; # 查看数据
    13. # 窗口1
    14. commit; # 提交事务
    15. select * from account; # 查看数据

    如下图所示:

    在窗口1开启一个事务并查询数据表的数据后,在窗口2中开启事务并更新提交事务,再返回窗口1中查询数据表发现查询结果和之前查询一样,这就叫可重复读,在窗口1中提交第一个事务后,再次查询数据表数据发现数据发生了改变。这就解决了并发问题的不可重复读。

    Repeatable Read可以解决脏读和不可重复读,但解决不了幻读。

    接下来我们演示幻读,示例代码如下:

    1. # 窗口1
    2. use mytest;
    3. start transaction;
    4. select * from account where id='3';
    5. # 窗口2
    6. use mytest;
    7. start transaction;
    8. insert into account(id,name,money) values(3,'中间商',2000);
    9. commit;
    10. # 窗口1
    11. insert into account(id,name,money) values(3,'大中间商',2000);
    12. select * from account where id='3';

    如下图所示:

    在窗口1中开启事务并查询id为3的数据,发现数据表中没有id为3的数据,在窗口2中,开启事务并添加id为3的数据并提交事务后,在窗口1中添加id为3的数据,发现已经有id为3的数据了,但查询id为3的数据时,发现没有查到,这就是幻读。

    Serializable

    为了解决幻读并发事务问题,我们把事务隔离级别改为Serializable,示例代码如下:

    1. # 窗口1
    2. use mytest; # 选择mytest数据库
    3. set session transaction isolation level Serializable; # 设置事务级别为Serializable
    4. start transaction; # 开始事务
    5. select * from account where id='5'; # 查看数据
    6. # 窗口2
    7. use mytest;
    8. start transaction;
    9. insert into account(id,name,money) values(5,'厂商',2000);

    如下图所示:

    在窗口1查询id为5的数据,因为没有id为5的数据,所以没有查询到,在窗口2中插入id为5的数据,发现一直没有插入成功,这是因为在窗口1中已经开启了一个事务,需要窗口1开启的事务结束后,才能执行窗口2的事务,这样就解决了幻读问题。

    好了,MySQL基础——事务就学到这里了,下篇文章学习MySQL进阶——存储引擎。

    公众号:Python侦探

  • 相关阅读:
    [Common c/c++] 生产者消费者模型 using mutex/cv/semaphore
    CS5266+MA8621做TYPEC转HDMI+PD+U3+2U+SD/TF七合一拓展坞方案设计|CS5266多口拓展坞PCB+原理图参考
    解决老版本Oracle VirtualBox 此应用无法在此设备上运行问题
    2022大厂Java面试题库|附答案
    C++官网 Tutorials C++ Language Basics of C++:Variables and types
    Unity入门08——核心系统02
    网络知识:内网、外网、宽带、带宽、流量、网速之间的联系?
    为什么 JVM 叫做基于栈的 RISC 虚拟机?
    2023-10-19 node.js-将异步处理修改为同步-使用Promise和async-记录
    SpringMVC(4)——数据封装与异常处理
  • 原文地址:https://blog.csdn.net/weixin_52122271/article/details/125499956