这是一个问题,我不确定根本原因在哪里,所以我将提供详细信息和我想到的问题点。任何帮助都会很棒(如果你住在附近的话我会喝啤酒)。我有这三个表:
做法:
`id` int(11) unsigned NOT NULL AUTO_INCREMENT,
`name` varchar(125) NOT NULL,
`description` text,
`deleted` int(11) unsigned DEFAULT NULL,
`created` timestamp NOT NULL DEFAULT CURRENT_TIMESTAMP,
`created_by` varchar(70) NOT NULL,
`last_update` timestamp NOT NULL DEFAULT CURRENT_TIMESTAMP ON UPDATE CURRENT_TIMESTAMP,
`last_update_by` varchar(70) NOT NULL,
PRIMARY KEY (`id`),
KEY `name` (`name`)
位置:
`id` int(11) unsigned NOT NULL AUTO_INCREMENT,
`practice_fk` int(11) unsigned NOT NULL,
`phone` char(12) DEFAULT NULL,
`fax` char(12) DEFAULT NULL,
`address` varchar(125) DEFAULT NULL,
`address_two` varchar(125) DEFAULT NULL,
`city` varchar(40) NOT NULL,
`state` char(2) NOT NULL,
`zip` char(5) DEFAULT NULL,
`lat` decimal(7,5) DEFAULT NULL,
`lng` decimal(7,5) DEFAULT NULL,
`deleted` int(11) unsigned DEFAULT NULL,
`created` timestamp NOT NULL DEFAULT CURRENT_TIMESTAMP,
`created_by` varchar(70) NOT NULL,
`last_update` timestamp NOT NULL DEFAULT CURRENT_TIMESTAMP ON UPDATE CURRENT_TIMESTAMP,
`last_update_by` varchar(70) NOT NULL,
`email` varchar(150) DEFAULT NULL,
`practice_name_temp` text,
PRIMARY KEY (`id`)
联系人:
`id` int(11) unsigned NOT NULL AUTO_INCREMENT,
`location_fk` int(11) unsigned NOT NULL,
`practice_fk` int(11) unsigned NOT NULL,
`fname` varchar(25) NOT NULL,
`lname` varchar(45) NOT NULL,
`phone` varchar(35) DEFAULT NULL,
`mobile` char(12) DEFAULT NULL,
`email` varchar(125) DEFAULT NULL,
`title` varchar(100) DEFAULT NULL,
`description` text,
`deleted` int(11) unsigned DEFAULT NULL,
`created` timestamp NOT NULL DEFAULT CURRENT_TIMESTAMP,
`created_by` varchar(70) NOT NULL,
`last_update` timestamp NOT NULL DEFAULT CURRENT_TIMESTAMP ON UPDATE CURRENT_TIMESTAMP,
`last_update_by` varchar(70) NOT NULL,
PRIMARY KEY (`id`)
架构背后的基本思想是有一系列实践。练习可以有多个位置,但如果没有与练习相关联,则不能存在位置。然后,练习也可以有多个联系人,但联系人必须与练习和位置相关联。 [这是问题的一部分可能开始的地方]。所以,我有这个问题:
SELECT DISTINCT p.id AS practice_id,
p.name,
l.id AS location_id,
address AS location_address,
l.phone AS disp_phone,
CONCAT(pc.fname, ' ', pc.lname) AS practiceContact,
CONCAT(lc.fname, ' ', lc.lname) AS locationContact,
pcc.qty AS practice_only_contact_qty,
lcc.qty AS location_contact_qty,
(pcc.qty + lcc.qty) AS contactQty
FROM practices p
LEFT JOIN practice_locations l on l.practice_fk=p.id
LEFT JOIN (
SELECT count(id) AS qty, practice_fk
FROM practice_contacts
GROUP BY practice_fk
) pcc ON pcc.practice_fk=p.id
LEFT JOIN practice_contacts pc ON pc.practice_fk=pcc.practice_fk AND pcc.qty=1
LEFT JOIN (
SELECT count(id) AS qty, location_fk
FROM practice_contacts
GROUP BY location_fk
) lcc ON lcc.location_fk=l.id
LEFT JOIN practice_contacts lc ON lc.location_fk=lcc.location_fk AND lcc.qty=1
WHERE p.name IS NOT NULL AND p.deleted IS NULL
GROUP BY p.id
ORDER BY p.name ASC, l.state, l.city, l.address;
这个查询应该做的是:
所以,它现在就做了所有这些。非常缓慢。当我在实践表中只有五个记录,而在其他两个表中只有不到20个时,查询效果很好。现在我已经将数据导入到这些表中(实践中约9,000条记录,位置中有14,000多条记录,联系人中有25,000多条记录),此查询需要28秒才能返回我需要的内容。如果我把小组拉出去,我们会看到33秒以上。拧我,对吧?!
显然,这是不可接受的。这个数据集相对较小,而且这个应用程序只会增长,可能有数百万个联系人在这里。所以,我想知道这是否实际上是一个三加分的问题:
第一部分:我应该引入一个参考表[类似于视图]来存储这些关系 - 例如:
`id` int(11) unsigned not null,
`practice_fk` int(11) unsigned not null,
`location_fk` int(11) unsigned not null,
`contact_fk` int(11) unsigned not null,
PRIMARY KEY(id),
KEY(practice_fk),
KEY(location_fk),
KEY(contact_fk)
但是,如果我这样做,我不确定如何构建查询以根据需要提取数据?它会提供任何性能优势。
第二部分:我没有适当的索引。在搜索了MySQL文档并绊倒了这篇文章(https://dba.stackexchange.com/questions/75091/why-are-simple-selects-on-innodb-100x-slower-than-on-myisam)后,我开始明白InnoDB是一头慢猪。从UX的角度来看,这是不可接受的,但从架构的角度来看,我被锁定在这个引擎中。如何正确设置索引以使此查询返回到亚秒范围?
第三部分:我的查询是垃圾。我认为这可能是最大的罪魁祸首。我还在学习如何构建这些更复杂的SQL查询,这需要花费一些精力来制作,所以任何关于如何使这个东西不那么猪的指针都会很棒。
我已经尝试过对我的查询进行各种操作(逐个删除,删除顺序等)并且几乎没有任何变化。查询始终在28到33秒之间运行。任何指导都将无法理解。
答案 0 :(得分:2)
并非所有这些都可以修复,但他们会跳出来作为性能红旗:
DISTINCT
和GROUP BY
。他们也做同样的事情。LEFT JOIN
为您提供所需内容,请勿使用JOIN
。 LEFT
意味着'权利'表可能缺少行。LEFT JOIN ( SELECT ... )
通常无法优化,但JOIN
可能。( SELECT ... ) JOIN ( SELECT ... )
JOINing
膨胀行数; GROUP BY
然后放气。这是性能问题的常见原因。 (也许我随着时间的推移可以更具体。)COUNT(x)
检查x
是否为NULL
。 通常,您真正想要的是COUNT(*)
。为了清晰阅读,以及"正确性"使用LEFT JOIN
时,只需添加' ON
中的条件; put'过滤' WHERE
中的条件。我认为 AND pcc.qty=1
应该从ON
移到WHERE
。 (我认为可能会更改结果集。)
可能的索引:
p: INDEX(deleted, name, id)
l: INDEX(practice_fk)
EXPLAIN SELECT ...
。如果你没有看到" auto-key",那么你有一个旧版本的MySQL;考虑升级。 "自动键"说我对( SELECT ... ) JOIN ( SELECT ... )
的评论不适用。否则,请考虑两个CREATE TEMPORARY TABLE
并在..._fk
上添加索引。然后使用tmp表而不是LEFT JOIN ( SELECT ... )
两次。
根据我的评论尽你所能,然后返回修改后的查询,加上EXPLAIN
进一步批评(如有必要)。
有关创建索引的更多信息:http://mysql.rjweb.org/doc.php/index_cookbook_mysql
答案 1 :(得分:2)
您似乎并不需要按子查询中的任何内容进行排序。因此,您可以显式设置ORDER BY NULL以提高子查询的性能。
修改后的查询:
SELECT
DISTINCT p.id AS practice_id,
p.name,
l.id AS location_id,
l.address AS location_address,
l.phone AS disp_phone,
CONCAT(pc.fname,
' ',
pc.lname) AS practiceContact,
CONCAT(lc.fname,
' ',
lc.lname) AS locationContact,
pcc.qty AS practice_only_contact_qty,
lcc.qty AS location_contact_qty,
(pcc.qty + lcc.qty) AS contactQty
FROM
practices p
LEFT JOIN
practice_locations l
ON l.practice_fk = p.id
LEFT JOIN
(
SELECT
COUNT(practice_contacts.id) AS qty,
practice_contacts.practice_fk
FROM
practice_contacts
GROUP BY
practice_contacts.practice_fk
ORDER BY
NULL
) pcc
ON pcc.practice_fk = p.id
LEFT JOIN
practice_contacts pc
ON pc.practice_fk = pcc.practice_fk
AND pcc.qty = 1
LEFT JOIN
(
SELECT
COUNT(practice_contacts.id) AS qty,
practice_contacts.location_fk
FROM
practice_contacts
GROUP BY
practice_contacts.location_fk
ORDER BY
NULL
) lcc
ON lcc.location_fk = l.id
LEFT JOIN
practice_contacts lc
ON lc.location_fk = lcc.location_fk
AND lcc.qty = 1
WHERE
p.name IS NOT NULL
AND p.deleted IS NULL
GROUP BY
p.id
ORDER BY
p.name ASC,
l.state,
l.city,
l.address
此外,添加以下可能优化您的查询的索引:
ALTER TABLE `practices` ADD INDEX `practices_index_1` (`deleted`,`id`,`name`);
ALTER TABLE `practice_contacts` ADD INDEX `practice_contacts_index_1` (`practice_fk`,`location_fk`);
ALTER TABLE `practice_contacts` ADD INDEX `practice_contacts_index_2` (`location_fk`);
ALTER TABLE `practice_locations` ADD INDEX `practice_locations_index_1` (`practice_fk`,`id`);
答案 2 :(得分:1)
您可以使用ROW LIMIT语法始终返回第一行,而不是计算联系人和实践的数量。我无法保证它会对性能有所帮助,但担心这两个联接的次数会减少。
LEFT JOIN (
SELECT *
FROM practice_contacts
GROUP BY practice_fk LIMIT 1
) pcc ON pcc.practice_fk=p.id
LEFT JOIN (
SELECT *
FROM practice_locations
GROUP BY practicec_fk LIMIT 1
) lcc ON lcc.practice_fk = p.id
我没有验证sql是否可行,但你明白了。如果您需要特定的联系人或位置(例如,最新的),则可以在子选择中包含ORDER BY子句。
请参阅Does MySQL "SELECT LIMIT 1" with multiple records select first record from the top?