锁是计算机协调多个进程或线程并发访问某一资源的机制
我们认知中的行锁,表锁等,读锁,写锁等,都是在做操作之前先上锁,这些锁统称为悲观锁
MySQL的锁机制比较简单,其最显著的特点是不同的存储引擎支持不同的锁机制。比如,MyISAM和MEMORY存储引擎采用的是表级锁(table-level locking);BDB存储引擎采用的是页面锁(page-level locking),但也支持表级锁;InnoDB存储引擎既支持行级锁(row-level locking),也支持表级锁,但默认情况下是采用行级锁。
从锁的角度来说:
MySQL的表级锁有两种模式:表共享读锁(Table Read Lock)和表独占写锁(Table Write Lock)。
InnoDB与MyISAM的不同:
- 是否支持行级锁 : MyISAM 只有表级锁(table-level locking),而InnoDB 支持行级锁(row-level locking)和表级锁,默认为行级锁。
- 是否支持事务和崩溃后的安全恢复: MyISAM 强调的是性能,每次查询具有原子性,其执行速度比InnoDB类型更快,但是不提供事务支持。但是InnoDB 提供事务支持事务,外部键等高级数据库功能。 具有事务(commit)、回滚(rollback)和崩溃修复能力(crash recovery capabilities)的事务安全(transaction-safe (ACID compliant))型表。
- 是否支持外键: MyISAM不支持,而InnoDB支持
- 是否支持MVCC :仅 InnoDB 支持。应对高并发事务, MVCC比单纯的加锁更高效;MVCC只在 READ COMMITTED 和 REPEATABLE READ 两个隔离级别下工作;MVCC可以使用 乐观(optimistic)锁 和 悲观(pessimistic)锁来实现;各数据库中MVCC实现并不统一
InnoDB实现了两种类型的行锁。
InnoDB行锁是通过给索引上的索引项加锁来实现的。
InnoDB这种行锁实现特点:
- 只有通过索引条件检索数据,InnoDB才使用行级锁,否则锁表
- 不论条件是普通索引还是主键索引,都会锁当前记录
- 建议操作普通字段,条件索引字段
共享锁(S):允许一个事务去读一行,阻止其他事务获得相同的数据集的排他锁。
select … lock in share mode
排他锁(X):允许获得排他锁的事务更新数据,但是阻止其他事务获得相同数据集的共享锁和排他锁。
select … for update insert update delete
可以这么理解:
共享锁就是我读的时候,你可以读,但是不能写。排他锁就是我写的时候,你不能读也不能写。其实就是MyISAM的读锁和写锁,但是针对的对象不同了而已。
这两种方式在事务(Transaction) 进行当中SELECT 到同一个数据表时,都必须等待其它事务数据被提交(Commit)后才会执行。而主要的不同在于LOCK IN SHARE MODE 在有一方事务要Update 同一个表单时很容易造成死锁 (所以实际项目中基本不使用共享锁IS)。
简单的说,如果SELECT 后面若要UPDATE 同一个表单,最好使用SELECT … for UPDATE (上面的悲观锁就是用for update来实现的)。
除此之外InnoDB还有两个表锁:
间隙锁作用在索引记录之间的间隔,又或者作用在第一个索引之前,最后一个索引之后的间隙。不包括索引本身。
这是出现在RR隔离级别下的一种锁,目的是防止幻读,MySQL会锁住相应数据的临近范围而避免其他事务再去插入新的数据,我们把它叫做间隙锁。
很显然,在使用范围条件检索并锁定记录时,InnoDB这种加锁机制会阻塞符合条件范围内键值的并发插入,这往往会造成严重的锁等待。因此,在实际应用开发中,尤其是并发插入比较多的应用,我们要尽量优化业务逻辑,尽量使用相等条件来访问更新数据,避免使用范围条件。
所以在很多情况下,我们会直接为了避免出现间隙锁,直接放弃RR这个MySql默认隔离级别而采用RC。
Next-key锁实际上是Record锁和gap锁的组合。Next-key锁是在下一个索引记录本身和索引之前的gap加上S锁或是X锁(如果是读就加上S锁,如果是写就加X锁)。
默认情况下,InnoDB的事务隔离级别为RR,系统参数innodb_locks_unsafe_for_binlog
的值为false
。InnoDB使用next-key锁对索引进行扫描和搜索,这样就读取不到幻象行,避免了幻读
的发生。
需要强调一下,意向锁是一种不与行级锁冲突表级锁
,这一点非常重要。意向锁分为两种:
意向共享锁
(intention shared lock, IS):事务有意向对表中的某些行加共享锁(S锁)
-- 事务要获取某些行的 S 锁,必须先获得表的 IS 锁。 SELECT column FROM table ... LOCK IN SHARE MODE;
意向排他锁
(intention exclusive lock, IX):事务有意向对表中的某些行加排他锁(X锁)
-- 事务要获取某些行的 X 锁,必须先获得表的 IX 锁。 SELECT column FROM table ... FOR UPDATE;
即:意向锁是有数据引擎自己维护的,用户无法手动操作意向锁
,在为数据行加共享 / 排他锁之前,InooDB 会先获取该数据行所在在数据表的对应意向锁。MySql InnoDB 中意向锁的作用
针对当前读
select ... for update
索引使用条件\索引类型 | 聚簇索引 | 二级唯一索引 | 二级非唯一索引 |
---|---|---|---|
精确匹配(RC级别) | 命中索引项记录锁 | 命中索引项、对应聚簇索引项记录锁 | 命中索引项、对应聚簇索引项记录锁 |
精确匹配(RR级别) | 命中索引项记录锁 | 命中索引项、对应聚簇索引项记录锁 | 命中索引项临键锁,后一索引项间隙锁,对应聚簇索引项记录锁 |
范围匹配(RC级别) | 所有命中索引项记录锁 | 所有命中索引项、对应聚簇索引项记录锁 | 所有命中索引项、对应聚簇索引项记录锁 |
范围匹配(RR级别) | 所有命中索引项、后一索引项临键锁 | 所有命中索引项、后一索引项临键锁,对应聚簇索引项记录锁 | 所有命中索引项、后一索引项临键锁,对应聚簇索引项记录锁 |
索引未命中(RC级别) | 不加锁 | 不加锁 | 不加锁 |
索引未命中(RR级别) | 未命中索引与前后索引的间隔加gap锁 | 未命中索引与前后索引的间隔加gap锁 | 未命中索引与前后索引的间隔加gap锁 |
不使用索引(RC级别) | 全表扫描,读到的每行加记录锁(释放不符合条件的锁) | ||
不使用索引(RR级别) | 全表扫描,读到的每一行加临键锁,直到事务结束释放 |