SQLAlchemy: сканировать огромные таблицы с помощью ORM?
В настоящее время я немного разбираюсь в SQLAlchemy, что действительно довольно аккуратно.
Для тестирования я создал огромную таблицу, содержащую архив моих фотографий, проиндексированный хешами SHA1 (чтобы удалить дубликаты:-)). Что было впечатляюще быстро...
Для удовольствия я сделал эквивалент select *
по полученной базе данных SQLite:
session = Session()
for p in session.query(Picture):
print(p)
Я ожидал увидеть прокрутку хэшей, но вместо этого он просто продолжал сканировать диск. В то же время использование памяти резко увеличилось, достигнув 1 ГБ через несколько секунд. Это, похоже, исходит из функции идентификационной карты SQLAlchemy, которая, как я думал, только сохраняла слабые ссылки.
Может кто-нибудь объяснить это мне? Я думал, что каждое изображение p будет собрано после того, как будет выписан хэш!?
Ответы
Ответ 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)
})
Как вы это делаете в документации здесь
Выполняя это в любом случае, убедитесь, что изображение загружается только при доступе к атрибуту.