Я использую SQLite3 в одном из моих проектов, и мне нужно убедиться, что строки, вставленные в таблицу, уникальны в отношении комбинации некоторых из их колонны. В большинстве случаев вставленные строки будут отличаться в этом отношении, но в случае соответствия новая строка должна обновить/заменить существующий.
Очевидным решением было использование составного первичного ключа с предложением конфликта для обработки конфликтов. Для этого:
CREATE TABLE Event (Id INTEGER, Fld0 TEXT, Fld1 INTEGER, Fld2 TEXT, Fld3 TEXT, Fld4 TEXT, Fld5 TEXT, Fld6 TEXT);
стало следующим:
CREATE TABLE Event (Id INTEGER, Fld0 TEXT, Fld1 INTEGER, Fld2 TEXT, Fld3 TEXT, Fld4 TEXT, Fld5 TEXT, Fld6 TEXT, PRIMARY KEY (Fld0, Fld2, Fld3) ON CONFLICT REPLACE);
Это действительно приводит к ограничению уникальности, в котором я нуждаюсь. К сожалению, это изменение также приводит к снижению производительности, которое выходит за рамки того, что я ожидал. я сделал
несколько тестов с использованием утилиты командной строки sqlite3
, чтобы убедиться, что в остальной части моего кода нет ошибки. Тест включает ввод 100 000 строк, либо в одном
транзакции или в 100 транзакций по 1000 строк каждая. Я получил следующие результаты:
| 1 * 100,000 | 10 * 10,000 | 100 * 1,000 |
|---------------|---------------|---------------|
| Time | CPU | Time | CPU | Time | CPU |
| (sec) | (%) | (sec) | (%) | (sec) | (%) |
--------------------------------|-------|-------|-------|-------|-------|-------|
No primary key | 2.33 | 80 | 3.73 | 50 | 15.1 | 15 |
--------------------------------|-------|-------|-------|-------|-------|-------|
Primary key: Fld3 | 5.19 | 84 | 23.6 | 21 | 226.2 | 3 |
--------------------------------|-------|-------|-------|-------|-------|-------|
Primary key: Fld2, Fld3 | 5.11 | 88 | 24.6 | 22 | 258.8 | 3 |
--------------------------------|-------|-------|-------|-------|-------|-------|
Primary key: Fld0, Fld2, Fld3 | 5.38 | 87 | 23.8 | 23 | 232.3 | 3 |
В настоящее время мое приложение выполняет транзакции не более 1000 строк, и я был удивлен 15-кратным снижением производительности. Я ожидал максимум 3-кратного снижения пропускной способности и увеличения использования ЦП, как видно в случае транзакции в 100 тыс. Транзакций. Я предполагаю, что индексирование, связанное с поддержанием ограничений первичного ключа, требует значительно большего числа синхронных операций БД, что делает мои жесткие диски узким местом в этом случае.
Использование режима WAL имеет некоторый эффект - увеличение производительности примерно на 15%. К сожалению, этого недостаточно. PRAGMA synchronous = NORMAL
, похоже, не имеет никакого эффекта.
Я мог бы восстановить некоторую производительность, увеличив размер транзакции, но я бы предпочел не делать этого из-за увеличения использования памяти и проблем с отзывчивостью и надежность.
Текстовые поля в каждой строке имеют переменную длину около 250 байтов в среднем. Производительность запросов не имеет большого значения, но производительность вставки очень важна. Мой код приложения находится на C и является (предположительно) переносимым, по крайней мере, для Linux и Windows.
Есть ли способ улучшить производительность вставки без увеличения размера транзакции? Либо какая-то настройка в SQLite (что-то, но постоянно заставляющее БД в асинхронную операцию, то есть) или программно в моем коде приложения? Например, существует ли способ обеспечить уникальность строк без использования индекса?
BOUNTY:
Используя метод хеширования/индексации, описанный в моем собственном ответе, мне удалось несколько снизить падение производительности до такой степени, что это, вероятно, приемлемо для моего приложения. Кажется, однако, что по мере увеличения количества строк в таблице наличие индекса делает вставки медленнее и медленнее.
Меня интересует какой-либо метод или настройка тонкой настройки, которые повысят производительность в данном конкретном случае использования, если это не связано с взломом кода SQLite3 или иным образом заставило проект стать незаменимым.