锁概述
锁是计算机协调多个进程或者线程并发访问同一资源的机制。
在数据库中,数据提供给多个用户使用。如何保证数据并发访问时的有效性和一致性是所有数据库都必须解决的问题。锁对数据库来说极其重要,如果没有锁,我们存储在数据库的数据将会不准确,对数据库来说,它就不是一个可用的数据库了。
锁分类
对数据操作的粒度来区分:
表锁:操作记录时,会导致整张表被锁定,其他用户不能操作;
行锁:操作的行记录,会被锁定,其他用户不能操作该行记录;
对数据操作的类型区分:
读锁:也叫共享锁,针对同一份数据,多个读操作不行会相互影响;
写锁:也叫排它锁,在修改同一份数据时,禁止其他用户操作该行纪录;
MySQL存储引擎锁支持
- MyISAM:表锁
- InnoDB:表锁、行锁
- MEMORY:表锁
表锁开销小、加锁快、不会出现死锁,但是锁的颗粒度比较大,容易发生锁冲突,并发度较低;
行锁开销大、加锁慢、容易死锁,但是加锁颗粒度较小,不容易产生锁冲突,并发度较高;
锁有各自的优势,表锁比较适合以查询为主的系统,而行锁则适合以并发操作数据为主的系统。
MyISAM表锁
MyISAM存储引擎会在执行查询语句前,自动给涉及的表加上读锁,在涉及更新操作时会自动加上写锁。如果我们想显示的进行操作,可使用下列语法操作:
-- 给表加读锁
LOCK TABLE 表名 READ;
-- 给表加写锁
LOCK TABLE 表名 WRITE;
-- 解锁
UNLOCK TABLE;
在给表加读锁后,获取读锁的客户端获得读锁,这时当前客户端只能操作当前表,如果在未进行解锁操作时,不可再操作其他表。其他线程可继续读取该表数据,但是不可进行写操作。
在给表加了写锁后,加锁的客户端对该表可进行增删改查的操作,但是其他客户端的操作则会被阻塞,等待加锁的客户端解锁后,数据才能正常操作。
InnoDB行锁
InnoDB不仅支持表锁,还支持行锁;行锁主要针对操作的行进行加锁,在进行更新、删除、插入操作,InnoDB引擎都会自动添加写锁。
对于查询语句,InnoDB引擎不会加任何的锁,如果需要加锁,可使用下列语句给查询语句加锁:
-- 给查询语句添加读锁(共享锁),只有当前事务能进行写操作,其他事务的读操作不受影响
SELECT * FROM app_user WHERE id=2 LOCK IN SHARE MODE;
-- 给查询语句添加写锁(排它锁),只有当前事务能进行读写操作,其他事务被阻塞
SELECT * FROM app_user WHERE id=2 LOCK IN SHARE MODE;
注意:
-
在使用InnoDB进行数据的更新操作时,如果where后面的条件没有索引,或者索引失效,会导致行锁升级为表锁。
-
如果where后面是范围查询,即使不存对应的数据,只要在范围内的数据都会被加锁,我们称之为“间隙锁”;
例如:where id<10,那么不管数据是否存在 id=2 的记录,它都会被加上行锁。
我们可以使用下列语句查询行锁的使用情况:
SHOW STATUS LIKE 'innodb_row_lock%';
key | 说明 |
---|---|
Innodb_row_lock_current_waits | 当前正在等待锁的数量 |
Innodb_row_lock_time | 锁的总时长 |
Innodb_row_lock_time_avg | 锁的平均时长 |
Innodb_row_lock_time_max | 锁的最大时长 |
Innodb_row_lock_waits | 等待锁的总次数 |
我们可根据以上参数,分析系统是否存在频繁进行锁等待、是否长时间锁等待,如果存在这些现象应该及时进行优化,提高sql的执行效率。
总结
InnoDB实现了行级锁,虽然行锁比较消耗资源,但是在并发能力上面比较优秀;如果在使用不当,行级锁可能升级为表锁,会让性能大幅下降甚至低于MyISAM引擎。
对于MySQL的锁:
- 尽可能的让数据检索使用索引,避免行锁升级为表锁;
- 合理设计索引,尽量缩小查询范围,减少被加锁的资源;
- 尽量减少大范围查询和不连续查询,防止间隙锁;
- 控制事务的大小,减少锁的资源量个时长;
- 在满足页面的情况下,使用较低的事务隔离。