mysql的锁的几种分类
mysql的锁是按照分类的方式来区分的,所以我们无法具象的说mysql到底有哪些锁。但是可以说按照锁粒度可以分为页锁,表锁,行锁。
按照锁粒度可以分为:全局锁(多用于数据备份),表锁(不会产生死锁),行锁(会产生死锁)。
按照加锁机制可以分为:乐观锁和悲观锁。
按照兼容性可以分为:共享锁和排它锁。
乐观锁和悲观锁
悲观锁
顾名思义,就是对于数据的处理持悲观态度,总认为会发生并发冲突,获取和修改数据时,别人会修改数据。
悲观锁的实现,通常依靠数据库提供的锁机制实现,比如mysql的排他锁,select .... for update来实现悲观锁。
注意:(???)
使用悲观锁,需要关闭mysql的自动提交功能,将 set autocommit = 0;如果采用了autocommint模式运行,这意味着,当执行一个用于更新/修改表的语句之后,mysql立刻更新到缓存中,同时记录锁也会被释放。因此如果事务要执行多条更新(修改)语句,那么从第2条更新语句开始就是在无锁条件下执行了,这样会导致事务失效,破坏数据一致性。
mysql中的行级锁是基于索引的,如果sql没有走索引,那将使用表级锁把整张表锁住;
乐观锁
顾名思义,就是对数据的处理持乐观态度,乐观的认为数据一般情况下不会发生冲突,只有提交数据更新时,才会对数据是否冲突进行检测。
乐观锁的实现不依靠数据库提供的锁机制,需要我们自已实现,实现方式一般是记录数据版本,快照读。
Myisam的锁
myisam锁的兼容性:
按照兼容性myisam锁分为:表共享读锁和表独占写锁(也就是上面分类提到的共享锁和排他锁,只是在myisam内的名称有所不同)。
对于读操作,不会阻塞其他用户对同一表的读请求,但会阻塞对同一表的写请求。
对于写操作,则会阻塞其他用户对同一表的读和写请求。
表锁的的读操作和写操作之间,以及写操作与写操作之间是串行的。当一个线程获得对一个表的写锁后,只有持有锁的线程可以对表进行更新操作。其他线程的读、写操作都会等待,直到锁被释放为止。
myisam的并发插入
myisam的读和写是串行的,但这是就总体而言的。在一定条件下,myisam也支持查询和插入操作的并发进行。
myisam存储引擎有一个系统变量concurrent_insert,专门用以控制其并发插入的行为。
myisam的表锁的调度
一个进程请求某个myisam表的读锁,同时另一个进程也请求同一表的写锁,mysql如何处理呢?答案是写进程先获得锁。即使读请求先到锁等待队列,写请求后到,写锁也会插到读锁请求之前,这是因为mysql认为写请求一般比读请求重要。这也正是myisam表不太适合有大量更新操作和查询操作应用的原因。因为,大量的更新操作会造成查询操作很难获得读锁,从而可能永远阻塞。当然,也可以通过一些设置来调节myisam的调度行为。
Innodb的锁
innodb按照兼容性来分类:共享锁和排它锁两种行级锁。
共享锁:允许一个事务去读一行,阻止其他事务获得相同数据集的排他锁。
排它锁:允许获取排他锁的事务更新数据,阻止其他事务取得相同的数据集共享读锁和排他写锁。
共享锁可以理解为多个事务只能读加共享锁数据,但是不能改数据。对于排他锁大家的理解可能就有些差别,我当初就犯了一个错误,以为排他锁锁住一行数据后,其他事务就不能读取和修改该行数据,其实不是这样的。排他锁指的是一个事务在一行数据加上排他锁后,其他事务不能再在其上加其他的锁,但是仍然可以进行普通无锁查询。也就是说使用select的普通查询,但是select...for update或select … lock in share mode查询语句是不能查询的。
innodb行锁是通过给索引上的索引项加锁来实现的,innodb这种行锁实现特点意味着:只有通过索引条件检索数据,innodb才使用行级锁,否则,innodb将使用表锁。
innodb使用表锁的例子:
Flush tables with read lock(这个命令是全局读锁定,执行了命令之后所有库所有表都被锁定只读)和select * from user where name = "libis" for update; 其中name字段不是user表的索引,这种情况下innodb会上表锁。innodb中的行锁的实现依赖于索引,一旦某个加锁操作没有使用到索引,那么该锁就会退化为表锁。
innodb按照锁粒度来分类:表锁(意向锁,自增锁),行锁(间隙锁,记录锁,临键锁,插入意向锁)。
意向锁
意向锁存在的意义:因为indodb支持行锁和表锁,如果事务A对一行数据加了行锁。这个时候事务B来了,要对这行所在的表加表锁,如果可以加上表锁证明事务B对该表的所有行都有更改的权限,这个时候就要对表的每一行检测是不是上了行锁,但是这种情况的性能效率是非常低的。所以引入了意向锁,这样事务A在对行加锁的时候会提前加上意向锁,然后事务B来对表加锁的时候只需要判断这个表是否有意向锁就可以知道是否能加表锁了。如果可以的话进行兼容,不可以的话阻塞即可。
自增锁
mysql表的主键要设置自增呢?
mysql表的索引是以B+树数据结构构成的,B+树索引的关键字都是有序的,所以我们在主键上设置自增属性,可以保证每次插入都是插入到最后面,可以有效的减少索引页的分裂和数据的移动。
自增锁是mysql一种特殊的表锁,如果表中存在自增字段,mysql便会自动维护一个自增锁,专门针对事务插入AUTO_INCREMENT类型的列。
如果一个事务正在往表中插入记录,所有其他事务的插入必须等待,以便第一个事务插入的行,是连续的主键值。
记录锁
记录锁、间隙锁、临键锁都是排它锁。
记录锁的使用方法跟排它锁一致。
例子:select for update/update from test where id = 100(id = 100存在,id是唯一索引否则会变成临键锁);
间隙锁
什么是间隙锁?
当我们用范围条件而不是相等条件检索数据,并请求共享或排他锁时,innodb会给符合条件的已有数据的索引项加锁;在条件范围内但并不存在的记录,innodb也会对这个记录加锁,这种锁机制就是间隙锁。
产生间隙锁的条件(RR事务隔离级别下)?
1.使用普通索引条件。
2.使用唯一索引锁定多行记录(如id为唯一索引,where id > 1或 where id = 1但是id = 1这条数据不存在)。
3.使用多列唯一索引(如id,name为多列唯一索引,where id > 1 and name = '小明')。
例子:表内包含id = 1,2,3...100;101;select for update/update from 表 where id > 100;InnoDB不仅会对符合条件的id值为101的记录加锁;也会对id大于101(这些记录并不存在)的“间隙”加锁;
间隙锁的目的?
1.防止幻读,以满足相关隔离级别的要求;例子:事务A select where id < 4;事务B insert into (id) value (2);事务A再次查询 select where id < 4;
2.防止数据误删/改;例子:事务A update/delete where id < 4;事务B insert into (id) value (2);
临键锁(可以理解为记录锁+间隙锁)
每个数据行上的非唯一索引,非主键列上都会存在一把临键锁,当某个事务持有该数据行的临键锁时,会锁住一段左开右闭区间的数据。
临键锁的目的?
1.可以解决幻读的问题;
2.防止数据误删/改;
插入意向锁
对已有数据行的修改与删除,必须加强互斥锁(X锁),那么对于数据的插入,是否还需要加这么强的锁,来实施互斥呢?插入意向锁,孕育而生。
插入意向锁,是间隙锁的一种(所以,也是实施在索引上的),它是专门针对insert操作的。多个事务,在同一个索引,同一个范围区间插入记录时,如果插入的位置不冲突,不会阻塞彼此。
例子:事务A先执行,在10与24两条记录中插入了一行,还未提交;insert into lock_example values(11,23, 'Jim');事务B后执行,也在10与24两条记录中插入了一行;insert into lock_example values(12,24, 'Bob');因为是插入操作,虽然是插入同一个区间,但是插入的记录并不冲突,所以使用的是插入意向锁,此处A事务并不会阻塞B事务。
总结
以上总结的7种锁,可以按两种方式来区分:
1. 按锁的兼容性来划分,可以分为共享、排他锁;
共享锁(S锁、IS锁),可以提高读读并发;
为了保证数据强一致,InnoDB使用强互斥锁(X锁、IX锁),保证同一行记录修改与删除的串行性;
2. 按锁的粒度来划分,可以分为:表锁:意向锁(IS锁、IX锁)、自增锁;行锁:记录锁、间隙锁、临键锁、插入意向锁;
其中,InnoDB的细粒度锁(即行锁),是实现在索引记录上的(如果未命中索引则会失效);
记录锁锁定索引记录;间隙锁锁定间隔,防止间隔中被其他事务插入;临键锁锁定索引记录+间隔,防止幻读;
InnoDB使用插入意向锁,可以提高插入并发;
间隙锁与临键锁只在Repeatable-Read(RR可重复读)以上的级别生效,Read-Committed(RC已提交读)下会失效。