應用程式開發人員長期以來一直相信將資料庫作業放在預存程序中可以得到最高的效能並能防止 SQL 隱碼攻擊。他們還認為這些優勢值得他們付出與資料庫邏輯的維護、測試和將資料庫邏輯遷移到不同供應商相關的額外成本。近年來,隨著開發人員開始質疑這些長久以來的假設,潮流已經從存儲過程轉向物件關係對映(ORM),如 Hibernate 或 Entity Framework。
《預存程序是過時的工具嗎?》文章重點講述了一些避免預存程序而推薦應用程式程式碼和 ORM 的原因。本週,我們將探討上面的兩個迷思,看看它們今天是否仍然站得往腳。
效能優勢
在互聯網出現的初期,為了提高效能,將網路流量降至最低是常見的做法。而預存程序只需要將程序名稱和參數傳輸到伺服器,而不需要完整的 SQL 陳述式,這有助於減少網路流量。考慮到某些生產用查詢的複雜性和長度,這可能帶來巨大效益。但今天,無論你從這種方法中獲得什麼效益,都很容易被一個事實所抵消:你很可能會在同一個請求中使用相同的參數呼叫相同的程序兩到三次。與此同時,ORM 會查看其恆等對映並識別出它已經擷取到了該結果集,因此無需進行另一次。此外,應該注意的是,快取的預存程序在伺服器上,而(ad-hoc)SQL 不是的說法。這是 Frans Bouma 在他的部落格文章《Stored Procedures are bad, m'kay?》中打破的迷思。
預存程序和 SQL 隱碼攻擊
人們常說,預存程序提供了防止 SQL 隱碼攻擊的保護,因為它們將資料與指令分開。這是真的,只要開發人員不在預存程序中使用動態 SQL,原始字串透過取代預留位置的輸入參數傳遞。 以下是一個寫得很差的程序,它確切地展示了如何將資料庫暴露於 SQL 隱碼攻擊的危險中:
create procedure GetStudents(@School nvarchar(50)) as begin declare @sql nvarchar(100) set @sql = 'SELECT STUDENT FROM SCHOOL WHERE SCHOOL LIKE ' + @School exec @sql end
你可以使用參數化查詢編寫消除 SQL 隱碼攻擊漏洞的 SQL。使用程式語言(如 python、TypeScript 或 Java)編寫的參數化查詢(如下所示)可以清理使用者輸入,以便可以在査詢中安全使用:
String sql = "SELECT STUDENT FROM SCHOOL WHERE SCHOOL LIKE ? "; PreparedStatement prepStmt = conn.prepareStatement(sql); prepStmt.setString(1, "Waterloo%"); ResultSet rs = prepStmt.executeQuery();
可見防止 SQL 隱碼攻擊不是預存程序本身的好處,只是不將 SQL 字串串連在一起的慣例。
預告
本篇文章探討了一些關於預存程序長久以來的假設,這些假設在今天並不完全成立。雖然他們本身並不是跳出預存程序潮流框架的充分理由,但強烈建議你是時候重新評估你的應用程式架構了。