在 3 个大表上使用内连接优化 SQL 查询

Posted

技术标签:

【中文标题】在 3 个大表上使用内连接优化 SQL 查询【英文标题】:Optimizing SQL query with inner join on 3 big tables 【发布时间】:2017-11-21 20:16:05 【问题描述】:

我有一个 SQL 查询,其中 3 个表连接到一个远程 mysql 数据库

其中两个表的大小约为 15GB(STEP_RESULT 和 meas_numericlimit)

在发送数据之前,会在服务器上创建一个 TMP 表,大约需要 2.5 小时才能结束

我不是服务器管理员,但我可以使用 MySql WorkBench 观察我的查询 此服务器是最新的,具有 64GB 内存

如何优化这个查询?

谢谢

我的查询:

select 
t1.UUT_NAME, 
t1.STATION_NUM, 
t1.START_DATE_TIME, 
t3.LOW_LIMIT, 
t3.DATA,
t3.HIGH_LIMIT,
t3.UNITS,
t2b.STEP_NAME 
from 
meas_numericlimit t3 
    inner join STEP_RESULT t2a on t3.ID = t2a.STEP_ID
    inner join STEP_RESULT t2b on t2a.STEP_PARENT = t2b.STEP_ID
    inner join uut_result t1 on t2b.UUT_RESULT = t1.ID
where 
t1.UUT_NAME like 'Variable1-1%' and 
t1.STATION_NUM = 'variable2' and 
t2b.STEP_NAME = 'variable3' and 
t2b.STEP_TYPE = 'constant'

这里是 SHOW TABLES 和 EXPLAIN 输出查询:

+--------------------+
| Tables_in_spectrum |
+--------------------+
| cal_dates          |
| calibrage          |
| execution_time     |
| meas_numericlimit  |
| station_feature    |
| step_callexe       |
| step_graph         |
| step_msgjnl        |
| step_msgpopup      |
| step_passfail      |
| step_result        |
| step_seqcall       |
| step_stringvalue   |
| syst_event         |
| uptime             |
| users              |
| uut_result         |
+--------------------+

+----+-------------+-------+--------+-------------------------+--------

| id | select_type | table | type   | possible_keys           | key     
| 
+----+-------------+-------+--------+-------------------------+--------

|  1 | SIMPLE      | t2a   | ALL    | NULL                    | NULL    
|
|  1 | SIMPLE      | t3    | eq_ref | PRIMARY                 | PRIMARY 
|
|  1 | SIMPLE      | t2b   | ALL    | NULL                    | NULL    
|
|  1 | SIMPLE      | t1    | eq_ref | PRIMARY,FK_uut_result_1 | PRIMARY 
|
+----+-------------+-------+--------+-------------------------+--------


---------+----------------------+----------- +-------------------------

key_len  | ref                  |    rows    | Extra                     
|
---------+----------------------+----------- +-------------------------
NULL    | NULL                 | 48120004   |                           
|
40      | spectrum.t2a.STEP_ID |        1   |                           
|
 NULL    | NULL                 | 48120004   | Using where; Using join
                                                             buffer  |
40      | spectrum.t2b.UUT_RESULT |       1 | Using where               
|
-------+----------------------+------------+---------------------------

这里显示创建表:

CREATE TABLE `uut_result` (
`ID` varchar(38) NOT NULL DEFAULT '', 
`STATION_NUM` varchar(255) DEFAULT NULL, 
`SOFTVER_ODTGEN` varchar(10) DEFAULT NULL, 
`HARDVER_ODTGEN` varchar(10) DEFAULT NULL, 
`NEXT_CAL_DATE` date DEFAULT NULL, 
`UUT_NAME` varchar(255) DEFAULT NULL, 
`UUT_SERIAL_NUMBER` varchar(255) DEFAULT NULL, 
`UUT_VERSION` varchar(255) DEFAULT NULL, 
`USER_LOGIN_NAME` varchar(255) DEFAULT NULL, 
`USER_LOGIN_LOGIN` varchar(255) NOT NULL DEFAULT '', 
`START_DATE_TIME` datetime DEFAULT NULL, 
`EXECUTION_TIME` float DEFAULT NULL, 
`UUT_STATUS` varchar(255) DEFAULT NULL, 
`UUT_ERROR_CODE` int(11) DEFAULT NULL, 
`UUT_ERROR_MESSAGE` varchar(1023) DEFAULT NULL, 
`PAT_NAME` varchar(255) NOT NULL DEFAULT '', 
`PAT_VERSION` varchar(10) NOT NULL DEFAULT '', 
`TEST_LEVEL` varchar(50) DEFAULT NULL, 
`INTERFACE_ID` int(10) unsigned NOT NULL DEFAULT '0', 
`EXECUTION_MODE` varchar(45) DEFAULT NULL, 
`LOOP_MODE` varchar(45) DEFAULT NULL, 
`STOP_ON_FAIL` tinyint(4) unsigned NOT NULL DEFAULT '0', 
`EXECUTION_COMMENT` text, 
PRIMARY KEY (`ID`), 
KEY `FK_uut_result_1` (`STATION_NUM`)
) ENGINE=MyISAM DEFAULT CHARSET=latin1;

CREATE TABLE `meas_numericlimit` (
`ID` varchar(38) NOT NULL DEFAULT '', 
`STEP_RESULT` varchar(38) NOT NULL DEFAULT '', 
`NAME` varchar(255) DEFAULT NULL, 
`COMP_OPERATOR` varchar(30) DEFAULT NULL, 
`HIGH_LIMIT` double DEFAULT NULL, 
`LOW_LIMIT` double DEFAULT NULL, 
`UNITS` varchar(255) DEFAULT NULL, 
`DATA` double DEFAULT NULL, 
`STATUS` varchar(255) DEFAULT NULL, 
`FORMAT` varchar(15) DEFAULT NULL, 
`NANDATA` int(11) DEFAULT '0', 
PRIMARY KEY (`ID`), 
KEY `FK_meas_numericlimit_1` (`STEP_RESULT`)
) ENGINE=MyISAM DEFAULT CHARSET=latin1

CREATE TABLE `step_result` (
`ID` varchar(38) NOT NULL DEFAULT '', 
`UUT_RESULT` varchar(38) NOT NULL DEFAULT '', 
`STEP_PARENT` varchar(38) DEFAULT NULL, 
`STEP_NAME` varchar(255) DEFAULT NULL, 
`STEP_ID` varchar(38) NOT NULL DEFAULT '', 
`STEP_TYPE` varchar(255) DEFAULT NULL, 
`STATUS` varchar(255) DEFAULT NULL, 
`REPORT_TEXT` text, 
`DIAG` text, 
`ERROR_OCCURRED` tinyint(1) NOT NULL DEFAULT '0', 
`ERROR_CODE` int(11) DEFAULT NULL, 
`ERROR_MESSAGE` varchar(1023) DEFAULT NULL, 
`MODULE_TIME` float DEFAULT NULL, 
`TOTAL_TIME` float DEFAULT NULL, 
`NUM_LOOPS` int(11) DEFAULT NULL, 
`NUM_PASSED` int(11) DEFAULT NULL, 
`NUM_FAILED` int(11) DEFAULT NULL, 
`ENDING_LOOP_INDEX` int(11) DEFAULT NULL, 
`LOOP_INDEX` int(11) DEFAULT NULL, 
`INTERACTIVE_EXENUM` int(11) DEFAULT NULL, 
`STEP_GROUP` varchar(30) DEFAULT NULL, 
`STEP_INDEX` int(11) DEFAULT NULL, 
`ORDER_NUMBER` int(11) DEFAULT NULL, 
PRIMARY KEY (`ID`), 
KEY `FK_step_result_1` (`UUT_RESULT`), 
KEY `IDX_step_parent` (`STEP_PARENT`)
) ENGINE=MyISAM DEFAULT CHARSET=latin

【问题讨论】:

由于您使用多个INNER JOIN,您可以尝试将一些WHERE 部分放入ON 部分。 这样就可以了,在on子句中加入条件,在where子句中加入其他条件。 @PeterM - 否。将WHERE 子句迁移到/从ON 不会更改查询计划,但会更改可读性。 trayvou -- 保持原样;很明显,ON 子句说明了表是如何相关的,WHERE 子句具有过滤功能。 VARCHAR(38) 是 UUID 吗?如果是这样,那是问题的一部分。你有多少内存? key_buffer_size 的值是多少? 你应该转移到 InnoDB。 【参考方案1】:

首先要注意的是,仅仅因为您以一个顺序编写连接并不意味着它们实际上是按该顺序执行的。 (查找声明性语言。)

出于这个原因,我将首先创建满足每个 where 子句的索引,然后是每个连接谓词,但在两个方向上......

WHERE 子句开始,所有复合/覆盖索引都应以...开头。

STEP_RESULT : (STEP_NAME, STEP_TYPE) uut_result : (STATION_NUM, UUT_NAME)

考虑到JOIN 谓词,所有复合/覆盖索引都应该是...

meas_numericlimit : (ID) STEP_RESULT : (STEP_NAME, STEP_TYPE, STEP_ID) STEP_RESULT : (STEP_NAME, STEP_TYPE, STEP_PARENT) STEP_RESULT : (STEP_NAME, STEP_TYPE, UUT_RESULT) uut_result : (STATION_NUM, UUT_NAME, ID)

在这 5 个索引中,您可能只会看到 4 个在使用中,因此您可能希望删除未使用的那个,或者保留它以防统计数据的变化改变解释计划。

这在一定程度上取决于您数据的性质。例如,您可能只想将uut_result 上的索引“反转”为(ID, STATION_NUM, UUT_NAME)。在不了解数据行为的情况下,两者都值得尝试。 (同样适用于其他索引建议。)

【讨论】:

【参考方案2】:

如果这些是 InnoDB 表(而不是 MyISAM),我会这样编写查询:

SELECT t1.uut_name
     , t1.station_num
     , t1.start_date_time
     , t3.low_limit
     , t3.data
     , t3.high_limit
     , t3.units
     , t2b.step_name
  FROM uut_result t1
  JOIN step_result t2b
    ON t2b.uut_result     = t1.id
   AND t2b.step_type      = 'constant'
   AND t2b.step_name      = 'variable3' 
  JOIN step_result t2a
    ON t2a.step_parent    = t2b.step_id
  JOIN meas_numericlimit t3
    ON t3.id = t2a.step_id
 WHERE t1.station_num     = 'variable2'
   AND t1.uut_name     LIKE 'Variable1-1%'

我会创建合适的覆盖索引:

... uut_result_IX1 ON uut_result (station_num, uut_name, start_date_time, id)

... step_result_IX1 ON step_result (uut_result, step_type, step_name, step_id) 

... step_result_IX2 ON step_result (step_parent, step_id)

为了提高性能,我还考虑再增加一个覆盖索引...

... meas_numericlimit_IX1 ON meas_numericlimit (id, low_limit, data, high_limit, units)

(对于 InnoDB,主键列是集群键,因此这里没有什么好处。)


对于 MyISAM,合适的索引很重要。但我不认为覆盖索引提供与 InnoDB 相同的好处。

使用 MyISAM,覆盖索引不会避免访问基础表中的页面。所以我认为我们最好使用较短的索引,只使用谓词中使用的列:

... uut_result_IX1 ON uut_result (station_num, uut_name)

... step_result_IX1 ON step_result (uut_result, step_type, step_name)

... step_result_IX2 ON step_result (step_parent)

key_buffer_size 增加到更大的值将允许缓存MyISAM 索引;但不要过度分配。 MyISAM 表页没有缓存,除了 OS 文件系统缓存。

对 MyISAM 的其他配置参数进行修改(根据我对 MyISAM 的有限经验)会产生过度分配(即浪费)内存的净效应,而对性能的影响可以忽略不计或微不足道。所以我不会惹那些。 (这并不是说有些人没有收集到一些性能改进。我只是在我的测试用例上没有取得任何成功。)

与其对 MyISAM 进行调优,我会努力游说将这些表更改为 InnoDB 存储引擎。然后调优 InnoDB。


编辑

我建议的索引中列的顺序是基于具有相等比较的前导列...优先选择最“选择性”的列,然后是具有更多重复值的列。

建议这些索引时考虑到执行计划...以 t1 (uut_result) 作为驱动表开始,连接到 t2b,然后连接到 t2a,最后连接到t3.

将谓词从 WHERE 子句移至 ON 子句并不是为了提高性能……而是为了在查询中将每个表上的谓词分组在一起,以帮助未来的读者。

我认为该查询从对t1 (uut_result) 表的查询开始

SELECT t1.uut_name
     , t1.station_num
     , t1.start_date_time

--      , t3.low_limit
--      , t3.data
--      , t3.high_limit
--      , t3.units
--      , t2b.step_name

  FROM uut_result t1

--   JOIN step_result t2b
--     ON t2b.uut_result     = t1.id
--    AND t2b.step_type      = 'constant'
--    AND t2b.step_name      = 'variable3' 

--   JOIN step_result t2a
--     ON t2a.step_parent    = t2b.step_id

--   JOIN meas_numericlimit t3
--     ON t3.id = t2a.step_id

 WHERE t1.station_num     = 'variable2'
   AND t1.uut_name     LIKE 'Variable1-1%'

然后取消注释引用 t2b 的行 ...

【讨论】:

你的意思是优化器处理 on 子句中的条件与 where 子句不同? @jarlh:不,我不是那个意思。对于优化器来说,谓词就是谓词。我认为谓词是在 WHERE 子句中还是在 INNER JOIN 的 ON 子句中并不重要。 (我没有观察到任何差异;也许有人在 MySQL 的某些版本中观察到了差异。)我会按照我展示的方式编写查询,以使查询的意图更加清晰......我们是查找行... from uut_result where...,然后根据与其他表的连接进行一些额外的过滤。我将 t2b 上的所有谓词放在一起,t3 上的所有谓词放在查询中。

以上是关于在 3 个大表上使用内连接优化 SQL 查询的主要内容,如果未能解决你的问题,请参考以下文章

MySQL查询在大表上很慢

如何在大表上优化这个 mysql 连接?

提高系统性能——对SQL语句优化的思考

Mysql 临时表 视图

如何优化这个 sql 查询(内连接)

如何将外链接向内连接转换?