4. 常用 SQL 的优化

常用 SQL 的优化

前面我们介绍了 MySQL 中怎么样通过索引来优化查询。日常开发中,除了使用查询外,我 们还会使用一些其他的常用 SQL,比如 INSERT、GROUP BY 等。对于这些 SQL 语句,我们该 怎么样进行优化呢?

本文属于SQL 优化系列篇

1. 大批量插入数据

当用 load 命令导入数据的时候,适当的设置可以提高导入的速度。

对于 MyISAM 存储引擎的表,可以通过以下方式快速的导入大量的数据。

ALTER TABLE tbl_name DISABLE KEYS; 
loading the data 
ALTER TABLE tbl_name ENABLE KEYS; 

DISABLE KEYSENABLE KEYS 用来打开或者关闭 MyISAM 表非唯一索引的更新。在导入大量的数据到一个非空的 MyISAM 表时,通过设置这两个命令,可以提高导入的效率。对于导入大量数据到一个空的 MyISAM 表,默认就是先导入数据然后才创建索引的,所以不用进行设置。

上面是对MyISAM表进行数据导入时的优化措施,对于InnoDB类型的表,这种方式并不能提高导入数据的效率,可以有以下几种方式提高InnoDB表的导入效率。

  1. 因为 InnoDB类型的表是按照主键的顺序保存的,所以将导入的数据按照主键的顺序排列,可以有效地提高导入数据的效率。

    例如,下面文本film_test3.txt是按表film_test4的主键存储的,那么导入的时候共耗时 27.92秒。

    mysql> load data infile '/home/mysql/film_test3.txt' into table film_test4; 
    Query OK, 1587168 rows affected (22.92 sec) 
    Records: 1587168  Deleted: 0  Skipped: 0  Warnings: 0 
    

    而下面的 film_test4.txt 是没有任何顺序的文本,那么导入的时候共耗时 31.16秒。

    mysql> load data infile '/home/mysql/film_test4.txt' into table film_test4; 
    Query OK, 1587168 rows affected (31.16 sec) 
    Records: 1587168  Deleted: 0  Skipped: 0  Warnings: 0 
    
  2. 在导入数据前执行 SET UNIQUE_CHECKS=0,关闭唯一性校验,在导入结束后执行 SET UNIQUE_CHECKS=1,恢复唯一性校验,可以提高导入的效率。

    例如,当 UNIQUE_CHECKS=1时:

    mysql> load data infile '/home/mysql/film_test3.txt' into table film_test4; 
    Query OK, 1587168 rows affected (22.92 sec) 
    Records: 1587168  Deleted: 0  Skipped: 0  Warnings: 0 
    

    SET UNIQUE_CHECKS=0时:

    mysql> load data infile '/home/mysql/film_test3.txt' into table film_test4; 
    Query OK, 1587168 rows affected (19.92 sec) 
    Records: 1587168  Deleted: 0  Skipped: 0  Warnings: 0 
    
  3. 如果应用使用自动提交的方式,建议在导入前执行 SET AUTOCOMMIT=0,关闭自动提交,导入结束后再执行 SET AUTOCOMMIT=1,打开自动提交,也可以提高导入的效率。

    例如,当 AUTOCOMMIT=1时:

    mysql> load data infile '/home/mysql/film_test3.txt' into table film_test4; 
    Query OK, 1587168 rows affected (22.92 sec) 
    Records: 1587168  Deleted: 0  Skipped: 0  Warnings: 0 
    

    AUTOCOMMIT=0时:

    mysql> load data infile '/home/mysql/film_test3.txt' into table film_test4; 
    Query OK, 1587168 rows affected (20.87 sec) 
    Records: 1587168  Deleted: 0  Skipped: 0  Warnings: 0 
    

2. 优化 INSERT 语句

当进行数据INSERT的时候,可以考虑采用以下几种优化方式。

  • 如果同时从同一客户插入很多行,尽量使用多个值表的INSERT语句,这种方式将大大缩减客户端与数据库之间的连接、关闭等消耗,使得效率比分开执行的单个INSERT语 快(在一些情况中几倍)。下面是一次插入多值的一个例子:

    insert into test values(1,2),(1,3),(1,4)... 
    
  • 如果从不同客户插入很多行,能通过使用INSERT DELAYED语句得到更高的速度。 DELAYED的含义是让INSERT语句马上执行,其实数据都被放在内存的队列中,并没有真正写入磁盘,这比每条语句分别插入要快的多;LOW_PRIORITY刚好相反,在所有其 他用户对表的读写完后才进行插入

  • 将索引文件和数据文件分在不同的磁盘上存放(利用建表中的选项)

  • 如果进行批量插入,可以增加bulk_insert_buffer_size变量值的方法来提高速度,但是, 这只能对MyISAM表使用;

  • 当从一个文本文件装载一个表时,使用LOAD DATA INFILE。这通常比使用很多INSERT语句快20倍。

3. 优化 GROUP BY 语句

默认情况下,MySQL对所有GROUP BY col1,col2....的字段进行排序。这与在查询中指定 ORDER BY col1,col2...类似。因此,如果显式包括一个包含相同的列的ORDER BY子句,则对MySQL的实际执行性能没有什么影响。

如果查询包括GROUP BY但用户想要避免排序结果的消耗,则可以指定ORDER BY NULL 禁止排序,如下面的例子:

mysql> explain select id,sum(moneys) from sales2 group by id\G; 
*************************** 1. row *************************** 
           id: 1 
  select_type: SIMPLE 
        table: sales2 
         type: ALL 
possible_keys: NULL 
          key: NULL 
      key_len: NULL 
          ref: NULL 
         rows: 1000 
        Extra: Using temporary; Using filesort 
1 row in set (0.00 sec) 

mysql> explain select id,sum(moneys) from sales2 group by id order by null\G; 
*************************** 1. row *************************** 
           id: 1 
  select_type: SIMPLE 
        table: sales2 
         type: ALL 
possible_keys: NULL 
          key: NULL 
      key_len: NULL 
          ref: NULL 
         rows: 1000 
        Extra: Using temporary; 
1 row in set (0.00 sec) 

从上面的例子可以看出第一个SQL语句需要进行 “filesort” , 而第二个SQL由于ORDER BY NULL 不需要进行“filesort” ,而filesort往往非常耗费时间。

4. 优化 ORDER BY 语句

在某些情况中, MySQL可以使用一个索引来满足ORDER BY子句, 而不需要额外的排序。 WHERE条件和ORDER BY使用相同的索引,并且ORDER BY的顺序和索引顺序相同,并且 ORDER BY的字段都是升序或者都是降序。

例如,下列SQL可以使用索引。

SELECT * FROM t1 ORDER BY key_part1,key_part2,... ; 
SELECT * FROM t1 WHERE key_part1=1 ORDER BY key_part1 DESC, key_part2 DESC; 
SELECT * FROM t1 ORDER BY key_part1 DESC, key_part2 DESC; 

但是在以下几种情况下则不使用索引:

SELECT * FROM t1 ORDER BY key_part1 DESC, key_part2 ASC;  
--order by的字段混合ASC和DESC 
SELECT * FROM t1 WHERE key2=constant ORDER BY key1;  
--用于查询行的关键字与ORDER BY中所使用的不相同 
SELECT * FROM t1 ORDER BY key1, key2;  
--对不同的关键字使用ORDER BY: 

5. 优化嵌套查询

MySQL 4.1开始支持SQL的子查询。这个技术可以使用SELECT语句来创建一个单列的查询结果,然后把这个结果作为过滤条件用在另一个查询中。使用子查询可以一次性地完成很 多逻辑上需要多个步骤才能完成的SQL操作,同时也可以避免事务或者表锁死,并且写起 来也很容易。但是,有些情况下,子查询可以被更有效率的连接(JOIN)替代。

在下面的例子中, 要从sales2表中找到那些在company2表中不存在的所有公司的信息:

mysql> explain select * from sales2 where company_id not in ( select id from company2 )\G; 
*************************** 1. row *************************** 
           id: 1 
  select_type: PRIMARY 
        table: sales2 
         type: ALL 
possible_keys: NULL 
          key: NULL 
      key_len: NULL 
          ref: NULL 
         rows: 1000 
        Extra: Using where
*************************** 2. row *************************** 
           id: 2
  select_type: DEPENDENT SUBQUERY  
        table: company2 
         type: index_subquery 
possible_keys: ind_company2_id 
          key: ind_company2_id 
      key_len: 5
          ref: func 
         rows: 2 
        Extra: Using index
2 rows in set (0.00 sec) 

如果使用连接(JOIN)来完成这个查询工作,速度将会快很多。尤其是当company2表中对id建有索引的话,性能将会更好,具体查询如下:

mysql> explain select * from sales2 left join company2 on sales2.company_id = company2.id where sales2.company_id is null\G; 
*************************** 1. row *************************** 
           id: 1 
  select_type: SIMPLE 
        table: sales2 
         type: ref 
possible_keys: ind_sales2_companyid_moneys 
          key: ind_sales2_companyid_moneys 
      key_len: 5 
          ref: const 
         rows: 1 
        Extra: Using where
*************************** 2. row *************************** 
           id: 1
  select_type: SIMPLE
        table: ref
         type: index_subquery 
possible_keys: ind_company2_id 
          key: ind_company2_id 
      key_len: 5
          ref: sakila.sales2.company_id 
         rows: 2 
        Extra:
2 rows in set (0.00 sec) 

从执行计划中可以明显看出查询扫描的记录范围和使用索引的情况都有了很大的改善。 连接(JOIN)之所以更有效率一些,是因为MySQL 不需要在内存中创建临时表来完成这个逻辑上的需要两个步骤的查询工作。

6. MySQL 如何优化 OR 条件

对于含有 OR 的查询子句, 如果要利用索引, 则 OR 之间的每个条件列都必须用到索引; 如果没有索引,则应该考虑增加索引。

7. 使用 SQL 提示

SQL 提示(SQL HINT)是优化数据库的一个重要手段,简单来说就是在 SQL 语句中加入一些 人为的提示来达到优化操作的目的。

下面是一个使用 SQL 提示的例子:

SELECT SQL_BUFFER_RESULTS * FROM... 

这个语句将强制 MySQL 生成一个临时结果集。只要临时结果集生成后,所有表上的锁定均被释放。 这能在遇到表锁定问题时或要花很长时间将结果传给客户端时有所帮助,因为 可以尽快释放锁资源。

下面是一些在 MySQL 中常用的 SQL 提示。

7.1 USE INDEX

在查询语句中表名的后面,添加 USE INDEX 来提供希望 MySQL 去参考的索引列表,就可以让 MySQL 不再考虑其他可用的索引。

mysql> explain select * from sales2 use index (ind_sales2_id) where id = 3\G; 
*************************** 1. row *************************** 
           id: 1 
  select_type: SIMPLE 
        table: sales2 
         type: ref
possible_keys: ind_sales2_id 
          key: ind_sales2_id 
      key_len: 5 
          ref: const 
         rows: 1
        Extra: Using where 
1 row in set (0.00 sec) 

7.2 IGNORE INDEX

如果用户只是单纯地想让 MySQL 忽略一个或者多个索引,则可以使用 IGNORE INDEX 作 为 HINT。同样是上面的例子,这次来看一下查询过程忽略索引 ind_sales2_id的情况:

mysql> explain select * from sales2 ignore index (ind_sales2_id) where id = 3\G; 
*************************** 1. row *************************** 
           id: 1 
  select_type: SIMPLE 
        table: sales2 
         type: ALL
possible_keys: NULL 
          key: NULL 
      key_len: NULL 
          ref: NULL 
         rows: 1000
        Extra: Using where 
1 row in set (0.00 sec) 

从执行计划可以看出,系统忽略了指定的索引,而使用了全表扫描

7.3 FORCE INDEX

为强制 MySQL 使用一个特定的索引,可在查询中使用 FORCE INDEX 作为 HINT。例如, 当不强制使用索引的时候,因为 id 的值都是大于 0 的,因此 MySQL 会默认进行全表扫描, 而不使用索引,如下所示:

mysql> explain select * from sales2 where id  > 0 \G; 
*************************** 1. row *************************** 
           id: 1 
  select_type: SIMPLE 
        table: sales2 
         type: ALL
possible_keys: ind_sales2_id 
          key: NULL 
      key_len: NULL 
          ref: NULL 
         rows: 1000
        Extra: Using where 
1 row in set (0.00 sec) 

但是,当使用FORCE INDEX 进行提示时,即便使用索引的效率不是最高,MySQL 还是选择使用了索引,这是 MySQL 留给用户的一个自行选择执行计划的权力。加入 FORCE INDEX 提示后再次执行上面的 SQL

mysql> explain select * from sales2 force index (ind_sales2_id) where id > 0 \G; 
*************************** 1. row *************************** 
           id: 1 
  select_type: SIMPLE 
        table: sales2 
         type: range
possible_keys: ind_sales2_id 
          key: ind_sales2_id 
      key_len: 5 
          ref: NULL 
         rows: 1000
        Extra: Using where 
1 row in set (0.00 sec) 

你可能感兴趣的:(SQL)