数据库(4)索引优化和SQL语句优化

这一段时间一直在学习关系型数据库,准备写一个小专题来总结一下这一段时间的学习结果。

话说数据库优化一直是SQL型数据库的热门问题,包括从网络I/O方面,从硬盘I/O方面,从CPU计算方面等等很多手段,都可以对数据库进行优化。

这篇文章主要总结了如何建立合适的索引提升查询速度,如何通过优化SQL提升语句执行速度。

当然这些探讨都是浅尝辄止的,如果有任何谬误,希望大家指出。

  • 一. 索引优化
    • 1.1 建立聚集索引
    • 1.2 常查询数据建立索引
    • 1.3 最左前缀原则
    • 1.4 不要建立无意义的索引
  • 二. SQL语句优化
    • 2.1 查询语句尽可能使用索引
    • 2.2 常见的技巧
      • 2.2.1 保证不查询多余的列与行
      • 2.2.2 慎用distinct关键字
      • 2.2.3 使用连接(join)来代替子查询
      • 2.2.4 使用limit对查询结果的记录进行限定
      • 2.2.5 一次提交多个insert语句

一. 索引优化

1.1 建立聚集索引

首先聚合索引是提升查询速度的最有效的手段。基于聚合索引的性质,我们可以了解到,数据库的物理存储顺序是按照聚合索引顺序排列的,而通过聚合索引的B+树,我们可以迅速的查找到任何一行的全部信息,注意是全部信息。

不了解聚集索引的同学可以参考我的 数据库(3)数据库索引这篇文章。

在Mysql中,InnoDB引擎会默认主键索引为聚集索引,所以建立一个表时,我们应该优先设立一个主键,后面所有基于主键的WHERE条件查询语句都会使用到这个聚集索引。

主键索引的选取,可以参考以下原则:

  1. 一行数据唯一的身份标识(数据库原理中叫做候选码),可以是几列的组合
  2. 额外添加的字段,用来唯一标识一行,通常是自增的id
CREATE TABLE test(
    id INT PRIMARY KEY,
    # 其他字段
);

同时需要特别注意的是,如果在一个表有很多数据的情况下,添加了主键,那么会导致这个表重新对硬盘上的存储结构做调整,这是一个费时的过程,所以推荐在建表时就建立主键,从而获得聚集索引。

1.2 常查询数据建立索引

我们知道,对于数据的查询,一般是通过条件查询(WHERE语句),而如果条件查询中的字段上没有建立索引的话,就会进行一次全表扫描,这是非常耗时的操作。

CREATE TABLE test(
    id INT,
    name VARCHAR(20)
); // 假设test表没有任何索引

SELECT name FROM test WHERE id=100; // 这条语句会导致全表查询,然后再进行where语句筛选(没有支持ICP的情况下)

这时候我们需要对id字段建立索引

CREATE INDEX index_name
ON table_name (id)

这样再进行查询语句就会使用B+树进行索引查询得到id=100的索引叶节点,然后根据查到的聚合索引的值,进行二次查找得到name

这是因为非聚集索引的叶子节点存储结构类似下图。
这里写图片描述

更好的解决方案是建立一个组合索引

CREATE INDEX index_name
ON table_name (id,name)

这样就不需要进行二次查询,而是直接完全通过索引B+树的叶子节点内容久能得到name

1.3 最左前缀原则

其实在上一篇文章中已经说过这个东西了,这里再拿出来说一下的原因是,有些索引你虽然建立了,但数据库不见得会用到这种查询手段。

比如说你建立了一个表:

CREATE TABLE test(
    name_1 CHAR(20),
    name_2 CHAR(20),
    name_3 CHAR(20),
    INDEX index_1 (name_1,name_2,name_3)
);

建立了一个组合索引(name_1,name_2,name_3)

然后你美滋滋的进行了一波查询:

SELECT name_1 FROM test WHERE name_1='haha';
SELECT name_1 FROM test WHERE name_2='wuwu';
SELECT name_1 FROM test WHERE name_3='yingyingying';
SELECT name_1 FROM test WHERE name_1='haha' AND name_2='wuwu';
SELECT name_1 FROM test WHERE name_1='haha' AND name_3='yingyingying';

我们逐条来分析这些语句会如何使用索引进行查询:

  1. 查询字段name_1在建立的索引里,而条件语句中WHERE name_1='haha'也在索引里,而且符合最左前缀规则,索引毫无疑问,这次查询会直接使用索引查询,而且查询数据在索引叶子节点里,查询效率是很高的。
  2. 查询字段name_1在建立的索引里,而条件语句中WHERE name_2='wuwu'也在索引里,但不符合最左前缀原则,这种查询不会使用到B+树的优秀特性,而是会进行全索引查询,然后再比较WHERE语句条件
  3. 和上面那种情况一样,也是会进行全索引查询,然后再比较WHERE语句条件
  4. 查询字段name_1在建立的索引里,条件语句WHERE name_1='haha' AND name_2='wuwu'字段在索引里,而且符合最左前缀原则,因此会利用B+树的性质进行查询,效率是很高的。
  5. WHERE name_1='haha' AND name_3='yingyingying'条件语句违背了最左前缀原则,需要进行全索引查询,而且这种写法会进行两次WHERE语句条件的比较,效率及其低下。

可以看到,建立组合索引优化查询语句时,一定要考虑到最左前缀原则,否则你的索引建立的可以说毫无意义。

1.4 不要建立无意义的索引

什么是无意义的索引?
基本上就是和上面体到的原则冲突的索引类型:

  1. 查询次数很少的语句中的字段的索引,因为建立索引是会降低数据表DML(INSERT,UPDATE,DELETE)性能的,如果一条语句查询语句一天都用不到几次,你却为它建立了繁杂的索引,那么将会导致数据表很不科学。
  2. 备注描述和大字段的索引,比如数据库中存储了文章字段,如果对这个字段添加了索引,那么将会增加非常多的额外磁盘占用开销,而且查询效率并不能得到很大的提升。
  3. 日期,年月,状态位,长字符,这些字段在数据库设计是尽量要满足3NF的要求,通过主键或者唯一键就能获得这些信息,尽量不要依据这些信息建立索引进行查询。

二. SQL语句优化

对于这方面我是真的不敢信口开河,因为我也只是处于学习阶段,因此也只总结一些常见的优化手段。

2.1 查询语句尽可能使用索引

这是显而易见的一条原则,我们知道B树这种数据结构实在是太适合数据库存储了,查询性能比起顺序查询高出太多倍了,因此如果能利用到自己建立的索引,那么就会极大的提升查询语句的性能。

SQL什么条件会使用索引?
当字段上建有索引时,通常以下情况会使用索引,在WHERE语句中:

INDEX_COLUMN = x
INDEX_COLUMN > x
INDEX_COLUMN >= x
INDEX_COLUMN < x
INDEX_COLUMN <= x
INDEX_COLUMN between x and x
INDEX_COLUMN in (x,y,...,z)
INDEX_COLUMN like 'x' 或者 'x%'(后导模糊查询)
T1. INDEX_COLUMN=T2. COLUMN1(两个表通过索引字段关联)

以上x,y,z代表任何和INDEX_COLUMN同一类型的数据

同时避免查询时不能使用索引查询的数据:
数据库(4)索引优化和SQL语句优化_第1张图片

可以看到几乎是和能使用索引查询的情况对立的。总结就是

  1. 不等的操作
  2. 函数运算或者运算符预算后的字段
  3. 前导模糊查询
  4. 字段类型不同
  5. NULL值
  6. 不符合最左前缀规则

2.2 常见的技巧

2.2.1 保证不查询多余的列与行

  • 尽量避免select * 的存在,使用具体的列代替*,避免多余的列
  • 使用where限定具体要查询的数据,避免多余的行
  • 使用top,distinct关键字减少多余重复的行

2.2.2 慎用distinct关键字

distinct在查询一个字段或者很少字段的情况下使用,会避免重复数据的出现,给查询带来优化效果。

但是查询字段很多的情况下使用,则会大大降低查询效率。

2.2.3 使用连接(join)来代替子查询

这样会减少I/O查询的次数。

2.2.4 使用limit对查询结果的记录进行限定

这样会减少网络I/O的压力,避免一次传输数据过多导致速度变慢。

2.2.5 一次提交多个insert语句

JDBC中的批处理了解一下,批量插入比起循环插入性能高太多了(当然这还和编程语言已经网络有关)


参考资料:

  1. 数据库优化
  2. SQL语句优化

你可能感兴趣的:(数据库)