尚硅谷-MySql-高级思维导图:思维导图(mmap+HTML格式)
1. 概述
1. 定义
- 锁是计算机协调多个进程或线程并发访问某一资源的机制。
- 在数据库中,除传统的计算资源(如CPU、RAM、I/O等)的争用以外,数据也是一种供许多用户共享的资源。如何保证数据并发访问的一致性、有效性是所有数据库必须解决的一个问题,锁冲突也是影响数据库并发访问性能的一个重要因素。从这个角度来说,锁对数据库而言显得尤其重要,也更加复杂。
2. 举例
打个比方,我们到淘宝上买一件商品,商品只有一件库存,这个时候如果还有另一个人买,那么如何解决是你买到还是另一个人买到的问题?
这里肯定要用到事务,我们先从库存表中取出物品数量,然后插入订单,付款后插入付款表信息,然后更新商品数量。在这个过程中,使用锁可以对有限的资源进行保护,解决隔离和并发的矛盾。
3. 锁的分类
- 按照对数据操作的类型(读/写)来分
- 读锁(共享锁):针对同一份数据,多个读操作可以同时进行而不会互相影响
- 写锁(排他锁):当前写操作没有完成前,他会阻断其他写操作和读操作
- 按照对数据操作的粒度来分
- 表锁
- 行锁
- 页锁
2. 表锁(偏向读操作)
1. 特点
- 偏向 MyISAM 存储引擎
- 开销小
- 加锁快
- 无死锁
- 锁定粒度大
- 发生锁冲突的概率高
- 并发度最低
2. 使用
- 手动增加表锁(读/写)
lock table 表名字1 read(write),表名字2 read(write)
- 查看表上加过的锁
show open tables;
- In_user 部分 为 1 :加锁 ; 为 0 :没加锁
- 释放表锁
unlock tables;
3. 总结
- MyISAM在执行查询语句(SELECT)前,会自动给涉及的所有表加读锁,在执行增删改操作前,会自动给涉及的表加写锁。
- MySQL的表级锁有两种模式:
- 表共享读锁(Table Read Lock)
- 表独占写锁(Table Write Lock)
- 读锁会阻塞写,但是不会堵塞读。而写锁则会把读和写都堵塞
锁类型 | 自己读 | 自己读其他表 | 自己写 | 他人读 | 他人读其他表 | 他人写 |
读锁 | 可 | 否 | 否 | 可 | 可 | 阻塞 |
写锁 | 可 | 否 | 可 | 阻塞 | 可 | 阻塞 |
4. 表锁分析
- 查看哪些表被加锁了
show open tables;
- 分析表锁定
show status like 'table%';
可以通过检查 Table_locks_waited 和 Table_locks_immediate 状态变量来分析系统上的表锁定
- Table_locks_immediate:产生表级锁的次数,表示可以立即获取锁的查询次数,每立即获取锁值 +1
- Table_locks_waited:出现表级锁争用而发生等待的次数(不能立即获取锁的次数,每等待一次值 +1),此值高则说明存在较严重的表级锁争用情况。
- 此外,Myisam的读写锁调度是写优先,这也是myisam不适合做写为主表的引擎。因为写锁后,其他线程不能做任何操作,大量的更新会使查询很难得到锁,从而造成永远阻塞
3. 行锁(偏向写操作)
1. 特点
- 偏向 InnoDB 存储引擎
- 开销大
- 加锁慢
- 会出现死锁
- 锁定粒度最小
- 发生锁冲突的概率最低
- 并发度最高
- 支持事务
- InnoDB 与 MyISAM 最大的不同就是:支持事务、以及采用了行级锁
2. 事务复习
- 事务的ACID
- 并发事务带来的问题
- 更新丢失
最后的更新操作,覆盖了前面其他事务所作的更新。 - 脏读
事务A读取到了事务B已修改但尚未提交的数据,甚至还做了修改 - 不可重复读
再次读以前读取的数据时,发现该数据已经改变了 - 幻读
事务A读取到事务B提交的新增数据
- 事务隔离级别
3. 行锁的特性
- 多个线程同时操作时:
- 自己锁的行,修改了数据,自己直接读取,他人读取的是锁之前的数据,(读已之写),
- 自己锁了行,不释放,他人只能阻塞
- 自己锁了第一行,他人可以正常读写其他行
- 无索引(或索引失效)时,行锁会升级为表锁
- 间隙锁的危害
间隙锁:当使用范围查询时,键值在条件范围内但不存在的数据,叫做间隙。请求共享或排他锁时,InnoDB 会给所有满足条件的数据记录的索引加锁。同时也会给间隙加锁。
- 宁可错杀不可放过
4. 锁定某一行
- 加共享锁(读锁)
select...(查询语句) LOCK IN SHARE MODE;
- 共享锁(Share Lock)
- 共享锁又称读锁,是读取操作创建的锁。其他用户可以并发读取数据,但任何事务都不能对数据进行修改(获取数据上的排他锁),直到已释放所有共享锁。
- 如果事务T对数据A加上共享锁后,则其他事务只能对A再加共享锁,不能加排他锁。获准共享锁的事务只能读数据,不能修改数据。
- 在查询语句后面增加 LOCK IN SHARE MODE ,Mysql会对查询结果中的每行都加共享锁,当没有其他线程对查询结果集中的任何一行使用排他锁时,可以成功申请共享锁,否则会被阻塞。其他线程也可以读取使用了共享锁的表(行?),而且这些线程读取的是同一个版本的数据。
- 加排他锁(写锁)
SELECT ... FOR UPDATE;
- 排他锁(eXclusive Lock)
- 如果事务T对数据A加上排他锁后,则其他事务不能再对A加任任何类型的封锁。获准排他锁的事务既能读数据,又能修改数据。
- 在查询语句后面增加 FOR UPDATE ,Mysql会对查询结果中的每行都加排他锁,当没有其他线程对查询结果集中的任何一行使用排他锁时,可以成功申请排他锁,否则会被阻塞。
5. 行锁的分析
- 通过检查InnoDB_row_lock状态变量来分析系统上的行锁的争夺情况
show status like 'innodb_row_lock%';
- 对各个状态量的说明如下:
Innodb_row_lock_current_waits:当前正在等待锁定的数量;
Innodb_row_lock_time:(*
)从系统启动到现在锁定总时间长度;
Innodb_row_lock_time_avg:(*
)每次等待所花平均时间;
Innodb_row_lock_time_max:从系统启动到现在等待最常的一次所花的时间;
Innodb_row_lock_waits:(*
)系统启动后到现在总共等待的次数;
尤其是当等待次数很高,而且每次等待时长也不小的时候,我们就需要分析系统中为什么会有如此多的等待,然后根据分析结果着手指定优化计划。
- 查询正在被锁阻塞的sql语句
SELECT * FROM information_schema.INNODB_TRX\G;
5. 总结
- 尽可能让所有数据检索都通过索引来完成,避免无索引行锁升级为表锁
- 尽可能使用较少检索条件,避免间隙锁
- 尽量控制事务大小,减少锁定资源量和时间长度
- 锁住某行后,尽量不要去调别的行或表,赶紧处理被锁住的行,然后释放掉锁
- 涉及相同表的事务,对于调用表的顺序尽量保持一致
- 在业务环境允许的情况下,尽可能低级别事务隔离
- 行锁的性能损耗比表锁跟高,但是 InnoDB 整体并发处理能立远远优于 MyISAM 的表级锁,当并发量高时其优势更明显。但是使用不当时,性能也会严重下降,甚至低于 MyISAM
4. 表锁
- 了解即可
1. 特点
- 开销和加锁时间介于表锁和行锁之间
- 会出现死锁
- 锁定粒度介于表锁和行锁之间
- 并发度介于表锁和行锁之间