这段时间一直在学习 MySQL 数据库. 项目组一直用的是 Oracle, 所以对 MySQL 的了解也不深. 本文主要是对 MySQL 锁的总结.
MySQL 的锁主要分为 3 大类:
表级锁: 存储引擎为 Myisam. 锁住整个表, 特点是开销小, 加锁快, 锁定力度大, 发生锁冲突的概率最高, 并发度最低.
页级锁: 存储引擎为 BDB. 锁住某一页的数据(16kb 左右), 特点: 开销和枷锁时间介于表级和行级之间; 会出现死锁, 锁定力度介于表锁和行锁之间, 并发度一般.
行级锁: 存储引擎为 innodb. 锁住某一行的数据, 特点: 锁的实现更加复杂, 开销大, 加锁速度慢.
根据以上特点, 仅从锁的角度来说: 表级锁更适合于以查询为主, 只有少量按索引条件更新数据的应用, 如 web 应用; 而行级锁则更适合于有大量按索引条件并发更新少量不同数据, 同时又有并发查询的应用, 如一些在线事务处理 (OLTP) 系统.
接下来进行行级锁的详解, 行级锁主要分为以下 7 类: 共享 / 排他锁, 意向锁, 记录锁, 间隙锁, 临建锁, 插入意向锁, 自增锁.
共享 / 排他锁:
共享锁: 又称读锁, 可以允许读, 但不能写. 共享锁可以与共享锁一起使用. 语句:
select ... lock in share mode
排他锁: 又称写锁, 不能允许读, 也不能允许写, 排他锁不能与其他所一起使用. 语句:
select ... for update
在 MySQL 中, update,delete,insert,alter 这些写的操作默认都会加上排他锁. Select 默认不会加任何锁类型. 一旦写数据的任务没有完成, 数据是不能被其他任务读取的, 这对并发操作有较大的影响.
意向锁: innoDB 为了支持多粒度的锁, 即允许行级锁和表级锁共存, 而引入意向锁. 意向锁是指未来的某个时刻, 事务可能要加共享 / 排他锁, 先提前声明一个意向. 这样如果有人尝试对全表进行修改, 就不需要判断表中的数据是否被加锁了, 只需要通过等待意向互斥锁被释放就行了.
意向共享锁(IS): 事务想要在获得表中某些记录的共享锁, 需要在表上先加意向共享锁.
意向互斥锁(IX): 事务想要在获得表中某些记录的互斥锁, 需要在表上先加意向互斥锁.
意向锁其实不会阻塞全表扫描之外的任何请求, 它们的主要目的是为了表示是否有人请求锁定表中的某一行数据.
记录锁(RS): 单个行记录上的锁. 记录锁总是会锁住索引记录, 如果 innoDB 存储引擎表
在建立的时候没有设置任何一个索引, 那么 innoDB 存储引擎会使用隐式的主键来进行锁定.
间隙锁(GR): 间隙锁锁住记录中的间隔, 即范围查询的记录.
Select * From user where id between 1 and 10 for update
这个脚本会锁住 1 到 10 的数据, 以防止其他事务修改该区间的记录;
间隙锁的主要目的, 就是为了防止其他事务在间隔中插入数据, 以导致 "不可重复读". 如果把事务的隔离级别降级为读提交(Read Committed, RC), 间隙锁则会自动失效
临建锁(next-key Locks): 临建锁是记录锁和间隙锁的组合, 锁的范围既包含记录又包含索引区间. 默认情况下, innoDB 使用临建锁来锁定记录. 但当查询的索引含有唯一属性的时候, 临建锁会进行优化, 将其降级为记录锁, 即仅锁住索引本身, 不是范围.
临键锁的主要目的, 也是为了避免幻读(Phantom Read). 如果把事务的隔离级别降级为 RC, 临键锁则也会失效.
插入意向锁(insert intention locks): 对已有数据行的修改和删除, 必须加互斥锁, 对于数据的插入, 加插入意向锁. 是专门针对于 insert 操作的.
自增锁(auto-inc locks): 是一种特殊的表级别的锁, 专门针对事务插入 auto-increment 类型的列. 最简单的情况, 如果一个事务正在往表中插入记录, 所有其他事务的插入必须等待, 以便第一个事务插入的行, 是连续的主键值.
----------------------------- 分界线 -----------------------------
接下看讲一下其他的锁:
死锁: 产生是因为线程锁之间交替等待产生的. 值两个或两个以上的事务在执行过程中, 因争夺资源而造成的一种相互等待的现象.
MySQL 处理死锁的方法: 根据数据写的数据量的大小来回滚小事务.
乐观 / 悲观锁:
乐观锁: 乐观的假定大概率不会发生并发更新冲突, 访问, 处理数据的过程中不加锁, 只在更新数据时根据版本号或时间戳判断是否有冲突, 有则处理, 无责提交事务.
如果系统并发量非常大, 悲观锁会带来非常大的性能问题, 选择使用乐观锁, 现在大部分应用属于乐观锁
悲观锁: 悲观的假定大概率会发生并发更新冲突, 访问, 处理数据前就加排他锁, 在整个数据处理过程中锁定数据, 事务提交或回滚后才释放锁.
优点:
悲观并发控制实际上是 "先取锁再访问" 的保守策略, 为数据处理的安全提供了保证.
缺点:
(a)在效率方面, 处理加锁的机制会让数据库产生额外的开销, 还有增加产生死锁的机会;
(b) 在只读型事务处理中由于不会产生冲突, 也没必要使用锁, 这样做只能增加系统负载; 还有会降低了并行性, 一个事务如果锁定了某行数据, 其他事务就必须等待该事务处理完才可以处理那行数
建议:
1. 控制事务的大小(操作写的数据量)
2. 使用锁的时候尽量要配合与携带索引的字段使用, 避免升级为表锁
3. 范围查询, 尽量减少基于范围查询的事务的大小
4. 如果业务必须要使用锁, 锁的冲突特别高的话, 改为表锁
5. 可以根据项目自身的情况调节事务的 innodb_flush_log_at_trx_commit
来源: http://www.linuxidc.com/Linux/2019-11/161378.htm