MySql锁
MySql锁
* | 行锁 | 表锁 | 页锁 |
---|---|---|---|
MyISAM | × | √ | × |
BDB | × | √ | √ |
InnoDB | √ | √ | × |
- 表锁:开销小,加锁快;不会出现死锁;锁定力度大,发生锁冲突概率高,并发度最低
- 行锁:开销大,加锁慢;会出现死锁;锁定粒度小,发生锁冲突的概率低,并发度高
- 页锁:开销和加锁速度介于表锁和行锁之间;会出现死锁;锁定粒度介于表锁和行锁之间,并发度一般
1.锁的分类
1.1 实现思想划分
(参考乐观锁悲观锁) - 乐观锁 - 悲观锁
1.2 从对数据操作的类型来分
- 读锁(S共享锁):针对同一份数据,多个读操作可以同时进行而不会互相影响。 >SELECT * FROM table_name WHERE … LOCK IN SHARE MODE
- 写锁(X排它锁):当前写操作没有完成前,它会阻断其他写锁和读锁。 >SELECT * FROM table_name WHERE … FOR UPDATE
1.3 从锁粒度划分
- 表级锁(table lock)
- 开销小,加锁快;不会出现死锁;锁定力度大,发生锁冲突概率高,并发度最低
- 行级锁(row lock)
- 开销大,加锁慢;会出现死锁;锁定粒度小,发生锁冲突的概率低,并发度高
表锁 LOCK TABLE mchopin READ;用读锁锁表,会阻塞其他事务修改表数据,但不会阻塞其他事务读该表。 LOCK TABLE mchopin WRITE;用写锁锁表,会阻塞其他事务读和写。 批量解锁:unlock tables; 行锁 select * from mchopin where id = 3 lock in share mode;读行锁,仅对一行数据加了读锁。 select * from mchopin where id = 3 for update;写行锁,仅对一行数据加了写锁。**
使用 select…for update 会把数据给锁住,不过需要注意一些锁的级别,MySQL InnoDB 默认行级锁。行级锁都是基于索引的,如果一条 SQL 语句用不到索引是不会使用行级锁的,会使用表级锁把整张表锁住,这点需要注意。
1.4 意向锁(表级锁)
IS锁:意向共享锁、Intention Shared Lock。事务打算给数据行加行共享锁,事务在给一个数据行加共享锁前必须先取得该表的IS锁。
IX锁,意向排他锁、Intention Exclusive Lock。事务打算给数据行加行排他锁,事务在给一个数据行加排他锁前必须先取得该表的IX锁。
意向锁也是数据库隐式帮我们做了,不需要程序员操心!意向锁不会锁住任何东西,除非有进行全表请求的操作,否则不会锁住任何数据。存在的意义只是用来表示有事务正在锁某一行的数据,或者将要锁某一行的数据。
1.5 记录锁(record Locks)
锁住某一行,如果表存在索引,那么记录锁是锁在索引上的,如果表没有索引,那么 InnoDB 会创建一个隐藏的聚簇索引加锁。所以在进行查询的时候尽量采用索引进行查询,这样可以降低锁的冲突。
1.6 间隙锁(Gap Locks)
- 间隙锁是一种记录行与记录行之间存在空隙或在第一行记录之前或最后一行记录之后产生的锁。间隙锁可能占据的单行,多行或者是空记录。通常的情况是采用范围查找的时候,比如在学生成绩管理系统中,如果此时有学生成绩 60,72,80,95,一个老师要查下成绩大于 72 的所有同学的信息,采用的语句是select * from student where grade > 72 for update,这个时候 InnoDB 锁住的不仅是 80,95,而是所有在 72-80,80-95,以及 95 以上的所有记录。
- 加锁点,不是加在记录上的,而是加在两条记录之间的位置。
- 作用:为了防止幻读,Repeatable read隔离级别下再通过GAP锁即可避免了幻读,两次当前读返回的是完全相同的记录。
- 幻读和不可重复读的关键点在于,幻读是数据增加了,而不可重复读是数据修改或删除了。从锁上来分析,幻读的关键是 GAP 锁,而不可重复读的关键是行锁。
1.7 Next-Key Locks
- NK是一种记录锁和间隙锁的组合锁
死锁
但一般来说MySQL通过回滚帮我们解决了不少死锁的问题了,但死锁是无法完全避免的,可以通过以下的经验参考,来尽可能少遇到死锁: - 1)以固定的顺序访问表和行。比如对两个job批量更新的情形,简单方法是对id列表先排序,后执行,这样就避免了交叉等待锁的情形;将两个事务的sql顺序调整为一致,也能避免死锁。 - 2)大事务拆小。大事务更倾向于死锁,如果业务允许,将大事务拆小。 - 3)在同一个事务中,尽可能做到一次锁定所需要的所有资源,减少死锁概率。 - 4)降低隔离级别。如果业务允许,将隔离级别调低也是较好的选择,比如将隔离级别从RR调整为RC,可以避免掉很多因为gap锁造成的死锁。 - 5)为表添加合理的索引。可以看到如果不走索引将会为表的每一行记录添加上锁,死锁的概率大大增大。
什么场景下用表锁?/什么时候走表锁(常问)
InnoDB默认采用行锁,在未使用索引字段查询时升级为表锁。MySQL这样设计并不是给你挖坑。它有自己的设计目的。即便你在条件中使用了索引字段,MySQL会根据自身的执行计划,考虑是否使用索引(所以explain命令中会有possible_key 和 key)。如果MySQL认为全表扫描效率更高,它就不会使用索引,这种情况下InnoDB将使用表锁,而不是行锁。因此,在分析锁冲突时,别忘了检查SQL的执行计划,以确认是否真正使用了索引。
第一种情况:全表更新。事务需要更新大部分或全部数据,且表又比较大。若使用行锁,会导致事务执行效率低,从而可能造成其他事务长时间锁等待和更多的锁冲突。
第二种情况:多表查询。事务涉及多个表,比较复杂的关联查询,很可能引起死锁,造成大量事务回滚。这种情况若能一次性锁定事务涉及的表,从而可以避免死锁、减少数据库因事务回滚带来的开销。