慢MySQL查询 - 缓存PHP数组中的数据?

时间:2015-08-21 15:02:31

标签: php mysql arrays

我需要使用PHP从MySQL DB中选择一些数据。它可以在一个单独的MySQL查询中完成,在一个好的服务器上运行需要5分钟(在具有超过10 Mio行的表上有多个JOIN)。

我想知道在PHP中拆分查询并使用一些循环而不是MySQL是否是更好的做法。此外,最好从数组中查询一个包含150 000行的表中的所有电子邮件,然后检查数组而不是执行数千个MySQL SELECT。

以下是查询:

SELECT count(contacted_emails.id), contacted_emails.email 
FROM contacted_emails
LEFT OUTER JOIN blacklist ON contacted_emails.email = blacklist.email
LEFT OUTER JOIN submission_authors ON contacted_emails.email = submission_authors.email
LEFT OUTER JOIN users ON contacted_emails.email = users.email
GROUP BY contacted_emails.email
HAVING count(contacted_emails.id) > 3

EXPLAIN返回:EXPLAIN

4个表中的索引是:

contacted_emails: id, blacklist_section_id, journal_id and mail
blacklist: id, email and name
submission_authors: id, hash_key and email
users: id, email, firstname, lastname, editor_id, title_id, country_id, workplace_id

jobtype_id

表的contacts_emails创建如下:

CREATE TABLE contacted_emails ( 
  id int(10) unsigned NOT NULL AUTO_INCREMENT, 
  email varchar(150) COLLATE utf8_unicode_ci NOT NULL,
  contacted_at datetime NOT NULL, 
  created_at datetime NOT NULL, 
  blacklist_section_id int(11) unsigned NOT NULL,
  journal_id int(10) DEFAULT NULL, 
  PRIMARY KEY (id), 
  KEY blacklist_section_id (blacklist_section_id), 
  KEY journal_id (journal_id), 
  KEY email (email) ) 
ENGINE=InnoDB AUTO_INCREMENT=4491706 DEFAULT CHARSET=utf8 COLLATE=utf8_unicode_ci

4 个答案:

答案 0 :(得分:3)

你的索引看起来很好。

性能问题似乎来自于您{{}} {{}}所有行,然后使用JOIN进行过滤。

这可能会更好地改为:

HAVING

您可以在SELECT * FROM ( SELECT email, COUNT(id) AS number_of_contacts FROM contacted_emails GROUP BY email HAVING COUNT(id) > 3 ) AS ce LEFT OUTER JOIN blacklist AS bl ON ce.email = bl.email LEFT OUTER JOIN submission_authors AS sa ON ce.email = sa.email LEFT OUTER JOIN users AS u ON ce.email = u.email /* EDIT: Exclude-join clause added based on comments below */ WHERE bl.email IS NULL AND sa.email IS NULL AND u.email IS NULL 之前限制初始GROUP数据集,这显然更为理想。

虽然给出了原始查询的上下文,但似乎根本没有使用JOIN表格,所以下面的内容可能会返回完全相同的结果,但开销更少:

LEFT OUTER JOIN

那些SELECT email, COUNT(id) AS number_of_contacts FROM contacted_emails GROUP BY email HAVING count(id) > 3 ed表的重点是什么? JOIN阻止他们减少任何数据,并且您只查看来自LEFT JOIN的汇总数据。您的意思是使用contacted_emails吗?

编辑:您提到联接的目的是排除现有表格中的电子邮件。我修改了我的第一个查询以进行正确的排除连接(这是您最初发布的代码中的错误)。

这是另一个可能适合您的可能选项:

INNER JOIN

我在这里做的是收集子查询中的现有电子邮件,并在该派生表上执行单个排除连接。

您尝试表达此内容的另一种方式是WHERE子句中的非相关子查询:

SELECT 
FROM contacted_emails
LEFT JOIN (
    SELECT email FROM blacklist
    UNION ALL SELECT email FROM submission_authors
    UNION ALL SELECT email FROM users
) AS existing ON contacted_emails.email = existing.email
WHERE existing.email IS NULL
GROUP BY contacted_emails.email
HAVING COUNT(id) > 3

尝试所有这些,看看哪个提供了MySQL中最好的执行计划

答案 1 :(得分:2)

根据查询的一些想法,如果你

,你可能会发现它更快
count(*) row_count 

并将HAVING更改为

row_count > 3

因为可以从contacted_emails.email索引满足这一点,而无需访问该行来获取contacted_emails.id。由于两个字段都是NOT NULLcontacted_emails是基表,因此这应该是相同的逻辑。

由于此查询只会在您收集更多数据时延长,我建议您使用汇总表来存储计数(可能每个时间单位)。这可以使用cronjob定期更新,也可以使用触发器和/或应用程序逻辑动态更新。

如果您在created_at上使用每时间单位选项和/或将最后一次更新存储到cron,您应该可以通过拉入并附加最新数据来获得实时结果。

无论如何都必须调整任何缓存解决方案以保持活动状态,并且每次清除/更新数据时都会运行完整查询。

正如评论中所建议的那样,数据库是为聚合大量数据而构建的.PHP不是。

答案 2 :(得分:2)

您可能最好使用一个摘要表,该表通过每次插入到您联系的电子邮件表中的触发器进行更新。此摘要表应包含电子邮件地址和计数列。每次插入到联系表中,更新计数。在摘要表中的计数列上有一个索引。然后你可以直接从THAT查询,有问题的电子邮件帐户,然后加入以获取其他任何需要提取的细节。

答案 3 :(得分:0)

按照您的建议,我选择了这个解决方案:

'django_thumbs'

这需要10秒才能运行,暂时没问题。一旦我在数据库中有更多数据,我将需要考虑另一个解决方案,我将创建一个临时表。

因此,总而言之,将整个表加载为php数组对于提高mysql查询的性能并不好。