面試官:你們的項目中竟然還在用多表關聯!
本文中我們繼續介紹Hash Join(哈希連接),以及從技術原理的角度上給出最終結論 —— 我們還是應該在數據庫中合理使用多表關聯查詢,而不是放到應用程序中。
圖片
我們在上一篇文章中說過,從MySQL 8.0.20開始,就不再使用緩存塊嵌套循環連接了,將以前使用緩存塊嵌套循環連接的場景全部改為哈希連接。
因為在絕大多數被驅動表沒有創建索引的場景,哈希連接比緩存塊嵌套循環連接性能更高。
MySQL表連接算法
哈希連接(Hash Join)
市面上所有支持哈希連接算法的數據庫,無論是OLTP數據庫中的Oracle,還是OLAP數據庫中的ClickHouse、Doris,其實現方式都是將小表加載到內存形成哈希表,再通過遍歷大表數據的方式與哈希表進行匹配,并返回匹配結果。
MySQL的實現方式也不例外,我們以下面的SQL語句舉例:
SELECT * FROM product p INNER JOIN order o
ON p.id = o.product_id WHERE p.id in(1,2);
圖片
Using where; Using join buffer (hash join)
SELECT * FROM product p INNER JOIN order o
ON p.id = o.product_id WHERE p.id in(1,2);
SELECT * FROM t1 JOIN t2 ON t1.c1 < t2.c1
SELECT * FROM t1 WHERE t1.c1 IN (SELECT t2.c2 FROM t2)
Antijoin:
SELECT * FROM t2 WHERE NOT EXISTS (SELECT * FROM t1 WHERE
t1.c1 = t2.c1)
Left outer join:
SELECT * FROM t1 LEFT JOIN t2 ON t1.c1 = t2.c1
Right outer join:
SELECT * FROM t1 RIGHT JOIN t2 ON t1.c1 = t2.c1
應用程序表連接算法
接下來開始聊重頭戲了,我們看一下,如果真的禁止使用SQL語句進行多表關聯,而是把各表的數據讀到應用程序中來,再由程序進行數據merge操作,這該如何實現呢?
圖片
步驟其實很簡單,從product表和order表中分別讀出數據后,在應用程序代碼中要么通過一個大嵌套循環來進行表數據的連接匹配,要么通過一個HashMap進行表的連接列匹配,跟MySQL中的實現基本上大同小異。
數據庫 VS 應用程序
那么,到底是放在數據庫中實現多表關聯查詢比較好,還是放到應用程序中比較好呢?
贊成后者的同學會說,數據庫是一個極為寶貴稀缺的資源,而應用服務器我們可以以集群部署的方式無限擴容。因此,將多表關聯操作放到應用服務器中可以減輕數據庫的壓力。
對于這種說法,我只能說:“呵呵,數據庫的主從復制了解一下?不僅可以搭從庫,還可以搭二級從庫呢?!?/p>
還有一種贊成后者說法是,單表查詢在應用程序中進行merge,這樣更有利于后續的維護,并且代碼復用性高。
原因在于,SQL語句寫得越簡單,那就越容易被復用,而多表關聯的復雜SQL只能有一種應用場景。
這種說法就相當于,把香河肉餅變成面粉、肉餡和食用油之后復用性高了,因為面粉可以蒸饅頭烙餅、肉餡可以做包子和餃子,而食用油則可以炒任何菜。
最離譜的一種說法是,放到應用服務器中進行merge操作,性能會高一些。
對于這種說法,我只想說,到底是梁靜茹給你的勇氣,張信哲給你的信仰,還是五月天給你的倔強呢?
你能在表連接算法優化上比數據庫研發者做得更好?我怎么就這么不信呢?
接下來,我說說贊成后者的原因吧,有如下三點:
(1)研發效率更高,畢竟一條SQL語句就可以搞定的事情,沒必要寫完SQL再寫代碼,兩邊兒一起忙活。
(2)代碼性能更快,如果驅動表有1萬條數據,而被驅動表也有1萬條,兩者關聯到一起后返回10條數據。這種場景下,到底是哪種方式更快一些,應該顯然易見了吧?
畢竟把兩表中的各1萬條數據通過網絡返回給應用服務器,再加載到內存中進行merge,這些都是比較耗費性能的操作。
(3)可用性提升,如果驅動表有10萬條數據,而被驅動表也有10萬條,哪怕在QPS為個位數的情況下,都可能將數據庫的網卡打滿,從而出現系統不可用的情況。
這種故障場景,我還真的在實際工作中遇到過,當時整整掛了一個多小時呢。