vitessio/vitess · error
lock already acquired
Error message
lock already acquired
What it means
AcquireGlobalReadLock enforces a single lock connection per Mysqld instance: mysqld.lockConn must be nil. If a read lock was already acquired (and not yet released), calling AcquireGlobalReadLock again returns this error, because two concurrent lock holders on the same connection state are not supported. Acquiring the lock twice would overwrite the remembered connection and leak the first lock.
Source
Thrown at go/vt/mysqlctl/query.go:323
query := fmt.Sprintf("SHOW STATUS LIKE '%s'", pattern)
qr, err := mysqld.FetchSuperQuery(ctx, query)
if err != nil {
return nil, err
}
if len(qr.Fields) != 2 {
return nil, fmt.Errorf("query %#v returned %d columns, expected 2", query, len(qr.Fields))
}
varMap := make(map[string]string, len(qr.Rows))
for _, row := range qr.Rows {
varMap[row[0].ToString()] = row[1].ToString()
}
return varMap, nil
}
// ExecuteSuperQuery allows the user to execute a query as a super user.
func (mysqld *Mysqld) AcquireGlobalReadLock(ctx context.Context) error {
if mysqld.lockConn != nil {
return errors.New("lock already acquired")
}
conn, err := getPoolReconnect(ctx, mysqld.dbaPool)
if err != nil {
return err
}
err = mysqld.executeSuperQueryListConn(ctx, conn, []string{"FLUSH TABLES WITH READ LOCK"})
if err != nil {
conn.Recycle()
return err
}
mysqld.lockConn = conn
return nil
}
func (mysqld *Mysqld) ReleaseGlobalReadLock(ctx context.Context) error {View on GitHub (pinned to 01a25a7d17)
Solutions
- Ensure every AcquireGlobalReadLock has a matching ReleaseGlobalReadLock (defer or finally-style cleanup)
- Check and serialize lock acquisition — do not call AcquireGlobalReadLock concurrently on the same Mysqld
- If the lock is stuck because of a leaked connection, restart the owning component or recreate the Mysqld instance so lockConn resets
Example fix
// before
mysqld.AcquireGlobalReadLock(ctx)
// ... forgot release ...
mysqld.AcquireGlobalReadLock(ctx) // error: lock already acquired
// after
if err := mysqld.AcquireGlobalReadLock(ctx); err == nil {
defer mysqld.ReleaseGlobalReadLock(ctx)
} Defensive patterns
Strategy: try-catch
Validate before calling
if mysqld.HasGlobalReadLock() { // if such an accessor exists; otherwise track locally
return errors.New("global read lock already held by this process")
}
err := mysqld.AcquireGlobalReadLock(ctx) Try / catch
if err := mysqld.AcquireGlobalReadLock(ctx); err != nil {
if strings.Contains(err.Error(), "lock already acquired") {
// either skip (lock already held) or wait and retry
return nil
}
return err
}
defer mysqld.ReleaseGlobalReadLock(ctx) Prevention
- Always pair acquire with a deferred release
- Serialize lock acquisition with a local mutex around the Mysqld
- Avoid sharing one Mysqld instance across components that each need the read lock
When it happens
Trigger: Calling AcquireGlobalReadLock twice without an intervening ReleaseGlobalReadLock; concurrent goroutines both acquiring the lock on the same Mysqld; a previous owner failing to release due to an error path skipping ReleaseGlobalReadLock.
Common situations: Refactored backup/health-check code that forgot a matching release; concurrent tasks sharing one Mysqld instance; leaked lockConn after a partial failure between acquire and release.
Related errors
- no read locks acquired yet
- ReadFile cannot be called on read-write backup
- AddFile cannot be called on read-only backup
- EndBackup cannot be called on read-only backup
- AbortBackup cannot be called on read-only backup
AI-assisted analysis of vitessio/vitess@01a25a7d17 (2026-09-01).
Data as JSON: /api/errors/213f0341234df2ae.
Report an issue: GitHub.