一、锁的定义
读锁(共享锁):是多事务可以并发读取数据的锁,但任何事务都被阻塞等待对该数据进行写操作和加写锁,直到已释放所有共享锁。
写锁(排他锁):是指只有当前事务才可以进行读写操作的锁,则其他事务都被阻塞等待对该数据进行读写操作和加读写锁,直到已释放所有排他锁。
二、锁特点
- 表级锁:开销小,加锁快;不会出现死锁;锁定粒度大,发生锁冲突的概率最高,并发度最低。
- 行级锁:开销大,加锁慢;会出现死锁;锁定粒度最小,发生锁冲突的概率最低,并发度也最高。
- 页面锁:开销和加锁时间界于表锁和行锁之间;会出现死锁;锁定粒度界于表锁和行锁之间,并发度一般。
三、MyISAM表锁
MyISAM 存储引擎只支持表锁,MySQL 的表级锁有两种模式:表共享读锁(Table Read Lock)和表独占写锁(Table Write Lock)。
读操作:一旦数据表被加上读锁,当一个请求在读数据时,其他请求也可以读,但是不能写,因为一旦另外一个线程写了数据,就会导致当前线程读取到的数据不是最新的了。这就是不可重复读现象。
写操作:一旦数据表被加上写锁,当一个请求在写数据时,其他请求不能执行任何操作,因为在当前事务提交之前,其他的请求无法看到本次修改的内容。这有可能产生脏读、不可重复读和幻读。
读锁和写锁都是阻塞锁:例如A对数据表增加了写锁,如果B请求对数据表加写锁,此时B的加锁请求会一直处于阻塞状态,直到A释放了对表的锁,B才加锁成功获取到结果。
四、表锁的加锁/解锁方式
MyISAM 在执行查询语句(SELECT
)前,会自动给涉及的所有表加读锁,在执行更新操作 (UPDATE
、DELETE
、INSERT
等)前,会自动给涉及的表加写锁;因此,用户一般不需要直接用LOCK TABLE
命令给MyISAM表显式加锁。
锁表:lock tables tbl_name read | wrinte,tbl_name1 read | wrinte,…
解锁表:unlock table
查看表加锁状态:show open tables like '%tbl_name%';
注意:当某事务在执行 LOCK TABLE
后,只能访问加锁的这些表,该事务不能访问未加锁的表,其他事务可以访问未加锁的表;
此外,Myisam的读写锁调度是写优先,因此Myisam不适合做写为主表的引擎,因为写锁后其他线程不能做任何操作,大量更新会使查询很难得到锁,从而造成永远堵塞。
五、Innodb行锁
行级锁是Mysql中锁定粒度最细的一种锁,表示只针对当前操作的行进行加锁。行级锁能大大减少数据库操作的冲突。其加锁粒度最小,但加锁的开销也最大。有可能会出现死锁的情况。行级锁也分为共享锁和排他锁。
六、Innodb 行加锁方式:
共享锁用法:select ... where id=1|2|3... lock in share mode;
排它锁用法: select ... where id=1|2|3... for update;
七、死锁
MyISAM中是不会产生死锁的,因为MyISAM总是一次性获得所需的全部锁,要么全部满足,要么全部等待。而在InnoDB中,锁是逐步获得的,就造成了死锁的可能。
在InnoDB中,行级锁并不是直接锁记录,而是锁索引。索引分为主键索引和非主键索引两种,如果一条sql语句操作了主键索引,MySQL就会锁定这条主键索引;如果一条语句操作了非主键索引,MySQL会先锁定该非主键索引,再锁定相关的主键索引。 当两个事务同时执行,一个锁住了主键索引,在等待其他相关索引。另一个锁定了非主键索引,在等待主键索引。这样就会发生死锁。