Group_by 并在表自联接 5-6 次时具有 vs 联接

cod*_*ool 5 performance join database-design database-agnostic

现在这个问题来找我的情况。架构是

    Table User_Read_Book
       user_id | book_id 
Run Code Online (Sandbox Code Playgroud)

现在我想获得阅读某些书籍的用户。假设给我读过第 1 和第 2 本书的用户。要测试的书数最多可以达到 10。

我写的第一个查询:

Select user_id from User_Read_Book Where book_id In (1,2) Group by user_id Having count(book_id) = 2
Run Code Online (Sandbox Code Playgroud)

第二个查询:

Select  user_id from User_Read_Book as U join User_Read_Book as U1 On
 U.user_id = U1.user_id And U1.book_id = 1 where U.book_id = 2
Run Code Online (Sandbox Code Playgroud)

正如这个答案中所说的/sf/answers/43532401/中它更喜欢在 group by 的情况下加入并让我进行第二次查询。

但我的问题是,当匹配的数字很大时,哪个查询更好。说当你必须找到读过 7 本书的用户时

   Having Count(book_id) = 7
   or
   6 joins to the same table.
Run Code Online (Sandbox Code Playgroud)

我知道在对大型实时数据进行测试时,最好回答这个问题。专家们对此有何看法?

ype*_*eᵀᴹ 4

对于 7 本书,我的猜测是 7 join 比GROUP BY / HAVING.

但这取决于 DBMS、版本、优化器的设置、数据库设置、您拥有的 RAM、硬盘的性能、索引碎片、服务器的整体压力以及可能的其他几个参数。更重要的是,即使前面的所有设置仍然如此,这取决于您的数据(及其分布)和查询的具体参数。例如,如果这 7 本书是哈利波特的 7 本书,并且您的所有用户都是哈利波特的粉丝,那么速度GROUP BY/HAVING可能会更快。

另外,当您可以进行测试时,您不应该相信其他人,无论他们(看起来)多么专家。为什么不使用服务器中的数据和设置来测试两种方式的性能,使用可变数量的书籍(和标题)?

另请检查这个问题(使用类似的查询),其中显示了其他几种(超过 10 种)方法(并在 PostgrSQL 中进行了基准测试):How to filter SQL results in a has-many-through relation


更新

对 7 个连接通常比“猜测”更好的解释GROUP BY / HAVING

想象一下您有 100 万用户和大约 100 万本书。现在,平均一个用户已经阅读了 100 本书(在你的数据库中,完全虚构的数据和分布)。因此,该表大约有 100M 行。

现在,GROUP BY查询将具有类似WHERE book_id IN (1,2,3,4,5,6,7). 让我们假设它book_id=1是最受欢迎的(《圣经》),大约有 10 万读者,而其他 6 个不太受欢迎,每个都有 100 到 1000 名读者。这会将要分组的行限制为 100K 到 106K 之间。这(大致)转换为 SQL 引擎从正确的索引读取 106K 数据,然后执行GROUP BY user_id. 因此,(它可能会选择使用索引(user_id, book_id)),并且会对COUNT(book_id)- 进行大约 100K 次计算,并拒绝任何不是 的计算7

在7个JOIN查询中,它有更多的选项。优化器可能会选择使用另一个索引,即该(book_id, user_id)索引。想象一下,“取出”这个大索引的 7 个较小的部分,部分(1, user_id)(记住:其中有 100K 数据(user_ids)),部分(2, user_id)(这里少于 1000 条数据),...,直到部分(7, user_id)(少于 1000 条数据)数据也在这里)。所以现在,它必须以某种方式组合这 7 个索引部分(这只是 7 个用户 ID 列表),并找到哪些用户 ID 位于所有 7 个列表中。有一些聪明的算法可以做到这一点,而无需对 7 个列表进行整体读取(全面扫描)。请注意,即使是先组合 6 个较小列表的愚蠢算法,最终也可能只得到少数用户 ID(假设只有 1 个)。要查找这 1 个 user_id 是否在大(第一个)列表中,只需要进行二分搜索(记住它不是真正的列表,它是一个索引,这就是索引的好处,您可以在其中快速搜索)。因此,即使只有 100 个 user_ids,在 100K 大列表/索引中进行 100 次搜索也只需要少于 100*17 的操作 ( log(100K) ~= 17)。这是 1700 次操作,比GROUP BY100K 次操作要少得多。不需要COUNT(*)

因此,使用联接,如果大多数书籍不是很受欢迎(或者只有一本书,我们很幸运),则查询将非常有效,因为它将必须查看极少数位置的索引。
(另一个想法是,使用 Group By 方法,查询在拒绝它们之前计算出所有那些已阅读 1 或 2 或 ... 或 6 本书的用户已阅读了多少本书。但我们不关心他们是否阅读 1 或 6。我们只需要知道他们是否已阅读全部 7 部分!)

当然,如果所选的 7 本书都很受欢迎,情况就不同了。现在,7 个索引部分都很大,将它们组合起来可能比使用GROUP BY一个索引仅使用一次传递的方法效率更低。
(另一种想法说 Group By 现在很高效,因为几乎所有 Count 计算都是 a 7,因此浪费了极少量的计算)