本文的目的是介绍今日份的深入理解MySQL锁类型和加锁原理的详细情况,特别关注mysql加锁处理分析的相关信息。我们将通过专业的研究、有关数据的分析等多种方式,为您呈现一个全面的了解今日份的深入理解M
本文的目的是介绍今日份的深入理解MySQL锁类型和加锁原理的详细情况,特别关注mysql 加锁处理分析的相关信息。我们将通过专业的研究、有关数据的分析等多种方式,为您呈现一个全面的了解今日份的深入理解MySQL锁类型和加锁原理的机会,同时也不会遗漏关于MySQL 的死锁系列 - 锁的类型以及加锁原理、Mysql学习深入理解MySQL索引与优化、Mysql实例深入理解Mysql的四种隔离级别、Mysql应用mysqli预处理编译的深入理解的知识。
本文目录一览:- 今日份的深入理解MySQL锁类型和加锁原理(mysql 加锁处理分析)
- MySQL 的死锁系列 - 锁的类型以及加锁原理
- Mysql学习深入理解MySQL索引与优化
- Mysql实例深入理解Mysql的四种隔离级别
- Mysql应用mysqli预处理编译的深入理解
今日份的深入理解MySQL锁类型和加锁原理(mysql 加锁处理分析)
相关免费学习推荐:mysql教程
前言
- MysqL索引底层数据结构与算法
- MysqL性能优化原理-前篇
- MysqL性能优化-实践篇1
- MysqL性能优化-实践篇2
- MysqL锁与事物隔离级别
前面我们讲了MysqL数据库底层的数据结构与算法、MysqL性能优化篇一些内容。以及上篇讲了MysqL的行锁与事务隔离级别。本篇再重点来讲讲锁类型和加锁原理。
首先对MysqL锁进行划分:
- 按照锁的粒度划分:行锁、表锁、页锁
- 按照锁的使用方式划分:共享锁、排它锁(悲观锁的一种实现)
- 还有两种思想上的锁:悲观锁、乐观锁。
- InnoDB中有几种行级锁类型:Record Lock、Gap Lock、Next-key Lock
- Record Lock:在索引记录上加锁
- Gap Lock:间隙锁
- Next-key Lock:Record Lock+Gap Lock
表锁
表级锁是 MysqL 锁中粒度最大的一种锁,表示当前的操作对整张表加锁,资源开销比行锁少,不会出现死锁的情况,但是发生锁冲突的概率很大。被大部分的MysqL引擎支持,MyISAM和InnoDB都支持表级锁,但是InnoDB默认的是行级锁。
表锁由 MysqL Server 实现,一般在执行 DDL 语句时会对整个表进行加锁,比如说 ALTER TABLE 等操作。在执行 sql 语句时,也可以明确指定对某个表进行加锁。
表锁使用的是一次性锁技术,也就是说,在会话开始的地方使用 lock 命令将后续需要用到的表都加上锁,在表释放前,只能访问这些加锁的表,不能访问其他表,直到最后通过 unlock tables 释放所有表锁。
除了使用 unlock tables 显示释放锁之外,会话持有其他表锁时执行lock table 语句会释放会话之前持有的锁;会话持有其他表锁时执行 start transaction 或者 begin 开启事务时,也会释放之前持有的锁。
共享锁用法:
LOCK TABLE table_name [ AS alias_name ] READ复制代码
排它锁用法:
LOCK TABLE table_name [AS alias_name][ LOW_PRIORITY ] WRITE复制代码
解锁用法:
unlock tables;复制代码
行锁
行级锁是MysqL中锁定粒度最细的一种锁,表示只针对当前操作的行进行加锁。行级锁能大大减少数据库操作的冲突。其加锁粒度最小,但加锁的开销也最大。有可能会出现死锁的情况。 行级锁按照使用方式分为共享锁和排他锁。
不同存储引擎的行锁实现不同,后续没有特别说明,则行锁特指 InnoDB 实现的行锁。
在了解 InnoDB 的加锁原理前,需要对其存储结构有一定的了解。InnoDB 是聚簇索引,也就是 B+树的叶节点既存储了主键索引也存储了数据行。而 InnoDB 的二级索引的叶节点存储的则是主键值,所以通过二级索引查询数据时,还需要拿对应的主键去聚簇索引中再次进行查询。关于MysqL索引的详细知识可以查看《MysqL索引底层数据结构与算法》。
下面以两条 sql 的执行为例,讲解一下 InnoDB 对于单行数据的加锁原理。
update user set age = 10 where id = 49; update user set age = 10 where name = 'Tom';复制代码
第一条 sql 使用主键索引来查询,则只需要在 id = 49 这个主键索引上加上写锁;
第二条 sql 则使用二级索引来查询,则首先在 name = Tom 这个索引上加写锁,然后由于使用 InnoDB 二级索引还需再次根据主键索引查询,所以还需要在 id = 49 这个主键索引上加写锁,如上图所示。
也就是说使用主键索引需要加一把锁,使用二级索引需要在二级索引和主键索引上各加一把锁。
根据索引对单行数据进行更新的加锁原理了解了,那如果更新操作涉及多个行呢,比如下面 sql 的执行场景。
update user set age = 10 where id > 49;复制代码
这种场景下的锁的释放较为复杂,有多种的优化方式,我对这块暂时还没有了解,还请知道的小伙伴在下方留言解释。
页锁
页级锁是MysqL中锁定粒度介于行级锁和表级锁中间的一种锁。表级锁速度快,但冲突多,行级冲突少,但速度慢。所以取了折衷的页级,一次锁定相邻的一组记录。BDB支持页级锁。
共享锁/排他锁
共享锁(Share Lock)
共享锁又称读锁,是读取操作创建的锁。其他用户可以并发读取数据,但任何事务都不能对数据进行修改(获取数据上的排他锁),直到已释放所有共享锁。
如果事务T对数据A加上共享锁后,则其他事务只能对A再加共享锁,不能加排他锁。获准共享锁的事务只能读数据,不能修改数据。
用法
SELECT ... LOCK IN SHARE MODE;
在查询语句后面增加LOCK IN SHARE MODE
,MysqL会对查询结果中的每行都加共享锁,当没有其他线程对查询结果集中的任何一行使用排他锁时,可以成功申请共享锁,否则会被阻塞。其他线程也可以读取使用了共享锁的表,而且这些线程读取的是同一个版本的数据。
排他锁(eXclusive Lock)
排他锁又称写锁,如果事务T对数据A加上排他锁后,则其他事务不能再对A加任任何类型的封锁。获准排他锁的事务既能读数据,又能修改数据。
用法
SELECT ... FOR UPDATE;
在查询语句后面增加FOR UPDATE
,MysqL会对查询结果中的每行都加排他锁,当没有其他线程对查询结果集中的任何一行使用排他锁时,可以成功申请排他锁,否则会被阻塞。
乐观锁和悲观锁
在数据库的锁机制中介绍过,数据库管理系统(DBMS)中的并发控制的任务是确保在多个事务同时存取数据库中同一数据时不破坏事务的隔离性和统一性以及数据库的统一性。
乐观并发控制(乐观锁)和悲观并发控制(悲观锁)是并发控制主要采用的技术手段。
无论是悲观锁还是乐观锁,都是人们定义出来的概念,可以认为是一种思想。其实不仅仅是关系型数据库系统中有乐观锁和悲观锁的概念,像memcache、hibernate、tair等都有类似的概念。
针对于不同的业务场景,应该选用不同的并发控制方式。所以,不要把乐观并发控制和悲观并发控制狭义的理解为DBMS中的概念,更不要把他们和数据中提供的锁机制(行锁、表锁、排他锁、共享锁)混为一谈。其实,在DBMS中,悲观锁正是利用数据库本身提供的锁机制来实现的。
悲观锁
在关系数据库管理系统里,悲观并发控制(又名“悲观锁”,pessimistic Concurrency Control,缩写“PCC”)是一种并发控制的方法。它可以阻止一个事务以影响其他用户的方式来修改数据。如果一个事务执行的操作对某行数据应用了锁,那只有当这个事务把锁释放,其他事务才能够执行与该锁冲突的操作。悲观并发控制主要用于数据争用激烈的环境,以及发生并发冲突时使用锁保护数据的成本要低于回滚事务的成本的环境中。
悲观锁,正如其名,它指的是对数据被外界(包括本系统当前的其他事务,以及来自外部系统的事务处理)修改持保守态度(悲观),因此,在整个数据处理过程中,将数据处于锁定状态。 悲观锁的实现,往往依靠数据库提供的锁机制 (也只有数据库层提供的锁机制才能真正保证数据访问的排他性,否则,即使在本系统中实现了加锁机制,也无法保证外部系统不会修改数据)
悲观锁的具体流程
- 在对任意记录进行修改前,先尝试为该记录加上排他锁(exclusive locking);
- 如果加锁失败,说明该记录正在被修改,那么当前查询可能要等待或者抛出异常。 具体响应方式由开发者根据实际需要决定;
- 如果成功加锁,那么就可以对记录做修改,事务完成后就会解锁了。
- 其间如果有其他对该记录做修改或加排他锁的操作,都会等待我们解锁或直接抛出异常。
悲观锁的优点和不足
悲观锁实际上是采取了“先取锁在访问”的策略,为数据的处理安全提供了保证,但是在效率方面,由于额外的加锁机制产生了额外的开销,并且增加了死锁的机会。并且降低了并发性;当一个事物所以一行数据的时候,其他事物必须等待该事务提交之后,才能操作这行数据。
乐观锁
在关系数据库管理系统里,乐观并发控制(又名“乐观锁”,Optimistic Concurrency Control,缩写“OCC”)是一种并发控制的方法。它假设多用户并发的事务在处理时不会彼此互相影响,各事务能够在不产生锁的情况下处理各自影响的那部分数据。在提交数据更新之前,每个事务会先检查在该事务读取数据后,有没有其他事务又修改了该数据。如果其他事务有更新的话,正在提交的事务会进行回滚。
乐观锁( Optimistic Locking ) 相对悲观锁而言,乐观锁假设认为数据一般情况下不会造成冲突,所以在数据进行提交更新的时候,才会正式对数据的冲突与否进行检测,如果发现冲突了,则让返回用户错误的信息,让用户决定如何去做。
相对于悲观锁,在对数据库进行处理的时候,乐观锁并不会使用数据库提供的锁机制。一般的实现乐观锁的方式就是记录数据版本。
数据版本,为数据增加的一个版本标识。当读取数据时,将版本标识的值一同读出,数据每更新一次,同时对版本标识进行更新。当我们提交更新的时候,判断数据库表对应记录的当前版本信息与第一次取出来的版本标识进行比对,如果数据库表当前版本号与第一次取出来的版本标识值相等,则予以更新,否则认为是过期数据。
乐观锁的优点和不足
乐观并发控制相信事务之间的数据竞争(data race)的概率是比较小的,因此尽可能直接做下去,直到提交的时候才去锁定,所以不会产生任何锁和死锁。但如果直接简单这么做,还是有可能会遇到不可预期的结果,例如两个事务都读取了数据库的某一行,经过修改以后写回数据库,这时就遇到了问题。
意向共享锁/意向排他锁
由于表锁和行锁虽然锁定范围不同,但是会相互冲突。所以当你要加表锁时,势必要先遍历该表的所有记录,判断是否加有排他锁。这种遍历检查的方式显然是一种低效的方式,MysqL 引入了意向锁,来检测表锁和行锁的冲突。
意向锁也是表级锁,也可分为读意向锁(IS 锁)和写意向锁(IX 锁)。当事务要在记录上加上读锁或写锁时,要首先在表上加上意向锁。这样判断表中是否有记录加锁就很简单了,只要看下表上是否有意向锁就行了。
意向锁之间是不会产生冲突的,也不和 AUTO_INC 表锁冲突,它只会阻塞表级读锁或表级写锁,另外,意向锁也不会和行锁冲突,行锁只会和行锁冲突。
意向锁是InnoDB自动加的,不需要用户干预。
对于insert、update、delete,InnoDB会自动给涉及的数据加排他锁(X);
对于一般的Select语句,InnoDB不会加任何锁,事务可以通过以下语句给显示加共享锁或排他锁。
意向共享锁(Intention Shared Lock)
意向共享锁(IS):表示事务准备给数据行加入共享锁,也就是说一个数据行加共享锁前必须先取得该表的IS锁
意向排他锁(Exclusive Lock)
意向排他锁(IX):类似上面,表示事务准备给数据行加入排他锁,说明事务在一个数据行加排他锁前必须先取得该表的IX锁。
记录锁(Record Lock)
记录锁是最简单的行锁,并没有什么好说的。上边描述 InnoDB 加锁原理中的锁就是记录锁,只锁住 id = 49 或者 name = 'Tom' 这一条记录。
当 sql 语句无法使用索引时,会进行全表扫描,这个时候 MysqL 会给整张表的所有数据行加记录锁,再由 MysqL Server 层进行过滤。但是,在 MysqL Server 层进行过滤的时候,如果发现不满足 WHERE 条件,会释放对应记录的锁。这样做,保证了最后只会持有满足条件记录上的锁,但是每条记录的加锁操作还是不能省略的。
所以更新操作必须要根据索引进行操作,没有索引时,不仅会消耗大量的锁资源,增加数据库的开销,还会极大的降低了数据库的并发性能。
间隙锁(Gap Lock)
当我们使用范围条件而不是相等条件检索数据,并请求共享或排他锁时,InnoDB会给符合条件的已有数据记录的索引项加锁;对于键值在条件范围内但并不存在的记录,InnoDB 也会对这个“间隙”加锁,这种锁机制就是所谓的间隙锁。
间隙锁是锁索引记录中的间隔,或者第一条索引记录之前的范围,又或者最后一条索引记录之后的范围。
间隙锁在 InnoDB 的唯一作用就是防止其它事务的插入操作,以此来达到防止幻读的发生,所以间隙锁不分什么共享锁与排他锁。
要禁止间隙锁,可以把隔离级别降为读已提交,或者开启参数 innodb_locks_unsafe_for_binlog
show variables like 'innodb_locks_unsafe_for_binlog';复制代码
innodb_locks_unsafe_for_binlog
:默认
值为OFF,即启用间隙锁。因为此参数是只读模式,如果想要禁用间隙锁,需要修改 my.cnf(windows是my.ini) 重新启动才行。
# 在 my.cnf 里面的[MysqLd]添加 [MysqLd] innodb_locks_unsafe_for_binlog = 1复制代码
案例1:唯一索引的间隙锁
测试环境:
MysqL5.7,InnoDB,默认的隔离级别(RR)
示例表:
CREATE TABLE `my_gap` ( `id` int(1) NOT NULL AUTO_INCREMENT, `name` varchar(8) DEFAULT NULL, PRIMARY KEY (`id`) ) ENGINE=InnoDB DEFAULT CHARSET=utf8;INSERT INTO `my_gap` VALUES ('1', '张三');INSERT INTO `my_gap` VALUES ('5', '李四');INSERT INTO `my_gap` VALUES ('7', '王五');INSERT INTO `my_gap` VALUES ('11', '赵六');复制代码
在进行测试之前,我们先看看 my_gap 表中存在的隐藏间隙:
- (-infinity, 1]
- (1, 5]
- (5, 7]
- (7, 11]
- (11, +infinity]
只使用记录锁(行锁),不会产生间隙锁
/* 开启事务1 */BEGIN;/* 查询 id = 5 的数据并加记录锁 */SELECT * FROM `my_gap` WHERE `id` = 5 FOR UPDATE;/* 延迟30秒执行,防止锁释放 */SELECT SLEEP(30); # 注意:以下的语句不是放在一个事务中执行,而是分开多次执行,每次事务中只有一条添加语句/* 事务2插入一条 name = '杰伦' 的数据 */INSERT INTO `my_gap` (`id`, `name`) VALUES (4, '杰伦'); # 正常执行/* 事务3插入一条 name = '学友' 的数据 */INSERT INTO `my_gap` (`id`, `name`) VALUES (8, '学友'); # 正常执行/* 提交事务1,释放事务1的锁 */COMMIT;复制代码
上述案例,由于主键是唯一索引,而且只使用一个索引查询,并且只锁定了一条记录,所以只会对 id = 5
的数据加上记录锁(行锁),而不会产生间隙锁。
产生间隙锁
恢复初始化的4条记录,继续在 id 唯一索引列上做以下测试:
/* 开启事务1 */BEGIN;/* 查询 id 在 7 - 11 范围的数据并加记录锁 */SELECT * FROM `my_gap` WHERE `id` BETWEEN 5 AND 7 FOR UPDATE;/* 延迟30秒执行,防止锁释放 */SELECT SLEEP(30); # 注意:以下的语句不是放在一个事务中执行,而是分开多次执行,每次事务中只有一条添加语句/* 事务2插入一条 id = 3,name = '思聪3' 的数据 */INSERT INTO `my_gap` (`id`, `name`) VALUES (3, '思聪3'); # 正常执行/* 事务3插入一条 id = 4,name = '思聪4' 的数据 */INSERT INTO `my_gap` (`id`, `name`) VALUES (4, '思聪4'); # 正常执行/* 事务4插入一条 id = 6,name = '思聪6' 的数据 */INSERT INTO `my_gap` (`id`, `name`) VALUES (6, '思聪6'); # 阻塞/* 事务5插入一条 id = 8, name = '思聪8' 的数据 */INSERT INTO `my_gap` (`id`, `name`) VALUES (8, '思聪8'); # 阻塞/* 事务6插入一条 id = 9, name = '思聪9' 的数据 */INSERT INTO `my_gap` (`id`, `name`) VALUES (9, '思聪9'); # 阻塞/* 事务7插入一条 id = 11, name = '思聪11' 的数据 */INSERT INTO `my_gap` (`id`, `name`) VALUES (11, '思聪11'); # 阻塞/* 事务8插入一条 id = 12, name = '思聪12' 的数据 */INSERT INTO `my_gap` (`id`, `name`) VALUES (12, '思聪12'); # 正常执行/* 提交事务1,释放事务1的锁 */COMMIT;复制代码
从上面可以看到,(5,7]、(7,11] 这两个区间,都不可插入数据,其它区间,都可以正常插入数据。所以可以得出结论:当我们给(5,7] 这个区间加锁的时候,会锁住(5,7]、(7,11] 这两个区间。
恢复初始化的4条记录,我们再来测试如果锁住不存在的数据时,会如何?
/* 开启事务1 */BEGIN;/* 查询 id = 3 这一条不存在的数据并加记录锁 */SELECT * FROM `my_gap` WHERE `id` = 3 FOR UPDATE;/* 延迟30秒执行,防止锁释放 */SELECT SLEEP(30); # 注意:以下的语句不是放在一个事务中执行,而是分开多次执行,每次事务中只有一条添加语句/* 事务2插入一条 id = 3,name = '小张' 的数据 */INSERT INTO `my_gap` (`id`, `name`) VALUES (2, '小张'); # 阻塞/* 事务3插入一条 id = 4,name = '小白' 的数据 */INSERT INTO `my_gap` (`id`, `name`) VALUES (4, '小白'); # 阻塞/* 事务4插入一条 id = 6,name = '小东' 的数据 */INSERT INTO `my_gap` (`id`, `name`) VALUES (6, '小东'); # 正常执行/* 事务5插入一条 id = 8, name = '大罗' 的数据 */INSERT INTO `my_gap` (`id`, `name`) VALUES (8, '大罗'); # 正常执行/* 提交事务1,释放事务1的锁 */COMMIT;复制代码
从上面可以看出,指定查询某一条记录时,如果这条记录不存在,会产生间隙锁。
结论
- 对于指定查询某一条记录的加锁语句,如果该记录不存在,会产生记录锁(行锁)和间隙锁,如果记录存在,则只会产生记录锁(行锁);
- 对于查找某一范围内的查询语句,会产生间隙锁。
案例2:普通索引的间隙锁
示例表:id 是主键,在 number 上,建立了一个普通索引。
# 注意:number 不是唯一值CREATE TABLE `my_gap1` ( `id` int(1) NOT NULL AUTO_INCREMENT, `number` int(1) NOT NULL COMMENT '数字', PRIMARY KEY (`id`), KEY `number` (`number`) USING BTREE ) ENGINE=InnoDB AUTO_INCREMENT=1 DEFAULT CHARSET=utf8;INSERT INTO `my_gap1` VALUES (1, 1);INSERT INTO `my_gap1` VALUES (5, 3);INSERT INTO `my_gap1` VALUES (7, 8);INSERT INTO `my_gap1` VALUES (11, 12);复制代码
在进行测试之前,我们先来看看 my_gap1 表中 number 索引存在的隐藏间隙:
- (-infinity, 1]
- (1, 3]
- (3, 8]
- (8, 12]
- (12, +infinity]
测试1
我们执行以下的事务(事务1最后提交),分别执行下面的语句:
/* 开启事务1 */BEGIN;/* 查询 number = 3 的数据并加记录锁 */SELECT * FROM `my_gap1` WHERE `number` = 3 FOR UPDATE;/* 延迟30秒执行,防止锁释放 */SELECT SLEEP(30); # 注意:以下的语句不是放在一个事务中执行,而是分开多次执行,每次事务中只有一条添加语句/* 事务2插入一条 number = 0 的数据 */INSERT INTO `my_gap1` (`number`) VALUES (0); # 正常执行/* 事务3插入一条 number = 1 的数据 */INSERT INTO `my_gap1` (`number`) VALUES (1); # 被阻塞/* 事务4插入一条 number = 2 的数据 */INSERT INTO `my_gap1` (`number`) VALUES (2); # 被阻塞/* 事务5插入一条 number = 4 的数据 */INSERT INTO `my_gap1` (`number`) VALUES (4); # 被阻塞/* 事务6插入一条 number = 8 的数据 */INSERT INTO `my_gap1` (`number`) VALUES (8); # 正常执行/* 事务7插入一条 number = 9 的数据 */INSERT INTO `my_gap1` (`number`) VALUES (9); # 正常执行/* 事务8插入一条 number = 10 的数据 */INSERT INTO `my_gap1` (`number`) VALUES (10); # 正常执行/* 提交事务1 */COMMIT;复制代码
我们会发现有些语句可以正常执行,有些语句被阻塞来。查看表中的数据:
这里可以看到,number(1,8) 的间隙中,插入语句都被阻塞来,而不在这个范围内的语句,正常执行,这就是因为有间隙锁的原因。
测试2
我们再进行以下测试,这里将数据还原成初始化那样
/* 开启事务1 */BEGIN;/* 查询 number = 3 的数据并加记录锁 */SELECT * FROM `my_gap1` WHERE `number` = 3 FOR UPDATE;/* 延迟30秒执行,防止锁释放 */SELECT SLEEP(30);/* 事务1插入一条 id = 2, number = 1 的数据 */INSERT INTO `my_gap1` (`id`, `number`) VALUES (2, 1); # 阻塞/* 事务2插入一条 id = 3, number = 2 的数据 */INSERT INTO `my_gap1` (`id`, `number`) VALUES (3, 2); # 阻塞/* 事务3插入一条 id = 6, number = 8 的数据 */INSERT INTO `my_gap1` (`id`, `number`) VALUES (6, 8); # 阻塞/* 事务4插入一条 id = 8, number = 8 的数据 */INSERT INTO `my_gap1` (`id`, `number`) VALUES (8, 8); # 正常执行/* 事务5插入一条 id = 9, number = 9 的数据 */INSERT INTO `my_gap1` (`id`, `number`) VALUES (9, 9); # 正常执行/* 事务6插入一条 id = 10, number = 12 的数据 */INSERT INTO `my_gap1` (`id`, `number`) VALUES (10, 12); # 正常执行/* 事务7修改 id = 11, number = 12 的数据 */UPDATE `my_gap1` SET `number` = 5 WHERE `id` = 11 AND `number` = 12; # 阻塞/* 提交事务1 */COMMIT;复制代码
查看表中的数据;
这里有一个奇怪的现象:
- 事务3 添加 id = 6,number = 8 的数据,阻塞了;
- 事务4 添加 id = 8,number = 8 的数据,正常执行了;
- 事务7 将 id = 11,number = 12 的数据修改为 id = 11, number = 5 的操作,给阻塞了。
这是为什么?我们来看看下面的图:
从图中库看出,当 number 相同时,会根据主键 id 来排序
- 事务 3 添加的 id = 6,number = 8,这条数据是在 (3,8) 的区间里边,所以会阻塞;
- 事务 4 添加的 id = 8,number = 8,这条数据实在 (8,12) 区间里边,所以不会阻塞;
- 事务 7 的修改语句相当于 在 (3,8) 的区间里边插入一条数据,所以也被阻塞了。
结论
- 在普通索引列上,不管是何种查询,只要加锁,都会产生间隙锁,这跟唯一索引不一样;
- 在普通索引跟唯一索引中,数据间隙的分析,数据行是优先根据普通普通索引排序,再根据唯一索引排序。
临键锁(Next-key Locks)
临键锁,是记录锁(行锁)与间隙锁的组合,它的锁范围,即包含索引记录,又包含索引区间。它指的是加在某条记录以及这条记录前面间隙上的锁。假设一个索引包含 15、18、20 ,30,49,50 这几个值,可能的 Next-key 锁如下:
(-∞, 15],(15, 18],(18, 20],(20, 30],(30, 49],(49, 50],(50, +∞)复制代码
通常我们都用这种左开右闭区间来表示 Next-key 锁,其中,圆括号表示不包含该记录,方括号表示包含该记录。前面四个都是 Next-key 锁,最后一个为间隙锁。和间隙锁一样,在 RC 隔离级别下没有 Next-key 锁,只有 RR 隔离级别才有。还是之前的例子,如果 id 不是主键,而是二级索引,且不是唯一索引,那么这个 sql 在 RR 隔离级别下就会加如下的 Next-key 锁 (30, 49](49, 50)
此时如果插入一条 id = 31 的记录将会阻塞住。之所以要把 id = 49 前后的间隙都锁住,仍然是为了解决幻读问题,因为 id 是非唯一索引,所以 id = 49 可能会有多条记录,为了防止再插入一条 id = 49 的记录。
注意:临键锁的主要目的,也是为了避免幻读(Phantom Read)。如果把事务隔离级别降级为 RC,临键锁则也会失效。
插入意向锁(Insert Intention Locks)
插入意向锁是一种特殊的间隙锁(简称II GAP)表示插入的意向,只有在 INSERT 的时候才会有这个锁。注意,这个锁虽然也叫意向锁,但是和上面介绍的表级意向锁是两个完全不同的概念,不要搞混了。
插入意向锁和插入意向锁之间互不冲突,所以可以在同一个间隙中有多个事务同时插入不同索引的记录。譬如在例子中,id = 30 和 id = 49 之间如果有两个事务要同时分别插入 id = 32 和 id = 33 是没问题的,虽然两个事务都会在 id = 30 和 id = 50 之间加上插入意向锁,但是不会冲突。
插入意向锁只会和间隙锁或 Next-key 锁冲突,正如上面所说,间隙锁唯一的作用就是防止其他事务插入记录造成幻读,正是由于在执行 INSERT 语句时需要加插入意向锁,而插入意向锁和间隙锁冲突,从而阻止了插入操作的执行。
插入意向锁的作用:
- 为来唤起等待。由于该间隙已经有锁,插入时必须阻塞,插入意向锁的作用具有阻塞功能;
- 插入意向锁是一种特殊的间隙锁,既然是一种间隙锁,为什么不直接使用间隙锁?间隙锁直接不相互排斥。不可以阻塞即唤起等待,会造成幻读。
- 为什么不实用记录锁(行锁)或 临键锁?申请了记录锁或临键锁,临键锁之间可能相互排斥,即影响 insert 的并发性。
自增锁(Auto-inc Locks)
AUTO_INC 锁又叫自增锁(一般简写成 AI 锁),是一种表锁,当表中有自增列(AUTO_INCREMENT)时出现。当插入表中有自增列时,数据库需要自动生成自增值,它会先为该表加 AUTO_INC 表锁,阻塞其他事务的插入操作,这样保证生成的自增值肯定是唯一的。AUTO_INC 锁具有如下特点:
- AUTO_INC 锁互不兼容,也就是说同一张表同时只允许有一个自增锁;
- 自增值一旦分配了就会 +1,如果事务回滚,自增值也不会减回去,所以自增值可能会出现中断的情况。
自增操作
使用AUTO_INCREMENT
函数实现自增操作,自增幅度通过 auto_increment_offset
和auto_increment_increment
这2个参数进行控制:
- auto_increment_offset 表示起始数字
- auto_increment_increment 表示调动幅度(即每次增加n个数字,2就代表每次+2)
通过使用last_insert_id()函数可以获得最后一个插入的数字
select last_insert_id();复制代码
自增锁
首先insert大致上可以分成三类:
- simple insert 如insert into t(name) values('test')
- bulk insert 如load data | insert into ... select .... from ....
- mixed insert 如insert into t(id,name) values(1,'a'),(null,'b'),(5,'c');
如果存在自增字段,MysqL 会维护一个自增锁,和自增锁相关的一个参数为(5.1.22 版本后加入) innodb_autoinc_lock_mode
,可以设定 3 值:
- 0 :Traditonal (每次都会产生表锁)
- 1 :consecutive(会产生一个轻量锁,simple insert 会获得批量的锁,保证连续插入)
- 2 :interleaved (不会锁表,来一个处理一个,并发最高)
MyISam引擎均为 Traditonal,每次均会进行表锁。但是InnoDB引擎会视参数不同产生不同的锁,默认为 1:consecutive。
show variables like 'innodb_autoinc_lock_mode';复制代码
Traditonal
innodb_autoinc_lock_mode
为 0 时,也就是 Traditional 级别。该自增锁时表锁级别,且必须等待当前 sql 执行完毕后或者回滚才会释放,在高并发的情况下可想而知自增锁竞争时比较大的。
- 它提供来一个向后兼容的能力
- 在这一模式下,所有的 insert 语句(“insert like”)都要在语句开始的时候得到一个表级的 auto_inc 锁,在语句结束的时候才释放这把锁。注意,这里说的是语句级而不是事务级的,一个事务可能包含有一个或多个语句;
- 它能保证值分配的可预见性、可连续性、可重复性,这个也就是保证了 insert 语句在复制到 slave 的时候还能生成和 master 那边一样的值(它保证了基于语句复制的安全);
- 由于在这种模式下 auto_inc 锁一直要保持到语句的结束,所以这个就影响了并发的插入。
consecutive
innodb_autoinc_lock_mode 为 1 时,也就是 consecutive 级别。这是如果是单一的 insert sql,可以立即获得该锁,并立即释放,而不必等待当前sql执行完成(除非在其它事务中已经有 session 获取了自增锁)。另外当sql是一些批量 insert sql 时,比如 insert into ... select ...
, load data
, replace ... select ...
时,这时还是表级锁,可以理解为退化为必须等待当前 sql 执行完才释放。可以认为,该值为 1 时相对比较轻量级的锁,也不会对复制产生影响,唯一的缺陷是产生自增值不一定是完全连续的。
- 这一模式下对 simple insert 做了优化,由于 simple insert 一次性插入的值的个数可以立马得到确定,所以 MyQL 可以一次生成几个连续的值,用于这个 insert 语句。总得来说这个对复制也是安全的(它保证了基于语句复制的安全);
- 这一模式也是MysqL的默认模式,这个模式的好处是 auto_inc 锁不要一直保持到语句的结束,只要语句得到了相应的值就可以提前释放锁。
interleaved
innodb_autoinc_lock_mode 为 2 时,也就是 interleaved 级别。所有 insert 种类的 sql 都可以立马获得锁并释放,这时的效率最高。但是会引入一个新的问题:当 binlog_format 为 statement 时,这是复制没法保证安全,因为批量的 insert,比如 insert ... select ...
语句在这个情况下,也可以立马获取到一大批的自增 id 值,不必锁整个表, slave 在回放这个 sql 时必然会产生错乱。
- 由于这个模式下已经没有了 auto_inc 锁,所以这个模式下的性能是最好的,但是也有一个问题,就是对于同一个语句来说它所得到的 auto_incremant 值可能不是连续的。
如果你的二进制文件格式是mixed | row 那么这三个值中的任何一个对于你来说都是复制安全的。
由于现在MysqL已经推荐把二进制的格式设置成row,所以在binlog_format不是statement的情况下最好是innodb_autoinc_lock_mode=2 这样可能知道更好的性能。
总结
InnoDB锁的特性
- 在不通过索引条件查询的时候,InnoDB使用的确实是表锁!
- 由于 MysqL 的行锁是针对索引加的锁,不是针对记录加的锁,所以虽然是访问不同行 的记录,但是如果是使用相同的索引键,是会出现锁冲突的。
- 当表有多个索引的时候,不同的事务可以使用不同的索引锁定不同的行,另外,不论 是使用主键索引、唯一索引或普通索引,InnoDB 都会使用行锁来对数据加锁。
- 即便在条件中使用了索引字段,但是否使用索引来检索数据是由 MysqL 通过判断不同 执行计划的代价来决定的,如果 MysqL 认为全表扫 效率更高,比如对一些很小的表,它 就不会使用索引,这种情况下 InnoDB 将使用表锁,而不是行锁。因此,在分析锁冲突时, 别忘了检查 sql 的执行计划(explain查看),以确认是否真正使用了索引。
锁模式
锁的模式有:读意向锁,写意向锁,读锁,写锁和自增锁(auto_inc)。
不同模式锁的兼容矩阵
IS | IX | S | X | AI | |
---|---|---|---|---|---|
IS | 兼容 | 兼容 | 兼容 | 兼容 | |
IX | 兼容 | 兼容 | 兼容 | ||
S | 兼容 | 兼容 | |||
X | |||||
AI | 兼容 | 兼容 |
总结起来有下面几点:
- 意向锁之间互不冲突;
- S 锁只和 S/IS 锁兼容,和其他锁都冲突;
- X 锁和其他所有锁都冲突;
- AI 锁只和意向锁兼容;
锁的类型
根据锁的粒度可以把锁细分为表锁和行锁,行锁根据场景的不同又可以进一步细分,依次为 Next-Key Lock,Gap Lock 间隙锁,Record Lock 记录锁和插入意向 GAP 锁。
不同的锁锁定的位置是不同的,比如说记录锁只锁住对应的记录,而间隙锁锁住记录和记录之间的间隔,Next-Key Lock 则所属记录和记录之前的间隙。不同类型锁的锁定范围大致如下图所示。
不同类型锁的兼容矩阵
RECORD | GAP | NEXT-KEY | II GAP | |
---|---|---|---|---|
RECORD | 兼容 | 兼容 | ||
GAP | 兼容 | 兼容 | 兼容 | 兼容 |
NEXT-KEY | 兼容 | 兼容 | ||
II GAP | 兼容 | 兼容 |
其中,第一行表示已有的锁,第一列表示要加的锁。插入意向锁较为特殊,所以我们先对插入意向锁做个总结,如下:
- 插入意向锁不影响其他事务加其他任何锁。也就是说,一个事务已经获取了插入意向锁,对其他事务是没有任何影响的;
- 插入意向锁与间隙锁和 Next-key 锁冲突。也就是说,一个事务想要获取插入意向锁,如果有其他事务已经加了间隙锁或 Next-key 锁,则会阻塞。
其他类型的锁的规则较为简单:
- 间隙锁不和其他锁(不包括插入意向锁)冲突;
记录锁和记录锁冲突,Next-key 锁和 Next-key 锁冲突,记录锁和 Next-key 锁冲突;
MySQL 的死锁系列 - 锁的类型以及加锁原理
转自:程序员历小冰 作者:历小冰
疫情期间在家工作时,同事使用了 insert into on duplicate key update 语句进行插入去重,但是在测试过程中发生了死锁现象:
ERROR 1213 (40001): Deadlock found when trying to get lock;
try restarting transaction
由于开发任务紧急,只是暂时规避了一下,但是对触发死锁的原因和相关原理不甚了解,于是这几天一直在查阅相关资料,总结出一个系列文章供大家参考,本篇是上篇,主要介绍 MySQL 加锁原理和锁的不同模式或类型的基本知识。后续会讲解常见语句的加锁情况和通过 MySQL 死锁日志分析死锁原因。
由于本篇文章涉及很多 MySQL 的基础知识,大家可以自行阅读我之前的 MySQL 系列文章 《MySQL 探秘》(公众号菜单处可进入系列文章) 中的对应章节。
表锁和行锁
我们首先来了解一下表锁和行锁:表锁是指对一整张表加锁,一般是 DDL 处理时使用;而行锁则是锁定某一行或者某几行,或者行与行之间的间隙。
表锁由 MySQL Server 实现,行锁则是存储引擎实现,不同的引擎实现的不同。在 MySQL 的常用引擎中 InnoDB 支持行锁,而 MyISAM 则只能使用 MySQL Server 提供的表锁。
表锁
表锁由 MySQL Server 实现,一般在执行 DDL 语句时会对整个表进行加锁,比如说 ALTER TABLE 等操作。在执行 SQL 语句时,也可以明确指定对某个表进行加锁。
mysql> lock table user read(write); # 分为读锁和写锁
Query OK, 0 rows affected (0.00 sec)
mysql> select * from user where id = 100; # 成功
mysql> select * from role where id = 100; # 失败,未提前获取该 role的读表锁
mysql> update user set name = ''Tom'' where id = 100; # 失败,未提前获得user的写表锁
mysql> unlock tables; # 显示释放表锁
Query OK, 0 rows affected (0.00 sec)
表锁使用的是一次性锁技术,也就是说,在会话开始的地方使用 lock 命令将后续需要用到的表都加上锁,在表释放前,只能访问这些加锁的表,不能访问其他表,直到最后通过 unlock tables 释放所有表锁。
除了使用 unlock tables 显示释放锁之外,会话持有其他表锁时执行 lock table 语句会释放会话之前持有的锁;会话持有其他表锁时执行 start transaction 或者 begin 开启事务时,也会释放之前持有的锁。
行锁
不同存储引擎的行锁实现不同,后续没有特别说明,则行锁特指 InnoDB 实现的行锁。
在了解 InnoDB 的加锁原理前,需要对其存储结构有一定的了解。InnoDB 是聚簇索引,也就是 B + 树的叶节点既存储了主键索引也存储了数据行。而 InnoDB 的二级索引的叶节点存储的则是主键值,所以通过二级索引查询数据时,还需要拿对应的主键去聚簇索引中再次进行查询。关于 InnoDB 和 MyISAM 的索引的详细知识可以阅读《Mysql 探索 (一):B+Tree 索引》一文。
下面以两条 SQL 的执行为例,讲解一下 InnoDB 对于单行数据的加锁原理。
update user set age = 10 where id = 49;
update user set age = 10 where name = ''Tom'';
第一条 SQL 使用主键索引来查询,则只需要在 id = 49 这个主键索引上加上写锁;第二条 SQL 则使用二级索引来查询,则首先在 name = Tom 这个索引上加写锁,然后由于使用 InnoDB 二级索引还需再次根据主键索引查询,所以还需要在 id = 49 这个主键索引上加写锁,如上图所示。
也就是说使用主键索引需要加一把锁,使用二级索引需要在二级索引和主键索引上各加一把锁。
根据索引对单行数据进行更新的加锁原理了解了,那如果更新操作涉及多个行呢,比如下面 SQL 的执行场景。
update user set age = 10 where id > 49;
上述 SQL 的执行过程如下图所示。MySQL Server 会根据 WHERE 条件读取第一条满足条件的记录,然后 InnoDB 引擎会将第一条记录返回并加锁,接着 MySQL Server 发起更新改行记录的 UPDATE 请求,更新这条记录。一条记录操作完成,再读取下一条记录,直至没有匹配的记录为止。
这种场景下的锁的释放较为复杂,有多种的优化方式,我对这块暂时还没有了解,还请知道的小伙伴在下方留言解释。
下面主要依次介绍 InnoDB 中锁的模式和类型,锁的类型是指锁的粒度或者锁具体加在什么地方;而锁模式描述的是锁的兼容性,也就是加的是什么锁,比如写锁或者读锁。
内容基本来自于 MySQL 的技术文档 innodb-lock 一章,感兴趣的同学可以直接去阅读原文,原文地址为见文章末尾。
行锁的模式
锁的模式有:读意向锁,写意向锁,读锁,写锁和自增锁 (auto_inc),下面我们依次来看。
读写锁
读锁,又称共享锁(Share locks,简称 S 锁),加了读锁的记录,所有的事务都可以读取,但是不能修改,并且可同时有多个事务对记录加读锁。
写锁,又称排他锁(Exclusive locks,简称 X 锁),或独占锁,对记录加了排他锁之后,只有拥有该锁的事务可以读取和修改,其他事务都不可以读取和修改,并且同一时间只能有一个事务加写锁。
读写意向锁
由于表锁和行锁虽然锁定范围不同,但是会相互冲突。所以当你要加表锁时,势必要先遍历该表的所有记录,判断是否加有排他锁。这种遍历检查的方式显然是一种低效的方式,MySQL 引入了意向锁,来检测表锁和行锁的冲突。
意向锁也是表级锁,也可分为读意向锁(IS 锁)和写意向锁(IX 锁)。当事务要在记录上加上读锁或写锁时,要首先在表上加上意向锁。这样判断表中是否有记录加锁就很简单了,只要看下表上是否有意向锁就行了。
意向锁之间是不会产生冲突的,也不和 AUTO_INC 表锁冲突,它只会阻塞表级读锁或表级写锁,另外,意向锁也不会和行锁冲突,行锁只会和行锁冲突。
自增锁
AUTOINC 锁又叫自增锁(一般简写成 AI 锁),是一种表锁,当表中有自增列(AUTOINCREMENT)时出现。当插入表中有自增列时,数据库需要自动生成自增值,它会先为该表加 AUTOINC 表锁,阻塞其他事务的插入操作,这样保证生成的自增值肯定是唯一的。AUTOINC 锁具有如下特点:
AUTO_INC 锁互不兼容,也就是说同一张表同时只允许有一个自增锁;
自增值一旦分配了就会 +1,如果事务回滚,自增值也不会减回去,所以自增值可能会出现中断的情况。
显然,AUTOINC 表锁会导致并发插入的效率降低,为了提高插入的并发性,MySQL 从 5.1.22 版本开始,引入了一种可选的轻量级锁(mutex)机制来代替 AUTOINC 锁,可以通过参数 innodbautoinclockmode 来灵活控制分配自增值时的并发策略。具体可以参考 MySQL 的 AUTOINCREMENT Handling in InnoDB 一文,链接在文末。
不同模式锁的兼容矩阵
下面是各个表锁之间的兼容矩阵。
总结起来有下面几点:
意向锁之间互不冲突;
S 锁只和 S/IS 锁兼容,和其他锁都冲突;
X 锁和其他所有锁都冲突;
AI 锁只和意向锁兼容;
行锁的类型
根据锁的粒度可以把锁细分为表锁和行锁,行锁根据场景的不同又可以进一步细分,依次为 Next-Key Lock,Gap Lock 间隙锁,Record Lock 记录锁和插入意向 GAP 锁。
不同的锁锁定的位置是不同的,比如说记录锁只锁住对应的记录,而间隙锁锁住记录和记录之间的间隔,Next-Key Lock 则所属记录和记录之前的间隙。不同类型锁的锁定范围大致如下图所示。
下面我们来依次了解一下不同的类型的锁。
记录锁
记录锁是最简单的行锁,并没有什么好说的。上边描述 InnoDB 加锁原理中的锁就是记录锁,只锁住 id = 49 或者 name = ''Tom'' 这一条记录。
当 SQL 语句无法使用索引时,会进行全表扫描,这个时候 MySQL 会给整张表的所有数据行加记录锁,再由 MySQL Server 层进行过滤。但是,在 MySQL Server 层进行过滤的时候,如果发现不满足 WHERE 条件,会释放对应记录的锁。这样做,保证了最后只会持有满足条件记录上的锁,但是每条记录的加锁操作还是不能省略的。
所以更新操作必须要根据索引进行操作,没有索引时,不仅会消耗大量的锁资源,增加数据库的开销,还会极大的降低了数据库的并发性能。
间隙锁
还是最开始更新用户年龄的例子,如果 id = 49 这条记录不存在,这个 SQL 语句还会加锁吗?答案是可能有,这取决于数据库的隔离级别。这种情况下,在 RC 隔离级别不会加任何锁,在 RR 隔离级别会在 id = 49 前后两个索引之间加上间隙锁。
间隙锁是一种加在两个索引之间的锁,或者加在第一个索引之前,或最后一个索引之后的间隙。这个间隙可以跨一个索引记录,多个索引记录,甚至是空的。使用间隙锁可以防止其他事务在这个范围内插入或修改记录,保证两次读取这个范围内的记录不会变,从而不会出现幻读现象。
值得注意的是,间隙锁和间隙锁之间是互不冲突的,间隙锁唯一的作用就是为了防止其他事务的插入,所以加间隙 S 锁和加间隙 X 锁没有任何区别。
Next-Key 锁
Next-key 锁是记录锁和间隙锁的组合,它指的是加在某条记录以及这条记录前面间隙上的锁。假设一个索引包含 15、18、20 ,30,49,50 这几个值,可能的 Next-key 锁如下:
(-∞, 15],(15, 18],(18, 20],(20, 30],(30, 49],(49, 50],(50, +∞)
通常我们都用这种左开右闭区间来表示 Next-key 锁,其中,圆括号表示不包含该记录,方括号表示包含该记录。前面四个都是 Next-key 锁,最后一个为间隙锁。和间隙锁一样,在 RC 隔离级别下没有 Next-key 锁,只有 RR 隔离级别才有。还是之前的例子,如果 id 不是主键,而是二级索引,且不是唯一索引,那么这个 SQL 在 RR 隔离级别下就会加如下的 Next-key 锁 (30, 49](49, 50)
此时如果插入一条 id = 31 的记录将会阻塞住。之所以要把 id = 49 前后的间隙都锁住,仍然是为了解决幻读问题,因为 id 是非唯一索引,所以 id = 49 可能会有多条记录,为了防止再插入一条 id = 49 的记录。
插入意向锁
插入意向锁是一种特殊的间隙锁(简写成 II GAP)表示插入的意向,只有在 INSERT 的时候才会有这个锁。注意,这个锁虽然也叫意向锁,但是和上面介绍的表级意向锁是两个完全不同的概念,不要搞混了。
插入意向锁和插入意向锁之间互不冲突,所以可以在同一个间隙中有多个事务同时插入不同索引的记录。譬如在上面的例子中,id = 30 和 id = 49 之间如果有两个事务要同时分别插入 id = 32 和 id = 33 是没问题的,虽然两个事务都会在 id = 30 和 id = 50 之间加上插入意向锁,但是不会冲突。
插入意向锁只会和间隙锁或 Next-key 锁冲突,正如上面所说,间隙锁唯一的作用就是防止其他事务插入记录造成幻读,正是由于在执行 INSERT 语句时需要加插入意向锁,而插入意向锁和间隙锁冲突,从而阻止了插入操作的执行。
不同类型锁的兼容矩阵
不同类型锁的兼容下如下图所示。
其中,第一行表示已有的锁,第一列表示要加的锁。插入意向锁较为特殊,所以我们先对插入意向锁做个总结,如下:
插入意向锁不影响其他事务加其他任何锁。也就是说,一个事务已经获取了插入意向锁,对其他事务是没有任何影响的;
插入意向锁与间隙锁和 Next-key 锁冲突。也就是说,一个事务想要获取插入意向锁,如果有其他事务已经加了间隙锁或 Next-key 锁,则会阻塞。
其他类型的锁的规则较为简单:
间隙锁不和其他锁(不包括插入意向锁)冲突;
记录锁和记录锁冲突,Next-key 锁和 Next-key 锁冲突,记录锁和 Next-key 锁冲突;
参考
https://dev.mysql.com/doc/refman/5.7/en/innodb-locking.html#innodb-record-locks
https://dev.mysql.com/doc/refman/5.7/en/innodb-locking.html#innodb-record-locks
https://www.aneasystone.com/archives/2017/11/solving-dead-locks-two.html
相关推荐
双剑合璧的开源项目 Kitty-Cloud
Kitty-Cloud 环境准备
扫码领取学习资料
后台回复 学习资料 即可领取
如有收获,点个在看,诚挚感谢
本文分享自微信公众号 - 猿天地(cxytiandi)。
如有侵权,请联系 support@oschina.cn 删除。
本文参与 “OSC 源创计划”,欢迎正在阅读的你也加入,一起分享。
Mysql学习深入理解MySQL索引与优化
《MysqL学习深入理解MysqL索引与优化》要点:
本文介绍了MysqL学习深入理解MysqL索引与优化,希望对您有用。如果有疑问,可以联系我们。
MysqL索引深入探讨
索引对查询的速度有着至关重要的影响,假设数据库中一个表有10^6条记录,DBMS的页面大小为4K,并存储100条记录.
如果没有索引,查询将对整个表进行扫描,最坏的情况下,如果所有数据页都不在内存,必要读取10^4个页面,如果这10^4个页面在磁盘上随机分布,必要进行10^4次I/O,假设磁盘每次I/O时间为10ms(忽略数据传输时间),则总共必要100s(但实际上要好很多很多).
如果对之建立B-Tree索引,则只必要进行log100(10^6)=3次页面读取,最坏情况下耗时30ms.这就是索引带来的效果,很多时候,当你的应用程序进行SQL查询速度很慢时,应该想想是否可以建索引.MysqL学习
第二章、MysqL索引与索引优化MysqL学习
1、选择索引的数据类型MysqL学习
MysqL支持很多数据类型,选择合适的数据类型存储数据对性能有很大的影响.通常来说,可以遵循以下一些指导原则:MysqL学习
(1)越小的数据类型通常更好:越小的数据类型通常在磁盘、内存和cpu缓存中都需要更少的空间,处理起来更快.(2)简单的数据类型更好:整型数据比起字符,处理开销更小,因为字符串的比较更复杂.在MysqL中,应该用内置的日期和时间数据类型,而不是用字符串来存储时间;以及用整型数据类型存储IP地址.
(3)尽量避免NULL:应该指定列为NOT NULL,除非你想存储NULL.在MysqL中,含有空值的列很难进行查询优化,因为它们使得索引、索引的统计信息以及比较运算更加复杂.你应该用0、一个特殊的值或者一个空串代替空值.
1.1、选择标识符
选择合适的标识符是非常重要的.选择时不仅应该考虑存储类型,而且应该考虑MysqL是怎样进行运算和比较的.一旦选定数据类型,应该保证所有相关的表都使用相同的数据类型.
(1) 整型:通常是作为标识符的最好选择,因为可以更快的处理,而且可以设置为AUTO_INCREMENT.
(2) 字符串:尽量避免使用字符串作为标识符,它们消耗更好的空间,处理起来也较慢.而且,通常来说,字符串都是随机的,所以它们在索引中的位置也是随机的,这会导致页面分裂、随机拜访磁盘,聚簇索引分裂(对于使用聚簇索引的存储引擎).MysqL学习
2、索引入门
对于任何DBMS,索引都是进行优化的最主要的因素.对于少量的数据,没有合适的索引影响不是很大,但是,当随着数据量的增加,性能会急剧下降.
如果对多列进行索引(组合索引),列的顺序非常重要,MysqL仅能对索引最左边的前缀进行有效的查找.例如:
假设存在组合索引it1c1c2(c1,c2),查询语句select * from t1 where c1=1 and c2=2能够使用该索引.查询语句select * from t1 where c1=1也能够使用该索引.但是,查询语句select * from t1 where c2=2不能够使用该索引,因为没有组合索引的引导列,即,要想使用c2列进行查找,必需出现c1等于某值.
2.1、索引的类型
索引是在存储引擎中实现的,而不是在服务器层中实现的.所以,每种存储引擎的索引都不必定完全相同,并不是所有的存储引擎都支持所有的索引类型.
2.1.1、B-Tree索引
假设有如下一个表:
MysqL学习
CREATE TABLE People (MysqL学习
last_name varchar(50) not null,MysqL学习
first_name varchar(50) not null,MysqL学习
dob date not null,MysqL学习
gender enum('m','f') not null,MysqL学习
key(last_name,first_name,dob)MysqL学习
);MysqL学习
其索引包括表中每一行的last_name、first_name和dob列.其结构大致如下:MysqL学习
MysqL学习
索引存储的值按索引列中的顺序排列.可以利用B-Tree索引进行全关键字、关键字范围和关键字前缀查询,当然,如果想使用索引,你必须保证按索引的最左边前缀(leftmost prefix of the index)来进行查询.
(1)匹配全值(Match the full value):对索引中的所有列都指定具体的值.例如,上图中索引可以赞助你查找出生于1960-01-01的Cuba Allen.
(2)匹配最左前缀(Match a leftmost prefix):你可以利用索引查找last name为Allen的人,仅仅使用索引中的第1列.
(3)匹配列前缀(Match a column prefix):例如,你可以利用索引查找last name以J开始的人,这仅仅使用索引中的第1列.
(4)匹配值的范围查询(Match a range of values):可以利用索引查找last name在Allen和Barrymore之间的人,仅仅使用索引中第1列.
(5)匹配部分精确而其它部分进行范围匹配(Match one part exactly and match a range on another part):可以利用索引查找last name为Allen,而first name以字母K开始的人.
(6)仅对索引进行查询(Index-only queries):如果查询的列都位于索引中,则不需要读取元组的值.
由于B-树中的节点都是顺序存储的,所以可以利用索引进行查找(找某些值),也可以对查询结果进行ORDER BY.当然,使用B-tree索引有以下一些限制:
(1) 查询必须从索引的最左边的列开始.关于这点已经提了很多遍了.例如你不能利用索引查找在某一天出生的人.
(2) 不能跳过某一索引列.例如,你不能利用索引查找last name为Smith且出生于某一天的人.
(3) 存储引擎不能使用索引中范围条件右边的列.例如,如果你的查询语句为WHERE last_name="Smith" AND first_name LIKE 'J%' AND dob='1976-12-23',则该查询只会使用索引中的前两列,因为LIKE是范围查询.
MysqL学习
2.1.2、Hash索引
MysqL中,只有Memory存储引擎显示支持hash索引,是Memory表的默认索引类型,尽管Memory表也可以使用B-Tree索引.Memory存储引擎支持非唯一hash索引,这在数据库领域是罕见的,如果多个值有相同的hash code,索引把它们的行指针用链表保存到同一个hash表项中.
假设创建如下一个表:
CREATE TABLE testhash (
fname VARCHAR(50) NOT NULL,
lname VARCHAR(50) NOT NULL,
KEY USING HASH(fname)
) ENGINE=MEMORY;
包括的数据如下:
MysqL学习
假设索引使用hash函数f( ),如下:MysqL学习
f('Arjen') = 2323MysqL学习
f('Baron') = 7437MysqL学习
f('Peter') = 8784MysqL学习
f('Vadim') = 2458MysqL学习
此时,索引的结构大概如下:MysqL学习
Slots是有序的,但是记录不是有序的.当你执行
MysqL> SELECT lname FROM testhash WHERE fname='Peter';
MysqL会计算’Peter’的hash值,然后通过它来查询索引的行指针.因为f('Peter') = 8784,MysqL会在索引中查找8784,得到指向记录3的指针.
因为索引自己仅仅存储很短的值,所以,索引非常紧凑.Hash值不取决于列的数据类型,一个tinyint列的索引与一个长字符串列的索引一样大.
Hash索引有以下一些限制:
(1)由于索引仅包含hash code和记录指针,MysqL不能通过使用索引避免读取记录.但是拜访内存中的记录是非常迅速的,不会对性造成太大的影响.
(2)不能使用hash索引排序.
(3)Hash索引不支持键的部分匹配,因为是通过整个索引值来计算hash值的.
(4)Hash索引只支持等值比较,例如使用=,IN( )和<=>.对于WHERE price>100并不能加速查询.
2.1.3、空间(R-Tree)索引
MyISAM支持空间索引,主要用于地理空间数据类型,例如GEOMETRY.
2.1.4、全文(Full-text)索引
全文索引是MyISAM的一个特殊索引类型,主要用于全文检索.
MysqL学习
3、高性能的索引策略
3.1、聚簇索引(Clustered Indexes)
聚簇索引保证关键字的值相近的元组存储的物理位置也相同(所以字符串类型不宜建立聚簇索引,特别是随机字符串,会使得系统进行大量的移动操作),且一个表只能有一个聚簇索引.因为由存储引擎实现索引,并不是所有的引擎都支持聚簇索引.目前,只有solidDB和InnoDB支持.
聚簇索引的结构大致如下:
MysqL学习
注:叶子页面包含完整的元组,而内节点页面仅包含索引的列(索引的列为整型).一些DBMS允许用户指定聚簇索引,但是MysqL的存储引擎到目前为止都不支持.InnoDB对主键建立聚簇索引.如果你不指定主键,InnoDB会用一个具有唯一且非空值的索引来代替.如果不存在这样的索引,InnoDB会定义一个暗藏的主键,然后对其建立聚簇索引.一般来说,DBMS都会以聚簇索引的形式来存储实际的数据,它是其它二级索引的基础.MysqL学习
3.1.1、InnoDB和MyISAM的数据布局的比拟
为了更加理解聚簇索引和非聚簇索引,或者primary索引和second索引(MyISAM不支持聚簇索引),来比拟一下InnoDB和MyISAM的数据布局,对于如下表:MysqL学习
MysqL学习
CREATE TABLE layout_test (MysqL学习
col1 int NOT NULL,MysqL学习
col2 int NOT NULL,MysqL学习
PRIMARY KEY(col1),MysqL学习
KEY(col2)MysqL学习
);MysqL学习
假设主键的值位于1---10,000之间,且按随机顺序插入,然后用OPTIMIZE TABLE进行优化.col2随机赋予1---100之间的值,所以会存在许多重复的值.
(1) MyISAM的数据布局
其布局十分简单,MyISAM依照插入的顺序在磁盘上存储数据,如下:
MysqL学习
注:左边为行号(row number),从0开始.因为元组的大小固定,所以MyISAM可以很容易的从表的开始位置找到某一字节的位置.
据些建立的primary key的索引结构大致如下:
MysqL学习
注:MyISAM不支持聚簇索引,索引中每一个叶子节点仅仅包含行号(row number),且叶子节点依照col1的顺序存储.
来看看col2的索引结构:
实际上,在MyISAM中,primary key和其它索引没有什么区别.Primary key仅仅只是一个叫做PRIMARY的唯一,非空的索引罢了.
(2) InnoDB的数据布局
InnoDB按聚簇索引的形式存储数据,所以它的数据布局有着很大的不同.它存储表的结构大致如下:
注:聚簇索引中的每个叶子节点包括primary key的值,事务ID和回滚指针(rollback pointer)——用于事务和MVCC,和余下的列(如col2).
相对于MyISAM,二级索引与聚簇索引有很大的不同.InnoDB的二级索引的叶子包括primary key的值,而不是行指针(row pointers),这减小了移动数据或者数据页面分裂时维护二级索引的开销,因为InnoDB不需要更新索引的行指针.其结构大致如下:
聚簇索引和非聚簇索引表的对比:MysqL学习
MysqL学习
3.1.2、按primary key的顺序插入行(InnoDB)MysqL学习
如果你用InnoDB,而且不需要特殊的聚簇索引,一个好的做法就是使用代理主键(surrogate key)——独立于你的应用中的数据.最简单的做法就是使用一个AUTO_INCREMENT的列,这会保证记录依照顺序插入,而且能提高使用primary key进行连接的查询的性能.应该尽量避免随机的聚簇主键,例如,字符串主键就是一个不好的选择,它使得插入操作变得随机.MysqL学习
3.2、覆盖索引(Covering Indexes)
如果索引包含满足查询的所有数据,就称为覆盖索引.覆盖索引是一种非常强大的工具,能大大提高查询性能.只需要读取索引而不用读取数据有以下一些优点:
(1)索引项通常比记录要小,所以MysqL拜访更少的数据;
(2)索引都按值的大小顺序存储,相对于随机拜访记录,需要更少的I/O;
(3)大多数据引擎能更好的缓存索引.比如MyISAM只缓存索引.
(4)覆盖索引对于InnoDB表尤其有用,因为InnoDB使用聚集索引组织数据,如果二级索引中包含查询所需的数据,就不再需要在聚集索引中查找了.
覆盖索引不能是任何索引,只有B-TREE索引存储相应的值.而且不同的存储引擎实现覆盖索引的方式都不同,并不是所有存储引擎都支持覆盖索引(Memory和Falcon就不支持).
对于索引覆盖查询(index-covered query),使用EXPLAIN时,可以在Extra一列中看到“Using index”.例如,在sakila的inventory表中,有一个组合索引(store_id,film_id),对于只需要拜访这两列的查询,MysqL就可以使用索引,如下:
MysqL学习
MysqL> EXPLAIN SELECT store_id,film_id FROM sakila.inventoryGMysqL学习
*************************** 1. row ***************************MysqL学习
id: 1MysqL学习
select_type: SIMPLEMysqL学习
table: inventoryMysqL学习
type: indexMysqL学习
possible_keys: NULLMysqL学习
key: idx_store_id_film_idMysqL学习
key_len: 3MysqL学习
ref: NULLMysqL学习
rows: 5007MysqL学习
Extra: Using indexMysqL学习
1 row in set (0.17 sec)MysqL学习
在大多数引擎中,只有当查询语句所拜访的列是索引的一部分时,索引才会覆盖.但是,InnoDB不限于此,InnoDB的二级索引在叶子节点中存储了primary key的值.因此,sakila.actor表使用InnoDB,而且对于是last_name上有索引,索引能覆盖那些拜访actor_id的查询,如:
MysqL学习
MysqL> EXPLAIN SELECT actor_id,last_nameMysqL学习
-> FROM sakila.actor WHERE last_name = 'HOPPER'GMysqL学习
*************************** 1. row ***************************MysqL学习
id: 1MysqL学习
select_type: SIMPLEMysqL学习
table: actorMysqL学习
type: refMysqL学习
possible_keys: idx_actor_last_nameMysqL学习
key: idx_actor_last_nameMysqL学习
key_len: 137MysqL学习
ref: constMysqL学习
rows: 2MysqL学习
Extra: Using where; Using indexMysqL学习
3.3、利用索引进行排序
MysqL中,有两种方式生成有序结果集:一是使用filesort,二是按索引顺序扫描.利用索引进行排序操作是非常快的,而且可以利用同一索引同时进行查找和排序操作.当索引的顺序与ORDER BY中的列顺序相同且所有的列是同一方向(全部升序或者全部降序)时,可以使用索引来排序.如果查询是连接多个表,仅当ORDER BY中的所有列都是第一个表的列时才会使用索引.其它情况都会使用filesort.
MysqL学习
create table actor(MysqL学习
actor_id int unsigned NOT NULL AUTO_INCREMENT,MysqL学习
name varchar(16) NOT NULL DEFAULT '',MysqL学习
password varchar(16) NOT NULL DEFAULT '',MysqL学习
PRIMARY KEY(actor_id),MysqL学习
KEY (name)MysqL学习
) ENGINE=InnoDBMysqL学习
insert into actor(name,password) values('cat01','1234567');MysqL学习
insert into actor(name,password) values('cat02',password) values('ddddd',password) values('aaaaa','1234567');MysqL学习
MysqL学习
MysqL> explain select actor_id from actor order by actor_id GMysqL学习
*************************** 1. row ***************************MysqL学习
id: 1MysqL学习
select_type: SIMPLEMysqL学习
table: actorMysqL学习
type: indexMysqL学习
possible_keys: NULLMysqL学习
key: PRIMARYMysqL学习
key_len: 4MysqL学习
ref: NULLMysqL学习
rows: 4MysqL学习
Extra: Using indexMysqL学习
1 row in set (0.00 sec)MysqL学习
MysqL学习
MysqL> explain select actor_id from actor order by password GMysqL学习
*************************** 1. row ***************************MysqL学习
id: 1MysqL学习
select_type: SIMPLEMysqL学习
table: actorMysqL学习
type: ALLMysqL学习
possible_keys: NULLMysqL学习
key: NULLMysqL学习
key_len: NULLMysqL学习
ref: NULLMysqL学习
rows: 4MysqL学习
Extra: Using filesortMysqL学习
1 row in set (0.00 sec)MysqL学习
MysqL学习
MysqL> explain select actor_id from actor order by name GMysqL学习
*************************** 1. row ***************************MysqL学习
id: 1MysqL学习
select_type: SIMPLEMysqL学习
table: actorMysqL学习
type: indexMysqL学习
possible_keys: NULLMysqL学习
key: nameMysqL学习
key_len: 18MysqL学习
ref: NULLMysqL学习
rows: 4MysqL学习
Extra: Using indexMysqL学习
1 row in set (0.00 sec)MysqL学习
当MysqL不能使用索引进行排序时,就会利用自己的排序算法(快速排序算法)在内存(sort buffer)中对数据进行排序,如果内存装载不下,它会将磁盘上的数据进行分块,再对各个数据块进行排序,然后将各个块合并成有序的结果集(实际上就是外排序).对于filesort,MysqL有两种排序算法.
(1)两遍扫描算法(Two passes)
实现方式是先将须要排序的字段和可以直接定位到相关行数据的指针信息取出,然后在设定的内存(通过参数sort_buffer_size设定)中进行排序,完成排序之后再次通过行指针信息取出所需的Columns.
注:该算法是4.1之前采用的算法,它需要两次拜访数据,尤其是第二次读取操作会导致大量的随机I/O操作.另一方面,内存开销较小.
(3) 一次扫描算法(single pass)
该算法一次性将所需的Columns全部取出,在内存中排序后直接将结果输出.
注:从 MysqL 4.1 版本开始使用该算法.它减少了I/O的次数,效率较高,但是内存开销也较大.如果我们将并不需要的Columns也取出来,就会极大地浪费排序过程所需要的内存.在 MysqL 4.1 之后的版本中,可以通过设置 max_length_for_sort_data 参数来控制 MysqL 选择第一种排序算法还是第二种.当取出的所有大字段总大小大于 max_length_for_sort_data 的设置时,MysqL 就会选择使用第一种排序算法,反之,则会选择第二种.为了尽可能地提高排序性能,我们自然更希望使用第二种排序算法,所以在 Query 中仅仅取出需要的 Columns 是非常有必要的.
当对连接操作进行排序时,如果ORDER BY仅仅引用第一个表的列,MysqL对该表进行filesort操作,然后进行连接处理,此时,EXPLAIN输出“Using filesort”;否则,MysqL必须将查询的结果集生成一个临时表,在连接完成之后进行filesort操作,EXPLAIN输出“Using temporary;Using filesort”.MysqL学习
3.4、索引与加锁
索引对于InnoDB非常重要,因为它可以让查询锁更少的元组.这点十分重要,因为MysqL 5.0中,InnoDB直到事务提交时才会解锁.有两个方面的原因:首先,即使InnoDB行级锁的开销非常高效,内存开销也较小,但不管怎么样,还是存在开销.其次,对不需要的元组的加锁,会增加锁的开销,降低并发性.
InnoDB仅对需要拜访的元组加锁,而索引能够减少InnoDB拜访的元组数.但是,只有在存储引擎层过滤掉那些不需要的数据才能达到这种目的.一旦索引不允许InnoDB那样做(即达不到过滤的目的),MysqL服务器只能对InnoDB返回的数据进行WHERE操作,已经无法避免对那些元组加锁了:InnoDB已经锁住那些元组,服务器无法解锁了.
来看个例子:
MysqL学习
create table actor(MysqL学习
actor_id int unsigned NOT NULL AUTO_INCREMENT,'1234567');MysqL学习
insert into actor(name,'1234567');MysqL学习
insert into actor(name,'1234567');MysqL学习
SET AUTOCOMMIT=0;MysqL学习
BEGIN;MysqL学习
SELECT actor_id FROM actor WHERE actor_id < 4MysqL学习
AND actor_id <> 1 FOR UPDATE;MysqL学习
该查询仅仅返回2---3的数据,实际已经对1---3的数据加上排它锁了.InnoDB锁住元组1是因为MysqL的查询计划仅使用索引进行范围查询(而没有进行过滤操作,WHERE中第二个条件已经无法使用索引了):MysqL学习
MysqL学习
MysqL> EXPLAIN SELECT actor_id FROM test.actorMysqL学习
-> WHERE actor_id < 4 AND actor_id <> 1 FOR UPDATE GMysqL学习
*************************** 1. row ***************************MysqL学习
id: 1MysqL学习
select_type: SIMPLEMysqL学习
table: actorMysqL学习
type: indexMysqL学习
possible_keys: PRIMARYMysqL学习
key: PRIMARYMysqL学习
key_len: 4MysqL学习
ref: NULLMysqL学习
rows: 4MysqL学习
Extra: Using where; Using indexMysqL学习
1 row in set (0.00 sec)MysqL学习
MysqL学习
MysqL>MysqL学习
表明存储引擎从索引的起始处开始,获取所有的行,直到actor_id<4为假,服务器无法告诉InnoDB去掉元组1.
为了证明row 1已经被锁住,我们另外建一个连接,执行如下操作:
MysqL学习
SET AUTOCOMMIT=0;MysqL学习
BEGIN;MysqL学习
SELECT actor_id FROM actor WHERE actor_id = 1 FOR UPDATE;MysqL学习
该查询会被挂起,直到第一个连接的事务提交释放锁时,才会执行(这种行为对于基于语句的复制(statement-based replication)是需要的).
如上所示,当使用索引时,InnoDB会锁住它不需要的元组.更糟糕的是,如果查询不能使用索引,MysqL会进行全表扫描,并锁住每一个元组,不管是否真正需要.
分享:MysqL索引优化的技巧
深入理解MysqL的列索引和多列索引
了解 MysqL 主键与索引的联系与区别
MysqL性能优化之索引优化MysqL学习
小编PHP培训学院每天发布《MysqL学习深入理解MysqL索引与优化》等实战技能,PHP、MysqL、LINUX、APP、JS,CSS全面培养人才。
Mysql实例深入理解Mysql的四种隔离级别
《MysqL实例深入理解MysqL的四种隔离级别》要点:
本文介绍了MysqL实例深入理解MysqL的四种隔离级别,希望对您有用。如果有疑问,可以联系我们。
一、首先什么是事务?MysqL入门
事务是应用程序中一系列严密的操作,所有操作必须成功完成,否则在每个操作中所作的所有更改都会被撤消.也就是事务具有原子性,一个事务中的一系列的操作要么全部成功,要么一个都不做.MysqL入门
事务的结束有两种,当事务中的所以步骤全部成功执行时,事务提交.如果其中一个步骤失败,将发生回滚操作,撤消撤消之前到事务开始时的所以操作.
MysqL入门
二、事务的 ACIDMysqL入门
事务具有四个特征:原子性( Atomicity )、一致性( Consistency )、隔离性( Isolation )和持续性( Durability ).这四个特性简称为 ACID 特性.MysqL入门
1 、原子性.事务是数据库的逻辑工作单位,事务中包含的各操作要么都做,要么都不做MysqL入门
2 、一致性.事 务执行的结果必须是使数据库从一个一致性状态变到另一个一致性状态.因此当数据库只包含成功事务提交的结果时,就说数据库处于一致性状态.如果数据库系统 运行中发生故障,有些事务尚未完成就被迫中断,这些未完成事务对数据库所做的修改有一部分已写入物理数据库,这时数据库就处于一种不正确的状态,或者说是 不一致的状态.MysqL入门
3 、隔离性.一个事务的执行不能其它事务干扰.即一个事务内部的操作及使用的数据对其它并发事务是隔离的,并发执行的各个事务之间不能互相干扰.MysqL入门
4 、持续性.也称永久性,指一个事务一旦提交,它对数据库中的数据的改变就应该是永久性的.接下来的其它操作或故障不应该对其执行结果有任何影响.
MysqL入门
三、MysqL的四种隔离级别MysqL入门
sql标准定义了4类隔离级别,包括了一些具体规则,用来限定事务内外的哪些改变是可见的,哪些是不可见的.低级别的隔离级一般支持更高的并发处理,并拥有更低的系统开销.MysqL入门
Read Uncommitted(读取未提交内容)MysqL入门
在该隔离级别,所有事务都可以看到其他未提交事务的执行结果.本隔离级别很少用于实际应用,因为它的性能也不比其他级别好多少.读取未提交的数据,也被称之为脏读(Dirty Read).MysqL入门
Read Committed(读取提交内容)MysqL入门
这是大多数数据库系统的默认隔离级别(但不是MysqL默认的).它满足了隔离的简单定义:一个事务只能看见已经提交事务所做的改变.这种隔离级别 也支持所谓的不可重复读(Nonrepeatable Read),因为同一事务的其他实例在该实例处理其间可能会有新的commit,所以同一select可能返回不同结果.MysqL入门
Repeatable Read(可重读)MysqL入门
这是MysqL的默认事务隔离级别,它确保同一事务的多个实例在并发读取数据时,会看到同样的数据行.不过理论上,这会导致另一个棘手的问题:幻读 (Phantom Read).简单的说,幻读指当用户读取某一范围的数据行时,另一个事务又在该范围内插入了新行,当用户再读取该范围的数据行时,会发现有新的“幻影” 行.InnoDB和Falcon存储引擎通过多版本并发控制(MVCC,Multiversion Concurrency Control)机制解决了该问题.MysqL入门
Serializable(可串行化)MysqL入门
这是最高的隔离级别,它通过强制事务排序,使之不可能相互冲突,从而解决幻读问题.简言之,它是在每个读的数据行上加上共享锁.在这个级别,可能导致大量的超时现象和锁竞争.MysqL入门
这四种隔离级别采取不同的锁类型来实现,若读取的是同一个数据的话,就容易发生问题.例如:MysqL入门
脏读(Drity Read):某个事务已更新一份数据,另一个事务在此时读取了同一份数据,由于某些原因,前一个RollBack了操作,则后一个事务所读取的数据就会是不正确的.MysqL入门
不可重复读(Non-repeatable read):在一个事务的两次查询之中数据不一致,这可能是两次查询过程中间插入了一个事务更新的原有的数据.MysqL入门
幻读(Phantom Read):在一个事务的两次查询中数据笔数不一致,例如有一个事务查询了几列(Row)数据,而另一个事务却在此时插入了新的几列数据,先前的事务在接下来的查询中,就会发现有几列数据是它先前所没有的.
在MysqL中,实现了这四种隔离级别,分别有可能产生问题如下所示:MysqL入门
MysqL入门
四、测试MysqL的隔离级别MysqL入门
下面,将利用MysqL的客户端程序,我们分别来测试一下这几种隔离级别.MysqL入门
测试数据库为demo,表为test;表结构:MysqL入门
两个命令行客户端分别为A,B;不断改变A的隔离级别,在B端修改数据.MysqL入门
(一)、将A的隔离级别设置为read uncommitted(未提交读)MysqL入门
A:启动事务,此时数据为初始状态MysqL入门
B:启动事务,更新数据,但不提交MysqL入门
A:再次读取数据,发现数据已经被修改了,这就是所谓的“脏读”MysqL入门
B:回滚事务MysqL入门
A:再次读数据,发现数据变回初始状态MysqL入门
经过上面的实验可以得出结论,事务B更新了一条记录,但是没有提交,此时事务A可以查询出未提交记录.造成脏读现象.未提交读是最低的隔离级别.MysqL入门
(二)、将客户端A的事务隔离级别设置为read committed(已提交读)MysqL入门
A:启动事务,此时数据为初始状态MysqL入门
B:启动事务,但不提交MysqL入门
A:再次读数据,发现数据未被修改MysqL入门
B:提交事务MysqL入门
A:再次读取数据,发现数据已发生变化,说明B提交的修改被事务中的A读到了,这就是所谓的“不可重复读”MysqL入门
经过上面的实验可以得出结论,已提交读隔离级别解决了脏读的问题,但是出现了不可重复读的问题,即事务A在两次查询的数据不一致,因为在两次查询之间事务B更新了一条数据.已提交读只允许读取已提交的记录,但不要求可重复读.MysqL入门
(三)、将A的隔离级别设置为repeatable read(可重复读)MysqL入门
A:启动事务,此时数据为初始状态MysqL入门
B:启动事务,但不提交MysqL入门
A:再次读取数据,发现数据未被修改MysqL入门
B:提交事务MysqL入门
A:再次读取数据,发现数据依然未发生变化,这说明这次可以重复读了MysqL入门
B:插入一条新的数据,并提交MysqL入门
A:再次读取数据,虽然可以重复读了,但是却发现读的不是最新数据,这就是所谓的“幻读”MysqL入门
A:提交本次事务,再次读取数据,发现读取正常了MysqL入门
由以上的实验可以得出结论,可重复读隔离级别只允许读取已提交记录,而且在一个事务两次读取一个记录期间,其他事务部的更新该记录.但该事务不要求与其他事务可串行化.例如,当一个事务可以找到由一个已提交事务更新的记录,但是可能产生幻读问题(注意是可能,因为数据库对隔离级别的实现有所差别).像以上的实验,就没有出现数据幻读的问题.MysqL入门
(四)、将A的隔离级别设置为可串行化(Serializable)MysqL入门
A:启动事务,此时数据为初始状态MysqL入门
B:发现B此时进入了等待状态,原因是因为A的事务尚未提交,只能等待(此时,B可能会发生等待超时)MysqL入门
A:提交事务MysqL入门
B:发现插入成功MysqL入门
serializable完全锁定字段,若一个事务来查询同一份数据就必须等待,直到前一个事务完成并解除锁定为止.是完整的隔离级别,会锁定对应的数据表格,因而会有效率的问题.MysqL入门
总结MysqL入门
以上就是这篇文章全部内容了,希望本文的内容对大家的学习或者能有所帮助,如果有疑问大家可以留言交流.MysqL入门
Mysql应用mysqli预处理编译的深入理解
《MysqL应用MysqLi预处理编译的深入理解》要点:
本文介绍了MysqL应用MysqLi预处理编译的深入理解,希望对您有用。如果有疑问,可以联系我们。
记得以前PHP点点通也写过MysqLi的预处理的PHP教程,那时候只是看书乱写的,没懂原理,数月过后,突然明白了很多:
想想看.假如我们要插入很多1000个用户,你怎么做,for循环?还是MysqLi处理多条sql? no!这些处理很慢的,PHP里面有很多操作MysqL数据库的函数,无非是把sql语句传递给MysqL数据库,真正处理sql语句的是MysqL,MysqL数据库是要编译sql语句进行执行的,上面这两种操作会对相同的sql语句进行多次编译,有这需要吗?程序员总是很聪明的,于是有了MysqLi预处理技术!MysqLi还能防止sql注入攻击!
看看下面这个预编译代码:
MysqL入门
<?PHP
//创建连接
$MysqLi=new MysqLi("localhost","root","","test");
//设置MysqLi编码
MysqLi_query($MysqLi,"SET NAMES utf8");
//检查连接是否被创建
if (MysqLi_connect_errno()) {
printf("Connect Failed:".MysqLi_connect_error());
exit();
}
//创建准备语句
$stmt = $MysqLi->prepare("select id,username from `user` where `id` > ?");
/*************************************************************/
$id=5;
//绑定参数
$stmt->bind_param("i",$id);
//绑定结果集
$stmt->bind_result($id,$username);
//执行查询
$stmt->execute();
//显示绑定结果的变量
while($stmt->fetch()){
echo "第".$id."个用户: ".$username."<br />";
}
/**************************************************************/
/*www.PHPddt.com为你提示:上面*之间的内容可以重复执行类似功能,不必要再次编译了*/
//释放结果
$stmt->free_result();
//关闭编译语句
$stmt->close();
//关闭数据库的链接
$MysqLi->close();
?>