
本文详解如何通过 database/sql 配合 mattn/go-sqlite3 驱动,安全、高效地执行 count() 聚合查询并提取整型计数值,重点强调参数化查询防注入与错误处理最佳实践。
本文详解如何通过 database/sql 配合 mattn/go-sqlite3 驱动,安全、高效地执行 count() 聚合查询并提取整型计数值,重点强调参数化查询防注入与错误处理最佳实践。
在 Go 中使用 SQLite 执行计数查询时,切勿拼接 SQL 字符串(如 fmt.Sprint(query, id)),这会导致 SQL 注入漏洞且语法易出错(例如字符串值未加引号)。正确做法是使用预编译语句(Prepared Statement)配合占位符 ?,并借助 QueryRow().Scan() 直接读取单行单列结果。
以下为推荐实现:
import (
"database/sql"
"fmt"
_ "github.com/mattn/go-sqlite3"
)
// 假设 db *sql.DB 已初始化
func getCountByNotebook(db *sql.DB, id string) (int, error) {
// ✅ 使用参数化查询,防止 SQL 注入
query := "SELECT COUNT(notebook) FROM pages WHERE notebook = ?"
var count int
err := db.QueryRow(query, id).Scan(&count)
if err != nil {
if err == sql.ErrNoRows {
return 0, nil // 无匹配记录,COUNT 返回 0,但 QueryRow 不会触发 ErrNoRows(COUNT 总返回一行)
}
return 0, fmt.Errorf("query failed: %w", err)
}
return count, nil
}
// 使用示例
func main() {
// ... 初始化 db
cnt, err := getCountByNotebook(db, "1234")
if err != nil {
log.Fatal(err)
}
fmt.Printf("Found %d pages for notebook '1234'\n", cnt)
}⚠️ 注意事项:
- COUNT() 函数总是返回一行结果(即使为 0),因此 QueryRow().Scan() 是最简洁的选择,无需 defer query.Close() 或手动遍历 Rows。
- Scan() 的目标变量类型必须与 SQL 类型兼容:SQLite 的 COUNT() 返回整数,故应使用 int、int64 等整型,而非 string(原答案中用 string 接收虽可工作,但需额外类型转换,且易引发 Scan 错误)。
- 若需支持大表计数,可考虑添加 LIMIT 1(虽对 COUNT 无实质影响)或使用 COUNT(*)(更通用,不依赖字段非空性)。
- 生产环境建议统一使用 int64 接收 COUNT 结果,避免 32 位平台溢出风险。
总结:始终优先使用 db.QueryRow(query, args...).Scan(&dest) 模式处理单值聚合查询;坚持参数化,杜绝字符串拼接;选择合适的目标类型(推荐 int64),并妥善处理潜在错误。


















