这可能是历史上发布到 SO 的最长查询。本质上,我想知道这对于执行我想要的查询是否是正确的想法,并且鉴于它非常慢,我将如何加快它的速度。
我一直在阅读有关数据规范化等的信息,也许这太规范化了?鉴于用户可以拥有数十万个项目,我需要最快的方式来执行它,并且它可以扩展。
我有六张桌子
--
-- Table structure for table `emails`
--
CREATE TABLE IF NOT EXISTS `emails` (
`ID` int(5) NOT NULL AUTO_INCREMENT,
`email` varchar(255) COLLATE utf8_unicode_ci NOT NULL,
`sent` smallint(1) NOT NULL DEFAULT '0',
PRIMARY KEY (`ID`)
) ENGINE=MyISAM DEFAULT CHARSET=utf8 COLLATE=utf8_unicode_ci AUTO_INCREMENT=5062 ;
-- --------------------------------------------------------
--
-- Table structure for table `ips`
--
CREATE TABLE IF NOT EXISTS `ips` (
`ID` int(11) NOT NULL AUTO_INCREMENT,
`ip` varchar(255) NOT NULL,
PRIMARY KEY (`ID`)
) ENGINE=InnoDB DEFAULT CHARSET=latin1 AUTO_INCREMENT=7534 ;
-- --------------------------------------------------------
--
-- Table structure for table `user_items`
--
CREATE TABLE IF NOT EXISTS `user_items` (
`ID` int(10) NOT NULL AUTO_INCREMENT COMMENT 'Allows sorting by last added..',
`name` varchar(255) NOT NULL,
`owner` varchar(255) NOT NULL,
`folder` int(10) NOT NULL,
`version` int(5) NOT NULL,
PRIMARY KEY (`ID`),
KEY `name` (`name`),
KEY `folder` (`folder`)
) ENGINE=InnoDB DEFAULT CHARSET=latin1 AUTO_INCREMENT=10431 ;
-- --------------------------------------------------------
--
-- Table structure for table `data`
--
CREATE TABLE IF NOT EXISTS `data` (
`ID` int(10) NOT NULL AUTO_INCREMENT,
`name` varchar(255) NOT NULL,
`info` varchar(255) DEFAULT NULL,
`inserted` varchar(255) NOT NULL,
`version` int(5) NOT NULL,
PRIMARY KEY (`ID`),
KEY `inserted` (`inserted`),
KEY `version` (`version`),
KEY `name_version` (`name`,`version`)
) ENGINE=InnoDB DEFAULT CHARSET=latin1 AUTO_INCREMENT=7207 ;
-- --------------------------------------------------------
--
-- Table structure for table `data_emails`
--
CREATE TABLE IF NOT EXISTS `data_emails` (
`ID` int(11) NOT NULL AUTO_INCREMENT,
`email_id` int(5) NOT NULL,
`name` varchar(255) NOT NULL,
`version` int(5) NOT NULL,
`time` int(255) NOT NULL,
PRIMARY KEY (`ID`),
KEY `version` (`version`),
KEY `name_version` (`name`,`version`)
) ENGINE=InnoDB DEFAULT CHARSET=latin1 AUTO_INCREMENT=9849 ;
-- --------------------------------------------------------
--
-- Table structure for table `data_ips`
--
CREATE TABLE IF NOT EXISTS `data_ips` (
`ID` int(5) NOT NULL AUTO_INCREMENT,
`ns_id` int(5) NOT NULL,
`name` varchar(255) NOT NULL,
`version` int(5) NOT NULL,
`time` int(255) NOT NULL,
PRIMARY KEY (`ID`),
KEY `version` (`version`),
KEY `name_version` (`name`,`version`)
) ENGINE=InnoDB DEFAULT CHARSET=latin1 AUTO_INCREMENT=17988 ;
我需要实现的目标如下。我需要获取每个 user_item 并获取与之关联的数据、电子邮件和 ips。user_items 通过列 'name' 和 version 链接到 data、data_emails 和 data_ips。
data_emails 和 data_nameservers 分别链接到 emails 和 ips,其中 email_id/ip_id = ID
因为多个连接会导致行乘法,所以我不得不使用嵌套子查询。因为一个用户可以有多个与一个项目相关联的电子邮件和 ips,所以我使用 group_concat 对所有特定行进行分组。然后我在我的 PHP 中分解了这一列 - 这本身似乎效率低下,但我看不到其他方法吗?
我玩过索引,但是由于复杂的连接以及我对此很陌生的事实,我不确定要拥有哪些索引。任何人都可以建议索引并解释它们吗?
SELECT user_items.ID, user_items.name, user_items.update,data.*, x.emails,
x.e_status, y.ip, x.email_counts, y.ip_counts
FROM user_items
LEFT JOIN data AS data
ON (data.name = user_items.name AND data.version = user_items.version)
LEFT JOIN (
SELECT data_emails.name, data_emails.version,
GROUP_CONCAT( b.email SEPARATOR ',' ) as emails,
GROUP_CONCAT( b.sent SEPARATOR ',' ) as e_status,
GROUP_CONCAT( b.email_count SEPARATOR ',' ) as email_counts
FROM data_emails
LEFT JOIN (
SELECT emails.ID, emails.email, emails.sent, data_emails.name,
data_emails.version, data_emails.email_id,
COUNT(data_emails.ID) as email_count
FROM data_emails
LEFT JOIN emails ON (data_emails.email_id = emails.ID)
GROUP BY data_emails.email_id
) b ON (data_emails.email_id = b.ID)
GROUP BY data_emails.name
) x ON (data.name = x.name AND x.version = user_items.version)
LEFT JOIN (
SELECT data_ips.name,data_ips.version,
GROUP_CONCAT( c.ip SEPARATOR ',' ) as ip,
GROUP_CONCAT( c.ips_count SEPARATOR ',' ) as ip_counts
FROM data_ips
LEFT JOIN (
SELECT ips.ID, ips.ip, data_ips.name, data_ips.version,
data_ips.ip_id, COUNT(data_ips.ID) as ips_count
FROM data_ips
LEFT JOIN ips ON (data_ips.ip_id = ips.ID)
GROUP BY data_ips.ip_id
) c ON (data_ips.ip_id = c.ID)
GROUP BY data_ips.name
) y ON (data.name = y.name AND y.version = user_items.version)
WHERE user_items.folder = '2'
GROUP BY user_items.name
为了完整起见,Explain 对查询的输出如下:
+----+-------------+-------------+--------+------- -------------+--------------+---------+--------- ------------------------------------+--------+------ ---------------+ | 编号 | 选择类型 | 表| 类型 | 可能的键 | 关键 | key_len | 参考 | 行 | 额外 | +----+-------------+-------------+--------+------- -------------+--------------+---------+--------- ------------------------------------+--------+------ ---------------+ | 1 | 初级 | 用户项目 | 参考 | 文件夹 | 文件夹 | 4 | 常量 | 第1139章 使用临时的;使用文件排序 | | 1 | 初级 | 数据 | 参考 | 版本,名称版本 | 名称版本 | 261 | gua.user_items.name,gua.user_items.version | 1 | | | 1 | 初级 | <派生2> | 全部 | 空 | 空 | 空 | 空 | 5591 | | | 1 | 初级 | <派生4> | 全部 | 空 | 空 | 空 | 空 | 5443 | | | 4 | 派生 | 数据_ips | 全部 | 空 | 空 | 空 | 空 | 16301 | 使用临时的;使用文件排序 | | 4 | 派生 | <派生5> | 全部 | 空 | 空 | 空 | 空 | 7533 | | | 5 | 派生 | 数据_ips | 全部 | 空 | 空 | 空 | 空 | 16301 | 使用临时的;使用文件排序 | | 5 | 派生 | IPS | eq_ref | 初级 | 初级 | 4 | gua.data_ips.ns_id | 1 | | | 2 | 派生 | 数据电子邮件 | 全部 | 空 | 空 | 空 | 空 | 10138 | 使用临时的;使用文件排序 | | 2 | 派生 | <派生3> | 全部 | 空 | 空 | 空 | 空 | 5061 | | | 3 | 派生 | 数据电子邮件 | 全部 | 空 | 空 | 空 | 空 | 10138 | 使用临时的;使用文件排序 | | 3 | 派生 | 电子邮件 | eq_ref | 初级 | 初级 | 4 | gua.data_emails.email_id | 1 | | +----+-------------+-------------+--------+------- -------------+--------------+---------+--------- ------------------------------------+--------+------ ---------------+