我怎样才能使这个 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_idsr_st_idsr_su_uid 上的索引应该会有所帮助 我建议阅读mysql.rjweb.org/doc.php/index_cookbook_mysql 以确定要在表中索引哪些列。使用 MySQL 的 EXPLAIN 应该有助于确定瓶颈所在。 大家好,感谢您的帮助,但我希望重组查询而不是添加索引。我在实际数据库中有不在此示例中的索引。我读过使用连接可能更有效,但我不太确定如何实现这一点? sr_tq_idsr_st_idsr_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 查询更有效的主要内容,如果未能解决你的问题,请参考以下文章

我怎样才能使这个二进制搜索代码更有效?

我怎样才能使这个工作与 UIScrollView?

使用来自其他类的公共静态 HashTable 的同步方法。我怎样才能使这个方法线程安全?

我怎样才能使这个 JSON 数据在 API 上可用?

我有以下测试代码。我怎样才能使这个条件为真

我怎样才能使这个 web3 python 脚本更快?