重构MySQL迁移逻辑,新增acquireMigrationLock适配Percona XtraDB Cluster的pxc_strict_mode限制,将原有的GET_LOCK迁移锁替换为更兼容的方案,同时为各迁移流程添加快速检查路径减少锁竞争
84 lines
2.7 KiB
Go
84 lines
2.7 KiB
Go
//go:build mysql
|
|
// +build mysql
|
|
|
|
package mysql
|
|
|
|
import (
|
|
"context"
|
|
|
|
"github.com/jmoiron/sqlx"
|
|
)
|
|
|
|
const userPrivacyMigrationLock = "CONCAT('im_user_privacy:', LEFT(SHA2(DATABASE(), 256), 40))"
|
|
|
|
// ensureUserPrivacySchema adds columns of im_user_privacy_settings that were
|
|
// introduced after the table's initial release. Databases created fresh by
|
|
// createIMUserProductTables already have them; legacy databases are upgraded
|
|
// in place. A missing table is skipped: it is created by the fresh-install
|
|
// path and there is nothing to upgrade yet.
|
|
func ensureUserPrivacySchema(db *sqlx.DB) error {
|
|
ctx := context.Background()
|
|
conn, err := db.Connx(ctx)
|
|
if err != nil {
|
|
return err
|
|
}
|
|
defer conn.Close()
|
|
|
|
// Fast path: check for missing columns without taking the migration lock.
|
|
// Databases created by createIMUserProductTables already have every column,
|
|
// so this is the common case and avoids GET_LOCK entirely (prohibited on
|
|
// Percona XtraDB Cluster with pxc_strict_mode=ENFORCING).
|
|
added, err := ensureUserPrivacyColumns(ctx, conn)
|
|
if err != nil || added {
|
|
return err
|
|
}
|
|
|
|
_, unlock, err := acquireMigrationLock(ctx, conn, userPrivacyMigrationLock)
|
|
if err != nil {
|
|
return err
|
|
}
|
|
if unlock != nil {
|
|
defer unlock()
|
|
}
|
|
|
|
// Double-check under the lock: another instance may have migrated while we
|
|
// were waiting. Without a lock (PXC fallback) this simply runs idempotently.
|
|
_, err = ensureUserPrivacyColumns(ctx, conn)
|
|
return err
|
|
}
|
|
|
|
// ensureUserPrivacyColumns adds any columns of im_user_privacy_settings that
|
|
// are missing and reports whether nothing was missing. A missing table is
|
|
// skipped: it is created by the fresh-install path and there is nothing to
|
|
// upgrade yet. Column definitions mirror createIMUserProductTables; the SELECT
|
|
// in userPrivacyByUID references every one of them.
|
|
func ensureUserPrivacyColumns(ctx context.Context, conn *sqlx.Conn) (bool, error) {
|
|
tableExists, err := identityTableExists(ctx, conn, "im_user_privacy_settings")
|
|
if err != nil || !tableExists {
|
|
return true, err
|
|
}
|
|
|
|
columns := []struct{ name, ddl string }{
|
|
{"profile_visibility", "VARCHAR(32) NOT NULL DEFAULT 'friends'"},
|
|
{"last_seen_visibility", "VARCHAR(32) NOT NULL DEFAULT 'friends'"},
|
|
{"read_receipts", "TINYINT NOT NULL DEFAULT 1"},
|
|
{"friend_request_verification", "TINYINT NOT NULL DEFAULT 1"},
|
|
{"group_invite_verification", "TINYINT NOT NULL DEFAULT 0"},
|
|
}
|
|
missing := false
|
|
for _, col := range columns {
|
|
exists, err := identityColumnExists(ctx, conn, "im_user_privacy_settings", col.name)
|
|
if err != nil {
|
|
return false, err
|
|
}
|
|
if exists {
|
|
continue
|
|
}
|
|
missing = true
|
|
if _, err = conn.ExecContext(ctx, "ALTER TABLE im_user_privacy_settings ADD COLUMN "+col.name+" "+col.ddl); err != nil {
|
|
return false, err
|
|
}
|
|
}
|
|
return !missing, nil
|
|
}
|