SQL是如何在資料庫中執行的?

2022-01-12 06:36:22 字數 2195 閱讀 3144

對很多開發者來說,資料庫就是個黑盒子,你會寫 sql,會用資料庫,但不知道盒子裡面到底是怎麼一回事兒,這樣你只能機械地去記住別人告訴你的那些優化規則,卻不知道為什麼要遵循這些規則,也就談不上靈活運用。

資料庫的服務端,可以劃分為執行器 (execution engine) 和儲存引擎 (storage engine) 兩部分。

我們通過乙個例子來看一下,執行器是如何來解析執行一條 sql 的。

資料庫收到查詢請求後,需要先解析 sql 語句,把這一串文字解析成便於程式處理的結構化資料:

這個樹太複雜,我只畫了主要的部分,你大致看一下,能理解這個 sql 的語法樹長什麼樣就行了。執行器解析這個 ast 之後,會生成乙個邏輯執行計畫。所謂的執行計畫,可以簡單理解為如何一步一步地執行查詢和計算,最終得到執行結果的乙個分步驟的計畫。這個邏輯執行計畫是這樣的:

和 sql、ast 不同的是,這個邏輯執行計畫已經很像可以執行的程式**了。你看上面這個執行計畫,很像我們程式語言的函式呼叫棧,外層的方法呼叫內層的方法。所以,要理解這個執行計畫,得從內往外看。

最內層的 2 個 logicaltablescan 的含義是,把 users 和 orders 這兩個表的資料都讀出來。

然後拿這兩個表所有資料做乙個 logicaljoin,join 的條件就是第 0 列 (u.id) 等於第 6 列 (o.user_id)。

然後再執行乙個 logicalfilter 過濾器,過濾條件是第 0 列 (u.id) 大於 50。

最後,做乙個 logicalproject 投影,只保留第 0(user_id)、1(user_name)、5(order_id) 三列。這裡「投影 (project)」的意思是,把不需要的列過濾掉。

把這個邏輯執行計畫翻譯成**,然後按照順序執行,就可以正確地查詢出資料了。但是,按照上面那個執行計畫,需要執行 2 個全表掃瞄,然後再把 2 個表的所有資料做乙個 join 操作,這個效能是非常非常差的。

優化的總體思路是,在執行計畫中,盡早地減少必須處理的資料量。也就是說,盡量在執行計畫的最內層減少需要處理的資料量。看一下簡單優化後的邏輯執行計畫:

對比原始的邏輯執行計畫,這裡我們做了兩點簡單的優化:

到這裡,執行器只是在邏輯層面分析 sql,優化查詢的執行邏輯,我們執行計畫中操作的資料,仍然是表、行和列。在資料庫中,表、行、列都是邏輯概念,所以,這個執行計畫叫「邏輯執行計畫」。執行查詢接下來的部分,就需要涉及到資料庫的物理儲存結構了。

資料真正儲存的時候,無論在磁碟裡,還是在記憶體中,都沒法直接儲存這種帶有行列的二維表。資料庫中的二維表,實際上是怎麼儲存的呢?這就是儲存引擎負責解決的問題,儲存引擎主要功能就是把邏輯的表行列,用合適的物理儲存結構儲存到檔案中。不同的資料庫,它們的物理儲存結構是完全不一樣的,這也是各種資料庫之間巨大效能差距的根本原因。

在 innodb 中,資料表的物理儲存結構是以主鍵為關鍵字的 b+ 樹,每一行資料直接就儲存在 b+ 樹的葉子節點上。比如,上面的訂單表組織成 b+ 樹,是這個樣的:

物理執行計畫同樣可以根據資料的物理儲存結構、是否存在索引以及資料多少等各種因素進行優化。這一塊兒的優化規則同樣是非常複雜的,比如,我們可以把對使用者樹的全樹掃瞄再按照主鍵過濾這兩個步驟,優化為對樹的範圍查詢

最終,按照優化後的物理執行計畫,一步一步地去執行查詢和計算,就可以得到 sql 的查詢結果了。

理解資料庫執行 sql 的過程,以及不同儲存引擎中的資料和索引的物理儲存結構,對於正確使用和優化 sql 非常有幫助:

因為表的每個索引儲存的都是主鍵的值,過長的主鍵會導致每乙個索引都很大。

資料庫在對物理執行計畫優化的時候,評估發現不走索引,直接全表掃瞄是更優的選擇。

SQL如何在資料庫間複製表

方法一 db1 tb1 db2 tb2 選擇db1 到表的列表那裡 選擇tb1表 右鍵 所有任務 資料匯出 下一步 選擇你要匯出的資料庫db1 下一步 選擇你要匯入的資料庫db2 下一步 選擇你要導的表 前面畫勾 tb1,後面對應的是新資料庫的表名tb2 預設是相同表名,可修改 方法二 sql語句 ...

如何在資料庫中使用索引

一 給資訊表建立索引 資訊表為 建立索引 create index 索引名 on 表名 列名.mysql create index idx lname pinyin on employee lname pinyin 顯示索引資訊 show index from 表名 mysql show index...

如何在資料庫動態建表

動態建表首先需要了解statement類 statement 物件用 connection 的方法createstatement 建立,例如 建立連線物件 connection connection dbutil.getconnection 建立statement物件 statement state...