优化涉及数百万行的mysql查询

时间:2016-09-16 12:29:03

标签: mysql

我在一个项目中有一个包含两个大表的数据库," terminosnoticia"有4亿行和#34; noticia" 3百万。我有一个问题,我想打造更轻(它从10s到400s):

    SELECT noticia_id, termino_id
      FROM noticia 
      LEFT JOIN terminosnoticia on terminosnoticia.noticia_id=noticia.id AND termino_id IN (7818,12345) 
     WHERE noticia.fecha BETWEEN '2016-09-16 00:00' AND '2016-09-16 10:00' 
       AND noticia_id is not null AND termino_id is not null;`

我必须探索的唯一可行的解​​决方案是对数据库进行非规范化以包含“'”。大表中的字段,但是,这将乘以索引大小。

解释计划:

+----+-------------+-----------------+--------+-----------------------+------------+---------+-----------------------------------------+-------+-------------+
| id | select_type | table           | type   | possible_keys         | key        | key_len | ref                                     | rows  | Extra       |
+----+-------------+-----------------+--------+-----------------------+------------+---------+-----------------------------------------+-------+-------------+
|  1 | SIMPLE      | terminosnoticia | ref    | noticia_id,termino_id | termino_id | 4       | const                                   | 58480 | Using where |
|  1 | SIMPLE      | noticia         | eq_ref | PRIMARY,fecha         | PRIMARY    | 4       | db_resumenes.terminosnoticia.noticia_id |     1 | Using where |
+----+-------------+-----------------+--------+-----------------------+------------+---------+-----------------------------------------+-------+-------------+

按照建议更改查询并创建索引,解释计划现在是:

+----+-------------+-------+--------+-------------------------------------------+---------------------+---------+---------------------------+-------+-------------+
| id | select_type | table | type   | possible_keys                             | key                 | key_len | ref                       | rows  | Extra       |
+----+-------------+-------+--------+-------------------------------------------+---------------------+---------+---------------------------+-------+-------------+
|  1 | SIMPLE      | T     | ref    | noticia_id,termino_id,terminosnoticia_cpx | terminosnoticia_cpx | 4       | const                     | 60600 | Using index |
|  1 | SIMPLE      | N     | eq_ref | PRIMARY,fecha                             | PRIMARY             | 4       | db_resumenes.T.noticia_id |     1 | Using where |
+----+-------------+-------+--------+-------------------------------------------+---------------------+---------+---------------------------+-------+-------------+

但执行时间并没有太大变化......

有什么想法吗?

4 个答案:

答案 0 :(得分:4)

正如Strawberry所指出的那样,在你的where子句中使用“AND”表示NOT NULL 与常规INNER JOIN相同,可以缩减为。

 SELECT 
       N.id as noticia_id, 
       T.termino_id
   FROM 
      noticia N  USING INDEX (fecha)
         JOIN terminosnoticia T
            on N.id = T.noticia_id
            AND T.termino_id IN (7818,12345) 
   WHERE 
      N.fecha BETWEEN '2016-09-16 00:00' AND '2016-09-16 10:00' 

现在,应用了所述和别名,我建议将以下覆盖索引作为

table           index
Noticia         ( fecha, id )
terminosnoticia ( noticia_id, termino_id )

这样查询可以直接从索引中获取所有结果,而不必转到原始数据页来限定其他字段。

答案 1 :(得分:1)

假设noticia_idnoticia的主键,我会添加以下索引:

create index noticia_fecha_idx on noticia(fecha);
create index terminosnoticia_id_noticia_idx on terminosnoticia(noticia_id);

再次尝试查询。

请包含查询的当前执行计划。它可能有助于你解决这个问题。

答案 2 :(得分:0)

试试这个:

SELECT tbl1.noticia_id, tbl1.termino_id FROM
( SELECT FROM terminosnoticia WHERE
terminosnoticia.termino_id IN (7818,12345) 
AND terminosnoticia.noticia_id is not null 
) tbl1 INNER JOIN 
( SELECT id FROM noticia 
  WHERE noticia.fecha
  BETWEEN '2016-09-16 00:00' AND '2016-09-16 10:00'
) tbl2 ON tbl1.id=tbl2.noticia.id

答案 3 :(得分:0)

我们假设noticia_idtermino_idterminosnoticia表中的列。 (如果所有列引用都使用表名或短表别名限定,我们就不必猜测。)

为什么这是外部联接? WHERE子句中的谓词将从terminosnoticia中排除具有NULL值的行。这将否定"外在"外部"加入。

如果我们将其写为内连接,那么WHERE子句中的那些谓词就是多余的。我们已经知道noticia_id不会为NULL(如果它满足ON子句中的等式谓词)。与termino_id相同,如果它等于IN列表中的值,则不会为NULL。

我相信这个查询会返回一个等价的结果:

  SELECT t.noticia_id
       , t.termino_id
    FROM noticia n
    JOIN terminosnoticia t
      ON t.noticia_id = n.id
     AND t.termino_id IN (7818,12345)
   WHERE n.fecha BETWEEN '2016-09-16 00:00' AND '2016-09-16 10:00'

现在剩下的就是弄清楚是否存在任何隐式数据类型转换。

我们没有看到termino_id的数据类型。所以我们不知道这是否被定义为数字。如果不是,这是个坏消息,因为MySQL必须为表格中的每一行执行数字转换,因此它可以与数字文字进行比较。

我们没有看到noticia_id的数据类型,以及它是否与要与之比较的列的数据类型匹配,来自id的{​​{1}}列} table。

我们也没有看到noticia的数据类型。基于谓词之间的字符串文字,看起来它可能是DATETIME或TIMESTAMP。但这只是猜测。我们不知道,因为我们没有可用的表格定义。

一旦我们确认没有任何隐含的数据类型转换会让我们感到不舒服......

对于使用内连接的查询(如上所述),合理性能的最佳镜头可能是MySQL有效使用覆盖索引。 (覆盖索引允许MySQL直接从索引块中满足查询,而无需在基础表中查找页面。)

正如DRApp的回答已经说明的那样,对于这个特定的查询,覆盖索引的最佳候选者将是:

fecha

具有相同前导列的索引也适用,并且会使这些索引冗余。

添加这些索引会使其他索引变得多余。

第一个索引对 ... ON noticia (fecha, id) ... ON terminosnoticia (noticia_id, termino_id) 是多余的。假设索引不强制执行UNIQUE约束,则可以删除它。任何有效使用该索引的查询都可以使用新索引,因为... ON noticia (fecha)是新索引中的前导列。

同样,索引fecha也是多余的。同样,假设它不是唯一索引,强制执行UNIQUE约束,那么也可以删除该索引。