我需要使用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
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
答案 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 NULL
而contacted_emails
是基表,因此这应该是相同的逻辑。
由于此查询只会在您收集更多数据时延长,我建议您使用汇总表来存储计数(可能每个时间单位)。这可以使用cronjob定期更新,也可以使用触发器和/或应用程序逻辑动态更新。
如果您在created_at上使用每时间单位选项和/或将最后一次更新存储到cron,您应该可以通过拉入并附加最新数据来获得实时结果。
无论如何都必须调整任何缓存解决方案以保持活动状态,并且每次清除/更新数据时都会运行完整查询。
正如评论中所建议的那样,数据库是为聚合大量数据而构建的.PHP不是。
答案 2 :(得分:2)
您可能最好使用一个摘要表,该表通过每次插入到您联系的电子邮件表中的触发器进行更新。此摘要表应包含电子邮件地址和计数列。每次插入到联系表中,更新计数。在摘要表中的计数列上有一个索引。然后你可以直接从THAT查询,有问题的电子邮件帐户,然后加入以获取其他任何需要提取的细节。
答案 3 :(得分:0)
按照您的建议,我选择了这个解决方案:
'django_thumbs'
这需要10秒才能运行,暂时没问题。一旦我在数据库中有更多数据,我将需要考虑另一个解决方案,我将创建一个临时表。
因此,总而言之,将整个表加载为php数组对于提高mysql查询的性能并不好。