Mysql事务和并发问题解决办法MVCC

事务

什么是事务?
   维基百科的定义:事务是数据库管理系统(DBMS)执行过程中的一个逻辑单位,由一个有限的数据库操作序列构成。

  • 它是数据库中的最小工作单元,是不可在分的
  • 它可能是一个或者一系列的语句
      
     支持的事务的存储引擎有InnoDB,NDB
     这个链接https://dev.mysql.com/doc/refman/5.7/en/storage-engines.html可以查看mysql支持的数据库引擎。

支持事务的四大特性

  • 原子性(Atomicity):也就是刚刚说的不可再分,对数据库的操作要么都成功,要么都失败,不可能出现出现一个失败。
      以转账为例,A给B转帐,A的钱减少了,对应B的余额就增加了,这两个一定是同时成功,或者失败的。原子性,在InnoDB里面是通过undo log日志来实现的,它记录了数据修改之前的(逻辑日志),一旦发生异常,就可以使用undo log 来实现回滚操作。

  • 持久性(Durable),事务的持久性是什么意思呢?我们对数据库的任意操作,增删改,只要事务提交成功,那么结果就是永久性的,不可能因为系统宕机或者重启了数据库的服务器,它又恢复到了原来的状态了。这个就是事务的持久性。
       数据库崩溃恢复(crash-safe)是通过什么实现的?持久性是通过redo log 和double write 双写缓冲来实现的,我们操作数据的时候会先把数据写到内存的Buffer pool里面,同时记录redo log,如果在刷盘之前出现异常,在重启后就可以读取redo log的内容,写入到磁盘,保证数据的持久性。恢复成功的前提是数据页本身是没有破坏,是完整的,这个双写缓存(double write)保证。

  • 隔离性(isolation) ,我们有了事务的定义以后,在数据库中会有很多的事务去操作同一张表或者同一行数据,必然会产生一些并发或者干扰的操作,那么我们去对隔离性的定义,就是这些很多个事务,对表或者行的并发操作,应该是透明了,互相不干扰的。通过这种方式,我们最终也是保证了业务数据的一致性,通过锁机制实现。

    原子性是事务隔离的基础,隔离性,持久性是手段,最后都是为了实现一致性。

  • 一致性(consistent),指的是数据库的完整性约束没有被破坏,事务执行的前后都是合法的数据状态。
       数据库自生的完整性约束,如主键必须是唯一的,字段长度符合要求。除了数据库自生的完整性约束,还有一个是。
    用户自定义的完整性:
       比如说是转账的这个场景,A账号余额减少1000,B账户余额只增加了500,这个时候因为两个操作都成功了,按照原子性的操作都成功了,它是满足原子性的,但是没有满足一致性,应该它导致了事务转账前后的不一致性。
    还有一种情况。,A的账户的余额0 ,如果这个时候转账成功了,A账号的余额变了-1000,虽然满足原子性,但是我们知道,余额是不能小于0,所以也违反了一致性。用户自定义的完整性通常要在代码块中控制。

数据库什么时候会出现事务

任何一条操作数据库的指令都是事务。
开启事务事务有两种方式,自动开启和自动提交,手动开启和手动提交。
  InnoDB里面有一个autocommit参数。它的默认值是ON(表示会自动开启事务和自动提交事务)。否则,那么数据库的事务就需要我们手动的开启和手动去结束。手动开启事务也有两种方式,一种是用begin;一种是用start transaction。结束事务也有两种方式,一种就是提交事务commit,还有一种就是rollback,表示回滚,事务也会结束。还有一种情况就是断开连接的时候,事务也会结束
  结束事务的时候,事务持有的锁就会释放。

事务并发会带来什么问题?

  • 脏读,事务A读取事务B未提交的事务
    在这里插入图片描述
    事务B修改数据后,并没有提交事务,这个时候事务A就能查到修改后的数据.这种事务并发问题就叫脏读.
  • 不可重复读:事务A多次读取同一份数据,事务B在此过程中对数据修改并提交,导致事务A多次读取同一份数据的结果不一致.
    在这里插入图片描述

上图中事务B执行了update以后通过一个commit提交了修改,然后事务A读取到了其他事务提交的数据导致前后两次读取数据不一致情况,这种事务并发问题就叫不可重复读.
  update和delete都会造成不可重复读的现象.

  • 幻读:事务A修改数据的同时,事务
    在这里插入图片描述
    在A事务中执行了一个范围查询,这个时候满足条件的数据只有一条,这个时候事务B插入了一条数据,并且提交了.重点:插入了一行数据,在A事务里面再去查询的时候,发现多了一条数据.
      一个事务前后两次读取数据数据不一致,是由于其他事务插入数据造成的,这种情况就叫做幻读.

小结: 事务并发带来的三大问题,无论是脏读,不可重复读,还是幻读,它们都是数据库的读一致性的问题,都是在一个事务里面前后两次读取出现了不一致的情况.
不可重复读主要是由于upate和delete 操作引发的,幻读主要是由于insert操作引发的。,很多教程和博客都写错了,查证于SQL92标准,http://www.contrib.andrew.cmu.edu/~shadow/sql/sql1992.txt,

     The isolation level specifies the kind of phenomena that can occur
     during the execution of concurrent SQL-transactions. The following
     phenomena are possible:

     1) P1 ("Dirty read"): SQL-transaction T1 modifies a row. SQL-
        transaction T2 then reads that row before T1 performs a COMMIT.
        If T1 then performs a ROLLBACK, T2 will have read a row that was
        never committed and that may thus be considered to have never
        existed.

			不可重复读,T2 then modifies or deletes that row and performs
        a COMMIT
     2) P2 ("Non-repeatable read"): SQL-transaction T1 reads a row. SQL-
        transaction T2 then modifies or deletes that row and performs
        a COMMIT. If T1 then attempts to reread the row, it may receive
        the modified value or discover that the row has been deleted.
	  
     3) P3 ("Phantom"): SQL-transaction T1 reads the set of rows N
        that satisfy some <search condition>. SQL-transaction T2 then
        executes SQL-statements that generate one or more rows that
        satisfy the <search condition> used by SQL-transaction T1. If
        SQL-transaction T1 then repeats the initial read with the same
        <search condition>, it obtains a different collection of rows.

读一至性问题,必须要数据库提供的事务隔离机制来解决。就像我们去饭店吃饭,基本的设施和卫生保证都是饭店来提供的。那么我们使用数据库,隔离性的问题也必须由数据库帮助我们来解决。

事务隔离级别

  1. 读未提交(Read Uncommitted RU),一个事务可以读取到其他事务未提交的数据,会出现脏读,所以叫做RU,它没有解决任何问题。
  2. 读已提交(Read Committed),就是一个事务只能读取到其他事务已经提交的数据,不能读取到其他事务没有提交的数据,它解决了脏读的问题,但是会出现不可重复读的问题。
  3. 可重复读(Repeatable Read) ,它解决了不可重复度的问题,也就是在同一个事务里面多次读取同样的数据结果是一样的,但是这个级别下,没有解决幻读问题。
  4. 串行化(Serializable),在这个隔离级别,在这个隔离级别里面,所有的事务都是串行执行的,也就是对数据的操作需要排队,已经不存在事务的并发操作了,所以它解决了所有问题。
    上面是SQL92对隔离级别定义的标准,但是不同的数据库厂商或者存储引擎的实现由一定的差异,比如Oracle里面就只有RC(已提交读)和串行化,
    INNODB对数据库的隔离级别的支持程度:如下表
事务隔离级别 脏读 不可重复读 幻读
读未提交 可能 不可能 可能
读已提交 不可能 可能 可能
可重复读 不可能 不可能 对InnoDB不可能
串行化 不可能 不可能 不可能

事务的隔离级别越高,事务的并发就越低,唯一的区别就在于,InnoDB在RR的级别就解决了幻读的问题。这个也是InnoDB默认使用使用RR作为事务隔离级的原因,既保证了数据的一致性,又支持较高的并发度。

设置隔离级别:
set [global|session] transaction isolation level ****;
查看当前隔离级别
 show variables like 'tx_isolation';

事务并发问题innodb两大解决方案

MVCC

一条记录在系统中可以存在多个版本,就是数据库的多版本并发控制(MVCC)
  
   
       在实现上,数据库里面会创建一个视图,访问的时候以视图的逻辑结果为准。在“可重复读”隔离级别下,这个视图是在事务启动时创建的,整个事务存在期间都用这个视图。在“读提交”隔离级别下,这个视图是在每个SQL语句开始执行的时候创建的。在可从复读,在第一次读取数据时生成一个视图,所以多个事务查询的结果是一样的。这里需要注意的是,“读未提交”隔离级别下直接返回记录上的最新值,没有视图概念;而“串行化”隔离级别下直接用加锁的方式来避免并行访问。
   如果要让事务前后两次读取的数据保持一致性,可以在修改数据的时候给它建立一个快照(视图 ),后来在来读取这个快照就行了。我们叫这种方案为多版本并发控制(Multi version Concuccrrency Control)。

MVCC的核心思想是:我可以查到在我这个事务开始以前已经存在的数据,即使它在后面被修改或者删除,或者新增加的数据,也是查不到的。
问题:这个快照是什么时候创建的?读取数据的时候,怎么保证读取到这个而不是最新的数据?
  在可重复读的隔离级别下,事务启动的时候时候就创建了快照,这个快照是基于整个数据库。innodb并不是把整个数据库都拷贝了一份,想要知道怎么保证读取到最新的数据,就要先明白快照是怎么实现的?
  InnoDB行格式默认是Dynamic,是Compat格式的增强版,记录头结构信息占用了5个字节,事务ID和回滚指针分别占用了6个和7个字节,行格式如下:

DB_ROW_ID DB_TRX_ID DB_ROLL_PTR 数据列1

Innodb里面每个事务有一个唯一的事务ID,叫做transaction id,它是在事务开始的时候向Innodb的事务系统申请的,按照申请顺序严格递增的。
innoDB的DB_TRX_ID:插入或更新行的最后一个事务的事务ID,6字节
DB_ROLL_PTR,7字节:回滚指针,数据被删除或记录为旧数据的时候,记录当前事务ID。

只能查找创建时间小于等于当前事务ID(事务2)的数据和删除时间大于当前事务ID的行。

undo log版本链,ReadView机制,RC,RR如何基于ReadView实现的?

https://blog.csdn.net/filling_l/article/details/112854716

下图不是表中真实数据,感觉整个图有问题,有时间仔细读官网了,再回来完善。
在这里插入图片描述
  通过上图的演示我们能看到,通过控制版本号,无论插入,删除修改,第二个事务查询到的数据都没有变化,在InnoDB中,MVCC是通过undo log实现的。在InnoDB中,MVCC和锁是协同使用的,这两种方案并不是互斥的。

LBCC

     既然要解决读一致性的问题,可以在读取数据的时候,锁定要操作的数据,不允许其他事务修改就好了,这种方案叫做基于锁的并发控制Lock Based Concurrency Control。
  但是如果一个事务读取的时候不允许其他修改,把么就意味着不支持并发读写操作,而大多数的应用都是读多写少的,这样会极大地影响操作数据的效率。

猜你喜欢

转载自blog.csdn.net/filling_l/article/details/108780401
今日推荐