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

  1. Ensure every AcquireGlobalReadLock has a matching ReleaseGlobalReadLock (defer or finally-style cleanup)
  2. Check and serialize lock acquisition — do not call AcquireGlobalReadLock concurrently on the same Mysqld
  3. 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

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


AI-assisted analysis of vitessio/vitess@01a25a7d17 (2026-09-01). Data as JSON: /api/errors/213f0341234df2ae. Report an issue: GitHub.