MySQL锁问题:MyISAM加表锁、并发插入以及锁调度
主要介绍:MySQL锁、MyISAM表锁、查询表锁互掐、MySQL表锁模式、如何加(显示)表锁、模拟并发插入以及MyISAM锁调度的介绍。
只停留在看上面,提升效果甚微。应该带着思考去测试佐证,或者使用(同类书籍)新版本进行对比,这样带来的效果更好。最重要的一环,养成阅读官方文档,是一个良好的习惯。能编写官方文档,至少证明他们在这个领域是有很高的造诣,对用法足够熟练。
接着上一篇MySQL数据库SQL优化流程。在对MySQL进行举例并使用到示例数据库:大多数情况使用MySQL官方提供的sakila(模拟电影出租信息管理系统)和world数据库,类似于Oracle的scott用户。
如果没有进行特别说明,一般是基于MySQL8.0.28进行测试验证。官方文档非常具有参考意义。你可以将这篇博文,当成过度到MySQL8.0的参考资料。友情提示:经验是用来参考,但不是拿来即用。如果你能看到并分享这篇文章,我很荣幸。如果有误导你的地方,我表示抱歉。
tips:图片资源可能被防盗链(寄)了,可以右键属性复制地址在地址栏查看哈。
MySQL锁问题——MyISAM表锁
注意:在某些情况,你自己测试的结果可能与我演示有所不同,我省略了查询结果的部分参数。
本文侧重点在SQL优化流程以及MySQL锁问题(MyISAM和InnoDB存储引擎)。图片可能会挂,演示时尽量使用SQL查询语句返回结果进行示例。篇幅很长,因此使用markdown语法加了目录。
如果你是从MySQL5.6或者5.7版本过渡到MySQL8.0。学习之前,建议线看官方文档这一章节:1.3 What Is New MySQL8.0 。在做对比的时候,文档中带有Note标识是你应该注意的地方。比如下面这张截图:
MyISAM锁问题
简单概括锁:锁是计算机协调多个进程或线程并发访问某一资源的机制。
MySQL中的锁看上去用法和表面实现(对比其它DBMS),貌似很简单,但真正深入理解其实也不是那么容易。
01 MySQL锁介绍
1.1 什么是锁
为何要使用锁?开发多用户、数据库驱动的应用时,难点(痛点):一方面要最大程度地利用数据库的并发访问,另一方面还要确保每个用户能以一致的方式读取和修改数据。因此有了锁(locking)的机制,同时也是数据库系统区别于文件系统的一个关键特性。
在数据库中,除传统的计算资源(如CPU、RAM、I/O等)的消耗外,数据也是一种供许多用户共享的资源。
如何保证数据并发访问的一致性、有效性是所有数据库必须解决的一个问题,锁冲突也是影响数据库并发访问性能的重要因素。从描述来看,锁对数据库显得尤为重要,也更加复杂。接下来,会对锁机制特点进行介绍、常见的锁问题,以及解决MySQL锁问题的方法。
1.2 MySQL锁
相比其它数据库来说,MySQL的锁机制相对好理解一点,其最显著的特点是不同的存储引擎支持不同锁机制。比如MyISAM和MEMORY存储引擎采用表级锁(table-level locking);BDB存储引擎(MySQL8.0文档没看到介绍)采用页面锁(page-level locking),但也支持表级锁(table-level locking);InnoDB存储引擎既支持行级锁(row-level locking),也支持表级锁,默认采用行级锁。
MySQL中3种锁特性:
- 表级锁:开销小,加锁块。不会出现死锁,锁粒度大,发生锁冲突概率最高,并发度最低。
- 行级锁:开销大,加锁慢。会出现死锁,锁粒度最小,发生锁冲突概率最低,并发度最高。
- 页面锁:开销和加锁时间介于表锁与行锁之间。会出现死锁,锁粒度介于表锁与行锁之间,并发度一般。
从上述各种锁特点来看,不能一概而论哪种锁更好,但可以从具体应用特点来判断哪种锁更合适。
单从锁角度出发:表锁较为适合以查询为主,少量按索引条件更新数据的应用。行级锁更适合有大量按索引条件、并发更新少量不同数据,同时有并发查询的应用。
02 MyISAM 表锁
MyISAM(存储引擎限制256TB)存储引擎只支持表锁,是MySQL开始几个版本中唯一支持的锁类型。
随着应用对事务完整性和并发性要求的不断提高,MySQL开发了基于事务存储引擎,后来出现了支持页面锁的BDB(逐渐被InnoDB替代,MySQL8.0文档已看不到介绍)存储引擎和支持行锁的InnoDB存储引擎。MyISAM表锁依旧是使用比较广泛的锁类型。
2.1 查询表锁互掐
通过检查Table_locks_waited和Table_locks_immediate状态变量来分析系统上表锁争抢:
1 | mysql> show status like 'table%'; |
如果Table_locks_waited
值比较大,则说明存在严重的表锁争抢情况。
2.2 MySQL表锁模式
MySQL表锁有两种模式:
- Table Read Lock:表共享读锁;
- Table Write Lock:表独占写锁。
MyISAM表读操作,不会阻塞其他用户对同一表的读请求,但会阻塞对同一表的写请求;对MyISAM表的写操作,则会阻塞其他用户对同一表的读和写操作。MyISAM表的读操作和写操作之间,以及与写操作之间是串行的。
当一个线程获得对一个表的写锁后,只有持有锁的线程可以对表进行更新操作。其它线程的读、写操作都会等待,直到被释放。
2.3 如何加表锁
MyISAM在执行查询(select)前,会自动给涉及的所有表加读锁;在执行更新(update、insert、delete等)操作前,会自动给涉及的表加写锁,这一过程并不需要用户干预。所以,一般情况不需要用户执行lock table命令给MyISAM显式加锁。
下面只是演示一下加锁和解锁,阻塞示例。如果想测试,可以在创表时指定存储引擎为MyISAM,或者使用alter table命令修改存储引擎。例如修改city表存储引擎为MyISAM,使用show create table
命令查看:
1 | mysql> alter table world.city engine=MyISAM; |
city表测试添加写锁(lock table write)
1 | mysql> lock table city write; |
当前session窗口,测试查询(select)、插入(insert)、修改(update)、删除(delete)均不受影响。
打开其它session窗口,测试增删改查均在等待中。分别给出查询上锁与解锁示例。
上了写锁(当前session),其它窗口查询显示等待中…
解锁(当前session),其它session窗口查询正常并显示查询等待时间:
1 | mysql> unlock tables; |
city表测试添加读锁(lock table read)
1 | mysql> lock table city read; |
演示更新操作插入(insert)、修改(update)、删除(delete):均提示有读锁(当前session)
1 | mysql> insert into city values(9527,'ts','ts','ts',9527000); |
解锁(unlock):
1 | mysql> unlock tables; |
tips:添加读锁,当前session的新增、修改删除均会提示已经上了锁,查询其它未上锁表也会提示报错。
其它session窗口依旧可以查询、更新未上锁的表。锁住表不会提示,但是会在等待中。
使用lock table时,可能需要一次性锁定用到的所有表,同一个表出现多次,需要对别名锁定。
2.4 并发插入
整体上看,MyISAM表读和写是串行。在一定条件下,MyISAM表也支持查询和插入操作并发进行。
MyISAM存储引擎有一个系统变量concurrent_insert,用于控制其并发插入行为,值可以为0、1或2,默认为AUTO。
如下所示,使用select @@参数形式查询系统变量值:
1 | mysql> select @@concurrent_insert; |
- 当concurrent_insert设置为0:不允许并发插入。
- 当concurrent_insert设置为AUTO(or 1):如果MyISAM表中间没有被删除的行,允许在一个进程读表的同时,另一个进程从表尾插入记录。也是MySQL默认设置AUTO(or 1)。
- 当concurrent_insert设置为2:无论MyISAM有无空洞(表中间没有被删除的行),都允许在表尾并发插入记录。
示例:其中一个session获得一张表read local锁,该线程可以对当前表进行查询,但不能进行更新操作。其它线程(session),虽然不能进行删除和更新操作,但可以进行插入(insert)操作。假定条件:表中间没有空洞。
其中一个线程(session1)获取锁:查询不受影响,不能做更新操作
1 | mysql> lock table xxls read local; -- session1获取read local锁 |
另外一个线程(session2)演示:插入数据完成,不受影响
1 | mysql> insert into xxls values(1015,'xxls','女','B'); |
总结:可以利用MyISAM存储引擎并发插入特性解决应用中对同一表查询和插入锁争用。设置concurrent_insert值为2,总是允许并发插入。与此同时,通过定期在系统空闲时段(不活跃时段)执行optimize table整理空间碎片,回收删除记录产生的空洞。
optimize用法如下:注意如果有读锁情况下,是不能进行操作的。
1 | mysql> optimize table tolove; -- 优化tolove表 |
更多详细描述(MySQL8.0)可以参考:
第5章节:5.1.8 Server System Variables(服务系统变量)
摘自:refman-8.0-en.pdf
2.5 MyISAM锁调度
MyISAM存储引擎读锁与写锁互斥,读写操作串行。
一个进程请求某MyISAM表读锁,另一个进程请求同一表写锁,MySQL能友好的处理么?
答案是写进程先获得锁。即使读请求先等等待(排队时,读在前),写在后等的不耐烦了。读你给我挪挪位,写优先插队进入队列中。个人认为写锁优先是合理的,毕竟写(更新操作:insert、update、delete)比较重要,如何在最大限度保证数据完整性、一致性。
写锁优先,MySQL认为写请求一般比读请求重要。这也是为什么MyISAM表不适合同时有大量更新操作和查询操作的原因。大量更新操作会造成查询操作很难获得读锁,导致永远阻塞。好在可以通过一些系统参数来调节MyISAM调度行为。通过指定参数low_priority_updates
,让MyISAM存储引擎默认给予读请求优先权利。
给出官方文档设置示范:
1 | mysql> select @@low_priority_updates; -- 查询出默认值是0 |
另外MySQL提供了折中方法调节锁冲突,给系统参数max_write_lock_count
设置合适值。当一张表读锁达到这个值后,MySQL暂时将写请求优先级降低,给读进程(session)获得锁的机会。
也不能太依赖一条SQL查询语句解决问题,适当进行拆分成中间表进行合理控制查询时间。有些复杂查询(统计)操作是无法避免的,但可以人为定时操作,在夜深人静之时(凌晨),悄无声息执行。如果你是一名Java开发人员,或许在配置文件(xml、yml)中应该做过这种操作。
03 MySQL官方示例数据库
给出sakila-db数据库包含三个文件,便于大家获取与使用:
- sakila-schema.sql:数据库表结构;
- sakila-data.sql:数据库示例模拟数据;
- sakila.mwb:数据库物理模型,在MySQL workbench中可以打开查看。
https://downloads.mysql.com/docs/sakila-db.zip
只是用于用于简单测试学习,建议使用world-db:
world-db数据库,包含三张表:city、country、countrylanguage。
https://downloads.mysql.com/docs/world-db.zip
最后附上官方示例数据库,sakila-db数据库一个非常完整的示例。包含:视图、函数、触发器以及存储过程,当然也存在使用外键。
参考资料&鸣谢:
《深入浅出MySQL 第2版 数据库开发、优化与管理维护》。
《MySQL技术内幕InnoDB存储引擎 第2版》。
MySQL8.0官网文档:refman-8.0-en.pdf,如果学习新版本,官方文档是非常不错的选择。
虽然书籍年份比较久远(停留在MySQL5.6.x版本),但仍然具有借鉴意义。
最后,对以上书籍和官方文档所有作者表示衷心感谢。让我充分体会到:前人栽树,后人乘凉。
莫问收获,但问耕耘
能看到这里的,都是帅哥靓妹。以上是本次MySQL优化篇(上部分)全部内容,希望能对你的工作与学习有所帮助。感觉写的好,就拿出你的一键三连。如果感觉总结的不到位,也希望能留下您宝贵的意见,我会在文章中定期进行调整优化。好记性不如烂笔头,多实践多积累。你会发现,自己的知识宝库越来越丰富。原创不易,转载也请标明出处和作者,尊重原创。