1.共享锁,排他锁,也叫读锁和写锁
共享锁(S锁)(读锁):事务在读取记录的时候获取共享锁,允许其它事务同时获取共享锁。
排他锁(X锁)(写锁):事务在修改记录的时候获取排他锁,只允许一个事务获取排他锁,其它事务会阻塞等待。
读锁和写锁是互斥的:
一个事务占用了某条记录的读锁,另一个事务想要获取写锁(可以获取读),需要等待读锁先释放。
一个事务占用了某条记录的写锁,另一个事务想要获取读或者写锁,需要等待写锁先释放。
2.表锁、行锁、页锁
================================================================
表锁:对整张表进行加锁,加锁之后其它事务无法对表进行任何读写操作。
表锁又分为:
1)表级别的S锁、X锁:
LOCK TABLES t READ; # 存储引擎会对表t加表级别的共享锁。共享锁也叫读锁或S锁(Share的缩写)
LOCK TABLES t WRITE; # 存储引擎会对表t加表级别的排他锁。排他锁也叫独占锁、写锁或X锁(exclusive的缩写)
一般不会使用,锁定整张表效率低。
给表加了读锁之后,自己可以读,自己不能写,而且不能操作其它的表,他人可读,他人写的话得等待。
给表加了写锁之后,自己可以读,自己可以写,而且不能操作其它的表,他人读需要等待,他人写也得等待。
2)意向锁:
意向锁的存在是为了协调行锁和表锁的关系。
假如业务上真用到了表锁,那么表锁和行锁之间肯定会存在冲突,当InnoDB加表锁的时候,如何判断表里面是否有行锁?难道一条条记录遍历去找?效率太低。所以有了意向锁。
意向锁分为:意向共享锁(IS)、意向排他锁(IX)
意向共享锁(IS): 当需要对表中的记录加共享锁的时候,先在表上加个意向共享锁,表示此时表内有共享锁。
例如:
事务要获取某些行的 S 锁,必须先获得表的 IS 锁。
SELECT column FROM table ... LOCK IN SHARE MODE;
意向排他锁(IX): 当需要对表中的记录加排他锁的时候,先在表上加个意向排他锁,表示此时表内有排他锁。
例如:
事务要获取某些行的 X 锁,必须先获得表的 IX 锁。
SELECT column FROM table ... FOR UPDATE;
这样一来,如果要加表锁,就不用去遍历了,直接根据意向锁进行判断。
如果要加表级别的S锁,如果表上没有IX,说明表中没有记录有排他锁,就可以直接上表级别的S锁。
如果要加表级别的X锁,如果表上没有IX和IS,说明表中的记录没有锁,就可以直接上表级别的X锁。
互斥性:
1.所有意向锁之间是相互兼容的。
2.只有表级别的共享锁和意向共享锁是兼容的,其它表级别的共享锁和排他锁和意向锁都是互斥的。
3.IX,IS是表级锁,不会和行级的X,S锁发生冲突。
例子:
事务A先获得了某一行的排他锁,并未提交:
BEGIN;
SELECT * FROM teacher WHERE id = 6 FOR UPDATE;
事务A获取了teacher表上的意向排他锁。事务A获取了id为6的数据行上的排他锁。
之后事务B想要获取teacher表上的共享锁。
BEGIN;
LOCK TABLES teacher READ;
事务B检测到事务A持有teacher表的意向排他锁。事务B对teacher表的加锁请求被阻塞(排斥)。
最后事务C也想获取teacher表中某一行的排他锁。
BEGIN;
SELECT * FROM teacher WHERE id = 5 FOR UPDATE;
事务C申请teacher表的意向排他锁。事务C检测到事务A持有teacher表的意向排他锁。
因为意向锁之间并不互斥,所以事务C获取到了teacher表的意向排他锁。因为id为5的数据行
上不存在任何排他锁,最终事务C成功获取到了该数据行上的排他锁。
3)元数据锁:
当对一个表做增删改查操作的时候,加 MDL读锁;当要对表做结构变更操作的时候,加 MDL 写锁。
读锁之间不互斥,因此你可以有多个线程同时对一张表增删查改。读写锁之间、写锁之间都是互斥的,用来保证变更表结构操作的安全性,解决了DML和DDL操作之间的一致性问题。
不需要显式使用
,在访问一个表的时候会被自动加上。
4)自增锁:
用于自增列插入数据时使用。在插入数据的时候,会在表上加上auto-inc lock,然后为自增列分配递增的值在语句插入结束之后,再释放auto-inc lock。
自增锁是在语句插入结束之后才释放锁,效率比较低,所以后面弄了个互斥量(mutex)来进行自增减的累加,互斥量是在语句插入的时候,获取递增值之后,就可以释放锁,所以性能更好。
mysql有参数innodb_autoinc_lock_mode
来控制使用自增锁还是互斥量,有3个值:
0:只使用自增锁
1:默认值,对于插入前已经知道行数的插入,用互斥量;插入前不知道具体插入行数的插入,用自增锁。这种最安全,statement格式的binlog也是安全的。
2: 只用互斥量
================================================================
行锁:仅对特定的行进行加锁,允许其它事务并发访问其它不同的行。
行锁又分为:
1)记录锁(record locks)
锁定当前的记录。对某条记录执行增删改的时候,会在这条记录上加记录锁。
记录锁是有S锁和X锁之分的,称之为 S型记录锁
和 X型记录锁
。
- 当一个事务获取了一条记录的S型记录锁后,其他事务也可以继续获取该记录的S型记录锁,但不可以继续获取X型记录锁;
- 当一个事务获取了一条记录的X型记录锁后,其他事务既不可以继续获取该记录的S型记录锁,也不可以继续获取X型记录锁。
2)间隙锁(Gap locks)
锁定的是一个间隙,防止其它事务插入数据到间隙中。
间隙锁之间不会冲突,也就是说不同的事务可以对同一个间隙同时加锁。因为间隙锁的目的是一致的,都是为了防止其它事务插入数据到间隙中。
3)临键锁(next-key locks)
临键锁 = 记录锁 + 间隙锁
除了锁定间隙,还锁定某条记录。
例如下面的语句:会产生临键锁,锁定id为8的这条记录,还锁定3~8的区间。锁定的范围是(3, 8].
begin;
select * from student where id <=8 and id > 3 for update;
4)插入意向锁
插入意向锁,它是一类间隙锁,但是它不是锁定间隙,而是等待间隙。
如果一个间隙上被加了间隙锁,这时候来了一个新的事务,想要往这个间隙上插入数据,那么这个事务会被间隙锁给阻塞,这时候新事务会生成一个插入意向锁,表示等待这个间隙锁的释放。
插入意向锁之间不会相互阻塞,因为他们的目的相同,都是等待这个间隙被释放。
3.页锁
================================================================
页锁:
页锁就是在 页的粒度
上进行锁定,锁定的数据资源比行锁要多,因为一个页中可以有多个行记录。当我 们使用页锁的时候,会出现数据浪费的现象,但这样的浪费最多也就是一个页上的数据行。页锁的开销介于表锁和行锁之间,会出现死锁。锁定粒度介于表锁和行锁之间,并发度一般。
4.悲观锁,乐观锁
================================================================
乐观锁:
假设不会发生冲突,因此操作数据时不会加锁,只会在更新数据的时候做版本
校验,看数据有没有被别的事务修改,如果修改了,则拒绝当前事务的修改。
使用版本或者时间戳实现,一般是在数据库里面加一个版本或者时间戳字段。联想到CAS操作。适用于读多写少的场景。
悲观锁:
假设会发生冲突,操作数据之前都会对数据进行加锁,使其它的事务无法访问。
数据库提供的锁实现,例如表锁,行锁等。适用于写操作频繁的场景。
5.死锁
================================================================
死锁:
发生死锁怎么解决?
1.自动检测+锁等待时间+回滚
mysql会自动检测是否存在死锁,如果存在的话,会对一个事务进行回滚,并且释放该事务占有的锁;
有锁等待时间,如果一个事务获取锁的等待时间超过该阈值的时候,则回滚该事务,并且释放该事务占有的锁。
2.手动分析,kill发生死锁的事务的线程
show engine innodb status命令:分析死锁日志,找到发生死锁的事务以及线程id,然后kill;
通过查询information scheme,找到发生死锁的事务以及线程id,然后kill
避免死锁的手段:
1.避免长事务。长事务长时间占有锁,发生死锁的概率高。
2.降低事务隔离级别。例如使用读已提交隔离级别,比可重复读少了间隙锁,临建锁,可以减少死锁发生的概率。
3.合理建立索引。提高索引命中率,命中索引可以减少要处理的行,这样锁定的行也少,就可以减少死锁发生的概率。
4.开启死锁检测,适当调整锁等待时间。
死锁例子1:
死锁例子2: