Go语言通过database/sql包和MySQL驱动实现数据库操作,需导入驱动、用DSN连接、执行SQL并关闭资源。示例包含建表、增删改查,使用db.Exec执行非查询语句,db.Query查询多行,db.QueryRow获取单行,配合sql.NullString处理NULL值,并通过defer关闭rows和db。连接池由*sql.DB自动管理,应长期持有该对象而非频繁创建。通过db.SetMaxOpenConns、SetMaxIdleConns、SetConnMaxLifetime合理配置连接数、空闲数和生命周期,避免资源耗尽或性能下降。常见陷阱包括未关闭rows或stmt导致连接泄露、遍历后未检查rows.Err()、硬编码敏感信息、DSN缺少parseTime=True导致时间类型错误、手动拼接SQL引发注入风险。应使用环境变量管理凭证,始终采用参数化查询防注入。合理配置连接池和规范编码可确保高效安全的数据库交互。

在Go语言中连接和操作MySQL数据库,核心在于使用标准库
database/sql
github.com/go-sql-driver/mysql
db.Query
db.Exec
作为一名Go开发者,我发现
database/sql
package main
import (
"database/sql"
"fmt"
"log"
"time"
// 导入 MySQL 驱动。注意这里的下划线,表示我们只导入包以执行其init()函数,
// 而不直接使用包中的任何导出标识符。这会将驱动注册到 database/sql 包中。
_ "github.com/go-sql-driver/mysql"
)
// User 结构体用于映射数据库中的用户表记录
type User struct {
ID int
Name string
Email sql.NullString // 使用 sql.NullString 处理可空的 email 字段
CreatedAt time.Time
}
func main() {
// 数据库连接字符串 (DSN - Data Source Name)
// 格式:username:password@tcp(host:port)/dbname?charset=utf8mb4&parseTime=True&loc=Local
// 请替换为你的实际数据库凭据和地址。
// parseTime=True 是非常重要的,它能将 MySQL 的 DATETIME/TIMESTAMP 类型自动解析为 Go 的 time.Time 类型。
// loc=Local 确保时间解析时使用本地时区。
dsn := "root:your_password@tcp(127.0.0.1:3306)/testdb?charset=utf8mb4&parseTime=True&loc=Local"
// 1. 打开数据库连接
// sql.Open 不会立即建立连接,它只是验证参数并返回一个 DB 对象。
db, err := sql.Open("mysql", dsn)
if err != nil {
// 如果这里出错,通常是 DSN 格式不正确或驱动名称错误
log.Fatalf("Error opening database connection: %v", err)
}
// defer db.Close() 确保在 main 函数结束时关闭数据库连接,释放资源。
// 这是一个非常重要的实践,避免资源泄露。
defer db.Close()
// 2. 验证数据库连接是否真正建立
// db.Ping() 会尝试与数据库建立连接并发送一个测试查询。
err = db.Ping()
if err != nil {
// 如果这里出错,可能是数据库服务器未运行、网络问题或凭据错误
log.Fatalf("Error connecting to the database: %v", err)
}
fmt.Println("Successfully connected to MySQL!")
// --- 数据库操作示例 ---
// 3. 创建表 (如果不存在)
createTableSQL := `
CREATE TABLE IF NOT EXISTS users (
id INT AUTO_INCREMENT PRIMARY KEY,
name VARCHAR(100) NOT NULL,
email VARCHAR(100) UNIQUE,
created_at DATETIME DEFAULT CURRENT_TIMESTAMP
);`
_, err = db.Exec(createTableSQL) // Exec 用于执行没有返回行的SQL语句,如 CREATE, INSERT, UPDATE, DELETE
if err != nil {
log.Fatalf("Error creating table: %v", err)
}
fmt.Println("Table 'users' ensured to exist.")
// 4. 插入数据
// 使用占位符 '?' 是防止 SQL 注入的最佳实践。
insertStmt := "INSERT INTO users(name, email) VALUES(?, ?)"
result, err := db.Exec(insertStmt, "Alice", "alice@example.com")
if err != nil {
log.Printf("Error inserting Alice: %v", err) // 使用 Printf 允许程序继续执行
} else {
id, _ := result.LastInsertId() // 获取最后插入的ID
fmt.Printf("Inserted new user Alice with ID: %d\n", id)
}
result, err = db.Exec(insertStmt, "Bob", nil) // 插入一个 email 为 NULL 的用户
if err != nil {
log.Printf("Error inserting Bob: %v", err)
} else {
id, _ := result.LastInsertId()
fmt.Printf("Inserted new user Bob with ID: %d\n", id)
}
// 5. 查询所有用户
fmt.Println("\nQuerying all users:")
// Query 用于执行 SELECT 语句,返回一个 *sql.Rows 对象
rows, err := db.Query("SELECT id, name, email, created_at FROM users")
if err != nil {
log.Fatalf("Error querying data: %v", err)
}
// 同样,defer rows.Close() 非常重要,确保结果集被关闭,释放数据库连接。
defer rows.Close()
for rows.Next() { // 遍历结果集中的每一行
var user User
// rows.Scan 将当前行的列值复制到指定的变量中。
// 注意这里使用了 user.Email (sql.NullString) 来处理可能为 NULL 的 email 字段。
err := rows.Scan(&user.ID, &user.Name, &user.Email, &user.CreatedAt)
if err != nil {
log.Fatalf("Error scanning row: %v", err)
}
emailStr := "NULL"
if user.Email.Valid { // 检查 email 是否有效 (即不为 NULL)
emailStr = user.Email.String
}
fmt.Printf("ID: %d, Name: %s, Email: %s, CreatedAt: %s\n",
user.ID, user.Name, emailStr, user.CreatedAt.Format("2006-01-02 15:04:05"))
}
// 遍历结束后,检查 rows.Err() 是否有错误发生,这很重要。
if err = rows.Err(); err != nil {
log.Fatalf("Error during rows iteration: %v", err)
}
// 6. 更新数据
updateStmt := "UPDATE users SET email = ? WHERE name = ?"
updateResult, err := db.Exec(updateStmt, "bob_new@example.com", "Bob")
if err != nil {
log.Printf("Error updating Bob: %v", err)
} else {
rowsAffected, _ := updateResult.RowsAffected() // 获取受影响的行数
fmt.Printf("\nUpdated %d rows for Bob.\n", rowsAffected)
}
// 7. 查询单个用户
fmt.Println("\nQuerying Bob after update:")
var bob User
// QueryRow 用于查询单行数据,它返回一个 *sql.Row 对象。
// 直接调用 Scan 方法,如果查询结果为空,Scan 会返回 sql.ErrNoRows 错误。
err = db.QueryRow("SELECT id, name, email, created_at FROM users WHERE name = ?", "Bob").Scan(
&bob.ID, &bob.Name, &bob.Email, &bob.CreatedAt)
if err != nil {
if err == sql.ErrNoRows {
fmt.Println("No user named Bob found.")
} else {
log.Fatalf("Error querying single row for Bob: %v", err)
}
} else {
emailStr := "NULL"
if bob.Email.Valid {
emailStr = bob.Email.String
}
fmt.Printf("Bob's updated info: ID: %d, Name: %s, Email: %s, CreatedAt: %s\n",
bob.ID, bob.Name, emailStr, bob.CreatedAt.Format("2006-01-02 15:04:05"))
}
}database/sql
sql.Open
db.Close
sql.Open
*sql.DB
主要有三个方法来配置连接池:
立即学习“go语言免费学习笔记(深入)”;
db.SetMaxOpenConns(n int)
n
db.SetMaxIdleConns(n int)
db.SetConnMaxLifetime(d time.Duration)
常见陷阱与规避:
*sql.Rows
*sql.Stmt
db.Query
stmt.Query
rows.Close()
db.Prepare
*sql.Stmt
stmt.Close()
defer
rows.Err()
rows.Next()
rows.Err()
db.Query
parseTime=True
loc=Local
parseTime=True
DATETIME
TIMESTAMP
loc=Local
database/sql
?
SetMaxOpenConns
SetMaxIdleConns
SetConnMaxLifetime
以上就是Golang连接MySQL数据库 database/sql操作指南的详细内容,更多请关注php中文网其它相关文章!
每个人都需要一台速度更快、更稳定的 PC。随着时间的推移,垃圾文件、旧注册表数据和不必要的后台进程会占用资源并降低性能。幸运的是,许多工具可以让 Windows 保持平稳运行。
Copyright 2014-2025 https://www.php.cn/ All Rights Reserved | php.cn | 湘ICP备2023035733号