Атомарные операции¶
Мутаторы обеспечивают атомарное выполнение операций на уровне БД.
Проблема race condition¶
// ❌ Плохо — race condition
user, _ := user.SelectById(ctx, 123)
user.SetCounter(user.GetCounter() + 1) // Чтение + модификация в памяти
user.Update(ctx) // Запись
// Между чтением и записью другой процесс мог изменить counter
// ✅ Хорошо — атомарная операция
user, _ := user.SelectById(ctx, 123)
user.IncCounter(1) // Операция выполнится в БД
user.Update(ctx)
Как это работает¶
sequenceDiagram
participant App as Приложение
participant DB as База данных
Note over App,DB: ❌ Без мутатора (race condition)
App->>DB: SELECT counter FROM users WHERE id=123
DB-->>App: counter = 10
Note over App: counter = 10 + 1 = 11
App->>DB: UPDATE users SET counter=11 WHERE id=123
Note over App,DB: ✅ С мутатором (атомарно)
App->>DB: UPDATE users SET counter=counter+1 WHERE id=123
Арифметические мутаторы¶
Inc (инкремент)¶
Dec (декремент)¶
Битовые мутаторы¶
SetBit¶
Устанавливает биты (OR):
ClearBit¶
Очищает биты (AND NOT):
And, Or, Xor¶
user.OrFlags(0x10) // flags = flags | 0x10
user.AndFlags(0xFF) // flags = flags & 0xFF
user.XorFlags(0x08) // flags = flags ^ 0x08 (toggle)
user.Update(ctx)
Именованные флаги¶
Объявление¶
type FieldsUser struct {
Flags uint32 `ar:""`
}
type FlagsUser struct {
Flags string `ar:"flags:Active,Verified,Premium,_,Admin,Banned"`
}
Генерируемые константы¶
const (
FlagsActiveFlag = 1 << 0 // 0x01
FlagsVerifiedFlag = 1 << 1 // 0x02
FlagsPremiumFlag = 1 << 2 // 0x04
// позиция 3 пропущена
FlagsAdminFlag = 1 << 4 // 0x10
FlagsBannedFlag = 1 << 5 // 0x20
)
Использование¶
// Установка флага
user.SetBitFlags(user.FlagsPremiumFlag)
// Очистка флага
user.ClearBitFlags(user.FlagsActiveFlag)
// Несколько флагов сразу
user.SetBitFlags(user.FlagsActiveFlag | user.FlagsVerifiedFlag)
// Применение
user.Update(ctx)
// Проверка флага
if user.GetFlags() & user.FlagsVerifiedFlag != 0 {
fmt.Println("Пользователь верифицирован")
}
// Проверка нескольких флагов (любой из)
if user.GetFlags() & (user.FlagsPremiumFlag | user.FlagsAdminFlag) != 0 {
fmt.Println("Premium или Admin")
}
// Проверка всех флагов
mask := user.FlagsPremiumFlag | user.FlagsVerifiedFlag
if user.GetFlags() & mask == mask {
fmt.Println("Оба флага установлены")
}
Комбинирование операций¶
Несколько мутаторов в одном Update:
user, _ := user.SelectById(ctx, 123)
// Все операции накапливаются
user.IncLoginCount(1)
user.DecPoints(10)
user.SetBitFlags(user.FlagsActiveFlag)
user.ClearBitFlags(user.FlagsBannedFlag)
// Один запрос к БД со всеми операциями
user.Update(ctx)
SQL для PostgreSQL:
UPDATE users SET
login_count = login_count + 1,
points = points - 10,
flags = (flags | 1) & ~32
WHERE id = 123
Порядок применения¶
При Update() операции применяются в порядке:
- Обычные SET (из
Set*методов) - Арифметические мутаторы (inc, dec)
- Битовые мутаторы (and, or, xor, set_bit, clear_bit)
user.SetCounter(100) // 1. SET counter = 100
user.IncCounter(10) // 2. counter = counter + 10 (станет 110)
user.SetBitFlags(0x01) // 3. flags = flags | 1
user.Update(ctx)
Синхронизация значений¶
После Update() значения в объекте синхронизируются:
user, _ := user.SelectById(ctx, 123)
fmt.Println(user.GetCounter()) // 100
user.IncCounter(10)
user.Update(ctx)
fmt.Println(user.GetCounter()) // 110 (обновлённое значение)
Практические примеры¶
Счётчик просмотров¶
func IncrementViews(ctx context.Context, articleId int64) error {
article, err := article.SelectById(ctx, articleId)
if err != nil {
return err
}
article.IncViewCount(1)
return article.Update(ctx)
}
Система достижений¶
const (
AchievementFirst = 1 << 0
AchievementHundred = 1 << 1
AchievementThousand = 1 << 2
)
func CheckAchievements(ctx context.Context, userId int64) error {
user, _ := user.SelectById(ctx, userId)
count := user.GetPostCount()
var newAchievements uint32
if count >= 1 && user.GetAchievements() & AchievementFirst == 0 {
newAchievements |= AchievementFirst
}
if count >= 100 && user.GetAchievements() & AchievementHundred == 0 {
newAchievements |= AchievementHundred
}
if newAchievements != 0 {
user.SetBitAchievements(newAchievements)
return user.Update(ctx)
}
return nil
}
Переключение статуса¶
func ToggleFeature(ctx context.Context, userId int64, feature uint32) error {
user, _ := user.SelectById(ctx, userId)
user.XorFlags(feature) // Toggle bit
return user.Update(ctx)
}
Best Practices¶
1. Используйте мутаторы для счётчиков¶
// ❌ Race condition
user.SetBalance(user.GetBalance() - amount)
// ✅ Атомарно
user.DecBalance(amount)
2. Используйте именованные флаги¶
3. Группируйте операции¶
// ❌ Несколько запросов
user.IncCounter(1)
user.Update(ctx)
user.SetBitFlags(flag)
user.Update(ctx)
// ✅ Один запрос
user.IncCounter(1)
user.SetBitFlags(flag)
user.Update(ctx)
Следующие шаги¶
- Конфигурация — настройка подключений
- Мутаторы и флаги — декларация мутаторов