sql优化-派生表与inner-join
首先来说明一下派生表?
外部的表查询的结果集是从子查询中生成的.如下形式:
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只保留短期数据,历史记录定期迁移至其他表。
sql优化-派生表与inner-join的更多相关文章
- 神奇的 SQL 之 联表细节 → MySQL JOIN 的执行过程(二)
开心一刻 一头母牛在吃草,突然一头公牛从远处狂奔而来说:“快跑啊!!楼主来了!” 母牛说:“楼主来了关我屁事啊?” 公牛急忙说:“楼主吹牛逼呀!” 母牛大惊,拔腿就跑,边跑边问:“你是公牛你怕什么啊? ...
- MYSQL优化派生表(子查询)在From语句中的
Mysql 在5.6.3中,优化器更有效率地处理派生表(在from语句中的子查询): 优化器推迟物化子查询在from语句中的子查询,知道子查询的内容在查询正真执行需要时,才开始物化.这一举措提高了性能 ...
- 神奇的 SQL 之 联表细节 → MySQL JOIN 的执行过程(一)
开心一刻 我:嗨,老板娘,有冰红茶没 老板娘:有 我:多少钱一瓶 老板娘:3块 我:给我来一瓶,给,3块 老板娘:来,你的冰红茶 我:玩呐,我要冰红茶,你给我个瓶盖干哈? 老板娘:这是再来一瓶,我家卖 ...
- 神奇的 SQL 之 联表细节 → MySQL JOIN 的执行过程
问题背景 对于 MySQL 的 JOIN,不知道大家有没有去想过他的执行流程,亦或有没有怀疑过自己的理解(自信满满的自我认为!):如果大家不知道怎么检验,可以试着回答如下的问题 驱动表的选择 MySQ ...
- Sql优化(一) Merge Join vs. Hash Join vs. Nested Loop
原创文章,首发自本人个人博客站点,转载请务必注明出自http://www.jasongj.com Nested Loop,Hash Join,Merge Join介绍 Nested Loop: 对于被 ...
- SQL优化单表案例
数据准备: -- 创建数据库 mysql> create database db_index_case; Query OK, row affected (0.00 sec) -- 查看数据库 m ...
- SQL优化之表连接方式
1.嵌套循环(DESTED LOOPS) Note:嵌套循环被驱动表必须走索引,而且索引只能走INDEX UNIQUE SCAN或者INDEX RANGE SCAN SQL> select /* ...
- (1.12)SQL优化——mysql表名、库名大小写敏感
mysql表名.库名大小写敏感 关键词:mysql大小写敏感
- SQL优化之索引分析
索引的重要性 数据库性能优化中索引绝对是一个重量级的因素,可以说,索引使用不当,其它优化措施将毫无意义. 聚簇索引(Clustered Index)和非聚簇索引 (Non- Clustered Ind ...
随机推荐
- C# Setting.settings . 用法
1.定义 在Settings.settings文件中定义配置字段.把作用范围定义为:User则运行时可更改,Applicatiion则运行时不可更改.可以使用数据网格视图,很方便: 2.读取配置值 t ...
- WPF DevExpress Chart控件 界面绑定数据源,不通过C#代码进行绑定
<Grid x:Name="myGrid" Loaded="Grid_Loaded" DataContext="{Binding PartOne ...
- Apache web服务器(LAMP架构)
1.apache介绍 1).世界上使用率最高的网站服务器,最高时可达70%:官方网站:apache.org 2).http 超文本协议 HTML 超文本标记语言 3).URL 统一资源定位符 http ...
- 工作中经常用到 github 上优秀、实用、轻量级、无依赖的插件和库
原文收录在 GitHub博客 ( https://github.com/jawil/blog ) ,喜欢的可以关注最新动态,大家一起多交流学习,共同进步,以学习者的身份写博客,记录点滴. 由于gith ...
- LeetCode.876-链表的中间节点(Middle of the Linked List)
这是悦乐书的第337次更新,第361篇原创 01 看题和准备 今天介绍的是LeetCode算法题中Easy级别的第206题(顺位题号是876).给定具有头节点的非空单链表,返回链表的中间节点.如果有两 ...
- (转载)深入解析String#intern
本文转载自:深入解析String#intern 引言 在 JAVA 语言中有8中基本类型和一种比较特殊的类型String.这些类型为了使他们在运行过程中速度更快,更节省内存,都提供了一种常量池的概念. ...
- kafak学习(一)
发布与订阅消息系统. 数据(消息)的发送者不会直接把消息发送给接受者,这是发布与订阅消息系统的一个特点.发布者以某种方式对消息进行分类,接受者订阅他们,以便接受特定类型的消息.发布与订阅系统一般会有一 ...
- ssh-config的使用
使用SSH的配置文件可以在很大程度上方便各种操作,特别适应于有多个SSH帐号.使用非标准端口或者写脚本等情况. man ssh_config 可以查看手册 如果之前是用密码方式来登录SSH,需要先改用 ...
- 第9周总结&实验报告7
完成火车站售票程序的模拟. 要求:(1)总票数1000张:(2)10个窗口同时开始卖票:(3)卖票过程延时1秒钟:(4)不能出现一票多卖或卖出负数号票的情况.一:实验代码 package first; ...
- numpy库的认识以及数组的创建
numpy库 numpy是Python数值计算最重要的基础包.大多数提供科学计算的包都是用NumPy的数组作为构建基础.numpy十分高效,基于NumPy的算法要比纯Python快10到100倍(甚至 ...