测试:
4.5、innodb_locks_unsafe_for_binlog
innodb_locks_unsafe_for_binlog参数用来控制innodb中是否允许间隔锁,默认是OFF代表允许间隔锁,设置成ON则代表不使用间隔锁,只是使用行级锁
测试:
五、InnoDB事务隔离级别
5.1、简介
1)InnoDB存储引擎提供了四种事务隔离级别,分别是:READ UNCOMMITTED:读取未提交内容
READ COMMITTED:读取提交内容
REPEATABLE READ:可重复读,默认值。
SERIALIZABLE:串行化
2)可以通过--transaction-isolation参数设置实例级别的事务隔离级别,也可以通过set [session/global] transaction isolation level语句修改当前数据库链接或者是后续创建的所有数据库链接的事务隔离级别。每个事务隔离级别所对应的锁的使用方法都有所不同
5.2、REPEATABLE READ:可重复读
REPEATABLE READ:可重复读,默认值。
1)表明对同一个事务来说第一次读数据时会创建快照,在事务结束前的其他读操作(不加锁)会获得和第一次读相同的结果。
2)当读操作是加锁的读语句(select … for update或者lock in share mode),或者update和delete语句时,加锁的方式依赖于语句是否使用唯一索引访问唯一值或者范围值当访问的是唯一索引的唯一值时,则InnoDB会在索引行施加行锁
当访问唯一索引的范围值时,则会在扫描的索引行上增加间隔锁或者next-key锁以防止其他链接对此范围的插入
5.3、READ COMMITTED:读取提交内容
1)意味着每次读都会有自己最新的快照。对于加锁读语句(select … for update和lock in share mode),或者update,delete语句会在对应的行索引上增加锁,但不像可重复读一样会增加间隔锁,因此其他的事务执行插入操作时如果是插入非索引行上的数值,则不影响插入。
2)由于该隔离级别是禁用间隔锁的,所以会导致幻读的情况
3)如果是使用此隔离级别,就必须使用行级别的二进制日志
4)此隔离级别还有另外的特点:
对于update和delete语句只会在约束条件对应的行上增加锁。对update语句来说,如果对应的行上已经有锁,则InnoDB会执行半一致读的操作,来确定update语句对应的行在上次commit之后的数据是否在锁的范围,如果不是,则不影响update操作,如果是,则需要等待对应的锁解开
链接1
链接二
Mysql> set session transaction isolation level read committed;
mysql> set autocommit=0;
mysql> update temp set name='aaa' where id=1;
Query OK, 1 row affected (0.00 sec)
Rows matched: 1 Changed: 1 Warnings: 0
mysql> set session transaction isolation level read committed;
mysql> set autocommit=0;
mysql> select * from temp;
+----+------+
| id | name |
+----+------+
| 1 | bb |
| 2 | bb |
| 3 | abc |
| 4 | abc |
mysql> commit;
Query OK, 0 rows affected (0.00 sec)
mysql> select * from temp; ##同一个事务中能看到另一个事务已经提交的数据
+----+------+
| id | name |
+----+------+
| 1 | aaa |
| 2 | bb |
| 3 | abc |
| 4 | abc |
Commit;
5.4、READ UNCOMMITTED:读取未提交内容
READ UNCOMMITTED:读取未提交内容,所读到的数据可能是脏数据链接
链接2
mysql>set session transaction isolation level read uncommitted;
Query OK, 0 rows affected (0.00 sec)
mysql> set autocommit=0;
Query OK, 0 rows affected (0.00 sec)
mysql> update temp set name='bb';
Query OK, 3 rows affected (0.00 sec)
Rows matched: 4 Changed: 3 Warnings: 0
mysql> set session transaction isolation level read uncommitted;
Query OK, 0 rows affected (0.00 sec)
mysql> set autocommit=0;
Query OK, 0 rows affected (0.00 sec)
mysql> select * from temp; ##已经可以看到未提交的数据
+----+------+
| id | name |
+----+------+
| 1 | bb |
| 2 | bb |
| 3 | bb |
| 4 | bb |
+----+------+
4 rows in set (0.00 sec)
mysql> commit;
Query OK, 0 rows affected (0.00 sec)
Commit;
5.5、SERIALIZABLE:串行化
此隔离级别更接近于可重复读这个级别,只是当autocommit功能被禁用后,InnoDB引擎会将每个select语句隐含的转化为select … lock in share mode链接1
链接2
mysql> set session transaction isolation level SERIALIZABLE;
Query OK, 0 rows affected (0.00 sec)
mysql> set autocommit=0;
Query OK, 0 rows affected (0.00 sec)
mysql> update temp set name='bb';
Query OK, 0 rows affected (0.00 sec)
Rows matched: 4 Changed: 0 Warnings: 0
mysql> set session transaction isolation level SERIALIZABLE;
Query OK, 0 rows affected (0.00 sec)
mysql> set autocommit=0;
Query OK, 0 rows affected (0.00 sec)
mysql> select * from temp; ##锁等待
mysql> commit;
Query OK, 0 rows affected (0.00 sec)
mysql> select * from temp;
+----+------+
| id | name |
+----+------+
| 1 | aa |
| 2 | aa |
| 3 | aa |
| 4 | aa |
+----+------+
Commit;
六、Autocommit/commit/rollback
1)当设置autocommit属性开启时,每个SQL语句都会隐含成为独立的事务。
2)默认情况下autocommit属性是开启的,也就意味着当每个SQL语句最后执行结果不返回错误时都会执行commit语句,当返回失败时会执行rollback语句
3)而当autocommit属性开启时,可以通过执行start transaction或者begin语句来显示的开启一个事务,而事务里可以包含多个SQL语句,最终事务的结束是由commit或者rollback来终结
4)而当在数据库链接里执行set autocommit=0代表当前数据库链接禁止自动提交,事务的终结由commit或者rollback决定,同时也意味着下一个事务的开始
5)如果一个事务在autocommit=0的情况下数据库链接退出而没有执行commit语句,则这个事务会回滚
6)一些特定的语句会隐含的终结事务,就好比是执行了commit语句
7)commit语句代表将此事务的数据修改永久化,并对其他事务可见,而rollback则代表将此事务的数据修改回滚。
8)commit和rollback都会把当前事务执行所施加的锁释放
9)当使用多语句事务时,如果全局的autocommit属性是开启的,则开始此事务的方式可以使set autocommit=0将当前链接的属性关闭,最后执行commit和rollback;或者是显示的使用start transaction语句开启事务
七、一致读
1)在默认的隔离级别下一致读是指InnoDB在多版本控制中在事务的首次读时产生一个镜像,在首次读时间点之前其他事务提交的修改可以读取到,而首次读时间点之后其他事务提交的修改或者是未提交的修改都读取不到
2)唯一例外的情况是在首次读时间点之前的本事务未提交的修改数据可以读取到
3)在读取提交数据隔离级别下,一致读的每个读取操作都会有自己的镜像
4)一致读操作不会施加任何的锁,所以就不会阻止其他事务的修改动作
5)一致读在某些DDL语句下不生效:碰到drop table语句时,由于InnoDB不能使用被drop的表,所以无法实现一致读
碰到alter table语句时,也无法实现一致读
6)当碰到insert into… select, update … select和create table … select语句时,在默认的事务隔离级别下,语句的执行更类似于在读取提交数据的隔离级别下
八、加锁读
8.1、加锁读介绍
1)当在一个事务中在读操作结束后会执行insert和update操作时,普通的读操作无法阻止其他事务对相同数据执行修改操作,所以InnoDB提供了两种在读操作时就增加锁的方式
2)select … lock in share mode:在读取的行数据上施加共享锁,其他的事务可以读相同的数据但无法修改;如果在执行此语句时有其他事务对相同的数据已经施加了锁,则需要等待事务完结释放锁
3)select … for update:和update操作一样,在涉及的行上施加排他锁,并阻止任何其他事务对涉及行上的修改操作、以及加锁读操作,但不会阻止对涉及行上的一般读(不加锁)操作
4)同样,锁的释放也是在事务提交或者回滚之后
8.2、加锁读应用
1)比如在子表中插入一行数据,要确保对应的列在父表中有值,通过一般的读操作先查父表有值然后再插入的方法是不保险的,因为在读操作和插入操作之间就有可能其他事务会将父表的数据修改掉。那保险的做法是在查询父表是用加锁读的方式
创建测试表:
mysql> create table temp(id int primary key,name varchar(10));
mysql> create table temp_child(id int primary key,temp_id int,constraint f_1 foreign key (temp_id) references temp(id));
mysql> insert into temp values(1,'a'),(2,'b'),(3,'c'),(4,'d');
链接1
链接2
mysql> set autocommit=0;
Query OK, 0 rows affected (0.00 sec)
mysql> select * from temp;
+----+------+
| id | name |
+----+------+
| 1 | a |
| 2 | b |
| 3 | c |
| 4 | d |
+----+------+
mysql> set autocommit=0;
Query OK, 0 rows affected (0.00 sec)
mysql> delete from temp where id=3;
Query OK, 1 row affected (0.00 sec)
mysql> commit;
Query OK, 0 rows affected (0.00 sec)
mysql> select * from temp;
+----+------+
| id | name |
+----+------+
| 1 | a |
| 2 | b |
| 3 | c |
| 4 | d |
+----+------+
mysql> insert into temp_child values(1,3); #报外键冲突
ERROR 1452 (23000): Cannot add or update a child row: a foreign key constraint fails (`locktest`.`temp_child`, CONSTRAINT `f_1` FOREIGN KEY (`temp_id`) REFERENCES `temp` (`id`))
mysql> rollback;
2)比如当表中有一个行数计数字段时,使用一致读和lock in share mode都有可能导致重复错误数据出现,因为有可能两个事务会读到相同的值,在这种情况下就要使用select … for update语句保证一个事务在读时,另一个事务必须等待
九、SQL语句对应的锁
1)加锁读,修改和删除SQL语句都会在索引扫描过的每一行增加锁,也就是说不光是在where条件限制的索引行上增加锁,也会对扫描到的间隔增加间隔锁
2)如果SQL语句是使用二级索引查找数据而且施加的是排他锁,则InnoDB也会在对应的聚簇索引行上施加锁
3)如果SQL语句没有任何索引可以使用,则MySQL需要扫描全表数据,而每行数据都会被施加锁,所以一个良好的习惯是为InnoDB添加合适的索引
4)针对不同的语句,InnoDB会施加不同的锁:
5)Select…from语句属于一致性读,在默认情况下不施加任何的锁,除非在可串行化隔离级别下,会施加共享next-key锁在扫描的索引行上,当碰到使用唯一索引查找唯一值时只在唯一值上施加锁
6)Select…lock in share mode语句会在索引扫描行上施加共享next-key锁,除非是当碰到使用唯一索引查找唯一值时只在唯一值上施加锁
7)Select…for update语句会对扫描索引的行上施加排他next-key锁,除非是当碰到使用唯一索引查找唯一值时只在唯一值上施加锁
8)Update语句会对扫描索引的行上施加排他next-key锁,除非是当碰到使用唯一索引查找唯一值时只在唯一值上施加锁。
9)Delete语句会对扫描索引的行上施加排他next-key锁,除非是当碰到使用唯一索引查找唯一值时只在唯一值上施加锁
10)Insert语句会对索引扫描的行上施加锁,但不是next-key锁,所以不会阻止其他事务对该行值前的间隔上插入数据
11)Insert into T select…from S语句会对插入到T表的行施加排他锁(非间隔锁),而在默认隔离级别下会对访问的S表上的行施加共享next-key锁
12)当表上有外键约束时,对任何的insert,update和delete操作都会在需要检查外键约束的行上施加共享行锁
13)Lock table语句是施加表级锁
十、幻读
1)幻读问题发生在同一个事务中当相同的读操作在前后两次读数据时返回不同的结果集。
2)比如在表的ID字段上有一个索引,当希望对ID>100的数据进行后续修改时,我们会使用如下的语句: SELECT * FROM child WHERE id > 100 FOR UPDATE,而如果表里目前只有90和102两个值时,如果没有间隔锁锁住90到102之间的间隔,则其他的事务会插入比如101这个值,这样的话在第二次读数据时就会返回三行记录而导致幻读
3)为了阻止幻读情况的发生,InnoDB使用了一种方法next-key锁将索引行锁和间隔锁合并在一起。InnoDb会在索引扫描的行上施加行级共享锁或者排他锁,而next-key锁也会在每个索引行之前的间隔上施加锁,会导致其他的session不能在每个索引之前的间隔内插入新的索引值
4)间隔锁会施加在索引读碰到的行数据上,所以对上例来说为了阻止插入任何>100的值,也会将最后扫描的索引值102之前的间隔锁住
十一、InnoDB锁性能监控
十二、InnoDB死锁
12.1、死锁介绍
1)死锁的情况发生在不同的的事务相互之间拥有对方需要的锁,而导致相互一直无限等待
2)死锁可能发生在不同的事务都会对多个相同的表和相同的行上施加锁,但事务对表的操作顺序不相同
3)为了减少死锁的发生,要避免使用lock table语句,要尽量让修改数据的范围尽可能的小和快速;当不同的事务要修改多个表或者大量数据时,尽可能的保证修改的顺序在事务之间要一致
4)默认情况下InnoDB下的死锁自动侦测功能是开启的,当InnoDB发现死锁时,会将其中的一个事务作为牺牲品回滚。
5)通过innodb_lock_wait_timeout参数配置自动侦测功能是否开启,如果关闭的话,InnoDB就会使用innodb_lock_wait_timeout参数来自动回滚等待足够时间的事务
6)可以通过show engine innodb status语句查看最后一次发生死锁的情况
7)默认情况下死锁检测功能是开启的,当死锁发生时InnoDB会自动检测到并牺牲(回滚)其中的一个或者几个事务,以便让其他的事务继续执行下去。InnoDB选择牺牲的事务往往是代价比较小的事务,其代价计算是根据事务insert,update, delete的数据行规模决定
8)如果事务中的某个语句因为错误而回滚,则这个语句上的锁可能还会保留,是因为InnoDB仅会存储行锁信息,而不会存储行锁是由事务中的哪个语句产生的
9)如果在一个事务中,select语句调用了函数,而函数中的某个语句执行失败,则那个语句会回滚,如果在整个事务结束时执行rollback,则整个事务回滚
10)可以通过innodb_deadlock_detect 参数关闭死锁检测功能,而仅仅用innodb_lock_wait_timeout的功能来释放锁等待
12.2、减少死锁发生的方法
在事务性数据库中,死锁是个经典的问题,但只要发生的频率不高则死锁问题不需要太过担心
查看死锁的方法有两种:通过show engine innodb status命令可以查看最后一个死锁的情况
通过innodb_print_all_deadlocks参数配置可以将所有死锁的信息都打印到MySQL的错误日志中
减少死锁发生的方法:尽可能的保持事务小型化,减少事务执行的时间可以减少发生影响的概率
及时执行commit或者rollback,来尽快的释放锁
可以选用较低的隔离级别,比如如果要使用select... for update和select...lock in share mode语句时可以使用读取提交数据隔离级别
当要访问多个表数据或者要访问相同表的不同行集合时,尽可能的保证每次访问的顺序是相同的。比如可以将多个语句封装在存储过程中,通过调用同一个存储过程的方法可以减少死锁的发生
增加合适的索引以便语句执行所扫描的数据范围足够小
尽可能的少使用锁,比如如果可以承担幻读的情况,则直接使用select语句,而不要使用select...for update语句
如果没有其他更好的选择,则可以通过施加表级锁将事务执行串行化,最大限度的限制死锁发生
12.3、死锁监控
十三、补充:锁的监控及处理
13.1、模拟锁等待定位过程
13.2、优化项目:锁的监控及处理