MySQL有三种锁:表级锁、行级锁和页面锁。BDB支持页面锁,MyISAM支持表级锁,而innoDB则支持表锁和行级锁。这篇文章主要介绍MYISAM引擎的表锁。




表锁分为读锁(read lock)和写锁(write lock)

1. 读锁(read lock)

当一个session给表加读锁,其他session也可以继续读取该表,但所有更新、删除和插入将会阻塞,直到将表解锁。下面是具体步骤:
session1给myisam_lock表加读锁:

wKioL1boDDaRKW4_AAAGYevYgdQ328.png
session2可以照常读取myisam_lock表:

wKioL1boDDbSZq1hAAAJg1Kek_U942.png
session2执行insert语句,被阻塞:

wKiom1boC6vgRFmOAAAFURcSSzA468.png
session1解锁myisam_lock表:

wKiom1boC6uT4lDfAAAFoawIBMI775.png
session2中被阻塞的insert操作成功执行:

wKioL1boDDewC6sHAAATEgropAU346.png
MyISAM引擎在执行select时会自动给相关表加读锁,在执行update、delete和insert时会自动给相关表加写锁。而InnoDB则加行锁。

表级读锁有几点需要特别注意的地方:

①lock表的时候一定要把所有需要访问的表都锁住,因为锁表之后无法访问其他未加锁的表。(InnoDB一样)

②当前session lock表之后,当前session只能读锁住的表,而无法对其进行update、delete和insert操作。(InnoDB一样)
wKiom1boVUTzORVLAAAjF7ZYLVA782.png
③同一个表如果在sql语句里面如果出现了N次,那么就要锁定N次,否则会出错。(InnoDB一样)

wKiom1boWuSShhalAAARnjIrwc0762.png

wKioL1boW3DAnA5VAAAa7VVZ-P8294.png




2. 写锁(write lock)


当一个session给表加写锁,其他session所有读取、更新、删除和插入将会阻塞,直到将表解锁。下面是具体步骤:
session1给myisam_lock表加写锁:

wKiom1cV-WnD4PPFAAAGdekOq0s561.png

session2对myisam_lock表的查询被阻塞:

wKioL1cV-s7i_4hiAAAEo5LX3Uo738.png

session1解锁myisam_lock表,session2查询出结果:

wKioL1cV-x-AHXaKAAALsxPEN34530.png

lock表的时候一定要把所有需要访问的表都锁住,因为锁表之后无法访问其他未加锁的表。




3. concurrent_insert和local操作(此操作为MyISAM引擎专有,InnoDB无此功能)


上面我们说到只要给一个表加了读锁,其他session对该表的写操作将被阻塞。那么有没有办法让其他session也能往里面添加数据呢?
这里我们可以使用local关键字,语法如下:lock table 表名 read local。这样在当前表被加读锁的时候,可以让其他session往表里添加记录,但需要配合concurrent_insert全局变量使用。
concurrent_insert属性有三中取值,分别是NEVER(0)、AUTO(1)和ALWAYS(2),从5.5.3版本开始concurrent_insert参数用枚举值,以前的版本则直接使用对应的数字。他们的含义如下:
NEVER:加读锁后,不允许其他session并发写入。
AUTO:加读锁后,在表里没有空洞(就是没有删除过行)的条件下,允许其他session并发写入。
ALWAYS:加读锁后,允许其他session并发写入。

通过show global variables like '%concurrent_insert%'命令可以查看当前数据库的设置:
wKioL1b6EHSwjF1iAAAP-W4fn1o969.png
通过set global concurrent_insert = ALWAYS命令可以改变数据库设置:
wKioL1b6E2SzjHMZAAAXYfn1yfA789.png
下面我们在AUTO的条件下进行试验,首先session1用local方式给myisam_lock表加读锁:
wKiom1b6E0jwPSwxAAAGnmubbSo277.png
session2可以正常读取,还可以插入数据:
wKiom1b6FXDxO_b2AAAl5CGbvyE325.png
但session2插入的数据对session1是不可见的,必须等session1释放锁之后才可见:
wKioL1b6Fj7BwqBYAAAg8MpdVh0862.png




4. MyISAM引擎锁的调度机制
MyISAM引擎默认是write lock优先于read lock的,也就是说如果一堆写请求和一堆读请求同时要一张表的锁,那读请求只能在所有的写请求执行完成后才能获得执行机会。这样就会出现一个很大的问题:如果我们在批量更新一张用户表,那么用户登录操作可能会出现长时间阻塞的情况,因为用户登录的读取操作在更新完之前无法访问用户表。
所以MyISAM最好不要用在那些更新和读取都非常频繁的表里,会造成读取的长时间阻塞。但我们可以用下面的方法来缓解这类问题:
①使用LOW_PRIORITY、HIGH_PRIORITY和DELAYED关键字。
语法为:insert [LOW_PRIORITY | HIGH_PRIORITY | DELAYED] into 表名 ...
执行delete、insert、update、load data和replace的时候可以使用LOW_PRIORITY来降低该更新语句的优先级,让读取操作能够执行。
执行select和insert的时候可以使用HIGH_PRIORITY来提高该语句的优先级,让读取操作能够执行。
执行insert和replace的时候可以使用DELAYED让MySQL返回OK状态给客户端,并且修改也是对该session可见的。但并不是已经将数据插入表,而是存储在内存里面等待排队。当能够获得表的写锁再插入。这样的好处是,提高插入的速度,客户端不需要等待太长时间。坏处是,不能返回自动递增的ID,以及系统崩溃时,MySQL还没有来得及插入数据的话,这些数据将会丢失。

②set LOW_PRIORITY_UPDATES = 1。
让所有支持LOW_PRIORITY选项的语句都默认地按照低优先级来处理。
③修改MAX_WRITE_LOCK_COUNT变量。
该变量默认为int最大值,表示当一个表的写锁数量达到设定的值后,就降低写锁的优先级,让读锁有机会执行。



5. 查看表锁的竞争情况

show status like 'table_locks%'

wKiom1boADLQbZ6EAAAMg2WSYyw108.png
如果Table_locks_waited很大,则说明表锁竞争很激烈,并发性能低下。