1.各种锁机制
2 加锁机制
乐观锁:假定大概率不会发生并发更新冲突,访问、处理数据过程中不加锁,只在更新数据时再根据版本号或时间戳判断是否有冲突,有则处理,无则提交事务;
悲观锁:假定大概率会发生并发更新冲突,访问、处理数据前就加排他锁,在整个数据处理过程中锁定数据,事务提交或回滚后才释放锁;
3 锁粒度
关于全局锁、行锁、表锁和死锁:
表锁:锁住整个表,主要是为了锁住表结构(写锁),使得其他update语句受到阻塞。
页锁:页级锁定的特点是锁定颗粒度介于行级锁定与表级锁之间,所以获取锁定所需要的资源开销,以及所能提供的并发处理能力也同样是介于上面二者之间。另外,页级锁定和行级锁定一样,会发生死锁。
行锁:锁住某一行的数据
- | 行锁 | 表锁 | 页锁 |
MyISAM | √ | ||
BDB | √ | √ | |
InnoDB | √ | √ |
4 兼容性
共享锁:也称为s锁,是一种读锁,允许一个事务去读一行,阻止其他事务获得相同数据集的排他锁。
排他锁:也称之为x锁,是一种写锁,允许获得排他锁的事务更新数据,阻止其他事务取得相同数据集的共享读锁和排他写锁。
事务隔离级别和兼容性锁:
5 锁模式
5.1 意向锁
意向共享锁(IS):事务打算给数据行加行共享锁,事务在给一个数据行加共享锁前必须先取得该表的IS锁。
意向排他锁(IX):事务打算给数据行加行排他锁,事务在给一个数据行加排他锁前必须先取得该表的IX锁。
意向锁和S、X锁的区别说明:
1)共享锁和排他锁都是行锁,意向锁都是表锁,应用中我们只会使用到共享锁和排他锁,意向锁是mysql内部使用的,不需要用户干预。
2)对于UPDATE、DELETE和INSERT语句,InnoDB会自动给涉及数据集加排他锁(X);对于普通SELECT语句,InnoDB不会加任何锁,事务可以通过以下语句显示给记录集加共享锁或排他锁。
共享锁(S):SELECT * FROM table_name WHERE … LOCK IN SHARE MODE。
排他锁(X):SELECT * FROM table_name WHERE … FOR UPDATE。
3)InnoDB行锁是通过给索引上的索引项加锁来实现的
,因此InnoDB这种行锁实现特点意味着:只有通过索引
条件检索数据,InnoDB才使用行级锁,否则,InnoDB将使用表锁
!
4)InnoDB 支持多种粒度的锁,也就是行锁和表锁。为了支持多粒度锁定,InnoDB 存储引擎引入了意向锁(Intention Lock)。如果没有意向锁,当已经有人使用行锁对表中的某一行进行修改时,如果另外一个请求要对全表进行修改,那么就需要对所有的行是否被锁定进行扫描,在这种情况下,效率是非常低的;不过,在引入意向锁之后,当有人使用行锁对表中的某一行进行修改之前,会先为表添加意向互斥锁(IX),再为行记录添加互斥锁(X),在这时如果有人尝试对全表进行修改就不需要判断表中的每一行数据是否被加锁了,只需要通过等待意向互斥锁被释放就可以了。
5.2 innoDB的三种锁算法
InnoDB存储引擎有3种行锁的算法,其分别是:
Record Lock(记录锁): 单个行记录上的锁。Record Lock总是会去锁住索引记录,如果InnoDB存储引擎表在建立的时候没有设置任何一个索引,那么这时InnoDB存储引擎会使用隐式的主键来进行锁定。
Gap Lock(间隙锁):间隙锁,锁定一个范围,但不包含记录本身。
Next-Key Lock(临键锁):Gap Lock+Record Lock,锁定一个范围,并且锁定记录本身。
Next-Key Lock是结合了Gap Lock和Record Lock的一种锁定算法,在Next-Key Lock算法下,InnoDB对于行的查询都是采用这种锁定算法。例如有一个索引有10,11,13和20这4个值,那么该索引可能被Next-Key Locking的区间为:
但是不是所有索引都会加上Next-key Lock的,在查询的列是唯一索引(包含主键索引)的情况下,Next-key Lock会降级为Record Lock。提高并发能力
接下来,我们来通过一个例子解释一下。
CREATE TABLE z (
a INT,
b INT,
PRIMARY KEY(a), // a是主键索引
KEY(b) // b是普通索引
);
INSERT INTO z select 1, 1;//插入数据a=1,b=1
INSERT INTO z select 3, 1;
INSERT INTO z select 5, 3;
INSERT INTO z select 7, 6;
INSERT INTO z select 10, 8;
这时候在会话A中执行 SELECT * FROM z WHERE b = 3 FOR UPDATE
,索引锁定如下:(对主键索引加上了记录锁锁住5这个数,对普通索引加上了间隙锁锁住(1,3],3,(3,6]这三段)
这时候会话B执行的语句落在锁定范围内的都会进行waiting.
第一个SQL语句不能执行,因为在会话A中执行的SQL语句已经对聚集索引中列a=5的值加上ⅹ锁,因此执行会被阻塞。
第二个SQL语句,主键插人4,没有问题,但是插人的辅助索引值2在锁定的范围(1,3)中,因此执行同样会被阻塞。
第三个SQL语句,插人的主键6没有被锁定,5也不在范围(1,3)之间。但插入的值5在另一个锁定的范围(3,6)中,故同样需要等待。
SELECT * FROM z WHERE a = 5 LOCK IN SHARE MODE;
INSERT INTO z SELECT 4, 2;
INSERT INTO z SELECT 6, 5;
用户可以通过以下两种方式来显示的关闭Gap Lock:
将事务的隔离级别设为 READ COMMITED。
将参数innodb_locks_unsafe_for_binlog设置为1
从上面的例子可以看出来,Gap Lock的作用是为了阻止一个事务A读取(1,100)之间的记录两次,事务B在A前后读取的事件中插入记录插入到(1,100)内
,设计它的目的是用来解决Phontom Problem(幻读问题)。在MySQL默认的隔离级别(Repeatable Read)下,InnoDB就是使用它来解决幻读问题。(事实上,考虑另一种常见情况,事务 A读取数据20到50之间的数据,数据库只有20和50两个数满足要求,持有锁(20,50)和(50,无穷)两把锁,由于没有间隙锁,事务 B 插入数据30,此时事务A再次读取数据,导致在同一个事务内两次读取数据不一致,产生幻读。)
6. 主键索引无需间隙锁?
查询单条语句,如where id = 100,关于唯一索引为什么不需要间隙锁?
因为普通索引可能存在扫描整合表的操作(where 条件中的b=100可能有多个b都等于100),也就是上面的insert 。。select。。这种类似于的语句操作数据库,所以需要操作的对象是整个表,所以会产生幻读,而唯一索引因为是唯一的,只需要操作这一行即可,行锁就可以解决该问题,不会产生幻读(幻读需要表整体发生变化)。
对于查找某一范围内的查询语句,会产生间隙锁唯一索引也会加上间隙锁。
,如:WHERE id BETWEEN 5 AND 7 FOR UPDATE;