想象这样一个场景,在设计一张用户表时,每人的身份证号是唯一的,需要搜索。但由于身份证号字段较大,不好将其作为主键。在业务代码已经保证插入身份证唯一的情况下,可以选择建立唯一索引和普通普通索引,这时该如何选择呢?接下来,将从查询和更新的执行过程进行分析。

查询过程

假设 k 是表 t 上的索引,在搜索 select id from t where k=5 时,会先从 k 这棵 B+ 的树根开始,按层搜索叶子节点,找到 k=5 的数据页,然后在数据页内容进行二分法定位。

对于普通索引,找到 k=5 的记录后,会继续向下查找一个,直到碰到第一个不是 5 的记录结束。

对于唯一索引,由于取值唯一,找到后直接停止。

由于 InnoDB 是按照数据页为单位(数据页默认 16 KB)进行读写的,在读取一条数据时,会将整个数据页整体读到内存。 在读入内存的数据页中,如果包含 k=5 的记录,在查询的情况下,唯一索引比普通索引多了一次查找和判断的过程,可以忽略。

如果 k=5 是当前数据页的最后一条,就需要在读取下一个数据页。但这发生的概率较低,也可以忽略。

所以总得来说,普通索引和唯一索引在查询的过程中差异不大。

change buffer

在分析唯一索引和普通索引的影响前,先来认识一下 change buffer 这个结构。

什么是 change buffer ?

在执行更新操作时,如果要更新的数据页在内存中就直接更新,否则的话,在不影响数据一致性的前提下,InnoDB 会将更新操作缓存在 change buffer 中,从而省去了从磁盘读取数据页的过程。在下次查询操作读取到恰好需要更新的数据页时,会将 change buffer 的更新语句执行,写入数据页。将操作应用到硬盘的过程叫,merge. 后台线程会定期 merge 或 数据库正常关闭时,也会进行 merge 操作。

change buffer 实际上是可以持久化到硬盘中的数据,也就是说在内存和硬盘上都 change buffer 的存在。change buffer 之前交 insert buffer,开始只对 insert buffer 有优化,后来加上了对 delete 和 update 的支持。

可以看到,先将更新操作记录在 change buffer,减少了将磁盘数据页读取到内存的过程,语句的执行速度会有很明显的提升。同时,将数据读入内存,会占用 buffer pool 内存,所以减少读操作,还提高了内存使用率。

Buffer Pool 是内存中的一个区域,InnoDB 在访问表和索引数据时会在其中进行缓存。允许在内存中直接更新经常使用的数据,来加快处理速度。在一些专用的服务器上,会将 80% 的物理内存分为 buffer pool.

可以通过 innodb_change_buffer_max_size 来设置 change buffer 占用 buffer pool 的大小。

change buffer 应用场景?

如上面提到,change buffer 预先保存了更新记录,减少了读取数据页的过程,从而提高性能。也就是说如果 change buffer 中针对不同的数据页如果包含的更新记录越多,其实收益也就越大。

因此对于写多读少的业务(更新完立即查询)change buffer 发挥的作用也就越大。如常见的账单类,日志类等系统。

如果业务是更新完立即查询,虽然可以将更新记录放在 change buffer 中,但由于之后要马上查询数据页,所以会立即触发 merge 过程。这样随机访问 IO 次数并不会减少,反而增加了 change buffer 的维护代价,起到反效果。

更新过程

对于唯一索引来说,所有的更新操作都需要判断是否违反唯一性约束。所以必须把所需要的数据页读入内存,然后直接更新就可以,不需要使用 change buffer. 所以 change buffer 只对普通索引有用。

具体分析下,对于一张表插入一个新记录:

如果新记录要更新的数据页在内存中:

对于唯一索引,找到合适的位置,判断有没有冲突,插入值,语句结束。

对于普通索引:找到位置,插入值,语句结束。

所以数据页在内存时,唯一和普通索引就差一个判断的过程。可以忽略。

如果新记录要更新的数据页不在内存中:

对于唯一索引,将数据页读入内存,判断冲突,插入,语句结束。

对于普通索引,将语句记录在 change buffer 中,语句结束。

由于从磁盘到内存涉及随机 IO 访问,是数据库成本最高的操作之一。普通索引比唯一索引减少的读入操作,可以有很好的性能提升。

唯一或普通索引的选择

通过在查询和更新方面,两者的比较。我们知道,在查询过程中,除了极特殊情况,其实两者的差异并不大。

主要的差异是在更新过程中,要更新的数据页并不在内容中的情况。这时唯一索引,由于需要唯一性检查,不能利用 change buffer. 多了从磁盘到内容读取数据的过程,其中涉及随机 IO 的访问,相对来说效率就低了。

所以如果业务需要更新不错的性能,这时可以选用普通索引。当然一切都是建立在能保证数据准确性的前提下。

当如果更新后来紧接着查询操作,可以考虑关掉 change buffer. 其他的情况,change buffer 都能有很好的提升。

特别针对机械硬盘,change buffer 效果很显著。

redo log 和 change buffer 的比较

InnoDB 中 redo log 的出现使其具有了 crash-safe 的能力,同时还提高了效率,通过 WAL 先写日志,再写磁盘。

而 change buffer 是节省了从磁盘读入数据页到内存的随机IO过程。

下面通过一条插入语句来分析下两者间的关系:

mysql> insert into t(id,k) values(id1,k1),(id2,k2);

假设 k 为普通索引,k1 所插入的数据页在内存中, k2 不在。

执行插入操作时,主要涉及了图中这四部分的内容:

InnoDB buffer pool:内存区域

redo log:日志

system table space(ibdata1):系统表空间

data(t.idb): 数据表空间

innodb_file_per_table 开启时,表被创建在独立的表空间下,否则的话被创建在系统的表空间下。

执行过程如下:

k1 所在的 page1 在内存中,直接更新内存

k2 所在的 page2 不在内存中,记录在 change buffer.

将 k1 和 k2 的操作记录在 redo log.

提交事务。

可以看到这条更新语句(包括插入,删除,更新操作)执行成本很低,两次写入内存,1次顺序写入磁盘。虚线的操作,是后台操作,不影响响应时间。

再来看一条查询语句:

select * from t where k in (k1, k2)

假设读语句发生在更新语句不久,内存数据还在,此时读操作就和系统表空间和 redo log 无关。

执行过程:

读取 k1 所在的 page1,在内存中,直接返回。注意,并没有读磁盘上的数据,而且磁盘上的数据还有可能是之前的版本的。

读取 k2 所在的 page2,这时需要将 page2 从磁盘加载到内存,并应用 change buffer 的内容,然后返回正确的结果。从这里也能看出,change buffer 不适用于更新完立马去读的情况。

从更新的过程来看, redo log 将随机写磁盘的 IO 转换成了顺序写,而 change buffer 则是节省了随机读磁盘的 IO 消耗。

参考

mysql唯一索引弊端_MySQL 关于唯一索引和普通索引的抉择相关推荐

  1. mysql 唯一索引和复合索引 区别_MySQL复合唯一索引分析

    MySQL复合唯一索引分析 关于复合唯一索引(unique key 或 unique index),网上搜索不少人说:"这种索引起到的关键作用是约束,查询时性能上没有得到提高或者查询时根本没 ...

  2. mysql 配置执行计划_MySQL深入学习(二)--配置、索引、执行计划

    MySQL 一.MySQL 5.7 初始化配置 1.初始化数据并配置 # 1.初始化数据 /usr/local/mysql/bin/mysqld --initialize-insecure --use ...

  3. mysql千万级数据索引查询_mysql千万级数据量根据索引优化查询速度

    (一)索引的作用 索引通俗来讲就相当于书的目录,当我们根据条件查询的时候,没有索引,便需要全表扫描,数据量少还可以,一旦数据量超过百万甚至千万,一条查询sql执行往往需要几十秒甚至更多,5秒以上就已经 ...

  4. mysql 子查询分页_MySQL子查询,正则表达式,索引,分页,三大范式

    子查询 可以将子查询放在许多的 SQL 子句中,包括: • WHERE 子句• HAVING 子句 • FROM 子句 使用子查询的原则 • 子查询放在圆括号中. • 将子查询放在比较条件的右边. • ...

  5. mysql 索引触发_Mysql基本查询、视图、索引、触发器

    基本查询 1.修改 String sql="update smbms_user set userCode=?,userName=? where id=?"; 2.删除用户 Stri ...

  6. mysql百万数据建索引时间_mysql百万的数据快速创建索引

    测试数据本机一张表users有100百万条记录.在创建此表前没有未相应字段添加索引,所以此时需要为表添加索引.但是因为数据量大的原因,索引添加可能不成功,想了很多办法,终于挖坑成功. 开始准备工作, ...

  7. mysql删除索引语句_MySQL:使用SQL语句删除所有索引

    删除所有索引 可利用ALTER TABLE或DROP INDEX语句来删除索引.这里使用ALTER TABLE,首先查询所有索引,然后拼接成删除语句,复制执行即可 #拼接删除索引的语法 SELECT ...

  8. mysql外部关联视图_MySQL数据库 : 自关联,视图,事物,索引

    自关联查询(自身id关联自身id(主键),查询的时候可以逻辑分为两个表,然后分别起一个别名来区分) select * from areas as city inner join areas as pr ...

  9. where 1=1会影响索引吗_MySQL之InnoDB存储引擎:索引的使用

    索引一直是MySQL的重点内容,这里主要介绍下InnoDB引擎下索引的使用 abstract.png 查找 为了后续行文方便,这里先给出一张表的SQL创建语句.可以看到在建立该表的同时还建立了一个联合 ...

最新文章

  1. JavaScript 渐变效果
  2. 【springboot】之自动配置原理
  3. python-pandas 高级功能(通过学习kaggle案例总结)
  4. 保险中介3.0时代:数字化中介在场景中打造核心竞争力
  5. android系统同时使用wifi和4g上网
  6. Altium designer—STM32F103ZET6最小系统原理图
  7. jdy40程序C语言,C语言习题集
  8. 2022年成考(专升本)考试政治练习题及答案
  9. 内存频率,CPU频率,主板频率之间的制约
  10. ZB级的大数据探索与应用实践【附PPT】
  11. python读取微博文本数据,对微博文本进行分句(分句主要以特定的标点符号为主)。...
  12. 开源软件 AnimeGANv2:一键生成宫崎骏动漫风格照
  13. 经典的期货量化交易策略大全
  14. [Unity3D]Unity3D 游戏开发之碰撞检测
  15. Java 将txt文本文档转换为excel
  16. Ubuntu安装QQ、VM、Chrom软件
  17. Unix timestamp时间 与 普通时间 转换方法
  18. vue3中组件给后代组件传值,provide和inject的使用
  19. 童年记忆中的各种水果
  20. 《Unity游戏优化》笔记(4)[21/02/05_周五][P29_46]

热门文章

  1. python 图片转文字错误_python3把base64字符串写成图片文件出错
  2. go conn 读取byte数组后是否要_【技术推荐】正向角度看Go逆向
  3. stl优先队列定义可以吗_C ++ STL | 用户定义的优先级队列比较器
  4. Java DataInputStream readUnsignedByte()方法(带示例)
  5. 计算机应用基础专2020春,计算机应用基础(专)(专,2020春)(20200831130023).pdf
  6. python爬取天气预报源代码_python抓取天气并分析 实例源码
  7. pygame render怎么显示中文_PyGame开发游戏(2D)02.基础图元
  8. express 项目生成器_用于项目的Express模板生成器(2)| 应用程序结构研究
  9. python中acosh_带有Python示例的math.acosh()方法
  10. Java面试详解(2020版):500+ 面试题和核心知识点详解