首先来说明一下派生表?

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

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

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

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

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

四张表的表结构如下:

       Table: t_info_setting
Create Table: CREATE TABLE `t_info_setting` (
`id` int(11) NOT NULL AUTO_INCREMENT,
`parent_key` varchar(32) NOT NULL,
`column_name` varchar(32) NOT NULL,
`column_key` varchar(32) NOT NULL,
`storage_way` tinyint(4) DEFAULT '',
`check_way` tinyint(4) DEFAULT '',
`remark` varchar(500) DEFAULT '',
`operator` varchar(128) DEFAULT '',
`status` int(11) DEFAULT '',
`update_time` datetime DEFAULT CURRENT_TIMESTAMP ON UPDATE CURRENT_TIMESTAMP,
PRIMARY KEY (`id`),
KEY `column_key` (`column_key`)
) ENGINE=InnoDB AUTO_INCREMENT=9 DEFAULT CHARSET=utf8

t_info_setting

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

t_articles_status

       Table: t_article_operations
Create Table: CREATE TABLE `t_article_operations` (
`id` int(11) NOT NULL AUTO_INCREMENT,
`linkId` varchar(36) NOT NULL,
`column_key` varchar(32) NOT NULL DEFAULT '',
`type` varchar(16) DEFAULT '',
`operator` varchar(128) DEFAULT '',
`operator_time` timestamp NULL DEFAULT CURRENT_TIMESTAMP ON UPDATE CURRENT_TIMESTAMP,
PRIMARY KEY (`id`),
KEY `article_operation` (`linkId`,`column_key`),
KEY `operator_time` (`operator_time`)
) ENGINE=InnoDB AUTO_INCREMENT=23316 DEFAULT CHARSET=utf8
1 row in set (0.00 sec)

t_article_operations

       Table: t_articles
Create Table: CREATE TABLE `t_articles` (
`id` int(11) unsigned NOT NULL AUTO_INCREMENT,
`linkId` varchar(36) DEFAULT NULL,
`source` int(11) DEFAULT '',
`title` varchar(150) NOT NULL,
`author` varchar(150) NOT NULL,
`tags` varchar(200) DEFAULT NULL,
`abstract` varchar(512) DEFAULT NULL,
`content` mediumtext,
`thumbnail` varchar(256) DEFAULT NULL,
`sourceId` varchar(24) DEFAULT '',
`accessoryUrl` text,
`relatedStock` text,
`contentUrl` text,
`secuInfo` text,
`market` varchar(10) DEFAULT 'hk',
`code` varchar(10) DEFAULT '',
`updator` varchar(64) DEFAULT '',
`createTime` timestamp NULL DEFAULT CURRENT_TIMESTAMP COMMENT '建立时间',
`updateTime` timestamp NULL DEFAULT CURRENT_TIMESTAMP ON UPDATE CURRENT_TIMESTAMP COMMENT '更新时间',
PRIMARY KEY (`id`),
UNIQUE KEY `linkId` (`linkId`)
) ENGINE=InnoDB AUTO_INCREMENT=15282 DEFAULT CHARSET=utf8

t_articles

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

(
SELECT
'daily' AS category,
e.linkId,
e.title,
e.updateTime
FROM
(
SELECT DISTINCT
b.column_key,
b. STATUS,
b.linkId
FROM
t_info_setting a
inner JOIN t_articles_status b ON a.column_key = b.column_key
inner JOIN t_article_operations c ON b.column_key = c.column_key
WHERE
a.parent_key = 'daily'
AND a. STATUS = 1
AND b. STATUS = 80000
ORDER BY
c.operator_time DESC
LIMIT 1
) AS d
inner JOIN t_articles e ON d.linkId = e.linkId
)
UNION ALL
(
SELECT
'ipo' AS category,
e.linkId,
e.title,
e.updateTime
FROM
(
SELECT DISTINCT
b.column_key,
b. STATUS,
b.linkId
FROM
t_info_setting a
inner JOIN t_articles_status b ON a.column_key = b.column_key
inner JOIN t_article_operations c ON b.column_key = c.column_key
WHERE
a.parent_key = 'ipo'
AND a. STATUS = 1
AND b. STATUS = 80000
ORDER BY
c.operator_time DESC
LIMIT 1
) AS d
inner JOIN t_articles e ON d.linkId = e.linkId
)
UNION ALL
(
SELECT
'research' AS category,
e.linkId,
e.title,
e.updateTime
FROM
(
SELECT DISTINCT
b.column_key,
b. STATUS,
b.linkId
FROM
t_info_setting a
inner JOIN t_articles_status b ON a.column_key = b.column_key
inner JOIN t_article_operations c ON b.column_key = c.column_key
WHERE
a.parent_key = 'research'
AND a. STATUS = 1
AND b. STATUS = 80000
ORDER BY
c.operator_time DESC
LIMIT 1
) AS d
inner JOIN t_articles e ON d.linkId = e.linkId
)
UNION ALL
(
SELECT
'news' AS category,
e.linkId,
e.title,
e.updateTime
FROM
(
SELECT DISTINCT
b.column_key,
b. STATUS,
b.linkId
FROM
t_info_setting a
inner JOIN t_articles_status b ON a.column_key = b.column_key
inner JOIN t_article_operations c ON b.column_key = c.column_key
WHERE
a.parent_key = 'news'
AND a. STATUS = 1
AND b. STATUS = 80000
ORDER BY
c.operator_time DESC
LIMIT 1
) AS d
inner JOIN t_articles e ON d.linkId = e.linkId
)

开发给的sql

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

        SELECT
'research' AS category,
e.linkId,
e.title,
e.updateTime
FROM
( -- 这里使用了派生表
SELECT DISTINCT --a
b.column_key,
b. STATUS,
b.linkId
FROM
t_info_setting a
inner JOIN t_articles_status b ON a.column_key = b.column_key
inner JOIN t_article_operations c ON b.column_key = c.column_key -- c
WHERE
a.parent_key = 'research'
AND a. STATUS = 1
AND b. STATUS = 80000
ORDER BY
c.operator_time DESC
LIMIT 1
) AS d
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如下:

select
'research' as category,
e.linkId,
e.title,
e.updateTime
from (
select b.linkId -- 去除不必要的列、distinct操作
from t_info_setting a
inner join t_articles_status b
on a.column_key=b.column_key
inner join t_article_operations c
on b.linkId=c.linkId and b.column_key=c.column_key -- 关联条件应包含linkId
where
a.parent_key='research'
and a.status=1
and b.status=80000
order by c.operator_time desc
limit 1
) d
inner join t_articles e
on d.linkId=e.linkId;

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

改写后的执行计划:

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

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

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

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

分析:

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

改写sql:

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

改写之后的sql如下:

SELECT
'research' AS category,
e.linkId,
e.title,
e.updateTime
FROM
t_info_setting a
INNER JOIN t_articles_status b ON a.column_key = b.column_key
INNER JOIN t_article_operations c ON b.linkId = c.linkId
AND b.column_key = c.column_key
INNER JOIN t_articles e ON c.linkId = e.linkId
WHERE
a.parent_key = 'research'
AND a. STATUS = 1
AND b. STATUS = 80000
ORDER BY
c.operator_time DESC
LIMIT 1

查看执行计划:

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

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

说明:

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

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

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

最新文章

  1. 以项目谈WebGIS中Web制图的设计和实现
  2. 数位DP HDU2089
  3. 《objective-c基础教程》学习笔记(十)—— 内存管理
  4. ASCII Table/ASCII表
  5. 上次的AJAX定时刷新多ID不正确,这次请教了高手之后补全
  6. 电脑打不开网页,使用dns优化下就可以了。
  7. Vector集合
  8. 浅析单点登录,以及不同二级域名下的SSO实现
  9. 2016.3.30 OneZero站立会议
  10. python循环语句与其他编程语言不同之处
  11. Percona XtraDB Cluster 的一些使用限制(PXC 5.7)
  12. hadoop2.4集群的搭建
  13. tomcat端口被占用 实际上却查询不到此端口
  14. 前端hash路由基本原理,及代码的基本实现
  15. API和正则表达式
  16. 【图像处理】Schmid滤波器
  17. Restful和WeBAPI学习笔记
  18. 使用ubuntu搭建时间机器备份服务
  19. jQuery 效果 - slideToggle() 方法
  20. scanf在竞赛中的技巧总结ing

热门文章

  1. oracle 日期格式操作
  2. 使用Sklearn构建朴素贝叶斯分类器-新闻分类
  3. python+selenium模拟鼠标操作
  4. docker配置文件不生效
  5. 获取客户机MAC地址 根据IP地址 获取机器的MAC地址 / 获取真实Ip地址
  6. 洛谷 P1631 序列合并(优先队列)
  7. 不能将X*类型的值分配到X*类型的实体问题的解决方法
  8. TypeScript ES6-Promise 递归遍历文件夹中的文件
  9. 什么是lambda函数?有什么好处?
  10. Python字符串中添加、插入特定字符