为什么这个 select 语句这么慢?
Why is this select statement so slow?
这个select 语句运行起来超级慢。完成执行需要 10 多秒。可能会更长,但我不知道,因为与 MySQL 的连接超时。那是一个单独的问题。
代码如下:
SELECT
f.id, f.name, GROUP_CONCAT(DISTINCT (c.firstname)) children
FROM
families f,
children c,
transactions t
WHERE
f.companyid = 1170 AND f.id = t.familyid
AND f.id = c.familyid
AND t.transactiontype = 'P'
AND t.taxdeductible = 'Y'
AND YEAR(t.date) = 2017
AND status = 'A'
OR f.id = 9779432
GROUP BY f.id
ORDER BY name;
我在 families.companyid、children.familyid、transactions.transactiontype、transactions.taxdeductible 和 transactions.date 上有索引。
有什么理由不顾我的索引而进行完整的 table 扫描吗?或者还有其他原因导致此查询运行缓慢?
编辑:根据以下评论填写一些空白:
children table 有 73,000 行的 17MB 数据。
家族 table 在 56,000 行中有 6MB 的数据
交易 table 在 980,000 行中有 83MB 的数据。
CHILDREN TABLE
CREATE TABLE `children` (
`id` int(11) NOT NULL AUTO_INCREMENT,
`familyid` int(10) unsigned DEFAULT '0',
`companyid` int(11) DEFAULT '0',
`picture` varchar(250) DEFAULT NULL,
`stockpicture` varchar(1) DEFAULT 'N',
`firstname` varchar(250) DEFAULT NULL,
`lastname` varchar(250) DEFAULT NULL,
`nickname` varbinary(250) DEFAULT NULL,
`birthdate` date NOT NULL DEFAULT '0000-00-00',
`usecustomfee` varchar(1) NOT NULL DEFAULT 'N',
`usecustomproviderfee` varchar(1) NOT NULL DEFAULT 'N',
`customfee` decimal(10,2) DEFAULT '0.00',
`customfeetypecode` varchar(45) DEFAULT 'MONTH',
`customproviderfee` decimal(10,2) DEFAULT '0.00',
`customproviderfeetypecode` varchar(45) DEFAULT 'MONTH',
`usecustomchargeitem` varchar(1) DEFAULT 'N',
`customchargeitem` int(11) DEFAULT '0',
`dailyrate` decimal(10,2) DEFAULT '55.00',
`startdate` date DEFAULT NULL,
`enddate` date DEFAULT NULL,
`subsidynotrequired` char(1) NOT NULL DEFAULT 'Y',
`subsidychildid` varchar(250) DEFAULT NULL,
`subsidyapplicantid` varchar(250) DEFAULT NULL,
`subsidynote` text,
`waitingsince` date DEFAULT NULL,
`waitingroom` int(11) DEFAULT NULL,
`waitingtype` varchar(1) DEFAULT 'F',
`preferredstart` date DEFAULT NULL,
`registrationdate` date DEFAULT NULL,
`groupid` int(11) NOT NULL DEFAULT '0',
`providerisparent` varchar(1) NOT NULL DEFAULT 'N',
`attendingschool` char(1) NOT NULL DEFAULT 'N',
`schoolname` varchar(250) DEFAULT NULL,
`liveswithmother` char(1) NOT NULL DEFAULT 'Y',
`liveswithfather` char(1) NOT NULL DEFAULT 'Y',
`liveswithother` char(1) NOT NULL DEFAULT 'N',
`otherguardian` varchar(250) DEFAULT NULL,
`sex` char(1) NOT NULL DEFAULT 'M',
`note` text,
`archived` char(1) NOT NULL DEFAULT 'N',
`priorityid` int(11) DEFAULT '0',
`onlineregistration` varchar(1) NOT NULL DEFAULT 'N',
`onlineregistrationaccept` varchar(1) NOT NULL DEFAULT 'N',
`registrationconfirmed` varchar(1) NOT NULL DEFAULT 'N',
`registrationconfirmeddate` datetime DEFAULT NULL,
`createddate` datetime DEFAULT NULL,
`modifieddate` timestamp NOT NULL DEFAULT CURRENT_TIMESTAMP ON UPDATE CURRENT_TIMESTAMP,
`fullpart` varchar(1) DEFAULT 'F',
`parttimedays` int(11) DEFAULT '10',
`parttimedaystype` varchar(45) DEFAULT 'D',
`parttimedaystypecode` varchar(45) DEFAULT 'MONTH',
`program` varchar(45) DEFAULT 'daycare',
`registrationnote` varchar(2000) DEFAULT NULL,
`registrationnoteread` varchar(1) DEFAULT 'N',
`registrationsubsidy` varchar(45) DEFAULT 'noplan',
`registrationsubsidydate` datetime DEFAULT NULL,
`registrationsubsidyamount` decimal(10,2) DEFAULT '0.00',
PRIMARY KEY (`id`),
KEY `Familyid` (`familyid`),
KEY `companyid` (`companyid`),
KEY `startdate` (`startdate`),
KEY `enddate` (`enddate`),
KEY `roomid` (`groupid`),
KEY `providerisparent` (`providerisparent`)
) ENGINE=InnoDB AUTO_INCREMENT=93685 DEFAULT CHARSET=latin1;
家庭TABLE
CREATE TABLE `families` (
`id` int(10) unsigned NOT NULL AUTO_INCREMENT,
`accountnumber` varchar(100) DEFAULT NULL,
`name` varchar(245) NOT NULL COMMENT 'The account name will typically be the name of the parent responsible for payment',
`motherid` int(10) unsigned NOT NULL,
`fatherid` int(10) unsigned NOT NULL,
`balance` decimal(10,2) NOT NULL DEFAULT '0.00',
`notes` varchar(2000) DEFAULT NULL,
`companyid` int(10) unsigned NOT NULL,
`status` varchar(1) NOT NULL DEFAULT 'A',
`financialaidrequired` char(1) NOT NULL DEFAULT 'N',
`intakesurveyid` int(10) unsigned DEFAULT NULL,
`referralid` int(10) unsigned NOT NULL DEFAULT '0',
`registrationemailrequired` varchar(1) DEFAULT 'N',
`registrationemailsent` varchar(1) DEFAULT 'N',
`registrationemaildate` date DEFAULT NULL,
`registrationemailaddressfound` varchar(1) DEFAULT NULL,
`waitinglistemailrequired` varchar(1) DEFAULT 'N',
`waitinglistemailsent` varchar(1) DEFAULT 'N',
`waitinglistemaildate` date DEFAULT NULL,
`waitinglistemailaddressfound` varchar(1) DEFAULT NULL,
`activationemailrequired` varchar(1) DEFAULT 'N',
`activationemailsent` varchar(1) DEFAULT 'N',
`activationemaildate` date DEFAULT NULL,
`activationemailaddressfound` varchar(1) DEFAULT NULL,
`createddate` datetime DEFAULT NULL,
PRIMARY KEY (`id`),
KEY `companyid` (`companyid`),
KEY `intakesurveyid` (`intakesurveyid`),
KEY `status` (`status`)
) ENGINE=InnoDB AUTO_INCREMENT=9803007 DEFAULT CHARSET=latin1;
笔交易 TABLE
CREATE TABLE `transactions` (
`id` int(10) unsigned NOT NULL AUTO_INCREMENT,
`familyid` int(10) unsigned NOT NULL,
`date` datetime NOT NULL,
`transactiontype` varchar(1) NOT NULL DEFAULT 'C' COMMENT '''C'' = Charge, ''P'' = Payment',
`paymenttype` varchar(3) DEFAULT NULL COMMENT '''DBT'' = Debit, ''CSH'' = Cash, ''CRE'' = Credit Card, ''CHQ'' = Cheque, ''MNY'' = Money Order,''EFT'' = Electronic Funds Transfer',
`comment` varchar(500) DEFAULT NULL,
`amount` decimal(10,2) NOT NULL DEFAULT '0.00',
`reference` varchar(45) DEFAULT NULL,
`chargeitem` int(10) unsigned DEFAULT '0',
`taxdeductible` varchar(1) NOT NULL DEFAULT 'Y',
`payer` varchar(1) DEFAULT 'M',
`createddate` datetime DEFAULT NULL,
`modifieddate` timestamp NOT NULL DEFAULT CURRENT_TIMESTAMP ON UPDATE CURRENT_TIMESTAMP,
PRIMARY KEY (`id`),
KEY `Familyid` (`familyid`),
KEY `Transaction Type` (`transactiontype`),
KEY `Tax Deductible` (`taxdeductible`),
KEY `date` (`date`)
) ENGINE=InnoDB AUTO_INCREMENT=1013472 DEFAULT CHARSET=latin1 ROW_FORMAT=DYNAMIC;
尝试
EXPLAIN
SELECT
f.id, f.name, GROUP_CONCAT(DISTINCT (c.firstname)) children
FROM
families f,
children c,
transactions t
WHERE
f.companyid = 1170 AND f.id = t.familyid
AND f.id = c.familyid
AND t.transactiontype = 'P'
AND t.taxdeductible = 'Y'
AND YEAR(t.date) = 2017
AND f.status = 'A'
OR f.id = 9779432
GROUP BY f.id
ORDER BY name;
确保加载了正确的索引
你说你"have indexes on",但你每次查询只能使用1个索引,为你需要的查询制作1
索引。
此外,我建议永远不要使用倍数 from
,而是使用 JOIN
语句,以便能够针对连接的 table 索引和索引
请提供您的表架构。我们需要检查您有哪些索引。
同时您可以尝试 JOIN
表并删除 ORDER BY
。
据我所知,您只有一个 f.id = 9779432
,为什么要订购相同的价值?
检查您的 OR
条件,我已经将其转换为对我有意义的东西。您最初声明的广泛 OR 意味着您需要任何东西 YEAR(t.date) OR f.id = 9779432
对您来说有意义吗?
SELECT
f.id, f.name, GROUP_CONCAT(DISTINCT (c.firstname)) children
FROM
families f
INNER JOIN children c
ON f.id = c.familyid
INNER JOIN transactions t
ON f.id = t.familyid
AND t.transactiontype = 'P'
AND t.taxdeductible = 'Y'
AND YEAR(t.date) = 2017
WHERE
(f.companyid = 1170 OR f.id = 9779432)
AND f.status = 'A'
GROUP BY f.id;
最好使用 21 世纪的 JOIN 语法。
SELECT f.id, f.name, GROUP_CONCAT(DISTINCT (c.firstname)) children
FROM families f
JOIN children c ON f.id = c.familyid
JOIN transactions t ON f.id = t.familyid
WHERE f.companyid = 1170
AND t.transactiontype = 'P'
AND t.taxdeductible = 'Y'
AND YEAR(t.date) = 2017
AND status = 'A'
OR f.id = 9779432
GROUP BY f.id
ORDER BY name;
将 AND YEAR(t.date) = 2017
更改为 AND t.date >='2017-01-01 AND t.date < '2018-01-01'
。为什么?该过滤器子句的 YEAR()
形式不是 sargeable.
无法根据您的问题判断哪个 table 包含 status
列,而且它对性能 非常重要 。如果是 t.status
,则尝试在
上创建复合索引
transaction(status, transactiontype, taxdeductible, date, familyid)
然后在
上尝试复合索引
transaction(familyid, status, transactiontype, taxdeductible, date)
其中一个应该很有帮助。为什么?当满足您对 transaction
table 的查询时,MySQL 可以随机访问第一个符合条件的记录的索引:匹配所有 =
条件并具有最低值的记录date
。然后它可以按顺序扫描索引,直到找到最后一个符合条件的日期。
使用性能最佳的索引。
如果 status
列不在 transaction
table 中,则将其从该索引中取出。
假设你是这个意思(MySQL 会这样解释它):
(this AND that ...) OR (f.id=...)
让我们使用 UNION
而不是 OR
。 (OR
优化不佳。)
让我们也使用 'standard' JOIN...ON
而不是 'commajoin'。
我们不要在函数中隐藏列 (YEAR
);它禁止使用索引。
您已经因为没有说出哪个 table 包含 status
而受到指责。我看到 Hamoon 不小心丢失了 status
在 f
中的事实(?)。我会假设。
DISTINCT
不是一个函数,所以我去掉了它后面的括号。
我会选择 UNION DISTINCT
(较慢,但符合 OR
的语义)而不是 UNION ALL
(较快,但可能重复一行)。
我将 children
移动到外部 SELECT
以避免一些潜在的问题。
当 GROUP BY
和 ORDER BY
匹配时,查询可以 运行 更快。所以,假设 id
和 name
在逻辑上是联系在一起的,我认为这会给你相同的分组和排序:
GROUP BY name, id
ORDER BY name, id
将我所有的技巧放在一起:
SELECT x.id, x.name,
GROUP_CONCAT(DISTINCT c.firstname) children
FROM (
( SELECT f.id, f.name,
FROM families f
JOIN transactions t ON f.id = t.familyid
WHERE f.companyid = 1170
AND t.transactiontype = 'P'
AND t.taxdeductible = 'Y'
AND t.date >= '2017-01-01'
AND t.date < '2017-01-01' + INTERVAL 1 YEAR
AND f.status = 'A'
)
UNION DISTINCT
( SELECT f.id, f.name
FROM families f
WHERE f.id = 9779432
)
) AS x
JOIN children c ON x.id = c.familyid
GROUP BY x.name, x.id
ORDER BY x.name, x.id
您将需要这些索引。列顺序通常很重要。
f: I assume it has PRIMARY KEY(id)
f: (companyid, status) -- in either order
t: (familyid, transactiontype, taxdeductible, date)
t: (transactiontype, taxdeductible, date, familyid)
c: (familyid, firstname)
一些注意事项:
- 我为
t
提供了 2 个索引——同时提供这两个索引,从而让优化器决定是从 f
还是 t
开始。
- 一些索引是 'covering',从而提供额外的提升。
- 重新制定后,
GROUP_CONCAT
中的DISTINCT
可能是不必要的。
- 多个单列索引通常不如 'composite'(多列)索引有益。
这个select 语句运行起来超级慢。完成执行需要 10 多秒。可能会更长,但我不知道,因为与 MySQL 的连接超时。那是一个单独的问题。
代码如下:
SELECT
f.id, f.name, GROUP_CONCAT(DISTINCT (c.firstname)) children
FROM
families f,
children c,
transactions t
WHERE
f.companyid = 1170 AND f.id = t.familyid
AND f.id = c.familyid
AND t.transactiontype = 'P'
AND t.taxdeductible = 'Y'
AND YEAR(t.date) = 2017
AND status = 'A'
OR f.id = 9779432
GROUP BY f.id
ORDER BY name;
我在 families.companyid、children.familyid、transactions.transactiontype、transactions.taxdeductible 和 transactions.date 上有索引。
有什么理由不顾我的索引而进行完整的 table 扫描吗?或者还有其他原因导致此查询运行缓慢?
编辑:根据以下评论填写一些空白:
CHILDREN TABLE
CREATE TABLE `children` (
`id` int(11) NOT NULL AUTO_INCREMENT,
`familyid` int(10) unsigned DEFAULT '0',
`companyid` int(11) DEFAULT '0',
`picture` varchar(250) DEFAULT NULL,
`stockpicture` varchar(1) DEFAULT 'N',
`firstname` varchar(250) DEFAULT NULL,
`lastname` varchar(250) DEFAULT NULL,
`nickname` varbinary(250) DEFAULT NULL,
`birthdate` date NOT NULL DEFAULT '0000-00-00',
`usecustomfee` varchar(1) NOT NULL DEFAULT 'N',
`usecustomproviderfee` varchar(1) NOT NULL DEFAULT 'N',
`customfee` decimal(10,2) DEFAULT '0.00',
`customfeetypecode` varchar(45) DEFAULT 'MONTH',
`customproviderfee` decimal(10,2) DEFAULT '0.00',
`customproviderfeetypecode` varchar(45) DEFAULT 'MONTH',
`usecustomchargeitem` varchar(1) DEFAULT 'N',
`customchargeitem` int(11) DEFAULT '0',
`dailyrate` decimal(10,2) DEFAULT '55.00',
`startdate` date DEFAULT NULL,
`enddate` date DEFAULT NULL,
`subsidynotrequired` char(1) NOT NULL DEFAULT 'Y',
`subsidychildid` varchar(250) DEFAULT NULL,
`subsidyapplicantid` varchar(250) DEFAULT NULL,
`subsidynote` text,
`waitingsince` date DEFAULT NULL,
`waitingroom` int(11) DEFAULT NULL,
`waitingtype` varchar(1) DEFAULT 'F',
`preferredstart` date DEFAULT NULL,
`registrationdate` date DEFAULT NULL,
`groupid` int(11) NOT NULL DEFAULT '0',
`providerisparent` varchar(1) NOT NULL DEFAULT 'N',
`attendingschool` char(1) NOT NULL DEFAULT 'N',
`schoolname` varchar(250) DEFAULT NULL,
`liveswithmother` char(1) NOT NULL DEFAULT 'Y',
`liveswithfather` char(1) NOT NULL DEFAULT 'Y',
`liveswithother` char(1) NOT NULL DEFAULT 'N',
`otherguardian` varchar(250) DEFAULT NULL,
`sex` char(1) NOT NULL DEFAULT 'M',
`note` text,
`archived` char(1) NOT NULL DEFAULT 'N',
`priorityid` int(11) DEFAULT '0',
`onlineregistration` varchar(1) NOT NULL DEFAULT 'N',
`onlineregistrationaccept` varchar(1) NOT NULL DEFAULT 'N',
`registrationconfirmed` varchar(1) NOT NULL DEFAULT 'N',
`registrationconfirmeddate` datetime DEFAULT NULL,
`createddate` datetime DEFAULT NULL,
`modifieddate` timestamp NOT NULL DEFAULT CURRENT_TIMESTAMP ON UPDATE CURRENT_TIMESTAMP,
`fullpart` varchar(1) DEFAULT 'F',
`parttimedays` int(11) DEFAULT '10',
`parttimedaystype` varchar(45) DEFAULT 'D',
`parttimedaystypecode` varchar(45) DEFAULT 'MONTH',
`program` varchar(45) DEFAULT 'daycare',
`registrationnote` varchar(2000) DEFAULT NULL,
`registrationnoteread` varchar(1) DEFAULT 'N',
`registrationsubsidy` varchar(45) DEFAULT 'noplan',
`registrationsubsidydate` datetime DEFAULT NULL,
`registrationsubsidyamount` decimal(10,2) DEFAULT '0.00',
PRIMARY KEY (`id`),
KEY `Familyid` (`familyid`),
KEY `companyid` (`companyid`),
KEY `startdate` (`startdate`),
KEY `enddate` (`enddate`),
KEY `roomid` (`groupid`),
KEY `providerisparent` (`providerisparent`)
) ENGINE=InnoDB AUTO_INCREMENT=93685 DEFAULT CHARSET=latin1;
家庭TABLE
CREATE TABLE `families` (
`id` int(10) unsigned NOT NULL AUTO_INCREMENT,
`accountnumber` varchar(100) DEFAULT NULL,
`name` varchar(245) NOT NULL COMMENT 'The account name will typically be the name of the parent responsible for payment',
`motherid` int(10) unsigned NOT NULL,
`fatherid` int(10) unsigned NOT NULL,
`balance` decimal(10,2) NOT NULL DEFAULT '0.00',
`notes` varchar(2000) DEFAULT NULL,
`companyid` int(10) unsigned NOT NULL,
`status` varchar(1) NOT NULL DEFAULT 'A',
`financialaidrequired` char(1) NOT NULL DEFAULT 'N',
`intakesurveyid` int(10) unsigned DEFAULT NULL,
`referralid` int(10) unsigned NOT NULL DEFAULT '0',
`registrationemailrequired` varchar(1) DEFAULT 'N',
`registrationemailsent` varchar(1) DEFAULT 'N',
`registrationemaildate` date DEFAULT NULL,
`registrationemailaddressfound` varchar(1) DEFAULT NULL,
`waitinglistemailrequired` varchar(1) DEFAULT 'N',
`waitinglistemailsent` varchar(1) DEFAULT 'N',
`waitinglistemaildate` date DEFAULT NULL,
`waitinglistemailaddressfound` varchar(1) DEFAULT NULL,
`activationemailrequired` varchar(1) DEFAULT 'N',
`activationemailsent` varchar(1) DEFAULT 'N',
`activationemaildate` date DEFAULT NULL,
`activationemailaddressfound` varchar(1) DEFAULT NULL,
`createddate` datetime DEFAULT NULL,
PRIMARY KEY (`id`),
KEY `companyid` (`companyid`),
KEY `intakesurveyid` (`intakesurveyid`),
KEY `status` (`status`)
) ENGINE=InnoDB AUTO_INCREMENT=9803007 DEFAULT CHARSET=latin1;
笔交易 TABLE
CREATE TABLE `transactions` (
`id` int(10) unsigned NOT NULL AUTO_INCREMENT,
`familyid` int(10) unsigned NOT NULL,
`date` datetime NOT NULL,
`transactiontype` varchar(1) NOT NULL DEFAULT 'C' COMMENT '''C'' = Charge, ''P'' = Payment',
`paymenttype` varchar(3) DEFAULT NULL COMMENT '''DBT'' = Debit, ''CSH'' = Cash, ''CRE'' = Credit Card, ''CHQ'' = Cheque, ''MNY'' = Money Order,''EFT'' = Electronic Funds Transfer',
`comment` varchar(500) DEFAULT NULL,
`amount` decimal(10,2) NOT NULL DEFAULT '0.00',
`reference` varchar(45) DEFAULT NULL,
`chargeitem` int(10) unsigned DEFAULT '0',
`taxdeductible` varchar(1) NOT NULL DEFAULT 'Y',
`payer` varchar(1) DEFAULT 'M',
`createddate` datetime DEFAULT NULL,
`modifieddate` timestamp NOT NULL DEFAULT CURRENT_TIMESTAMP ON UPDATE CURRENT_TIMESTAMP,
PRIMARY KEY (`id`),
KEY `Familyid` (`familyid`),
KEY `Transaction Type` (`transactiontype`),
KEY `Tax Deductible` (`taxdeductible`),
KEY `date` (`date`)
) ENGINE=InnoDB AUTO_INCREMENT=1013472 DEFAULT CHARSET=latin1 ROW_FORMAT=DYNAMIC;
尝试
EXPLAIN
SELECT
f.id, f.name, GROUP_CONCAT(DISTINCT (c.firstname)) children
FROM
families f,
children c,
transactions t
WHERE
f.companyid = 1170 AND f.id = t.familyid
AND f.id = c.familyid
AND t.transactiontype = 'P'
AND t.taxdeductible = 'Y'
AND YEAR(t.date) = 2017
AND f.status = 'A'
OR f.id = 9779432
GROUP BY f.id
ORDER BY name;
确保加载了正确的索引
你说你"have indexes on",但你每次查询只能使用1个索引,为你需要的查询制作1
索引。
此外,我建议永远不要使用倍数 from
,而是使用 JOIN
语句,以便能够针对连接的 table 索引和索引
请提供您的表架构。我们需要检查您有哪些索引。
同时您可以尝试 JOIN
表并删除 ORDER BY
。
据我所知,您只有一个 f.id = 9779432
,为什么要订购相同的价值?
检查您的 OR
条件,我已经将其转换为对我有意义的东西。您最初声明的广泛 OR 意味着您需要任何东西 YEAR(t.date) OR f.id = 9779432
对您来说有意义吗?
SELECT
f.id, f.name, GROUP_CONCAT(DISTINCT (c.firstname)) children
FROM
families f
INNER JOIN children c
ON f.id = c.familyid
INNER JOIN transactions t
ON f.id = t.familyid
AND t.transactiontype = 'P'
AND t.taxdeductible = 'Y'
AND YEAR(t.date) = 2017
WHERE
(f.companyid = 1170 OR f.id = 9779432)
AND f.status = 'A'
GROUP BY f.id;
最好使用 21 世纪的 JOIN 语法。
SELECT f.id, f.name, GROUP_CONCAT(DISTINCT (c.firstname)) children
FROM families f
JOIN children c ON f.id = c.familyid
JOIN transactions t ON f.id = t.familyid
WHERE f.companyid = 1170
AND t.transactiontype = 'P'
AND t.taxdeductible = 'Y'
AND YEAR(t.date) = 2017
AND status = 'A'
OR f.id = 9779432
GROUP BY f.id
ORDER BY name;
将 AND YEAR(t.date) = 2017
更改为 AND t.date >='2017-01-01 AND t.date < '2018-01-01'
。为什么?该过滤器子句的 YEAR()
形式不是 sargeable.
无法根据您的问题判断哪个 table 包含 status
列,而且它对性能 非常重要 。如果是 t.status
,则尝试在
transaction(status, transactiontype, taxdeductible, date, familyid)
然后在
上尝试复合索引 transaction(familyid, status, transactiontype, taxdeductible, date)
其中一个应该很有帮助。为什么?当满足您对 transaction
table 的查询时,MySQL 可以随机访问第一个符合条件的记录的索引:匹配所有 =
条件并具有最低值的记录date
。然后它可以按顺序扫描索引,直到找到最后一个符合条件的日期。
使用性能最佳的索引。
如果 status
列不在 transaction
table 中,则将其从该索引中取出。
假设你是这个意思(MySQL 会这样解释它):
(this AND that ...) OR (f.id=...)
让我们使用 UNION
而不是 OR
。 (OR
优化不佳。)
让我们也使用 'standard' JOIN...ON
而不是 'commajoin'。
我们不要在函数中隐藏列 (YEAR
);它禁止使用索引。
您已经因为没有说出哪个 table 包含 status
而受到指责。我看到 Hamoon 不小心丢失了 status
在 f
中的事实(?)。我会假设。
DISTINCT
不是一个函数,所以我去掉了它后面的括号。
我会选择 UNION DISTINCT
(较慢,但符合 OR
的语义)而不是 UNION ALL
(较快,但可能重复一行)。
我将 children
移动到外部 SELECT
以避免一些潜在的问题。
当 GROUP BY
和 ORDER BY
匹配时,查询可以 运行 更快。所以,假设 id
和 name
在逻辑上是联系在一起的,我认为这会给你相同的分组和排序:
GROUP BY name, id
ORDER BY name, id
将我所有的技巧放在一起:
SELECT x.id, x.name,
GROUP_CONCAT(DISTINCT c.firstname) children
FROM (
( SELECT f.id, f.name,
FROM families f
JOIN transactions t ON f.id = t.familyid
WHERE f.companyid = 1170
AND t.transactiontype = 'P'
AND t.taxdeductible = 'Y'
AND t.date >= '2017-01-01'
AND t.date < '2017-01-01' + INTERVAL 1 YEAR
AND f.status = 'A'
)
UNION DISTINCT
( SELECT f.id, f.name
FROM families f
WHERE f.id = 9779432
)
) AS x
JOIN children c ON x.id = c.familyid
GROUP BY x.name, x.id
ORDER BY x.name, x.id
您将需要这些索引。列顺序通常很重要。
f: I assume it has PRIMARY KEY(id)
f: (companyid, status) -- in either order
t: (familyid, transactiontype, taxdeductible, date)
t: (transactiontype, taxdeductible, date, familyid)
c: (familyid, firstname)
一些注意事项:
- 我为
t
提供了 2 个索引——同时提供这两个索引,从而让优化器决定是从f
还是t
开始。 - 一些索引是 'covering',从而提供额外的提升。
- 重新制定后,
GROUP_CONCAT
中的DISTINCT
可能是不必要的。 - 多个单列索引通常不如 'composite'(多列)索引有益。