Это утечка памяти (программа на python с sqlalchemy / sqlite) - PullRequest
4 голосов
/ 27 января 2010

У меня следующий код работает с большим набором данных (2M). Он съедает всю мою память 4G перед тем, как закончить.

    for sample in session.query(CodeSample).yield_per(100):
            for proj in projects:
                    if sample.filename.startswith(proj.abs_source):
                            sample.filename = "some other path"
                            session.add(sample)

Затем я запустил его, хотя сокращенный набор данных, и проанализировал кучу с кучей. get_rp () дал мне следующую подсказку

0: _ --- [-] 47821 (0x9163aec | 0x9165fec | 0x916d6cc | 0x9251414 | 0x925704...
 1: a      [-] 8244 tuple: 0x903ec8c*37, 0x903fcfc*13, 0x9052ecc*46...
 2: aa ---- [S] 3446 types.CodeType: parseresult.py:73:src_path...
 3: ab      [S] 364 type: __builtin__.Struct, _random.Random, sqlite3.Cache...
 4: ac ---- [-] 90 sqlalchemy.sql.visitors.VisitableType: 0x9162f2c...
 5: aca      [S] 11 dict of module: ..sql..., codemodel, sqlalchemy
 6: acb ---- [-] 48 sqlalchemy.sql.visitors.VisitableType: 0x9162f2c...
 7: acba      [S] 9 dict of module: ..sql..., codemodel, sqlalchemy
 8: acbb ---- [-] 45 sqlalchemy.sql.visitors.VisitableType: 0x9165fec...
 9: acbba      [S] 8 dict of module: ..sql..., codemodel, sqlalchemy

Я новичок в sqlalchemy. Это утечка памяти? Спасибо.

Ответы [ 2 ]

6 голосов
/ 28 января 2010

Большинство DBAPI, включая psycopg2 и mysql-python, полностью загружают все результаты в память перед тем, как отправить их клиенту. Параметр yield_per () в SQLA не решает эту проблему, за одним исключением ниже, поэтому обычно он не очень полезен (edit: полезен в том смысле, что он начинает потоковую передачу результатов до того, как фактические строки будут полностью извлечены).

Исключениями из этого поведения являются:

  1. Использование DBAPI, который не буферизует строки. cx_oracle - один из них, в результате естественного способа работы OCI. Не уверен в поведении pg8000, и есть также новый DBAPI MySQL под названием OurSQL, который, как мне сказали его создатель, не буферизует строки. pg8000 и OurSQL поддерживаются SQLAlchemy 0.6.
  2. С psycopg2 можно использовать «курсор на стороне сервера». SQLAlchemy поддерживает флаг create_engine () "server_side_cursors = True", который использует серверные курсоры для всех операций выбора строк. Однако, поскольку серверные курсоры, как правило, дороги и, следовательно, снижают производительность для небольших запросов, SQLAlchemy 0.6 теперь поддерживает курсор на стороне сервера psycopg2 для каждого оператора или для каждого запроса, используя .execution_options (stream_results = True), где выполнение_опции доступно для Запрос, выберите (), текст () и соединение. Объект Query вызывает эту опцию, когда yield_per () используется, поэтому в 0.6 yield_per () в сочетании с psycopg2 действительно полезен.
2 голосов
/ 27 января 2010

Сессия будет отслеживать все CodeSample объекты, которые вы извлекаете. Таким образом, после итерации по 2М объектам сеанс сохраняет ссылку на все из них. Для сеанса нужны эти ссылки, чтобы он мог записать правильные изменения в базу данных на flush. Поэтому я верю, что то, что вы видите, следует ожидать.

Чтобы одновременно хранить в памяти только N объектов, вы можете сделать что-то вроде приведенного ниже кода (вдохновленный этим ответом , заявление об отказе от ответственности: я не проверял это).

offset = 0
N = 10000
got_rows = True
while got_rows:
    got_rows = False
    for sample in session.query(CodeSample).limit(N).offset(offset):
        got_rows = True
        for proj in projects:
            if sample.filename.startswith(proj.abs_source):
                sample.filename = "some other path"
    offset += N
    session.flush()       # writes changes to DB
    session.expunge_all() # removes objects from session

Но вышесказанное немного неуклюже, возможно, некоторые гуру SQLAlchemy знает, как лучше это сделать.

Кстати, вам не нужен session.add (), сессия отслеживает изменения объектов. Почему вы используете yield_per ( РЕДАКТИРОВАТЬ: Я полагаю, это для получения строк в кусках из БД, это правильно? Сеанс все равно будет отслеживать их все.)

EDIT:

Хм, похоже, что-то я неправильно понял. От Документы :

weak_identity_map: При значении по умолчанию True используется карта со слабой ссылкой; случаи, которые не являются внешне ссылка будет сборка мусора немедленно. Для разыменованных экземпляры с ожидающими изменениями настоящее, атрибут управления Система создаст временную сильная ссылка на объект, который длится, пока изменения не сбрасываются в база данных, в этот момент это снова разыменованный. С другой стороны, при использовании значения False, Карта идентичности использует обычный Python словарь для хранения экземпляров. сеанс будет поддерживать все экземпляры присутствовать, пока они не будут удалены с помощью expunge (), clear () или purge ().

и

prune (): удаляет ссылки на экземпляры, кэшированные в карте идентификации.

Обратите внимание, что этот метод имеет смысл только в том случае, если для «weak_identity_map» задано значение False. Слабая карта идентичности по умолчанию является самонастраивающейся.

Удаляет любой объект в карте идентификации этого сеанса, на который нет ссылок в коде пользователя, который был изменен, добавлен или запланирован для удаления. Возвращает количество обрезанных объектов.

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