mysql - PHP; 随着 WHERE 条件更新,对大型数据集的 MySQL JOIN 查询变慢
问题描述
所以这可能有点傻,但我使用的替代方案更糟。我正在尝试使用我的数据库中的数据和一个名为Box/Spout的 PHP 工具编写一个 excel 表。问题是 Box/Spout 一次读取一行,并且不通过索引检索它们(例如 rows[10], rows[42], rows[156] )
我需要按照行出现的顺序从数据库中检索数据。我有一个包含客户列表的数据库,通过它进来,Import
我必须将它们写入 excel 电子表格。他们有电话号码、电子邮件和地址。很抱歉造成混乱......:/所以我编译了这个相当复杂的查询:
SELECT
`Import`.`UniqueID`,
`Import`.`RowNum`,
`People`.`PeopleID`,
`People`.`First`,
`People`.`Last`,
GROUP_CONCAT(
DISTINCT CONCAT_WS(',', `PhonesTable`.`Phone`, `PhonesTable`.`Type`)
ORDER BY `PhonesTable`.`PhoneID` DESC
SEPARATOR ';'
) AS `Phones`,
GROUP_CONCAT(
DISTINCT CONCAT_WS(',', `EmailsTable`.`Email`)
ORDER BY `EmailsTable`.`EmailID` DESC
SEPARATOR ';'
) AS `Emails`,
`Properties`.`Address1`,
`Properties`.`city`,
`Properties`.`state`,
`Properties`.`PostalCode5`,
...(17 more `People` Columns)...,
FROM `T_Import` AS `Import`
LEFT JOIN `T_CustomerStorageJoin` AS `CustomerJoin`
ON `Import`.`UniqueID` = `CustomerJoin`.`ImportID`
LEFT JOIN `T_People` AS `People`
ON `CustomerJoin`.`PersID`=`People`.`PeopleID`
LEFT JOIN `T_JoinPeopleIDPhoneID` AS `PeIDPhID`
ON `People`.`PeopleID` = `PeIDPhID`.`PeopleID`
LEFT JOIN `T_Phone` AS `PhonesTable`
ON `PeIDPhID`.`PhoneID`=`PhonesTable`.`PhoneID`
LEFT JOIN `T_JoinPeopleIDEmailID` AS `PeIDEmID`
ON `People`.`PeopleID` = `PeIDEmID`.`PeopleID`
LEFT JOIN `T_Email` AS `EmailsTable`
ON `PeIDEmID`.`EmailID`=`EmailsTable`.`EmailID`
LEFT JOIN `T_JoinPeopleIDPropertyID` AS `PeIDPrID`
ON `People`.`PeopleID` = `PeIDPrID`.`PeopleID`
AND `PeIDPrID`.`PropertyCP`='CurrentImported'
LEFT JOIN `T_Property` AS `Properties`
ON `PeIDPrID`.`PropertyID`=`Properties`.`PropertyID`
WHERE `Import`.`CustomerCollectionID`=$ccID
AND `RowNum` >= $rnOffset
AND `RowNum` < $rnLimit
GROUP BY `RowNum`;
所以我对每个ON
段和WHERE
段都有索引。当RowNumber
值约为 0->2500 时,查询运行良好并在几秒钟内执行。但似乎查询执行时间成倍地增加了较大的 RowNumber。
我在EXPLAIN
这里有一个:在pastebin(https://pastebin.com/PksYB4n2)
id select_type table partitions type possible_keys key key_len ref rows filtered Extra
1 SIMPLE Import NULL ref CustomerCollectionID,RowNumIndex CustomerCollectionID 4 const 48108 8.74 Using index condition; Using where; Using filesort;
1 SIMPLE CustomerJoin NULL ref ImportID ImportID 4 MyDatabase.Import.UniqueID 1 100 NULL
1 SIMPLE People NULL eq_ref PRIMARY,PeopleID PRIMARY 4 MyDatabase.CustomerJoin.PersID 1 100 NULL
1 SIMPLE PeIDPhID NULL ref PeopleID PeopleID 5 MyDatabase.People.PeopleID 8 100 NULL
1 SIMPLE PhonesTable NULL eq_ref PRIMARY,PhoneID,PhoneID_2 PRIMARY 4 MyDatabase.PeIDPhID.PhoneID 1 100 NULL
1 SIMPLE PeIDEmID NULL ref PeopleID PeopleID 5 MyDatabase.People.PeopleID 5 100 NULL
1 SIMPLE EmailsTable NULL eq_ref PRIMARY,EmailID,DupeDeleteSelect PRIMARY 4 MyDatabase.PeIDEmID.EmailID 1 100 NULL
1 SIMPLE PeIDPrID NULL ref PeopleMSCP,PeopleID,PropertyCP PeopleMSCP 5 MyDatabase.People.PeopleID 4 100 Using where
1 SIMPLE Properties NULL eq_ref PRIMARY,PropertyID PRIMARY 4 MyDatabase.PeIDPrID.PropertyID 1 100 NULL
如果格式非常糟糕,我深表歉意。我不确定好的格式是什么样的,所以我可能不小心把它弄乱了,而且标签搞砸了。
我想知道的是如何加快查询时间。数据库非常大,例如数百万行。而且它们并不总是这样,因为我们的桌子在不断变化,但是我希望能够在它们发生变化时处理它。
例如,我尝试使用 LIMIT 2000, 1000,但我知道它的效率低于使用索引列。所以我切换到 RowNumber。我觉得这是一个很好的决定,但似乎 MySQL 仍在循环偏移变量之前的每一行,这违背了我的索引的目的......我想?我不确定。我还基本上将这个特定查询拆分为大约 10 个单个查询,并针对 excel 文件的每一行逐个运行它们。这需要很长时间……太长了。这很快,但是,显然我遇到了问题。
任何帮助将不胜感激,并提前感谢您。我再次为我缺乏帖子组织而感到抱歉。
解决方案
- 索引中列的顺序很重要。子句的顺序
WHERE
无关紧要(通常)。 INDEX(a), INDEX(b)
与“复合”不同INDEX(a,b)
。我特意在它们看起来有用的地方制作了复合索引。INDEX(a,b)
和INDEX(b,a)
不可互换,除非两者a
和b
都用 进行了测试=
。(加上一些例外。)- “覆盖”索引是一个表的所有列都在一个索引中找到的索引。这有时会提供额外的性能提升。我推荐的一些索引是“覆盖”。这意味着只需要访问索引BTree,而不需要访问数据BTree;这是它加快速度的地方。
- 在
EXPLAIN SELECT ...
“覆盖”索引中,由“使用索引”表示(与“使用索引条件”不同)。(您的解释目前没有显示覆盖索引。) - 索引“不应该”有超过 5 列。(这不是一个硬性规定。)
T5
的索引有 f5 列要覆盖;为 .建立一个覆盖索引是不切实际的T2
。
当 时JOINing
,表格的顺序无关紧要;优化器可以随意打乱它们。但是,这些“规则”适用:
- A
LEFT JOIN
可能会强制对表格进行排序。(我认为在这种情况下确实如此。)(我根据我认为优化器想要的内容对列进行了排序;可能会有一些灵活性。) - 该
WHERE
子句通常确定要“从哪个表开始”。(你T1
只测试,所以很明显它将从 T1 开始。 - 要引用的“下一个表”(通过 NLJ - 嵌套循环连接)由多种因素决定。(在您的情况下,这很明显 - 即
ON
列。)
更多关于索引:http: //mysql.rjweb.org/doc.php/index_cookbook_mysql
修改后的查询
1. Import: (CustomerCollectionID, -- '=' comes first
RowNum, -- 'range'
UniqueID) -- 'covering'
Import shows up in WHERE, so is first in Explain; Also due to LEFTs
Properties: (PropertyID) -- is that the PK?
PeIDPrID: (PropertyCP, PeopleID, PropertyID)
3. People: (PeopleID)
I assume that is the `PRIMARY KEY`? (Too many for "covering")
(Since `People` leads to 3 other table; I won't number the rest.)
EmailsTable: (EmailID, Email)
PeIDEmID: (PeopleID, -- JOIN from People
EmailID) -- covering
PhonesTable: (PhoneID, Type, Phone)
PeIDPhID: (PeopleID, PhoneID)
2. CustomerJoin: (ImportID, -- coming from `Import` (see ON...)
PersID) -- covering
添加这些之后,我希望大多数行EXPLAIN
都说Using index
.
缺少至少一个综合索引Import
是导致您的绩效投诉的主要问题。
不好的 GROUP BY
当存在GROUP BY
不包括不直接依赖于 group by 列的所有非聚合列时,您将获得额外的随机值。我从EXPLAIN
(“行”)中看到几个表可能有多行。你真的应该考虑一下这个查询产生的垃圾。
奇怪的是,电话和电子邮件被输入GROUP_CONCAT()
,从而避免了上述问题,但“行”只有 1。
(阅读一下ONLY_FULL_GROUP_BY
;它可能会更好地解释这个问题。)
推荐阅读
- python - gnu 并行与多处理
- javascript - 在highcharts的移动全屏中隐藏“范围输入”按钮
- ios - Objective-C框架的条件编译
- c# - 无法将内存中的 PDF 文件与 iText7 合并
- python - 使用 xarray 对 netcdf 进行增量存储
- android - 未解决的参考:AndroidJUnit4 注释参数必须是编译时常量
- python - UnicodeDecodeError 无法解码通过写入和读取(使用熊猫)到文件中解决的字节
- qt - 何时评估分配给属性的 qml 谓词?
- javascript - 如何将类添加到 javascript 链接
- typescript - typescript-eslint 错误:无角括号类型断言