1、MySQL锁基本介绍
锁是计算机协调多个进程或线程并发访问某一个资源的机制。在数据库层面,除传统的计算机资源(CPU、RAM、I/O等)的争用之外,数据也是一种供多用户共享的资源。如何保证数据并发访问的一致性和有效性是所有数据库必须解决的一个问题,锁冲突也是影响数据库并发访问性能的一个重要因素。从这个角度来说,锁对数据库而言尤其重要,也更加复杂。
相对于其他的数据库,MySQL的锁机制比较简单,其最显著的特点是不同的存储引擎支持不同的锁机制。比如:MyIsam和Memory存储引擎采用的是表级锁(table-level locking),而Innodb存储引擎支持行级锁(row-level locking)也支持表表级锁,默认情况下采用行级锁。
表级锁:
开销小,加锁快;不会出现死锁;锁定粒度大,发生锁冲突的概率最高,并发度最低。
行级锁:
开销大,加锁慢;会出现死锁;锁定粒度最小,发生锁冲突的概率最低,并发度也最高。
从上述特点来看,很难笼统的说哪种锁更好,只能说应用场景更适合哪种锁。仅从锁的角度来说 :表级锁更适合与查询为主,只有少量按索引条件更新数据的应用,比如Web应用;而行级锁更适合于按大量索引条件并发更新少量不同数据,又并发查询的应用,比如在线事务处理系统。
2、MyIsam表锁
MyIsam的表锁有两种模式:表共享锁(Table Read Lock) 和 表独占锁(Table Write Lock)
1)概述
- 对MyIsam表的读操作不会阻塞其他用户对同一表的读操作,但会阻塞其他用户对同一表的写操作。
- 对MyIsam表的写操作会阻塞其他用户对同一表的读写操作。
- MyIsam表的读操作和写操作之间、以及写操作之间是串行的。
- 一个session会话使用lock table给表加读锁后,这个session可以锁定表中的记录,但更新和访问其他表都会提示错误:
- MyISAM在执行查询语句之前,会自动给涉及的所有表加读锁,在执行更新操作前,会自动给涉及的表加写锁,这个过程并不需要用户干预,因此用户一般不需要使用命令来显式加锁。
3、模拟锁场景
(1)建两个表
1)写锁阻塞读案例
当一个线程获取对一个表的写锁之后,只有持有锁的线程才能对该表进行读写操作,其他线程无论是读操作还是写操作都会被阻塞,等待锁的释放。
session1 | session2 |
获取表的write锁定 lock table mylock write; | |
当前session对表的查询,插入,更新操作都可以执行 select * from mylock; insert into mylock values(5,‘e’); | 当前session对表的查询会被阻塞 select * from mylock; |
释放锁: unlock tables; | 当前session能够立刻执行,并返回对应结果 |
2)MyIsam读阻塞写操作
当一个线程给表加了读锁之后,线程对应的session会锁定表中的记录,但更新和访问其他表都会提示错误。同时,其他线程可以查询表中的记录,但更新表就会出现锁等待。
session1 | session2 |
获得表的read锁定 lock table mylock read; | |
当前session可以查询该表记录: select * from mylock; | 当前session可以查询该表记录: select * from mylock; |
当前session不能查询没有锁定的表 select * from person Table ‘person’ was not locked with LOCK TABLES | 当前session可以查询或者更新未锁定的表 select * from person; insert into person values(1,‘zhangsan’); |
当前session插入或者更新表会提示错误 insert into mylock values(6,‘f’) Table ‘mylock’ was locked with a READ lock and can’t be updated update mylock set name=‘aa’ where id = 1; Table ‘mylock’ was locked with a READ lock and can’t be updated | 当前session插入数据会等待获得锁 insert into mylock values(6,‘f’); |
释放锁 unlock tables; | 获得锁,数据插入成功 |
session1开始释放锁,session2会自动获取到锁,然后插入数据。
3)MyISAM的并发插入问题
MyISAM表的读和写是串行的,这是就总体而言的,在一定条件下,MyISAM也支持查询和插入操作的并发执行;需要使用read local锁。
session1 | session2 |
获取表的read local锁定 lock table mylock read local | |
其他session可以进行插入操作,但是更新会阻塞 insert into mylock values(8,‘h’) update mylock set name = ‘aa’ where id = 1; | |
select * from mylock; 当前session不能访问其他session插入的记录; | |
释放锁资源:unlock tables | 当前session获取锁,更新操作完成 |
当前session可以查看其他session插入的记录 |
session1:
session2:
session1释放read local读锁之后,session2更新数据完成;session1可以获取到session2插入的数据。
注意:
可以通过检查table_locks_waited和table_locks_immediate状态变量来分析系统上的表锁定争夺:
如果Table_locks_waited的值比较高,则说明存在着较严重的表级锁争用情况。
4、总结
- 共享读锁(S)之间是兼容的,但共享读锁(S)与排他写锁(X)之间,以及排他写锁(X)之间是互斥的,也就是说读和写是串行的。
- 在一定条件下,MyISAM允许查询和插入并发执行,我们可以利用这一点来解决应用中对同一表查询和插入的锁争用问题。
- MyISAM默认的锁调度机制是写优先,这并不一定适合所有应用,用户可以通过设置LOW_PRIORITY_UPDATES参数,或在INSERT、UPDATE、DELETE语句中指定LOW_PRIORITY选项来调节读写锁的争用。
- 由于表锁的锁定粒度大,读写之间又是串行的;因此,如果更新操作较多,MyISAM表可能会出现严重的锁等待,可以考虑采用InnoDB存储引擎来减少锁冲突。