Подтвердить что ты не робот

SQLAlchemy: сканировать огромные таблицы с помощью ORM?

В настоящее время я немного разбираюсь в SQLAlchemy, что действительно довольно аккуратно.

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

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

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

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

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

4b9b3361

Ответ 1

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

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

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

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

Итак, если использовать yield_per на самом деле правильный путь (tm) для сканирования больших объемов данных SQL при использовании ORM, когда это безопасно использовать?

Ответ 2

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

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

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

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

Ответ 3

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

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

или вы можете сделать это в mapper

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

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

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