0

0

Golang连接MySQL数据库 database/sql操作指南

P粉602998670

P粉602998670

发布时间:2025-08-29 09:11:01

|

958人浏览过

|

来源于php中文网

原创

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引发注入风险。应使用环境变量管理凭证,始终采用参数化查询防注入。合理配置连接池和规范编码可确保高效安全的数据库交互。

golang连接mysql数据库 database/sql操作指南

在Go语言中连接和操作MySQL数据库,核心在于使用标准库

database/sql
包,并搭配一个合适的第三方MySQL驱动(通常是
github.com/go-sql-driver/mysql
)。这个组合为我们提供了一套统一且强大的API,能够安全、高效地与关系型数据库进行交互。简单来说,你需要导入驱动、通过数据源名称(DSN)打开数据库连接、然后利用
db.Query
db.Exec
等方法执行SQL语句,最后别忘了妥善关闭资源并处理可能出现的错误。

解决方案

作为一名Go开发者,我发现

database/sql
的魅力在于它的简洁和对底层细节的抽象。下面是一个完整的示例,展示了如何连接MySQL、创建表、插入数据、查询数据以及更新数据。这应该能覆盖你日常开发中大部分的基础需求。

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"))
    }
}

Golang连接MySQL时如何管理连接池和避免常见陷阱?

database/sql
包在连接管理方面做得相当出色,它内置了连接池机制,这在处理高并发请求时至关重要。我刚开始接触Go的时候,也曾天真地以为每次操作都
sql.Open
db.Close
就行,结果在高并发下直接把数据库搞崩溃了。后来才明白,
sql.Open
返回的
*sql.DB
对象是设计为长期存在的,它代表了一个数据库的抽象,并且内部维护着一个连接池。我们真正需要做的是配置这个连接池,以适应我们的应用场景。

What-the-Diff
What-the-Diff

检查请求差异,自动生成更改描述

下载

主要有三个方法来配置连接池:

立即学习go语言免费学习笔记(深入)”;

  1. db.SetMaxOpenConns(n int)
    : 设置数据库可以打开的最大连接数。这意味着在任何给定时间,与数据库的活跃连接(包括正在使用的和空闲的)不会超过
    n
    个。如果所有的连接都在使用中,新的请求将等待,直到有连接可用。设置这个值可以防止你的应用耗尽数据库资源。我觉得这个值应该根据你的数据库服务器性能、应用并发量和数据库允许的最大连接数来权衡。太小会导致请求堆积,太大可能压垮数据库。
  2. db.SetMaxIdleConns(n int)
    : 设置连接池中最大空闲连接数。这些空闲连接在不使用时会保持打开状态,以便下次请求时可以直接复用,减少建立新连接的开销。如果空闲连接数超过这个值,多余的空闲连接会被关闭。一个合理的空闲连接数可以显著提高性能,但也不能过高,否则会占用不必要的数据库资源。
  3. 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
    的错误,却忽略了迭代中的错误。
  • 硬编码敏感信息: 数据库的用户名、密码等敏感信息不应该直接写在代码里。应该通过环境变量、配置文件或秘密管理服务来获取。
  • DSN配置不当: 尤其是
    parseTime=True
    loc=Local
    这两个参数。如果忘记
    parseTime=True
    ,从MySQL读取的
    DATETIME
    TIMESTAMP
    字段可能会被Go视为字符串,导致类型转换错误。
    loc=Local
    则确保时间处理与服务器或应用所在时区一致,避免时区混乱。
  • SQL注入: 虽然
    database/sql
    的占位符机制(如
    ?
    )已经很好地防止了SQL注入,但如果你尝试手动拼接SQL字符串,就可能引入安全漏洞。始终使用占位符和参数化查询。
  • 连接池配置不合理: 上面提到的
    SetMaxOpenConns
    SetMaxIdleConns
    SetConnMaxLifetime
    如果配置不当,可能导致性能瓶颈(连接数太少)或资源浪费(连接数太多

热门AI工具

更多
DeepSeek
DeepSeek

幻方量化公司旗下的开源大模型平台

豆包大模型
豆包大模型

字节跳动自主研发的一系列大型语言模型

通义千问
通义千问

阿里巴巴推出的全能AI助手

腾讯元宝
腾讯元宝

腾讯混元平台推出的AI助手

文心一言
文心一言

文心一言是百度开发的AI聊天机器人,通过对话可以生成各种形式的内容。

讯飞写作
讯飞写作

基于讯飞星火大模型的AI写作工具,可以快速生成新闻稿件、品宣文案、工作总结、心得体会等各种文文稿

即梦AI
即梦AI

一站式AI创作平台,免费AI图片和视频生成。

ChatGPT
ChatGPT

最最强大的AI聊天机器人程序,ChatGPT不单是聊天机器人,还能进行撰写邮件、视频脚本、文案、翻译、代码等任务。

相关专题

更多
golang如何定义变量
golang如何定义变量

golang定义变量的方法:1、声明变量并赋予初始值“var age int =值”;2、声明变量但不赋初始值“var age int”;3、使用短变量声明“age :=值”等等。本专题为大家提供相关的文章、下载、课程内容,供大家免费下载体验。

210

2024.02.23

golang有哪些数据转换方法
golang有哪些数据转换方法

golang数据转换方法:1、类型转换操作符;2、类型断言;3、字符串和数字之间的转换;4、JSON序列化和反序列化;5、使用标准库进行数据转换;6、使用第三方库进行数据转换;7、自定义数据转换函数。本专题为大家提供相关的文章、下载、课程内容,供大家免费下载体验。

247

2024.02.23

golang常用库有哪些
golang常用库有哪些

golang常用库有:1、标准库;2、字符串处理库;3、网络库;4、加密库;5、压缩库;6、xml和json解析库;7、日期和时间库;8、数据库操作库;9、文件操作库;10、图像处理库。本专题为大家提供相关的文章、下载、课程内容,供大家免费下载体验。

356

2024.02.23

golang和python的区别是什么
golang和python的区别是什么

golang和python的区别是:1、golang是一种编译型语言,而python是一种解释型语言;2、golang天生支持并发编程,而python对并发与并行的支持相对较弱等等。本专题为大家提供相关的文章、下载、课程内容,供大家免费下载体验。

214

2024.03.05

golang是免费的吗
golang是免费的吗

golang是免费的。golang是google开发的一种静态强类型、编译型、并发型,并具有垃圾回收功能的开源编程语言,采用bsd开源协议。本专题为大家提供相关的文章、下载、课程内容,供大家免费下载体验。

409

2024.05.21

golang结构体相关大全
golang结构体相关大全

本专题整合了golang结构体相关大全,想了解更多内容,请阅读专题下面的文章。

490

2025.06.09

golang相关判断方法
golang相关判断方法

本专题整合了golang相关判断方法,想了解更详细的相关内容,请阅读下面的文章。

200

2025.06.10

golang数组使用方法
golang数组使用方法

本专题整合了golang数组用法,想了解更多的相关内容,请阅读专题下面的文章。

1438

2025.06.17

C# ASP.NET Core微服务架构与API网关实践
C# ASP.NET Core微服务架构与API网关实践

本专题围绕 C# 在现代后端架构中的微服务实践展开,系统讲解基于 ASP.NET Core 构建可扩展服务体系的核心方法。内容涵盖服务拆分策略、RESTful API 设计、服务间通信、API 网关统一入口管理以及服务治理机制。通过真实项目案例,帮助开发者掌握构建高可用微服务系统的关键技术,提高系统的可扩展性与维护效率。

3

2026.03.11

热门下载

更多
网站特效
/
网站源码
/
网站素材
/
前端模板

精品课程

更多
相关推荐
/
热门推荐
/
最新课程
MySQL 教程
MySQL 教程

共48课时 | 2.5万人学习

MySQL 初学入门(mosh老师)
MySQL 初学入门(mosh老师)

共3课时 | 0.3万人学习

简单聊聊mysql8与网络通信
简单聊聊mysql8与网络通信

共1课时 | 846人学习

关于我们 免责申明 举报中心 意见反馈 讲师合作 广告合作 最新更新
php中文网:公益在线php培训,帮助PHP学习者快速成长!
关注服务号 技术交流群
PHP中文网订阅号
每天精选资源文章推送

Copyright 2014-2026 https://www.php.cn/ All Rights Reserved | php.cn | 湘ICP备2023035733号