【问题标题】:Bulk update SQL (Oracle) with Python使用 Python 批量更新 SQL (Oracle)
【发布时间】:2014-01-04 00:40:19
【问题描述】:

我正在寻求有关如何批量更新包含多达 250,000 条记录的 Oralce SQL 表的帮助。

基本上,我有一个键列表,这些键被传递给需要更新 Oracle 表的函数。该列表最多可以包含 250,000 行,我可以使用普通的更新语句或使用“executemany”来执行此操作,但两种方法都效率太低,所以我需要进行批量更新,但我不熟悉如何执行此操作。我已经搜索了几个小时,但我无法弄清楚!

todays_date = datetime.now().strftime("%d-%b-%Y")
status = str("DONE")

try:
  bind_values = {"status" : str(status),
                 "todays_date" : todays_date,
                 "keys_list" : list_of_keys}

  query = ("""FORALL i IN :keys_list.FIRST .. :keys_list.LAST
              UPDATE TABLE_NAME
              SET COLUMN1 = :status,
              UPDATE_DATE = :todays_date
              WHERE KEY = :i""")

  cursor.execute(query, bind_values)
  conn.commit()
  self.CloseConnection(conn)
except cx_Oracle.DatabaseError, e:
  error, = e.args
  print("  >> Database error: %s" % format(e))
  conn.rollback()
  return False

任何帮助将不胜感激。

更新 @abarnert - 非常感谢你的建议,你肯定在这里做一些事情,我设法做到了这一点

cursor.execute("""CREATE GLOBAL TEMPORARY TABLE TodaysKeys
                 (key STRING PRIMARY KEY)
                 on commit delete rows
                 AS (INSERT INTO TodaysKeys VALUES (:i))
                 UPDATE TABLE_NAME
                 SET COLUMN1 = :status,
                 UPDATE_DATE = :todays_date
                 WHERE KEY IN (SELECT * FROM TodaysKeys)
                 TABLE TodaysKeys""", i=keys_list, 
                 status=str(updatestatus), 
                 todays_date=todays_date)

但现在我得到的只是一个错误:“ORA-01036:非法变量名称/编号”。我确信这是非常明显的事情,但我已经检查了一遍又一遍,但我一生都看不到我哪里出错了!

从对这种方法的所有研究来看,这似乎是正确的方法……如果我可以让它工作测试! 请帮忙。

【问题讨论】:

  • “效率太低”是什么意思?你的意思是上传时间太长了?您如何知道解决方案何时足够高效?
  • 如果跨 250K 行的单个 UPDATE 语句太慢,则几乎可以肯定问题出在您的数据模型或数据库配置中,而 Python 无法加快速度。
  • 如果表有 250K 行并且您尝试一次更新每一行,可能将其转储到文件、更改文件并@ 987654324@(或者可能使用单独的散装装载机)......但我对此表示怀疑。
  • “效率太低” - 是的,它需要太多时间,它会超时。例如更新 75,000 行,大约需要 3 个小时,这太疯狂了!这是对每条记录进行更新!不知道还能怎么做。

标签: python sql oracle


【解决方案1】:

您需要找到某种方法来跨 250K 行执行 1 次操作,而不是 250K 单独的 1 行操作,因为显然,考虑到您的数据模型设计(我猜您既无法控制也无法理解)后者实在是太慢了。

那么,你是怎么做到的呢?

一种方法是创建一个非常简单的临时表,将今天的所有键都转储到其中(使用executemany 应该更快,或者,如果不是,至少使用LOAD DATA 更简单...),然后执行 UPDATE 引用该临时表中的键。像这样(伪代码,基于使用 sqlite3 进行测试,然后从远程内存转换为 Oracle……):

CREATE TEMPORARY TABLE TodaysKeys (key INT PRIMARY KEY)

INSERT INTO TodaysKeys VALUES (:i)

UPDATE TABLE_NAME
    SET COLUMN1 = :status,
    UPDATE_DATE = :todays_date
    WHERE KEY IN (SELECT * FROM TodaysKeys)

DROP TABLE TodaysKeys

如果 this 很慢,这意味着您在 KEY 列上没有索引,在这种情况下……如果不修复它,真的没有办法加快速度。

【讨论】:

  • FWIW,在数据仓库的世界中,索引往往被视为一种责任而不是好处——因为它们会导致过多的轨道间寻道,这比轨道内寻道慢得多。
  • 这可能会有所帮助:infolab.stanford.edu/~ullman/fcdb/oracle/or-load.html。至少,对于(现已死的)Datallegro 产品,批量加载大大加快了大量加载。
  • @abarnert - 感谢您的建议,希望这是正确的答案....一旦我可以让它运行测试,我会告诉你。
  • @dstromberg:这里的诀窍是您不能批量加载更新。使用临时表将键转储到其中可能会使批量加载变得不必要——但如果仍然需要,临时键表也恰好使批量加载变得非常简单。至于您的其他评论,对我来说,这听起来更像是一张生产表;通常使用数据仓库,您每天插入 250K 行,但几乎没有更新……但由于 OP 没有告诉我们,这实际上只是一个猜测。
【解决方案2】:

感谢大家的建议,我终于使用了这种方法,希望这可以帮助将来在类似情况下的其他人。

query = """
         DECLARE
           CURSOR rec_cur IS
           SELECT UNQ_KEY
           FROM TABLE_NAME
           WHERE COLUMN1 = 'NEW';
           TYPE updated_keys IS TABLE OF VARCHAR(100);
           pk_tab updated_keys;
         BEGIN
           OPEN rec_cur;
           LOOP
             FETCH rec_cur BULK COLLECT INTO pk_tab LIMIT 5000;
             EXIT WHEN pk_tab.COUNT() = 0;

             FORALL i IN pk_tab.FIRST .. pk_tab.LAST
               UPDATE TABLE_NAME
               SET    COLUMN1 = :status,
                      UPDATE_DATE = :todays_date
               WHERE  unq_key = pk_tab(i);
           END LOOP;
           CLOSE rec_cur;
         END
     """

再次感谢

【讨论】:

    猜你喜欢
    • 1970-01-01
    • 2021-08-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 2013-12-02
    相关资源
    最近更新 更多