Как я могу оптимизировать несколько вложенных SELECT в SQLite (с Python)? - PullRequest
5 голосов
/ 05 февраля 2010

Я создаю сценарий CGI, который опрашивает базу данных SQLite и создает таблицу статистики. Таблица исходной базы данных описана ниже, как и часть соответствующего кода. Все работает (функционально), но сам CGI работает очень медленно, так как у меня несколько вложенных вызовов SELECT COUNT(id). Я полагаю, что мой лучший шанс на оптимизацию - спросить SO-сообщество, поскольку мое время с Google было относительно бесплодным.

Таблица:

CREATE TABLE messages (
    id TEXT PRIMARY KEY ON CONFLICT REPLACE,
    date TEXT,
    hour INTEGER,
    sender TEXT,
    size INTEGER,
    origin TEXT,
    destination TEXT,
    relay TEXT,
    day TEXT);

(Да, я знаю, что таблица не нормализована, но она заполнена выдержками из почтового журнала ... Я был достаточно счастлив, чтобы получить извлечение и заполнение, не говоря уже о нормализации. Я не думаю, что таблица На данный момент структура во многом связана с моим вопросом, но я могу ошибаться.)

Пример строки:

476793200A7|Jan 29 06:04:47|6|admin@mydomain.com|4656|web02.mydomain.pvt|user@example.com|mail01.mydomain.pvt|Jan 29

И код Python, который создает мои таблицы:

#!/usr/bin/python
print 'Content-type: text/html\n\n'

from datetime import date

import re
p = re.compile('(\w+) (\d+)')

d_month = {'Jan':1,'Feb':2,'Mar':3,'Apr':4,'May':5,'Jun':6,'Jul':7,'Aug':8,'Sep':9,'Oct':10,'Nov':11,'Dec':12}
l_wkday = ['Mo','Tu','We','Th','Fr','Sa','Su']

days = []
curs.execute('SELECT DISTINCT(day) FROM messages ORDER BY day')
for day in curs.fetchall():
    m = p.match(day[0]).group(1)
    m = d_month[m]
    d = p.match(day[0]).group(2)
    days.append([day[0],"%s (%s)" % (day[0],l_wkday[date.weekday(date(2010,int(m),int(d)))])])

curs.execute('SELECT DISTINCT(sender) FROM messages')
senders = curs.fetchall()
for sender in senders:
    curs.execute('SELECT COUNT(id) FROM messages WHERE sender=%s',(sender[0]))
    print '  <div id="'+sender[0]+'">'
    print '   <h1>Stats for Sender: '+sender[0]+'</h1>'
    print '   <table><caption>Total messages in database: %d</caption>' % curs.fetchone()[0]
    print '    <tr><td>&nbsp;</td><th colspan=24>Hour of Day</th></tr>'
    print '    <tr><td class="left">Day</td><th>%s</th></tr>' % '</th><th>'.join(map(str,range(24)))
    for day in days:
            print '    <tr><td>%s</td>' % day[1]
            for hour in range(24):
                    sql = 'SELECT COUNT(id) FROM messages WHERE sender="%s" AND day="%s" AND hour="%s"' % (sender[0],day[0],str(hour))
                    curs.execute(sql)
                    d = curs.fetchone()[0]
                    print '    <td>%s</td>' % (d>0 and str(d) or '')
            print '    </tr>'
    print '   </table></div>'

print ' </body>\n</html>\n'

Я не уверен, есть ли какие-либо способы, которыми я могу объединить некоторые запросы или подойти к нему под другим углом, чтобы извлечь данные. Я также думал о создании второй таблицы с подсчетами в ней и просто обновлении ее при обновлении исходной таблицы. Я слишком долго смотрел на это сегодня, поэтому завтра я снова буду атаковать его снова, надеюсь, с некоторой проницательностью от экспертов;)


Редактировать: Используя ответ GROUP BY, представленный ниже, я смог получить необходимые данные из базы данных в одном запросе. Я переключился на Perl, поскольку поддержка Python для вложенных dict не очень хорошо работала для того, как мне нужно было подходить к этому (создание набора таблиц HTML особым образом). Вот фрагмент исправленного кода:

my %data;
my $rows = $db->selectall_arrayref("SELECT COUNT(id),sender,day,hour FROM messages GROUP BY sender,day,hour ORDER BY sender,day,hour");
for my $row (@$rows) {
    my ($ct, $se, $dy, $hr) = @$row;
    $data{$se}{$dy}{$hr} = $ct;
}
for my $se (keys %data) {
    print "Sender: $se\n";
    for my $dy (keys %{$data{$se}}) {
    print "Day: ",time2str('%a',str2time("$dy 2010"))," $dy\n";
        for my $hr (keys %{$data{$se}{$dy}}) {
            print "Hour: $hr = ".$data{$se}{$dy}{$hr}."\n";
        }
    }
    print "\n";
}

То, что когда-то выполнялось за 28.024 с, теперь занимает 0.415 с!

Ответы [ 2 ]

3 голосов
/ 05 февраля 2010

прежде всего вы можете использовать группу по выражению:

select count(*), sender from messages group by sender;

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

select count(*), sender, day, hour
    from messages group by sender, day, hour
    order by sender, day, hour;

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

секунда, создайте индексы на основе столбцов поиска, в вашем случае отправитель, день и час.

если этого недостаточно, используйте инструменты профилирования, чтобы найти, где больше всего времени тратится. Вы также должны рассмотреть возможность использования fetchmany вместо fetchall, чтобы сохранить низкое потребление памяти. помните, что поскольку модуль sqlite написан на языке C, используйте его как можно чаще.

1 голос
/ 05 февраля 2010

Для начала создайте индекс:

CREATE INDEX messages_sender_by_day ON сообщения (отправитель, день);

(Вам, вероятно, не нужно указывать там "час".)

Если это не помогает или вы уже попробовали это, то, пожалуйста, немного исправьте свой вопрос: дайте нам код для генерации тестовых данных и SQL для всех индексов в таблице.

Поддержание кэша подсчета довольно распространено, но я не могу сказать, нужно ли это здесь.

...