尚硅谷-MySql-高级思维导图:思维导图(mmap+HTML格式)

1. 概述

1. 定义

  • 锁是计算机协调多个进程或线程并发访问某一资源的机制。
  • 在数据库中,除传统的计算资源(如CPU、RAM、I/O等)的争用以外,数据也是一种供许多用户共享的资源。如何保证数据并发访问的一致性、有效性是所有数据库必须解决的一个问题,锁冲突也是影响数据库并发访问性能的一个重要因素。从这个角度来说,锁对数据库而言显得尤其重要,也更加复杂。

2. 举例

打个比方,我们到淘宝上买一件商品,商品只有一件库存,这个时候如果还有另一个人买,那么如何解决是你买到还是另一个人买到的问题?

尚硅谷 mysql 演示数据库_mysql

这里肯定要用到事务,我们先从库存表中取出物品数量,然后插入订单,付款后插入付款表信息,然后更新商品数量。在这个过程中,使用锁可以对有限的资源进行保护,解决隔离和并发的矛盾。

3. 锁的分类

  1. 按照对数据操作的类型(读/写)来分
  • 读锁(共享锁):针对同一份数据,多个读操作可以同时进行而不会互相影响
  • 写锁(排他锁):当前写操作没有完成前,他会阻断其他写操作和读操作
  1. 按照对数据操作的粒度来分
  • 表锁
  • 行锁
  • 页锁

2. 表锁(偏向读操作)

1. 特点

  1. 偏向 MyISAM 存储引擎
  2. 开销小
  3. 加锁快
  4. 无死锁
  5. 锁定粒度大
  6. 发生锁冲突的概率高
  7. 并发度最低

2. 使用

  1. 手动增加表锁(读/写)
lock table 表名字1 read(write),表名字2 read(write)
  1. 查看表上加过的锁
show open tables;

尚硅谷 mysql 演示数据库_数据_02

  • In_user 部分 为 1 :加锁 ; 为 0 :没加锁
  1. 释放表锁
unlock tables;

3. 总结

  1. MyISAM在执行查询语句(SELECT)前,会自动给涉及的所有表加读锁,在执行增删改操作前,会自动给涉及的表加写锁。
  2. MySQL的表级锁有两种模式:
  • 表共享读锁(Table Read Lock)
  • 表独占写锁(Table Write Lock)
  1. 读锁会阻塞写,但是不会堵塞读。而写锁则会把读和写都堵塞

锁类型

自己读

自己读其他表

自己写

他人读

他人读其他表

他人写

读锁






阻塞

写锁




阻塞


阻塞

4. 表锁分析

  1. 查看哪些表被加锁了
show open tables;
  1. 分析表锁定
show status like 'table%';

可以通过检查 Table_locks_waited 和 Table_locks_immediate 状态变量来分析系统上的表锁定

  • Table_locks_immediate:产生表级锁的次数,表示可以立即获取锁的查询次数,每立即获取锁值 +1
  • Table_locks_waited:出现表级锁争用而发生等待的次数(不能立即获取锁的次数,每等待一次值 +1),此值高则说明存在较严重的表级锁争用情况。
  1. 此外,Myisam的读写锁调度是写优先,这也是myisam不适合做写为主表的引擎。因为写锁后,其他线程不能做任何操作,大量的更新会使查询很难得到锁,从而造成永远阻塞

3. 行锁(偏向写操作)

1. 特点

  1. 偏向 InnoDB 存储引擎
  2. 开销大
  3. 加锁慢
  4. 会出现死锁
  5. 锁定粒度最小
  6. 发生锁冲突的概率最低
  7. 并发度最高
  8. 支持事务
  • InnoDB 与 MyISAM 最大的不同就是:支持事务、以及采用了行级锁

2. 事务复习

  1. 事务的ACID
  2. 并发事务带来的问题
  1. 更新丢失
    最后的更新操作,覆盖了前面其他事务所作的更新。
  2. 脏读
    事务A读取到了事务B已修改但尚未提交的数据,甚至还做了修改
  3. 不可重复读
    再次读以前读取的数据时,发现该数据已经改变了
  4. 幻读
    事务A读取到事务B提交的新增数据
  1. 事务隔离级别

3. 行锁的特性

  1. 多个线程同时操作时:
  1. 自己锁的行,修改了数据,自己直接读取,他人读取的是锁之前的数据,(读已之写),
  2. 自己锁了行,不释放,他人只能阻塞
  3. 自己锁了第一行,他人可以正常读写其他行
  1. 无索引(或索引失效)时,行锁会升级为表锁
  2. 间隙锁的危害

间隙锁:当使用范围查询时,键值在条件范围内但不存在的数据,叫做间隙。请求共享或排他锁时,InnoDB 会给所有满足条件的数据记录的索引加锁。同时也会给间隙加锁。

  • 宁可错杀不可放过

4. 锁定某一行

  1. 加共享锁(读锁)
select...(查询语句) LOCK IN SHARE MODE;
  • 共享锁(Share Lock)
  • 共享锁又称读锁,是读取操作创建的锁。其他用户可以并发读取数据,但任何事务都不能对数据进行修改(获取数据上的排他锁),直到已释放所有共享锁。
  • 如果事务T对数据A加上共享锁后,则其他事务只能对A再加共享锁,不能加排他锁。获准共享锁的事务只能读数据,不能修改数据。
  • 在查询语句后面增加 LOCK IN SHARE MODE ,Mysql会对查询结果中的每行都加共享锁,当没有其他线程对查询结果集中的任何一行使用排他锁时,可以成功申请共享锁,否则会被阻塞。其他线程也可以读取使用了共享锁的表(行?),而且这些线程读取的是同一个版本的数据。
  1. 加排他锁(写锁)
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. 总结

  1. 尽可能让所有数据检索都通过索引来完成,避免无索引行锁升级为表锁
  2. 尽可能使用较少检索条件,避免间隙锁
  3. 尽量控制事务大小,减少锁定资源量和时间长度
  4. 锁住某行后,尽量不要去调别的行或表,赶紧处理被锁住的行,然后释放掉锁
  5. 涉及相同表的事务,对于调用表的顺序尽量保持一致
  6. 在业务环境允许的情况下,尽可能低级别事务隔离
  7. 行锁的性能损耗比表锁跟高,但是 InnoDB 整体并发处理能立远远优于 MyISAM 的表级锁,当并发量高时其优势更明显。但是使用不当时,性能也会严重下降,甚至低于 MyISAM

4. 表锁

  • 了解即可

1. 特点

  1. 开销和加锁时间介于表锁和行锁之间
  2. 会出现死锁
  3. 锁定粒度介于表锁和行锁之间
  4. 并发度介于表锁和行锁之间