mysql - 需要帮助优化外连接 SQL 查询

标签 mysql sql database query-optimization outer-join

我希望得到一些关于如何优化我使用外连接的查询的性能的建议。首先,我将解释我正在尝试做什么,然后我将展示代码和结果。

我有一个帐户表,其中包含所有客户帐户的列表。我有一个数据使用表,用于跟踪每个客户使用的数据量。在多个服务器上运行的后端进程每天将记录插入数据使用表,以跟踪该服务器上每个客户当天的使用量。

后端流程是这样工作的——如果那天某个帐户在该服务器上没有事件,则不会为该帐户写入任何记录。如果有事件,一条记录将写入当天的 "LogDate"。这发生在多台服务器上。因此,总的来说,数据使用表最终没有任何行(该客户每天根本没有事件)、一行(当天事件仅在一台服务器上)或多行(当天事件在多台服务器上)。

我们需要生成一份报告,列出所有客户,以及他们在特定日期范围内的使用情况。一些客户可能根本没有使用(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      |         |               |

我尝试在数据使用上创建不同的索引以查看是否有帮助,但没有任何效果。我尝试了 AccountID 的索引,AccountID 的索引,LogDataLogData 的索引, AccountID,以及 LogData 上的索引。这些都没有任何区别。

我还尝试将 UNION ALL 与 logdata 范围内的一个查询和另一个 logdata 为 null 的查询一起使用,但结果大致相同(实际上更糟)。

有人可以帮助我了解可能发生的情况以及优化查询执行时间的方法吗?谢谢!!

更新:应 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 

最佳答案

首先,您可以通过将 where 子句移动到 on 子句来简化查询:

SELECT a.accountID as AccountID, coalesce(a.name, a.accountID) as AccountName,
       ap.plantype as AccountType, a.status as AccountStatus,
       date(a.created_at) as Created,
       sum(coalesce(du.Core, 0) + (coalesce(du.CoreDeluxe, 0) * 3)) as CoreData
FROM Accounts a LEFT JOIN 
     datausage du
     on a.accountID = du.`accountID` AND
        du.`LogDate` >= '2014-06-01' and du.`LogDate` < '2014-07-01'
LEFT JOIN 
     AccountPlans ap
     on ap.`PlanID` = a.`PlanID`
GROUP BY a.accountID 
ORDER BY AccountName asc ;

(我还引入了表别名以使查询更易于阅读。)

这个版本应该更好地利用索引,因为它消除了 where 子句中的 or。但是,它仍然不会对外部排序使用索引。以下可能更好:

SELECT a.accountID as AccountID, coalesce(a.name, a.accountID) as AccountName,
       ap.plantype as AccountType, a.status as AccountStatus,
       date(a.created_at) as Created,
       sum(coalesce(du.Core, 0) + (coalesce(du.CoreDeluxe, 0) * 3)) as CoreData
FROM Accounts a LEFT JOIN 
     datausage du
     on a.accountID = du.`accountID` AND
        du.LogDate >= '2014-06-01' and du.LogDate < '2014-07-01'LEFT JOIN 
     AccountPlans ap
     on ap.PlanID = a.PlanID
GROUP BY a.accountID 
ORDER BY a.name, a.accountID ;

为此,我推荐以下索引:

Accounts(name, AccountId)
Datausage(AccountId, LogDate)
AccountPlans(PlanId, PlanType)

关于mysql - 需要帮助优化外连接 SQL 查询,我们在Stack Overflow上找到一个类似的问题: https://stackoverflow.com/questions/24189340/

相关文章:

mysql不等于不工作

sql - 使用两列以上的聚合函数

sql - 如何一次将多个 JSON 文件插入到 postgresql 表中?

mysql - 使用 play2 框架运行 JUnit 测试时出现 mysql 连接问题

java - java 13.0.1、mysql 5.7 我应该选择哪个版本的 Connector/J?

php - 根据结束日期每月有效订阅?

php - MYSQLI - 每次运行增量查询 WHERE 值

javascript - 如何从 javascript 调用 python 脚本?

database - Cassandra:请求未在 rpc_timeout 内完成

database - 将 BLOB 数据插入对象类型 - Oracle