Psycopg2 использует память для большого запроса select

Я использую psycopg2 для запроса базы данных Postgresql и пытается обработать все строки из таблицы с примерно 380M строк. Есть только 3 столбца (id1, id2, count) всего типа integer. Однако, когда я запускаю простой запрос выбора ниже, процесс Python начинает потреблять все больше и больше памяти, пока он не будет убит ОС.

Минимальный рабочий пример (предполагается, что mydatabase существует и содержит таблицу под названием mytable):

import psycopg2
conn = psycopg2.connect("dbname=mydatabase")
cur = conn.cursor()
cur.execute("SELECT * FROM mytable;")

В этот момент программа начинает потреблять память.

Я посмотрел, и процесс Postgresql ведет себя хорошо. Он использует справедливый бит процессора, который хорош и очень ограниченный объем памяти.

Я ожидал, что psycopg2 вернет итератор, не пытаясь выгрузить все результаты из выбора. Затем я мог бы использовать cur.fetchone() для обработки всех строк.

Итак, как я могу выбрать из таблицы строк 380M без использования доступной памяти?

Ответ 1

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

cur = conn.cursor('cursor-name') # server side cursor
cur.itersize = 10000 # how much records to buffer on a client
cur.execute("SELECT * FROM mytable;")

Ответ 2

Другой способ использования курсоров на стороне сервера:

with psycopg2.connect(database_connection_string) as conn:
    with conn.cursor(name='name_of_cursor') as cursor:

        cursor.itersize = 20000

        query = "SELECT * FROM ..."
        cursor.execute(query)

        for row in cursor:
            # process row 

Psycopg2 будет извлекать строки itersize клиенту за раз. Как только цикл for исчерпает эту партию, он выберет следующий.

Ответ 3

Вы можете использовать опцию OFFSET в Postgresql с настраиваемым LIMIT для получения небольшого количества наборов данных. Более подробная информация в разделе: https://www.postgresql.org/docs/8.1/queries-limit.html

Например:

offset = 0
while True:
      data = cursor.execute("SELECT * FROM mytable LIMIT 100 OFFSET {}".format(offset))
      if not data:
         break

      for row in data:
          # process data
      offset += 100