2009-07-17 2 views
33

Я сейчас немного играю с SQLAlchemy, что на самом деле довольно аккуратно.SQLAlchemy: Сканировать огромные таблицы с помощью ORM?

Для тестирования я создал огромную таблицу, содержащую мой архив фотографий, индексированный хэшами SHA1 (для удаления дубликатов :-)). Который был impressingly быстро ...

Для удовольствия я сделал эквивалент select * над полученной SQLite базой данных:

session = Session() 
for p in session.query(Picture): 
    print(p) 

Я ожидал увидеть хэш прокрутки, но вместо этого он просто продолжал сканирование диска. В то же время использование памяти резко увеличилось, достигнув 1 ГБ через несколько секунд. Это, похоже, исходит из функции идентификационной карты SQLAlchemy, которая, как я думала, только сохраняла слабые ссылки.

Может кто-нибудь объяснить это мне? Я думал, что каждый Picture p будет собран после того, как будет выписан хэш !?

+0

Заметил эту же проблему сам. Если я делаю 'len (Session.query (Model) .limit (100) .all())', я получаю '1'. Если я удалю «limit», используйте skyrockets для использования памяти. –

ответ

45

Хорошо, я просто нашел способ сделать это сам. Изменение кода на

session = Session() 
for p in session.query(Picture).yield_per(5): 
    print(p) 

загружает всего 5 снимков за раз. Кажется, что запрос будет загружать все строки за раз по умолчанию. Тем не менее, я пока не понимаю отказ от этого метода. Цитата из

ВНИМАНИЕ: используйте этот метод с осторожностью; если один и тот же экземпляр присутствует в более чем одной партии строк, изменения в атрибутах конечного пользователя будут перезаписаны. В частности, это правило невозможно использовать с загруженными коллекциями (то есть любым lazy = False), так как эти коллекции будут очищены для новой загрузки, если они встречаются в последующей партии результатов.

Так при использовании yield_per на самом деле правильный путь (тм) для сканирования по обильным данным SQL при использовании ОРМА, когда это безопасно использовать?

+4

yield_per безопасен, если у вас есть одна строка SQL результата в каждом результирующем экземпляре. Вы получаете дополнительные строки для каждого экземпляра, когда вы загружаете или присоединяете отношение «один ко многим». Если вам нужна дополнительная информация об обстоятельствах, вы можете создать отдельный вопрос о yield_per. –

+0

Что-то примечание: Это вызывает исключение, если вы пытаетесь выполнить фиксацию при этом. См. Http://stackoverflow.com/questions/12233115/sqlalchemy-cursor-error-during-yield-per –

+2

После долгой битвы с утечками памяти в MySQL я увидел это в документах yield_per: 'Также обратите внимание, что while yield_per() установит параметр исполнения stream_results в True, в настоящее время это понимается только на диалекте psycopg2, который будет передавать результаты с использованием курсоров на стороне сервера вместо из предварительно буфера всех строк для этого запроса. Другие DBAPI предварительно загружают все строки, прежде чем сделать их доступными. «Обход проблемы здесь: http://stackoverflow.com/a/3699677/281469 – bcoughlan

6

Вы можете отложить съемку только до получения доступа. Вы можете сделать это по запросу по запросу. как

session = Session() 
for p in session.query(Picture).options(sqlalchemy.orm.defer("picture")): 
    print(p) 

или вы можете сделать это в картографа

mapper(Picture, pictures, properties={ 
    'picture': deferred(pictures.c.picture) 
}) 

Как сделать это в документации here

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

+0

Спасибо Дэвиду, это довольно интересно. На самом деле я просто изменил мой код, чтобы использовать от импорта sqlalchemy.orm отсроченного ... класс Picture (объект): ... ImageData = отложено (колонка (Binary)) Какой большой шаг вперед. Тем не менее, это все равно занимает несколько секунд, пока первый результат не будет выводиться, поскольку для запроса создано несколько объектов 1000 изображений. Я хотел бы иметь эти объекты, созданные по одному, в то время как SQLAlchemy выполняет итерацию по строкам результатов SQLite. – Bluehorn

+0

Я думаю, что большую часть времени будет потрачено на извлечение данных из базы данных и не создание объектов. Таким образом, запрос должен иметь ограничения. Так. для p в session.query (Изображение) [0: 5]: напечатать p. –

26

вот что я обычно делаю в этой ситуации:

def page_query(q): 
    offset = 0 
    while True: 
     r = False 
     for elem in q.limit(1000).offset(offset): 
      r = True 
      yield elem 
     offset += 1000 
     if not r: 
      break 

for item in page_query(Session.query(Picture)): 
    print item 

Это позволяет избежать различной буферизации, что DBAPIs сделать так (например, psycopg2 и MySQLdb). Его по-прежнему необходимо использовать надлежащим образом, если в вашем запросе есть явные JOINs, хотя надежно загруженные коллекции гарантированно загружаются полностью, так как они применяются к подзапросу, который содержит фактический LIMIT/OFFSET.

Я заметил, что Postgresql занимает почти столько же времени, чтобы возвращать последние 100 строк большого результирующего набора, как и для возврата всего результата (за вычетом фактических служебных издержек строки), поскольку OFFSET просто выполняет простое сканирование Все это.

+0

Я думаю, что вы можете избавиться от одного задания в цикле, например: , в то время как True: elem = None; для elem .... offset + = 1000; если elem == None: break; offset + = 1000 – Dave

+0

Нет, Дэйв, вам нужно установить 'elem = None' в конце цикла или он никогда не завершится. –

+4

Обратите внимание, что нарезка запроса даст вам тот же эффект: 'q [offset: offset + 1000]'. –

Смежные вопросы