Golang中MYSQL GET_LOCK函数失效问题排查

Golang中MYSQL GET_LOCK函数失效问题排查 你好,

我在使用 MySQL 的 GET_LOCK 函数时遇到了问题,需要帮助。我的代码大致如下:

        var locked bool
        r := db.QueryRow("SELECT GET_LOCK('abc', 1)")
        if err := r.Scan(&locked); err != nil {
        	return false
        }
        fmt.Println("returning lock!", locked)
        return locked

locked 变量返回的结果看起来像是操作成功了,但我在数据库中检查 SELECT IS_FREE_LOCK('abc'),发现锁仍然是空闲的……所以看起来 GET_LOCK 没有生效。有什么想法吗?

先谢谢了!


更多关于Golang中MYSQL GET_LOCK函数失效问题排查的实战教程也可以访问 https://www.itying.com/category-94-b0.html

2 回复

似乎 Go 的 SQL 在查询完成后会终止会话,并在查询后断开连接,从而自动释放锁。有什么想法吗?

更多关于Golang中MYSQL GET_LOCK函数失效问题排查的实战系列教程也可以访问 https://www.itying.com/category-94-b0.html


在Go中使用MySQL的GET_LOCK函数时,锁失效通常是由于连接问题导致的。GET_LOCK获取的锁是与数据库连接绑定的,当连接释放或关闭时锁会自动释放。

你的代码中可能存在的问题是:每次调用都使用了不同的数据库连接,导致锁无法保持。以下是解决方案和示例代码:

1. 使用单一连接保持锁

// 获取专用连接
conn, err := db.Conn(context.Background())
if err != nil {
    return false
}
defer conn.Close()

var locked bool
err = conn.QueryRowContext(context.Background(), "SELECT GET_LOCK('abc', 1)").Scan(&locked)
if err != nil {
    return false
}

// 保持连接不释放,锁才会持续有效
// 执行需要加锁的操作...

// 手动释放锁(可选)
_, _ = conn.ExecContext(context.Background(), "SELECT RELEASE_LOCK('abc')")
return locked

2. 使用连接池并确保同一连接(Go 1.19+)

// 设置连接参数确保连接复用
db.SetConnMaxLifetime(0) // 连接永不过期
db.SetMaxIdleConns(1)    // 保持至少一个空闲连接

// 使用事务绑定连接
tx, err := db.BeginTx(context.Background(), nil)
if err != nil {
    return false
}
defer tx.Rollback()

var locked bool
err = tx.QueryRow("SELECT GET_LOCK('abc', 1)").Scan(&locked)
if err != nil {
    return false
}

// 在事务内执行需要加锁的操作...
// 事务提交或回滚时会自动释放连接和锁

3. 完整示例:分布式锁实现

func acquireLock(db *sql.DB, lockName string, timeout int) (bool, func()) {
    // 获取专用连接
    conn, err := db.Conn(context.Background())
    if err != nil {
        return false, nil
    }
    
    var locked bool
    query := fmt.Sprintf("SELECT GET_LOCK('%s', %d)", lockName, timeout)
    err = conn.QueryRowContext(context.Background(), query).Scan(&locked)
    if err != nil || !locked {
        conn.Close()
        return false, nil
    }
    
    // 返回释放函数
    release := func() {
        conn.ExecContext(context.Background(), "SELECT RELEASE_LOCK(?)", lockName)
        conn.Close()
    }
    
    return true, release
}

// 使用示例
func main() {
    db, _ := sql.Open("mysql", "user:pass@/dbname")
    defer db.Close()
    
    if ok, release := acquireLock(db, "my_lock", 10); ok {
        defer release()
        // 执行临界区代码
        fmt.Println("Lock acquired")
    }
}

关键点:

  1. 连接必须保持:获取锁后,对应的MySQL连接必须保持打开状态
  2. 避免连接池干扰:默认连接池可能导致连接切换,使用db.Conn()获取专用连接
  3. 锁超时处理:GET_LOCK的第二个参数是等待超时时间(秒)
  4. 验证锁状态:在同一个连接中验证SELECT IS_USED_LOCK('abc')

调试建议:

// 检查当前连接的锁状态
var lockResult int
err = conn.QueryRowContext(ctx, "SELECT IS_USED_LOCK('abc')").Scan(&lockResult)
if err == nil {
    fmt.Printf("Lock held by connection ID: %d\n", lockResult)
}

使用专用连接是解决GET_LOCK失效问题的关键。确保在持有锁期间不释放数据库连接,锁才能保持有效状态。

回到顶部