一、所谓‘锁’ 是什么
个人理解,所谓的锁就是为了保证数据库数据操作的一致性而产生的一种机制,即我们可能有很多数据,但是当我们有多个人或者多个线程或会话对同一条数据或同一批数据执行操作时,可能大家都要修改这一部分数据,但为了保证单独一个人的操作在前后都保持一致而不被其他人在自己不知情的情况下中途又修改了数据而导致的与自己预期结果不一致而导致的问题。
二、MySQL 行锁以及表锁
MySQL 常用存储引擎的不同锁机制
MyISAM 和MEMORY采用 表级锁(table-level locking)
BDB 采用页面锁(page-level locking)或表级锁,默认为页面锁
InnoDB支持行级锁(row-level locking)和表级锁,默认为行级锁
MySQL 默认为 InnoDB 引擎,也就默认是行级锁
行锁特点
- 行锁是在数据行级别上进行的锁定,只锁定被访问的具体数据行,而不是整个表。
- 行锁可以同时允许多个事务并发地访问同一张表的不同行,提高了并发性能。
- 行锁的粒度更细,可以减少锁冲突,但也会增加锁管理的开销。
- 会出现死锁,发生锁冲突几率低,并发高。
- MySQL 的行锁是通过索引加载的,即是行锁是加在索引响应的行上,若对应 SQL 语句没有走索引则会全表扫描,行锁将变为表锁。
表锁特点
- 表锁是在整个表级别上进行的锁定,锁定整张表,而不是具体的数据行。
- 表锁只允许一个事务访问整个表,其他事务需要等待该事务释放锁才能访问。
- 表锁的粒度较大,可能会导致并发性能较差,特别是在高并发环境下。
- 不会出现死锁,发生锁冲突几率高,并发低。
行锁又分共享锁和排他锁
共享锁又称读锁:
当一个事务对某几行上读锁时,允许其他事务对这几行进行读操作,但不允许其进行写操作,也不允许其他事务给这几行上排它锁,但允许上读锁。
排它锁又称写锁:
当一个事务对某几个上写锁时,不允许其他事务写但允许读。更不允许其他事务给这几行上任何锁。包括写锁。
insert ,delete , update在事务中都会自动默认加上排它锁
共享锁写法:加 lock in share mode
例如:
SELECT ........ LOCK IN SHARE MODE
共享锁写法:加 for update
例如:
SELECT ........ FOR UPDATE
(表级锁定分为表共享读锁(共享锁)与表独占写锁(排他锁))
如何检测是否是行锁?
开启两个会话,在第一个会话中将事务级别改为不提交,然后读取某个范围内的数据。
然后在第二个会话中访问第一个会话中选中的数据,由于没有提交事务所以应该还是等待,无法执行的结果。
访问第一个会话中指定范围外的数据,如果立即成功说明不是表锁,是行锁,否则就是表锁。
通过show VARIABLES like “%innodb_lock_wait_timeout%” 查询当前mysql设置的锁超时时间,默认50秒。
通过set innodb_lock_wait_timeout = 60; 设置锁的超时时间为60秒。
三、死锁
假设目前我们有二十条数据,第一个事务先锁定了前十条,第二个事务锁定了后十条。
后来第一个事务开始申请访问后十条并锁定,阻碍了后十条的释放,而第二个事务也申请访问前十条并锁定,导致前十条无法释放,从而形成循环导致死锁问题。
如何避免
- 不同事务访问多个表时使用相同的顺序去访问,避免出现上下交叉导致中间死锁
- 简化事务,大事务变小事务,小事务考虑不使用事务。尽量走索引完成数据操作。使用等值查询,例如 id = XX 或 id IN (X1, X2, X3),减少使用 id > 或 < XXX的操作,避免间隙锁(见下方补充说明)
- 避免在同一时间有多个操作对同一张表或同一张表的相同数据做操作。
间隙锁:在条件查询如:where id > XXX,InnoDB会给符合条件的已有数据记录的索引项加锁;对于键值在条件范围内但并不存在的记录,叫做“间隙(GAP)”,间隙的目的是为了防止幻读
意向锁:意向锁分为 (意向共享锁)intention shared lock (IS) 和 (意向排他锁)intention exclusive lock (IX),意向锁的目的是表明有事务正在或者将要锁住某个表中的行