MySQL 普通索引和唯一索引的区别详解

1 概念区分

  • 普通索引和唯一索引

普通索引可重复,唯一索引和主键一样不能重复。 唯一索引可作为数据的一个合法验证手段,例如学生表的身份证号码字段,我们人为规定该字段不得重复,那么就使用唯一索引。(一般设置学号字段为主键)

  • 主键和唯一索引

主键保证数据库里面的每一行都是唯一的,比如身份证,学号等,在表中要求唯一,不重复。唯一索引的作用跟主键的作用一样。 不同的是,在一张表里面只能有一个主键,主键不能为空,唯一索引可以有多个,唯一索引可以有一条记录为空,即保证跟别人不一样就行。 比如学生表,在学校里面一般用学号做主键,身份证则弄成唯一索引;而到了教育局,他们就把身份证号弄成主键,学号换成了唯一索引。 选谁做表的主键,要看实际应用,主键不能为空。

2 案例引入

某居民系统,每人有唯一身份证号。如果系统需要按身份证号查姓名,就会执行类似如下SQL:

select name from CUser where id_card = 'ooxx';

然后你肯定会在id_card字段建索引。但id_card字段较大,不推荐将其做主键。于是现有俩选择:

  1. 给id_card字段创建唯一索引
  2. 创建一个普通索引

假定业务代码已保证不会写入重复的身份证号,这两个选择逻辑上都正确。但从性能角度考虑,唯一索引还是普通索引呢?

再看如下案例:假设字段 k 上的值都不重复。

  • InnoDB的索引组织结构:

接下来分析性能。

3 查询性能

select id from T where k=4

通过B+树从树根开始层序遍历到叶节点,可认为数据页内部是通过二分法搜索。

  • 普通索引,查找到满足条件的第一个记录(4,400)后,需查找下个记录,直到碰到第一个不满足k=4的记录
  • 唯一索引,由于索引具备唯一性,查找到第一个满足条件的记录后,就会停止检索

看起来性能差距很微小。

InnoDB数据按数据页单位读写。即读一条记录时,并非将该一个记录从磁盘读出,而以页为单位,将其整体读入内存。

因此普通索引,要多做一次“查找和判断下一条记录”的操作,也就一次指针寻找和一次计算。 如果k=4记录恰为该数据页最后一个记录,那么要取下个记录,还得读取下个数据页,操作稍微复杂。 对整型字段,一个数据页可存近千key,因此这种情况概率其实也很低。因此计算平均性能差异时,可认为该操作成本对现在CPU开销忽略不计。

我们知道 MySQL 有 change buffer。

4 更新性能

现在来看往表中插入一个新记录(4,400),InnoDB会做什么?

需要区分该记录要更新的目标页是否在内存:

4.1 在内存

  • 唯一索引

找到3和5之间位置,判断到没有冲突,插入值,语句执行结束。

  • 普通索引

找到3和5之间位置,插入值,语句执行结束。

普通索引和唯一索引对更新语句性能影响的差别,只是一个判断,耗费微小CPU时间。

4.2 不在内存

  • 唯一索引

需将数据页读入内存,判断到没有冲突,插入值,语句执行结束。

  • 普通索引

将更新记录在change buffer,语句执行结束。

将数据从磁盘读入内存涉及随机IO访问,是数据库里面成本最高操作之一。而change buffer减少随机磁盘访问,所以更新性能提升明显。

5 实践中的索引选择

普通索引和唯一索引究竟如何抉择?这两类索引在查询性能上没差别,主要考虑对更新性能影响。所以,推荐尽量选择普通索引。

如果所有更新后面,都紧跟对该记录的查询,那么该关闭change buffer。 而在其他情况下,change buffer都能提升更新性能。 普通索引和change buffer的配合使用,对于数据量大的表的更新优化还是很明显的。

在使用机械硬盘时,change buffer机制的收效非常显著。 所以,当你有一个类似“历史数据”的库,并且出于成本考虑用机械硬盘时,应该关注这些表里的索引,尽量使用普通索引,把change buffer 开大,确保“历史数据”表的数据写速度。

6 change buffer 和 redo log

WAL 提升性能的核心机制,也是尽量减少随机读写,这两个概念易混淆。 所以,这里我把它们放到了同一个流程里来说明区分。

6.1 插入流程

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

假设当前k索引树的状态,查找到位置后,k1所在数据页在内存(InnoDB buffer pool),k2数据页不在内存。

  • 带change buffer的更新流程图,图中两个箭头都是后台操作,不影响更新响应。

该更新做了如下操作:

  1. Page1在内存,直接更新内存
  2. Page2不在内存,就在change buffer区,缓存下“往Page2插一行记录”的信息
  3. 将前两个动作记入redo log

之后事务完成。执行该更新语句成本很低,只写两处内存,然后写一处磁盘(前两次操作合在一起写了一次磁盘),还是顺序写。

6.2 怎么处理之后的读请求?

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

读语句紧随更新语句,内存中的数据都还在,此时这俩读操作就与系统表空间和 redo log 无关。所以在图中就没画这俩。

  • 带change buffer的读过程

读Page1时,直接从内存返回。 WAL之后如果读数据,是不是一定要读盘,是不是一定要从redo log里面把数据更新以后才可以返回?其实不用。 看上图状态,虽然磁盘上还是之前数据,但这里直接从内存返回结果,结果正确。

要读Page2时,需把Page2从磁盘读入内存,然后应用change buffer里面的操作日志,生成一个正确版本并返回结果。 可见直到需读Page2时,该数据页才被读入内存。

所以,要简单对比这俩机制对更新性能影响

  • redo log 主要节省随机写磁盘的IO消耗(转成顺序写)
  • change buffer主要节省随机读磁盘的IO消耗

7 总结

由于唯一索引用不了change buffer的优化机制,因此如果业务可以接受,从性能角度,推荐优先考虑非唯一索引。

7.1 关于到底是否使用唯一索引

主要纠结在“业务可能无法确保”。本文前提是“业务代码已经保证不会写入重复数据”下,讨论性能问题。

如果业务不能保证,或者业务就是要求数据库来做约束,那么没得选,必须创建唯一索引。这种情况下,本文意义在于,如果碰上大量插入数据慢、内存命中率低时,多提供一个排查思路。
然后,在一些“归档库”的场景,可考虑使用唯一索引的。比如,线上数据只需保留半年,然后历史数据保存在归档库。此时,归档数据已是确保没有唯一键冲突。要提高归档效率,可考虑把表的唯一索引改普通索引。

7.2 如果某次写入使用change buffer,之后主机异常重启,是否会丢失change buffer的数据?

不会丢失。 虽然是只更新内存,但在事务提交时,我们把change buffer的操作也记录到redo log,所以崩溃恢复时,change buffer也能找回。

7.3 merge的过程是否会把数据直接写回磁盘?

merge执行流程

  1. 从磁盘读入数据页到内存(老版本数据页)
  2. 从change buffer找出该数据页的change buffer 记录(可能有多个),依次应用,得到新版数据页
  3. 写redo log

该redo log包含数据的变更和change buffer的变更

至此merge过程结束。 这时,数据页和内存中change buffer对应磁盘位置都尚未修改,是脏页,之后各自刷回自己物理数据,就是另外一过程。

问题思考

在构造第一个例子的过程,通过session A的配合,让session B删除数据后又重新插入一遍数据,然后就发现explain结果中,rows字段从10001变成37000多。 而如果没有session A的配合,只是单独执行delete from t 、call idata()、explain这三句话,会看到rows字段其实还是10000左右。这是什么原因呢?

如果没有复现,检查

  • 隔离级别是不是RR(Repeatable Read,可重复读)
  • 创建的表t是不是InnoDB引擎

为什么经过这个操作序列,explain的结果就不对了? delete 语句删掉了所有的数据,然后再通过call idata()插入了10万行数据,看上去是覆盖了原来10万行。 但是,session A开启了事务并没有提交,所以之前插入的10万行数据是不能删除的。这样,之前的数据每行数据都有两个版本,旧版本是delete之前数据,新版本是标记deleted的数据。 这样,索引a上的数据其实有两份。

然后你会说,不对啊,主键上的数据也不能删,那没有使用force index的语句,使用explain命令看到的扫描行数为什么还是100000左右?(潜台词,如果这个也翻倍,也许优化器还会认为选字段a作为索引更合适) 是的,不过这个是主键,主键是直接按照表的行数来估计的。而表的行数,优化器直接用的是show table status的值。 大家的机器如果IO能力比较差的话,做这个验证的时候,可以把innodb_flush_log_at_trx_commit sync_binlog 都设置成0。

以上就是MySQL 普通索引和唯一索引的区别详解的详细内容,更多关于MySQL 普通索引和唯一索引的资料请关注我们其它相关文章!

(0)

相关推荐

  • MySQL普通索引和唯一索引的深入讲解

    场景 1.维护一个市民系统,有一个字段为身份证号 2.业务代码能保证不会写入两个重复的身份证号(如果业务无法保证,可以依赖数据库的唯一索引来进行约束) 3.常用SQL查询语句:SELECT name FROM CUser WHERE id_card = 'XXX' 4.建立索引 身份证号比较大,不建议设置为主键 从性能角度出发,选择普通索引还是唯一索引? 假设字段k上的值都不重复 查询过程 1.查询语句:SELECT id FROM T WHERE k=5 2.查询过程 通过B+树从树根开始,按

  • MySQL唯一索引和普通索引选哪个?

    想象这样一个场景,在设计一张用户表时,每人的身份证号是唯一的,需要搜索.但由于身份证号字段较大,不好将其作为主键.在业务代码已经保证插入身份证唯一的情况下,可以选择建立唯一索引和普通索引,这时该如何选择呢?接下来,将从查询和更新的执行过程进行分析. 查询过程 假设 k 是表 t 上的索引,在搜索 select id from t where k=5 时,会先从 k 这棵 B+ 的树根开始,按层搜索叶子节点,找到 k=5 的数据页,然后在数据页内容进行二分法定位. 对于普通索引,找到 k=5 的记

  • Mysql普通索引与唯一索引的选择详析

    假设一个用户管理系统,每个人注册都有一个唯一的手机号,而且业务代码已经保证了不会写入两个重复的手机号.如果用户管理系统需要按照手机号查姓名,就会执行类似这样的 SQL 语句: select name from users where mobile = '15202124529'; 通常会考虑在 mobile 字段上建索引.由于手机号字段相对较大,通常基本不会把手机号当做主键,那么现在就有两个选择: 1.  给 id_card 字段创建唯一索引 2.  创建一个普通索引 如果业务代码已经保证了不会

  • mysql下普通索引和唯一索引的效率对比

    今天在我的虚拟机中布置了环境,测试抓图如下: 抓的这几个都是第一次执行的,刷了几次后,取平均值,效率大致相同,而且如果在一个列上同时建唯一索引和普通索引的话,mysql会自动选择唯一索引. 谷歌一下: 唯一索引和普通索引使用的结构都是B-tree,执行时间复杂度都是O(log n). 补充下概念: 1.普通索引 普通索引(由关键字KEY或INDEX定义的索引)的唯一任务是加快对数据的访问速度.因此,应该只为那些最经常出现在查询条件(WHEREcolumn=)或排序条件(ORDERBYcolumn

  • mysql int(3)与int(11)的区别详解

    mysql int(3)与int(11)的区别 总结,int(M) zerofill,加上zerofill后M才表现出有点点效果,比如 int(3) zerofill,你插入到数据库里的是10,则实际插入为010,也就是在前面补充加了一个0.如果int(3)和int(10)不加zerofill,则它们没有什么区别.M不是用来限制int个数的.int(M)的最大值和最小值与undesigned有关,最下面那副图有说明. mysql> create table t (t int(3) zerofil

  • MySQL存储引擎中MyISAM和InnoDB区别详解

    InnoDB和MyISAM是许多人在使用MySQL时最常用的两个表类型,这两个表类型各有优劣,视具体应用而定.基本的差别为:MyISAM类型不支持事务处理等高级处理,而InnoDB类型支持.MyISAM类型的表强调的是性能,其执行数度比InnoDB类型更快,但是不提供事务支持,而InnoDB提供事务支持以及外部键等高级数据库功能. 以下是一些细节和具体实现的差别: ◆1.InnoDB不支持FULLTEXT类型的索引. ◆2.InnoDB 中不保存表的具体行数,也就是说,执行select coun

  • Mysql内连接与外连接的区别详解

    目录 前言 内连接inner join 外连接outer join 左(外)连接 left join 右(外)连接 right join 总结 前言 我在写sql查询的时候,用的最多的就是where条件查询,这种查询也叫内连查询inner join,当然还有外连查询outer join,左外连接,右外连接查询,常用在多对多关系中,那他们区别和联系是什么呢? 内连接inner join 内连接最常用定义: 连接结果仅包含符合连接条件的行组合起来作为结果集,参与连接的两个表都应该符合连接条件使用关键

  • MySQL 普通索引和唯一索引的区别详解

    1 概念区分 普通索引和唯一索引 普通索引可重复,唯一索引和主键一样不能重复. 唯一索引可作为数据的一个合法验证手段,例如学生表的身份证号码字段,我们人为规定该字段不得重复,那么就使用唯一索引.(一般设置学号字段为主键) 主键和唯一索引 主键保证数据库里面的每一行都是唯一的,比如身份证,学号等,在表中要求唯一,不重复.唯一索引的作用跟主键的作用一样. 不同的是,在一张表里面只能有一个主键,主键不能为空,唯一索引可以有多个,唯一索引可以有一条记录为空,即保证跟别人不一样就行. 比如学生表,在学校里

  • MySQL中索引与视图的用法与区别详解

    前言 本文主要给大家介绍了关于MySQL中索引与视图的使用与区别的相关内容,分享出来供大家参考学习,下面话不多说了,来一起看看详细的介绍吧. 索引 一.概述 所有的Mysql列类型都可以被索引. mysql支持BTREE索引.HASH索引.前缀索引.全文本索引(FULLTEXT)[只有MyISAM引擎支持,且仅限于char,varchar,text列].空间列索引[只有MyISAM引擎支持,且索引的字段必须非空],但不支持函数索引. MyISAM和InnoDB存储引擎的表默认创建BTREE索引,

  • MySQL的视图和索引用法与区别详解

    MySQL的视图 简单来说MySQL的视图就是对SELECT 命令的定义的一个快捷键,我们查询时会用到非常复杂的SELECT语句,而这个语句我们以后还会经常用到,我们可以经这个语句生产视图.视图是一个虚拟的表,它不存储数据,所用的数据都在真实的表中. 这样做的好处有: 1.防止有未经允许的租户访问到敏感数据 2.将多个物理表抽象成一个逻辑表 3.结果容易理解 4.获得数据更容易,很多人对SQL语句不太了解,我们可以通过创建视图的形式方便用户使用. 5.显示数据更容易. 6.维护程序更方便.调试视

  • mysql error 1071: 创建唯一索引时字段长度限制的问题

    目录 一.先描述一下问题吧 二.显而易见 三.问题和解决方案分析 一.先描述一下问题吧 如下创建表时候报错了 CREATE TABLE `xxx` (   `id` bigint(20) NOT NULL AUTO_INCREMENT,   `sys_code` varchar(255) DEFAULT NULL COMMENT '系统编码',   `module_name` varchar(1000) DEFAULT NULL COMMENT '模块名',   `call_num` bigin

随机推荐