【MySQL】索引

功能

  索引可以提高条件查询速度。

示例

  • 新建一个数据库test,新建一张表user,在表中新建一个字段id。
CREATE DATABASE test;
use test;
CREATE TABLE user( id INT(8) );
  • 通过代码插入100万条数据。
@Test
public void insert() {
    String url = "jdbc:mysql://localhost:3306/test?useSSL=true";
    Connection connection = null;
    Statement statement = null;
    try {
        Class.forName("com.mysql.jdbc.Driver");
        connection = DriverManager.getConnection(url, "root", "root");
        statement = connection.createStatement();
        List values = new ArrayList<>();
        for (int i = 0; i < 10; i++) {

            int startNum = 100000 * i;
            List values = new ArrayList<>();
            for (int j = startNum; j < 100000 * (i + 1); j++) {
                 values.add("(" + j + ")");
            }

            String result = StringUtils.strip(values.toString(), "[]");
            String sql = "insert into user(id) values" + result;
            statement.executeUpdate(sql);
            System.out.println("已成功插入数据" + (i + 1) + "0万条。");
        }
    } catch (SQLException e) {
    } catch (ClassNotFoundException e2) {
    } finally {
        try {
            statement.close();
        } catch (SQLException e) {
        }
        try {
            connection.close();
        } catch (SQLException e2) {
        }
    }
}
  • 根据指定id查询,记录id添加索引和不添加索引的执行时间。

    sql语句:SELECT * FROM user WHERE id = 888888;
    查询结果:添加索引,30ms ; 不添加索引,450ms 。

  • 备注:本来想用一个for循环,添加100万个值到insert语句,没有成功。所以,采用了一次10万,执行10次的方式。

定义

  索引是一个单独的、存储在磁盘上的数据库结构,其包含了对数据库表中指定记录的引用指针。
  
  非官方说明:某一天,你有一个字不认识,手边正好有一个字典。通常,要么根据拼音来查询,要么根据偏旁来查询。而不会把字典从头到尾翻一遍。因为,根据拼音或者偏旁来查询的效率高。对于数据库来说,没有索引的表,每次查询都会从头到尾遍历整张表,然后返回查询结果。如果表中添加索引,则不会全表扫描,索引项充当了指向表行的指针,可以快速确定哪些行与查询条件匹配,并检索这些行的其他列值。所有MySQL数据类型都可以添加索引,但是不必要的索引也会浪费时间和空间,为了建立最优索引集,实现快速查询,添加索引时,性能测试是关键。

格式

CREATE [UNIQUE|FULLTEXT|SPATIAL] INDEX index_name
    [index_type]
    ON tbl_name (index_col_name,...)
    [index_option]
    [algorithm_option | lock_option] ...

index_col_name:
    col_name [(length)] [ASC | DESC]

index_option:
    KEY_BLOCK_SIZE [=] value
  | index_type
  | WITH PARSER parser_name
  | COMMENT 'string'

index_type:
    USING {BTREE | HASH}

algorithm_option:
    ALGORITHM [=] {DEFAULT|INPLACE|COPY}

lock_option:
    LOCK [=] {DEFAULT|NONE|SHARED|EXCLUSIVE}

实例:CREATE INDEX part_of_name ON customer (name(10));

类型

  索引有多种分类方式,按照存储结构可以划分为聚集索引和非聚集索引;按照数据唯一性可以划分为唯一索引和非唯一索引;按照键列个数可以划分为单列索引和组合索引。

  • 聚集索引:对磁盘上实际数据重新组织以按照指定的一列或多列值排序,确定了表中数据的物理顺序。因此,磁盘上会多出该表的副本和索引中间页,大概相当于该表120%的附加空间。查询效率通常比其他索引要快。在聚集索引下,数据在物理上是按序排列的,因此,聚集索引对于那些经常要搜索范围值的列特别有效。如:按日期范围查询。多行检索快。
  • 非聚集索引:不会重新组织表的数据,而是对每一行存储对应的索引列值并用一个指针指向数据所在的页面。其运行原理与汉语字典中根据“偏旁部首”查找汉字类似。不用全表扫描。单行检索快。
  • 唯一索引(unique):索引列值必须唯一。但会忽略NULL值。主键是一种特殊的唯一索引。不允许有Null值。
  • 普通索引(normal):索引列值没有限制。MySQL基本索引类型,允许在定义索引的列中插入重复值。
  • 单列索引:一个索引只包含单个列。一个表中可以有多个单例索引。
  • 组合索引:在表中的多个字段的组合上创建的索引。只有查询条件中使用了这些字段的左边字段时,索引才会被使用,使用组合索引遵循最左前缀的原则。比如,建立组合索引:title,time,相当于分别建立了两组索引:titletitle,time。为什么没有time这样的索引呢?这是因为MySQL组合索引遵循“最左前缀”的原则,简单的理解就是只能从最左面开始组合。
  • 全文索引(full text):待续。

方法

  索引存储方法有BTREE和HASH两种。
  大部分的索引(主键索引,唯一索引,普通索引)都是以BTREE的方式来存信息。

缺点

  1. 索引会增加insert、update和delete的成本,因为每个索引都必须同时更新。
  2. 索引需要占用物理空间。除了数据表占空间之外,每一个索引也占物理空间,如果建立聚集索引,需要的空间会更大。

原则

  • 经常用于查询条件的字段应该创建索引。(官网原文)
  • 数据量小的表,有没有索引影响不大。(官网原文)
  • 在重复值多的列上不建立索引。比如在学生表的性别字段上只有男和女两个不同的值。相反的,在一个字段上不同值较多的列上可建立索引。这个也容易验证,还是用示例代码。
      1、首先,在user表中添加一个字段gender。
      2、修改示例代码,如下所示。
      3、查询语句:SELECT * from user WHERE gender = '男';
      4、查询结果:有索引,1000ms;没有索引,700ms。
      
      原因分析:官网上说,“当查询需要访问大多数行时,顺序读取要比执行索引更快,顺序读取最小化磁盘查找(官网原文)。”重复值越多,根据此列查询时就会访问越多的行,根据索引指针查找表的次数就会越多,整体性能就会降低。当然,到底重复率多少,才不用索引,这个要根据具体情况,测试后再决定。
f( i%2 == 0){
    for (int j = startNum; j < 100000 * (i + 1); j++) {
        values.add("(" + j + ",\'男\'"+")");
    }
}else{
    for (int j = startNum; j < 100000 * (i + 1); j++) {
        values.add("(" + j + ",\'女\'"+")");
    }
}
String result = StringUtils.strip(ids.toString(), "[]");
String sql = "insert into user(id,gender) values" + result ;
  • 如果查询条件涉及到多个索引,MySQL通常使用找到最小行数的索引(优先级最高)。(官网原文)

  • 按照字段在查询条件中出现的频度建立组合索引。只有组合索引的第一个字段出现在查询条件中,该索引才会被使用。如果需要根据组合索引排序或分组,那么ORDER BY或GROUP BY后面的顺序要和组合索引顺序对应,如:ORDER BY key_part1, key_part2。(官网原文)

使用情况监测

  在sql语句前加关键字:EXPLAIN,可以查看sql语句的索引使用情况。(官网说明)
  示例:

EXPLAIN SELECT * FROM te_paper_record WHERE paper_id = 'Vh12yU4BUtijy1hYECs9jG' AND student_id = 'FCW8ySwQVsrGVQSTvfETR1';

  输出格式:
这里写图片描述

  • possible_keys:本次查询可能使用的索引。
  • key:本次查询实际使用的索引。
  • 其他字段说明,请查看官网:EXPLAIN输出格式。

杂谈

  1. 索引是帮助MySQL高效获取数据的数据结构。
  2. 数据库默认会为主键生成索引。因此,一般不必显式声明。这也很容易验证,利用文章开头的示例,去掉索引,设id为主键,记录查询时间,并与有索引时的查询时间做比较即可。
  3. 利用覆盖索引来进行查询,避免回表。
    被查询的列,数据能从索引中取得,而不用通过行定位符row-locator再到row上获取,即“被查询的列要被所建的索引覆盖”,这能够加速查询速度。如:Select login_time from user where login_name=? and passwd=?,可以建立(login_name,passwd,login_time)的组合索引,由于login_time已经建立在索引中了,不再需要去row上获取,从而提高查询速度。
  4. 平常用的SQL查询语句一般都有比较多的限制条件,所以为了进一步提高效率,就要考虑建立组合索引。
  5. 使用短索引。对字符串列进行索引,如果可能,应该指定一个前缀长度。例如,如果有一个Char(255)的列,如果在前10个或20个字符内,多数值是唯一的,那么就不要对整个列进行索引。短索引不仅可以提高查询速度而且可以节省磁盘空间和I/O操作。建立短索引格式:CREATE INDEX part_of_name ON customer (name(10));
  6. 索引用于快速找出在某个列中有一特定值的行,不使用索引,MySQL必须从第一条记录开始读完整个表,直到找出相关的行,表越大,查询数据所花费的时间就越多。如果表中查询的条件列有索引,MySQL能够快速到达一个位置去搜索数据文件,而不必查看所有数据,会节省很多时间。
  7. 索引也需要占用空间,我们知道数据表中的数据也会有最大上线设置,如果我们有大量的索引,索引文件可能会比数据文件更快达到上线值。
  8. 索引列排序。MySQL查询只使用一个索引,因此如果where子句中已经使用了索引的话,那么order by 中的列是不会使用索引的。因此数据库默认排序可以符合要求的情况下不要使用排序操作,尽量不要包含多个的排序,如果需要最好给这些列创建组合索引。
  9. 不要在列上进行运算。例如select * from users where YEAR(addate) < 2017 ,将在每行上进行运算。这将导致索引失效而进行全表扫描,因此我们可以改成:select * from users where addate < '2017-01-01'
  10. 索引是一种特殊的文件,它们包含着对数据表记录的引用指针。索引就好比是一本书前面的目录,能加快数据库的查询速度。在没有索引的情况下,数据库会遍历表以选择符合条件的数据;有了索引之后,数据库会直接在索引中查找符合条件的选项。
  11. MySQL只对某些操作符才使用索引:< , <= , = , > , >= , between , in,以及某些时候的like(不以通配符%或_开头的情形)。而理论上每张表里面最多可创建16个索引,不过除非是数据量真的很多,否则过多的索引会占用很多资源。
  12. 在数据库中建立索引是为了提高数据的查询速度。查询是数据库中用的最多的操作,如何能更快地找到所需信息,是数据库中的一项重要内容,特别是在海量数据检索方面尤为重要。
  13. 数据库索引类似于书籍目录,在书籍中可以通过目录所标示的关键字和页码快速找到所需信息,同理数据库索引页可以快速定位表中的数据,从而避免了扫描整个数据表。
  14. 在Student数据表中新建一个索引sno,当数据库管理系统执行一个在Student表上根据指定sno列的值查找该学生信息的操作时,它能够识别sno列的索引,并首先在索引部分查找该学生的学号,然后根据找到的学号所指向的数据的存储位置,到student表中直接检索需要的信息;如果没有索引,DBMS会从Student表的第一行开始,遍历出包含有指定sno值的行。

全表扫描

  1. 先将存放在磁盘上的该表的数据全部加载进内存。
  2. 通过where条件将所有记录一条一条判断,将不符合条件的记录全部舍弃。
  3. 返回符合条件的结果集。

总结

  字段上添加索引,根据此字段值查询时,会先从索引表中找到符合查询条件的索引值,然后通过保存在索引中的指针快速找到表中对应的记录。由于索引表的结构和查询方式是经过优化的,查询速度要高于遍历表,所以使用索引查询时的速度会高很多。至于,索引表的结构是什么?查询方式是什么?那我就不知道了。以上内容,有些经过验证,有些还没有验证,有些不知道怎么验证。所有内容只是奉行“拿来主义”,请在家长的陪同下观看。

参考资料:https://dev.mysql.com/doc/refman/5.7/en/optimization-indexes.html
参考资料:https://juejin.im/post/5b14e0fd6fb9a01e8c5fc663
参考资料:https://blog.csdn.net/qq_21987433/article/details/79753551

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