MYSQL索引及查询优化

一、什么是索引

        索引类似是大型图书馆建立书目索引,可以提高数据检索的效率,降低数据库的IO成本。MySQL在数据量较大(官方文档说500~800w记录)的情况下性能开始逐渐下降,所以大数据量建立索引来提高数据的检索效率是非常有必要的。

1.1、索引的概念

       MySQL官方对索引的定义为:索引(Index)是帮助MySQL高效获取数据的数据结构。我们可以简单理解为:快速查找排好序的一种数据结构。

       索引是对数据库表中一列或多列的值进行排序的一种结构。在关系数据库中,索引是一种与表有关的数据库结构,它可以使对应于表的SQL语句执行得更快。索引的作用相当于图书的目录,可以根据目录中的页码快速找到所需的内容。当表中有大量记录时,若要对表进行查询,第一种搜索信息方式是全表搜索,是将所有记录一一取出,和查询条件进行一一对比,然后返回满足条件的记录,这样做会消耗大量数据库系统时间,并造成大量磁盘I/O操作;第二种就是在表中建立索引,然后在索引中找到符合查询条件的索引值,最后通过保存在索引中的ROWID(相当于页码)快速找到表中对应的记录。

       索引是一个单独的、物理的数据库结构,它是某个表中一列或若干列值的集合和相应的指向表中物理标识这些值的数据页的逻辑指针清单。索引提供指向存储在表的指定列中的数据值的指针,然后根据您指定的排序顺序对这些指针排序。数据库使用索引的方式与您使用书籍中的索引的方式很相似:它搜索索引以找到特定值,然后顺指针找到包含该值的行。在数据库关系图中,可以在选定表的“索引/键”属性页中创建、编辑或删除每个索引类型。当保存索引所附加到的表,或保存该表所在的关系图时,索引将保存在数据库中。

       MYSQL索引主要有两种结构:B+Tree索引和Hash索引。我们平常所说的索引,如果没有特别指明,一般都是指B树结构组织的索引(B+Tree索引)。B+Tree索引结构图如下所示:

MYSQL索引及查询优化_第1张图片

说明:

      最外层浅蓝色磁盘块1里有数据17、35(深蓝色)和指针P1、P2、P3(黄色)。P1指针表示小于17的磁盘块,P2是在17-35之间,P3指向大于35的磁盘块。真实数据存在于子叶节点也就是最底下的一层3、5、9、10、13……等,非叶子节点不存储真实的数据,只存储指引搜索方向的数据项,如17、35。

     查找过程:例如搜索28数据项,首先加载磁盘块1到内存中,发生一次I/O,用二分查找确定在P2指针。接着发现28在26和30之间,通过P2指针的地址加载磁盘块3到内存,发生第二次I/O。用同样的方式找到磁盘块8,发生第三次I/O。

     真实的情况是,上面3层的B+Tree可以表示上百万的数据,在上百万的数据只发生了三次I/O而不是上百万次I/O,时间提升是巨大的。

1.2、索引的创建

1)首先是要肯定是根据业务经常查询的语句;

2)尽量选择区分度高的列作为索引,区分度的公式是 COUNT(DISTINCT col) / COUNT(*)。表示字段不重复的比率,比率越大我们扫描的记录数就越少;

3)如果业务中唯一特性最好建立唯一键,一方面可以保证数据的正确性,另一方面索引的效率能大大提高。

4)创建索引脚本

ALTER TABLE sys_connect ADD INDEX `idx_createTime`(`create_time`) USING BTREE

1.3、导致SQL执行慢的原因

1)硬件问题。如网络速度慢,内存不足,I/O吞吐量小,磁盘空间满了等;

2)服务器调优及各个参数设置(调整my.cnf;

3)数据过多(分库分表);

4) 没有索引或者索引失效。

1.4、索引分析Explain

MySQL提供了Explain,用于显示SQL执行的详细信息,可以进行索引的优化。

以查询店长级别为1的SQL语句分析索引:

例:

EXPLAIN SELECT * FROM user_info T1 
INNER JOIN user_agent T2 ON T1.id = T2.id 
WHERE T1.create_time>='2019-06-01 00:00:00' AND T1.create_time <='2019-06-20 23:59:59' 
AND T2.shop_level=1

EXPLAIN执行分析结果如下:

索引使用情况在possible_keys、key和key_len三列。每个字段代表的含义分别为:

1) id

id列表示执行顺序,id越大则越先执行,id相同则由上至下执行。

例:

EXPLAIN SELECT * FROM user_info WHERE id = (SELECT id from user_agent WHERE agent_level =1 LIMIT 1);

2) select_type

     select_type列提供了各种表示table列引用的使用方式的类型。最常见的值包括SIMPLE、PRIMARY、DERIVED 和UNION。其他可能的值还有UNION RESULT、DEPENDENT SUBQUERY、DEPENDENT UNION、UNCACHEABLE UNION 以及UNCACHEABLE QUERY。

       1、SIMPLE

对于不包含子查询和其他复杂语法的简单查询,这是一个常见的类型。

例:

EXPLAIN SELECT T1.* FROM user_info T1;

       2、PRIMARY

这是为更复杂的查询而创建的首要表(也就是最外层的表)。这个类型通常可以在DERIVED 和UNION 类型混合使用时见到。

例:EXPLAIN SELECT * FROM (SELECT * FROM user_info WHERE nick_name = '东方不败') T1

UNION

SELECT T1.* FROM user_info T1 WHERE T1.id > 5 AND T1.id<10;

        3、DERIVED

当一个表不是一个物理表时,那么就被叫做DERIVED。下面的SQL语句给出了一个QEP中select-type为DERIVED类型。

例:EXPLAIN SELECT * FROM (SELECT MAX(T1.id) FROM (SELECT id FROM user_info WHERE nick_name = '东方不败') T1) T2;

        4、DEPENDENT SUBQUERY

这个select-type 值是为使用子查询而定义的。下面的SQL语句提供了这个值:

例:EXPLAIN SELECT T1.* FROM user_info T1 WHERE T1.id NOT IN (SELECT id FROM user_agent WHERE agent_level=1);

       5、UNION

这是UNION 语句其中的一个SQL 元素。

      6、UNION RESULT

这是一系列定义在UNION语句中的表的返回结果。当select_type为这个值时,经常可以看到table的值是,这说明匹配的id行是这个集合的一部分。下面的SQL产生了一个UNION和UNION RESULT。      

例:

EXPLAIN SELECT T1.* FROM user_info T1 WHERE T1.nick_name LIKE '东方不败%'
        UNION
        SELECT T1.* FROM user_info T1 WHERE T1.id > 5 AND T1.id<10;

3) table

table列是EXPLAIN命令输出结果中的一个单独行的唯一标识符。这个值可能是表名、表的别名或者一个为查询产生临时表的标识符,如派生表、子查询或集合。

       例:EXPLAIN SELECT * FROM (SELECT MAX(T1.id) FROM (SELECT TU.id FROM user_info TU,user_agent TA WHERE TU.id=TA.id AND TU.nick_name LIKE '东方不败%' AND TA.vip_num>=20) T1) T2;

       上图中可以看出,id=1的表[derived2]表示为id=2的表[TU]和TA衍生出来的表。

4) type

type列代表QEP(执行计划)中指定的表使用的连接方式。type字段比较重要,它提供了判断查询是否高效的重要依据依据。 通过type字段,我们判断此次查询是全表扫描 还是索引扫描等。下面是最常用的几种连接方式:

先从最佳类型到最差类型:NULL > system > const > eq_ref > ref > range > Index > All。ALL 类型因为是全表扫描,因此在相同的查询条件下,它是速度最慢的。而index类型的查询虽然不是全表扫描,但是它扫描了所有的索引,因此比ALL类型的稍快,前面的几种类型都是利用了索引来查询数据,因此可以过滤部分或大部分数据,因此查询效率就比较高了

       1、NULL在优化过程中就已得到结果,不用再访问表或索引。

例:EXPLAIN SELECT MAX(id) FROM user_info

        2、system: 表中只有一条数据,这个类型是特殊的const类型,这种情况很少见。

       例:EXPLAIN SELECT * FROM (SELECT MAX(T1.id) FROM (SELECT TU.id FROM user_info TU WHERE TU.nick_name LIKE '东方不败%') T1) T2;

        3、const: 针对主键或索引的等值查询扫描,最多只返回一行数据。const查询速度非常快,因为它仅仅读取一次即可。例如下面的这个查询,它使用了主键索引,因此type就是const类型的。

       例:EXPLAIN SELECT * FROM user_info WHERE id=1

        4、eq_ref: 此类型通常出现在多表的join查询,表示对于前表的每一个结果,都只能匹配到后表的一行结果。并且查询的比较操作通常是=,查询效率较高。

       例:EXPLAIN SELECT * FROM user_info, user_agent WHERE user_info.id = user_agent.id AND user_info.id<100;

        5、ref: 此类型通常出现在多表的 join查询,针对于非或非主键索引,或者是使用了最左前缀的规则索引的查询。例如下面这个例子中,就使用到了ref类型的查询:

       例:EXPLAIN SELECT * FROM user_info, user_agent WHERE user_info.id = user_agent.id AND user_agent.agent_level=1;

        6、range: 表示使用索引范围查询,通过索引字段范围获取表中部分数据记录。这个类型通常出现在 =, <>, >, >=, <, <=, IS NULL, <=>, BETWEEN, IN() 操作中。例如下面的例子就是一个范围查询:

       例:EXPLAIN SELECT * FROM user_info WHERE id>=1 AND id<=100;

        7、index: 表示全索引扫描(full index scan),和ALL类型类似,只不过 ALL类型是全表扫描,而index类型则仅仅扫描所有的索引,而不扫描数据。index类型通常出现在:所要查询的数据直接在索引树中就可以获取到, 而不需要扫描数据,通常就是查询索引列, 并不是在WHERE条件后面使用索引列。当是这种情况时,Extra字段会显示Using index。

       例:EXPLAIN SELECT nick_name FROM user_info;

        8、ALL: 表示全表扫描,这个类型的查询是性能最差的查询之一。通常来说,我们的查询不应该出现ALL类型的查询,因为这样的查询在数据量大的情况下,对数据库的性能是巨大的灾难。 如一个查询是ALL类型查询, 那么一般来说可以对相应的字段添加索引来避免。

      例:EXPLAIN SELECT * FROM user_info WHERE real_name ='TEST';

​​​​​​​​​​​​​​5)possible_keys

它表示mysql在查询时,可能使用到的索引。 注意,即使有些索引在possible_keys中出现,但是并不表示此索引会真正地被mysql使用到。mysql在查询时具体使用了哪些索引,由key字段决定。

6)key

key列指出mysql优化器决定选择使用的索引来优化对该表的访问。一般来说SQL查询中的每个表都仅使用一个索引。也存在索引合并的少数例外情况,如给定表上用到了两个或者更多索引。此字段是mysql在当前查询时所真正使用到的索引。

7)key_len

定义了mysql在索引里使用的字节数,表示查询优化器使用了索引的字节数,这个字段可以评估组合索引是否完全被使用。

例:EXPLAIN SELECT nick_name FROM user_info WHERE id = 5610286981397000001;

注:key_len表示索引字段最大可能使用的长度,id是long类型,long类型的长度是8个字节,所以key_len=8。

8)ref

这个表示显示索引的哪一列被使用了,如果可能的话,是一个常量。前文的type属性里也有ref,注意区别。

9)  rows

       MYSQL估计为了找到所需的行而要读取的行数。这个数字是内嵌循环关联计划里的循环数目,也就是说它不是MYSQL认为它最终要从表里读取出来的行数,而是MYSQL为了找到符合查询的每一点上标准的那些行而必须读取的行的平均数。rows 列提供了试图分析所有存在于累计结果集中的行数目的MySQL 优化器估计值。.rows是一个重要的字段,MYSQL查询优化器根据统计信息,估算SQL要查找到结果集需要扫描读取的数据行数,这个值非常直观的显示SQL效率好坏,原则上rows越少越好。可以对比key中的例子,一个没建立索引钱,rows是9,建立索引后,rows是4。

10)filtered

在mysql5.1里新加的,在使用explain extended时出现。它显示的是针对表里符合条件的记录数的百分比所做的一个悲观估算值。

  filtered 列给出了一个百分比的值,这个百分比值和rows 列的值相乘,可以估计出那些将要和QEP(执行计划)中的前一个表进行连接的行的数目。前一个表就是指id 列的值比当前表的id 小的表。这一列只有在EXPLAIN EXTENDED 语句中才会出现。​​​​​​​

11)Extra

显示上述信息之外的其它信息,但却很重要。Extra 列可以包含多个值,可以有很多不同的取值,并且这些值还在随着MySQL 新版本的发布而进一步增加

例:EXPLAIN SELECT * FROM (SELECT MAX(T1.id) FROM (SELECT TU.id FROM user_info TU,user_agent TA WHERE TU.id=TA.id AND TU.nick_name LIKE '东方不败%' AND TA.vip_num>=20) T1) T2;

using filesort说明MySQL会对数据使用一个外部的索引排序,而不是按照表内的索引顺序进行读取。MySQL中无法利用索引完成的排序操作称为“文件排序”,其实不一定是文件排序,内部使用的是快排序。一般有using filesort都建议优化去掉,因为这样的查询cpu资源消耗大。

using temporary使用了临时表保存中间结果,MySQL在对查询结果排序时使用临时表。常见于排序order by和分组查询group by

using index表示相应的SELECT操作中使用了覆盖索引(Covering Index),避免访问了表的数据行,效率不错。

using where表示MySQL服务器从存储引擎收到行后再进行“后过滤”(Post-filter)。所谓“后过滤”,就是先读取整行数据,再检查此行是否符合where句的条件,符合就留下,不符合便丢弃。因为检查是在读取行后才进行的,所以称为“后过滤”。。

select tables optimized away在没有GROUP BY子句的情况下基于索引优化MIN/MAX操作或者对于MyISAM存储引擎优化COUNT(*)操作,不必等到执行阶段再进行计算,查询执行计划生成的阶段即完成优化

distinct优化distinct操作,在找到第一匹配的元祖后即停止找同样值的操作

1.5、索引失效的原因

1)对索引列运算,运算包括(+、-、*、/、!、<>、%、like'%_'(%放在前面);

2)类型错误,如字段类型为varchar,where条件用number;

3)对索引应用内部函数,这种情况下应该建立基于函数的索引,如

SELECT * FROM table T1 WHERE ROUND(T1.logicdb_id) = 1,此时应该建ROUND(T1.logicdb_id)为索引,mysql8.0开始支持函数索引,5.7可以通过虚拟列的方式来支持,之前只能新建一个ROUND(T1.logicdb_id)列然后去维护;

3)如果条件有OR,即使其中有条件带索引也不会使用(这也是为什么建议少使用OR的原因),如果想使用OR,又想索引有效,只能将OR条件中的每个列加上索引

4)如果列类型是字符串,那一定要在条件中数据使用引号,否则不使用索引;

5)B-Tree索引IS NULL不会走索引,IS NOT NULL会走索引,对于位图索引IS NULL,IS NOT NULL都会走;

6)组合索引遵循最左原则;

7)条件范围查询索引不生效的原因,如果MYSQL估计使用全表扫描要比使用索引快,则不使用索引,即查询满足条件的数据量接近数据表的数据量时,索引失效。

例如:EXPLAIN SELECT * FROM sys_connect WHERE create_time >= '2019-06-22 00:00:00' AND create_time<='2019-06-22 23:59:59';

1.6 字段类型和编码

1)MYSQL返回字符串长度:CHARACTER_LENGTH方法(与CHAR_LENGTH一样)返回的是字符数,LENGTH函数返回的是字节数,一个汉字三个字节;

2) varvhar等字段建立索引长度计算语句:select count(distinct left(test,5))/count(*) from table; 越趋近1越好;

3) MYSQL的utf8最大是3个字节不支持emoji表情符号,若支持emoji表情符号,必须使用utf8mb4。需要在MYSQL配置文件中配置客户端字符集为utf8mb4。jdbc的连接串不支持配置characterEncoding=utf8mb4,最好的办法是在连接池中指定初始化SQL;

4) MYSQL排序规则(一般使用_bin和_genera_ci):

①:utf8_genera_ci不区分大小写,ci为case insensitive的缩写,即大小写不敏感;

②:utf8_general_cs区分大小写,cs为case sensitive的缩写,即大小写敏感,但是目前MySQL版本中已经不支持类似于***_genera_cs的排序规则,直接使用utf8_bin替代;

③:utf8_bin将字符串中的每一个字符用二进制数据存储,区分大小写。

1.7、索引的优缺点

1)索引的优点

可以大大加快数据的检索速度,这也是创建索引的最主要的原因。且通过使用索引,可以在查询的过程中,使用优化隐藏器,提高系统的性能。

2) 索引的缺点

索引需要额外的维护成本,因为索引文件是单独存在的文件,对数据的增加、修改、删除都会产生额外的对索引文件的操作,这些操作需要消耗额外的IO,会降低增、改、删的执行效率。

二、MYSQL分页LIMIT查询速度的优化

在MYSQL中LIMIT可以实现快速分页,但是如果数据到几百万以上时,使用LIMIT必须优化才能有效的合理的实现分页,否则可能会拖垮服务器。当一个表数据有几百万的数据的时候LIMIT分页就出现了性能问题问题!

例如:使用LIMIT分页获取用户信息,查询的SQL语句如下:

SELECT * FROM user_info LIMIT 0,20

当查询第一页的时间,查询速度很快,也就几十毫秒的事情,而当LIMIT 2000000,20的时候就很忙了。

所以,在使用LIMIT进行分页查询了,要巧用LIMIT的分页的一些技巧。

2.1 借用书签进行快速分页

       所谓的书签,是指能够快速定位到某一条数据的所在位置。我们设计的每一张表都有一个主键id,可以把id当做一个书签。每次获取分页数据后,记录当前分页的最大id,下次分页时使用这个id进行过滤机分页。

       例:SELECT * FROM user_info WHERE id> 890113247 LIMIT 0,20

       这样的SQL语句查询效率是非常快的,跟查询第一页的效率相当。

2.2 子查询优化

       子查询优化,是只先找出第一条数据,然后再查询大于等于这条数据的id就是要获取的数据。子查询优化的缺点是:数据必须是连续的,可以说不能有WHERE条件,WHERE条件会筛选数据,导致数据失去连续性。

       例:SELECT * FROM user_info WHERE id >= (SELECT id FROM user_info LIMIT 2000000,1) limit 20

查询一下EXPLAIN结果:

2.3 延迟关联

       我们项目中的一个关于用户资产流水的分页查询,当用户资产流水很多时,分页就会很忙。

       例:查询用户资产流水的SQL语句如下:      

SELECT id,user_id,
              from_user_id,
              order_no,
              asset_type,
              label,
              balance,
              balance_result,
              type,
              COMMENT,
              create_time
FROM  asset_detail
WHERE  user_id = 1889 AND type IN (1,2,8,11,12,5,4,2,3,7,6,13,14,51,102)
ORDER BY create_time DESC
       LIMIT 0,20

 EXPLAIN分析结果:

       上述的查询语句中,也走了索引,但实际查询还是非常慢,其中慢的原因在于使用的倒序排序,整个查询结果将近需要60秒的时间。

       经过优化的SQL如下:     

  SELECT
       T1.*
FROM (
       SELECT
              id,
              user_id,
              from_user_id,
              order_no,
              asset_type,
              label,
              balance,
              balance_result,
              type,
              COMMENT,
              create_time
       FROM
              asset_detail
       WHERE
              1 = 1
         AND user_id = 1867
              AND type IN (201,202,8,11,12,5,4,2,3,7,6,13,14,51,102)
       ) T1
       INNER JOIN asset_detail T2 ON T1.id = T2.id
ORDER BY
       T2.create_time DESC
       LIMIT 0,20

       EXPLAIN分析结果:

       经过优化后的SQL,查询用了不到1秒中的时间。

三、MYSQ优化的一些建议

       MYSQL查询优化的一些常用的建议点,如下:

  1. EXPLAIN

做MySQL优化,我们要善用 EXPLAIN 查看SQL执行计划。

2. SQL语句中IN包含的值不应过多

       MySQL对于IN做了相应的优化,即将IN中的常量全部存储在一个数组里面,而且这个数组是排好序的。但是如果数值较多,产生的消耗也是比较大的,建议控制在1000以内。

例如:对于IN内的连续数值查询,SELECT * FROM user_info WHERE id IN(1,2,3,4,5,6,7,8,9,10),能用 BETWEEN就不要用IN了;再或者使用连接来替换。

3.SELECT语句务必指明字段名称

       SELECT * FROM增加很多不必要的消耗(CPU、IO、内存、网络带宽);增加了使用覆盖索引的可能性;当表结构发生改变时,前端也需要更新。所以建议直接在SELECT后面接上字段名。

4. 获取唯一一条数据的时候,请使用LIMIT1

       使用LIMIT 1,是为了使EXPLAIN中type列达到const类型,提高查询效率。

5. 如果排序字段没有用到索引,就尽量少排序

6. 如果限制条件中其他字段没有索引,尽量少用OR

OR两边的字段中,如果有一个不是索引字段,而其他条件也不是索引字段,会造成该查询不走索引的情况。在很多时候使用 UNION ALL或者是UNION的方式来代替“OR”会得到更好的效果。

7. 建议使用UNION ALL代替UNION

       UNION和UNION ALL的差异主要是前者需要将结果集合并后再进行唯一性过滤操作,这就会涉及到排序,增加大量的CPU运算,加大资源消耗及延迟。使用UNION ALL的前提条件是两个结果集没有重复数据。

8. 不使用ORDER BY RAND()

       比如说,随机查询100个用户信息,SQL语句如下:

       SELECT * FROM user_info ORDER BY RAND() LIMIT 100;

       上面的SQL语句可以优化为:

       SELECT * FROM user_info T1 INNER JOIN (SELECT RAND() * (SELECT MAX(id) FROM user_info) AS rand_id) T2 ON T1.id > T2.rand_id LIMIT 100;

9. 区分IN和EXISTS,NOT IN和NOT EXISTS

       区分IN和EXISTS主要是造成了驱动顺序的改变(这是性能变化的关键),如果是EXISTS,那么以外层表为驱动表先被访问,如果是IN,那么先执行子查询。所以IN适合于外表大而内表小的情况;EXISTS适合于外表小而内表大的情况。

关于NOT IN和NOT EXISTS,推荐使用NOT EXISTS,不仅仅是效率问题,NOTIN可能存在逻辑问题。如何高效的写出一个替代NOT EXISTS的SQL语句?

       使用NOT EXISTS统计表A中的id在表B中不存在的数据总数的SQL语句:

       SELECT COUNT(*) FROM user_info A WHERE NOT EXISTS (SELECT B.id FROM user_agent B WHERE A.id=B.id);

       优化后的SQL语句:

SELECT COUNT(*) FROM user_info A LEFT JOIN user_agent B ON A.id = B.id WHERE B.id IS NULL;

10. 使用合理的分页方式以提高分页的效率

       例:SELECT * FROM user_info LIMIT 2000000,20;

使用上述SQL语句做分页的时候,我们会发现,随着表数据量的不断增加,直接使用LIMIT分页查询会越来越慢。优化的方法是:可以借助表的id唯一主键索引的特性,取前一页的最大行数的id,然后根据这个最大的id来限制下一页的起点。

SELECT * FROM user_info WHERE id>890113272 LIMIT 20;

11. 分段查询

在一些用户选择条件查询的页面中,可能一些用户选择的时间范围过大,造成查询缓慢。主要的原因是扫描行数过多。这个时候可以通过程序,分段进行查询,循环遍历,将结果合并处理进行展示。

例:EXPLAIN SELECT * FROM user_info WHERE id BETWEEN 1000000 AND 2000000;

     对于上述的这条SQL的查询,扫描的行数接近百万级以上的时候就可以使用分段查询。

12. 避免在WHERE子句中对字段进行NULL值判断

       对于NULL的判断会导致引擎放弃使用索引而进行全表扫描,大幅降低了查询效率。

13. 不建议使用%前缀模糊查询

       例如:LIKE “%name”或者LIKE “%name%”,这种查询会导致索引失效而进行全表扫描。但是可以使用LIKE “name%”。

14. 避免在WHERE子句中对字段进行表达,函数式操作

       例:SELECT * FROM user_info WHERE vip_number*2=36

上述的查询语句中对字段就行了算术运算,这会造成引擎放弃使用索引,应该改成:

SELECT * FROM user_info WHERE vip_number=36*2

15. 避免隐式类型转换

       WHERE子句中出现 COLUMN字段的类型和传入的参数类型不一致的时候发生的类型转换,建议先确定WHERE中的参数类型。

16. 对于联合索引来说,要遵守最左前缀法则

举列来说索引含有字段id,name,time,可以直接用id字段,也可以id,name这样的顺序,但是name,time都无法使用这个索引。所以在创建联合索引的时候一定要注意索引字段顺序,常用的查询字段放在最前面。

17. 必要时可以使用FORCE INDEX来强制查询走某个索引

有的时候MYSQL优化器采取它认为合适的索引来检索SQL语句,但是可能它所采用的索引并不是我们想要的。这时就可以采用FORCE INDEX来强制优化器使用我们制定的索引。

18. 注意范围查询语句

对于联合索引来说,如果存在范围查询,比如BETWEEN,>,<等条件时,会造成后面的索引字段失效。

19. 关于JOIN优化

       LEFT JOIN A表为驱动表;

INNER JOIN MySQL会自动找出那个数据少的表作用驱动表;

RIGHT JOIN B表为驱动表;

尽量使用INNER JOIN,避免LEFT JOIN:

参与联合查询的表至少为2张表,一般都存在大小之分。如果连接方式是INNER JOIN,在没有其他过滤条件的情况下MySQL会自动选择小表作为驱动表,但是LEFT JOIN在驱动表的选择上遵循的是左边驱动右边的原则,即LEFT JOIN左边的表名为驱动表。

合理利用索引

被驱动表的索引字段作为on的限制字段。

利用小表去驱动大表

20. 保证每张表都有一个主键ID

养成一种良好的习惯,每设计一张新表的时候,都应该为其设计一个ID字段,并让其成为主键,而且最好是整型,同时设置这个ID字段为自增(AUTO_INCREMENT)的标志。

21. 选择正确的存储引擎

       MYSQL中的两个主要存储引擎是InnoDB和MYISAM。每个人都有自己的优点和缺点。

InnoDB是一个更复杂的存储引擎,在大多数小型应用程序中可能比MYISAM慢,但是它支持基于行的锁定,这样可以更好地扩展。它还支持一些更高级的特性,如事务。

MYISAM很适合阅读量大的应用程序,但是当有大量的写操作时,它的扩展就不太好了。即使您正在更新一行中的一个字段,整个表也会被锁定,在该查询完成之前,任何其他进程都无法从中读取。MYISAM在计算SELECT(*)类型查询方面非常快速。

四、数据库性能的一些思考

在对慢SQL查询做优化的时候,很多时候可能是忘了建索引,像这种问题很容易解决,加个索引就行了。但是有一下几种情况就不是简单能加索引能解决了。

4.1、业务代码循环读数据库

考虑这样一个场景,获取用户粉丝列表信息 加入分页是10个,其实像这样的SQL是十分简单的,通过连表查询性能也很高,但是有的时候,很多开发采用了取出一串id,然后循环读每个id的信息,这样如果id很多对数据库的压力是很大的,而且性能也很低。

4.2、统计SQL

很多时候,业务上都会有排行榜统计,发现开发有很多地方直接采用数据库做计算,在对一些大表的做聚合运算的时候,经常超过五秒,这些SQL一般很长而且很难优化。像这种场景,如果业务允许(比如一致性要求不高或者是隔一段时间才统计的),可以专门在从库里面做统计。

4.3、超大分页

在慢SQL查询日志中发现了一些超大分页的慢查询如LIMIT 4000000,1000,因为MYSQL的分页是在server层做的,可以采用延迟关联再减少回表。但是看了相关的业务代码,正常的业务逻辑是不会出现这样的请求的,这种情况很有可能是有恶意用户在刷接口。所以最好在开发的时候也对接口加上校验拦截这些恶意请求。

你可能感兴趣的:(mysql)