MySQL事务处理与并发控制( 六 )


-- 事务要获取某些行的 X 锁 , 必须先获得表的 IX 锁 。SELECT column FROM table ... FOR UPDATE;即:意向锁是有数据引擎自己维护的 , 用户无法手动操作意向锁 , 在为数据行加共享/排他锁之前 , InooDB 会先获取该数据行所在在数据表的对应意向锁 。换言之 , 数据库引擎看到某表上有事务T持有IX , 就可以得知事务T必然持有该表中某些数据行的排他锁 。共享锁同理 。后面会看到 , 这个对性能提升意义非凡 。
意向锁可以提高性能 , 例如:
事务 A 获取了某一行的排他锁 , 并未提交:
SELECT * FROM users WHERE id = 6 FOR UPDATE;此时 users 表存在两把锁:users 表上的意向排他锁与 id 为 6 的数据行上的排他锁 。
事务 B 想要获取 users 表的共享锁:
LOCK TABLES users READ;此时事务 B 检测事务 A 持有 users 表的意向排他锁 , 就可以得知事务 A 必然持有该表中某些数据行的排他锁 , 那么事务 B 对 users 表的加锁请求就会被排斥(阻塞) , 而无需去检测表中的每一行数据是否存在排他锁(如果是一张大表 , 这个检查的性能是非常低的) 。
4.1.1 锁的种类InnoDB为了实现基于锁的并发控制 , 实现了多种类型的锁 , 包括在聚集索引上实现的行级记录锁 , 间隙锁 , 记录锁+间隙锁组合成的范围锁 , 插入意象锁 。这些锁的实现依赖于InnoDB的聚集索引额封锁机制 。

  • 1 记录锁:行级锁 , 依据InnoDB的聚集索引实现 , 针对索引加的锁 。
  • 2 间隙锁:两个索引项之间的间隔称为间隙 。把这个间隙视为一个对象 , 然后在此对象上加锁 , 就是间隙锁 。
  • 3 Next-key lock:由记录锁和此记录前的间隙上的间隙锁组成 。用于在重复读的隔离级别下解决幻读问题 。
  • 4 插入意象锁:基于间隙锁 , 专门用于插入操作 。目的是为了提高并发度 。
4.2 基于MVCC的并发控制InnoDB在锁的基础上实现了MVCC , 增加了并发度 。
概括来说就是当一个事务开始时 , 它被赋予一个唯一的 , 永远增长的事务ID( txid ) 。每当事务向数据库写入任何内容时 , 它所写入的数据都会被标记上写入者的事务ID , 一个事务只能看到在他之前提交的事务修改的数据 。
  • 在可重复读的隔离级别下 , InnoDB会在第一个select操作时创建一个数据快照 。在一致性读(快照读)的查询下 , 通过快照实现了可重复读 , 解决了幻读 。不过对于非一致性读(当前读)的情况下 , 需要通过范围锁才能解决幻读 。
  • 对于未提交读和已提交读 , 事务块内的select会分别创建自己额快照 , 因此每次读的都不同 。后边的select可以读到本次select之前提交的数据 。
4.3 InnoDB的四种隔离级别InnoDB实现了四种隔离级别 , InnoDB的四种隔离级别的语义除了可重复读之外与标准隔离级别一致 。默认情况下为可重复性读 , 不同之处在于InnoDB的可重复读通过MVCC和范围锁(Next-Key lock)避免了幻读 。不同的场景下避免幻读的机制不一样 。
  • 对于SELECT ... FROM 语句 , 为一致性读(快照读) , 这种场景下 , InnoDB通过MVCC技术 , 在SELECT的时候创建一个表的一致性快照 , 该技术下读写不阻塞 。
  • 对于SELECT ... FROM FOR UPDATE(当前读--读取的是最新版本, 并且对读取的记录加锁, 不让其他事务同时去改):则会通过范围锁来实现可重复性读 , 并且避免幻读 。
5、隔离级别与数据异常本节演示一下各种隔离级别下的数据异常 。
分别启动两个客户端client1 , client2连接到mysql服务器 , client1为主事务端 , 在此改变隔离级别 。
创建表如下:
create table bluesea(c1 int, c2 varchar(32), c3 int, primary key(c1));5.1 可串行化隔离级别
MySQL事务处理与并发控制

文章插图
可串行化
client2执行insert后 , 阻塞 , 这时在client3查看加锁情况如下:
mysql> select * from data_locks;+--------+--------------------------+-----------------------+-----------+----------+---------------+-------------+----------------+-------------------+------------+-----------------------+-----------+-----------+-------------+------------------------+| ENGINE | ENGINE_LOCK_ID | ENGINE_TRANSACTION_ID | THREAD_ID | EVENT_ID | OBJECT_SCHEMA | OBJECT_NAME | PARTITION_NAME | SUBPARTITION_NAME | INDEX_NAME | OBJECT_INSTANCE_BEGIN | LOCK_TYPE | LOCK_MODE | LOCK_STATUS | LOCK_DATA |+--------+--------------------------+-----------------------+-----------+----------+---------------+-------------+----------------+-------------------+------------+-----------------------+-----------+-----------+-------------+------------------------+| INNODB | 18262823:7872 | 18262823 | 50 | 18 | test | bluesea | NULL | NULL | NULL | 139693811345432 | TABLE | IX | GRANTED | NULL || INNODB | 18262823:6815:4:1 | 18262823 | 50 | 18 | test | bluesea | NULL | NULL | PRIMARY | 139693811342392 | RECORD | X | WAITING | supremum pseudo-record || INNODB | 421168866928480:7872 | 421168866928480 | 49 | 209 | test | bluesea | NULL | NULL | NULL | 139693811339480 | TABLE | IS | GRANTED | NULL || INNODB | 421168866928480:6815:4:1 | 421168866928480 | 49 | 209 | test | bluesea | NULL | NULL | PRIMARY | 139693811336440 | RECORD | S | GRANTED | supremum pseudo-record |+--------+--------------------------+-----------------------+-----------+----------+---------------+-------------+----------------+-------------------+------------+-----------------------+-----------+-----------+-------------+------------------------+4 rows in set (0.00 sec)


推荐阅读