共计 2251 个字符,预计需要花费 6 分钟才能阅读完成。
这段时间一直在学习 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
: