[MySQL数据库之事务、读现象、数据库锁机制、多版本控制MVCC、事务隔离机制]

时间:2023-03-09 08:54:59
[MySQL数据库之事务、读现象、数据库锁机制、多版本控制MVCC、事务隔离机制]

[MySQL数据库之事务、读现象、数据库锁机制、多版本控制MVCC、事务隔离机制]

事务

1、什么是事务:

事务(Transaction),顾名思义就是要做的或所做的事情,数据库事务指的则是作为单个逻辑工作单元执行的一系列操作(SQL语句)。这些操作要么全部执行,要么全部不执行。

2、为什么需要事务

把一系列sql放入一个事务中有两个目的:

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

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

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

3、事务的4个特性

这四个特性通常称为ACID特性

1、原子性(Atomicity)
一个事务的执行是整体性的,不可分割,包含在其中的对数据库的操作要么全部被执行,要么都不执行。 2、一致性(Consistency)
事务应确保数据库的状态从一个一致状态转变为另一个一致状态。例如转账行为中:一个人减了50元,另
外一个人就应该加上这50元,而不能是40元。其他一致状态的含义是数据库中的数据应满足完整性约束,
例如字段约束不能为负数,事务执行完毕后的该字段也同样不是负数 3、隔离性(Isolation)
多个事务并发执行时,一个事务的执行不应影响其他事务的执行,
且多个事务不能看到对方的中间状态(提交或者回滚之前的状态) 4、持久性(Durability)
一个事务的提交,对数据库的修改是永久保存在数据库中的,不受外部因素或其他操作影响。
验证事务的一致性、隔离性

[MySQL数据库之事务、读现象、数据库锁机制、多版本控制MVCC、事务隔离机制]

4、事务的3种运行模式

隐式 == 自动

显式 == 手动

  1. 自动提交事务(隐式开启、隐式提交)

每一条单独的SQL语句都在其执行完成后进行自动提交事务,即执行 SQL 语句后就会马上自动隐式执行 COMMIT 操作。如果出现错误,则进行事务回滚至之前状态。

  1. 显式事务(显式开启、显式提交)

通过指定事务开始语句来显式开启事务来作为开始,并由以提交命令或者回滚命令来提交或者回滚事务作为结束的一段代码就是一个用户定义的显式事务。SQL SERVER、MYSQL和ORACLE都以不同的方式来开始显式事务

1)SQL SERVER:以begin transaction开启事务开始,由commit transaction 提交事务或者rollback transaction 回滚事务结束。

2)MySQL: 以start transaction或者begin;开启事务commit提交事务或者rollback回滚事务,commit或者rollback事务都结束
# 注意
这种方式在当你使用commit或者rollback后,事务就结束了
再次进入事务状态需要再次start transaction或begin 3)ORACLE:ORACLE事务起始于第一条SQL语句的执行,不需要特别指定事务的开始和结束,一个事务结束就意味着下一事务开始。以commit或不带有savepoint子句的rollback命令作为结束。
  1. 隐式事务(隐式开启、显式提交)

在隐式事务中,无需使用BEGIN TRANASACTION 来开启事务,每个SQL语句第一次执行就会开启一个事务,直到用COMMIT [TRANSACTION]来提交或者ROLLBACK [TRANSACTION]来回滚结束事务。

1)SQL SERVER中使用下面语句来开启和关闭隐式事务模式。或者通过 Sql Server Managerment Studio,在 Tools -> Options Query Execution -> SQL Server -> ANSI 选项中勾选SET IMPLICIT_TRANSACTIONS设置开启还是关闭隐式事务模式。

ser implicit_autocommit on -- on是开启,off是关闭

2)ORACLE默认就是隐式开启事务,显式提交事务。可以通过下面语句开启自动提交事务,以达到隐式提交事务。

set autocommit on; -- on是开启自动事务模式,off是关闭自动提交事务模式

3)MYSQL通过关闭自动提交事务,来达到隐式开启事务,显式提交事务的目的。

set session autocommit = 0; -- 0是关闭,1是开启;session是设置当前会话变量,global是设置全局变量

总结:MySQL 默认为每条sql开启事务,并且会在本条sql执行完毕后自动执行commit提交,若想设置手动提交,有两种方式

方式一:直接用 SET 来改变 MySQL 的自动提交模式(下述设置均为会话级别的设置):

  • set autocommit=0 禁止自动提交
  • set autocommit=1 开启自动提交

方式二: 手动开启的事务里默认不会自动提交

# 手动开启的事务里默认不会自动提交,所以我们可以将要执行的sql语句放在我们自己手动开启的事务里
start transaction; 或者begin; 开启事务
commit; 提交事务
rollback;回滚事务 commit或者rollback事务都结束 start transaction;
update user set name="EGON" where id =1;
commit; # 注意
这种方式在当你使用commit或者rollback后,事务就结束了
再次进入事务状态需要再次start transaction

数据库读现象

数据库管理软件的“读现象”指的是当多个事务并发执行时,在读取数据方面可能碰到的问题,包括有脏读、不可重复读和幻读。

ps:对于一些数据库管理软件会自带相应的机制去解决脏读、不可重复读、幻读等问题,因为这些自带的机制,下述的一些实验现象可能在某一数据库管理软件的默认机制下并不成立,即我们并不能在所有数据库管理软件中看到所有的读现象。所以此处我们暂且抛开具体的某个数据库管理软件的默认机制的干扰,暂时假设没有附加任何机制为前提,单纯地去理解数据库的读现象。

脏读 (dirty read)

事务T2更新了一行记录的内容,但是并没有提交所做的修改。

事务T1读取更新后的行,然后T1执行回滚操作,取消了刚才所做的修改。此时T1所读取的行就无效了,称之为脏数据。

不可重复读取 (non repeatable read)

事务T1读取一行记录,紧接着事务T2修改了T1刚才读取的那一行记录并且提交了。

然后T1又再次读取这行记录,发现与刚才读取的结果不同。这就称为“不可重复”读,因为T1原来读取的那行记录已经发生了变化。

幻像读取 (phantom read)

幻读(phantom read)”是不可重复读(Non-repeatable reads)的一种特殊场景:

当事务没有获取范围锁的情况下执行SELECT … WHERE操作有可能会发生“幻影读(phantom read)”。

事务T1读取或修改了指定的WHERE子句所返回的结果集。

然后事务T2新插入一行记录,这行记录恰好可以满足T1所使用的查询条件中的WHERE 子句的条件。

然后T1又使用相同的查询再次对表进行检索,但是此时却看到了事务T2刚才插入的新行或者发现了处于WHRE子句范围内,但却未曾修改过的记录。就好像“幻觉”一样,因为对T1来说这一行就像突然出现的一样。

一般解决幻读的方法是增加范围锁RangeS,锁定检锁范围为只读,这样就避免了幻读。

数据库锁机制:行级锁之共享锁与排他锁

行级锁分为共享锁和排他锁两种

与行处理相关的sql有:insert、update、delete、select,这四类sql在操作记录行时,可以为行加上锁,但需要知道的是

1、对于insert、update、delete语句,InnoDB会自动给涉及的数据加锁,而且是排他锁(X);

2、对于普通的select语句,InnoDB不会加任何锁,需要我们手动自己加,可以加两种类型的锁,如下所示

# 共享锁(S):SELECT ... LOCK IN SHARE MODE;  -- 查出的记录行都会被锁住

# 排他锁(X):SELECT ... FOR UPDATE;  -- 查出的记录行都会被锁住

[MySQL数据库之事务、读现象、数据库锁机制、多版本控制MVCC、事务隔离机制]

共享锁(Share Lock)

共享锁又称为读锁,简称S锁,顾名思义,共享锁就是多个事务对于同一数据可以共享一把锁,获准共享锁的事务只能读数据,不能修改数据直到已释放所有共享锁,所以共享锁可以支持并发读

如果事务T对数据A加上共享锁后,则其他事务只能对A再加共享锁或不加锁(在其他事务里一定不能再加排他锁,但是在事务T自己里面是可以加的),反之亦然。

用法

SELECT ... LOCK IN SHARE MODE;

在查询语句后面增加LOCK IN SHARE MODE,Mysql会对查询结果中的每行都加共享锁,当没有其他线程对查询结果集中的任何一行使用排他锁时,可以成功申请共享锁,否则会被阻塞。其他线程也可以读取使用了共享锁的表,而且这些线程读取的是同一个版本的数据。

排他锁(eXclusive Lock)

排他锁又称为写锁,简称X锁,顾名思义,排他锁就是不能与其他所并存,如一个事务获取了一个数据行的排他锁,其他事务就不能再对该行加任何类型的其他他锁(共享锁和排他锁),但是获取排他锁的事务是可以对数据就行读取和修改。

用法

SELECT ... FOR UPDATE;

在查询语句后面增加FOR UPDATE,Mysql会对查询结果中的每行都加排他锁,当没有其他线程对查询结果集中的任何一行使用排他锁时,可以成功申请排他锁,否则会被阻塞。

特例:加过排他锁的数据行在其他事务种是不能修改数据的,也不能通过for update和lock in share mode锁的方式查询数据,但可以直接通过select ...from...查询数据,因为普通select查询没有任何锁机制。
  • 数据库锁机制

    • 1、一旦事务1对数据A加了排它锁

      • 那么其他事务无法对数据A加任何锁
      • 只有事务1可以操作数据A,而且可以读也可以写
    • 2、一旦事务1对数据A加了共享锁

      • 那么其他事务可以对数据A加锁,但只能加共享锁
      • 一旦多个事务都会数据A加了共享锁,大家都只能读不能改

多版本控制MVCC:Snapshot Read vs Current Read

MySQL InnoDB存储引擎,实现的是基于多版本的并发控制协议——MVCC (Multi-Version Concurrency Control) (注:与MVCC相对的,是基于锁的并发控制,Lock-Based Concurrency Control)。MVCC最大的好处,相信也是耳熟能详:读不加锁,读写不冲突。在读多写少的OLTP应用中,读写不冲突是非常重要的,极大的增加了系统的并发性能,这也是为什么现阶段,几乎所有的RDBMS,都支持了MVCC。

  在MVCC并发控制中,读操作可以分成两类:快照读 (snapshot read)与当前读 (current read)。快照读,读取的是记录的可见版本 (有可能是历史版本),不用加锁。当前读,读取的是记录的最新版本,并且,当前读返回的记录,都会加上锁,保证其他事务不会再并发修改这条记录。

  在一个支持MVCC并发控制的系统中,哪些读操作是快照读?哪些操作又是当前读呢?以MySQL InnoDB为例:

  快照读:简单的select操作,属于快照读,不加锁。(当然,也有例外,下面会分析)

       select * from table where ?;

   当前读:特殊的读操作,插入/更新/删除操作,属于当前读,需要加锁。  

       select * from table where ? lock in share mode;

select * from table where ? for update;

insert into table values (…);

update table set ? where ?;

delete from table where ?;

       所有以上的语句,都属于当前读,读取记录的最新版本。并且,读取之后,还需要保证其他并发事务不能修改当前记录,对读取记录加锁。其中,除了第一条语句,对读取记录加 S锁 (共享锁)外,其他的操作,都加的是 X锁 (排它锁)。

  为什么将 插入/更新/删除 操作,都归为当前读?可以看看下面这个 更新 操作,在数据库中的执行流程:

[MySQL数据库之事务、读现象、数据库锁机制、多版本控制MVCC、事务隔离机制]

  从图中,可以看到,一个Update操作的具体流程。当Update SQL被发给MySQL后,MySQL Server会根据where条件,读取第一条满足条件的记录,然后InnoDB引擎会将第一条记录返回,并加锁 (current read)。待MySQL Server收到这条加锁的记录之后,会再发起一个Update请求,更新这条记录。一条记录操作完成,再读取下一条记录,直至没有满足条件的记录为止。因此,Update操作内部,就包含了一个当前读。同理,Delete操作也一样。Insert操作会稍微有些不同,简单来说,就是Insert操作可能会触发Unique Key的冲突检查,也会进行一个当前读。

:根据上图的交互,针对一条当前读的SQL语句,InnoDB与MySQL Server的交互,是一条一条进行的,因此,加锁也是一条一条进行的。先对一条满足条件的记录加锁,返回给MySQL Server,做一些DML操作;然后在读取下一条加锁,直至读取完毕。

事务隔离机制

  事务具有原子性、一致性、隔离性、持久性四大特性,而隔离性顾名思义指的就是事务彼此之间隔离开,多个事务在同时处理一个数据时彼此之间互相不影响,如如果隔离的不够好就有可能会产生脏读、不可重复度、幻读等读现象,为此,隔离性总共分为四种级别

由低到高依次为Read uncommitted 、Read committed 、Repeatable read 、Serializable ,这四个级别可以逐个解决脏读 、不可重复读 、幻读 这几类问题。

[MySQL数据库之事务、读现象、数据库锁机制、多版本控制MVCC、事务隔离机制]

需要强调的是:我们确实可以采用提高事务的隔离级别的方式来解决脏读、不可重复读、幻读等问题,但与此同时,事务的隔离级别越

高,并发能力也就越低。所以,还需要读者根据业务需要进行权衡。

MySQL/InnoDB定义的4种隔离级别:(mysql的级别不是ios定义的级别,iso定义的RR存在幻读)

    • 未提交读(Read Uncommited)

      未提交读是最低的隔离级别。通过名字我们就可以知道,在这种事务隔离级别下,一个事务可以读到另外一个事务未提交的数据。

    • 提交读(Read committed)

      提交读也可以翻译成读已提交,通过名字也可以分析出,在一个事务修改数据过程中,如果事务还没提交,其他事务不能读该数据。

      提交读这种隔离级别保证了读到的任何数据都是提交的数据,避免了脏读(dirty reads)。但是不保证事务重新读的时候能读到相同的数据,因为在每次数据读完之后其他事务可以修改刚才读到的数据。存在幻读现象。

    • 可重复读(Repeatable read)

      可重复读隔离级别保证对读取到的记录加锁 (记录锁),同时保证对读取的范围加锁,新的满足查询条件的记录不能够插入 (间隙锁),不存在幻读现象。

    • 可序列化(Serializable)

      从MVCC并发控制退化为基于锁的并发控制。不区别快照读与当前读,所有的读操作均为当前读,读加读锁 (S锁),写加写锁 (X锁)。

      Serializable隔离级别下,读写冲突,因此并发度急剧下降,在MySQL/InnoDB下不建议使用。

    • 在MYSQL的事务引擎中,INNODB是使用范围最广的。它默认的事务隔离级别是REPEATABLE READ(可重复读),在标准的事务隔离级别定义下,REPEATABLE READ是不能防止幻读产生的。INNODB使用了next-key locks实现了防止幻读的发生。


数据库事务详解参考:https://www.cnblogs.com/linhaifeng/articles/14387841.html

数据库锁机制详解参考:https://www.cnblogs.com/linhaifeng/articles/14386584.html

事务隔离机制详解参考:https://www.cnblogs.com/linhaifeng/articles/14387903.html

多版本控制MVCC详情参考:https://www.cnblogs.com/crazylqy/p/7611069.html