MySQL锁介绍

目录

一、前言

二、MySQL锁概述

1、引擎不同,锁的类型不同

2、锁的特性

三、全局锁

1、什么是全局锁?

2、使用全局锁的场景

3、为什么要使用全局锁?

4、如何解决视图逻辑不一致的问题?

5、全库只读设置方法的比较

四、mysql表锁

1、元数据锁

2、如何安全的给表加资源?

五、行级锁

1、什么是行级锁?

2、两阶段锁协议

3、两阶段锁在事务上的帮助

六、总结


一、前言

锁是计算机协调多个进程或线程并发访问某一资源的机制。在数据库中,除传统的计算资源(如CPU、RAM、I/O等)的争用以外,数据也是一种供许多用户共享的资源。如何保证数据并发访问的一致性、有效性是所有数据库必须解决的一个问题,锁冲突也是影响数据库并发访问性能的一个重要因素。从这个角度来说,锁对数据库而言显得尤其重要,也更加复杂。

二、MySQL锁概述

1、引擎不同,锁的类型不同

相对其他数据库而言,MySQL的锁机制比较简单,其最显著的特点是不同的存储引擎支持不同的锁机制。

1)MyISAM和MEMORY存储引擎采用的是表级锁(table-level locking);

2)BDB存储引擎采用的是页面锁(page-level locking),但也支持表级锁;

3)InnoDB存储引擎既支持行级锁(row-level locking),也支持表级锁,但默认情况下是采用行级锁。

BDB已经被InnoDB取代,即将成为历史,在此就不做进一步的讨论了

2、锁的特性

MySQL这3种锁的特性可大致归纳如下。

开销、加锁速度、死锁、粒度、并发性能

1、表级锁:开销小,加锁快;不会出现死锁;锁定粒度大,发生锁冲突的概率最高,并发度最低。

2、行级锁:开销大,加锁慢;会出现死锁;锁定粒度最小,发生锁冲突的概率最低,并发度也最高。

3、页锁:开销和加锁时间界于表锁和行锁之间;会出现死锁;锁定粒度界于表锁和行锁之间,并发度一般。

从上述特点可见,很难笼统地说哪种锁更好,只能就具体应用的特点来说哪种锁更合适!仅从锁的角度来说:

1)表级锁更适合于以查询为主,只有少量按索引条件更新数据的应用,如Web应用;

2)而行级锁则更适合于有大量按索引条件并发更新少量不同数据,同时又有并发查询的应用,如一些在线事务处理(OLTP)系统。

3、锁的分类

mysql中的锁在不同维度分类不一样,以下图整理了整体的锁分类

 

三、全局锁

1、什么是全局锁?

全局锁会让整个库处于只读状态,其他线程语句(DML,DDL,更新事务类)的语句都被会阻塞。

2、使用全局锁的场景

在做全库逻辑备份时,会把整库进行 select 然后保存成文本。

3、为什么要使用全局锁?

想象这样一个场景,要备份一个购买系统,其中购买操作设计到更新账号余额表和用户课程表。

现在进行逻辑备份,在备份过程中,一位用户购买了一门课程,这时需要在余额表扣掉余额,然后在购买的课程中加上一门课。正确的顺序肯定是先进行购买操作,减少余额和增加课程然后在进行备份。但却有可能出现这样的问题:

如果在时间顺序上先备份余额表 (u_account),然后用户购买(操作两张表),再备份用户课程表(u_course)?

这时用备份的数据做恢复时,会发现用户没花钱却买了一堂课。原因在于,先备份余额表,说明用户余额不变。之后才进行购买操作,余额表减钱,课程表增加一门课程。接着备份课程表,课程表课程加一。购买操作在已经备份完的余额表后进行。

如果在时间顺序上先备份用户课程表(u_course),然后用户购买(操作两张表),再备份余额表 (u_account)?

同样的,如果先备份课程表,课程没有增加,因为没有进行购买操作。之后进行购买操作后,余额表减钱,然后被备份。就出现了,用户花钱却没有购买成功的情况。

也就是说,不加锁的话,备份系统的得到的库不是一个逻辑时间点,这个视图是逻辑不一致。

4、如何解决视图逻辑不一致的问题?

对于不支持事务的引擎,像 MyISAM. 通过使用 Flush tables with read lock (FTWRL) 命令来开启全局锁。通过FTWRL开启全局锁之后,整个库会变成只读状态,不能写

但使用 FTWRL 存在的问题是:

  1. 在主库上备份时,备份期间不能执行更新,业务基本暂停。
  2. 在从库上备份,备份期间从库不能执行主库同步过来的 binlog,导致主从延迟。

对于支持事务并且开启一致性视图(可重复读级别)下配合上 MVCC 的功能的引擎(InnoDB),备份就很简单了。

使用官方的 mysqldump 工具时,加上 --single-transaction 选项,再导出数据前就会启动一个事务,来确保拿到一致性视图。并且由于 MVCC 的支持,同时可以进行更新操作。

5、全库只读设置方法的比较

为什么不推荐使用 set global readonly=true ,要使用 FTWRL

1)在有些系统中,readonly 的值会被用来做其他逻辑,比如用来判断一个库是主库还是备库。因此,修改 global 变量的方式影响面更大,不建议使用。

2)在异常处理机制上有差异。

执行 FTWRL 命令之后由于客户端发生异常断开,那么 MySQL 会自动释放这个全局锁,整个库回到可以正常更新的状态。

将整个库设置为 readonly 之后,如果客户端发生异常,则数据库就会一直保持 readonly 状态,这样会导致整个库长时间处于不可写状态,风险较高。

四、mysql表锁

MySQL的表级锁有两种模式:表共享读锁(Table Read Lock)和表独占写锁(Table Write Lock)。

1)对MyISAM表的读操作,不会阻塞其他用户对同一表的读请求,但会阻塞对同一表的写请求;

2)对 MyISAM表的写操作,则会阻塞其他用户对同一表的读和写操作;

3)MyISAM表的读操作与写操作之间,以及写操作之间是串行的(一个处理完才能处理下一个)

与 FTWRL 类似,可以使用 lock tables … read/write 来锁定某张表。在释放时,可以使用 unlock tables 来释放锁或者断开连接时,主动释放。

需要注意的是,这样方式的锁表,不但会限制其他线程的读写,也限定了自己线程的操作对象。

假如,线程 A 执行 lock tables t1 read, t2 write; 操作。

这时对于表 t1 来说,其他线程只能只读,线程 A 也只能只读,不能写。

对于表 t2 来说,只允许线程 A 读写,其他线程读写都会被阻塞。

1、元数据锁

与表锁手动加锁不同,元数据锁会自动加上。

1)为什么要有 MDL?

        MDL 保证的就是读写的正确性,比如在查询一个表中的数据时,同时另一个线程改变了表结构,查询的结果和表结构不一致肯定不行。简单来说,MDL 就是解决 DML 和 DDL 之间同时操作的问题。

        在 MySQL 5.5 引入了 MDL,在对一个进行 DML 时,会自动加 DML 读锁。进行 DDL 时,会自动加MDL写锁。

读锁间不互斥,允许多个线程同时对同一张表进行 DML。 
读写锁之间、写锁之间是互斥的,用来保证变更表结构操作的安全性。
  1. 如果有两个线程要同时给一个表加字段,其中一个要等另一个执行完才能开始执行。
  2. 如果一个线程要读,另一个线程要写。根据访问表的时间,一个操作进行完之后,另一个才可以进行。

2)MDL 引发的问题?

由于 MDL 是自动加的,并且在给表加字段或者修改字段或者加索引时,需要扫描全表的数据。所以在对大表操作时,要非常小心,以免对线上的服务造成影响。但实际上,操作小表时,也可能出问题。

        有一点需要注意,不要将 DDL 写在事务中,因为对于 DDL 操作是不支持 rollback 操作,所以在回滚时会出现不一致的情况。原因也可以理解,MVCC 所支持的行级别的数据,并不支持表级别的多版本控制。

2、如何安全的给表加资源?

        MDL 会直到事务提交才释放,在做表结构变更的时候,一定要小心不要导致锁住线上查询和更新。在开启事务后,并没有在短时间内结束,也就是由于所谓的长事务造成的。如果想对某个表进行 DDL 的操作时,可以先查询下是否有长事务的运行(information_schema 下的 innodb_trx 表),可以先 kill 这个事务,然后做 DDL 操作。

但有时 kill 也未必可以,在表        被频繁使用时,新的事务可能马上就来了。比较理想的情况,在 alter table 中设定等待时间,如果在时间内拿到最好,否则就放弃,不要阻塞语句。之后再重复这个操作。

五、行级锁

1、什么是行级锁?

        MySQL 的行锁是由引擎层自己实现的,所以不是所有的引擎都执行行锁,比如在 MyISAM 引擎就不支持行锁。不支持行锁意味着并发控制只能用表锁,这就造成了在同一时刻只有一个更新在执行,就影响到了业务的并发度。InnoDB 支持行锁是让 MyISAM 被取代的重要原因。

        行锁就是对数据库表中行记录的锁。比如事务 A,B 同时想要更新一行数据,在更新时一定会按照一定的顺序进行,而不能同时更新。行锁的目的就是减少像表级别的锁冲突,来提升业务的并发度。

2、两阶段锁协议

在 InnoDB 的事务中,行锁是在需要的时候再加上,但并不是使用完就释放,而是在事务结束后才释放,这就是两阶段锁协议。

假设有一个表 t,事务 A, B 操作表 t 的过程如下:

事务 A

事务 B

begin;

UPDATE t SET k=k+1 where id=1;

UPDATE t SET k=k+1 where id=2;

begin;

UPDATE t SET k=k+2 where id=1;

commit;

在事务 A 的两条语句更新后,事务 B 更新操作会被阻塞。直到事务 A 中执行 commit 操作后才能执行。

3、两阶段锁在事务上的帮助

由于两阶段锁的特点,在事务结束时才会释放锁,所以需要遵循的一个原则是事务中需要锁多个行时,把有可能造成锁冲突,最可能影响并发度的锁尽量向后放。

比如购买课程的例子,顾客 A 购买培训机构 B 一门课程。涉及到操作:

  1. 顾客 A 的余额减少
  2. 培训机构 B 所在的余额增加。
  3. 插入一条交易信息的操作。

对于第二个操作,当有许多人同时购买时并发度就较高,出现锁冲突的情况也较高。所以将操作 2 放置一个事务的最后就更好。

当有时并发度过大时,我们会发现一种现象 CPU 的使用率接近 100%,但事务执行数量却很少。这就可能出现了死锁。

六、总结

对于全局锁来说,使用 InnoDB 引擎 在 RR 级别和 MVCC 的帮助下,可以让其在备份的同时更新数据。

对于表级锁来说,对于更新热点表的表结构时,要注意 MDL 读写锁互斥,造成数据库挂掉的情况。

对于行级锁来说,合理的利用两段锁协议,降低锁的冲突。并要注意死锁发生的情况,采取合适的死锁检测手段。

  • 0
    点赞
  • 8
    收藏
    觉得还不错? 一键收藏
  • 1
    评论

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值