一篇文章带你了解清楚Mysql 锁

一丶为什么数据库需要锁

数据库锁设计的初衷是处理并发问题。作为多用户共享 的资源,当出现并发访问的时候,数据库需要合理地控制资源的访问规则。而锁就是用来实 现这些访问规则的重要数据结构。

根据加锁的范围,MySQL 里面的锁大致可以分成全局锁表级锁行锁三类

二丶全局锁&全库逻辑备份

全局锁就是对整个数据库实例加锁。全局锁的典型使用场景是,做全库逻辑备份,全库逻辑备份有以下几种方式:

1.Flush tables with read lock (FTWRL)

Flush tables with read lock (FTWRL)可以让整个库处于只读状态的时候,使用这个命令,之后其他线程的数据更新语句(数据的增删改)、数据定义语句(包括建表、修改表结构等)和更新类事务的提交语句讲被阻塞。之所以备份需要加全局锁是为了保证数据的一致性,

2.mysqldump –single-transaction

使用此命令会先启动一个事务拿到一致性视图。由于 MVCC 的支持, 这个过程中其他线程可以进行正常操作,但是使用mysqldump –single-transaction的前提是需要支持事务,如果存在MyISAM引擎的表,并不能保证一致性。

3.set global readonly=true

set global readonly=true也可以保证全库只读,主要有两个原因:

在有些系统中,readonly 的值会被用来做其他逻辑,比如用来判断一个库是主库还是备库。因此,修改 global 变量的方式影响面更大。 在异常处理机制上有差异。如果执行 FTWRL 命令之后由于客户端发生异常断开, 那么 MySQL 会自动释放这个全局锁,整个库回到可以正常更新的状态。而将整个库设置为 readonly 之后,如果客户端发生异常,则数据库就会一直保持 readonly 状态,这样会导致整个库长时间处于不可写状态,风险较高。

三丶锁解决并发事务带来的问题

1.锁解决脏写

多个未提交事务修改相继对同一条记录进行改动的时候,需要进行排队,排队过程其实就是通过为记录加锁实现的。当一个事务想修改记录的时候,首先需要看下有没有于记录相关联的锁结构,如果没有,那么会在内存中生成与之对应的锁结构

可能多个事务同时修改同一记录,会产生多个锁结构,其中只有一个事务可以获取到锁,其is_waiting为false,其他事务is_waiting为true,当获取锁的事务结束后,会唤醒其他等待的事务

2.脏读,幻读,不可重复读如何避免

mysql innodb 在repeatable隔离级别下很大程度下避免了幻读(后续详细说到)

2.1 mvcc解决脏读,幻读,不可重复读问题

Mysql InnoDB多版本并发控制MVCC这篇博客我们详细说到了mvcc的实现的原理,简单来说就是在事务运行时会生成read view,其包含四个部分

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

查询语句只可以查询生成read view 时刻可以看到的数据,及事务id小于min_trx_id中的数据,写操作则针对最新的数据。非当前读的普通语句在读数据的时候,生成read view的时机不同,在可重复读的时候只会在第一次读取(如果使用start transaction with consistent snapshot则是在事务启动时)生成一个read view 后续不变,达到可重复读的目的,对于读已提交,则是每次读取都会生成新的read view,从刷新未提交事务集合,和min_trx_id,读取到已经提交的数据。使用mvcc让读写并不冲突,数据库的并发能力更强。

2.2读写均加锁

比如在银行存款业务中,我们需要先读取账户余额,然后加上新增存款,然后进行写回操作,整个流程中,我们不希望存在另外一个ATM进行存取的操作,读写都需要排队,这时候就得使用锁。

上面我们说了,写操作进行排队可以解决脏写,那么脏读,不可重复读,幻读是怎么使用读写加锁解决昵?

脏读产生的原因是,当前事务读取到了另外一个事务没有提交的数据,那么只需要另外一个事务对操作的记录加锁,当前事务无法获取到锁,自然就不会发生脏读。

不可重复读产生的原因是,当前事务先读取了一条记录,然后存在另外一个事务修改了此记录的数据,那么只要当前事务对记录进行加锁,自然后续的事务将无法修改,自然不会发生不可重复读。

幻读产生的原因是,当前事务根据条件查询得到一批数据,然后后续事务新增了满足这些条件的数据,导致再次查询时发现多了一些数据,如同出现了幻觉。这里加锁则不是单对记录加锁,而是要锁住一个范围,让其他事务无法插入数据,从而解决幻读(后续会说到这种锁)

很明显读写都加锁,并发能力不及mvcc

3.一致性读

事务利用MVCC进行读取操作称为一致性读(又称一致性无锁读,快照读),基本上所有的普通读在可重复读,读提交隔离级别下,都是一致性读。

4.锁定读

4.1共享锁&独占锁

共享锁:简称S锁,事务要读取一条记录的时候需要先获取到记录的共享锁

独占锁:简称x锁,当事务需要改动记录的时候,需要先获取记录的独占锁

共享锁和共享锁兼容,独占锁和独占锁,独占锁和共享锁互斥

4.2锁定读的语句

select xxx lock in share mode可以对读取的记录加S锁

select xxx for update可以对读取的记录加X锁

4.3 写操作

delete

删除记录首先要在b+数中定位到这条记录的位置,然后获取x锁,然后指向delete mark(标记记录被删除)

update

如果未修改键值,并且修改前后数据的存储空间大小不变,那么现在b+树上定位记录,然后加x锁。反之需要在b+树上定位数据,然后把记录彻底删除吗,然后再插入一条新的记录,对新增的这条数据加x锁

insert

新插入记录一般都是加隐式锁(后面说)不需要在内存中生成对应的锁结构。

三丶InnoDB表锁

1.表级S,X锁

innodb支持表级锁,也支持行级锁,表级锁粒度相对更粗,占用资源较少。使用表级锁效果相当于为表中的所有记录加锁,所以性能比较差。

表级S锁,X锁

使用Lock Tables t Read,innodb存储引擎会对表t加共享锁

使用Lock tables t write,innodb存储引擎会对表t加独占锁

类似于Java中的读写锁,共享锁和共享锁不互斥,独占锁和独占锁,独占锁和共享锁互斥。

2.表级意向锁

innodb存储引擎中,当对表中某些记录加S锁之前,会在表上加上一个IS锁,同样加X锁之前会加表级IX锁,这里的I表示意向锁,SX表示共享还是互斥,表级意向锁存在的目的是后续对表加S锁,X锁的时候,可以快速判断表中是否存在加锁的记录,避免遍历每一个记录查看是否被加锁。

3.表级AUTO-INC锁

mysql可以为某列执行Auto Increment自增,系统给自增列赋值的实现方式主要存在两种

使用AUTO-INC锁,执行插入语句的时候加一个表级AUTO-INC锁,然后为每条待插入的记录中的自增列,进行递增赋值,单个插入语句执行结束后释放AUTO-INC锁。这样会导致其他事务的插入被阻塞。 采用轻量级AUTO-INC锁,在为插入列赋值结束后,就释放轻量级锁,而不是插入语句执行完后才释放 四.MDL

一般情况下 对某一个表执行增删改查的时候,都不会加表锁,但是执行一些DDL修改表结构,删除表时,其他事务对这表的增删改查发生阻塞。这是由MDL锁实现的,MDL锁也分为读锁和写锁,在进行crud操作的时候,会加MDL读锁,进行DDL的时候会加MDL写锁。

我们需要注意MDL读锁写锁是互斥的

如图四个不同的session先后依次执行语句,其中A,B都是获取MDL读锁,互不阻塞,随即C获取MDL写锁,这时候C会被阻塞,这一阻塞不打紧,还会阻塞后续获取MDL读锁的事务,造成整个表不可用。这启发了我们,在做DDL的时候要解决长事务,事务不提交,就会一直占着MDL锁。在 MySQL 的 information_schema 库的innodb_trx 表中,可以查到当前执行中的事务。如果要做 DDL 变更的表刚好有长事务在执行,要考虑先暂停 DDL,或者 kill 掉这个长事务。

五丶innodb 行锁

1.Record Lock

官方名称Lock_REC_NOT_GAP

记录锁有S锁和X锁,S型记录锁之间可以共享,X型记录锁和S型记录锁,X型记录锁互斥

2.GAP Lock

innodb的可重复读级别,使用词锁解决幻读问题,前面我们说过,其难点在于,加锁的时候幻影记录还未出现。官方使用Lock_GAP实现如下操作

此处的gap锁可以反之其他事务在number为8记录前面的间隙插入新的记录,在区间(3,8)内无法进行插入操作,当另外一个事务要插入number为4的记录时,首先需要定位到该条记录的下一条记录,也就是number为8的记录,此时number为8的记录具备gap锁,所以将阻塞插入操作,直到gap锁被释放,其他事务才能进行插入。
gap锁出现的目的,就是为了防止插入幻影记录,如果对记录上gap锁,并不会限制其他事务对记录加记录锁

innodb有两个虚拟的记录Infimum(虚拟最小),Supermun(虚拟最大)当我们想在(xx,正无穷)范围锁住幻影记录时就可以对Supermun加gap锁。

3.Next-Key Lock

Next-Key Lock = 记录锁 + gap锁,既锁住记录,也锁住记录之前的间隙

4.Insert Intention Lock

插入意向锁,表示事务想在某个间隙插入新的记录,但是当前处于等待状态。

比如事务A持有(4,8)范围内的gap锁,事务B和C,想插入(4,8)范围内的记录,就会在内存中生成事务B,C对应的插入意向锁,当前事务A释放gap锁的时候,将唤醒事务B和C,事务B和C可以同时获取插入意向锁,然后进行插入。插入意向锁并不会阻止对记录继续上锁。

5.隐式锁

为事务生成内存中的锁结构并不是一个0成本的事情,为了节省这个成本,提出隐式锁的概念。

当一个事务插入语一条记录A,其他事务

select xxx Lock in share mode读取记录A(获取记录A的S锁),或者使用select xxx for update(获取记录A的X锁) 立即修改记录A(获取x锁)

对于聚簇索引来说,有一个隐藏列trx_id此列存储着最后更改记录的事务id,在当前事务A插入记录后,便是存储着当前事务A的id,其他事务B企图获取x锁,s锁的时候,就需要下先看一下,trx_id隐藏列对应的事务是否存活,如果不是那么正常获取,反之需要为当前事务A创建一个x锁内存结构,并标记is_waiting为false,然后事务B将为自己创建一个锁结构,is_waiting 为true然后事务B进入等待状态

对于二级索引来说,其不具备隐藏列trx_id但是在二级索引页面的page header中的page_maxt_trx_id属性,记录了改动页面最大的事务id,如果其属性值小于当前最小的活跃事务id,那么说明对页面的改动事务已经提交,否则需要定位到二级索引记录,然后回表对聚簇索引进行上述聚簇索引的操作。

一个事务对新插入的记录不需要显示的加锁,由于事务id的存在相当于加了一个隐式锁,别的事务需要加S锁或者X锁的时候,先帮之前的事务生成锁结构,然后为自己生成锁结构,再进入阻塞状态。隐式锁起到了延迟加锁的作用,也许别的事务不会获取于隐式锁冲突的锁,这时候可以减少内存中生成锁结构。

到此这篇关于一篇文章带你了解清楚Mysql 锁的文章就介绍到这了,更多相关Mysql 锁详解内容请搜索我们以前的文章或继续浏览下面的相关文章希望大家以后多多支持我们!

(0)

相关推荐

  • mysql锁表和解锁语句分享

    页级的典型代表引擎为BDB. 表级的典型代表引擎为MyISAM,MEMORY以及很久以前的ISAM. 行级的典型代表引擎为INNODB. -我们实际应用中用的最多的就是行锁. 行级锁的优点如下: 1).当很多连接分别进行不同的查询时减小LOCK状态. 2).如果出现异常,可以减少数据的丢失.因为一次可以只回滚一行或者几行少量的数据. 行级锁的缺点如下: 1).比页级锁和表级锁要占用更多的内存. 2).进行查询时比页级锁和表级锁需要的I/O要多,所以我们经常把行级锁用在写操作而不是读操作. 3).

  • MYSQL锁表问题的解决方法

    本文实例讲述了MYSQL锁表问题的解决方法.分享给大家供大家参考,具体如下: 很多时候!一不小心就锁表!这里讲解决锁表终极方法! 案例一 mysql>show processlist; 参看sql语句 一般少的话 mysql>kill thread_id; 就可以解决了 kill掉第一个锁表的进程, 依然没有改善. 既然不改善, 咱们就想办法将所有锁表的进程kill掉吧, 简单的脚本如下. #!/bin/bash mysql - u root - e " show processli

  • Mysql数据库锁定机制详细介绍

    前言 为了保证数据的一致完整性,任何一个数据库都存在锁定机制.锁定机制的优劣直接应想到一个数据库系统的并发处理能力和性能,所以锁定机制的实现也就成为了各种数据库的核心技术之一.本章将对MySQL中两种使用最为频繁的存储引擎MyISAM和Innodb各自的锁定机制进行较为详细的分析. MySQL锁定机制简介 数据库锁定机制简单来说就是数据库为了保证数据的一致性而使各种共享资源在被并发访问访问变得有序所设计的一种规则.对于任何一种数据库来说都需要有相应的锁定机制,所以MySQL自然也不能例外.MyS

  • Mysql使用kill命令解决死锁问题(杀死某条正在执行的sql语句)

    在使用mysql运行某些语句时,会因数据量太大而导致死锁,没有反映.这个时候,就需要kill掉某个正在消耗资源的query语句即可, KILL命令的语法格式如下: KILL [CONNECTION | QUERY] thread_id 每个与mysqld的连接都在一个独立的线程里运行,您可以使用SHOW PROCESSLIST语句查看哪些线程正在运行,并使用KILL thread_id语句终止一个线程. KILL允许自选的CONNECTION或QUERY修改符:KILL CONNECTION与不

  • MySQL Innodb表导致死锁日志情况分析与归纳

    案例描述在定时脚本运行过程中,发现当备份表格的sql语句与删除该表部分数据的sql语句同时运行时,mysql会检测出死锁,并打印出日志.两个sql语句如下:(1)insert into backup_table select * from source_table(2)DELETE FROM source_table WHERE Id>5 AND titleWeight<32768 AND joinTime<'$daysago_1week'teamUser表的表结构如下:PRIMARY

  • 详解MySQL中的死锁情况以及对死锁的处理方法

    当多个事务同时持有和请求同一资源上的锁而产生循环依赖的时候就产生了死锁.死锁发生在事务试图以不同的顺序锁定资源.以StockPrice表上的两个事务为例: 事务1 START TRANSACTION; UPDATE StockPrice SET close = 45.50 WHERE stock_id = 4 and date = '2002-05-01'; UPDATE StockPrice SET close = 19.80 WHERE stock_id = 3 and date = '20

  • mysql 数据库死锁原因及解决办法

    死锁(Deadlock) 所谓死锁:是指两个或两个以上的进程在执行过程中,因争夺资源而造成的一种互相等待的现象,若无外力作用,它们都将无法推进下去.此时称系统处于死锁状态或系统产生了死锁,这些永远在互相等待的进程称为死锁进程.由于资源占用是互斥的,当某个进程提出申请资源后,使得有关进程在无外力协助下,永远分配不到必需的资源而无法继续运行,这就产生了一种特殊现象死锁. 一种情形,此时执行程序中两个或多个线程发生永久堵塞(等待),每个线程都在等待被其他线程占用并堵塞了的资源.例如,如果线程A锁住了记

  • 通过mysql show processlist 命令检查mysql锁的方法

    processlist命令的输出结果显示了有哪些线程在运行,可以帮助识别出有问题的查询语句,两种方式使用这个命令. 1.      进入mysql/bin目录下输入mysqladmin processlist; 2.      启动mysql,输入show processlist; 如果有SUPER权限,则可以看到全部的线程,否则,只能看到自己发起的线程(这是指,当前对应的MySQL帐户运行的线程). 得到数据形式如下(只截取了三条): mysql> show processlist; +---

  • MySQL行级锁、表级锁、页级锁详细介绍

    页级:引擎 BDB.表级:引擎 MyISAM , 理解为锁住整个表,可以同时读,写不行行级:引擎 INNODB , 单独的一行记录加锁 表级,直接锁定整张表,在你锁定期间,其它进程无法对该表进行写操作.如果你是写锁,则其它进程则读也不允许行级,,仅对指定的记录进行加锁,这样其它进程还是可以对同一个表中的其它记录进行操作.页级,表级锁速度快,但冲突多,行级冲突少,但速度慢.所以取了折衷的页级,一次锁定相邻的一组记录. MySQL 5.1支持对MyISAM和MEMORY表进行表级锁定,对BDB表进行

  • 一次Mysql死锁排查过程的全纪录

    前言 之前接触到的数据库死锁,都是批量更新时加锁顺序不一致而导致的死锁,但是上周却遇到了一个很难理解的死锁.借着这个机会又重新学习了一下mysql的死锁知识以及常见的死锁场景.在多方调研以及和同事们的讨论下终于发现了这个死锁问题的成因,收获颇多.虽然是后端程序员,我们不需要像DBA一样深入地去分析与锁相关的源码,但是如果我们能够掌握基本的死锁排查方法,对我们的日常开发还是大有裨益的. PS:本文不会介绍死锁的基本知识,mysql的加锁原理可以参考本文的参考资料提供的链接. 死锁起因 先介绍一下数

随机推荐