Golang中多会话访问本地数据库的问题与解决方案

Golang中多会话访问本地数据库的问题与解决方案 我不知道该如何提出这个问题,但对于服务器位于本地主机时如何管理会话感到困惑。

直接访问数据库
在没有中间件(REST)的情况下,一旦连接到数据库就会创建会话。即使来自同一台计算机的两个连接也会获得两个独立的会话。

session1

通过Go和本地主机(127.0.0.1)访问

使用Golang作为中间件时,所有连接都通过127.0.0.1进行,并且只会有一个单独的会话。

session2

我的问题是:

  1. 有没有办法将cookie传递给Go以创建独立的数据库会话?

  2. 或者这就是它应有的方式?

任何处理这个问题的线索都将不胜感激。


更多关于Golang中多会话访问本地数据库的问题与解决方案的实战教程也可以访问 https://www.itying.com/category-94-b0.html

4 回复

内置的连接池解决了这个问题。进行了快速测试,看起来运行正常。

更多关于Golang中多会话访问本地数据库的问题与解决方案的实战系列教程也可以访问 https://www.itying.com/category-94-b0.html


geosoft1:

好吧,我不太清楚你的应用程序具体是做什么的

通用的增删改查功能。理论上你可以通过公共IP地址访问数据库,并自动获取所需的会话。但如果数据库是公开的,使用REST还有什么意义呢?

我不太清楚你的应用程序具体是做什么的,但一般来说,独立的会话意味着为你的服务提供独立的环境,这是件好事。我认为在中间件上为多个服务使用公共会话,除了少数情况(比如身份验证),可能会因各种原因产生问题(我不确定…如果该会话过期会发生什么…丢失所有端点?挂起或其他情况,并发问题,返回结果等)。

思考中

在Golang中管理数据库会话时,您观察到的行为是正常的,特别是在使用连接池的情况下。让我详细解释并提供解决方案。

问题分析

当Go应用作为中间件访问本地数据库时,通常会使用数据库连接池。这意味着多个客户端请求可能共享同一个数据库连接,从而导致共享会话状态。

解决方案

1. 使用连接字符串参数控制会话

对于PostgreSQL,您可以在连接字符串中添加会话相关参数:

package main

import (
    "database/sql"
    "fmt"
    "log"
    _ "github.com/lib/pq"
)

func main() {
    // 为每个"逻辑会话"使用不同的连接参数
    connStr := "user=youruser dbname=yourdb sslmode=disable application_name=session_1"
    
    db, err := sql.Open("postgres", connStr)
    if err != nil {
        log.Fatal(err)
    }
    defer db.Close()
    
    // 设置会话级参数
    _, err = db.Exec("SET application_name = 'user_session_1'")
    if err != nil {
        log.Fatal(err)
    }
}

2. 实现基于上下文的会话管理

package main

import (
    "context"
    "database/sql"
    "log"
    "net/http"
    "time"
    _ "github.com/lib/pq"
)

type SessionAwareDB struct {
    db *sql.DB
}

func (s *SessionAwareDB) QueryWithSession(ctx context.Context, query string, args ...interface{}) (*sql.Rows, error) {
    // 从上下文获取会话ID
    sessionID, ok := ctx.Value("session_id").(string)
    if !ok {
        sessionID = "default"
    }
    
    // 设置会话特定参数
    _, err := s.db.ExecContext(ctx, "SET myapp.session_id = $1", sessionID)
    if err != nil {
        return nil, err
    }
    
    return s.db.QueryContext(ctx, query, args...)
}

func main() {
    db, err := sql.Open("postgres", "user=youruser dbname=yourdb sslmode=disable")
    if err != nil {
        log.Fatal(err)
    }
    defer db.Close()
    
    sessionDB := &SessionAwareDB{db: db}
    
    http.HandleFunc("/data", func(w http.ResponseWriter, r *http.Request) {
        // 从cookie获取会话ID
        sessionCookie, err := r.Cookie("session_id")
        var sessionID string
        if err == nil {
            sessionID = sessionCookie.Value
        } else {
            sessionID = generateSessionID()
        }
        
        ctx := context.WithValue(r.Context(), "session_id", sessionID)
        
        rows, err := sessionDB.QueryWithSession(ctx, "SELECT * FROM my_table")
        if err != nil {
            http.Error(w, err.Error(), http.StatusInternalServerError)
            return
        }
        defer rows.Close()
        
        // 处理查询结果
    })
    
    log.Fatal(http.ListenAndServe(":8080", nil))
}

func generateSessionID() string {
    return fmt.Sprintf("session_%d", time.Now().UnixNano())
}

3. 使用连接级别的会话隔离

package main

import (
    "database/sql"
    "sync"
    _ "github.com/lib/pq"
)

type SessionManager struct {
    db        *sql.DB
    sessions  map[string]*sql.Conn
    mutex     sync.RWMutex
}

func NewSessionManager(db *sql.DB) *SessionManager {
    return &SessionManager{
        db:       db,
        sessions: make(map[string]*sql.Conn),
    }
}

func (sm *SessionManager) GetSession(sessionID string) (*sql.Conn, error) {
    sm.mutex.RLock()
    conn, exists := sm.sessions[sessionID]
    sm.mutex.RUnlock()
    
    if exists {
        return conn, nil
    }
    
    // 创建新的数据库连接用于此会话
    conn, err := sm.db.Conn(sessionID)
    if err != nil {
        return nil, err
    }
    
    sm.mutex.Lock()
    sm.sessions[sessionID] = conn
    sm.mutex.Unlock()
    
    return conn, nil
}

func (sm *SessionManager) CloseSession(sessionID string) error {
    sm.mutex.Lock()
    defer sm.mutex.Unlock()
    
    if conn, exists := sm.sessions[sessionID]; exists {
        delete(sm.sessions, sessionID)
        return conn.Close()
    }
    
    return nil
}

4. 数据库特定的会话控制

对于PostgreSQL,您可以使用事务级别的设置:

func executeInSession(db *sql.DB, sessionID string, query string) error {
    tx, err := db.Begin()
    if err != nil {
        return err
    }
    defer tx.Rollback()
    
    // 设置会话参数
    _, err = tx.Exec("SET LOCAL myapp.session_id = $1", sessionID)
    if err != nil {
        return err
    }
    
    // 执行查询
    _, err = tx.Exec(query)
    if err != nil {
        return err
    }
    
    return tx.Commit()
}

总结

  1. 这是正常行为:Go应用使用连接池时,多个客户端可能共享数据库连接
  2. 可以通过以下方式实现会话隔离
    • 使用连接字符串参数
    • 实现基于上下文的会话管理
    • 使用独立的数据库连接
    • 利用数据库的会话/事务级设置

选择哪种方案取决于您的具体需求、数据库类型和性能要求。对于大多数Web应用,基于上下文的会话管理方案通常是最实用的选择。

回到顶部