Golang中多协程访问Oracle数据库的实现与优化

Golang中多协程访问Oracle数据库的实现与优化 我们有一个服务,在一个无限循环中执行多个SQL语句,每5秒暂停一次。我想知道我在数据库处理方面是否采用了正确的流程。我创建了一个全局变量,在服务启动时,我们也启动与数据库的连接并将其保存在这个全局变量中。然后对于SQL的执行,我创建了一个局部变量来执行查询或更新、删除、插入(DML)操作,这些局部变量使用全局变量中的数据库来执行。

以下是代码:

type GERENCIACON struct {
  DataBase *sql.DB
}

func (gc *GERENCIACON) F_FECHAR_CONEXAO() {
  gc.DataBase.Close()
}

func (gc *GERENCIACON) F_ABRIR_CONEXAO()  {	
if gc.DataBase == nil || gc.DataBase.Ping() != nil {	
	gc.DataBase, _ = sql.Open("goracle", "XXXX/XXXX@10.0.254.10:1521/orcl")
}
}

var VGGerenciaConexao GERENCIACON

全局变量是 VGGerenciaCON。

现在是我在代码中创建的用于查询和DML的局部变量代码:

  type GERENCIACONSULTA struct {
DataBase *sql.DB
Rows *sql.Rows
}

func (gc *GERENCIACONSULTA) F_EXECUTA_CONSULTA(pSql string) {
VGGerenciaConexao.F_ABRIR_CONEXAO()
gc.DataBase = VGGerenciaConexao.DataBase
gc.Rows, _ = gc.DataBase.Query(pSql)
}

type GERENCIADML struct {
DataBase *sql.DB
Stmt *sql.Stmt
Result sql.Result
Error error
}

func (gd *GERENCIADML) F_EXECUTA_DML(pSql string) {
VGGerenciaConexao.F_ABRIR_CONEXAO()
gd.DataBase = VGGerenciaConexao.DataBase
gd.Stmt, gd.Error = gd.DataBase.Prepare(pSql)
gd.Result, gd.Error = gd.Stmt.Exec()
}

如你所见,我检查全局变量的连接,并在出错时启动它,然后将数据库的值分配给局部变量。我在所有服务中都使用这种结构。

这样做可以吗?我遇到了一些崩溃,所以我仍在尝试找出原因。


更多关于Golang中多协程访问Oracle数据库的实现与优化的实战教程也可以访问 https://www.itying.com/category-94-b0.html

2 回复

更好的做法是创建一个例程来返回你的数据库连接对象,在 goroutine 中将其作为局部变量使用,然后在 defer 中关闭该连接…

我认为 sql.DB 已经处理了数据库连接池。

更多关于Golang中多协程访问Oracle数据库的实现与优化的实战系列教程也可以访问 https://www.itying.com/category-94-b0.html


你的代码存在几个关键问题,特别是在多协程环境下访问Oracle数据库时。以下是主要问题和改进方案:

主要问题

  1. 连接池竞争sql.DB是线程安全的,但你的F_ABRIR_CONEXAO()方法存在竞态条件
  2. 错误处理缺失:忽略了sql.OpenQueryPrepare等操作的错误
  3. 连接泄漏:没有关闭RowsStmt
  4. 全局状态管理不当Ping()调用可能在高并发下成为瓶颈

改进后的实现

import (
    "database/sql"
    "sync"
    "time"
    _ "github.com/godror/godror" // 推荐使用godror驱动
)

type DBManager struct {
    db     *sql.DB
    mu     sync.RWMutex
    config string
}

var (
    globalDB *DBManager
    once     sync.Once
)

func GetDBManager(dsn string) *DBManager {
    once.Do(func() {
        globalDB = &DBManager{
            config: dsn,
        }
        globalDB.initialize()
    })
    return globalDB
}

func (dm *DBManager) initialize() {
    dm.mu.Lock()
    defer dm.mu.Unlock()
    
    if dm.db != nil {
        return
    }
    
    db, err := sql.Open("godror", dm.config)
    if err != nil {
        panic(err)
    }
    
    // 配置连接池
    db.SetMaxOpenConns(25)
    db.SetMaxIdleConns(10)
    db.SetConnMaxLifetime(5 * time.Minute)
    db.SetConnMaxIdleTime(2 * time.Minute)
    
    // 测试连接
    if err := db.Ping(); err != nil {
        panic(err)
    }
    
    dm.db = db
}

func (dm *DBManager) GetDB() *sql.DB {
    dm.mu.RLock()
    defer dm.mu.RUnlock()
    return dm.db
}

func (dm *DBManager) Close() {
    dm.mu.Lock()
    defer dm.mu.Unlock()
    if dm.db != nil {
        dm.db.Close()
        dm.db = nil
    }
}

// 查询执行器
type QueryExecutor struct {
    db *sql.DB
}

func NewQueryExecutor() *QueryExecutor {
    dbMgr := GetDBManager("user/pass@host:port/service")
    return &QueryExecutor{
        db: dbMgr.GetDB(),
    }
}

func (qe *QueryExecutor) ExecuteQuery(query string, args ...interface{}) (*sql.Rows, error) {
    return qe.db.Query(query, args...)
}

func (qe *QueryExecutor) ExecuteQueryContext(ctx context.Context, query string, args ...interface{}) (*sql.Rows, error) {
    return qe.db.QueryContext(ctx, query, args...)
}

// DML执行器
type DMLExecutor struct {
    db *sql.DB
}

func NewDMLExecutor() *DMLExecutor {
    dbMgr := GetDBManager("user/pass@host:port/service")
    return &DMLExecutor{
        db: dbMgr.GetDB(),
    }
}

func (de *DMLExecutor) ExecuteDML(query string, args ...interface{}) (sql.Result, error) {
    return de.db.Exec(query, args...)
}

func (de *DMLExecutor) ExecuteDMLContext(ctx context.Context, query string, args ...interface{}) (sql.Result, error) {
    return de.db.ExecContext(ctx, query, args...)
}

// 使用示例
func worker(id int, stop chan bool) {
    qe := NewQueryExecutor()
    de := NewDMLExecutor()
    
    for {
        select {
        case <-stop:
            return
        default:
            // 执行查询
            rows, err := qe.ExecuteQuery("SELECT * FROM users WHERE status = ?", "active")
            if err != nil {
                log.Printf("Worker %d query error: %v", id, err)
                continue
            }
            defer rows.Close()
            
            // 处理结果...
            
            // 执行DML
            result, err := de.ExecuteDML("UPDATE users SET last_seen = ? WHERE id = ?", time.Now(), id)
            if err != nil {
                log.Printf("Worker %d update error: %v", id, err)
                continue
            }
            
            affected, _ := result.RowsAffected()
            log.Printf("Worker %d updated %d rows", id, affected)
            
            time.Sleep(5 * time.Second)
        }
    }
}

// 主函数
func main() {
    stop := make(chan bool)
    
    // 启动多个协程
    for i := 0; i < 10; i++ {
        go worker(i, stop)
    }
    
    // 运行一段时间后停止
    time.Sleep(30 * time.Second)
    close(stop)
    time.Sleep(1 * time.Second)
    
    // 关闭数据库连接
    if globalDB != nil {
        globalDB.Close()
    }
}

关键优化点

  1. 单例模式:确保只有一个数据库连接池实例
  2. 连接池配置:合理设置连接池参数
  3. 上下文支持:使用QueryContextExecContext支持超时和取消
  4. 错误处理:正确处理所有可能返回的错误
  5. 资源清理:确保关闭RowsStmt
  6. 线程安全:使用读写锁保护共享状态

崩溃可能的原因

  1. 连接泄漏:没有关闭查询结果
  2. 竞态条件:多个协程同时调用F_ABRIR_CONEXAO()
  3. 空指针引用:当Ping()失败时,gc.DataBase可能为nil
  4. 驱动问题:建议使用维护更活跃的godror驱动替代goracle

这个改进方案应该能解决你遇到的崩溃问题,并提供更好的性能和稳定性。

回到顶部