これはおそらく、SO に投稿された歴史の中で最も長いクエリです。基本的に、これが私が望むことを行うために必要なクエリの正しいアイデアであるかどうか、そしてそれが非常に遅いことを考えると、どのように高速化するかを知りたいです。
データの正規化などについて読んでいますが、これは正規化されすぎているのでしょうか? ユーザーが何十万ものアイテムを持つことができることを考えると、これを実行する最速の方法とスケーリングが必要です。
私は6つのテーブルを持っています
--
-- 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 を取得し、それに関連付けられているデータ、電子メール、および IP を取得する必要があります。user_items は、列「名前」とバージョンによって data、data_emails、および data_ips にリンクします。
data_emails と data_nameservers は、それぞれ email_id/ip_id = ID を持つ電子メールと IP にリンクします
複数の結合は行の乗算になるため、ネストされたサブクエリを使用する必要がありました。ユーザーはアイテムに関連付けられた複数の電子メールと IP を持つことができるため、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 の出力は次のとおりです。
+----+-------------+-------------+--------+------- ---------------+--------------+---------+--------- -----------------------------------+-------+------ ---------------------------+ | | ID | select_type | テーブル | タイプ | 可能な_キー | キー | key_len | 参照 | 行 | 行 エクストラ | +----+-------------+-------------+--------+------- ---------------+--------------+---------+--------- -----------------------------------+-------+------ ---------------------------+ | | 1 | プライマリ | ユーザーアイテム | 参照 | フォルダ | フォルダ | 4 | 定数 | 1139 | 一時的な使用; ファイルソートの使用 | | | 1 | プライマリ | データ | 参照 | バージョン、名前_バージョン | name_version | 261 | gua.user_items.name,gua.user_items.version | 1 | | | | | 1 | プライマリ | <派生2> | すべて | ヌル | ヌル | ヌル | ヌル | 5591 | | | | | 1 | プライマリ | <派生4> | すべて | ヌル | ヌル | ヌル | ヌル | 5443 | | | | | 4 | 派生 | data_ips | すべて | ヌル | ヌル | ヌル | ヌル | 16301 | 一時的な使用; ファイルソートの使用 | | | 4 | 派生 | <派生5> | すべて | ヌル | ヌル | ヌル | ヌル | 7533 | | | | | 5 | 派生 | data_ips | すべて | ヌル | ヌル | ヌル | ヌル | 16301 | 一時的な使用; ファイルソートの使用 | | | 5 | 派生 | ips | eq_ref | プライマリ | プライマリ | 4 | gua.data_ips.ns_id | 1 | | | | | 2 | 派生 | data_email | すべて | ヌル | ヌル | ヌル | ヌル | 10138 | 一時的な使用; ファイルソートの使用 | | | 2 | 派生 | <派生3> | すべて | ヌル | ヌル | ヌル | ヌル | 5061 | | | | | 3 | 派生 | data_email | すべて | ヌル | ヌル | ヌル | ヌル | 10138 | 一時的な使用; ファイルソートの使用 | | | 3 | 派生 | メール | eq_ref | プライマリ | プライマリ | 4 | gua.data_emails.email_id | 1 | | | +----+-------------+-------------+--------+------- ---------------+--------------+---------+--------- -----------------------------------+-------+------ ---------------------------+