Python с postgres с использованием именованных переменных и массовых вставок - PullRequest
1 голос
/ 18 октября 2011

Мне нужна помощь в понимании того, как Python и postgres обрабатывают транзакции и массовые вставки, особенно при вставке нескольких наборов данных в одну транзакцию.Окружение:

  • Windows 7 64-битная
  • Python 3.2
  • Postgresql 9.1
  • psycopg2

Вот мой сценарийЯ преобразовываю данные из одной базы данных (оракула) в строки XML и вставляю эти данные в новую базу данных (postgres).Это большой набор данных, поэтому я пытаюсь оптимизировать некоторые из моих вставок.Многие из этих данных я рассматриваю объекты библиотечного типа, поэтому у меня есть таблица библиотеки, а затем таблицы для моих метаданных xml и содержимого xml, поля для этих данных - это текстовые типы в базе данных.Я вытаскиваю данные из оракула, а затем создаю словари данных, которые мне нужно вставить.У меня есть 3 оператора вставки, первая вставка создает запись в таблице библиотеки с использованием серийного идентификатора, и этот идентификатор необходим для связи в следующих двух запросах, которые вставляют XML в таблицы метаданных и содержимого.Вот пример того, о чем я говорю:

for inputKey in libDataDict.keys():
  metaString = libDataDict[inputKey][0]
  contentString = libDataDict[inputKey][1]
  insertLibDataList.append({'objIdent':"%s" % inputKey, 'objName':"%s" % inputKey, objType':libType})
  insertMetadataDataList.append({'objIdent':inputKey,'objMetadata':metaString}) 
  insertContentDataList.append({'objIdent':inputKey, 'objContent':contentString})

dataDict['cmsLibInsert'] = insertLibDataList
dataDict['cmsLibMetadataInsert'] = insertMetadataDataList
dataDict['cmsLibContentInsert'] = insertContentDataList

sqlDict[0] = {'sqlString':"insert into cms_libraries (cms_library_ident, cms_library_name, cms_library_type_id, cms_library_status_id) \
              values (%(objIdent)s, %(objName)s, (select id from cms_library_types where cms_library_type_name = %(objType)s), \
              (select id from cms_library_status where cms_library_status_name = 'active'))", 'data':dataDict['cmsLibInsert']}

sqlDict[1] = {'sqlString':"insert into cms_library_metadata (cms_library_id, cms_library_metadata_data) values \
              ((select id from cms_libraries where cms_library_ident = %(objIdent)s), $$%(objMetadata)s$$)", \
              'data':dataDict['cmsLibMetadataInsert']}

sqlDict[2] = {'sqlString':"insert into cms_library_content (cms_library_id, cms_library_content_data) values \
              ((select id from cms_libraries where cms_library_ident = %(objIdent)s), $$%(objContent)s$$)", \
              'data':dataDict['cmsLibContentInsert']}

bulkLoadData(myConfig['pgConn'], myConfig['pgCursor'], sqlDict)

Проблема, с которой я сталкиваюсь, - это когда я запускаю первый запрос (sqlDict [0]) и выполняю вставку, все работает нормально, пока яон отделяется и фиксируется, прежде чем я запусту следующие два.В идеале я хотел бы, чтобы все эти запросы выполнялись в одной транзакции, но это не удалось, поскольку он не может найти идентификатор из таблицы cms_libraries для 2-го и 3-го запросов.Вот мой текущий код вставки:

def bulkLoadData(dbConn, dbCursor, sqlDict):
 try:
   libInsertSql = sqlDict.pop(0)
   dbSql = libInsertSql['sqlString']
   data = libInsertSql['data']
   dbCursor.executemany(dbSql, data)
   dbConn.commit()
   for sqlKey in sqlDict:
     dbSql = sqlDict[sqlKey]['sqlString']
     data = sqlDict[sqlKey]['data']
     dbCursor.executemany(dbSql, data)

   dbConn.commit()

Ранее я добавлял значения в запрос, а затем выполнял запрос для каждой вставки.Когда я делаю это, я могу поместить все это в одну транзакцию, и он находит сгенерированный идентификатор, и все в порядке.Я не понимаю, почему он не находит идентификатор, когда я делаю массовую вставку с executemany ()?Есть ли способ выполнить массовую вставку и два других запроса в одной и той же транзакции?

Я читал эту документацию и искал stackoverflow и Интернет, но не нашел ответа на мою проблему: pyscopg docs , а также postgres: Postgresql string docs

Любая помощь, предложения или комментарии приветствуются.Спасибо, Митч

1 Ответ

0 голосов
/ 22 февраля 2012

У вас есть два варианта здесь.Либо сгенерируйте идентификаторы извне (что позволяет выполнять массовые вставки), либо сгенерируйте их из серийного номера (что означает, что вы должны выполнять вставки с одной записью).Я думаю, что довольно просто вычислить генерацию внешнего идентификатора и массовую загрузку (хотя я рекомендую вам взглянуть на инструмент ETL , а не на ручное кодирование чего-либо на python).Если вам нужно извлечь идентификаторы из серийного номера, вам следует рассмотреть подготовленные операторы на стороне сервера .

Ваше первое утверждение должно выглядеть следующим образом:

dbCursor.execute("""
PREPARE cms_lib_insert (bigint, text, text) AS 
INSERT INTO cms_libraries (cms_library_ident, cms_library_name, cms_library_type_id, cms_library_status_id)
VALUES ($1, $2,
    (select id from cms_library_types where cms_library_type_name = $3), 
    (select id from cms_library_status where cms_library_status_name = 'active')
)
RETURNING cms_library.id
""")

Вы запустите это один раз, во время запуска.Затем вы захотите выполнить следующий оператор EXECUTE на уровне входа.

dbCursor.execute("""
EXECUTE cms_lib_insert(%(objIndent)s, %(objName)s, %(objType)s)
""", {'objIndent': 345, 'objName': 'foo', 'objType': 'bar'))
my_new_id = dbCursor.fetchone()[0]

Это вернет сгенерированный серийный идентификатор.В дальнейшем я настоятельно рекомендую вам отказаться от шаблона, который вы используете в настоящее время, пытаясь абстрагировать обмен данными с базой данных (ваш подход sqlDict), и идти с очень прямым шаблоном кодирования (умный - ваш враг здесь, это повышает производительность).настройка сложнее).

Вы захотите объединить ваши вставки в размер блока, который работает для производительности.Это означает настройку вашего BLOCK_SIZE в зависимости от вашего реального поведения.Ваш код должен выглядеть примерно так:

BLOCK_SIZE = 500
while not_done:
   dbCursor.begin()
   for junk in irange(BLOCK_SIZE):
       dbCursor.execute("EXECUTE cms_lib_insert(...)")
       cms_lib_id = dbCursor.fetchone()[0]     # you're using this below.
       dbCursor.executemany("EXECUTE metadata_insert(...)")
       dbCursor.executemany("EXECUTE library_insert(...)")
   dbCursor.commit()

Если вам нужно добиться более высоких уровней производительности, чем этот, следующим шагом будет создание функции-обработчика вставки, которая принимает массивы строк для зависимых таблиц.Я не рекомендую делать это, поскольку это быстро становится кошмаром обслуживания.

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