背景
我们使用了 PostgreSQL
表的记录作为任务只在单个节点上执行的全局锁,
并且使用的是 FOR UPDATE
的行级别锁,
但在锁已被表持有的情况下,如果要对表结构进行变更字段的话,会执行失败,
场景为旧部署服务已持有了行级别锁,新部署服务就变更不了表结构,
因为 DDL 操作(如添加列)会对整个表加锁,这两者发生了冲突。
解决方法:
- 尽量少变更作为锁机制的表结构
- 先停止旧部署服务,再启动新部署服务,不要让新旧服务同时存在
可以使用如下语句查询锁占用情况
-- 查看锁占用情况
SELECT * FROM pg_locks WHERE relation = 'service_deployments'::regclass;
示例代码
Go
语言下使用 Gorm
库获取 PostgreSQL
表的记录作为全局锁的示例代码
const (
ServiceDeploymentNameGlobalJobLock = "global-job-lock" // 全局任务锁
)
type ServiceDeployment struct {
Name string `gorm:"primarykey"` // 服务名称
Config string // 后续添加的字段
CreatedAt int64 `gorm:"autoCreateTime:milli"` // 记录创建时间,单位 milli
UpdatedAt int64 `gorm:"autoUpdateTime:milli"` // 记录更新时间,单位 milli
}
func GetServiceDeploymentGlobalJobLock(db *gorm.DB) (bool, error) {
deployment := ServiceDeployment{
Name: ServiceDeploymentNameGlobalJobLock,
}
if err := db.FirstOrCreate(&deployment, ServiceDeployment{Name: ServiceDeploymentNameGlobalJobLock}).Error; err != nil {
return false, err
}
tx := db.Session(&gorm.Session{Logger: logger.Discard}).Begin() // 不打印错误日志,拿到锁会占用一个数据库连接
exist, err := gormx.FindOne(tx.Clauses(clause.Locking{Strength: "UPDATE", Options: "NOWAIT"}).Where("name = ?", ServiceDeploymentNameGlobalJobLock), &deployment)
if err != nil {
if pgErr, ok := err.(*pgconn.PgError); ok && pgErr.Code == "55P03" {
err = nil
}
_ = tx.Rollback()
}
return exist, err
}