【发布时间】:2014-08-03 01:40:42
【问题描述】:
我希望就如何使用外连接优化此查询的性能获得一些建议。首先我会解释我想要做什么,然后我会展示代码和结果。
我有一个包含所有客户帐户列表的帐户表。我有一个数据使用表,可以跟踪每个客户使用了多少数据。在多台服务器上运行的后端进程每天将记录插入到 datausage 表中,以跟踪该服务器上每个客户当天发生的使用量。
后端流程是这样工作的 - 如果当天该服务器上没有针对某个帐户的活动,则不会为该帐户写入任何记录。如果有活动,则用当天的"LogDate" 写入一条记录。这发生在多台服务器上。因此,总的来说,datausage 表最终没有行(该客户每天根本没有活动)、一行(当天活动仅在一台服务器上)或多行(当天活动在多台服务器上)。
我们需要生成一份报告,列出所有客户以及他们在特定日期范围内的使用情况。一些客户可能根本没有使用(datausage 表中没有任何内容)。一些客户可能在当前期间完全没有使用(但在其他期间使用)。
无论是否有任何使用情况(曾经,或在选定的时间段内),我们都需要将帐户表中的每个客户都列在报告中,即使他们没有显示使用情况。因此,这似乎需要外部连接。
这是我正在使用的查询:
SELECT
Accounts.accountID as AccountID,
IFNULL(Accounts.name,Accounts.accountID) as AccountName,
AccountPlans.plantype as AccountType,
Accounts.status as AccountStatus,
date(Accounts.created_at) as Created,
sum(IFNULL(datausage.Core,0) + (IFNULL(datausage.CoreDeluxe,0) * 3)) as 'CoreData'
FROM `Accounts`
LEFT JOIN `datausage` on `Accounts`.`accountID` = `datausage`.`accountID`
LEFT JOIN `AccountPlans` on `AccountPlans`.`PlanID` = `Accounts`.`PlanID`
WHERE
(
(`datausage`.`LogDate` >= '2014-06-01' and `datausage`.`LogDate` < '2014-07-01')
or `datausage`.`LogDate` is null
)
GROUP BY Accounts.accountID
ORDER BY `AccountName` asc
此查询大约需要 2 秒才能运行。 但是,如果“or datausage.LogDate is NULL”被删除,运行只需要 0.3 秒。 但是,我似乎必须在其中包含该子句,因为没有使用的帐户被排除在结果之外如果不出现则设置。
这是表格数据:
| id | select_type | table | type | possible_keys | key | key_len | ref | rows | Extra |
+----+-------------+--------------+--------+---------------------------------------------------------+---------+---------+----------------------+------- +----------------------------------------------------+
| 1 | SIMPLE | Accounts | ALL | PRIMARY,accounts_planid_foreign,accounts_cardid_foreign | NULL | NULL | NULL | 57 | Using temporary; Using filesort |
| 1 | SIMPLE | datausage | ALL | NULL | NULL | NULL | NULL | 96805 | Using where; Using join buffer (Block Nested Loop) |
| 1 | SIMPLE | AccountPlans | eq_ref | PRIMARY | PRIMARY | 4 | mydb.Accounts.planID | 1 | NULL |
Accounts 表的索引如下:
| Table | Non_unique | Key_name | Seq_in_index | Column_name | Collation | Cardinality | Sub_part | Packed | Null | Index_type | Comment | Index_comment |
+----------+------------+-------------------------+--------------+-------------+-----------+-------------+----------+--------+------+------------+---------+---------------+
| Accounts | 0 | PRIMARY | 1 | accountID | A | 57 | NULL | NULL | | BTREE | | |
| Accounts | 1 | accounts_planid_foreign | 1 | planID | A | 5 | NULL | NULL | | BTREE | | |
| Accounts | 1 | accounts_cardid_foreign | 1 | cardID | A | 0 | NULL | NULL | YES | BTREE | | |
datausage表上的索引如下:
| Table | Non_unique | Key_name | Seq_in_index | Column_name | Collation | Cardinality | Sub_part | Packed | Null | Index_type | Comment | Index_comment |
+------------+------------+----------+--------------+-------------+-----------+-------------+----------+--------+------+------------+---------+---------------+
| datausage | 0 | PRIMARY | 1 | UsageID | A | 96805 | NULL | NULL | | BTREE | | |
我尝试在 datausage 上创建不同的索引以查看是否有帮助,但没有任何帮助。我尝试了AccountID 上的索引、AccountID、LogData 上的索引和LogData、AccountID 上的索引以及LogData 上的索引。这些都没有任何区别。
我还尝试将UNION ALL 与其中一个查询与 logdata 范围一起使用,而另一个查询恰好在 logdata 为空的地方,但结果大致相同(实际上有点糟糕)。
有人可以帮助我了解可能发生的情况以及我可以优化查询执行时间的方法吗?谢谢!!
更新:应 Philipxy 的要求,这里是表定义。请注意,我删除了一些与此查询无关的列和约束,以帮助保持尽可能紧凑和干净。
CREATE TABLE `Accounts` (
`accountID` varchar(25) NOT NULL,
`name` varchar(255) COLLATE utf8_unicode_ci DEFAULT NULL,
`status` int(11) NOT NULL,
`planID` int(10) unsigned NOT NULL DEFAULT '1',
`created_at` timestamp NOT NULL DEFAULT '0000-00-00 00:00:00'
PRIMARY KEY (`accountID`),
KEY `accounts_planid_foreign` (`planID`),
KEY `acctname_id_ndx` (`name`,`accountID`),
CONSTRAINT `accounts_planid_foreign` FOREIGN KEY (`planID`) REFERENCES `AccountPlans` (`planID`)
) ENGINE=InnoDB DEFAULT CHARSET=utf8 COLLATE=utf8_unicode_ci
CREATE TABLE `datausage` (
`UsageID` int(11) NOT NULL AUTO_INCREMENT,
`Core` int(11) DEFAULT NULL,
`CoreDelux` int(11) DEFAULT NULL,
`AccountID` varchar(25) DEFAULT NULL,
`LogDate` date DEFAULT NULL
PRIMARY KEY (`UsageID`),
KEY `acctusage` (`AccountID`,`LogDate`)
) ENGINE=MyISAM AUTO_INCREMENT=104303 DEFAULT CHARSET=latin1
CREATE TABLE `AccountPlans` (
`planID` int(10) unsigned NOT NULL AUTO_INCREMENT,
`name` varchar(150) COLLATE utf8_unicode_ci NOT NULL,
`params` text COLLATE utf8_unicode_ci NOT NULL,
`created_at` timestamp NOT NULL DEFAULT '0000-00-00 00:00:00',
`plantype` varchar(25) COLLATE utf8_unicode_ci NOT NULL,
PRIMARY KEY (`planID`),
KEY `acctplans_id_type_ndx` (`planID`,`plantype`)
) ENGINE=InnoDB AUTO_INCREMENT=10 DEFAULT CHARSET=utf8 COLLATE=utf8_unicode_ci
【问题讨论】:
-
表格定义是什么?特别是,哪些列可以为空,在您计时的情况下为空?
-
发布足以让 sqlfiddle.com 供我们测试的 SQL 表定义会很有帮助。
-
请为您的查询和一些答案查询更新您对当前键的解释。如果您的定义以 sqlfiddle(错别字和顺序)加上示例数据值运行,这样回答者可以生成解释,这很有帮助。重新引擎:对于基线,尝试使用 FK datausage accountid 到帐户的所有 innodb。 (没有 mysql innodb-isam fks。)FK 可能非常重要。
-
嗨,philipxy - 您想要哪个查询的 EXPLAIN 输出?我原来的一个,还是这里发布的其他一个(如果是,是哪一个)?我需要一个多星期才能更新系统以将表从 myISAM 转换为 InnoDB。我认为这可能是主要问题。并且将 LogData 定义为 NOT NULL 可能会有所帮助。当我能够在系统上试用时,我将在月底将这些结果发回这里。
-
嗨,philipxy 和所有 - 我终于能够继续工作了。我尝试的第一件事是将数据使用表从 myIASM 转换为 InnoDB。令人惊讶的是,它没有任何用途。然后我将 LogDate 列更改为 NOT NULL,这也没有帮助。我创建了您要求的 SQL 小提琴:sqlfiddle.com/#!9/f3259/4/0。请注意,此处提供的所有版本的查询(包括我自己的)都具有相同的 EXPLAIN 计划。请注意,虽然这个查询速度很快,但它的数据使用量只有十几行。在我们的真实系统中,它有近 100,000 行。谢谢!
标签: mysql sql database query-optimization outer-join