[MySQL] MySQL重要的知识点总结

[MySQL] MySQL重要的知识点总结

0 前言

此篇文章由阿森一人完成,转载请标注出处。

此篇文章内容均来自网络。感谢各位大大的知识分享。

此篇文章主要讲述MySQL的重要知识点,不适合初学者阅读。

需要Markdown或pdf版本,请联系我的邮箱。

因为此篇文章知识复杂,推荐使用Markdown版本观看,下面是Typora的Pixyll主题的观看效果:

Pixyll主题

1 日志

1.1 日志介绍

Server层:

  • 错误日志(wrong log):记录MySQL的启动、运行、关闭过程,能帮助定位MySQL问题。
  • 慢查询日志(slow query log):记录超过long_query_time时间的查询语句。
  • 一般查询日志(general log):记录了所有对MySQL数据库请求的信息,无论是否正确允许。
  • 二进制日志(binlog):记录数据库所有执行的DDL和DML语句(除了查询语句之外),以事件的形式记录并保存在二进制文件中。

InnoDB(存储引擎层):

  • 重做日志(redo log):记录了对于InnoDB存储引擎的事务日志。
  • 回滚日志(undo log):记录事务相反的更新语句(insert-delete;set-set),以便提供回滚操作;记录数据的版本日志,以便事务并发访问数据。

其中,binlog、redo log、undo log是非常重要且复杂的log,这一部分需要掌握。

1.2 binlog

ps:没有空格,不是bin log,而是binlog。

1.2.1 概念

binlog用于记录数据库执行的更新操作(不包括查询)信息,以二进制的形式保存在磁盘中。binlog是MySQL的逻辑日志,并由Server层进行记录,使用任何存储引擎都会记录binlog日志。

下面补充两个关键词:

  • 逻辑日志:可以简单理解为记录的就是sql语句。
  • 物理日志:因为MySQL数据最终是保存在数据页中的,物理日志记录的就是数据页变更。

binlog是通过追加的方式进行写入的,可以通过max_binlog_size参数设置每个binlog文件的大小,当文件大小达到给定值之后,会生成新的文件来保存日志。

1.2.2 使用场景

在实际应用中,binlog的主要使用场景有两个,分别是主从复制和数据恢复。

  • 主从复制:在Master端开启binlog,然后将binlog发送到各个Slave端,Slave端重放binlog从而达到主从数据一致。
  • 数据恢复:通过使用MySQLbinlog工具来恢复数据。
1.2.3 刷入磁盘时机

对于InnoDB存储引擎而言,只有在事务提交时才会记录binlog,此时记录还在内存中。MySQL通过sync_binlog参数控制binlog的刷盘时机,取值范围是0-N:

  • 0:不去强制要求,由系统自行判断何时写入磁盘;
  • 1:每次commit的时候都要将binlog写入磁盘;
  • N:每N个事务,才会将binlog写入磁盘。

sync_binlog最安全的是设置是1,这也是MySQL 5.7.7之后版本的默认值。但是设置一个大一些的值可以提升数据库性能,因此实际情况下也可以将值适当调大,牺牲一定的一致性来获取更好的性能。

1.2.4 日志格式

binlog日志有三种格式,分别为STATMENT、ROW和MIXED。

在 MySQL 5.7.7之前,默认的格式是STATEMENT,MySQL 5.7.7之后,默认值是ROW。日志格式通过binlog-format指定。

  • STATEMENT:基于SQL语句的复制(statement-based replication,SBR),每一条会修改数据的sql语句会记录到binlog中。
    • 优点:不需要记录每一行的变化,减少了binlog日志量,节约了IO, 从而提高了性能。
    • 缺点:在某些情况下会导致主从数据不一致,比如执行sysdate()、slepp()等。
  • ROW:基于行的复制(row-based replication,RBR),不记录每条sql语句的上下文信息,仅需记录哪条数据被修改后的信息。
    • 优点:不会出现某些特定情况下的存储过程、或function、或trigger的调用和触发无法被正确复制的问题。
    • 缺点:会产生大量的日志,尤其是alter table的时候会让日志暴涨。
  • MIXED:基于STATMENT和ROW两种模式的混合复制(mixed-based replication,MBR),一般的复制使用STATEMENT模式保存binlog,对于STATEMENT模式无法复制的操作使用ROW模式保存binlog。
1.2.5 默认情况下的运行逻辑

在默认情况下binlog的运行逻辑:

  • 事务提交时,从内存写入磁盘。
  • 记录每行数据修改后的数据。

1.3 redo log

1.3.1 概念

记录事务对数据页做了哪些修改,保证事务的持久性:只要事务提交成功,那么对数据库做的修改就被永久保存下来了,不可能因为任何原因再回到原来的状态。

1.3.2 组成部分

redo log包括两部分:

  • 内存中的日志缓冲(redo log buffer)
  • 磁盘上的日志文件(redo log file)

MySQL每执行一条DML语句,先将记录写入redo log buffer,后续某个时间点再一次性将多个操作记录写到redo log file。

1.3.3 作用

在InnoDB存储引擎中,当进行数据的修改(例如INSERT、UPDATE、DELETE等操作)时,这些修改不会立即写入磁盘上的数据文件。相反,修改会首先记录到redo log中,形成一条日志记录。这个过程称为“write-ahead logging”(预写式日志)。

redo log是一组预分配的文件,用来存储这些修改操作。写入redo log的过程是非常快速的,因为它是顺序写,而不是随机写。写入redo log的记录会被认为是已经持久化到磁盘上的,即使实际数据页上的修改尚未被写入磁盘。这样可以确保即使在数据库发生崩溃的情况下,通过重新应用redo log中的修改,可以将数据库恢复到崩溃前的状态。

随着时间的推移,InnoDB引擎会定期将内存中的脏页(已经被修改但尚未写入磁盘)刷入磁盘的数据文件。这个过程被称为Check Point。Check Point操作会将脏页中的修改写入到磁盘,同时也会更新数据页的LSN(Log Sequence Number),表示该数据页上次被修改的LSN。

在数据库恢复时,InnoDB引擎会比较数据页的LSN和事务日志中的LSN,如果数据页的LSN小于事务日志中的LSN,表示该数据页的修改尚未被写入磁盘。InnoDB会从这个LSN开始,将日志中的修改重新应用到数据文件上,以确保数据的一致性和持久性。这个过程确保了在数据库发生崩溃时,不会丢失已经提交的事务,并且可以通过redo log进行恢复。

下面有几个关键词进行补充:

  • LSN(Log Sequence Number):InnoDB存储引擎中用来标识事务日志中位置的唯一标识符。它并不代表事务的ID,而是一个递增的数字,用来表示日志中的顺序。
  • 数据页(data page):数据库中存储数据的基本单位。数据页是由数据库引擎管理的一块内存区域,也是数据在磁盘上的存储单元。
1.3.4 刷入磁盘时机

在计算机操作系统中,用户空间(user space)下的缓冲区数据一般情况下是无法直接写入磁盘的,中间必须经过操作系统内核空间(kernel space)缓冲区(OS Buffer)。因此,redo log buffer写入redo log file实际上是:1. 先写入OS Buffer;2. 通过系统调用fsync()将其刷到redo log file中。

mysql支持三种将redo log buffer写入redo log file的时机,可以通过InnoDB_flush_log_at_trx_commit参数配置,各参数值含义如下:

默认情况下是1。

  • 0(延迟写):事务提交时不会将redo log buffer中日志写入到os buffer,而是每秒写入os buffer并调用fsync()写入redo log file中。当系统崩溃,会丢失1秒钟的数据。
  • 1(实时写,实时刷入磁盘):事务每次提交都会将redo log buffer中的日志写入os buffer并调用fsync()刷到redo log file中。即使系统崩溃也不会丢失任何数据,因为每次提交都写入磁盘,IO的性能较差。
  • 2(实时写,延迟刷入磁盘):每次提交都仅写入到os buffer,然后是每秒调用fsync()将os buufer中的日志写入到redo log file。当系统崩溃,会丢失1秒钟的数据。
1.3.5 应用到数据页的方式

redo log只记录数据页的变更,而这种变更记录是没必要全部保存,因此redo log实现上采用了大小固定,循环写入的方式,当写到结尾时,会回到开头循环写日志。

img

在InnoDB中,不仅redo log需要刷盘,数据页也需要刷盘,redo log存在的意义主要就是降低对数据页刷盘的要求。在上图中,write pos表示redo log当前记录的LSN位置,check point表示数据页更改记录刷盘后对应redo log所处的LSN位置。

write pos到check point之间的部分是redo log空着的部分,用于记录新的记录;check point到write pos之间是redo log待刷入磁盘的数据页更改记录。当write pos追上check point时,会先推动check point向前移动多个单位,触发Check Point操作,空出位置再记录新的日志。

下面有三种情况:

  • 启动InnoDB时:不管上次是正常关闭还是异常关闭,总是会进行恢复操作。因为redo log记录的是数据页的物理变化,因此恢复的时候速度比逻辑日志(如binlog)要快很多。
  • 重启InnoDB时:首先会检查磁盘中数据页的LSN,如果数据页的LSN小于日志中的LSN,表示该数据页的修改尚未持久化到磁盘,InnoDB将会从该LSN开始,应用日志中的修改,以确保数据的一致性。
  • 在宕机前正处于Check Point的刷入磁盘过程,且数据页的刷盘进度超过了日志页的刷盘进度,此时会出现数据页中记录的LSN大于日志中的LSN,这时超出日志进度的部分将不会重做,因为这本身就表示已经做过的事情,无需再重做。

其中注意:

  • check point代表最后一个待刷入的LSN,Check Point代表Check Point操作。
  • “redo log信息刷入磁盘”和“redo log的Check Point修改数据页信息”是两个独立的线程,不会产生影响。前者将记录redo log写入磁盘,方便宕机恢复;后者将redo log记录的数据修改应用到数据页中,保证数据的同步。
  • 为了提高性能,通常会采用异步刷写的策略,即将脏页的写入和日志的刷写与用户操作进行异步处理。这样做的好处是能够提高数据库的响应速度,因为不需要等待每个修改都同步写入磁盘。但是,由于异步刷写的特性,系统中可能存在一定程度的数据延迟,即数据在内存中已经被修改,但尚未持久化到磁盘上。

1.4 undo log

1.4.1 概念

undo log是一种用于撤销回退的日志,在事务没提交之前,MySQL会先记录更新前的数据到undo log日志文件里面,当事务回滚时或者数据库崩溃时,可以利用undo log来进行回退。

1.4.2 作用

在MySQL中,undo log日志的作用主要有两个:

  1. 提供回滚操作
  2. 提供多版本控制(MVCC)

1. 提供回滚操作

在进行数据更新操作的时候,不仅会记录redo log,还会记录undo log,如果因为某些原因导致事务回滚,那么这个时候MySQL就要执行回滚(rollback)操作,利用undo log将数据恢复到事务开始之前的状态。

例如执行delete语句,那么undo log中就会插入一条insert语句:

DELETE FROM users WHERE id=1;
INSERT INTO users(id,...) VALUES(1,...);

例如执行set语句,那么undo log中就如追加一条set语句;

UPDATE users
SET user_name='NewName' 
WHERE id=1;
UPDATE users
SET user_name='OriginalName'
WHERE id=1;

当事务需要回滚时,则使用undo log日志来实现回滚操作,以保证事务的一致性。

2. 提供多版本控制(MVCC)

在MySQL数据库InnoDB存储引擎中,用undo log来实现MVCC(Multi-Version Concurrency Control)。当读取的某一行被其他事务锁定时,它可以从undo log中分析出该行记录以前的数据版本是怎样的,从而让用户能够读取到当前事务操作之前的数据,即快照读。主要用于实现事务的读取已提交和可重复读的隔离性。

下面给出了关键词的补充:

  • 快照读:SQL读取的数据是快照版本(可见版本),也就是历史版本,不用加锁,普通的SELECT就是快照读。
  • 当前读:SQL读取的数据是最新版本。通过锁机制来保证读取的数据无法通过其他事务进行修改,UPDATE | DELETE | INSERT | SELECT … LOCK IN SHARE MODESELECT … FOR UPDATE都是当前读。
1.4.3 存储机制

undo log分为两部分组成:

  • 回滚段(Rollback Segment):回滚段是一个逻辑概念,用于存储事务修改前的数据版本,以支持事务的回滚操作。每个事务开始时,都会分配一个或多个回滚段来保存它的修改。当事务执行修改操作时,InnoDB会将修改前的数据保存到回滚段中。如果事务需要回滚,可以通过回滚段中的数据将修改撤销,使数据恢复到事务开始之前的状态。
  • 回滚日志(Rollback Segment Log):回滚日志是用于记录回滚段中数据版本的变化历史。它是物理存储结构,记录了回滚段中的数据版本的变化。回滚日志是持久化的,确保了在数据库崩溃或异常关闭时,可以通过回滚日志将回滚段的数据版本恢复到正确的状态。

在MySQL5.5之前,只支持1个rollback segment,也就是只能记录1024个undo操作。在MySQL5.5之后,可以支持128个rollback segment,分别从resg slot0~resg slot127,每一个resg slot,也就是每一个回滚段,内部由1024个undo segment 组成,即总共可以记录128*1024个undo操作。

下面详细说明回滚日志的作用——实现事务的两个隔离性:读已提交、可重复读。

  • 读已提交:事务只能看到已经提交的其他事务所做的修改。换句话说,事务在查询数据时,只能看到已经被其他事务提交的修改。
  • 可重复读:事务可以多次读取同一行数据,而且保证这些读取结果是一致的。即使其他事务在事务执行期间对数据进行了修改,该事务在本次事务内读取的数据依然是一致的,不会受到其他事务的影响。

1. 隐藏列

对于InnoDB存储引擎,每一行记录都有两个隐藏列:

  • DB_TRX_ID:事务ID,修改时,都会把该事务ID复制给DB_TRX_ID。
  • DB_ROLL_PTR:回滚指针,指向上一版本的undo回滚日志。

2. 版本链

每当该行被更新时,会执行1. 将旧行数据放入到回滚日志中;2. 替换数据;3. 让该行的回滚指针指向旧数据。从而形成版本链。如图:

image-20230108100308210 image-20230108100317599 image-20230108100329010

3. ReadView

事务会在某个时期创建一个ReadView快照,里面记录了下面几个内容:

  • m_ids:表示在生成ReadView时当前系统中活跃的读写事务的事务id列表。
  • min_trx_id:表示在生成ReadView时当前系统中活跃 单独写十五中最小的事务id,也就是m_ids中的最小值。
  • max_trx_id:表示生成ReadView时系统中应该分配给下一个事务的id值。
  • creator_trx_id:表示生成该ReadView的事务的事务id。

4. 访问方式

当事务存在ReadView时,访问某条记录时,只需要按照以下步骤判断记录的某个版本是否可见:

  • 如果被访问版本的DB_TRX_ID属性值与ReadView中的creator_trx_id值相同,意味着当前事务在访问它修改过的记录,所以该版本可以被当前事务访问。
  • 如果被访问版本的DB_TRX_ID属性值小于ReadView中的min_trx_id值,表明生成该版本的事务在当前事务生成ReadView前以提交,所以该版本可以被当前事务访问。
  • 如果被访问版本的DB_TRX_ID属性值大于ReadView中的max_trx_id值,表名生成该版本的事务在当前事务生成ReadView后才开启,所以该版本不可以被当前事务访问。
  • 如果被访问版本的DB_TRX_ID属性值在ReadView的min_trx_id和max_trx_id之间,那就需要判断啊trx_id属性值是不是在m_ids列表中:
    • 如果在,说明创建ReadView时生成该版本的事务还是活跃的,该版本不可以被访问。
    • 如果不在,说明创建ReadView时生成该版本的事务已经被提交,该版本可以被访问。

如果某个版本的数据对当前事务不可以见的话,那就顺着版本链找到下一个版本的数据,继续按照上边的步骤判断可见性,一次退嘞,知道版本链中的最后一个版本。如果最后一个版本也不可见的话,那么就意味着该条记录对该事务完全不可见,查询结果就不包含该记录。

5. ReadView生成时间

在MySQL中,读已提交可重复读隔离级别的对应着不同的ReadView生成时间。

  • 读已提交:每次读取数据前都生成一个ReadView,这样就能确保自己每次都能读到其他事务提交的数据。
  • 可重复读:在第一次读取数据是生成一个ReadView,这样就能保证后续读取的结果完全一致。
1.4.4 擦除

undo log的擦除机制是InnoDb引擎后台完成的,无法由用户或者程序直接触发的。其机制是标志该undo log可复用,被标记为可复用的undo log空间可以被新的事务使用,以覆盖和存储新的undo log记录。

下面undo log会在下面情况下被标记可复用:

  • 事务提交后擦除:当事务提交时,undo log中该事务所产生的undo log记录将被标记为可复用,表示该部分undo log已经不再需要。这意味着undo log中被事务修改的数据版本已经不再被当前事务或其他事务需要,可以被覆盖和复用。
  • 事务回滚时擦除:如果事务被回滚,undo log中该事务产生的undo log记录也将不再需要,会被标记为可复用。
  • 自动擦除机制:InnoDB存储引擎会定期执行一个操作称为“Purge”(清理),该操作的任务之一是回收不再需要的undo log空间。Purge会扫描undo log,识别不再需要的undo log记录,并将这些记录标记为可复用。

1.5 更新语句的执行过程

1.5.1 大致过程

更新语句的执行过程

下面先用一个张图概览一下更新语句的执行流程,进一步来讲的话一条更新语句可以作为一个事务,也就是MySQL处理事务的流程。

在这里插入图片描述

ps:图片说明有瑕疵,在第13步的时候,不是提交数据后存入binlog cache,而是在事务进行的过程中不断的添加。

上面过程更加细化,能够了解binlog、redo log和undo log之间的协调。

1.5.2 redo log和binlog的过程

可以看到的是,redo log分为两个阶段:perpare阶段和commit阶段。下面是redo log和binlog的前后逻辑:

  1. 事务执行中:将数据的变更放入redo log中。
  2. redo log的刷入磁盘:对事务的数据的变更进行遍历
    1. 将数据的变更应用与磁盘中,持续化。
    2. 将redo log中的记录数据的变更的信息设置为perpare阶段。
    3. 将更新的行(或SQL语句)写入binlog中。
  3. 当事务进行提交:异步执行:
    1. 将redo log buffer全部刷入磁盘。
    2. 将redo log中的记录数据的变更的信息修改为commit阶段。
1.5.3 两阶段提交的作用

采用单阶段的方式不可以,最低方案就是两阶段提交。下面两个方案都会产生问题:

  • 先写入redo log,后写入binlog。
  • 先写入binlog,后写入redo log。

简单说,redo log和binlog都可以⽤于表⽰事务的提交状态,而分两阶段提交就是让这两个状态保逻辑上的⼀致。

2 锁

2.1 分类

MySQL的锁根据分类可以分为下面几种:

  • 锁粒度:
    • 表锁:开销小,加锁快;锁定力度大,发生锁冲突概率高,并发度最低;不会出现死锁。
    • 页锁:开销大,加锁慢;会出现死锁;锁定粒度小,发生锁冲突的概率低,并发度高。
    • 行锁:开销和加锁速度介于表锁和行锁之间;会出现死锁;锁定粒度介于表锁和行锁抓紧,并发度一般。
  • 锁模式:
    • 记录锁
    • 间隙锁
    • 临键锁
    • 意向锁
    • 插入意向锁
  • 兼容性:
    • 共享锁:也称读锁(Read Lock)。事务之间读数据相互不阻塞,但会阻塞事务写数据。
    • 排他锁:也称写锁(Write Lock)。加锁的事务在一定时间内,能执行写入,并阻止其他锁读取的该部分数据。
  • 加锁机制:
    • 乐观锁:认为数据的变动不大,不会产生较大并发问题,不会加入锁来限制读写。
    • 悲观锁:认为数据的变动非常频繁,会产生较大并发问题,会加入锁限制读写。

2.2 共享锁和排他锁

2.2.1 共享锁

共享锁(Shared Lock,S锁)代表多个事务对统一数据可以共享一把锁。

通过下面SQL语句实现共享锁:

SELECT ... LOCK IN SHARE MODE;

共享锁有下面的特点:

  • 多个事务的查询语句可以共用一把共享锁。
  • 如果只有一个事务拿到了共享锁,则该事务可以对数据进行UPDATE | DELETE操作。
  • 如果有多个事务拿到了共享锁,则所有事务都不能对数据进行UPDATE | DELETE操作。
2.2.2 排他锁

排他锁(Exclusive Lock,X锁)不能与其他锁并存,而且只有一个事物能拿到某一数据行的排他锁,其余事务不能再获取该数据行的所有锁。

通过下面SQL语句实现排他锁:

SELECT ... FOR UPDATE;

排他锁有下面的特点:

  • 只有一个事务能获取该数据的排他锁。
  • 一旦有一个事务获取了该数据的排他锁之后,其余事务对于改数据的操作将会被阻塞,直至锁被释放。
2.2.3 共享锁和排他锁的使用场景

共享锁:

  • 确保某个事务查到最新的数据。
  • 这个事务不需要对数据进行修改、删除等操作。
  • 不允许其他事务对数据进行修改、删除等操作。
  • 其他事务也能确保查到最新的数据。

排他锁:

  • 确保某个事务查到最新的数据。
  • 只有该事务能对数据进行修改、删除等操作。

2.3 行锁的实现

2.3.1 记录锁

记录锁(Record Lock)直接锁定某行的记录,当使用唯一性的索引时(包括唯一索引和聚簇索引)进行等值查询且精准匹配到一条记录时,此时会将这条记录锁定。

例如:

SELECT *
FROM users
WHERE id = 6
FOR UPDATE; -- 加X锁

此时id=6的行会被锁定。

2.3.2 间隙锁

间隙锁(Gap Lock)的间隙(Gap)是指两个记录之间逻辑上尚未填入数据的部分,是一个左开右开空间

间隙锁就是锁定某些间隙区间的。当使用值查询或范围查询,并且没有命中任何一个记录锁,此事就会将对应的间隙区间锁定。

例如:

SELECT *
FROM users
WHERE id > 1 
  AND id < 6 
FOR UPDATE; -- 加X锁

此时区间(1, 6)会被锁定。

2.3.3 临键锁

临键锁(Next-Key Lock)是间隙加上它右边的记录组成的左开右闭区间。例如区间(1, 6]。

临键锁就是记录锁和间隙锁的组合,即除了锁住记录本身,还要再锁住索引之间的间隙。当使用范围查询,并且命中了部分record记录,此时锁住的就是临键区间。

例如:

SELECT *
FROM users
WHERE id > 5
  AND is <= 7
FOR UPDATE; -- 加X锁

此时区间(4, 7]、[7, +∞)会被锁定。

当使⽤唯⼀性索引,等值查询匹配到⼀条记录的时候,临键锁会退化成记录锁;没有匹配到任何记录的时候,退化成间隙锁。

2.3.4 插入意向锁

一个事务在插入一条记录时,需要判断一下插入位置是不是被其他事务加了意向锁,如果有的话插入操作需要等待,直到拥有gap锁的那个事务提交。但是事务在等待的时候也需要在内存中生成一个锁结构,表名有个事务想在某个间隙中插入新纪录,但是在等待。这种类型的锁,命名为Insert Intention Locks,也就是插入意向锁。

假如现在有一个事务T1,给区间(1,6)加上了意向锁,现在有事务T2,要插入一个数据,id为4,他会获取到一个(1, 6)的插入意向锁,若又有一个事务T3,想要插入一个数据,id为3,它也会获取相同的插入意向锁。

2.4 意向锁

意向锁是一个表级锁,不同于插入意向锁。

意向锁的出现是为了支持InnoDB的多粒度锁,主要解决表锁和行锁并存的问题。

当需要给一个表加表锁时,需要去判断表中有没有数据行被锁定,已确定是否能加成功。

  • 假如没有意向锁的情况下,那么就需要遍历所有数据行来判断表中是否有数据行被锁定,已确定是否能加成功。
  • 有了意向锁的情况下,直接判定该表是否有意向锁就知道表中还是否有数据行被锁定了。

在有意向锁之后,要执行的事务A在执行锁(写锁)之前,数据库会自动先给事务A申请表的意向锁排他锁。当事务B去申请表的互斥锁时就会被阻塞。

2.5 乐观锁和悲观锁

2.5.1 悲观锁

悲观锁(Pessimistic Concurrency Control)认为被它保护的数据每时每刻都有可能被改动,一个事务拿到悲观锁后,其他任何事务都不能对该数据进行修改,只能等待锁被释放才可以进行。

数据库中的行锁、表锁、读锁、写锁均为悲观锁。

2.5.2 乐观锁

乐观锁(Optimistic Concurrency Control)认为数据的变动不会太频繁。

不同于悲观锁,乐观锁通常是由开发者实现的:通过在表中增加一个版本(version)或时间戳(timestamp)列来实现,其中版本最为常用。

事务在数据库中取数据时,会将该数据的版本也取出来(v1),当事务对数据变动完毕想要将其更新到表中时,会将之前去除的版本v1与数据中最新的版本v2相对比:

  • 如果v1=v2,那么说明在数据变动期间,没有其它事务对数据进行修改,此时,就允许事务对表中的数据进行修改,并且修改时version会加1,一次来表明数据已被更新。
  • 如果v1!=v2,那么说明数据变动期间,数据被其他事务改动了,此时不允许数据更新到表中,一般的处理办法是通知用户让其重新操作。

2.6 死锁

2.6.1 概念
  • 两个事务都持有对方需要的锁,并且在等待对方释放,并且双方都不会释放自己的锁。
  • InnoDB会自动检测事务死锁(wait-for graph机制),立即回滚其中某个事务,并且返回一个错误。它根据某种机制来选择那个最简单(代价最小)的事务来进行回滚
  • 偶然发生的死锁是正常的,但频繁出现死锁的时候就要引起注意。
  • InnoDB存储引擎有一个后台的锁监控线程,该线程负责查看可能的死锁问题,并自动告知用户。
2.6.2 查看方式
  • 在MySQL 5.6之前,只有最新的死锁信息可以使用show engine innodb status命令来进行查看。
  • 如果使用的是MySQL 5.6或以上版本,可以启用一个新增的参数innodb_print_all_deadlocks把InnoDB中发生的所有死锁信息都记录在错误日志里面。
2.6.3 为什么会形成死锁

产生死锁的必要条件:

  1. 多个并发事务(2个或者以上)
  2. 每个事务都持有锁(或者是已经在等待锁)
  3. 每个事务都需要再继续持有锁(为了完成事务逻辑,还必须更新更多的行)
  4. 事务之间产生加锁的循环等待,形成死锁
2.6.4 如何解决死锁

系统:InnoDB提供了wait-for graph算法来主动进行死锁检测,在每个事务请求锁并发生等到的时候都会判断是存在回路,若存在则有死锁。通常来说InnoDB选择回滚undo量最小的事务。

2.6.5 如何避免发生死锁

1. 锁机死锁信息

  • 查看死锁原因
  • 找出死锁SQL
  • 分析SQL加锁情况
  • 模拟死锁案发
  • 分析死锁日志
  • 分析死锁结果

2. 减少死锁

  • 使用事务,不使用表锁。
  • 保证没有长事务。
  • 尽量基于primary或unique key更新数据。
  • 操作完之后立即提交事务,特别是在交互式命令行中。
  • 如果在用SELECT … FOR UPDATE | SELECT … LOCK IN SHARE MODE,尝试降低隔离级别。
  • 修改多个表或者多个行的时候,将修改的顺序保持一致。
  • 创建索引,可以使创建的锁更少。
  • 最好不要用 SELECT … FOR UPDATE | SELECT … LOCK IN SHARE MODE。
  • 如果上述都无法解决问题,那么尝试使用 lock tables t1, t2, t3 锁多张表。
    若存在则有死锁。通常来说InnoDB选择回滚undo量最小的事务。
2.6.5 如何避免发生死锁

1. 锁机死锁信息

  • 查看死锁原因
  • 找出死锁SQL
  • 分析SQL加锁情况
  • 模拟死锁案发
  • 分析死锁日志
  • 分析死锁结果

2. 减少死锁

  • 使用事务,不使用表锁。
  • 保证没有长事务。
  • 尽量基于primary或unique key更新数据。
  • 操作完之后立即提交事务,特别是在交互式命令行中。
  • 如果在用SELECT … FOR UPDATE | SELECT … LOCK IN SHARE MODE,尝试降低隔离级别。
  • 修改多个表或者多个行的时候,将修改的顺序保持一致。
  • 创建索引,可以使创建的锁更少。
  • 最好不要用 SELECT … FOR UPDATE | SELECT … LOCK IN SHARE MODE。
  • 如果上述都无法解决问题,那么尝试使用 lock tables t1, t2, t3 锁多张表。
  • 16
    点赞
  • 15
    收藏
    觉得还不错? 一键收藏
  • 1
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论 1
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值