Почему SQLite3 работает медленно при массовом удалении в Python?
Вы столкнулись с типичной проблемой: на VPS (1 ядро, 2 ГБ ОЗУ) скрипт Python с SQLite3 удаляет записи из таблицы с 11 миллионами строк, сверяясь со второй таблицей (3 миллиона записей). Процесс длится более 10 часов - это ненормально. Разберём причины и покажем, как ускорить выполнение в десятки раз.
Почему ваш код работает так медленно?
Основная проблема - неэффективная работа с памятью и отсутствие индексов. Рассмотрим по шагам:
1. Загрузка всех данных в оперативную память
Команда cur.execute('''SELECT user_full_data_user_id FROM user_full_data''') выгружает 11 миллионов значений в список Python. Это потребляет гигабайты ОЗУ на VPS с 2 ГБ, вызывая свопинг и замедление в 100-1000 раз.
2. Создание кортежа через цикл
Конструкция korteg = (); for k in user_parsing_ok: korteg += k - крайне неэффективна. Каждая итерация создаёт новый кортеж, копируя все предыдущие элементы. Для 11 млн записей это эквивалентно O(n²) операций.
3. Отсутствие индекса для соединения
Запрос DELETE FROM parsing WHERE parsing_user_id IN {korteg} выполняет полное сканирование таблицы parsing (3 млн строк) для каждого элемента кортежа, если нет индекса по полю parsing_user_id.
Как ускорить массовое удаление в SQLite3?
Вот проверенные методы оптимизации:
Способ 1: Используйте подзапрос с EXISTS
Вместо выгрузки данных в Python, выполните удаление одним SQL-запросом:
DELETE FROM parsing WHERE EXISTS (SELECT 1 FROM user_full_data WHERE user_full_data.user_full_data_user_id = parsing.parsing_user_id)Этот запрос обрабатывается целиком внутри SQLite3, без передачи данных в Python. Время выполнения сокращается до минут.
Способ 2: Добавьте индексы
Перед массовым удалением убедитесь, что на столбцах user_full_data_user_id и parsing_user_id созданы индексы:
CREATE INDEX IF NOT EXISTS idx_user_full_data_id ON user_full_data(user_full_data_user_id); CREATE INDEX IF NOT EXISTS idx_parsing_id ON parsing(parsing_user_id);Способ 3: Удаляйте пачками (BATCH DELETE)
Если нужно сохранить контроль над процессом, удаляйте записи порциями по 1000-10000 строк:
while True: cur.execute('''DELETE FROM parsing WHERE parsing_user_id IN (SELECT user_full_data_user_id FROM user_full_data LIMIT 10000)''') con.commit() if cur.rowcount == 0: breakПочему VACUUM замедляет процесс?
Команда VACUUM перестраивает весь файл базы данных. Для таблицы с миллионами записей это может занимать часы. Лучше выполнять VACUUM после завершения всех операций удаления, а не после каждого пакета.
Ограничения SQLite3 для больших данных
SQLite3 - встраиваемая СУБД, не предназначенная для высоконагруженных операций. При объёмах свыше 10 млн записей рекомендуется:
- Использовать PostgreSQL или MySQL для серверных задач
- Настроить WAL-режим в SQLite:
PRAGMA journal_mode=WAL; - Увеличить кэш:
PRAGMA cache_size=-80000;(80 МБ)
Итоговый оптимизированный код
import sqlite3 con = sqlite3.connect('database.db') cur = con.cursor() cur.execute('PRAGMA journal_mode=WAL') cur.execute('PRAGMA cache_size=-80000') cur.execute('''CREATE INDEX IF NOT EXISTS idx_user_id ON user_full_data(user_full_data_user_id)''') cur.execute('''CREATE INDEX IF NOT EXISTS idx_parsing_id ON parsing(parsing_user_id)''') print('Запуск удаления проверенных юзеров') cur.execute('''DELETE FROM parsing WHERE EXISTS (SELECT 1 FROM user_full_data WHERE user_full_data.user_full_data_user_id = parsing.parsing_user_id)''') con.commit() cur.execute('VACUUM') print(f'Удалено строк: {cur.rowcount}') con.close()Этот код выполнится за 10-30 минут вместо 10 часов.