我怎样才能使这个 WHERE NOT EXISTS 查询更有效
Posted
技术标签:
【中文标题】我怎样才能使这个 WHERE NOT EXISTS 查询更有效【英文标题】:How can I make this WHERE NOT EXISTS query more efficient 【发布时间】:2021-05-26 18:27:29 【问题描述】:首先是表格
调查
CREATE TABLE `surveys` (
`survey_id` int(11) NOT NULL AUTO_INCREMENT,
`survey_name` varchar(100) COLLATE utf8_unicode_ci NOT NULL,
PRIMARY KEY (`survey_id`)
) ENGINE=InnoDB AUTO_INCREMENT=3 DEFAULT CHARSET=utf8 COLLATE=utf8_unicode_ci
INSERT INTO `surveys` (`survey_id`, `survey_name`) VALUES
(1, 's1'),
(2, 's2');
Survey_responses
CREATE TABLE `survey_responses` (
`sr_id` int(10) NOT NULL AUTO_INCREMENT,
`sr_text` varchar(100) COLLATE utf8_unicode_ci NOT NULL,
`sr_tq_id` int(10) NOT NULL,
`sr_st_id` int(10) NOT NULL,
`sr_su_uid` int(10) NOT NULL,
PRIMARY KEY (`sr_id`)
) ENGINE=InnoDB AUTO_INCREMENT=3 DEFAULT CHARSET=utf8 COLLATE=utf8_unicode_ci
INSERT INTO `survey_responses` (`sr_id`, `sr_text`, `sr_tq_id`, `sr_st_id`, `sr_su_uid`) VALUES
(1, 'a', 3, 2, 3),
(2, 'b', 4, 2, 3);
Survey_topics
CREATE TABLE `survey_topics` (
`st_id` int(10) NOT NULL AUTO_INCREMENT,
`st_name` varchar(100) COLLATE utf8_unicode_ci NOT NULL,
`st_survey_id` int(10) NOT NULL,
PRIMARY KEY (`st_id`)
) ENGINE=InnoDB AUTO_INCREMENT=7 DEFAULT CHARSET=utf8 COLLATE=utf8_unicode_ci
INSERT INTO `survey_topics` (`st_id`, `st_name`, `st_survey_id`) VALUES
(1, 't1', 1),
(2, 't2', 1),
(3, 't3', 1),
(4, 't4', 2),
(5, 't5', 2),
(6, 't6', 2);
Survey_users
CREATE TABLE `survey_users` (
`su_id` int(10) NOT NULL AUTO_INCREMENT,
`su_s_id` int(10) NOT NULL,
`su_uid` int(10) NOT NULL,
PRIMARY KEY (`su_id`)
) ENGINE=InnoDB AUTO_INCREMENT=4 DEFAULT CHARSET=utf8 COLLATE=utf8_unicode_ci
INSERT INTO `survey_users` (`su_id`, `su_s_id`, `su_uid`) VALUES
(1, 1, 1),
(2, 1, 2),
(3, 2, 2);
topic_questions
CREATE TABLE `topic_questions` (
`tq_id` int(11) NOT NULL AUTO_INCREMENT,
`tq_text` varchar(100) COLLATE utf8_unicode_ci NOT NULL,
`tq_st_id` int(10) NOT NULL,
PRIMARY KEY (`tq_id`)
) ENGINE=InnoDB AUTO_INCREMENT=5 DEFAULT CHARSET=utf8 COLLATE=utf8_unicode_ci
INSERT INTO `topic_questions` (`tq_id`, `tq_text`, `tq_st_id`) VALUES
(1, 'q1', 1),
(2, 'q2', 1),
(3, 'q3', 2),
(4, 'q4', 2);
用户
CREATE TABLE `users` (
`u_id` int(10) NOT NULL AUTO_INCREMENT,
`uname` varchar(100) COLLATE utf8_unicode_ci NOT NULL,
PRIMARY KEY (`u_id`)
) ENGINE=InnoDB AUTO_INCREMENT=3 DEFAULT CHARSET=utf8 COLLATE=utf8_unicode_ci
INSERT INTO `users` (`u_id`, `uname`) VALUES
(1, 'Bob'),
(2, 'Gary');
现在是查询,
SELECT * FROM `surveys`
INNER JOIN survey_users ON survey_users.su_s_id = surveys.survey_id
INNER JOIN survey_topics ON survey_topics.st_survey_id = surveys.survey_id
INNER JOIN topic_questions ON survey_topics.st_id = topic_questions.tq_st_id
WHERE NOT EXISTS (
SELECT * FROM survey_responses a WHERE a.sr_tq_id = topic_questions.tq_id AND a.sr_st_id = survey_topics.st_id AND a.sr_su_uid = survey_users.su_uid
)
基本上,我试图获取一份调查列表,其中即使是一个用户也错过了调查回复,即使这只是针对 1 个主题的 1 个问题。
此查询运行良好,但在包含数千个调查、主题、用户、问题和回复的数据库上运行速度极慢。
请有人提供一个更快的查询,我已经读过使用左连接而不是 WHERE NOT EXISTS 更有效?
感谢帮助,我在实际数据库中有索引,这个例子没有任何索引。
谢谢。
【问题讨论】:
一个好的开始是索引您在 SELECT 语句中使用的列,例如,索引sr_tq_id
可能会有所帮助。
在survey_responses
字段sr_tq_id
、sr_st_id
、sr_su_uid
上的索引应该会有所帮助
我建议阅读mysql.rjweb.org/doc.php/index_cookbook_mysql 以确定要在表中索引哪些列。使用 MySQL 的 EXPLAIN
应该有助于确定瓶颈所在。
大家好,感谢您的帮助,但我希望重组查询而不是添加索引。我在实际数据库中有不在此示例中的索引。我读过使用连接可能更有效,但我不太确定如何实现这一点?
在sr_tq_id
、sr_st_id
、sr_su_uid
上添加组合索引并仅使用 SELECT 1 而不是 pf *
【参考方案1】:
考虑NOT IN vs. NOT EXISTS vs. LEFT JOIN / IS NULL 的其他变体,其性能可能会有所不同:
左连接/空
...
LEFT JOIN survey_responses sr
ON sr.sr_tq_id = topic_questions.tq_id
AND sr.sr_st_id = survey_topics.st_id
AND sr.sr_su_uid = survey_users.su_uid
WHERE sr.sr_tq_id IS NULL
AND sr.sr_st_id IS NULL
AND sr.sr_su_uid IS NULL
NOT IN (MySQL似乎支持多列IN)
...
WHERE (topic_questions.tq_id, survey_topics.st_id, survey_users.su_uid)
NOT IN (
SELECT a.sr_tq_id, a.sr_st_id, a.sr_su_uid
FROM survey_responses
)
NOT IN (由于多列而使用 CTE)
WITH sub AS (
SELECT a.sr_tq_id, a.sr_st_id, a.sr_su_uid
FROM survey_responses
)
SELECT
...
WHERE topic_questions.tq_id NOT IN (SELECT a.str_tq_id FROM sub)
AND survey_topics.st_id NOT IN (SELECT a.sr_st_id FROM sub)
AND survey_users.su_uid NOT IN (SELECT a.sr_su_uid FROM sub)
【讨论】:
【参考方案2】:也许尝试对 topic_questions 表进行 LEFT OUTER JOIN 并在该表中包含问题的列。然后,您可以检查这些列中的 NULL 以确定用户尚未回答的问题。
另外,您可能想尝试将 INNER 联接切换为 LEFT。它应该处理得更快。如果您需要过滤掉未回答的项目、空用户等,请尝试在 WHERE 子句中执行此操作。
【讨论】:
【参考方案3】:需要复合索引:
survey_users: (su_s_id, su_uid)
survey_topics: (st_survey_id, st_id)
topic_questions: (tq_st_id, tq_id)
a: (sr_tq_id, sr_su_uid, sr_st_id)
【讨论】:
以上是关于我怎样才能使这个 WHERE NOT EXISTS 查询更有效的主要内容,如果未能解决你的问题,请参考以下文章