zoukankan      html  css  js  c++  java
  • MySQL中的事务

    秋招告一段落,浪了好久也该总结总结了,虽然面试这一类别下绝大部分都是参考的别的博客,但自己再整理一遍总感觉印象更深吧。对于参考的原文都有表明原文链接

    -----------------------------------------------------------------------------------------------

    转自:https://www.cnblogs.com/hebao0514/category/719525.html

    一、事务的四大特性(ACID)

    1. 原子性(atomicity):一个事务必须视为一个不可分割的最小工作单元,整个事务中的所有操作要么全部提交成功,要么全部失败回滚,对于一个事务来说,不可能只执行其中的一部分操作,这就是事务的原子性。

    2. 一致性(consistency):数据库总是从一个一致性的状态转换到另一个一致性的状态。
    拿转账来说,假设用户A和用户B两者的钱加起来一共是5000,那么不管A和B之间如何转账,转几次账,事务结束后两个用户的钱相加起来应该还得是5000,这就是事务的一致性。

    3. 隔离性(isolation):一个事务所做的修改在最终提交以前,对其他事务是不可见的。
    比如操作同一张表时,数据库为每一个用户开启的事务,不能被其他事务的操作所干扰,多个并发事务之间要相互隔离。

    4. 持久性(durability):一旦事务提交,则其所做的修改就会永久保存到数据库中。此时即使系统崩溃,修改的数据也不会丢失。

    二、事务的简单使用
    1. 在使用数据库时候需要使用事务,必须先开启事务,开启事务的语句具体如下:

    start transaction

    2. 事务开启之后就可以执行SQL语句
    3. SQL语句执行成功之后,需要提交事务,提交事务的语句如下:

    commit

    note:
    在MySQL中直接书写的SQL语句都是自动提交的,而事务中的操作语句需要使用commit语句手动提交,只有事务提交后其中的操作才会生效。
    如果不想提交事务,我们还可以使用相关语句取消事务(也称回滚),具体语句如下:

    rollback

    需要注意的是,rollback语句只能针对未提交的事务执行的回滚操作,已经提交的事务是不能回滚的。

    例子:通过一个转账的案例演示如何使用事务。
    1. 创建表

    create table account(
        id int primary key auto_increment,
        name varchar(40),
        money float
    );
    insert into account(name,money) values('a',1000);
    insert into account(name,money) values('b',1000);

    2. 使用事务来实现转账:首先开启一个事务,然后通过update语句将 a账户的100元 转给 b 账户,然后提交事务

    start transaction;
    update account set money=money-100 where name='a';
    update account set money=money+100 where name='b';
    commit;

    1). 在命令行中执行,执行效果如下:

    事务提交和事务回滚的具体例子参考这里:事务提交事务回滚

    2). 在Navicat中执行,执行结果如下:

     

    参考:

    https://www.cnblogs.com/hebao0514/p/5490698.html

     三、脏读

    (一)、什么是脏读

     脏读就是指一个事务读取了另一个事务未提交的数据。

    (二)、脏读的例子及避免

    (1). 脏读例子:

    脏读例子:https://www.cnblogs.com/hebao0514/p/5490764.html  Read Uncommitted(读未提交)(个人理解:一个事务读到另一个事务还未提交的数据)

    例子说明:开启两个线程,分别模拟a账户和b账户,
    MySQL的默认隔离级别是Repeatable Read(可重复读),该级别是可以避免脏读的,因此需要将b账户中事务的隔离级别设置为Read Uncommitted(读未提交)。
    在a账户中开启一个事务,执行转账功能,但未提交(commit);在b账户中开启一个事务,执行查询功能,因为b账户的事务隔离级别低,
    就读到了a账户还没提交的数据(即出现脏读),这时候,b误以为a账户已经转账成功,便会给a发货,当b发货之后,a如果不提交事务而将事务回滚,b就会受到损失。

    (2). 避免脏读:Read Committed 隔离级别来避免脏读的例子:

    1. a账户(左)和b账户(右)当前余额:

       

    2. 开启事务,转账给b账户:

    3. 此时a账户的事务并未提交,此时b账户查看余额:

     

    可以看出,b账户中仍为1000,没有读到a账户中没有提交的信息。说明Read Committed 隔离级别可以避免脏读

     四、事务隔离级别

    https://www.cnblogs.com/hebao0514/p/5492108.html

    1). Read Uncommitted   2). Read Committed   3). Repeatable Read   4). Serializable
    (1)Read Uncommitted
    Read UnCommitted(读未提交)是事务中最低的级别,该级别下的事务可以读取到另一个事务中未提交的数据,也被称为脏读(Dirty Read),这是相当危险的。由于该级别较低,在实际开发中避免不了任何情况,所以一般很少使用。
     
    (2)Read Committed
    大多数的数据库管理系统的默认隔离级别都是Read Committed(读提交),该级别下的事务只能读取其他事务中已经提交的内容,可以避免脏读,但是不能避免重复读和幻读的情况。
    重复读:在事务内读取了别的线程已经提交的数据,但是两次查询读取结果不一样,原因是查询的过程中其他事务做了更新操作
    幻读:在事务内两次查询的数据条数不一样,原因是查询的过程中其他事务做了添加操作
     
    (3)Repeatable Read
    Repeatable Read(可重复读)是MySQL默认的事务隔离级别,它可以避免脏读、不可重复读的问题,确保同一个事务的多个实例在并发操作数据的时候,会看到相同的数据行。但是理论上,该级别会出现幻读情况,不过MySQL的存储引擎通过多版本并发控制机制解决了该问题,因此该级别是可以避免幻读的。
     
    (4)Serializable
    Serializable(可串行化)是事务的最高隔离级别,它会强制对事务进行排序,使它们彼此之间不会发生冲突,从而解决脏读、幻读、重复读的问题。实际上,就是在每个读的数据行上加上锁。这个级别,可能导致大量超时现象和锁竞争,实际应用中很少使用。

    五、不可重复读

    (一)、什么是不可重复读

    不可重复读(Non-Repeatable Read)是指事务中两次查询的结果不一致,原因是在查询的过程中其他事务做了更新的操作。

    例如,银行在做统计报表的时候,第一次查询a账户有1000元,第二次查询a账户有900元,原因是统计期间a账户取出了100元,这样导致多次查询中,查询结果不一致。

    不可重复读和脏读有点类似,但是脏读是读取了另一个事务未提交的脏数据,不可重复读是在事务内重复读取了别的线程已提交的数据。

    note:MySQL的默认事务隔离级别是:Repeatable Read(可重复读)

    (二)、不可重复读的例子及避免

    (1). 不可重复读的例子

    不可重复读的例子:https://www.cnblogs.com/hebao0514/p/5494442.html   Repeatable Read(可重复读)(个人理解:一个事务中,重复查询的结果是一样的,哪怕在第一次查询之后,数据库已经变了,查询结果仍与第一次查询结果一致,而不是查询数据库中的最新数据)

    1. 线程2:查看线程2中事务隔离级别:

    1. 线程2:首先在线程2中开启一个事务,然后在当前事务中查询各个账户的余额信息:

    3. 线程1:线程1中不用开启事务,直接使用update更新a账户,并查询余额:

    note:由于线程1只需要执行修改的操作,不需要保证同步性,因此直接执行SQL语句就可以
    4. 线程2:当线程1更新操作执行完成后,在线程2中再次查询各账户余额,发现a账户变为900:

    线程2中,a账户两次的查询结果不一致,实际上这种操作是没有错的(虽然没错,但应该避免这种情况?)

    例子说明:开启两个线程1和2,线程2的隔离级别为Read Committed。在线程2中开启事务,查询a账户为1000,此时还不提交事务;在线程1中不用开启事务,更新a账务为900;返回线程2(此时事务还没有提交),查询a账户变为900。即:线程2在同一个事务中,两次查询结果不一致。(博客例子中的a账户和b账户应该为表述错误,应为:线程1和线程2,操作的账户都是a账户。另外博客的线程2(即b账户中)继前一篇博客的操作,已经改成了Read Committed 隔离级别)

    (2). 避免不可重复读:Repeatable Read 隔离级别来避免不可重复读的例子:

    1. 查看线程1中事务隔离级别:

    2. 在线程1中开启一个事务,然后在当前事务中查询各个账户的余额信息:

    3. 线程2中不用开启事务,直接使用update语句执行更新操作,并查询余额:

    4. 返回线程1:当线程2更新操作执行完成后,在线程1中再次查询账户余额,发现a账户仍未1000:

    5. 如果此时在线程1中修改a账户,会报错:ERROR 1205 (HY000): Lock wait timeout exceeded; try restarting transaction

    六、幻读

     (一)、什么是幻读

    幻读(Phantom Read)又称为虚读,是指在一个事务内两次查询中数据条数不一致,幻读和不重复读有些不同,同样是在两次查询过程中,不同的是,幻读是由于其他事务做了插入记录的操作,导致记录数有所增加。

    例如:银行在做统计报表时统计account表中所有用户的总金额时候,此时总共有两个账户,总共金额为2000元,这时候新增了一个用户账户,并且存入1000元,这时候银行再次统计就会发现账户总金额为3000,造成了幻读情况

    (二)、幻读的例子及避免

    (1). 幻读的例子

    幻读的例子:https://www.cnblogs.com/hebao0514/p/5494588.html  Phantom Read(幻读、虚读)(个人理解:一个事务中,重复查询的结果是一样的,哪怕在第一次查询之后,数据库已经变了,查询结果仍与第一次查询结果一致,而不是查询数据库中的最新数据。幻读是针对insert操作)

    1. 线程2:首先设置线程2的隔离级别为Read Committed,并查询。(可重复读隔离级别是可以避免幻读的出现,因此需要将事务的隔离级别设置为更低)

    2. 线程2:开启一个事务,然后在当前事务中查询账户的余额信息:

    3. 线程1:先查询account表中的信息,然后进行添加操作:(线程1不用开启事务,直接执行添加操作即可)

    4. 线程2:线程1添加完记录后,在线程2中查询余额信息:

    可以发现,在Read Committed隔离级别下,线程2中第二次查询数据比第一查询数据的时候多一条记录,这种情况并不是错误的,但可能不符合实际需求。

    例子说明:开启两个线程1和2,线程2的隔离级别为Read Committed。在线程2中开启事务,查询account表的结果为共有两条记录;在线程1中不用开启事务,在线程1中添加一条记录(c,1000);返回线程2,查询account表,查询结果变为共有三条记录。即:线程2在同一个事务中,两次查询结果不一致。

    幻读和不重复读的区别:
    同样在两次查询过程中,不同的是,幻读是由于其他事务做了插入操作(insert),导致记录数有所增加。而不重复读是由于其他事务做了更新操作(update),导致同一条记录的查询结果不同。

    (2). 避免幻读:Repeatable Read 隔离级别来避免幻读的例子:

    先删除刚才添加的(c,1000)这一条记录

    1. 线程2:为了防止出现幻读,可以将线程2的隔离级别设置为Repeatable Read

    2. 线程2:开启一个事务,然后在当前事务中查询账户的余额信息:

    3. 线程1:先查询account表中的信息,然后进行添加操作:(线程1不用开启事务,直接执行添加操作即可)

    4. 线程2:线程1添加完记录后,在线程2中查询余额信息

    可以发现,在Repeatable Read隔离级别下,线程2中两次查询结果是一样的。
    5. 线程2:使用commit提交当前事务,再查询account表,查询到三条记录

    Repeatable Read从理论的角度是会出现幻读的,但是MySQL内部通过多版本控制机制【实际上就是对读取到的数据加锁】解决这个问题。
    因此,用户才可以放心大胆使用Repeatable Read这个事务隔离级别。

    note:Serializable 和 Repeatable Read都可以防止幻读。但是Serializable 事务隔离级别效率低下,比较耗数据库性能,一般不使用。

  • 相关阅读:
    shell的一本书
    linux设置网络三种方法
    BIOS讲解
    对于ssh和hadoop联系讲解和ssh的基本内容
    Httphandler
    ASP.NET配置文件
    Httpmoudle
    ASP.NET页面生命周期
    ASP.NET页面跳转方法的集合
    OutputCache的使用
  • 原文地址:https://www.cnblogs.com/zeroingToOne/p/9564231.html
Copyright © 2011-2022 走看看