• 一文了解MySQL事务


    一文了解MySQL事务

    🏠个人主页:不会写代码的满满
    🧑个人简介:大家好,我是满满,一个想要与大家共同进步的男人😉😉
    目前状况🎉:开学即将大三,目标就是半年内找到一份实习工作👏👏
    💕欢迎大家:这里是CSDN,我总结知识的地方,欢迎来到我的博客,我亲爱的大佬😘

    正文开始 ----------

    1.事务概述😪

    事务处理 (事务操作)

    保证所有的事务都为同一个工作单元来执行,即使出现了任何故障,也不会改变这样的执行方式。当在一个事务中执行多个操作时,要么所有的事务都被提交(commit),那么这些修改就永久地保存下来;要么数据库管理系统将放弃所作的所有修改,整个事务回滚(rollback)到最初状态。

    ​ 一个数据库事务通常包含对数据库进行读或写的一个操作序列。它的存在包含有以下两个目的 :

    1、为数据库操作提供了一个从失败中恢复到正常状态的方法,同时提供了数据库即使在异常状态下仍能保持一致性的方法。
    2、当多个应用程序在并发访问数据库时,可以在这些应用程序之间提供一个隔离方法,以防止彼此的操作互相干扰。

     例如转账操作:A账户要转账给B账户,那么A账户上减少的钱数和B账户上增加的钱数必须一致,也就是说A账户的转出操作和B账户的 
     转入操作要么全部执行,要么全不执行;如果其中一个操作出现异常而没有执行的话,就会导致账户A和账户B的转入转出金额不一   
     致的情况,为而事实上这种情况是不允许发生的,所以为了防止这种情况的发生,需要使用事务处理。
    
    • 1
    • 2
    • 3

    当一个事务被提交给了DBMS(数据库管理系统),则DBMS需要确保该事务中的所有操作都成功完成且其结果被永久保存在数据库中,如果事务中有的操作没有成功完成,则事务中的所有操作都需要被回滚,回到事务执行前的状态(要么全执行,要么全都不执行); 同时,该事务对数据库或者其他事务的执行无影响,所有的事务都好像在独立的运行。

    当一个数据库事务在执行的过程中,有可能会遇上事务操作失败、数据库系统/操作系统失败,甚至是存储介质失败等情况。这便需要DBMS对一个执行失败的事务执行恢复操作,将其数据库状态恢复到一致状态(数据的一致性得到保证的状态)。为了实现将数据库状态恢复到一致状态的功能,DBMS通常需要维护事务日志以追踪事务中所有影响数据库数据的操作。

    2.数据库事务的特性👀

    以银行转账举例,说明如何通过数据库事务保证数据的准确性和完整性。从帐号A到帐号B需要6个操作:

    img

    用一个常用的“A账户向B账号汇钱”的例子来说明如何通过数据库事务保证数据的准确性和完整性。熟悉关系型数据库事务的都知道从帐号A到帐号B需要6个操作:

    1、从A账号中把余额读出来(500)。
    2、对A账号做减法操作(500-100)。
    3、把结果写回A账号中(400)。
    4、从B账号中把余额读出来(500)。
    5、对B账号做加法操作(500+100)。
    6、把结果写回B账号中(600)。

    在这个过程中可能会出现以下问题:

    1. 转账操作的第一步执行成功,A账户上的钱减少了100元,但是第二步执行失败或者未执行便发生系统崩溃,导致B账户并没有相应增加100元。
    2. 转账操作刚完成就发生系统崩溃,系统重启恢复时丢失了崩溃前的转账记录。
    3. 同时又另一个用户转账给B账户,由于同时对B账户进行操作,导致B账户金额出现异常。

    事务的ACID属性:

    • 原子性(Atomicity)

      保证上述例子中 1-6所有过程,要么都执行,要么都不执行、一旦在执行某个过程发生问题,就需要执行回滚操作,假如执行到第五步的时候,B账户突然不可用(比如被注销),那么之前的所有操作都应该回滚到执行事务之前的状态。

      原子性是指事务是一个不可分割的工作单位,事务中的操作要么都发生,要么都不发生。

    • 一致性(Consistency)

      在转账之前,A账户和B账户总共有500元 + 500元 = 1000元,在转账之后,A和B的账户中现在有400 + 600=1000元。也就是,说数据的状态在执行该事务操作之后从一个状态改变到了另外一个状态。同时一致性还能保证账户余额不会变成负数等。

      事务必须使数据库从一个一致性状态变换到另外一个一致性状态。

    • 隔离性(Isolation)

      在A账户向B账户转账的过程中,只要事务没有提交,那么,A账户和B账户的余额就不会有变化。如果在A账户向B账户转账的过程中,C账户也向B账户进行转账,那么最后,B账户的钱是A账户转进来的余额 + C账户转进来的余额 + B账户余额。

      事务的隔离性是指一个事务的执行不能被其他事务干扰,即一个事务内部的操作及使用的数据对并发的其他事务是隔离的,并发执行的各个事务之间不能互相干扰。

    • 持久性(Durability)

      一旦转账成功(事务提交),两个账户的里面的钱就会真的发生变化(会把数据写入数据库做持久化保存)!

      持久性是指一个事务一旦被提交,它对数据库中数据的改变就是永久性的,接下来的其他操作和数据库故障不应该对其有任何影响。

      关于事务的定义有几点需要注意:

      1. 数据库事务可以包含一个或多个数据库操作,但这些操作构成一个逻辑上的整体。
      2. 构成逻辑整体的这些数据库操作,要么全部执行成功,要么全部不执行。
      3. 构成事务的所有操作,要么全都对数据库产生影响,要么全都不产生影响,即不管事务是否执行成功,数据库总能保持一致性状态。
      4. 以上即使在数据库出现故障以及并发事务存在的情况下依然成立。

    3.如何实现数据库事务☕

    在事务的ACID特性中,C即一致性是事务的根本追求,而对数据一致性的破坏主要来自两个方面

    • 事务的并发执行
    • 事务故障或系统故障

    数据库系统是通过并发控制技术和日志恢复技术来避免这种情况发生的。

    并发控制技术保证了事务的隔离性,使数据库的一致性状态不会因为并发执行的操作被破坏。
    日志恢复技术保证了事务的原子性,使一致性状态不会因事务或系统故障被破坏。同时使已提交的对数据库的修改不会因系统崩溃而丢失,保证了事务的持久性。

    img

    4. MySQL中如何开启、提交 、回滚事务🧅

    MYSQL 事务处理主要有两种方法:

    1、用 begin/start transaction, ROLLBACK, COMMIT来实现

    • begin/start transaction 开始一个事务

    • rollback 事务回滚

    • commit 事务确认

      begin;
      update student set sname='王军' where id=;
      rollback;
      commit;
      
      • 1
      • 2
      • 3
      • 4

    2、直接用 SET 来改变 MySQL 的自动提交模式:

    • SET AUTOCOMMIT=0 禁止自动提交

    • SET AUTOCOMMIT=1 开启自动提交

      show session variables like 'autocommit';  --查看自动提交状态
      set session autocommit = 0;  --关闭自动提交
      set session autocommit = off;  --关闭自动提交
      update student set sname='飞飞';
      rollback;
      commit;
      
      • 1
      • 2
      • 3
      • 4
      • 5
      • 6

    5. 事务的隔离级别💧

    5. 1 事务并发问题

    对于同时运行的多个事务, 当这些事务访问数据库中相同的数据时, 如果没有采取必要的隔离机制, 就会导致各种并发问题:

    • 脏读 : 对于两个事务 T1 , T2 , T1已经读到了事务T2更新但是还未提交的字段。之后, 若 T2 回滚, T1读取的内容就是临时且无效的。

    1599461151424

    • 不可重复读 : 对于两个事务T1 , T2 , T1读取一个字段之后,T2对该事务进行了 更新/修改操作。之后T1再次读取该字段的时候,值就不一样了。

      1599461170319

    • 幻读 : 对于两个事物 T1 , T2 ,T1从表中读取一个字段,此时T2进行插入/ 删除 了一些新的行, 之后, 如果 T1 再次读取同一个表, 就会多/少几行。

    1599461184631

    不可重复度和幻读区别:

    不可重复读的重点是修改,幻读的重点在于新增或者删除

    解决不可重复读的问题只需锁住满足条件的行,解决幻读需要锁表

    例1(同样的条件, 你读取过的数据, 再次读取出来发现值不一样了 ):事务1中的A先生读取自己的工资为 1000的操作还没完成,事务2中的B先生就修改了A的工资为2000,导致A再读自己的工资时工资变为 2000;这就是不可重复读。

    例2(同样的条件, 第1次和第2次读出来的记录数不一样 ):假某工资单表中工资大于3000的有4人,事务1读取了所有工资大于3000的人,共查到4条记录,这时事务2 又插入了一条工资大于3000的记录,事务1再次读取时查到的记录就变为了5条,这样就导致了幻读

    5.2事务隔离级别

    2、事务隔离级别

    数据库事务的隔离性:数据库系统必须具有隔离并发运行各个事务的能力, 使它们不会相互影响, 避免各种并发问题。 ** 一个事务与其他事务隔离的程度称为隔离级别。**数据库规定了多种事务隔离级别, 不同隔离级别对应不同的干扰程度, 隔离级别越高, 数据一致性就越好, 但并发性越弱。

    数据库提供的 4 种事务隔离级别:

    隔离级别描述
    read-uncommitted允许A事务读取其他事务未提交和已提交的数据。会出现脏读、不可重复读、幻读问题
    read-committed只允许A事务读取其他事务已提交的数据。可以避免脏读,但仍然会出现 不可重复读、幻读 问题
    repeatable-read确保事务可以多次从一个字段中读取相同的值。在这个事务持续期间,禁止其他事务对这个字段进行更新。可以避免脏读和不可重复读。但是幻读问题仍然存在。
    serializable确保事务可以从一个表中读取相同的行,相同的记录。在这个事务持续期间,禁止其他事务对该表执行插入、更新、删除操作。所有并发问题都可以避免,但性能十分低下。
    • Oracle 支持的 2 种事务隔离级别:READ-COMMITED, SERIALIZABLE。 Oracle 默认的事务隔离级别为: READ COMMITED 。
    • Mysql 支持 4 种事务隔离级别。 Mysql 默认的事务隔离级别为: REPEATABLE-READ。在mysql中REPEATABLE READ的隔离级别也可以避免幻读了。

    5.3 查看事务隔离级别

    每启动一个 mysql 程序, 就会获得一个单独的数据库连接。每个数据库连接都有一个变量 @@tx_isolation, 表示当前连接的事务隔离级别。mysql服务也有一个全局变量@@global.tx_isolation,表示所有连接的默认事务隔离级别。

    • 查看当前mysql连接的隔离级别:
    select @@tx_isolation;
    
    • 1
    • 查看全局的隔离级别:
    select @@global.tx_isolation;
    
    • 1
    • 设置当前 mysql连接的隔离级别:
    set tx_isolation ='repeatable-read';
    
    • 1
    • 设置数据库系统的全局的隔离级别:
    set global tx_isolation ='read-committed';
    
    • 1

    注意:这里的隔离级别中间是减号,不是下划线。

    实验一下,首先做数据准备

    select @@transaction_isolation;  --查看当前隔离级别
    
    • 1

    MySQL默认的事务隔离等级,可重复读(repeatable reads)。

    --首先我们创建一个表作为测试表
    drop table if exists test_trac;
    create table if not exists test_trac (
        t_id varchar(16) primary key,
        t_name varchar(32)
    );
    
    --我们开两个会话,一个用客户端(会话A),一个用命令行(会话B),会话A开启事务
    insert into test_trac values('aaa', 'bbb');  --在会话A上执行
    --在会话B上读取,会发现无法读取未提交的数据,会话A执行commit后,会话B可以正常读取。
    
    --将会话B的事务隔离级别设为“读未提交”
    sessionB: set session transaction isolation level read uncommitted;
    sessionB: start transaction;
    sessionB: select * from test_trac;
    --显示会话A插入之前的值
    sessionA: start transaction;
    sessionA: insert into test_trac values('ccc0', 'aaa');
    --在sessionA未提交的情况下,会话B可读取:会话A的未提交的insert(脏读);查询过后被修改(不可重读);查询过被新增(幻读)。
    
    
    --将会话B的事务隔离级别设为“读已提交”
    sessionB: set session transaction isolation level read committed;
    sessionB: start transaction;  --开启事务
    sessionB: select * from test_trac where t_name='aaa';
    --会话A也开启事务
    sessionA: start transaction;
    sessionA: insert into test_trac values('ccc1', 'aaa');
    --在sessionA未提交的情况下,sessionB读不到这条记录,除非sessionA已提交
    sessionA: commit;
    sessionB: select * from test_trac where t_name='aaa';
    --关键是会话B的自己的事务未办结提交过程中,读取到了会话A提交的数据。
    
    --现在把会话B的隔离基本设为“可重复读”
    sessionB: set session transaction isolation level repeatable read;
    sessionB: start transaction;  --开启事务
    sessionB: select * from test_trac where t_name='bbb';
    --可重复读的例子
    sessionA: start transaction;  --开启事务
    sessionA: insert into test_trac values('ddd0', 'bbb');
    sessionA: commit;
    --虽然会话A已提交事务,会话B查询,得不到新的值,除非会话B也提交。
    sessionB: commit;
    sessionB: select * from test_trac where t_name='bbb';
    --幻读的例子
    sessionA: start transaction; 
    sessionB: start transaction;
    sessionA: select * from test_trac where t_name='bbb';
    sessionB: select * from test_trac where t_name='bbb';
    --会话A和B开启事务后,读取的结果集。会话A发现可以插入某个值,插入并提交。会话B也要插入同样的值,结果发现主键冲突,也就是数据库库在查询之后,新增了对应的数据。但是会话B的事务中不知道这个改变。
    sessionA: insert into test_trac values('ddd1', 'bbb');
    sessionA: commit;
    sessionB: insert into test_trac values('ddd1', 'bbb');
    sessionB: ERROR 1062 (23000): Duplicate entry 'ddd1' for key 'PRIMARY'
    --如果会话A插入一个主键值,未提交。会话B插入同样的主键值,在会话A未提交的情况下被阻塞,指导会话A提交或回滚。
    
    --现在把会话B的隔离基本设为“串行化”
    sessionB: set session transaction isolation level serializable;
    sessionB: start transaction;  --开启事务
    --在此隔离等级下,一旦有某个事务进行了insert、update、delete操作更新类数据,所有其他会话的select操作会被阻塞,除非更新的会话被提交。
    --如果两个会话都更新过了,第一个会话select被阻塞,第二个会话进行select直接报错
    ERROR 1213 (40001): Deadlock found when trying to get lock; try restarting transaction
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17
    • 18
    • 19
    • 20
    • 21
    • 22
    • 23
    • 24
    • 25
    • 26
    • 27
    • 28
    • 29
    • 30
    • 31
    • 32
    • 33
    • 34
    • 35
    • 36
    • 37
    • 38
    • 39
    • 40
    • 41
    • 42
    • 43
    • 44
    • 45
    • 46
    • 47
    • 48
    • 49
    • 50
    • 51
    • 52
    • 53
    • 54
    • 55
    • 56
    • 57
    • 58
    • 59
    • 60
    • 61
    • 62

    6. 行锁与表锁🔍

    ​ 所有的事务实现,处理并发冲突,都需要依赖锁来实现。

    ​ 一般可以分为两类,一个是悲观锁,一个是乐观锁,悲观锁一般就是我们通常说的数据库锁机制,乐观锁一般是指用户自己实现的一种锁机制,比如hibernate实现的乐观锁甚至编程语言也有乐观锁的思想的应用。

    悲观锁:顾名思义,就是很悲观,它对于数据被外界修改持保守态度,认为数据随时会修改,所以整个数据处理中需要将数据加锁。悲观锁一般都是依靠关系数据库提供的锁机制,事实上关系数据库中的行锁,表锁不论是读写锁都是悲观锁。

    1.表级锁定(table-level)

    表级别的锁定是MySQL各存储引擎中最大颗粒度的锁定机制。该锁定机制最大的特点是实现逻辑非常简单。所以获取锁和释放锁的速度很快。由于表级锁一次会将整个表锁定,所以可以很好的避免困扰我们的死锁问题。
    当然,锁定颗粒度大所带来最大的负面影响就是出现锁定资源争用的概率也会最高,致使并大度大打折扣。
    使用表级锁定的主要是MyISAM,MEMORY,CSV等一些非事务性存储引擎。

    2.行级锁定(row-level)

    行级锁定最大的特点就是锁定对象的颗粒度很小,也是目前各大数据库管理软件所实现的锁定颗粒度最小的。由于锁定颗粒度很小,所以发生锁定资源争用的概率也最小能够给予应用程序尽可能大的并发处理能力而提高一些需要高并发应用系统的整体性能。
    虽然能够在并发处理能力上面有较大的优势,但是行级锁定也因此带来了不少弊端。由于锁定资源的颗粒度很小,所以每次获取锁和释放锁需要做的事情也更多,带来的消耗自然也就更大了。此外,行级锁定也最容易发生死锁。
    使用行级锁定的主要是InnoDB存储引擎。

    表级锁:开销小,加锁快;较难出现死锁;锁定粒度大,发生锁冲突的概率最高,并发度最低;
    行级锁:开销大,加锁慢;容易出现死锁;锁定粒度最小,发生锁冲突的概率最低,并发度也最高;

  • 相关阅读:
    Jvm基础面试题
    【C++】——类和对象(中)
    GUI编程学习
    Python 小贴士(3)
    Redis(五)
    芯驰D9评测(3)--建立开发环境
    薪资25k,从华为外包测试“跳”入字节,说说我转行做测试的这5年...
    HarmonyOS-9(stage模式)
    Linux 下 C语言版本的线程池
    linux脚本执行docker容器命令
  • 原文地址:https://blog.csdn.net/qq_58608526/article/details/126317453