Go 連接 MySQL之 MySQL 預處理
一、ChatGPT 關于 MySQL 預處理 的回答
問:什么是MySQL 的預處理 具體執行程序時什么
ChatGPT 答:
MySQL的預處理是一種在執行SQL陳述句之前,先進行編譯和優化的機制,它將SQL陳述句分成兩個階段:預處理階段和執行階段,
具體的執行程序如下:
- 預處理階段:
- 應用程式發送SQL陳述句給MySQL服務器,將其中的引數部分用占位符(如?)代替,形成一個預處理陳述句,
- MySQL服務器對預處理陳述句進行決議、編譯和優化,
- 在這個階段,MySQL會檢查語法的正確性,創建執行計劃,以及進行權限驗證等,
- 執行階段:
- 在預處理完成后,應用程式可以多次執行這個預處理陳述句,每次執行時只需提供具體的引數值,
- 應用程式將具體的引數值系結到預處理陳述句的占位符上,
- MySQL服務器使用系結的引數值執行預處理陳述句,并回傳執行結果,
預處理的好處包括:
- 提高執行效率:預處理陳述句只需要編譯一次,后續的執行可以重復使用,避免了每次執行都要重新決議和編譯的開銷,
- 防止SQL注入:通過使用引數化查詢,將引數值與SQL陳述句分開處理,可以防止惡意用戶利用輸入資料進行SQL注入攻擊,
- 簡化引數處理:預處理陳述句可以通過占位符來接收引數,避免了手動拼接SQL陳述句的繁瑣和潛在的錯誤,
總結而言,MySQL的預處理是一種提前編譯和優化SQL陳述句的機制,它將SQL陳述句分為預處理和執行兩個階段,提高了執行效率和安全性,并簡化了引數處理程序,
問:Go實作MySQL預處理
ChatGPT 答:
在 Go 中,可以使用官方提供的 database/sql 包來實作 MySQL 的預處理,下面是一個示例代碼,展示了如何在 Go 中進行 MySQL 預處理:
import (
"database/sql"
"fmt"
"log"
_ "github.com/go-sql-driver/mysql"
)
func main() {
db, err := sql.Open("mysql", "username:password@tcp(hostname:port)/database")
if err != nil {
log.Fatal(err)
}
defer db.Close()
// 準備預處理陳述句
stmt, err := db.Prepare("INSERT INTO users (name, age) VALUES (?, ?)")
if err != nil {
log.Fatal(err)
}
defer stmt.Close()
// 執行預處理陳述句
name := "John"
age := 30
_, err = stmt.Exec(name, age)
if err != nil {
log.Fatal(err)
}
fmt.Println("Insert successful!")
}
在上述代碼中,我們使用 sql.Open 函式建立與 MySQL 資料庫的連接,然后使用 db.Prepare 函式準備預處理陳述句,其中 SQL 陳述句中的引數位置用占位符 ? 表示,接下來,通過 stmt.Exec 函式執行預處理陳述句,并傳入具體的引數值,最后,我們在 main 函式中進行了錯誤處理,并列印了插入成功的訊息,
請確保在代碼中填入正確的 MySQL 連接資訊(用戶名、密碼、主機名、埠號和資料庫名),以便與目標資料庫建立連接,同時,請根據實際的預處理陳述句和引數進行適當的修改,
注意:上述示例代碼僅演示了預處理的基本流程,并沒有包含完整的錯誤處理和其他功能,在實際應用中,建議加入適當的錯誤處理、事務處理等機制,以確保代碼的健壯性和安全性,
問:MySQL預處理適用于什么場景?
ChatGPT 答:
MySQL預處理適用于以下場景:
- 執行頻繁的重復查詢:如果應用程式需要多次執行相同的SQL查詢,但只是引數值不同,預處理可以減少服務器的作業量,預處理陳述句只需要編譯一次,后續的執行可以重復使用,避免了每次都重新決議和編譯SQL陳述句的開銷,
- 引數化查詢:當需要執行帶有引數的查詢時,預處理可以提高安全性并防止SQL注入攻擊,通過使用引數化查詢,將引數值與SQL陳述句分開處理,可以確保輸入資料被正確地轉義和處理,避免惡意用戶利用輸入資料進行攻擊,
- 提高性能:預處理可以在編譯和優化階段對SQL陳述句進行處理,生成更高效的執行計劃,這可以提高查詢的性能和回應時間,特別是對于復雜查詢或涉及大量資料的查詢,
- 簡化引數處理:預處理陳述句可以通過占位符(如?)來接收引數值,避免了手動拼接SQL陳述句的繁瑣和潛在的錯誤,同時,使用預處理陳述句可以直接將引數值系結到占位符上,而無需手動轉義和處理引數值,
總結而言,MySQL預處理適用于需要執行重復查詢、引數化查詢、提高性能和簡化引數處理的場景,它可以減少服務器的作業量,提高查詢的安全性和性能,并簡化代碼的撰寫和維護,
二、Go 實作 MySQL 預處理
查詢
package main
import (
"database/sql"
"fmt"
"time"
_ "github.com/go-sql-driver/mysql" // 匿名匯入 自動執行 init()
)
var db *sql.DB
func initMySQL() (err error) {
//DSN (Data Source Name)
dsn := "root:12345678@tcp(127.0.0.1:3306)/sql_test"
// 注意:要初始化全域的 db 物件,不要新宣告一個 db 變數
db, err = sql.Open("mysql", dsn) // 只對格式進行校驗,并不會真正連接資料庫
if err != nil {
return err
}
// Ping 驗證與資料庫的連接是否仍處于活動狀態,并在必要時建立連接,
err = db.Ping()
if err != nil {
fmt.Printf("connect to db failed, err: %v\n", err)
return err
}
// 數值需要根據業務具體情況來確定
db.SetConnMaxLifetime(time.Second * 10) // 設定可以重用連接的最長時間
db.SetConnMaxIdleTime(time.Second * 5) // 設定連接可能處于空閑狀態的最長時間
db.SetMaxOpenConns(200) // 設定與資料庫的最大打開連接數
db.SetMaxIdleConns(10) // 設定空閑連接池中的最大連接數
return nil
}
type user struct {
id int
age int
name string
}
// 預處理查詢
func prepareQueryDemo(id int) {
sqlStr := "SELECT id, name, age FROM user WHERE id > ?"
stmt, err := db.Prepare(sqlStr)
if err != nil {
fmt.Printf("prepare failed, err: %v\n", err)
return
}
defer stmt.Close()
rows, err := stmt.Query(id)
if err != nil {
fmt.Printf("query failed, err: %v\n", err)
return
}
defer rows.Close()
// 回圈讀取結果集中的資料
for rows.Next() {
var u user
err := rows.Scan(&u.id, &u.name, &u.age)
if err != nil {
fmt.Printf("scan failed, err: %v\n", err)
return
}
fmt.Printf("id: %d name: %s age: %d\n", u.id, u.name, u.age)
}
}
func main() {
if err := initMySQL(); err != nil {
fmt.Printf("connect to db failed, err: %v\n", err)
}
// 檢查完錯誤之后執行,確保 db 不為 nil
// Close() 用來釋放資料庫連接相關的資源
// Close 將關閉資料庫并阻止啟動新查詢,關閉,然后等待服務器上已開始處理的所有查詢完成,
defer db.Close()
fmt.Println("connect to database success")
// db.xx() 去使用資料庫操作...
// MySQL預處理查詢多行資料
prepareQueryDemo(0)
}
運行
Code/go/mysql_demo via ?? v1.20.3 via ?? base
? go run main.go
connect to database success
id: 1 name: 小喬 age: 16
id: 2 name: 小喬 age: 12
Code/go/mysql_demo via ?? v1.20.3 via ?? base
?
插入、更新和洗掉操作的預處理基本一樣,下面是插入示例
package main
import (
"database/sql"
"fmt"
"time"
_ "github.com/go-sql-driver/mysql" // 匿名匯入 自動執行 init()
)
var db *sql.DB
func initMySQL() (err error) {
//DSN (Data Source Name)
dsn := "root:12345678@tcp(127.0.0.1:3306)/sql_test"
// 注意:要初始化全域的 db 物件,不要新宣告一個 db 變數
db, err = sql.Open("mysql", dsn) // 只對格式進行校驗,并不會真正連接資料庫
if err != nil {
return err
}
// Ping 驗證與資料庫的連接是否仍處于活動狀態,并在必要時建立連接,
err = db.Ping()
if err != nil {
fmt.Printf("connect to db failed, err: %v\n", err)
return err
}
// 數值需要根據業務具體情況來確定
db.SetConnMaxLifetime(time.Second * 10) // 設定可以重用連接的最長時間
db.SetConnMaxIdleTime(time.Second * 5) // 設定連接可能處于空閑狀態的最長時間
db.SetMaxOpenConns(200) // 設定與資料庫的最大打開連接數
db.SetMaxIdleConns(10) // 設定空閑連接池中的最大連接數
return nil
}
type user struct {
id int
age int
name string
}
// 預處理插入
func prepareInsertDemo() {
sqlStr := "INSERT INTO user(name, age) VALUES (?,?)"
// 為以后的查詢或執行創建預準備陳述句,
// 可以從回傳的陳述句并發運行多個查詢或執行,
// 當不再需要陳述句時,呼叫方必須呼叫陳述句的 Close 方法,
stmt, err := db.Prepare(sqlStr)
if err != nil {
fmt.Printf("prepare failed, err:%v\n", err)
return
}
defer stmt.Close()
_, err = stmt.Exec("昭君", 12)
if err != nil {
fmt.Printf("insert failed, err:%v\n", err)
return
}
_, err = stmt.Exec("黛玉", 16)
if err != nil {
fmt.Printf("insert failed, err:%v\n", err)
return
}
fmt.Println("insert success.")
}
func main() {
if err := initMySQL(); err != nil {
fmt.Printf("connect to db failed, err: %v\n", err)
}
// 檢查完錯誤之后執行,確保 db 不為 nil
// Close() 用來釋放資料庫連接相關的資源
// Close 將關閉資料庫并阻止啟動新查詢,關閉,然后等待服務器上已開始處理的所有查詢完成,
defer db.Close()
fmt.Println("connect to database success")
// db.xx() 去使用資料庫操作...
// MySQL預處理 插入資料
prepareInsertDemo()
}
運行
Code/go/mysql_demo via ?? v1.20.3 via ?? base
? go run main.go
connect to database success
insert success.
Code/go/mysql_demo via ?? v1.20.3 via ?? base
?
SQL 查詢 插入結果
mysql> select * from user;
+----+--------+------+
| id | name | age |
+----+--------+------+
| 1 | 小喬 | 16 |
| 2 | 小喬 | 12 |
| 5 | 昭君 | 12 |
| 6 | 黛玉 | 16 |
+----+--------+------+
4 rows in set (0.00 sec)
mysql>
三、SQL注入問題
問:什么是SQL注入問題?
ChatGPT 答:
SQL注入(SQL Injection)是一種常見的安全漏洞,指的是攻擊者通過在應用程式的用戶輸入中插入惡意的SQL代碼,從而利用應用程式對資料庫的不當處理,執行未經授權的資料庫操作,
SQL注入攻擊通常發生在使用動態構建SQL陳述句的應用程式中,例如Web應用程式,攻擊者利用應用程式沒有正確驗證、過濾或轉義用戶輸入的資料,將惡意的SQL代碼插入到查詢陳述句中,
攻擊者可以利用SQL注入問題進行各種惡意行為,包括但不限于以下情況:
- 繞過身份驗證:通過注入惡意的SQL代碼,攻擊者可以繞過應用程式的身份驗證機制,以管理員或其他特權用戶的身份執行操作,
- 資料泄露:攻擊者可以通過注入SELECT陳述句,獲取應用程式未授權訪問的資料,包括敏感資訊(如用戶密碼、個人資訊等),
- 資料篡改:攻擊者可以通過注入UPDATE或DELETE陳述句,修改或洗掉資料庫中的資料,破壞資料完整性,
- 執行任意代碼:攻擊者可以通過注入惡意的SQL代碼,執行任意的資料庫操作或其他系統命令,導致系統崩潰、服務拒絕或其他安全問題,
為了防止SQL注入攻擊,開發人員應該采取以下措施:
- 使用引數化查詢或預處理陳述句:使用引數化查詢或預處理陳述句可以將引數值與SQL陳述句分開處理,確保輸入資料被正確轉義和處理,避免了直接拼接用戶輸入到SQL陳述句中的問題,
- 輸入驗證與過濾:對用戶輸入的資料進行嚴格的驗證和過濾,確保只接受符合預期的資料格式和范圍,并在必要時進行轉義處理,
- 最小權限原則:應用程式在連接資料庫時,使用具有最低權限的資料庫用戶,僅具備執行必要操作的權限,
- 安全配置:確保資料庫和應用程式的安全配置,包括禁用不必要的資料庫功能、限制網路訪問、定期更新資料庫軟體等,
通過采取這些安全措施,可以有效減輕SQL注入攻擊的風險,提升應用程式的安全性,
注意:不要自己拼接SQL陳述句,不要相信用戶輸入的內容是安全的、合法的,
例子
以下是一個示例,展示了一個存在SQL注入漏洞的情況,并使用Go語言來演示如何預防注入問題:
import (
"database/sql"
"fmt"
"log"
_ "github.com/go-sql-driver/mysql"
)
func main() {
db, err := sql.Open("mysql", "username:password@tcp(hostname:port)/database")
if err != nil {
log.Fatal(err)
}
defer db.Close()
// 假設用戶輸入的引數
username := "admin' OR 1=1 --"
password := "123456"
// 構建SQL查詢陳述句
query := fmt.Sprintf("SELECT * FROM users WHERE username = '%s' AND password = '%s'", username, password)
// 執行查詢
rows, err := db.Query(query)
if err != nil {
log.Fatal(err)
}
defer rows.Close()
// 處理查詢結果
for rows.Next() {
// 處理每一行資料
var id int
var username string
var password string
err := rows.Scan(&id, &username, &password)
if err != nil {
log.Fatal(err)
}
fmt.Println(id, username, password)
}
if err = rows.Err(); err != nil {
log.Fatal(err)
}
}
在上述代碼中,我們假設用戶輸入的username引數為admin' OR 1=1 --,這是一種常見的注入嘗試,然后,我們使用字串插值的方式構建了一個SQL查詢陳述句,將用戶輸入的引數直接嵌入到SQL陳述句中,
這種構建SQL陳述句的方式存在SQL注入漏洞,攻擊者可以在username引數中插入惡意的SQL代碼,使查詢陳述句的邏輯被破壞,在本例中,1=1條件將始終為真,繞過了正常的身份驗證,
為了防止SQL注入,我們應該使用預處理陳述句或引數化查詢,以下是修復SQL注入漏洞的示例代碼:
import (
"database/sql"
"fmt"
"log"
_ "github.com/go-sql-driver/mysql"
)
func main() {
db, err := sql.Open("mysql", "username:password@tcp(hostname:port)/database")
if err != nil {
log.Fatal(err)
}
defer db.Close()
// 假設用戶輸入的引數
username := "admin' OR 1=1 --"
password := "123456"
// 使用預處理陳述句
stmt, err := db.Prepare("SELECT * FROM users WHERE username = ? AND password = ?")
if err != nil {
log.Fatal(err)
}
defer stmt.Close()
// 執行查詢
rows, err := stmt.Query(username, password)
if err != nil {
log.Fatal(err)
}
defer rows.Close()
// 處理查詢結果
for rows.Next() {
// 處理每一行資料
var id int
var username string
var password string
err := rows.Scan(&id, &username, &password)
if err != nil {
log.Fatal(err)
}
fmt.Println(id, username, password)
}
if err = rows.Err
例子二
package main
import (
"database/sql"
"fmt"
"time"
_ "github.com/go-sql-driver/mysql" // 匿名匯入 自動執行 init()
)
var db *sql.DB
func initMySQL() (err error) {
//DSN (Data Source Name)
dsn := "root:12345678@tcp(127.0.0.1:3306)/sql_test"
// 注意:要初始化全域的 db 物件,不要新宣告一個 db 變數
db, err = sql.Open("mysql", dsn) // 只對格式進行校驗,并不會真正連接資料庫
if err != nil {
return err
}
// Ping 驗證與資料庫的連接是否仍處于活動狀態,并在必要時建立連接,
err = db.Ping()
if err != nil {
fmt.Printf("connect to db failed, err: %v\n", err)
return err
}
// 數值需要根據業務具體情況來確定
db.SetConnMaxLifetime(time.Second * 10) // 設定可以重用連接的最長時間
db.SetConnMaxIdleTime(time.Second * 5) // 設定連接可能處于空閑狀態的最長時間
db.SetMaxOpenConns(200) // 設定與資料庫的最大打開連接數
db.SetMaxIdleConns(10) // 設定空閑連接池中的最大連接數
return nil
}
type user struct {
id int
age int
name string
}
// sql注入示例
func sqlInjectDemo(name string) {
sqlStr := fmt.Sprintf("select id, name, age from user where name='%s'", name)
fmt.Printf("SQL: %s\n", sqlStr)
var u user
err := db.QueryRow(sqlStr).Scan(&u.id, &u.name, &u.age)
if err != nil {
fmt.Printf("exec failed, err: %v\n", err)
return
}
fmt.Printf("user: %#v\n", u)
}
func main() {
if err := initMySQL(); err != nil {
fmt.Printf("connect to db failed, err: %v\n", err)
}
// 檢查完錯誤之后執行,確保 db 不為 nil
// Close() 用來釋放資料庫連接相關的資源
// Close 將關閉資料庫并阻止啟動新查詢,關閉,然后等待服務器上已開始處理的所有查詢完成,
defer db.Close()
fmt.Println("connect to database success")
// db.xx() 去使用資料庫操作...
// SQL 注入
//sqlInjectDemo("黛玉")
// select id, name, age from user where name='xxx ' or 1=1#'
sqlInjectDemo("xxx ' or 1=1#")
}
運行
? go run main.go
connect to database success
SQL:select id, name, age from user where name='黛玉'
user: main.user{id:6, age:16, name:"黛玉"}
Code/go/mysql_demo via ?? v1.20.3 via ?? base
? go run main.go
connect to database success
SQL: select id, name, age from user where name='xxx ' or 1=1#'
user: main.user{id:1, age:16, name:"小喬"}
Code/go/mysql_demo via ?? v1.20.3 via ?? base
?
四、SQL 占位符
不同的資料庫使用不同的占位符語法,下面是一些常見資料庫的占位符語法:
- MySQL 使用問號(?)作為占位符,
示例:
SELECT * FROM table WHERE column = ?
- PostgreSQL 使用美元符號加數字($1、$2、$3...)作為占位符,
示例:
SELECT * FROM table WHERE column = $1
- SQLite 使用問號(?)或冒號加數字(:1、:2、:3...)作為占位符,
示例:
SELECT * FROM table WHERE column = ?
或
SELECT * FROM table WHERE column = :1
- Oracle 使用冒號加數字(:1、:2、:3...)作為占位符,
示例:
SELECT * FROM table WHERE column = :1
需要注意的是,占位符的語法可能會因不同的資料庫驅動程式或框架而略有差異,因此,使用資料庫驅動程式的檔案或框架的指南來確定正確的占位符語法是很重要的,
此外,還有一些ORM(物件關系映射)框架,如GORM、Hibernate等,它們可以在不同的資料庫中使用統一的語法和方式來表示占位符,以提供更高級的查詢功能和更好的跨資料庫兼容性,
本文來自博客園,作者:尋月隱君,轉載請注明原文鏈接:https://www.cnblogs.com/QiaoPengjun/p/17473280.html
轉載請註明出處,本文鏈接:https://www.uj5u.com/houduan/554897.html
標籤:其他
上一篇:Java 網路編程 —— 基于 UDP 的資料報和套接字
下一篇:返回列表
