MySQL(三)

一、事务

1)什么是ACID

Atomicity 原子性:某个操作,要么全部执行完毕,要么全部执行回滚

Consistency 一致性:数据库中的数据全部符合现实世界的约束,则满足一致性。比如性别没有不男不女,转账过程不影响已有货币总量(不凭空多出货币或少)

Isolation 隔离性(作用体现在我们开启和关闭事务及设置隔离级别时):多个事务访问相同数据时。对该数据状态的转换对应数据库操作顺序有一定规律,彼此互不干涉

Durability 持久性:现实中数据状态的转换映射到数据库中,即对数据的修改要保存在磁盘中

2)并发执行时数据的一致性问题

脏写:两个事务开启并发修改同一数据(不重要,因为正常使用的所有隔离级别都不会发生脏写)

脏读:一个事务(A)读取到了另一个事务(B)修改过的数据

不可重复读:另一个事务(B)修改一个事务(A)读取过的数据

针对某一条记录读前读后不一致

幻读:一个事务(A)根据(select ... where vip='是')查询了一些记录。A未提交时,另一个事务(B)写入了(insert、update、delete都行,以insert为例)一些符合上述搜索条件的记录(insert into ... values(3,700,'是') )

针对某条件范围读前读后不一致

3)SQL事务隔离级别(不单指MySQL隔离级别,而是指所有SQL)

隔离级别从上到下安全度递增,效率递减。

MySQL默认隔离级别为REPEATABLE READ(可重复读),而且MySQL的REPEATABLE READ可大概率防止幻读

二、MVCC(开启事务后操作才会分配新的trx_id)

1)MVCC & ReadView

MVCC(Multi-Version Concurrency Control):多版本并发控制,利用记录的版本链和ReadView,来控制并发事务访问相同记录的行为

ReadView:一致性视图,用来判断版本链中的哪个版本是当前事务可见的(主要用于 Read Committed 和 Repeatable Read)

2)版本链

每次更新该记录,都会将旧值放在一条undo日志中。随着更新次数的增加被roll_point属性连接成一条链表,这条链表称之为版本链

trx_id为80的版本是首次INSERT操作的记录

3)ReadView()包含的内容

m_ids:在生成ReadView时,当前系统中的事务id列表

min_trx_id:在生成ReadView时,当前系统活跃的读写事务中,也就是m_ids中的最小值

max_trx_id:在生成ReadView时,系统应该分配给

creator_trx_id:的事务的事务id

4)如何通过ReadView莱判断记录的某个版本可见

如果,则表明当前事务在访问自己修改过的记录,所以此版本可以被当前事务访问

如果,则表明生成该版本的事务在当前事务生成ReadView之前已经提交了,所以此版本可以被当前事务访问

如果,则表明生成该版本的事务在当前事务生成ReadView之后才开启,所以此版本不可以被当前事务访问

如果包含,说明生成此版本的事务还在活跃中,此版本不可以被当前事务访问

如果不包含,说明生成此版本的事务已经提交了,此版本可以被当前事务访问

Ps:如果某个版本的记录对当前事务不可见,那么上述判断指向版本链的下一条记录,直至找到可见记录或者到最后一个版本为止(属于记录,其他的事务Id都属于此刻生成的版本快照)

5)ReadView()生成的时机

在Read Committed隔离级别下,此刻开启事务T3,执行第一次SELECT name操作,生成的ReadView【m_ids:10,20  、 min_trx_id:10 、 max_trx_id:21 、 creator_trx_id(无增删改暂未分配):0】,结果为‘muse’
在Read Committed隔离级别下,此刻事务T3执行第二次SELECT name操作,生成的ReadView【m_ids:20  、 min_trx_id:20 、 max_trx_id:21 、 creator_trx_id(无增删改暂未分配):0】,结果为‘b’

Read Committed和Repeatable Read隔离级别之间最大的区别——生成ReadView的时机不同!!!

Read Committed:在一个事务中,每次读取数据前都生成一个ReadView

Repeatable Read:在一个事务中,只有第一次读取数据时生成一个ReadView

三、锁

1)并发事务问题——写-写

锁结构创建在内存中,跟记录相关联

2)并发事务问题——读-写/写-读

方案1:读操作——

             写操作——对记录进行

方案2:读、写操作都进行加锁

如果采用MVCC的方式,读-写批次并不冲突,性能更高(日常默认方案1)

如果采用加锁方式,读-写操作彼此需要排队执行,从而影响性能

Ps:一般来说,会选择MVCC方式来解决读-写操作并发执行的问题,但是在某些特殊的业务场景(如:银行业务,需要读取最新数据。严格要求数据准确性,对执行时间却并无苛刻要求),才会选择读、写操作都进行加锁

3)行级锁

共享锁/S锁(Shared Lock):在事务要读取一条记录时,需要先获取该记录的S锁

独占锁/排它锁/X锁(Exclusive Lock):在事务要修改一条记录时,需要先获取该记录的X锁

行级锁兼容图

针对操作:

1、加写锁:select * from tb for update  

2、加读锁:select * from tb lock in share mode

针对操作:先在B+树中定位到这条记录的位置,然后获取这条记录的,最后执行操作

针对操作:一般情况,新插入的一条记录受保护,不需要在内存中为其生成锁结构。不过事务插入一条记录之前,需要先定位该记录在B+树中的位置,如果该位置的下一条记录已经加了或。那当前事务就会为该记录加并进入等待状态

针对操作,分为三种情况:

1、并且记录更新前后所:先在B+树中定位到这条记录的位置,然后获取这条记录的X锁,最后在原位置修改

2、并且记录更新前后所:先在B+树中定位到这条记录的位置,然后获取这条记录的X锁,之后将原纪录彻底删除(即:把记录移入垃圾链表),最后插入一条新记录

3、:原纪录之行DELETE操作之后,再INSERT一条新数据。加锁操作按照DELETE和INSERT的规则进行

4)表级锁 & 表级意向锁

表级共享锁(S锁):其他事务可以继续获得/的锁,其他事务不可以继续获得/的锁

表级独占锁(X锁):其他事务不可以继续获得/的锁或锁

意向共享锁(IS锁):当事务准备在上加锁时,首先需要在表级别加一个锁

意向独占锁(IX锁):当事务准备在上加锁时,首先需要在表级别加一个锁 

IS锁和IX锁仅仅用来在之后需要加表级锁的时候(起标识作用),表中的记录有无加行级锁,而不是一条一条遍历查看记录有无加行级锁

表级锁兼容图

5)InnoDB表级锁

InnoDB存储引擎提供的表级锁比较“鸡肋”,特殊情况才用(比如系统崩溃恢复数据)

Ps:当使用了auto_increment()修饰列的时候,就会涉及到AUTO_INC锁和轻量级锁。其中innodb_autoinc_lock_mode系统变量,用来控制到底使用上述两种方式中的哪一种

AUTO_INC锁和轻量级锁区别:多线程情况下,AUTO_INC一次只分配一个主键id,待其插入成功之后,其他要插入的线程才能获得AUTO_INC锁(AUTO_INC锁保证数据插入成功);轻量级锁可以一次分配多个id,不需要插入成功,其他要插入的线程也能获得轻量级锁(轻量级锁不保证数据插入成功)

0:一律采用AUTO_INC锁    1:混合采用,即插入记录数量确定用轻量级锁,不确定用AUTO_INC锁    2:一律采用轻量级锁

6)InnoDB行级锁

LOCK_REC_NOT_GAP:记录锁,也就是仅仅负责把一条记录锁上的锁

LOCK_GAP:gap锁,锁住了指定记录,防止其间插入新纪录(gap锁仅仅是为了防止幻读现象)

LOCK_ORDINARY:next-key锁,本质就是+的合体。既能锁住该条记录,也能防止别的事务将新纪录插入到被保护记录前面的间隙

LOCK_INSERT_INTENTION:插入意向锁,事务在等待时也需要在内存中生成一个锁结构,表明有事务想在某个间隙中插入新纪录,但是现在处于等待状态

隐式锁:

场景一:对于有一个trx_id隐藏列,该隐藏列记录着最后改动该记录的事务id,在当前事务新插入一条聚簇索引记录后,该记录的trx_id隐藏列代表的就是当前事务的事务id。如果其他事务想对该记录添加或,首先会看一下该记录的trx_id是否为当前活跃事务。如果不是正常读取,如果是就由想对该记录加锁的其他事务帮该事务创建一个X锁的锁结构,该锁结构的is_waiting属性为false,同时想对该记录加锁的其他事务自己也创建一个锁结构,该锁结构的is_waiting属性为true然后进入等待状态

场景二:对于本身没有trx_id隐藏列,但是在二级索引页的Page Header部分有一个属性,该属性代表对该页面做改动的最大事务id。如果此属性,说明对该页面改动的事务都已提交,否则就需要在页面中定位到对应的二级索引记录,再回表找到对应的聚簇索引,然后再重复场景一操作

7)InnoDB锁的内存结构

8)加锁操作解析

8.1)普通的select语句

针对事务隔离级别呈现不同的状态

8.2)锁定读语句

归类为以下四种语句:

语句一:SELECT ... LOCK IN SHARE MODE;

语句二:SELECT ... FOR UPDATE;

语句三:UPDATE ...

语句四:DELETE ...

不管是,还是,首要条件是先查到对应的语句再进行操作

不同的隔离级别加不同的锁,在不满足查询条件的情况下,前两个隔离级别释放锁,后两个不满足查询条件也不释放锁(参考SELECT示例一和SELECT示例二)

SELECT ... LOCK IN SHARE MODE示例一:

针对number为搜索条件,隔离级别为或,执行             

聚簇索引超出边界的记录释放锁

SELECT ... LOCK IN SHARE MODE示例二:

针对number为搜索条件,隔离级别为或,执行

              

number3首次满足检索条件number>2,number9首次超出检索条件number<=7

SELECT ... LOCK IN SHARE MODE示例三:

针对name作为搜索条件,隔离级别为或,执行

        ''  ''      

二级索引超出边界的记录不释放锁

SELECT ... LOCK IN SHARE MODE示例四:

针对name作为搜索条件,隔离级别为或,执行

       ‘‘  ‘‘      

检索条件age=20需要回表,通过主键的回表查询只是记录锁而不是next-key锁

UPDATE ... 示例一:

针对name进行修改,隔离级别为或,执行

   ''      

根据检索条件先找到聚簇索引中的记录,确定聚簇索引中要修改的记录,再通过主键确定二级索引中要修改的记录

PS::当Update语句读取到已经被其他事务加了的记录时,InooDB将此记录的读出来,再判断最新版本记录是否和Update语句的,不匹配则忽视该记录跳到下一条记录;匹配则再次读取该记录并加锁(仅当隔离级别为Read Uncommitted或Read Committed且执行Update操作时,采用半一执行性读)

UPDATE ... 示例二:

针对name进行修改,隔离级别为

或,执行

   ‘‘      

假如不是修改二级索引,就不需要再找二级索引的步骤了

SELECT ... FOR UPDATE & DELETE ...  :

SELECT ... FOR UPDATE语句的加锁过程与SELECT ... LOCK IN SHARE MODE语句类似,区别点在于SELECT ... FOR UPDATE为记录加。

DELETE ... 语句加锁过程与UPDATE ...语句处理方式相同,当表中含有,或二级索引记录之前实际加的是,效果等同于加记录锁

8.3)加锁操作补充内容

二级索引精准匹配(非范围查找)示例一:

隔离级别为或时,不会为扫描记录的后一条。比如执行

select * from tb_user where name = ‘muse’ for update,情况如下图

二级索引精准匹配(非范围查找)示例二:

隔离级别为或时,会为扫描记录的后一条。比如执行

select * from tb_user where name = ‘muse’ for update,情况如下图

二级索引查找不到记录,示例一:

隔离级别为或并且是,则为扫描区间。比如执行

select * from tb_user where name = ‘moon’ for update,情况如下图

二级索引查找不到记录,示例二:

隔离级别为或并且是,则为扫描区间。比如执行

select * from tb_user where name > ‘m’ and name < 't' for update,情况如下图

左闭区间加锁示例:

隔离级别为或,使用,如果定位到第一个聚簇索引记录的number值刚好与扫描区间中最小的值。那么会为该聚簇索引记录加。比如执行

select * from tb_user where number >= 3 for update,情况如下图

自右向左扫描加锁(DESC)示例:

隔离级别为或,的顺序扫描,会讲匹配到的第一条记录。比如执行

select * from tb_user where name >= 'john' and name <= 'tom' and age = 20 order by name  FOR UPDATE,情况如下图

你可能感兴趣的:(MySQL(三))