Golang database/sql 包深度解析(一)

database/sql 是 Go 语言标准库中用于与 SQL(或类 SQL)数据库交互的核心包,提供了一套轻量级、通用的接口,使得开发者可以用统一的方式操作各种不同的数据库,而无需关心底层数据库驱动的具体实现。

核心设计理念

database/sql 包本身并不包含任何特定数据库的驱动程序,只是定义了一系列接口,而具体的数据库驱动则需要实现这些接口。这种设计的好处在于:

  • 通用性: 开发者可以编写与特定数据库无关的数据访问代码。
  • 可移植性: 只需更换导入的数据库驱动和连接字符串,即可轻松切换数据库。
  • 专注性: database/sql 包专注于提供统一的数据库操作抽象,而将底层细节交由第三方驱动实现。

在使用时,通常会像下面这样导入 database/sql 包和一个匿名的数据库驱动包:

import ("database/sql"_ "github.com/go-sql-driver/mysql" // 匿名导入,仅执行其init()函数
)

匿名导入(使用下划线 _)的目的是执行驱动包的 init() 函数,该函数会将驱动注册到 database/sql 中。之后就可以通过 database/sql 提供的函数来使用这个驱动了。

核心类型

database/sql 包主要由以下几个核心类型组成:

类型描述
sql.DB表示一个数据库句柄,代表一个维护了零到多个底层连接的连接池。它是并发安全的,应该被视为一个长生命周期的对象,通常在应用启动时创建,并在整个应用生命周期内共享。
sql.Tx表示一个数据库事务。在事务中执行的所有操作要么全部成功(Commit),要么全部失败(Rollback)。
sql.Stmt表示一个预编译的 SQL 语句。预编译可以防止 SQL 注入,并且在重复执行相同语句时能提升性能。
sql.Rows表示一个查询返回的多行结果集。使用 Next() 方法遍历每一行,并用 Scan() 方法读取行中的数据。
sql.Row表示一个查询返回的单行结果。通常用于期望最多返回一行结果的查询。
sql.Result表示 Exec 方法(用于 INSERT, UPDATE, DELETE 等操作)的执行结果,可以获取最后插入的 ID 和受影响的行数。
sql.Null* 类型sql.NullString, sql.NullInt64, sql.NullBool 等,用于处理可能为 NULL 的数据库列。每个类型都包含一个 Valid 字段(布尔型)来表示值是否为 NULL,以及一个 Value 字段来存储实际的值。

基本操作

1. 连接数据库

使用 sql.Open() 函数来创建一个 sql.DB 对象。这个函数需要两个参数:驱动名称和数据源名称(Data Source Name, DSN)。

db, err := sql.Open("mysql", "user:password@tcp(127.0.0.1:3306)/dbname")
if err != nil {log.Fatal(err)
}
defer db.Close()

注意: sql.Open() 并不会立即建立与数据库的连接,也不会验证连接参数的有效性,只是准备好数据库抽象对象。要验证连接是否有效,可以使用 db.Ping() 方法。

err = db.Ping()
if err != nil {log.Fatal("数据库连接失败: ", err)
}

sql.DB 对象内部管理着一个连接池,开发者无需手动管理连接。可以通过以下方法配置连接池:

  • db.SetMaxOpenConns(n): 设置连接池中的最大打开连接数。
  • db.SetMaxIdleConns(n): 设置连接池中的最大空闲连接数。
  • db.SetConnMaxLifetime(d): 设置连接可被复用的最大时间。

2. 执行查询

查询多行

使用 db.Query()db.QueryContext() 方法执行返回多行结果的 SELECT 查询。

rows, err := db.Query("SELECT id, name FROM users WHERE age > ?", 30)
if err != nil {log.Fatal(err)
}
defer rows.Close()type User struct {ID   int64Name string
}var users []User
for rows.Next() {var u Userif err := rows.Scan(&u.ID, &u.Name); err != nil {log.Fatal(err)}users = append(users, u)
}
if err := rows.Err(); err != nil {log.Fatal(err)
}

关键点:

  • 参数化查询: 使用 ? (或其他数据库驱动支持的占位符,如 $1 for PostgreSQL) 作为参数占位符,并将实际参数传递给 Query 方法,可以有效防止 SQL 注入攻击。
  • 遍历结果: 使用 for rows.Next() 循环遍历结果集。
  • 读取数据: 在循环体内,使用 rows.Scan() 将当前行的数据读入到变量中。
  • 错误检查: 在 rows.Next() 循环结束后,务必调用 rows.Err() 来检查遍历过程中是否发生了错误。
  • 释放资源: 使用 defer rows.Close() 来确保结果集被关闭,从而释放底层的数据库连接。

查询单行

使用 db.QueryRow()db.QueryRowContext() 方法执行最多返回一行的查询。

var name string
err := db.QueryRow("SELECT name FROM users WHERE id = ?", 1).Scan(&name)
if err != nil {if err == sql.ErrNoRows {// 查无此行,是正常情况fmt.Println("未找到记录")} else {// 发生了其他错误log.Fatal(err)}
}

关键点:

  • QueryRow 总是返回一个 *sql.Row 对象(即使没有找到行)。
  • 错误(包括 sql.ErrNoRows)会在调用 Scan() 方法时返回。因此,需要检查 Scan() 返回的错误。

3. 执行非查询操作

对于 INSERT, UPDATE, DELETE 等不返回行的 SQL 语句,使用 db.Exec()db.ExecContext() 方法。

result, err := db.Exec("UPDATE users SET name = ? WHERE id = ?", "New Name", 1)
if err != nil {log.Fatal(err)
}rowsAffected, err := result.RowsAffected()
if err != nil {log.Fatal(err)
}
fmt.Printf("受影响的行数: %d\n", rowsAffected)lastInsertId, err := result.LastInsertId()
if err != nil {// 注意:并非所有数据库驱动都支持此功能log.Fatal(err)
}
fmt.Printf("最后插入的ID: %d\n", lastInsertId)

高级技巧

1. 预编译语句 (Prepared Statements)

当需要重复执行相同的 SQL 语句时,使用预编译语句可以获得更好的性能,并能提供额外的安全保障。

stmt, err := db.Prepare("INSERT INTO users(name, age) VALUES(?, ?)")
if err != nil {log.Fatal(err)
}
defer stmt.Close()_, err = stmt.Exec("Alice", 28)
if err != nil {log.Fatal(err)
}_, err = stmt.Exec("Bob", 32)
if err != nil {log.Fatal(err)
}

2. 事务处理

事务用于将一组操作作为一个原子单元来执行。使用 db.Begin() 开始一个事务。

tx, err := db.Begin()
if err != nil {log.Fatal(err)
}// 在事务中执行操作
_, err = tx.Exec("UPDATE accounts SET balance = balance - 100 WHERE id = 1")
if err != nil {tx.Rollback() // 出错时回滚log.Fatal(err)
}_, err = tx.Exec("UPDATE accounts SET balance = balance + 100 WHERE id = 2")
if err != nil {tx.Rollback() // 出错时回滚log.Fatal(err)
}// 提交事务
err = tx.Commit()
if err != nil {log.Fatal(err)
}

关键点:

  • tx 对象上调用 Exec, Query, QueryRow 等方法来执行事务内的操作。
  • 如果任何一步操作失败,调用 tx.Rollback() 来撤销所有已执行的操作。
  • 所有操作成功后,调用 tx.Commit() 来持久化更改。
  • 推荐使用 defer 语句来处理回滚,以确保在函数返回前事务能够被正确处理:
tx, err := db.Begin()
if err != nil {// ...
}
defer tx.Rollback() // 如果Commit成功,Rollback会返回sql.ErrTxDone,可以忽略
// ... 事务操作
if err = tx.Commit(); err != nil {// ...
}

最佳实践

  • 不要在短函数中打开和关闭数据库: sql.DB 被设计为长生命周期的对象。频繁地 OpenClose 会影响性能。
  • 总是检查错误: database/sql 中的许多操作都会返回错误,务必对每一个错误进行检查。
  • 使用参数化查询: 杜绝拼接字符串来构建 SQL 查询,以防止 SQL 注入。
  • 正确关闭 Rows: 确保 sql.Rows 对象在使用完毕后被关闭,以释放连接。
  • 处理 NULL: 使用 sql.Null* 类型或在 Scan 时使用指针类型来处理可能为 NULL 的数据库列。
  • 利用 Context: 对于可能耗时较长的数据库操作,使用带有 Context 的方法(如 QueryContext),以便在需要时能够取消操作或设置超时。

小结

database/sql 包为 Go 语言提供了一个强大而灵活的与数据库交互的工具。通过理解其设计和核心组件,并遵循最佳实践,开发者可以编写出健壮、高效且可维护的数据访问代码。

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。
如若转载,请注明出处:http://www.pswp.cn/pingmian/93398.shtml
繁体地址,请注明出处:http://hk.pswp.cn/pingmian/93398.shtml

如若内容造成侵权/违法违规/事实不符,请联系多彩编程网进行投诉反馈email:809451989@qq.com,一经查实,立即删除!

相关文章

文章自然润色 API 数据接口

文章自然润色 API 数据接口 ai / 文本处理 基于 AI 的文章润色 专有模型 / 智能纠错。 1. 产品功能 基于自有专业模型进行 AI 智能润色对原始内容进行智能纠错高效的文本润色性能全接口支持 HTTPS(TLS v1.0 / v1.1 / v1.2 / v1.3);全面兼容…

【状压DP】3276. 选择矩阵中单元格的最大得分|2403

本文涉及知识点 C动态规划 3276. 选择矩阵中单元格的最大得分 给你一个由正整数构成的二维矩阵 grid。 你需要从矩阵中选择 一个或多个 单元格,选中的单元格应满足以下条件: 所选单元格中的任意两个单元格都不会处于矩阵的 同一行。 所选单元格的值 互…

IDEA 清除 ctrl+shift+r 全局搜索记录

定位文件:在Windows系统中,文件通常位于C:Users/用户名/AppData/Roaming/JetBrains/IntelliJIdea(idea版本)/workspace目录下,文件名为一小串随机字符;在Mac系统中,文件位于/Users/用户名/Library/Application /Suppor…

解锁AI大模型:Prompt工程全面解析

解锁AI大模型&#xff1a;Prompt工程全面解析 本文较长&#xff0c;建议点赞收藏&#xff0c;以免遗失。更多AI大模型开发 学习视频/籽料/面试题 都在这>>Github<< 从新手到高手&#xff0c;Prompt 工程究竟是什么&#xff1f; 在当今数字化时代&#xff0c;AI …

HTTP0.9/1.0/1.1/2.0

在HTTP0.9中&#xff0c;只有GET方法&#xff0c;没有请求头headers&#xff0c;没有状态码&#xff0c;只能用于传输HTML文件。到了HTTP1.0(1996)&#xff0c;HTTP1.0传输请求头&#xff0c;有状态码&#xff0c;并且新增了POST和HEAD方法。HTTP1.0中&#xff0c;使用短连接&a…

gitee 流水线+docker-compose部署 nodejs服务+mysql+redis

文章中的方法是自己琢磨出来的&#xff0c;或许有更优解&#xff0c;共同学习&#xff0c;共同进步&#xff01; docker-compose.yml 文件配置&#xff1a; 说明&#xff1a;【配置中有个别字段冗余&#xff0c;但不影响使用】该文件推荐放在nodejs项目的根目录中&#xff0c…

【算法】模拟专题

什么是模拟&#xff1f; 是一种通过模仿现实世界或问题场景的运行过程来求解问题的算法思想。它不依赖复杂的数学推导或逻辑优化&#xff0c;而是按照问题的实际规则、步骤或流程&#xff0c;一步步地 “复现” 过程&#xff0c;最终得到结果。 使用场景&#xff1a;当问题的逻…

【FreeRTOS】刨根问底6: 应该如何防止任务栈溢出?

【加关注&#xff0c;不迷路】一、栈溢出&#xff1a;程序世界的“越界洪水”就象一个装水的玻璃杯&#xff08;栈空间&#xff09;&#xff0c;每次调用函数就像向水杯中倒水&#xff08;压入保护需要恢复的数据&#xff09;。当函数嵌套调用过深&#xff08;如递归失控&#…

牛客周赛 Round 105

A.小苯的xor构造题目描述小红喜欢整数 k&#xff0c;他想让小苯构造两个不相等的非负整数&#xff0c;使得两数的异或和等于 k。请你帮帮小苯。#include <bits/stdc.h> using namespace std; using ll long long; void solve() {int k;cin>>k;cout<<0<&l…

《R for Data Science (2e)》免费中文翻译 (第4章) --- Workflow: code style

写在前面 本系列推文为《R for Data Science (2)》的中文翻译版本。所有内容都通过开源免费的方式上传至Github&#xff0c;欢迎大家参与贡献&#xff0c;详细信息见&#xff1a; Books-zh-cn 项目介绍&#xff1a; Books-zh-cn&#xff1a;开源免费的中文书籍社区 r4ds-zh-cn …

11-verilog的RTC驱动代码

verilog的RTC驱动代码 1.例化parameter SLAVE_ADDR 7h51 ; // 器件地址 parameter BIT_CTRL 1b0 ; // 字地址位控制参数(16b/8b) parameter CLK_FREQ 26d50_000_000; // i2c_dri模块的驱动时钟频率(CLK_FREQ) parameter I2C_FR…

【k8s、docker】Headless Service(无头服务)

文章目录问题背景1、什么是Headless Service1.2 为什么 Zookeeper 使用 Headless Service&#xff1f;1.2 Headless Service 的 DNS 行为1.3 验证示例1.4 如何创建 Headless Service&#xff1f;2. zk-0.zookeeper.default.svc.cluster.local 域名是如何创建出来的&#xff1f;…

scikit-learn/sklearn学习|套索回归Lasso解读

【1】引言 前序学习进程中&#xff0c;对用scikit-learn表达线性回归进行了初步解读。 线性回归能够将因变量yyy表达成由自变量xxx、线性系数矩阵www和截距bbb组成的线性函数式&#xff1a; y∑i1nwi⋅xibwTxby\sum_{i1}^{n}w_{i}\cdot x_{i}bw^T{x}byi1∑n​wi​⋅xi​bwTxb实…

暴雨服务器:以定制化满足算力需求多样化

在数字经济与实体经济深度融合的浪潮下&#xff0c;互联网行业正经历着前所未有的技术变革。大数据分析、云计算服务、人工智能算法等技术的快速演进&#xff0c;推动着企业对于高性能计算基础设施的需求呈现指数级增长。据IDC数据显示&#xff0c;互联网行业已成为全球服务器采…

JavaScript字符串详解

创建字符串&#xff1a; 1.使用字面量(推荐)&#xff1a; 这是最常用、最直接的方式。你可以用单引号 ()、双引号 (") 或反引号 () 把文本包起来 let singleQuote 单引号; let doubleQuote "双引号"; let templateLiteral 反引号;2.使用String 构造函数&…

Kiro Preview 应用评测

Kiro应用评测 Kiro 是一个由亚马逊推出的 AI 驱动的智能开发环境&#xff0c;从原型到生产全程陪伴您的开发过程。它将"灵感编程"的流畅性与规范的清晰性相结合&#xff0c;帮助您更快地构建更好的软件。 昨天收到了Kiro的试用邮件&#xff0c;收到邮件后第一时间下载…

Flink2.0学习笔记:Flink服务器搭建与flink作业提交

一&#xff0c;下载flink:Downloads | Apache Flink,解压后放入IDE工作目录&#xff1a;我这里以1.17版本为例 可以看到&#xff0c;flink后期的版本中没有提供window启动脚本:start-cluster.bat 所以这里要通过windows自带的wsl 系统启动它 打开终端依次运行下列命令完成w…

MySQL锁的分类

MySQL锁可以按照多个维度进行分类&#xff0c;下面我用最清晰的方式为你梳理所有分类方式&#xff1a;一、按锁的粒度分类&#xff08;最常用分类&#xff09;锁类型作用范围特点适用引擎示例场景表级锁整张表开销小、加锁快&#xff0c;并发度低MyISAM, MEMORY数据迁移、全表统…

电脑上搭建HTTP服务器在局域网内其它客户端无法访问的解决方案

在电脑上开发一套HTTP服务器的程序在调试时&#xff0c;在本机内访问正常&#xff0c;但是在本机外访问就不正常&#xff0c;外部客户端无法访问或无法连接到本机的服务器的问题&#xff0c;这可能涉及网络配置、防火墙、端口转发或服务绑定等问题&#xff0c;在这里提供了解决…

双指针和codetop2(最短路问题BFS)

双指针和codetop21.双指针1.[复写0](https://leetcode.cn/problems/duplicate-zeros/)2.动态规划1.[珠宝的最高价值](https://leetcode.cn/problems/li-wu-de-zui-da-jie-zhi-lcof/description/)2.[解码方法](https://leetcode.cn/problems/decode-ways/)3.[下降路径最小和](ht…