转载:数据库索引原理,及MySQL索引类型

原始链接:https://blog.csdn.net/weixin_42181824/article/details/82261988

 

 

数据库索引原理,及MySQL索引类型

 

本文链接:https://blog.csdn.net/weixin_42181824/article/details/82261988

版权

MySQL索引类型一览 让MySQL高效运行起来

 

本文介绍了七种MySQL索引类型。在数据库表中,对字段建立索引可以大大提高查询速度。通过善用这些索引,可以令MySQL的查询和运行更加高效。

索引是快速搜索的关键。MySQL索引的建立对于MySQL的高效运行是很重要的。下面介绍几种常见的MySQL索引类型。

在数据库表中,对字段建立索引可以大大提高查询速度。假如我们创建了一个 mytable表:

 

    1. CREATE TABLE mytable(  
    2.  
    3. ID INT NOT NULL,   
    4.  
    5. username VARCHAR(16) NOT NULL 
    6.  
    7. );  
    8.  

 

我们随机向里面插入了10000条记录,其中有一条:5555, admin

在查找username="admin"的记录 SELECT * FROM mytable WHERE username='admin';时,如果在username上已经建立了索引,MySQL无须任何扫描,即准确可找到该记录。相反,MySQL会扫描所有记录,即要查询10000条记录。

索引分单列索引和组合索引。单列索引,即一个索引只包含单个列,一个表可以有多个单列索引,但这不是组合索引。组合索引,即一个索包含多个列。

MySQL索引类型包括:

1)普通索引

这是最基本的索引,它没有任何限制。它有以下几种创建方式:

创建索引

 

  1. CREATE INDEX indexName ON mytable(username(length)); 

 

如果是CHARVARCHAR类型,length可以小于字段实际长度;如果是BLOBTEXT类型,必须指定 length,下同。

修改表结构

 

  1. ALTER mytable ADD INDEX [indexName] ON (username(length)) 

 

创建表的时候直接指定

 

    1. CREATE TABLE mytable(  
    2.  
    3. ID INT NOT NULL,   
    4.  
    5. username VARCHAR(16) NOT NULL,  
    6.  
    7. INDEX [indexName] (username(length))  
    8.  
    9. );  

 

删除索引的语法:

 

  1. DROP INDEX [indexName] ON mytable; 

 

2)唯一索引

它与前面的普通索引类似,不同的就是:索引列的值必须唯一,但允许有空值。如果是组合索引,则列值的组合必须唯一。它有以下几种创建方式:

创建索引

 

  1. CREATE UNIQUE INDEX indexName ON mytable(username(length)) 

 

修改表结构

 

  1. ALTER mytable ADD UNIQUE [indexName] ON (username(length)) 

 

创建表的时候直接指定

 

    1. CREATE TABLE mytable(  
    2.  
    3. ID INT NOT NULL,   
    4.  
    5. username VARCHAR(16) NOT NULL,  
    6.  
    7. UNIQUE [indexName] (username(length))  
    8.  
    9. );  

 

3)主键索引

它是一种特殊的唯一索引,不允许有空值。一般是在建表的时候同时创建主键索引:

 

    1. CREATE TABLE mytable(  
    2.  
    3. ID INT NOT NULL,   
    4.  
    5. username VARCHAR(16) NOT NULL,  
    6.  
    7. PRIMARY KEY(ID)  
    8.  
    9. );  

 

当然也可以用 ALTER 命令。记住:一个表只能有一个主键。

4)组合索引

为了形象地对比单列索引和组合索引,为表添加多个字段:

 

  1. CREATE TABLE mytable(  
  2.  
  3. ID INT NOT NULL,   
  4.  
  5. username VARCHAR(16) NOT NULL,  
  6.  
  7. city VARCHAR(50) NOT NULL,  
  8.  
  9. age INT NOT NULL 
  10.  
  11. );  

为了进一步榨取MySQL的效率,就要考虑建立组合索引。就是将 name, city, age建到一个索引里:

 

  1. ALTER TABLE mytable ADD INDEX name_city_age (name(10),city,age); 

建表时,usernname长度为 16,这里用 10。这是因为一般情况下名字的长度不会超过10,这样会加速索引查询速度,还会减少索引文件的大小,提高INSERT的更新速度。

如果分别在 usernnamecityage上建立单列索引,让该表有3个单列索引,查询时和上述的组合索引效率也会大不一样,远远低于我们的组合索引。虽然此时有了三个索引,但MySQL只能用到其中的那个它认为似乎是最有效率的单列索引。

建立这样的组合索引,其实是相当于分别建立了下面三组组合索引:

 

  1. usernname,city,age  
  2.  
  3. usernname,city  
  4.  
  5. usernname  

为什么没有 cityage这样的组合索引呢?这是因为MySQL组合索引最左前缀的结果。简单的理解就是只从最左面的开始组合。并不是只要包含这三列的查询都会用到该组合索引,下面的几个SQL就会用到这个组合索引:

 

  1. SELECT * FROM mytable WHREE username="admin" AND city="郑州" 
  2.  
  3. SELECT * FROM mytable WHREE username="admin" 

而下面几个则不会用到:

 

  1. SELECT * FROM mytable WHREE age=20 AND city="郑州" 
  2.  
  3. SELECT * FROM mytable WHREE city="郑州" 

5)建立索引的时机

到这里我们已经学会了建立索引,那么我们需要在什么情况下建立索引呢?一般来说,在WHEREJOIN中出现的列需要建立索引,但也不完全如此,因为MySQL只对<<==>>=BETWEENIN,以及某些时候的LIKE才会使用索引。例如:

 

  1. SELECT t.Name 
  2.  
  3. FROM mytable t LEFT JOIN mytable m   
  4.  
  5. ON t.Name=m.username WHERE m.age=20 AND m.city='郑州' 

此时就需要对cityage建立索引,由于mytable表的userame也出现在了JOIN子句中,也有对它建立索引的必要。

刚才提到只有某些时候的LIKE才需建立索引。因为在以通配符%_开头作查询时,MySQL不会使用索引。例如下句会使用索引:

 

  1. SELECT * FROM mytable WHERE username like'admin%' 

而下句就不会使用:

 

  1. SELECT * FROM mytable WHEREt Name like'%admin' 

因此,在使用LIKE时应注意以上的区别。

6)索引的不足之处

上面都在说使用索引的好处,但过多的使用索引将会造成滥用。因此索引也会有它的缺点:

虽然索引大大提高了查询速度,同时却会降低更新表的速度,如对表进行INSERTUPDATEDELETE。因为更新表时,MySQL不仅要保存数据,还要保存一下索引文件。

建立索引会占用磁盘空间的索引文件。一般情况这个问题不太严重,但如果你在一个大表上创建了多种组合索引,索引文件的会膨胀很快。

索引只是提高效率的一个因素,如果你的MySQL有大数据量的表,就需要花时间研究建立最优秀的索引,或优化查询语句。

7)使用索引的注意事项

使用索引时,有以下一些技巧和注意事项:

索引不会包含有NULL值的列

只要列中包含有NULL值都将不会被包含在索引中,复合索引中只要有一列含有NULL值,那么这一列对于此复合索引就是无效的。所以我们在数据库设计时不要让字段的默认值为NULL

使用短索引

对串列进行索引,如果可能应该指定一个前缀长度。例如,如果有一个CHAR(255)的列,如果在前10个或20个字符内,多数值是惟一的,那么就不要对整个列进行索引。短索引不仅可以提高查询速度而且可以节省磁盘空间和I/O操作。

索引列排序

MySQL查询只使用一个索引,因此如果where子句中已经使用了索引的话,那么order by中的列是不会使用索引的。因此数据库默认排序可以符合要求的情况下不要使用排序操作;尽量不要包含多个列的排序,如果需要最好给这些列创建复合索引。

like语句操作

一般情况下不鼓励使用like操作,如果非使用不可,如何使用也是一个问题。like “%aaa%” 不会使用索引而like “aaa%”可以使用索引。

不要在列上进行运算

 

  1. select * from users where YEAR(adddate)<2007; 

将在每个行上进行运算,这将导致索引失效而进行全表扫描,因此我们可以改成

 

  1. select * from users where adddate<‘2007-01-01’;   

不使用NOT IN<>操作

索引原理: 

 

想要理解索引原理必须清楚一种数据结构「平衡树」(非二叉),也就是b tree或者 b+ tree,重要的事情说三遍:平衡树,平衡树,平衡树。当然, 有的数据库也使用哈希桶作用索引的数据结构 然而, 主流的RDBMS都是把平衡树当做数据表默认的索引数据结构的。

我们平时建表的时候都会为表加上主键, 在某些关系数据库中, 如果建表时不指定主键,数据库会拒绝建表的语句执行。 事实上, 一个加了主键的表,并不能被称之为「表」。一个没加主键的表,它的数据无序的放置在磁盘存储器上,一行一行的排列的很整齐, 跟我认知中的「表」很接近。如果给表上了主键,那么表在磁盘上的存储结构就由整齐排列的结构转变成了树状结构,也就是上面说的「平衡树」结构,换句话说,就是整个表就变成了一个索引。没错, 再说一遍, 整个表变成了一个索引,也就是所谓的「聚集索引」。 这就是为什么一个表只能有一个主键, 一个表只能有一个「聚集索引」,因为主键的作用就是把「表」的数据格式转换成「索引(平衡树)」的格式放置。

 

上图就是带有主键的表(聚集索引)的结构图。图画的不是很好, 将就着看。其中树的所有结点(底部除外)的数据都是由主键字段中的数据构成,也就是通常我们指定主键的id字段。最下面部分是真正表中的数据。 假如我们执行一个SQL语句:

select * from table where id = 1256;

首先根据索引定位到1256这个值所在的叶结点,然后再通过叶结点取到id等于1256的数据行。 这里不讲解平衡树的运行细节, 但是从上图能看出,树一共有三层, 从根节点至叶节点只需要经过三次查找就能得到结果。如下图

 

假如一张表有一亿条数据 ,需要查找其中某一条数据,按照常规逻辑, 一条一条的去匹配的话, 最坏的情况下需要匹配一亿次才能得到结果,用大O标记法就是O(n)最坏时间复杂度,这是无法接受的,而且这一亿条数据显然不能一次性读入内存供程序使用, 因此, 这一亿次匹配在不经缓存优化的情况下就是一亿次IO开销,以现在磁盘的IO能力和CPU的运算能力, 有可能需要几个月才能得出结果 。如果把这张表转换成平衡树结构(一棵非常茂盛和节点非常多的树),假设这棵树有10层,那么只需要10IO开销就能查找到所需要的数据, 速度以指数级别提升,用大O标记法就是O(log n)n是记录总树,底数是树的分叉数,结果就是树的层次数。换言之,查找次数是以树的分叉数为底,记录总数的对数,用公式来表示就是

 

用程序来表示就是Math.Log(100000000,10)100000000是记录数,10是树的分叉数(真实环境下分叉数远不止10), 结果就是查找次数,这里的结果从亿降到了个位数。因此,利用索引会使数据库查询有惊人的性能提升。

然而, 事物都是有两面的, 索引能让数据库查询数据的速度上升, 而使写入数据的速度下降,原因很简单的, 因为平衡树这个结构必须一直维持在一个正确的状态, 增删改数据都会改变平衡树各节点中的索引数据内容,破坏树结构, 因此,在每次数据改变时, DBMS必须去重新梳理树(索引)的结构以确保它的正确,这会带来不小的性能开销,也就是为什么索引会给查询以外的操作带来副作用的原因。

讲完聚集索引 接下来聊一下非聚集索引, 也就是我们平时经常提起和使用的常规索引。

非聚集索引和聚集索引一样, 同样是采用平衡树作为索引的数据结构。索引树结构中各节点的值来自于表中的索引字段, 假如给user表的name字段加上索引 那么索引就是由name字段中的值构成,在数据改变时, DBMS需要一直维护索引结构的正确性。如果给表中多个字段加上索引 那么就会出现多个独立的索引结构,每个索引(非聚集索引)互相之间不存在关联。 如下图

 

每次给字段建一个新索引, 字段中的数据就会被复制一份出来, 用于生成索引。 因此, 给表添加索引,会增加表的体积, 占用磁盘存储空间。

非聚集索引和聚集索引的区别在于, 通过聚集索引可以查到需要查找的数据, 而通过非聚集索引可以查到记录对应的主键值 再使用主键的值通过聚集索引查找到需要的数据,如下图

 

不管以任何方式查询表, 最终都会利用主键通过聚集索引来定位到数据, 聚集索引(主键)是通往真实数据所在的唯一路径。

然而, 有一种例外可以不使用聚集索引就能查询出所需要的数据, 这种非主流的方法 称之为「覆盖索引」查询, 也就是平时所说的复合索引或者多字段索引查询。 文章上面的内容已经指出, 当为字段建立索引以后, 字段中的内容会被同步到索引之中, 如果为一个索引指定两个字段, 那么这个两个字段的内容都会被同步至索引之中。

先看下面这个SQL语句

//建立索引

create index index_birthday on user_info(birthday);

//查询生日在1991111日出生用户的用户名

select user_name from user_info where birthday = '1991-11-1'

这句SQL语句的执行过程如下

首先,通过非聚集索引index_birthday查找birthday等于1991-11-1的所有记录的主键ID

然后,通过得到的主键ID值执行聚集索引查找,找到主键ID值对就的真实数据(数据行)存储的位置

最后, 从得到的真实数据中取得user_name字段的值返回, 也就是取得最终的结果

我们把birthday字段上的索引改成双字段的覆盖索引

create index index_birthday_and_user_name on user_info(birthday, user_name);

这句SQL语句的执行过程就会变为

通过非聚集索引index_birthday_and_user_name查找birthday等于1991-11-1的叶节点的内容,然而, 叶节点中除了有user_name表主键ID的值以外, user_name字段的值也在里面, 因此不需要通过主键ID值的查找数据行的真实所在, 直接取得叶节点中user_name的值返回即可。 通过这种覆盖索引直接查找的方式, 可以省略不使用覆盖索引查找的后面两个步骤, 大大的提高了查询性能,如下图

 

数据库索引的大致工作原理就是像文中所述, 然而细节方面可能会略有偏差,这但并不会对概念阐述的结果产生影响

最后, 推荐三本关系数据库方面的书籍, 文中所讲解的概念内容都是来自于此。

SQL Server2005技术内幕之T-SQL查询》

这本书虽然是针对SQL Server写的, 但是里面的大部份内容同样适用于其它关系数据库,此书对查询编写的技巧和优化讲解的非常透彻。

 

《关系数据库系统概论》第四版

王珊和萨师煊写的那本, 是大学计算机教材, 讲的通俗易懂, 在国内计算机书图书出版领域质量是排的上号的。

 

《数据库系统概念》

这本书在数据库领域非常出名, 被称之为帆船书, 书中内容博大精深,非一朝一夕可参透的。

 

 

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值