SQLAlchemy: сканировать огромные таблицы с помощью ORM? - PullRequest
36 голосов
/ 18 июля 2009

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

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

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

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

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

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

Ответы [ 3 ]

51 голосов
/ 18 июля 2009

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

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

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

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

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

29 голосов
/ 02 августа 2009

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

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 просто выполняет простое сканирование всего вещь.

7 голосов
/ 18 июля 2009

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

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

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

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

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

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

Добро пожаловать на сайт PullRequest, где вы можете задавать вопросы и получать ответы от других членов сообщества.
...