首先来说明一下派生表?

外部的表查询的结果集是从子查询中生成的.如下形式:

select ... from (select ....) dt

如上形式中括号中的查询的结果作为外面select语句的查询源,派生表必须指定别名,因此后面的dt必须指定。派生表和临时表差不多,但是在select语句中派生表比临时表要容易,因为派生表不用创建。

一个有关派生表优化的实例。

开发同事发来一个sql优化,涉及到4张表,表中的数据都不是很大,但是查询起来真的很慢。服务器性能又差,查询总是超时。

四张表的表结构如下:

  1. Table: t_info_setting
  2. Create Table: CREATE TABLE `t_info_setting` (
  3. `id` int(11) NOT NULL AUTO_INCREMENT,
  4. `parent_key` varchar(32) NOT NULL,
  5. `column_name` varchar(32) NOT NULL,
  6. `column_key` varchar(32) NOT NULL,
  7. `storage_way` tinyint(4) DEFAULT '',
  8. `check_way` tinyint(4) DEFAULT '',
  9. `remark` varchar(500) DEFAULT '',
  10. `operator` varchar(128) DEFAULT '',
  11. `status` int(11) DEFAULT '',
  12. `update_time` datetime DEFAULT CURRENT_TIMESTAMP ON UPDATE CURRENT_TIMESTAMP,
  13. PRIMARY KEY (`id`),
  14. KEY `column_key` (`column_key`)
  15. ) ENGINE=InnoDB AUTO_INCREMENT=9 DEFAULT CHARSET=utf8

t_info_setting

  1. Table: t_articles_status
  2. Create Table: CREATE TABLE `t_articles_status` (
  3. `id` int(11) unsigned NOT NULL AUTO_INCREMENT,
  4. `linkId` varchar(36) NOT NULL,
  5. `column_key` varchar(32) NOT NULL,
  6. `status` int(11) DEFAULT '',
  7. `operator_time` timestamp NULL DEFAULT CURRENT_TIMESTAMP ON UPDATE CURRENT_TIMESTAMP,
  8. PRIMARY KEY (`id`),
  9. KEY `article_status` (`linkId`,`column_key`)
  10. ) ENGINE=InnoDB AUTO_INCREMENT=22232 DEFAULT CHARSET=utf8
  11. 1 row in set (0.00 sec)

t_articles_status

  1. Table: t_article_operations
  2. Create Table: CREATE TABLE `t_article_operations` (
  3. `id` int(11) NOT NULL AUTO_INCREMENT,
  4. `linkId` varchar(36) NOT NULL,
  5. `column_key` varchar(32) NOT NULL DEFAULT '',
  6. `type` varchar(16) DEFAULT '',
  7. `operator` varchar(128) DEFAULT '',
  8. `operator_time` timestamp NULL DEFAULT CURRENT_TIMESTAMP ON UPDATE CURRENT_TIMESTAMP,
  9. PRIMARY KEY (`id`),
  10. KEY `article_operation` (`linkId`,`column_key`),
  11. KEY `operator_time` (`operator_time`)
  12. ) ENGINE=InnoDB AUTO_INCREMENT=23316 DEFAULT CHARSET=utf8
  13. 1 row in set (0.00 sec)

t_article_operations

  1. Table: t_articles
  2. Create Table: CREATE TABLE `t_articles` (
  3. `id` int(11) unsigned NOT NULL AUTO_INCREMENT,
  4. `linkId` varchar(36) DEFAULT NULL,
  5. `source` int(11) DEFAULT '',
  6. `title` varchar(150) NOT NULL,
  7. `author` varchar(150) NOT NULL,
  8. `tags` varchar(200) DEFAULT NULL,
  9. `abstract` varchar(512) DEFAULT NULL,
  10. `content` mediumtext,
  11. `thumbnail` varchar(256) DEFAULT NULL,
  12. `sourceId` varchar(24) DEFAULT '',
  13. `accessoryUrl` text,
  14. `relatedStock` text,
  15. `contentUrl` text,
  16. `secuInfo` text,
  17. `market` varchar(10) DEFAULT 'hk',
  18. `code` varchar(10) DEFAULT '',
  19. `updator` varchar(64) DEFAULT '',
  20. `createTime` timestamp NULL DEFAULT CURRENT_TIMESTAMP COMMENT '建立时间',
  21. `updateTime` timestamp NULL DEFAULT CURRENT_TIMESTAMP ON UPDATE CURRENT_TIMESTAMP COMMENT '更新时间',
  22. PRIMARY KEY (`id`),
  23. UNIQUE KEY `linkId` (`linkId`)
  24. ) ENGINE=InnoDB AUTO_INCREMENT=15282 DEFAULT CHARSET=utf8

t_articles

上面四张表,由上面的自增字段的值可以知道表的数据并不是很大,最大的表也就2万多行,表中的索引情况已经一目了然。开发同学给出的sql语句如下:

  1. (
  2. SELECT
  3. 'daily' AS category,
  4. e.linkId,
  5. e.title,
  6. e.updateTime
  7. FROM
  8. (
  9. SELECT DISTINCT
  10. b.column_key,
  11. b. STATUS,
  12. b.linkId
  13. FROM
  14. t_info_setting a
  15. inner JOIN t_articles_status b ON a.column_key = b.column_key
  16. inner JOIN t_article_operations c ON b.column_key = c.column_key
  17. WHERE
  18. a.parent_key = 'daily'
  19. AND a. STATUS = 1
  20. AND b. STATUS = 80000
  21. ORDER BY
  22. c.operator_time DESC
  23. LIMIT 1
  24. ) AS d
  25. inner JOIN t_articles e ON d.linkId = e.linkId
  26. )
  27. UNION ALL
  28. (
  29. SELECT
  30. 'ipo' AS category,
  31. e.linkId,
  32. e.title,
  33. e.updateTime
  34. FROM
  35. (
  36. SELECT DISTINCT
  37. b.column_key,
  38. b. STATUS,
  39. b.linkId
  40. FROM
  41. t_info_setting a
  42. inner JOIN t_articles_status b ON a.column_key = b.column_key
  43. inner JOIN t_article_operations c ON b.column_key = c.column_key
  44. WHERE
  45. a.parent_key = 'ipo'
  46. AND a. STATUS = 1
  47. AND b. STATUS = 80000
  48. ORDER BY
  49. c.operator_time DESC
  50. LIMIT 1
  51. ) AS d
  52. inner JOIN t_articles e ON d.linkId = e.linkId
  53. )
  54. UNION ALL
  55. (
  56. SELECT
  57. 'research' AS category,
  58. e.linkId,
  59. e.title,
  60. e.updateTime
  61. FROM
  62. (
  63. SELECT DISTINCT
  64. b.column_key,
  65. b. STATUS,
  66. b.linkId
  67. FROM
  68. t_info_setting a
  69. inner JOIN t_articles_status b ON a.column_key = b.column_key
  70. inner JOIN t_article_operations c ON b.column_key = c.column_key
  71. WHERE
  72. a.parent_key = 'research'
  73. AND a. STATUS = 1
  74. AND b. STATUS = 80000
  75. ORDER BY
  76. c.operator_time DESC
  77. LIMIT 1
  78. ) AS d
  79. inner JOIN t_articles e ON d.linkId = e.linkId
  80. )
  81. UNION ALL
  82. (
  83. SELECT
  84. 'news' AS category,
  85. e.linkId,
  86. e.title,
  87. e.updateTime
  88. FROM
  89. (
  90. SELECT DISTINCT
  91. b.column_key,
  92. b. STATUS,
  93. b.linkId
  94. FROM
  95. t_info_setting a
  96. inner JOIN t_articles_status b ON a.column_key = b.column_key
  97. inner JOIN t_article_operations c ON b.column_key = c.column_key
  98. WHERE
  99. a.parent_key = 'news'
  100. AND a. STATUS = 1
  101. AND b. STATUS = 80000
  102. ORDER BY
  103. c.operator_time DESC
  104. LIMIT 1
  105. ) AS d
  106. inner JOIN t_articles e ON d.linkId = e.linkId
  107. )

开发给的sql

原sql很长大概有107行,但是分析这条sql发现了使用了三个union联合查询,然后每条联合的sql语句基本是一模一样的,只是改变了a.parent_key = 'research'这个条件。这说明我们只需要分析其中的一条sql即可。

  1. SELECT
  2. 'research' AS category,
  3. e.linkId,
  4. e.title,
  5. e.updateTime
  6. FROM
  7. ( -- 这里使用了派生表
  8. SELECT DISTINCT --a
  9. b.column_key,
  10. b. STATUS,
  11. b.linkId
  12. FROM
  13. t_info_setting a
  14. inner JOIN t_articles_status b ON a.column_key = b.column_key
  15. inner JOIN t_article_operations c ON b.column_key = c.column_key -- c
  16. WHERE
  17. a.parent_key = 'research'
  18. AND a. STATUS = 1
  19. AND b. STATUS = 80000
  20. ORDER BY
  21. c.operator_time DESC
  22. LIMIT 1
  23. ) AS d
  24. inner JOIN t_articles e ON d.linkId = e.linkId -- b

首先:这条sql语句中使用了派生表,分析里面的子查询,最后有一个limit 1也就是只查出一条数据,并且是按照operator_time 进行排序,那么distinct的去重复就是不需要的。再看子查询中查询出了三个字段,但是在b处和e表进行联合查询的时候只使用了linkId 这一个字段,因此子查询中多余的两个字段需要去掉。

在表t_article_operations上有一个符合索引,我们知道mysql在使用复合索引时,采用最左原则,因此在c处的联合查询我们需要加上linkId ,根据上面分析,改写sql如下:

  1. select
  2. 'research' as category,
  3. e.linkId,
  4. e.title,
  5. e.updateTime
  6. from (
  7. select b.linkId -- 去除不必要的列、distinct操作
  8. from t_info_setting a
  9. inner join t_articles_status b
  10. on a.column_key=b.column_key
  11. inner join t_article_operations c
  12. on b.linkId=c.linkId and b.column_key=c.column_key -- 关联条件应包含linkId
  13. where
  14. a.parent_key='research'
  15. and a.status=1
  16. and b.status=80000
  17. order by c.operator_time desc
  18. limit 1
  19. ) d
  20. inner join t_articles e
  21. on d.linkId=e.linkId;

然后查看下改写前后两个sql的执行计划。

改写后的执行计划:

  1. +----+-------------+------------+--------+-------------------+----------------+---------+-----------------------------------------------------------+-------+-------------+
  2. | id | select_type | table | type | possible_keys | key | key_len | ref | rows | Extra |
  3. +----+-------------+------------+--------+-------------------+----------------+---------+-----------------------------------------------------------+-------+-------------+
  4. | 1 | PRIMARY | <derived2> | system | NULL | NULL | NULL | NULL | 1 | NULL |
  5. | 1 | PRIMARY | e | const | linkId | linkId | 111 | const | 1 | NULL |
  6. | 2 | DERIVED | c | index | article_operation | operator_time | 5 | NULL | 14711 | NULL |
  7. | 2 | DERIVED | a | ref | column_key | column_key | 98 | wlb_live_contents.c.column_key | 1 | Using where |
  8. | 2 | DERIVED | b | ref | article_status | article_status | 208 | wlb_live_contents.c.linkId,wlb_live_contents.c.column_key | 1 | Using where |
  9. +----+-------------+------------+--------+-------------------+----------------+---------+-----------------------------------------------------------+-------+-------------+

改写之后的单个sql很快就有了结果,大概0.12秒就有了结束,而原来的sql会超时结束的。

在原sql语句中使用了union,因为我们最后的结果并不要求去重复,只是四个结果集的联合,因此这里我们可以使用union all代替上面的union。

改写后的执行计划DERIVED表示使用了派生表,我们看到在e表与派生表进行inner查询的使用了索引。

分析:

之前看到一种说法是,在数据表和派生表联合进行查询时,不能使用索引,但是上面的的执行计划说明使用了索引(e表和派生表联合查询,e表使用了索引)。【究竟要怎么用还需进一步研究】

改写sql:

上面使用了派生表,其实数据量比较大时,派生表的效率并不是很高的,上面的查询我们试着用4张表的联合查询来改写。

改写之后的sql如下:

  1. SELECT
  2. 'research' AS category,
  3. e.linkId,
  4. e.title,
  5. e.updateTime
  6. FROM
  7. t_info_setting a
  8. INNER JOIN t_articles_status b ON a.column_key = b.column_key
  9. INNER JOIN t_article_operations c ON b.linkId = c.linkId
  10. AND b.column_key = c.column_key
  11. INNER JOIN t_articles e ON c.linkId = e.linkId
  12. WHERE
  13. a.parent_key = 'research'
  14. AND a. STATUS = 1
  15. AND b. STATUS = 80000
  16. ORDER BY
  17. c.operator_time DESC
  18. LIMIT 1

查看执行计划:

  1. +----+-------------+-------+-------+-------------------+----------------+---------+-----------------------------------------------------------+------+-------------+
  2. | id | select_type | table | type | possible_keys | key | key_len | ref | rows | Extra |
  3. +----+-------------+-------+-------+-------------------+----------------+---------+-----------------------------------------------------------+------+-------------+
  4. | 1 | SIMPLE | c | index | article_operation | operator_time | 5 | NULL | 1 | NULL |
  5. | 1 | SIMPLE | a | ref | column_key | column_key | 98 | wlb_live_contents.c.column_key | 1 | Using where |
  6. | 1 | SIMPLE | b | ref | article_status | article_status | 208 | wlb_live_contents.c.linkId,wlb_live_contents.c.column_key | 1 | Using where |
  7. | 1 | SIMPLE | e | ref | linkId | linkId | 111 | wlb_live_contents.c.linkId | 1 | NULL |
  8. +----+-------------+-------+-------+-------------------+----------------+---------+-----------------------------------------------------------+------+-------------+

根据执行计划,这个inner join的执行计划是要比上面的使用派生表的执行计划要高一些。

说明:

1:在使用联合查询的时候,可以考虑联合查询的键上创建索引,效率可能会高点。

2:可以考虑在order by的键上创建索引。

3:根据数据可以知道,t_article_operations本质上是一个流水表,记录日志类信息,不应出现在日常查询中。解决此种查询的办法:operator_time保存在t_articles_status中,查询彻底移除t_article_operations,或临时方法:t_article_operations只保留短期数据,历史记录定期迁移至其他表。

sql优化-派生表与inner-join的更多相关文章

  1. 神奇的 SQL 之 联表细节 → MySQL JOIN 的执行过程(二)

    开心一刻 一头母牛在吃草,突然一头公牛从远处狂奔而来说:“快跑啊!!楼主来了!” 母牛说:“楼主来了关我屁事啊?” 公牛急忙说:“楼主吹牛逼呀!” 母牛大惊,拔腿就跑,边跑边问:“你是公牛你怕什么啊? ...

  2. MYSQL优化派生表(子查询)在From语句中的

    Mysql 在5.6.3中,优化器更有效率地处理派生表(在from语句中的子查询): 优化器推迟物化子查询在from语句中的子查询,知道子查询的内容在查询正真执行需要时,才开始物化.这一举措提高了性能 ...

  3. 神奇的 SQL 之 联表细节 → MySQL JOIN 的执行过程(一)

    开心一刻 我:嗨,老板娘,有冰红茶没 老板娘:有 我:多少钱一瓶 老板娘:3块 我:给我来一瓶,给,3块 老板娘:来,你的冰红茶 我:玩呐,我要冰红茶,你给我个瓶盖干哈? 老板娘:这是再来一瓶,我家卖 ...

  4. 神奇的 SQL 之 联表细节 → MySQL JOIN 的执行过程

    问题背景 对于 MySQL 的 JOIN,不知道大家有没有去想过他的执行流程,亦或有没有怀疑过自己的理解(自信满满的自我认为!):如果大家不知道怎么检验,可以试着回答如下的问题 驱动表的选择 MySQ ...

  5. Sql优化(一) Merge Join vs. Hash Join vs. Nested Loop

    原创文章,首发自本人个人博客站点,转载请务必注明出自http://www.jasongj.com Nested Loop,Hash Join,Merge Join介绍 Nested Loop: 对于被 ...

  6. SQL优化单表案例

    数据准备: -- 创建数据库 mysql> create database db_index_case; Query OK, row affected (0.00 sec) -- 查看数据库 m ...

  7. SQL优化之表连接方式

    1.嵌套循环(DESTED LOOPS) Note:嵌套循环被驱动表必须走索引,而且索引只能走INDEX UNIQUE SCAN或者INDEX RANGE SCAN SQL> select /* ...

  8. (1.12)SQL优化——mysql表名、库名大小写敏感

    mysql表名.库名大小写敏感 关键词:mysql大小写敏感

  9. SQL优化之索引分析

    索引的重要性 数据库性能优化中索引绝对是一个重量级的因素,可以说,索引使用不当,其它优化措施将毫无意义. 聚簇索引(Clustered Index)和非聚簇索引 (Non- Clustered Ind ...

随机推荐

  1. diff()函数

    1 diff()是将原来的数据减去移动后的数据. 在numpy和pandas中都能调用. pandas的调用方法: import pandas as pd df = pd.DataFrame( {'a ...

  2. Windows命令集锦

    1.用于私网的IP地址段: 10.0.0.0/8: 10.0.0.0-10.255.255.255 172.16.0.0/12: 172.16.0.0-172.31.255.255 192.168.0 ...

  3. oracle-不完全数据库恢复-被动恢复-ORA-00313/ORA-00366

    继上一篇不完全恢复 oracle-不完全数据库恢复-被动恢复-ORA-00313/ORA-00366 场景2:数据库拥有备份,CURRENT状态日志组中所有的在线日志头损坏,在发生日志切换时实例被自动 ...

  4. python2和python3中split的坑

    执行同样的split,python2和python3截取的行数内容却不一样 我想要截取Dalvik Heap行,使用split('\n')的方法 import os cpu='adb shell du ...

  5. 操作系统(5)实验0——makefile的写法

    之前GCC那部分我提到过,gcc啥啥啥啥傻傻的那个指令只能够编译简单的代码,如果要干大事(例如突然心血来潮写个c开头的神经网络库之类的),还是要写Makefile来编译.其实在Windows下通常用I ...

  6. 修改jupyter notebook默认路径,亲测

    anaconda环境 任务栏中找到anaconda/jupyter notebook,鼠标右键属性 点击确认即可.

  7. css简介和属性

    CSS指的是层叠样式表(Cascading Style Sheets) 样式定义如何显示HTML元素,通常存储在样式表中. css使用方式 内联式 <!DOCTYPE html> < ...

  8. Docker中的Dockerfile命令详解FROM RUN COPY ADD ENTRYPOINT...

    Dockerfile指令 这些建议旨在帮助您创建高效且可维护的Dockerfile. FROM FROM指令的Dockerfile引用 尽可能使用当前的官方图像作为图像的基础.我们推荐Alpine图像 ...

  9. MySQL-快速入门(3)运算符

    1.常见的运算符:算术运算符.比较运算符.逻辑运算符.位运算符. 算术运算符:+.-.*./.%(求余). 比较运算符:>.<.=.>=.<=.!=.in.between an ...

  10. JDK安装中配置Path无效解决办法

    1. 问题 在安装jdk后,配置完环境变量,然后在控制台输入java -version出现与安装版本不一致的版本,如安装1.8后version仍显示1.7,即:修改环境变量没有生效且原先存在安装过的J ...