
本文详解如何在 Go 中安全执行任意 SELECT 查询,并将结果以字段名-值映射的 JSON 格式(而非纯数组)返回,解决 sql.Rows.Scan 默认只返回值列表导致 JSON 键缺失的问题。
本文详解如何在 go 中安全执行任意 select 查询,并将结果以字段名-值映射的 json 格式(而非纯数组)返回,解决 `sql.rows.scan` 默认只返回值列表导致 json 键缺失的问题。
在 Go 中使用 database/sql 执行动态 SQL 查询时,一个常见痛点是:rows.Scan() 仅提供按列顺序填充的值切片(如 [1, "abc", 40]),若直接序列化为 JSON,会丢失字段名信息,生成无意义的数组而非对象。要获得形如 {"id":1,"name":"abc","age":40} 的结构化 JSON,关键在于将每行数据显式映射为 map[string]interface{},以列名为键、对应值为值。
以下是优化后的完整实现,已修复原始代码中的多个隐患(如错误处理粗暴、未校验扫描后值类型、未处理 NULL 值等),并增强健壮性与可维护性:
使用约定式提交(Conventional Commits)生成标准化的 Git 提交信息。适用于以下场景: (1) 暂存更改时需要提交信息; (2) 审查 diff 时希望获得结构化摘要; (3) 编写 PR 描述或变更日志(changelog)条目; (4) 用户明确要求“为本次更改编写提交信息”。 该功能会分析已暂存/未暂存的 diff,并生成按类型(type)划分范围的提交信息。
import (
"database/sql"
"encoding/json"
"fmt"
"log"
)
// executeSQL 执行任意 SELECT 查询,返回标准 JSON 格式:{"Data":[{...}, {...}]}
func executeSQL(queryStr string) ([]byte, error) {
connString := createConnectString()
conn, err := sql.Open("mssql", connString)
if err != nil {
return nil, fmt.Errorf("failed to open database connection: %w", err)
}
defer conn.Close()
// 验证连接有效性(重要!sql.Open 不立即建连)
if err := conn.Ping(); err != nil {
return nil, fmt.Errorf("failed to ping database: %w", err)
}
rows, err := conn.Query(queryStr)
if err != nil {
return nil, fmt.Errorf("query execution failed: %w", err)
}
defer rows.Close()
// 获取列名(确保顺序与 Scan 一致)
columns, err := rows.Columns()
if err != nil {
return nil, fmt.Errorf("failed to fetch column names: %w", err)
}
count := len(columns)
var result struct {
Data []map[string]interface{} `json:"Data"`
}
for rows.Next() {
// 为每列分配 interface{} 指针,支持任意类型(含 sql.NullXXX)
values := make([]interface{}, count)
valuePtrs := make([]interface{}, count)
for i := range columns {
valuePtrs[i] = &values[i]
}
if err := rows.Scan(valuePtrs...); err != nil {
return nil, fmt.Errorf("scan row failed: %w", err)
}
// 构建 map[string]interface{},列名 → 值(自动处理 nil/NULL)
rowMap := make(map[string]interface{})
for i, col := range columns {
// 安全转换:nil 值转为 JSON null,其他值保持原类型
val := values[i]
if val == nil {
rowMap[col] = nil
} else {
rowMap[col] = val
}
}
result.Data = append(result.Data, rowMap)
}
// 检查 rows.Err() 以捕获迭代过程中的错误(如类型不匹配)
if err := rows.Err(); err != nil {
return nil, fmt.Errorf("error during row iteration: %w", err)
}
jsonBytes, err := json.Marshal(result)
if err != nil {
return nil, fmt.Errorf("JSON marshaling failed: %w", err)
}
return jsonBytes, nil
}关键改进说明:
✅ 结构化映射:通过 map[string]interface{} 显式绑定列名与值,确保 JSON 输出含语义化键;
✅ 错误处理专业化:使用 fmt.Errorf 包装错误链,避免 log.Fatal 导致服务崩溃,便于上层统一处理;
✅ 连接健康检查:添加 conn.Ping(),防止因连接池空闲超时导致首查询失败;
✅ NULL 值安全处理:显式判断 nil 并映射为 JSON null,避免运行时 panic;
✅ 迭代错误校验:调用 rows.Err() 捕获 Scan 后可能发生的延迟错误(如类型转换失败);
✅ 返回值规范:函数签名改为 (data []byte, err error),符合 Go 错误处理惯例。
注意事项:
⚠️ SQL 注入风险:该函数直接拼接用户输入的 queryStr,严禁用于不可信输入。生产环境应改用参数化查询或白名单验证机制;
⚠️ 性能考量:对大数据集,频繁创建 map 和 interface{} 有内存开销,高并发场景建议结合 sql.Scanner 或结构体反射优化;
⚠️ 驱动兼容性:示例基于 mssql 驱动,其他驱动(如 pq、mysql)行为一致,但需确保其 Rows.Columns() 返回列名准确。
此方案平衡了简洁性与健壮性,是 Go 动态查询 JSON 化的推荐实践。

















