InnoDB存储引擎_数据库隔离级别(二)

来源:互联网 发布:office2016激活软件 编辑:程序博客网 时间:2024/05/10 13:41

InnoDB存储引擎提供了两个特点
1.事务

2.行锁

关于事务的四种隔离级别,在上篇已经说过了,关于可重复读隔离策略能够防止“幻读”问题,需要根据InnoDB的加锁策略来分析。

InnoDB支持的行锁是指通过索引实现对结果记录集合加锁,可以理解为对索引加锁,来保证根据索引搜索的范围内,存在“范围锁”,之所以这么说,是因为如果表中不存在索引,则可能加锁的范围为整张表,即升级为“表锁”。

锁模式

锁的类型可以分为两种

1.共享锁

2.排他锁

对记录集上加共享锁目的很明显,就是防止其他事务对相同记录集上加排他锁;排他锁的用意更明显,就是防止其他事务对相同记录集加锁,无论是共享锁还是排他锁。

加锁的方式

共享锁加锁:select ... lock in share mode

排他锁加锁:select ... for update,其实insert、delete和update自动加排他锁,不过演示效果起见,使用for update方式较好,至于select则不加任何锁,所以无论什么隔离级别,或者已经占据什么锁,对select都没有影响。(你可以阻止它读到数据,但是不能阻止它的执行)

比方说java自身的synchronized关键字,当存在代码块:

synchronized(obj){
//action
}
只是说明 obj 对象上的锁被占用,但是并不能阻止对 obj 对象的访问。

可重复读隔离级别

回顾下,可重复读隔离级别事务可以避免“不可重复读”问题

建表 t 无主键,索引为 id1,插入数据,确定隔离级别

mysql> create table t(    -> id1 int,    -> id2 int,    -> index index_id1(id1))    -> engine=innodb;Query OK, 0 rows affected (0.22 sec)mysql> insert into t values(1,1),(2,2);Query OK, 2 rows affected (0.04 sec)Records: 2  Duplicates: 0  Warnings: 0mysql> select @@session.tx_isolation;+------------------------+| @@session.tx_isolation |+------------------------+| REPEATABLE-READ        |+------------------------+1 row in set (0.00 sec)

session 1 开启事务,读取数据

mysql> start transaction;Query OK, 0 rows affected (0.00 sec)mysql> select * from t;+------+------+| id1  | id2  |+------+------+|    1 |    1 ||    2 |    2 |+------+------+2 rows in set (0.00 sec)
session 2开启事务,修改数据并提交事务

mysql> start transaction;Query OK, 0 rows affected (0.00 sec)mysql> update t set id2=3 where id1=1;Query OK, 1 row affected (0.00 sec)Rows matched: 1  Changed: 1  Warnings: 0mysql> commit;Query OK, 0 rows affected (0.03 sec)
然后再查询session 1 中的表数据

mysql> select * from t;+------+------+| id1  | id2  |+------+------+|    1 |    1 ||    2 |    2 |+------+------+2 rows in set (0.00 sec)
数据没有变化,即不存在“不可重复读”,可重复读。(如果session 1 中没有对表 t的修改,则数据显示为session 2 中对表 t的修改)

幻读问题

分析幻读问题,主要查看记录加锁的范围。

1.对包含索引的表进行加锁检索

session 1 开启事务,加锁记录

mysql> start transaction;Query OK, 0 rows affected (0.00 sec)mysql> select * from t where id1=1 for update;+------+------+| id1  | id2  |+------+------+|    1 |    1 |+------+------+1 row in set (0.00 sec)
session 2 开启事务,修改非锁定记录,并执行对session 1 锁定记录的修改

mysql> start transaction;Query OK, 0 rows affected (0.00 sec)mysql> update t set id2=3 where id1=2;Query OK, 1 row affected (0.00 sec)Rows matched: 1  Changed: 1  Warnings: 0mysql> update t set id2=3 where id1=1;

阻塞直到session 1 提交事务然后完成执行或者超时

2.对非索引表加锁检索

session 1 开启事务锁定记录

mysql> alter table t drop index index_id1;Query OK, 0 rows affected (0.11 sec)Records: 0  Duplicates: 0  Warnings: 0mysql> start transaction;Query OK, 0 rows affected (0.00 sec)mysql> select * from t where id1=1 for update;+------+------+| id1  | id2  |+------+------+|    1 |    1 |+------+------+1 row in set (0.00 sec)
session 2 仍然执行修改非锁定记录

mysql> start transaction;Query OK, 0 rows affected (0.00 sec)mysql> update t set id2=3 where id1=2;
修改被阻塞,因为session 1 已经锁定了整张表

举例

在其他网页上看到有描述的这样一种情况:


个人持不同观点:

事务的隔离级别的产生,究其原因是为了协调并发事务的高效性和共享索引的安全性,也就是在并发和串行这两个对立概念之间找个折中。隔离作为维护串行性的概念是阻碍并发的产生的,造成的影响就是索引使用效率的降低,响应时间增长,但是给其做出的弥补则是增强了安全性。

但是上图中所示,事务的隔离只发挥了其“隔离”的作用,在模拟事务操作中,并没有对其中的访问记录加锁保护,结果就是,session B中索引的更新因为事务的“隔离性”,对session A不可见,而在session A执行加锁操作之后,才显示出记录的修改。如下:

建表后,session A 开启事务,查询记录

mysql> create table t(    -> id int,    -> age int,    -> primary key (id))    -> engine=innodb;Query OK, 0 rows affected (0.14 sec)mysql> start transaction;Query OK, 0 rows affected (0.00 sec)mysql> select * from t;Empty set (0.00 sec)
session B 开启事务,更新索引并提交事务

mysql> start transaction;Query OK, 0 rows affected (0.00 sec)mysql> insert into t values(1,1);Query OK, 1 row affected (0.02 sec)mysql> commit;Query OK, 0 rows affected (0.02 sec)
在session A 对加锁范围进行更新之后,才会显示出更新记录

mysql> select * from t;Empty set (0.00 sec)mysql> select * from t for update;+----+------+| id | age  |+----+------+|  1 |    1 |+----+------+1 row in set (0.00 sec)

也就是上图片中提到的session A 执行的insert、update或delete等自带加锁性质的操作之后,才会对记录集加锁

举例:

session 1 开启事务,限定加锁范围

mysql> select * from t;+----+------+| id | age  |+----+------+|  1 |    1 |+----+------+1 row in set (0.00 sec)mysql> start transaction;Query OK, 0 rows affected (0.00 sec)mysql> explain update t set age=2 where id=1;</span><span style="font-family:FangSong_GB2312;font-size:12px;">+----+-------------+-------+------------+-------+---------------+---------+---------+-------+------+----------+-------------+| id | select_type | table | partitions | type  | possible_keys | key     | key_len | ref   | rows | filtered | Extra       |+----+-------------+-------+------------+-------+---------------+---------+---------+-------+------+----------+-------------+|  1 | UPDATE      | t     | NULL       | range | PRIMARY       | PRIMARY | 4       | const |    1 |   100.00 | Using where |+----+-------------+-------+------------+-------+---------------+---------+---------+-------+------+----------+-------------+</span><span style="font-family:FangSong_GB2312;font-size:18px;">1 row in set (0.00 sec)mysql> update t set age=2 where id=1;Query OK, 1 row affected (0.00 sec)Rows matched: 1  Changed: 1  Warnings: 0mysql> select * from t;+----+------+| id | age  |+----+------+|  1 |    2 |+----+------+1 row in set (0.00 sec)
执行更新操作,观察explain结果,加锁范围type:range(该值会受MySQL影响,比如表数据较少时可能不会用指定的索引,而改为全局遍历,毕竟一次IO就可以把所有数据读出来(局部性原理),就没必要先在索引中查询再取数据,执行两次IO操作)

session 2 插入数据并提交事务

mysql> start transaction;Query OK, 0 rows affected (0.00 sec)mysql> insert into t values(2,2);Query OK, 1 row affected (0.02 sec)mysql> commit;Query OK, 0 rows affected (0.02 sec)
更新session 1 的加锁范围即可显示出新数据
mysql> select * from t;+----+------+| id | age  |+----+------+|  1 |    2 |+----+------+1 row in set (0.00 sec)mysql> explain select * from t for update;</span><span style="font-family:FangSong_GB2312;font-size:14px;">+----+-------------+-------+------------+------+---------------+------+---------+------+------+----------+-------+| id | select_type | table | partitions | type | possible_keys | key  | key_len | ref  | rows | filtered | Extra |+----+-------------+-------+------------+------+---------------+------+---------+------+------+----------+-------+|  1 | SIMPLE      | t     | NULL       | ALL  | NULL          | NULL | NULL    | NULL |    2 |   100.00 | NULL  |+----+-------------+-------+------------+------+---------------+------+---------+------+------+----------+-------+</span><span style="font-family:FangSong_GB2312;font-size:18px;">1 row in set, 1 warning (0.00 sec)mysql> select * from t for update;+----+------+| id | age  |+----+------+|  1 |    2 ||  2 |    2 |+----+------+2 rows in set (0.00 sec)
所以使用next-key机制用来保证一定范围内,即满足加锁条件的当前表项以及待添加表项,避免“幻读”问题

总结

InnoDB在一定条件可重复读隔离级别下可以实现避免“幻读”问题。至于for update加锁的范围可以通过explain进行查看,例如上面对包含索引的表的加锁,type:ref,锁定相关索引表示的某些列,无索引表的加锁,type:all,还有range等表示范围的加锁。


0 0