Эффективная классификация БД журналов событий по столбцу - PullRequest
0 голосов
/ 04 марта 2019

Ситуация

Я использую Python 3.7.2 со встроенным модулем sqlite3.(sqlite3.version == 2.6.0)

У меня есть база данных sqlite, которая выглядит следующим образом:

| user_id | action | timestamp  |
| ------- | ------ | ---------- |
| Alice   |      0 | 1551683796 |
| Alice   |     23 | 1551683797 |
| James   |      1 | 1551683798 |
| ....... | ...... | .......... |

, где user_id равно TEXT, action является произвольным INTEGER, а timestamp - это INTEGER, представляющее время UNIX.

База данных имеет 200M строк, и существует 70K различных user_id с.

Цель

Мне нужно создать словарь Python, который будет выглядеть следующим образом:

{
    "Alice":[(0, 1551683796), (23, 1551683797)],
    "James":[(1, 1551683798)],
    ...
}

, который имеет user_id s в качестве ключей и соответствующие журналы событий в качестве значений, которые являются списками кортежей (action, timestamp).Надеемся, что каждый список будет отсортирован по timestamp в порядке возрастания, но даже если это не так, я думаю, что этого легко добиться, отсортировав каждый список после создания словаря.

Усилие

У меня есть следующий код для запроса базы данных.Сначала он запрашивает список пользователей (с user_list_cursor), а затем запрашивает все строки, принадлежащие пользователю.

import sqlite3
connection = sqlite3.connect("database.db")
user_list_cursor = connection.cursor()
user_list_cursor.execute("SELECT DISTINCT user_id FROM EVENT_LOG")
user_id = user_list_cursor.fetchone()

classified_log = {}
log_cursor = connection.cursor()
while user_id:
    user_id = user_id[0] # cursor.fetchone() returns a tuple
    query = (
        "SELECT action, timestamp"
        " FROM TABLE"
        " WHERE user_id = ?"
        " ORDER BY timestamp ASC"
    )
    parameters = (user_id,)
    local_cursor.execute(query, parameters) # Here is the bottleneck
    classified_log[user_id] = list()
    for row in local_cursor.fetchall():
        classified_log[user_id].append(row)
        user_id = user_list_cursor.fetchone()

Проблема

Выполнение запроса для каждого пользователя слишкоммедленный.Эта единственная строка кода (которая комментируется как бутылочное горлышко ) занимает около 10 секунд для каждого user_id.Я думаю, что я делаю неправильный подход с запросами.Как правильно достичь цели?

Я попытался выполнить поиск по ключевым словам «классифицировать БД по столбцу», «классифицировать БД по столбцу», «Журнал SQL в словарь Python», но, похоже, ничего не соответствуетмоя ситуация.Я думаю, что это не будет редкой необходимостью, поэтому, возможно, мне не хватает подходящего ключевого слова для поиска.

Воспроизводимость

Если кто-то желает воспроизвести ситуацию с 200M строкой sqliteбазы данных, следующий код создаст файл базы данных объемом 5 ГБ.

Но я надеюсь, что есть кто-то, кто знаком с такой ситуацией и знает, как написать правильный запрос.

import sqlite3
import random

connection = sqlite3.connect("tmp.db")
cursor = connection.cursor()
cursor.execute(
    "CREATE TABLE IF NOT EXISTS EVENT_LOG (user_id TEXT, action INTEGER, timestamp INTEGER)"
)
query = "INSERT INTO EVENT_LOG VALUES (?, ?, ?)"
parameters = []
for timestamp in range(200_000_000):
    user_id = f"user{random.randint(0, 70000)}"
    action = random.randint(0, 1_000_000)
    parameters.append((user_id, action, timestamp))
cursor.executemany(query, parameters)
connection.commit()
cursor.close()
connection.close()

1 Ответ

0 голосов
/ 06 марта 2019

Большое спасибо @Strawberry и @Solarflare за помощь, предоставленную в комментариях.

Следующее решение увеличило производительность более чем в 70 раз, поэтому я оставляю то, что сделал, в качестве ответа для полноты картины.

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

import sqlite3
from operators import attrgetter

connection = sqlite3.connect("database.db")

# Creating index, thanks to @Solarflare
cursor = connection.cursor()
cursor.execute("CREATE INDEX IF NOT EXISTS idx_user_id ON EVENT_LOG (user_id)")
cursor.commit()

# Reading the whole table, then make lists by user_id. Thanks to @Strawberry
cursor.execute("SELECT user_id, action, timestamp FROM EVENT_LOG ORDER BY user_id ASC")
previous_user_id = None
log_per_user = list()
classified_log = dict()
for row in cursor:
    user_id, action, timestamp = row
    if user_id != previous_user_id:
        if previous_user_id:
            log_per_user.sort(key=itemgetter(1))
            classified_log[previous_user_id] = log_per_user[:]
        log_per_user = list()
    log_per_user.append((action, timestamp))
    previous_user_id = user_id

Так что точки

  • Индексация по user_id для создания ORDER BY user_id ASC выполнить в приемлемое время.
  • Считать всю таблицу, затем классифицировать по user_id, вместо того, чтобы делать отдельные запросы для каждого user_id.
  • Итерировать по cursor для чтения строкипо строке вместо cursor.fetchall().
Добро пожаловать на сайт PullRequest, где вы можете задавать вопросы и получать ответы от других членов сообщества.
...