Уведомления

Группа в Telegram: @pythonsu

#1 Июнь 16, 2009 07:20:44

hellslade
От:
Зарегистрирован: 2008-01-28
Сообщения: 240
Репутация: +  0  -
Профиль   Отправить e-mail  

sqlalchemy и скорость

Всем привет. Прощу помощи гуру алхимии. Есть вот такое описание структуры БД:

appointment_table = Table('appointment', metadata,
Column('user_id',Integer,ForeignKey("user.id")),
Column('job_id',Integer,ForeignKey("job.id")),
Column('who_raised',Integer,ForeignKey('user.id')),
autoload=True
)
user_table=Table('user',metadata,autoload=True)

who_raised_table = alias(user_table,'who_raised')

job_table= Table('job', metadata,
Column('date0', DateTime, default=lambda:datetime.date.today()),
Column('date70', DateTime, default=lambda:datetime.date.today()),
Column('date100', DateTime, default=lambda:datetime.date.today()),
Column('done100',Integer, default=0),
Column('flags', Integer,default=0),
Column('hours', Integer,default=0),
Column('tarif', Integer,default='T'),
Column('fine',Integer, default=0),
Column('num',Integer, default=0),
Column('description',String,default=''),
Column('parent_id',Integer, ForeignKey('job.id'),nullable=True,default=0),
autoload=True)
score_table=Table('score', metadata,
Column('job_id',Integer,ForeignKey("job.id")),
Column('user_id',Integer,ForeignKey("user.id")),
autoload=True)

mapper(Appointment,appointment_table,properties=dict(
job_table = relation(Job,lazy=True),
to_user = relation(mapper(User,user_table),primaryjoin = appointment_table.c.user_id == user_table.c.id,lazy=True),
from_user = relation(mapper(User,who_raised_table, non_primary=True),primaryjoin = appointment_table.c.who_raised== who_raised_table.c.id,lazy=True),
))
mapper(Job,job_table,properties=
{
'appointment':relation(Appointment,lazy=True,uselist=False),
'score':relation(Score,lazy=True),
'child':relation(Job, remote_side=job_table.c.parent_id, primaryjoin=job_table.c.parent_id==job_table.c.id, backref=backref('parent', remote_side=job_table.c.id),lazy=True),
})
mapper(Score,score_table,properties={'job':relation(Job,lazy=True),'user':relation(User,lazy=True)})
Классы:
JOB_STOPPED = 1
JOB_DONE = 2
JOB_DUENOW = 4
JOB_LATER = 8
JOB_DUE = 16
class Job(object):
def get_status(self):
''' job.status = вычисляемое информативное поле - получение статуса задания. В зависимости от текущей даты,флажка
готовности, срока окончания, флага со штрафами
'''
status = 'Не определен'
if self.done100 == 1 :
status = 'Выполнено'
elif self.date100 > cur_date():
status = 'В работе'
elif self.date100 < cur_date():
status = 'Просрочено'
elif self.date100 == cur_date():
status = 'Срок выполнения: сегодня'
if self.flags == 1 and self.done100 != 1:
status = status + '-остановлено'
return status
def get_flag(self):
''' битовое значение различных признаков задания.
Служит для одновременного хранения разных статусов которые существуют у задания.
'''
flag = 0
if self.flags == 1 :
flag = flag | JOB_STOPPED
if self.done100 == 1 :
flag = flag | JOB_DONE
if self.date100 == cur_date():
flag = flag | JOB_DUENOW
if self.date100 < cur_date():
flag = flag | JOB_LATER
if self.date100 > cur_date():
flag = flag | JOB_DUE
return flag
def _get_fines(self):
""" возвращает штрафы по текущему заданию
"""
from dbsup.tables import getsession
sql = """
select SUM(score.score) from score, job
where
score.job_id = job.id AND
job.id = %s
""" % self.id
session = getsession()
res =session.execute(sql).fetchone()
return res[0] or 0

status = property(lambda self: self.get_status(),None)
flag = property(lambda self: self.get_flag(),None)
fines = property(lambda self: self._get_fines(),None)
class Score(object):
pass
class Appointment(object):
pass
class User(object):
pass
Есть такой обработчик.
def Populate(self):
""" Заполнение словаря данными из БД
"""
session = getsession()
# Получение списка всех невыполненных задач
jobs = session.query(Job).filter(
and_(
Appointment.job_id == Job.id,
Appointment.user_id == user_table.c.id,
Appointment.who_raised == who_raised_table.c.id,
Job.done100 == 0,
)
).group_by(Job.id).order_by(Job.date100)
job_view = []
for job in jobs:
job_view.append(
[
[job.id, job.appointment.to_user.id], # id задания и постановщика
str(job.id), # номер задания
job.status, # статус выполнения
str(job.description[:100]), # Содержание задания
str(job.appointment.from_user.fio), # Постановщик задания
str(job.appointment.to_user.fio), # Исполнитель задания
job.date0, # Дата получения задания
job.date100, # Дата выполнения
job.fines, # Количество штрафов
str(job.hours), # Часы
str(job.tarif), # Тариф
1 if datetime.datetime.now().date() > job.date70 else 0, # Просрочен ли срок 70%
1 if datetime.datetime.now().date() > job.date100 else 0, # Просрочен ли срок 100%
str(job.description), # Полное описание работы
job.done70, # Первый срок закрыт? (70%)
job.flags & 1, # Флаги по заданию
'' # @ если есть присоединенные файлы
]
)
self.items = job_view
session.close()
Проблема в том, что работает это все медленно. Медленно работает цикл, в котором формируется list с параметрами работы. Почему работает медленно, в принципе понятно: из таблицы job идет обращение к таблице appointment, а оттуда еще и к таблице user. Таких цепочек обращений целых три в одной итерации. На малом массиве данных оно и незаметно, но! селект в БД, с недавнего времени, стал возвращать около 1000 записей, что существенно сказывается на скорости. Да еще и БД на сервере и пользователей порядка 80 :)
Вот как бы ускорить этот процесс? Думается мне, что надо что-то прописать в mapper, вот только что – непонятно.
Буду очень признателен за помощь.
Python 2.6.2
sqlalchemy.__version__ ‘0.5.4p2’



Офлайн

#2 Июнь 16, 2009 08:12:06

poltergeist
От:
Зарегистрирован: 2007-02-28
Сообщения: 522
Репутация: +  0  -
Профиль   Отправить e-mail  

sqlalchemy и скорость

Сразу скажу - с алхимией не сильно хорошо знаком, но у неё есть такая фича - identity map. Т.е. если вы перед циклом сделаете запрос в БД, который заставит алхимию подгрузить все нужные обекты из таблицы Appointment, то уже в цикле она не будет обращаться к БД за ними, а будет использовать уже загруженные данные. Это в рамках вашей схемы, в которой указано, что “”"'appointment':relation(Appointment,lazy=True,uselist=False).“”“, поэтому и даётся запрос на каждое первое обращение к этому полю. С User-ами от Appointment в том же духе - сделать перед циклом соответствующий запрос. Другой вариант - это менять схему, а именно убрать ”lazy=True", но не до конца уверен что алхимия будет правильно сама делать дополнительные запросы, да и эти изменения повлияют на всё приложение.



Офлайн

#3 Июнь 16, 2009 08:24:53

bw
От:
Зарегистрирован: 2007-09-26
Сообщения: 938
Репутация: +  20  -
Профиль   Адрес электронной почты  

sqlalchemy и скорость

Попробуй добавить в запрос ".options(eagerload('appointment'))" или убери соотв. lazy из mapperJob в определении свойства appointment. Так же можно поступить для остального. Либо в самом запросе указать на то, что зависимости должны подгружаться предварительно, либо еще при описании mapper'а, это от задачи зависит.
А так, ничего криминального не вижу. Может сама схема не оптимальна, хотя при таком кол. данных, конечно же, индексы не помогут. Вообщем я сейчас плохо соображаю, потом еще раз гляну :-).

p.s. Посчитай вообще про options и eagerload, так же есть еще eagerload_all, их описание находится в описании маппера в документации.

..bw



Отредактировано (Июнь 16, 2009 08:27:24)

Офлайн

#4 Июнь 16, 2009 10:45:47

hellslade
От:
Зарегистрирован: 2008-01-28
Сообщения: 240
Репутация: +  0  -
Профиль   Отправить e-mail  

sqlalchemy и скорость

lazy=False сделать не получится. придется весь код программы перелопатить, а этого очень не хочется :)
попробовал eagerload_all (подгрузил еще таблицу user). Результат меня просто потряс :) скорость обработки увеличилась примерно на 80%
Но все равно, 3 секунды для 800 с небольшим записей….мне кажется это много :) еще почитаю про mapper. Всем спасибо большое, очень помогли, а главное оперативно :) А то начальство уже кипятком начинало писать :D



Офлайн

Board footer

Модераторировать

Powered by DjangoBB

Lo-Fi Version