Миграция базы данных - Исправление дублирующихся подписок
Проблема
В боте была обнаружена ошибка, при которой после истечения подписки создавалась новая запись вместо обновления существующей. Это приводило к дублированию записей в таблице subscriptions.
Решение
Добавлена автоматическая миграция, которая:
- Обнаруживает дублирующиеся подписки (несколько записей для одного
user_id) - Удаляет старые записи, оставляя только самую новую
- Работает с разными типами баз данных (SQLite, PostgreSQL, MySQL)
Запуск
Автоматический запуск
Миграция запускается автоматически при старте бота:
# С Docker
docker-compose up
# Без Docker
python main.pyРучная проверка состояния БД
# Создайте скрипт check_subscriptions.py
python -c "
import asyncio
from app.database.universal_migration import run_universal_migration
asyncio.run(run_universal_migration())
"Переменные окружения
SKIP_MIGRATION=true- пропустить миграцию при запуске (не рекомендуется)DATABASE_URL- строка подключения к базе данных
Поддерживаемые базы данных
- SQLite (по умолчанию):
sqlite+aiosqlite:///./bot.db - PostgreSQL:
postgresql+asyncpg://user:password@host:port/dbname - MySQL:
mysql+aiomysql://user:password@host:port/dbname
Логи миграции
Миграция выводит подробные логи:
INFO - Тип базы данных: sqlite
INFO - Всего подписок: 150
INFO - Уникальных пользователей: 140
INFO - Найдено 10 пользователей с дублирующимися подписками
INFO - Удалено 15 дублирующихся подписок для пользователя 123
INFO - Всего удалено дублирующихся подписок: 15
INFO - === МИГРАЦИЯ ЗАВЕРШЕНА УСПЕШНО ===
Безопасность
- Миграция создает резервную копию логики - удаляет только дубликаты, оставляя самую новую запись
- Если миграция не может определить самую новую запись, она оставляет запись с максимальным
id - Миграция выполняется в транзакции - при ошибке все изменения откатываются
Проблемы и решения
База данных заблокирована
# Остановите все процессы, использующие БД
docker-compose down
# Запустите снова
docker-compose upОшибка "UNIQUE constraint failed"
Это означает, что миграция не была выполнена до добавления unique=True в модель. Выполните миграцию вручную.
Принудительная очистка (ОСТОРОЖНО!)
-- Только для крайних случаев! Создайте бэкап!
DELETE FROM subscriptions
WHERE id NOT IN (
SELECT MIN(id)
FROM subscriptions
GROUP BY user_id
);Разработка
После успешной миграции можно безопасно обновлять модели:
# В models.py можно добавить:
user_id = Column(Integer, ForeignKey("users.id"), nullable=False, unique=True)Тестирование
Для тестирования на новой базе установите переменную:
export SKIP_MIGRATION=trueТогда миграция не запустится автоматически.