并发锁 (三):myisam表锁

本文涉及的产品
云数据库 RDS MySQL,集群系列 2核4GB
推荐场景:
搭建个人博客
RDS MySQL Serverless 基础系列,0.5-2RCU 50GB
云数据库 RDS MySQL,高可用系列 2核4GB
简介: 并发锁 (三):myisam表锁

在之前我们讲到了并发下锁的重要性,以及在php中怎么实现文件锁

现在我们来讲讲关于mysql之间的锁:表锁和行锁

MyISAM 表锁

MyISAM 存储引擎只支持表锁,这也是MySQL 开始几个版本中唯一支持的锁类型。

表锁模式

所谓表锁,就是按表为单位直接锁住整个表

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

在前面的文章已经讲过了共享锁和独占锁,不多解释

如何加表锁

MyISAM在执行查询语句(SELECT)前,会自动给涉及的所有表加读锁,在执行更新操作(UPDATE、DELETE、INSERT等)前,会自动给涉及的表加写锁,这个过程并不需要用户干预,

例如:

select * from test limit 1

自动加共享锁,查询结束之后释放

再未查询成功之前,该表不允许执行更新操作:

测试案例

mysql终端1执行

select * ,sleep(100) from test limit 1 ; // sleep 100秒之后再获取查询结果

mysql终端2执行

update test set id=1;

mysql终端3执行

select * from test limit 1 ;

此时会出现 终端1  sleep等待状态,同时终端2在等待终端1释放共享锁,终端3在等待终端2释放独占锁(虽然没获取到独占锁):


image.png

image.png

如果去掉终端2语句,终端3语句将正常执行(不多说明)

MyISAM之锁机制

在上面,我们发现了一个这样的现象:(现象1)

终端1 sleep读取,共享锁

终端3读取,共享锁,正常执行

然而:(现象2)

终端1 sleep读取,共享锁

终端2 写入,独占锁,阻塞等待锁

终端3 读取,共享锁,阻塞等待锁

理论上来说,终端1和终端3都是共享锁,为什么终端3还需要等待呢?

因为myisam引擎的读写操作是串行的,

在现象1中

终端1 sleep读取,共享锁运行之后,其他连接还可以继续读取表,(共享锁特性)

串行到终端3,终端3可以继续执行

现象2中

终端1 sleep读取,共享锁运行之后,其他连接可以继续读取表,不能更新表数据(共享锁特性)

串行到终端2,终端2由于是写入操作,独占锁,将会被阻塞,但是终端2已经是独占锁等待状态,其他连接不能读取,不能更新(独占锁特性)

此时串行到终端3,由于myisam已经有了独占锁等待情况,所以终端3被阻塞

MyISAM之锁调度

前面讲过,MyISAM存储引擎的读锁和写锁是互斥的,读写操作是串行的。那么,一个进程请求某个 MyISAM表的读锁,同时另一个进程也请求同一表的写锁,MySQL如何处理呢?答案是写进程先获得锁。不仅如此,即使读请求先到锁等待队列,写请求后到,写锁也会插到读锁请求之前!这是因为MySQL认为写请求一般比读请求要重要。这也正是MyISAM表不太适合于有大量更新操作和查询操作应用的原因,因为,大量的更新操作会造成查询操作很难获得读锁,从而可能永远阻塞。这种情况有时可能会变得非常糟糕!幸好我们可以通过一些设置来调节MyISAM 的调度行为。

  • 通过指定启动参数low-priority-updates,使MyISAM引擎默认给予读请求以优先的权利。
  • 通过执行命令SET LOW_PRIORITY_UPDATES=1,使该连接发出的更新请求优先级降低。
  • 通过指定INSERT、UPDATE、DELETE语句的LOW_PRIORITY属性,降低该语句的优先级。

另外,MySQL也提供了一种折中的办法来调节读写冲突,即给系统参数max_write_lock_count设置一个合适的值,当一个表的读锁达到这个值后,MySQL就暂时将写请求的优先级降低,给读进程一定获得锁的机会。

上面已经讨论了写优先调度机制带来的问题和解决办法。这里还要强调一点:一些需要长时间运行的查询操作,也会使写进程“饿死”!因此,应用中应尽量避免出现长时间运行的查询操作,不要总想用一条SELECT语句来解决问题,因为这种看似巧妙的SQL语句,往往比较复杂,执行时间较长,在可能的情况下可以通过使用中间表等措施对SQL语句做一定的“分解”,使每一步查询都能在较短时间完成,从而减少锁冲突。如果复杂查询不可避免,应尽量安排在数据库空闲时段执行,比如一些定期统计可以安排在夜间执行。

手动加锁

有一个订单表orders,其中记录有各订单的总金额total,同时还有一个订单明细表order_detail,其中记录有各订单每一产品的金额小计 subtotal,假设我们需要检查这两个表的金额合计是否相符,可能就需要执行如下两条SQL:

select sum(total) from orders;
select sum(subtotal) from order_detail;

这个语句看起来是没错的,但是可能出现这个情况:

语句1查询,加共享锁,查询完,释放

在其他连接中,出现一条insert的语句,在order_detail中加了一条数据 加独占锁,因为语句1已经查询完,锁已经释放,所以正常加锁

语句2查询需要等待insert插入完毕,释放锁,才能继续查询

这时候就出现了2个问题:

1:语句1和语句2原本是同一个逻辑,统计2种金额的,却导致了语句2多统计了一条数据,导致金额合计不符合

2:语句2需要等待insert插入完毕才能够继续执行,如果insert插入花费了10秒,那语句2也得等10秒之后才能查询

那该怎么解决呢?

myisam可以使用语句手动加锁:

LOCK TABLES tbl\_name \[AS alias\] {READ \[LOCAL\] | \[LOW\_PRIORITY\] WRITE}

例如:

lock tables orders read local, order_detail read local;
select sum(total) from orders;
select sum(subtotal) from order_detail;
unlock tables;
  • 上面的例子在LOCK TABLES时加了“local”选项,其作用就是在满足MyISAM表并发插入条件的情况下,允许其他用户在表尾并发插入记录。
  • 在用LOCK TABLES给表显式加表锁时,必须同时取得所有涉及到表的锁,并且MySQL不支持锁升级。也就是说,在执行LOCK TABLES后,只能访问显式加锁的这些表,不能访问未加锁的表;同时,如果加的是读锁,那么只能执行查询操作,而不能执行更新操作。其实,在自动加锁的情况下也基本如此,MyISAM总是一次获得SQL语句所需要的全部锁。这也正是MyISAM表不会出现死锁(Deadlock Free)的原因。

手动锁表之后不能操作未锁的表:

mysql> lock tables test read local;
Query OK, 0 rows affected
mysql> select * from test2 limit 1;
1100 - Table 'test2' was not locked with LOCK TABLES
mysql> unlock tables;
Query OK, 0 rows affected
mysql> select * from test2 limit 1;
+----+------+------------------------+------------------------+
| id | name | url1                   | url2                   |
+----+------+------------------------+------------------------+
|  1 | 0    | http://www.KmxhtRE.com | moc.ERthxmK.www//:ptth |
+----+------+------------------------+------------------------+
1 row in set
mysql>

并发插入

myisam在加共享锁之后,理论上是不允许同时插入数据的,但是mysql有个系统变量concurrent_insert 用于控制其插入行为(只允许插入,不允许更新等)

  • 当concurrent_insert设置为0时,不允许并发插入。
  • 当concurrent_insert设置为1时,如果MyISAM表中没有空洞(即表的中间没有被删除的行),MyISAM允许在一个进程读表的同时,另一个进程从表尾插入记录。这也是MySQL的默认设置。
  • 当concurrent_insert设置为2时,无论MyISAM表中有没有空洞,都允许在表尾并发插入记录。
mysql> select @@concurrent_insert;//查询当前系统全局配置 
+---------------------+
| @@concurrent_insert |
+---------------------+
| AUTO                |
+---------------------+
1 row in set

AUTO 等于1 NEVER为0,ALWAYS为2

可以利用MyISAM存储引擎的并发插入特性,来解决应用中对同一表查询和插入的锁争用。例如,将concurrent_insert系统变量设为2,总是允许并发插入;同时,通过定期在系统空闲时段执行 OPTIMIZE TABLE语句来整理空间碎片,收回因删除记录而产生的中间空洞。

分析数据库锁状态

mysql> show status like 'table%';
+----------------------------+-------+
| Variable_name              | Value |
+----------------------------+-------+
| Table\_locks\_immediate      | 157   |
| Table\_locks\_waited         | 22    |
| Table\_open\_cache_hits      | 10    |
| Table\_open\_cache_misses    | 1     |
| Table\_open\_cache_overflows | 1     |
+----------------------------+-------+
5 rows in set

Table_locks_immediate:能够立即获得表级锁的锁请求次数

Table_locks_waited:不能立即获取表级锁而需要等待的锁请求次数

分析:

如果table_locks_waited值较高,且存在性能问题,则说明存在着较严重的表级锁争用情况。这时,需要对应用做进一步的检查,来确定问题所在,应首先优化查询,然后拆分表或复制表。


相关实践学习
如何在云端创建MySQL数据库
开始实验后,系统会自动创建一台自建MySQL的 源数据库 ECS 实例和一台 目标数据库 RDS。
全面了解阿里云能为你做什么
阿里云在全球各地部署高效节能的绿色数据中心,利用清洁计算为万物互联的新世界提供源源不断的能源动力,目前开服的区域包括中国(华北、华东、华南、香港)、新加坡、美国(美东、美西)、欧洲、中东、澳大利亚、日本。目前阿里云的产品涵盖弹性计算、数据库、存储与CDN、分析与搜索、云通信、网络、管理与监控、应用服务、互联网中间件、移动服务、视频服务等。通过本课程,来了解阿里云能够为你的业务带来哪些帮助     相关的阿里云产品:云服务器ECS 云服务器 ECS(Elastic Compute Service)是一种弹性可伸缩的计算服务,助您降低 IT 成本,提升运维效率,使您更专注于核心业务创新。产品详情: https://www.aliyun.com/product/ecs
目录
相关文章
|
5月前
|
SQL 存储 关系型数据库
MySQL数据库——锁-表级锁(表锁、元数据锁、意向锁)
MySQL数据库——锁-表级锁(表锁、元数据锁、意向锁)
214 0
|
5月前
|
SQL 关系型数据库 MySQL
MySQL数据库——锁-行级锁(行锁、间隙锁和临键锁)
MySQL数据库——锁-行级锁(行锁、间隙锁和临键锁)
88 0
|
6月前
|
数据库 数据安全/隐私保护
什么是行级锁和表级锁
什么是行级锁和表级锁
118 0
|
6月前
|
SQL 关系型数据库 MySQL
⑩⑦【MySQL】锁:全局锁、表级锁、行级锁
⑩⑦【MySQL】锁:全局锁、表级锁、行级锁
76 0
|
存储 监控 关系型数据库
InnoDB中的各种锁及其应用
InnoDB中的各种锁及其应用
|
关系型数据库
InnoDB行级锁的分析
在主键上的查询 下面做过实验
|
SQL 监控 Oracle
【悲观锁/乐观锁/排它锁/共享锁/表级锁/行级锁/死锁】
【悲观锁/乐观锁/排它锁/共享锁/表级锁/行级锁/死锁】
116 0
|
存储 关系型数据库 数据库
|
存储 SQL NoSQL
mysql锁(全局锁、表锁、行锁、页锁、排他锁、共享锁)
mysql锁(全局锁、表锁、行锁、页锁、排他锁、共享锁)
369 0
|
存储 关系型数据库 MySQL
MySQL的表级锁、行级锁、页级锁是干什么的?底层原理是什么?
MySQL的表级锁、行级锁、页级锁是干什么的?底层原理是什么?
500 0