mySql加入表最佳做法?

时间:2016-12-30 15:28:40

标签: mysql

我正在构建一个Web应用程序,它具有多种类型的对象以及这些对象之间的众多关系。每种类型的对象我给出了一个3位数的代码(即" TRA"," COM"," APR"," CRI&# 34;等等)。我有一个连接表,询问类型,然后是应该链接在一起的主要和次要对象的type_id。

CREATE TABLE `obj_rels` (
`id` int(11) NOT NULL AUTO_INCREMENT,
`pri_type` varchar(3) DEFAULT NULL,
`pri_type_id` int(11) DEFAULT NULL,
`sec_type` varchar(3) DEFAULT NULL,
`sec_type_id` int(11) DEFAULT NULL,
`effective_on` timestamp NULL DEFAULT CURRENT_TIMESTAMP,
`trashed_by` int(11) DEFAULT NULL,
`trashed_on` datetime DEFAULT NULL,
PRIMARY KEY (`id`),
KEY `types` (`pri_type`,`sec_type`)
) ENGINE=InnoDB DEFAULT CHARSET=utf8;

我玩了很多不同类型的索引,但是这个表上的查询正在减慢应用程序的速度。

条目只添加一次,因此只用一条记录定义关系,例如pri_type = FIL,pri_type_id = 123,sec_type = TRA,sec_type_id = 456 ...如果我需要搜索与特定相关的任何内容因此,TRA需要搜索主要和次要类型/ type_id才能找到它们。我使用的查询是:

SELECT *
FROM ( 
    SELECT pri_type, pri_type_id, sec_type, sec_type_id, effective_on, trashed_by, trashed_on 
    FROM obj_rels 
    UNION 
    SELECT sec_type AS pri_type, sec_type_id AS pri_type_id, pri_type AS sec_type, pri_type_id AS sec_type_id, effective_on, trashed_by, trashed_on 
    FROM obj_rels 
) AS qry 
WHERE pri_type = 'TRA' AND pri_type_id = 21375 AND (trashed_on = 0 OR ISNULL(trashed_on))

但是这个查询需要大约1.5秒才能在表中运行大约71,000条记录,而我所拥有的其他查询依赖于此查询,因此它们需要4-5秒才能运行。

如何最好地设置索引或调整查询以优化关系结果?

提前致谢!

2 个答案:

答案 0 :(得分:1)

使用当前表设计获得的最快速度是完全消除子查询和联合,并使用IF语句获取动态列,如下所示:

SELECT
    IF(pri_type = 'TRA' AND pri_type_id = 21375, pri_type, sec_type) AS pri_type,
    IF(pri_type = 'TRA' AND pri_type_id = 21375, pri_type_id, sec_type_id) AS pri_type_id,
    IF(pri_type = 'TRA' AND pri_type_id = 21375, sec_type, pri_type) AS sec_type,
    IF(pri_type = 'TRA' AND pri_type_id = 21375, sec_type_id, pri_type_id) AS sec_type_id,
    effective_on,
    trashed_by,
    trashed_on 
FROM obj_rels 
WHERE (trashed_on = 0 OR trashed_on IS NULL)
    AND (
        (pri_type = 'TRA' AND pri_type_id = 21375)
        OR (sec_type = 'TRA' AND sec_type_id = 21375)
    );

这将导致行与自身联合的行数的1/2,并且将为那些大量令人讨厌的数据避免令人讨厌的临时表。

当然,如果您为搜索列编制索引,您将会变得非常快:

ALTER TABLE obj_rels
    ADD INDEX (pri_type),
    ADD INDEX (pri_type_id),
    ADD INDEX (sec_type),
    ADD INDEX (sec_type_id),
    ADD INDEX (trashed_on);

PS - 请注意,我已将ISNULL函数调用更改为trashed_on IS NULL。前者是COALESCE别名(COALESCE是首选,顺便说一句,因为它适用于其他RDBMS),后者是比较。如果您想使用前者,可以说WHERE COALESCE(trashed_on, 0) = 0来处理这两种情况。

答案 1 :(得分:0)

UNION可以在两个更简单的可索引查询之间进行组合。您可以消除表扫描,并在应用相应的WHERE子句后组合两个较小的中间结果,这些子句受益于两个不同的索引。

但是你误解了这种技巧。您执行此操作的方式会两次读取表中的每个行,创建一个包含142,000行的临时表,然后将您的条件应用于该临时表。

所以改为以这种方式编写查询:

(
  SELECT pri_type, pri_type_id, sec_type, sec_type_id, effective_on, trashed_by, trashed_on 
  FROM obj_rels 
  WHERE pri_type = 'TRA' AND pri_type_id = 21375 AND trashed_on IS NULL
)

UNION ALL

(
  SELECT sec_type, sec_type_id, pri_type, pri_type_id, effective_on, trashed_by, trashed_on 
  FROM obj_rels
  WHERE sec_type = 'TRA' AND sec_type_id = 21375 AND trashed_on IS NULL
)

重复类似的WHERE子句而不是编写一个WHERE子句似乎是违反直觉的,但目的是使用相应的索引来减少匹配行的集合,然后使用UNION这些较小的行集。这比创建表中总行数的2倍的临时表要好得多,然后将WHERE子句应用于临时表。

要优化每个子查询,请创建以下索引:

ALTER TABLE obj_rels
 ADD KEY (pri_type_id, pri_type, trashed_on),
 ADD KEY (sec_type_id, sec_type, trashed_on);

UNION中的每个查询都使用相应的索引。

我首先放置type_id列,因为我认为它们比type列更具选择性。

我还会对应用程序进行更改,以确保trashed_on永远不会为0.如果没有有效的日期时间,请使用NULL。原因是允许索引包含trashed_on。我不确定它是否会使用OR将索引应用于更复杂的表达式。

使用UNION ALL而不是UNION消除了UNION对总结果进行排序以消除重复的步骤。如果您想要消除重复,请忽略该更改。