安装go环境,参考官网即可。
安装 dm 驱动包
进入环境变量GOPATH下的src文件下
新建项目文件dmtest,在此目录执行以下命令
go env -w GOPROXY=https://goproxy.cn,direct
go mod tidy
编写连接代码,内容如下
进行连接测试
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)
}
插入数据
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测试全部完成")
}
修改删除数据
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测试全部完成")
}
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,
)
}
初始化阶段(Ping 之后)
db.Ping() 建立了1 条数据库连接,放到空闲池;InUse=0,没有协程在使用连接;WaitCount=0。5 个协程并发执行
全部并发任务执行完成
InUse=0;WaitCount=2:累计发生过 2 次连接排队,这个是历史累计值,不会清零;WaitDuration=3.6017ms:所有协程等待连接的总耗时。查看当前会话数
SELECT * from v$sessions;
文章
阅读量
获赞
