有什么办法加快这个查询?

时间:2009-10-22 22:38:22

标签: sql mysql

此查询非常慢。需要9到10秒......

SELECT DISTINCT a.*
FROM addresses a
LEFT JOIN contacts c
ON c.id = a.contact_id
LEFT JOIN organizations o
ON o.id = a.organization_id
ORDER BY c.last_name, c.first_name, o.name
LIMIT 0, 24

如果我注释掉ORDER BY子句,查询运行得更快 - 大约5毫秒。但我需要ORDER BY来支持搜索结果的分页。用户需要通过联系和组织对地址进行排序。


表格结构

addresses
---------
id int NOT NULL
contact_id int       # could be NULL
organization_id int  # could be NULL

contacts
--------
id int NOT NULL
first_name varchar(255)
last_name varchar(255)

organizations
-------------
id int NOT NULL
name varchar(255)

他们都是InnoDB表。

我在联系人表格上有这些索引:

  KEY `idx_contacts_first_name` (`first_name`),
  KEY `idx_contacts_last_name` (`last_name`),
  KEY `idx_contacts_first_name_last_name` (`first_name`,`last_name`)

在组织表上:

  KEY `idx_organization_name` (`name`)

数据量

Addresses:     22,271
Contacts:      17,906
Organizations:  8,246

DESCRIBE输出

mysql> DESCRIBE
    -> SELECT DISTINCT a.*
    -> FROM addresses a
    -> LEFT JOIN contacts c
    -> ON c.id = a.contact_id
    -> LEFT JOIN organizations o
    -> ON o.id = a.organization_id
    -> ORDER BY c.last_name, c.first_name, o.name
    -> LIMIT 0, 24;
+----+-------------+-------+--------+---------------+---------+---------+--------------------------------------------+-------+---------------------------------+
| id | select_type | table | type   | possible_keys | key     | key_len | ref                                        | rows  | Extra                           |
+----+-------------+-------+--------+---------------+---------+---------+--------------------------------------------+-------+---------------------------------+
|  1 | SIMPLE      | a     | ALL    | NULL          | NULL    | NULL    | NULL                                       | 22387 | Using temporary; Using filesort | 
|  1 | SIMPLE      | c     | eq_ref | PRIMARY       | PRIMARY | 4       | contactdb_v2_development.a.contact_id      |     1 | Distinct                        | 
|  1 | SIMPLE      | o     | eq_ref | PRIMARY       | PRIMARY | 4       | contactdb_v2_development.a.organization_id |     1 | Distinct                        | 
+----+-------------+-------+--------+---------------+---------+---------+--------------------------------------------+-------+---------------------------------+
3 rows in set (0.00 sec)

5 个答案:

答案 0 :(得分:2)

我尝试了你的例子,数据量相近,而在我的低端笔记本电脑(Pentium M 1,7 GHz)上,查询只需不到一秒钟(首次运行,后来运行甚至更少)。

你是不是偶然忘记了id列上的PK?你没有提到它,所以只是问......如果你忘了这一点,表现显然会很糟糕 - 更不用说每个DBA都会在没有PK的桌子上畏缩。

否则,试试这个:

DESCRIBE <your query>

这将为您提供MySQL的查询计划。发布(编辑你的问题),它应该更清楚地花了这么长时间。

进一步思考:

查询将始终存在问题性能,因为您要求数据库读取所有地址并对其进行排序并显示它们。 ORDER BY意味着它必须在给出任何东西之前读取所有东西,所以它总是很慢。这样整个数据库的重叠是什么意思呢?用户会翻阅几千条记录吗?

考虑例如允许搜索查询。使用WHERE条件,查询会更快。

答案 1 :(得分:1)

如果你在服务器端没有太多资源限制而且这个东西不会扩展得太远,你就没有很多数据,所以你可以简单地在那个级别进行排序和分页

答案 2 :(得分:1)

尝试添加此索引:

idx_contacts_last_name_first_namelast_namefirst_name

BTW:您可以删除idx_contacts_first_name,因为它是重复的,如果您添加此索引,则可以删除idx_contacts_last_name。

答案 3 :(得分:1)

尝试将SQL更改为以下内容:

SELECT a.column1, a.column2, ...
FROM addresses a
LEFT JOIN contacts c
ON c.id = a.contact_id
LEFT JOIN organizations o
ON o.id = a.organization_id
GROUP BY a.column1, a.column2, ...
ORDER BY c.last_name, c.first_name, o.name
LIMIT 0, 24

我发现GROUP BY一般比DISTINCT要快得多,但我不知道为什么会这样。{/ p>

答案 4 :(得分:0)

让我们看看。

  • 地址:22,271
  • 联络人:17,906
  • 组织:8,246

解决LEFT JOIN联系人c为大约20,000个结果提供大约20,000 * 20,000~4亿的比较

LEFT JOIN组织为大约20,000个结果提供了大约10,000 * 20,000~2亿的比较

我们主要对联系行进行排序,然后丢弃除24个之外的所有行。似乎地址的清晰度至关重要。

由于我们主要按联系人排序,我们如何对联系人进行子选择,保留 比我们需要的更多(比如大约4倍):

SELECT * FROM contacts ORDER BY last_name, first_name LIMIT 100

然后加入他们的地址,保持前百名左右

     SELECT a.* 
       FROM (SELECT * FROM contacts ORDER BY last_name, first_name LIMIT 0, 100) AS c
  LEFT JOIN addresses a
         ON c.id = a.contact_id
      LIMIT 0, 100

然后将这些加入组织

 SELECT * 
   FROM (
        SELECT * 
          FROM (SELECT * FROM contacts ORDER BY last_name, first_name LIMIT 0, 100) AS c
     LEFT JOIN addresses a
            ON c.id = a.contact_id
         LIMIT 0, 100
         ) AS ca LEFT JOIN organizations o
      ON o.id = ca.organization_id
ORDER BY ca.last_name, ca.first_name, o.name
   LIMIT 0, 24

我确信语法被搞砸了,但我同样确信在每个阶段减少结果集的原则都是一种有益的方式。我也可能做了几次折衷,结果非常接近10秒的答案,但更快地到达那里。