注册
GO连接数据库
专栏/技术分享/ 文章详情 /

GO连接数据库

DM_738629 2026/09/30 176 0 0
摘要

环境准备

安装go环境,参考官网即可。

安装 dm 驱动包

进入环境变量GOPATH下的src文件下
新建项目文件dmtest,在此目录执行以下命令
go env -w GOPROXY=https://goproxy.cn,direct
go mod tidy

image20260907211146635.png
image20260907211359839.png

数据库操作

数据库连接

编写连接代码,内容如下
进行连接测试
go run test_conn.go
package main

// 导入标准库
import (
	"database/sql" // Go标准数据库接口,统一操作各类数据库
	"fmt"          // 控制台打印输出
	_ "dm"         // 匿名导入达梦驱动,只执行驱动初始化,不直接使用dm包的对象
	// 匿名导入:驱动会注册 "dm" 这个数据库驱动名称,供sql.Open使用
)

func main() {
	// ========== 1. 配置达梦数据库连接串 DSN ==========
	// 格式:dm://用户名:密码@ip:端口[?参数]
	dsn := "dm://SYSDBA:DMdata456@192.168.43.3:5234"

	// sql.Open:创建数据库句柄(连接池对象)
	// 注意:Open**不会真正建立网络连接**,只是初始化连接池配置
	// 第一个参数:驱动名,对应dm驱动注册的"dm"
	db, err := sql.Open("dm", dsn)
	if err != nil {
		// 打开句柄失败,一般是DSN格式写错
		fmt.Printf("Open创建数据库句柄失败:%v\n", err)
		return
	}
	// defer:函数退出前执行,关闭数据库连接池,释放资源
	defer db.Close()

	// ========== 2. Ping 真正建立数据库网络连接 ==========
	// sql.Open只是创建对象,Ping才会真正和达梦服务建立TCP连接
	// 如果这里报错:数据库没启动、IP/端口不对、账号密码错误、防火墙拦截
	err = db.Ping()
	if err != nil {
		fmt.Printf("Ping数据库失败,无法连通:%v\n", err)
		return
	}
	fmt.Println("数据库连接成功")

	// ========== 3. 执行简单SQL查询验证 ==========
	// QueryRow:执行查询SQL,返回单行结果
	// SELECT 1 是数据库通用测试语句,达梦可以执行,返回数字1
	var res int // 用来接收查询返回结果
	err = db.QueryRow("SELECT 1").Scan(&res)
	if err != nil {
		fmt.Printf("执行测试查询失败:%v\n", err)
		return
	}
	// Scan:把数据库返回的一行数据,赋值给变量res
	fmt.Printf("查询返回结果:%d\n", res)
}

image20260907211917396.png

数据库增删改查

插入数据

go run test_sql.go
package main

import (
	"database/sql"
	"fmt"
	_ "dm" //匿名导入达梦驱动,注册驱动
)

func main() {
	// DSN:log=1开启驱动底层日志;autoCommit=false关闭自动提交
	dsn := "dm://SYSDBA:DMdata456@192.168.43.3:5234?log=1&autoCommit=false"

	// 创建连接池句柄,Open不会真实建立网络连接
	db, err := sql.Open("dm", dsn)
	if err != nil {
		fmt.Printf("Open句柄失败:%v\n", err)
		return
	}
	defer db.Close() //程序退出关闭连接池释放资源

	// Ping真正建立数据库TCP连接
	err = db.Ping()
	if err != nil {
		fmt.Printf("Ping数据库失败:%v\n", err)
		return
	}
	fmt.Println(" 数据库连接成功\n")

	// ========== 1.批量插入3行数据 ==========
	fmt.Println("===== 1.批量插入3条数据 =====")
	insertSQL := `INSERT INTO t_user(id,username,age) VALUES(?,?,?)`

	// 插入第1条
	_, err = db.Exec(insertSQL, 1, "zhangsan", 22)
	if err != nil {
		fmt.Printf("插入id=1失败:%v\n", err)
		return
	}
	// 插入第2条
	_, err = db.Exec(insertSQL, 2, "lisi", 24)
	if err != nil {
		fmt.Printf("插入id=2失败:%v\n", err)
		return
	}
	// 插入第3条
	_, err = db.Exec(insertSQL, 3, "wangwu", 26)
	if err != nil {
		fmt.Printf("插入id=3失败:%v\n", err)
		return
	}

	// 关闭自动提交,手动提交事务,数据才会落库
	_, err = db.Exec("COMMIT")
	if err != nil {
		fmt.Printf("提交事务失败:%v\n", err)
		return
	}
	fmt.Println(" 3条数据插入完成\n")

	// ========== 2.全表查询,查询全部数据 ==========
	fmt.Println("===== 2.全表查询全部数据 =====")
	listSQL := `SELECT id,username,age FROM t_user`
	rows, err := db.Query(listSQL)
	if err != nil {
		fmt.Printf("全表查询失败:%v\n", err)
		return
	}
	defer rows.Close() //必须关闭结果集,防止连接池泄露

	//循环遍历所有行
	for rows.Next() {
		var id int
		var name string
		var age int
		err = rows.Scan(&id, &name, &age)
		if err != nil {
			fmt.Printf("扫描行数据异常:%v\n", err)
			continue
		}
		fmt.Printf("id:%d, username:%s, age:%d\n", id, name, age)
	}

	//遍历结束后必须检查rows.Err(),捕获遍历过程中发生的错误
	if err = rows.Err(); err != nil {
		fmt.Printf("遍历结果集出错:%v\n", err)
	}
	fmt.Println()

	fmt.Println("\n CRUD测试全部完成")
}

image20260907213245116.png

image20260907213323034.png

修改删除数据

package main

import (
	"database/sql"
	"fmt"
	_ "dm" //匿名导入达梦驱动,注册驱动
)

func main() {
	// DSN:log=1开启驱动底层日志;autoCommit=false关闭自动提交
	dsn := "dm://SYSDBA:DMdata456@192.168.43.3:5234?log=1&autoCommit=false"

	// 创建连接池句柄,Open不会真实建立网络连接
	db, err := sql.Open("dm", dsn)
	if err != nil {
		fmt.Printf("Open句柄失败:%v\n", err)
		return
	}
	defer db.Close() //程序退出关闭连接池释放资源

	// Ping真正建立数据库TCP连接
	err = db.Ping()
	if err != nil {
		fmt.Printf("Ping数据库失败:%v\n", err)
		return
	}
	fmt.Println(" 数据库连接成功\n")

	
	// ========== 3.更新示例 ==========
	fmt.Println("===== 3.更新id=1的数据 =====")
	updateSQL := `UPDATE t_user SET username=?,age=? WHERE id=?`
	resUpdate, err := db.Exec(updateSQL, "张三", 23, 1)
	if err != nil {
		fmt.Printf("更新失败:%v\n", err)
		return
	}
	affectUpdate, _ := resUpdate.RowsAffected()
	fmt.Printf("更新成功,受影响行数:%d\n", affectUpdate)
	_, err = db.Exec("COMMIT")
	if err != nil {
		fmt.Printf("提交事务失败:%v\n", err)
		return
	}
	fmt.Println()

	// ========== 4.删除示例 ==========
	fmt.Println("===== 4.删除id=3的数据 =====")
	delSQL := `DELETE FROM t_user WHERE id=?`
	resDel, err := db.Exec(delSQL, 3)
	if err != nil {
		fmt.Printf("删除失败:%v\n", err)
		return
	}
	affectDel, _ := resDel.RowsAffected()
	fmt.Printf("删除成功,受影响行数:%d\n", affectDel)
	_, err = db.Exec("COMMIT")
	if err != nil {
		fmt.Printf("提交事务失败:%v\n", err)
		return
	}
	fmt.Println()

	// ========== 5.删除后再次全表查询,验证结果 ==========
	fmt.Println("===== 5.删除后再次全表查询 =====")
	listSQL := `SELECT id,username,age FROM t_user`
	rows2, err := db.Query(listSQL)
	if err != nil {
		fmt.Printf("查询失败:%v\n", err)
		return
	}
	defer rows2.Close()

	for rows2.Next() {
		var id int
		var name string
		var age int
		err = rows2.Scan(&id, &name, &age)
		if err != nil {
			fmt.Printf("扫描行异常:%v\n", err)
			continue
		}
		fmt.Printf("id:%d, username:%s, age:%d\n", id, name, age)
	}
	if err = rows2.Err(); err != nil {
		fmt.Printf("遍历错误:%v\n", err)
	}
	

	fmt.Println("\n CRUD测试全部完成")
}

image20260907213701261.png

image20260907213758165.png

连接池使用

package main

import (
	"database/sql"   // Go标准数据库接口,管理连接池
	"fmt"            // 控制台打印输出
	"sync"           // 同步工具,wg等待协程完成
	"time"           // 时间、休眠

	_ "dm" // 匿名导入达梦驱动,注册驱动名称"dm"
)

func main() {
	// DSN连接串
	// log=1:开启达梦go驱动底层日志,可以看到新建连接、归还连接等报文
	// autoCommit=false:关闭自动提交,DML需要手动commit
	dsn := "dm://SYSDBA:DMdata456@192.168.43.3:5234?log=1&autoCommit=false"

	// sql.Open 创建连接池对象,**不会真正建立网络连接**
	db, err := sql.Open("dm", dsn)
	if err != nil {
		fmt.Printf("open创建连接池失败: %v\n", err)
		return
	}
	// 程序退出,关闭整个连接池,释放所有连接
	defer db.Close()

	// ========== 连接池核心参数配置 ==========
	db.SetMaxOpenConns(3)    // 最大活跃连接数:数据库侧最多同时建立3个连接
	db.SetMaxIdleConns(2)    // 最大空闲连接:连接用完归还后,最多保留2个空闲连接
	db.SetConnMaxLifetime(0) // 连接最大生命周期,0代表永不过期;生产建议设置,防止僵死连接
	db.SetConnMaxIdleTime(0) // 空闲连接超时,0代表永不过期

	// Ping() 真正发起TCP连接,测试数据库连通
	err = db.Ping()
	if err != nil {
		fmt.Printf("ping数据库失败: %v\n", err)
		return
	}
	fmt.Println(" 数据库连接成功")

	// 调用自定义函数,打印当前连接池统计信息
	printPoolStats(db)

	// ========== 并发测试场景 ==========
	// 启动5个goroutine并发执行查询,但是连接池最大只允许3个活跃连接
	// 预期:3个协程拿到连接执行,剩余2个协程排队等待连接
	var wg sync.WaitGroup
	wg.Add(5) // 等待5个协程全部完成

	for i := 0; i < 5; i++ {
		go func(id int) {
			defer wg.Done() // 协程结束,计数器-1

			var res int
			// 执行简单查询,会从连接池获取一个连接
			err := db.QueryRow("SELECT 1").Scan(&res)
			if err != nil {
				fmt.Printf("goroutine %d 查询出错: %v\n", id, err)
				return
			}
			fmt.Printf("goroutine %d 查询成功,返回结果=%d\n", id, res)

			// 模拟业务处理耗时,占用连接500ms
			// 在这500ms内,该连接处于InUse(使用中)状态,不会归还到空闲池
			time.Sleep(500 * time.Millisecond)
		}(i)
	}

	wg.Wait() // 阻塞等待全部5个协程执行完毕

	fmt.Println("\n===== 并发任务全部执行完毕,查看连接池状态 =====")
	printPoolStats(db)

	// 等待1秒,让使用完的连接归还到空闲池
	time.Sleep(1 * time.Second)
	fmt.Println("\n===== 等待1秒之后,连接池状态 =====")
	printPoolStats(db)
}

// printPoolStats 打印连接池统计指标
// db.Stats() 返回sql.DBStats结构体,包含连接池全部运行指标
func printPoolStats(db *sql.DB) {
	stats := db.Stats()
	fmt.Printf(`
===== sql.DB 连接池统计 =====
OpenConnections: %d   // 当前总连接数:活跃正在使用 + 空闲
Idle:            %d   // 当前空闲连接数,可以直接复用
InUse:           %d   // 当前正在被goroutine占用的连接
WaitCount:       %d   // 累计等待连接的次数;>0代表发生过连接排队
WaitDuration:    %v  // 所有协程等待连接累计耗时
MaxOpenConnections: %d // 配置的最大打开连接数
`,
		stats.OpenConnections,
		stats.Idle,
		stats.InUse,
		stats.WaitCount,
		stats.WaitDuration,
		stats.MaxOpenConnections,
	)
}

image20260907221219977.png

初始化阶段(Ping 之后)

  1. db.Ping() 建立了1 条数据库连接,放到空闲池;
  2. 没有业务请求,InUse=0,没有协程在使用连接;
  3. 还没有发生等待,WaitCount=0。

5 个协程并发执行

  1. 连接池上限是 3,同一时刻最多 3 个协程拿到数据库连接;
  2. 5 个协程同时发起请求,有2 个协程拿不到连接,进入排队等待

全部并发任务执行完成

  1. 所有协程执行完毕,连接全部归还到空闲池,InUse=0;
  2. 总连接数变成2:连接池自动新建了一条连接,不会销毁连接,保留在空闲池,供后续复用;
  3. WaitCount=2:累计发生过 2 次连接排队,这个是历史累计值,不会清零;
  4. WaitDuration=3.6017ms:所有协程等待连接的总耗时。
查看当前会话数
SELECT * from v$sessions;

image20260907220640002.png

评论
后发表回复

作者

文章

阅读量

获赞

扫一扫
联系客服