Golang

关注公众号 jb51net

关闭
首页 > 脚本专栏 > Golang > Go 连接 MySQL之 MySQL 预处理

Go 连接 MySQL之 MySQL 预处理详解

作者:寻月隐君

Go语言提供了丰富的库和工具,可以方便地连接MySQL数据库。MySQL预处理是一种提高数据库操作效率和安全性的技术。Go语言中的第三方库提供了MySQL预处理的支持,通过使用预处理语句,可以避免SQL注入攻击,并且可以提高数据库操作的效率。

一、ChatGPT 关于 MySQL 预处理 的回答

问:什么是MySQL 的预处理 具体执行过程时什么

ChatGPT 答:

MySQL的预处理是一种在执行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预处理适用于以下场景:

总结而言,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注入攻击,开发人员应该采取以下措施:

通过采取这些安全措施,可以有效减轻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 占位符

不同的数据库使用不同的占位符语法,下面是一些常见数据库的占位符语法:

示例:

SELECT * FROM table WHERE column = ?

示例:

SELECT * FROM table WHERE column = $1

示例:

SELECT * FROM table WHERE column = ?

SELECT * FROM table WHERE column = :1

示例:

SELECT * FROM table WHERE column = :1

需要注意的是,占位符的语法可能会因不同的数据库驱动程序或框架而略有差异。因此,使用数据库驱动程序的文档或框架的指南来确定正确的占位符语法是很重要的。

此外,还有一些ORM(对象关系映射)框架,如GORM、Hibernate等,它们可以在不同的数据库中使用统一的语法和方式来表示占位符,以提供更高级的查询功能和更好的跨数据库兼容性。

Go语言提供了丰富的库和工具,可以方便地连接MySQL数据库。MySQL预处理是一种提高数据库操作效率和安全性的技术,通过预处理可以避免SQL注入攻击,并且可以重复利用已经编译好的语句,提高执行效率。Go语言中的第三方库提供了MySQL预处理的支持,使用方法与普通SQL语句类似,只需要在执行前进行预处理即可。在实际应用中,合理使用MySQL预处理技术,可以提高数据库操作的效率和安全性,避免潜在的安全风险。

到此这篇关于Go 连接 MySQL之 MySQL 预处理详解的文章就介绍到这了,更多相关Go 连接 MySQL之 MySQL 预处理 内容请搜索脚本之家以前的文章或继续浏览下面的相关文章希望大家以后多多支持脚本之家!

您可能感兴趣的文章:
阅读全文