二.MySQL锁机制详解-创新互联

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

在云梦等地区,都构建了全面的区域性战略布局,加强发展的系统性、市场前瞻性、产品创新能力,以专注、极致的服务理念,为客户提供成都做网站、网站建设 网站设计制作定制网站设计,公司网站建设,企业网站建设,成都品牌网站建设,成都全网营销推广,成都外贸网站制作,云梦网站建设费用合理。全局锁

全局锁就是对整个数据库实例加锁。 MySQL提供了一个加全局读锁的方法,命令是Flush tables with read lock (FTWRL)。当你需要让整个库处于只读状态的时候,可以使用这个命令,之后其他线程的以下语句会被阻塞:数据更新语句(数据的增删改)、数据定义语句(包括建表、修改表结构等)和更新类事务的提交语句。
全局锁的典型使用场景是,做全库逻辑备份。也就是把整库每个表都select出来存成文本。
然而加全局锁会有许多问题:

  • 如果你在主库上备份,那么在备份期间都不能执行更新,业务基本上就得停摆;
  • 如果你在从库上备份,那么备份期间从库不能执行主库同步过来的binlog,会导致主从延迟。

然而为了避免备份数据与原数据不同,在备份期间加锁是必要的。在InnoDB引擎中逻辑备份工具是mysqldump。当mysqldump使用参数–single-transaction的时候,导出数据之前就会启动一个事务,来确保拿到一致性视图。而由于MVCC的支持,这个过程中数据是可以正常更新的。

注意:single-transaction方法只适用于所有的表使用事务引擎的库。如果有的表使用了不
支持事务的引擎,那么备份就只能通过FTWRL方法。

表级锁

MySQL里面表级别的锁有两种:一种是表锁,一种是元数据锁(meta data lock, MDL)。
锁的语法是locktables…read/write。与FTWRL类似,可以用unlock tables主动释放锁,
也可以在客户端断开的时候自动释放。

另一类表级的锁是MDL(metadata lock)。MDL不需要显式使用,在访问一个表的时候会被自动加上。 MDL的作用是,保证读写的正确性。你可以想象一下,如果一个查询正在遍历一个表中的数据,而执行期间另一个线程对这个表结构做变更,删了一列,那么查询线程拿到的结果跟表结构对不上,肯定是不行的。

当对一个表做增删改查操作的时候,加MDL读锁;当要对表做结构变更操作的时候,加MDL写锁。

表锁一般是在数据库引擎不支持行锁的时候才会被用到的。如果你发现你的应用程序里有lock
tables这样的语句,你需要追查一下,比较可能的情况是:

  • 要么是你的系统现在还在用 MyISAM这类不支持事务的引擎,那要安排升级换引擎;
  • 要么是你的引擎升级了,但是代码还没升级。我见过这样的情况,最后业务开发就是把lock tables 和 unlock tables 改成 begin 和 commit,问题就解决了。
行锁

MySQL的行锁是在引擎层由各个引擎自己实现的。但并不是所有的引擎都支持行锁,比如
MyISAM引擎就不支持行锁。不支持行锁意味着并发控制只能使用表锁,对于这种引擎的表,同
一张表上任何时刻只能有一个更新在执行,这就会影响到业务并发度。 InnoDB是支持行锁的,
这也是MyISAM被InnoDB替代的重要原因之一。

两阶段锁协议

在InnoDB事务中,行锁是在需要的时候才加上的,但并不是不需要了就立刻释,而是要等到事务结束时才释放。这个就是两阶段锁协议。
知道了这个设定,对我们使用事务有什么帮助呢?那就是, 如果你的事务中需要锁多个行,要把
最可能造成锁冲突、最可能影响并发度的锁尽量往后放。 

行级锁的使用有什么注意事项?

InnoDB 的⾏锁是针对索引字段加的锁,表级锁是针对⾮索引字段加的锁。当我们执⾏UPDATE 、 DELETE 语句时,如果 WHERE 条件中字段没有命中唯⼀索引或者索引失效的话,就会导致扫描全表对表中的所有⾏记录进⾏加锁。这个在我们⽇常⼯作开发中经常会遇到,⼀定要多多注意!!!
不过,很多时候即使⽤了索引也有可能会⾛全表扫描,这是因为 MySQL 优化器的原因。

意向锁

如果需要⽤到表锁的话,如何判断表中的记录没有⾏锁呢?⼀⾏⼀⾏遍历肯定是不⾏,性能太差。我们需要⽤到⼀个叫做意向锁的东东来快速判断是否可以对某个表使⽤表锁。
意向锁是表级锁,共有两种:

  • 意向共享锁(Intention Shared Lock, IS 锁) :事务有意向对表中的某些加共享锁(S 锁),加共享锁前必须先取得该表的 IS 锁。
  • 意向排他锁(Intention Exclusive Lock, IX 锁) :事务有意向对表中的某些记录加排他锁(X锁),加排他锁之前必须先取得该表的 IX 锁。

意向锁是有数据引擎⾃⼰维护的,⽤户⽆法⼿动操作意向锁,在为数据⾏加共享 / 排他锁之前,
InnoDB 会先获取该数据⾏所在在数据表的对应意向锁。

意向锁与S锁/X锁的互斥关系

即对同一对象加S/X锁时,若该对象上已有意向锁且与之互斥,那么就会阻塞。

MVCC

全称Multi-Version Concurrency Control,即多版本并发控制,主要是为了提高数据库的并发性能。同一行数据平时发生读写请求时,会上锁阻塞住。但mvcc用更好的方式去处理读—写请求,做到在发生读—写请求冲突时不用加锁。这个读是指的快照读,而不是当前读,当前读是一种加锁操作,是悲观锁。MVCC是“维持一个数据的多个版本,使读写操作没有冲突”的一个抽象概念

当前读

它读取的数据库记录,都是当前最新版本,会对当前读取的数据进行加锁,防止其他事务修改数据。是悲观锁的一种操作。

如下操作都是当前读:(可以发现,增删改查操作中,只有普通查询操作不是当前读)

  • select lock in share mode (共享锁)

  • select for update (排他锁)

  • update (排他锁)

  • insert (排他锁)

  • delete (排他锁)

  • 串行化事务隔离级别

快照读

快照读的实现是基于多版本并发控制,即MVCC,既然是多版本,那么快照读读到的数据不一定是当前最新的数据,有可能是之前历史版本的数据。

如下操作是快照读:

  • 不加锁的select操作(注:事务级别不是串行化)
MVCC解决并发哪些问题?

mvcc用来解决读—写冲突的无锁并发控制,就是为事务分配单向增长时间戳。为每个数据修改保存一个版本,版本与事务时间戳相关联

读操作只读取该事务开始前数据库快照

解决问题如下:

  • 并发读-写时:可以做到读操作不阻塞写操作,同时写操作也不会阻塞读操作。

  • 解决脏读幻读不可重复读等事务隔离问题,但不能解决写-写 更新丢失问题。

因此有了下面提高并发性能的组合拳

  • MVCC + 悲观锁:MVCC解决读写冲突,悲观锁解决写写冲突

  • MVCC + 乐观锁:MVCC解决读写冲突,乐观锁解决写写冲突

MVCC的实现原理

它的实现原理主要是版本链undo日志Read View来实现的

版本链

我们数据库中的每行数据,除了我们肉眼看见的数据,还有几个隐藏字段,分别是db_trx_iddb_roll_pointerdb_row_id

  • db_trx_id

    6byte,最近修改(修改/插入)事务ID:记录创建这条记录/最后一次修改该记录的事务ID

  • db_roll_pointer(版本链关键)

    7byte,回滚指针,指向这条记录上一个版本(存储于rollback segment里)

  • db_row_id

    6byte,隐含的自增ID(隐藏主键),如果数据表没有主键,InnoDB会自动以db_row_id产生一个聚簇索引

  • 实际还有一个删除flag隐藏字段, 记录被更新删除并不代表真的删除,而是删除flag变了

每次对数据库记录进行改动,都会记录一条undo日志,每条undo日志也都有一个roll_pointer属性(INSERT操作对应的undo日志没有该属性,因为该记录并没有更早的版本),可以将这些undo日志都连起来串成一个链表,所以现在的情况就像下图一样:

对该记录每次更新后,都会将旧值放到一条undo日志中,就算是该记录的一个旧版本,随着更新次数的增多,所有的版本都会被roll_pointer属性连接成一个链表,我们把这个链表称之为版本链,版本链的头节点就是当前记录最新的值。另外,每个版本中还包含生成该版本时对应的事务id,这个信息很重要,在根据ReadView判断版本可见性的时候会用到。

Read View(读视图)

事务进行快照读操作的时候生产的读视图(Read View),在该事务执行的快照读的那一刻,会生成数据库系统当前的一个快照

记录并维护系统当前活跃事务的ID(没有commit,当每个事务开启时,都会被分配一个ID, 这个ID是递增的,所以越新的事务,ID值越大),是系统中当前不应该被本事务看到的其他事务id列表

Read View主要是用来做可见性判断的, 即当我们某个事务执行快照读的时候,对该记录创建一个Read View读视图,把它比作条件用来判断当前事务能够看到哪个版本的数据,既可能是当前最新的数据,也有可能是该行记录的undo log里面的某个版本的数据。

Read View几个属性

  • trx_ids: 当前系统活跃(未提交)事务版本号集合。

  • low_limit_id: 创建当前read view 时“当前系统大事务版本号+1”。

  • up_limit_id: 创建当前read view 时“系统正处于活跃事务最小版本号

  • creator_trx_id: 创建当前read view的事务版本号;

在实现上, InnoDB为每个事务构造了一个数组,用来保存这个事务启动瞬间,当前正在“活
跃”的所有事务ID。 “活跃”指的就是,启动了但还没提交。
数组里面事务ID的最小值记为低水位,当前系统里面已经创建过的事务ID的大值加1记为高水
位。
这个视图数组和高水位,就组成了当前事务的一致性视图(read-view)。
而数据版本的可见性规则,就是基于数据的row trx_id和这个一致性视图的对比结果得到的。
这个视图数组把所有的row trx_id 分成了几种不同的情况。

这样,对于当前事务的启动瞬间来说,一个数据版本的row trx_id,有以下几种可能:
1. 如果落在绿色部分,表示这个版本是已提交的事务或者是当前事务自己生成的,这个数据是
可见的;
2. 如果落在红色部分,表示这个版本是由将来启动的事务生成的,是肯定不可见的;
3. 如果落在黄色部分,那就包括两种情况
a. 若 row trx_id在数组中,表示这个版本是由还没提交的事务生成的,不可见;
b. 若 row trx_id不在数组中,表示这个版本是已经提交了的事务生成的,可见。

MVCC和事务隔离级别

上面所讲的Read View用于支持RC(Read Committed,读提交)和RR(Repeatable Read,可重复读)隔离级别实现

  • 在可重复读隔离级别(RR)下,只需要在事务开始的时候创建一致性视图,之后事务里的其他查询都共用这个一致性视图;
  • 在读提交隔离级别(RC)下,每一个语句执行前都会重新算出一个新的视图。
  • 在读提交隔离级别(RC)下的事务中,每次快照读都会新生成一个快照和Read View, 这就是我们在RC级别下的事务中可以看到别的事务提交的更新的原因

总结:

从以上的描述中我们可以看出来,所谓的MVCC指的就是在使用READ COMMITTDREPEATABLE READ这两种隔离级别的事务在执行普通的SEELCT操作时访问记录的版本链的过程,这样子可以使不同事务的读-写写-读操作并发执行,从而提升系统性能(而不是依靠行锁)

你是否还在寻找稳定的海外服务器提供商?创新互联www.cdcxhl.cn海外机房具备T级流量清洗系统配攻击溯源,准确流量调度确保服务器高可用性,企业级服务器适合批量采购,新人活动首月15元起,快前往官网查看详情吧


当前文章:二.MySQL锁机制详解-创新互联
标题网址:http://ybzwz.com/article/dhoppp.html