Низкая производительность массовой вставки с использованием Python 3 и SQLite

1

У меня мало текстовых файлов, содержащих URL-адреса. Я пытаюсь создать базу данных SQLite для хранения этих URL-адресов в таблице. Таблица URL имеет два столбца, т.е. первичный ключ (INTEGER) и URL (ТЕКСТ).

Я пытаюсь вставить 100 000 записей в одну команду и цикл вставки, пока я не закончу список URL. В принципе, прочитайте все содержимое текстовых файлов и сохраните их в списке, а затем я использую создать меньший список из 100 000 записей и вставить в таблицу.

Всего URL-адресов в текстовых файлах составляет 4,591,415, а общий размер текстового файла - около 97,5 МБ.

Проблемы:

  1. Когда я выбрал базу данных файлов, для ее вставки требуется около 7-7,5 минут. Я чувствую, что это не очень быстрая вставка, учитывая, что у меня твердый жесткий диск, который имеет более быструю запись/запись. Наряду с этим у меня есть примерно 10 ГБ оперативной памяти, как показано в диспетчере задач. Процессор i5-6300U 2,4 ГГц.

  2. Общие текстовые файлы составляют ок. 97,5 МБ. Но после того, как я вставляю URL-адреса в SQLite, база данных SQLite составляет приблизительно 350 МБ, т.е. почти в 3,5 раза больше исходного размера. Поскольку база данных не содержит никаких других таблиц, индексов и т.д., Этот размер базы данных выглядит немного странным.

Для проблемы 1 я попробовал играть с параметрами и придумал как можно лучшие на основе тестовых прогонов с разными параметрами.

table, th, td {
    border: 1px solid black;
    border-collapse: collapse;
}
th, td {
    padding: 15px;
    text-align: left;
}
<table style="width:100%">
<tr> 
<th>Configuration</th>
<th>Time</th>    
</tr>
  
<tr><th>50,000 - with journal = delete and no transaction                           </th><th>0:12:09.888404</th></tr>
<tr><th>50,000 - with journal = delete and with transaction                         </th><th>0:22:43.613580</th></tr>
<tr><th>50,000 - with journal = memory and transaction                              </th><th>0:09:01.140017</th></tr>
<tr><th>50,000 - with journal = memory                                              </th><th>0:07:38.820148</th></tr>
<tr><th>50,000 - with journal = memory and synchronous=0                            </th><th>0:07:43.587135</th></tr>
<tr><th>50,000 - with journal = memory and synchronous=1 and page_size=65535        </th><th>0:07:19.778217</th></tr>
<tr><th>50,000 - with journal = memory and synchronous=0 and page_size=65535        </th><th>0:07:28.186541</th></tr>
<tr><th>50,000 - with journal = delete and synchronous=1 and page_size=65535        </th><th>0:07:06.539198</th></tr>
<tr><th>50,000 - with journal = delete and synchronous=0 and page_size=65535        </th><th>0:07:19.810333</th></tr>
<tr><th>50,000 - with journal = wal and synchronous=0 and page_size=65535           </th><th>0:08:22.856690</th></tr>
<tr><th>50,000 - with journal = wal and synchronous=1 and page_size=65535           </th><th>0:08:22.326936</th></tr>
<tr><th>50,000 - with journal = delete and synchronous=1 and page_size=4096         </th><th>0:07:35.365883</th></tr>
<tr><th>50,000 - with journal = memory and synchronous=1 and page_size=4096         </th><th>0:07:15.183948</th></tr>
<tr><th>1,00,000 - with journal = delete and synchronous=1 and page_size=65535      </th><th>0:07:13.402985</th></tr>



</table>

Я просматривал онлайн и видел эту ссылку https://adamyork.com/2017/07/02/fast-database-inserts-with-python-3-6-and-sqlite/, где система намного медленнее, чем я, но все еще очень хорошо. Две вещи, которые выделялись из этой ссылки, были:

  1. В таблице в ссылке было больше столбцов, чем у меня.
  2. Файл базы данных не увеличился в 3,5 раза.

Я поделился кодом python и файлами здесь: https://github.com/ksinghgithub/python_sqlite

Может ли кто-нибудь помочь мне оптимизировать этот код. Благодарю.

Среда:

  1. Windows 10 Professional на i5-6300U и 20 ГБ оперативной памяти и 512 SSD.
  2. Python 3.7.0

Редактировать 1 :: Новая диаграмма производительности, основанная на обратной связи, полученной по ограничению UNIQUE, и мне играет с размером кеша.

self.db.execute('CREATE TABLE blacklist (id INTEGER NOT NULL PRIMARY KEY AUTOINCREMENT, url TEXT NOT NULL UNIQUE)')

table, th, td {
    border: 1px solid black;
    border-collapse: collapse;
}
th, td {
    padding: 15px;
    text-align: left;
}
<table>
<tr> 
<th>Configuration</th>
<th>Action</th>
<th>Time</th>    
<th>Notes</th>
</tr>
<tr><th>50,000 - with journal = delete and synchronous=1 and page_size=65535 cache_size = 8192</th><th>REMOVE UNIQUE FROM URL</th><th>0:00:18.011823</th><th>Size reduced to 196MB from 350MB</th><th></th></tr>
<tr><th>50,000 - with journal = delete and synchronous=1 and page_size=65535 cache_size = default</th><th>REMOVE UNIQUE FROM URL</th><th>0:00:25.692283</th><th>Size reduced to 196MB from 350MB</th><th></th></tr>
<tr><th>100,000 - with journal = delete and synchronous=1 and page_size=65535 </th><th></th><th>0:07:13.402985</th><th></th></tr>
<tr><th>100,000 - with journal = delete and synchronous=1 and page_size=65535 cache_size = 4096</th><th></th><th>0:04:47.624909</th><th></th></tr>
<tr><th>100,000 - with journal = delete and synchronous=1 and page_size=65535 cache_size = 8192</th><th></th><<th>0:03:32.473927</th><th></th></tr>
<tr><th>100,000 - with journal = delete and synchronous=1 and page_size=65535 cache_size = 8192</th><th>REMOVE UNIQUE FROM URL</th><th>0:00:17.927050</th><th>Size reduced to 196MB from 350MB</th><th></th></tr>
<tr><th>100,000 - with journal = delete and synchronous=1 and page_size=65535 cache_size = default   </th><th>REMOVE UNIQUE FROM URL</th><th>0:00:21.804679</th><th>Size reduced to 196MB from 350MB</th><th></th></tr>
<tr><th>100,000 - with journal = delete and synchronous=1 and page_size=65535 cache_size = default   </th><th>REMOVE UNIQUE FROM URL & ID</th><th>0:00:14.062386</th><th>Size reduced to 134MB from 350MB</th><th></th></tr>
<tr><th>100,000 - with journal = delete and synchronous=1 and page_size=65535 cache_size = default   </th><th>REMOVE UNIQUE FROM URL & DELETE ID</th><th>0:00:11.961004</th><th>Size reduced to 134MB from 350MB</th><th></th></tr>

</table>
Теги:
sqlite3

2 ответа

1

SQLite использует режим автоматической фиксации по умолчанию. Это позволяет опустить begin transaction. Но здесь мы хотим, чтобы все вставки были в транзакции, и единственный способ сделать это - начать транзакцию с begin transaction чтобы все операторы, которые будут запущены, все в этой транзакции.

Метод executemany - это всего лишь цикл, execute за пределами Python, который вызывает функцию SQLite для подготовки только один раз.

Ниже приведен неверный способ удалить последние N элементов из списка:

    templist = []
    i = 0
    while i < self.bulk_insert_entries and len(urls) > 0:
        templist.append(urls.pop())
        i += 1

Лучше это сделать:

   templist = urls[-self.bulk_insert_entries:]
   del urls[-self.bulk_insert_entries:]
   i = len(templist)

Слайс и дель-срез работают даже в пустом списке.

Оба могут иметь одинаковую сложность, но вызовы 100K для добавления и поп-трафика намного больше, чем позволить Python делать это за пределами интерпретатора.

  • 0
    К сожалению, не так много пользы от использования нового механизма списков и добавления транзакций. 1. Отсутствует механизм исходного списка транзакций = 0: 05: 13.974366 2. Отсутствует механизм среза / деления транзакции = 0: 05: 05.689453 3. Оператор вставки только в транзакции и механизм списка срезов / деления = 0: 07: 27.693308 4. Транзакция внешний цикл while и механизм списка слайсов / деления = 0: 05: 07.213928
0

Ограничение UNIQUE в столбце "url" создает неявный индекс в URL-адресе. Это объясняет увеличение размера.

Я не думаю, что вы можете заполнить таблицу, а затем добавить уникальное ограничение.

Ваше узкое место, безусловно, является центральным процессором. Попробуйте следующее:

  1. Установить toolz: pip install toolz
  2. Используйте этот метод:

    from toolz import partition_all
    
    def add_blacklist_url(self, urls):
        # print('add_blacklist_url:: entries = {}'.format(len(urls)))
        start_time = datetime.now()
        for batch in partition_all(100000, urls):
            try:
                start_commit = datetime.now()
                self.cursor.executemany('''INSERT OR IGNORE INTO blacklist(url) VALUES(:url)''', batch)
                end_commit = datetime.now() - start_commit
                print('add_blacklist_url:: total time for INSERT OR IGNORE INTO blacklist {} entries = {}'.format(len(templist), end_commit))
            except sqlite3.Error as e:
                print("add_blacklist_url:: Database error: %s" % e)
            except Exception as e:
                print("add_blacklist_url:: Exception in _query: %s" % e)
        self.db.commit()
        time_elapsed = datetime.now() - start_time
        print('add_blacklist_url:: total time for {} entries = {}'.format(records, time_elapsed))
    

Код не был протестирован.

  • 0
    Есть ли возможность обрезать индекс после того, как сделано? Во-вторых, я думаю, что узким местом, вероятно, является сама база данных, потому что я пытался закрыть все приложения и просто запустить скрипт, и в диспетчере задач я вижу скорость записи около 3-4 МБ / с. Но когда я просто читаю текстовые файлы, он показывает около 30-35 МБ / с.
  • 0
    Бутленек должен быть ресурсом вашего компьютера. Это может быть диск или процессор. Если вы не видите, что процессор использует одно ядро на 100%, это диск.
Показать ещё 10 комментариев

Ещё вопросы

Сообщество Overcoder
Наверх
Меню