BUG: pymysql executemany不支援insert on duplicate key update

realcp1018發表於2024-09-11

pymysql的executemany()方法支援傳入單個SQL和一個sequence of records(sequence or mapping)來同時寫入多條資料。

例如:

sql = "insert into t(c1, c2) values(%s, %s)"
args = [(1, 2), (3, 4)]
cursor.executemany(sql, args)
# If args is a list or tuple, %s can be used as a placeholder in the query.  
# If args is a dict, %(name)s can be used as a placeholder in the query.

這樣可以同時將(1,2) (3,4)兩條記錄寫入資料庫,至於效能取決於executemany自身的實現,並不是說一定比多次execute()快,只能說有利於程式碼簡化。

檢視了下executemany的程式碼發現,executemany只對insert/replace語句有加速效果,只有這兩種語句pymysql會一次性拼接完成發至mysql server端,其他語句依然是迴圈呼叫execute()。

executemany的問題:

經實測發現,pymysql的executemany 不支援insert into ... on duplicate key update ...語句, 而sqlalchemy支援。

當在pymysql executemany()中使用insert into ... on duplicate key update ...語句時,需要雙倍的引數,例如:

cursor.executemany("insert into t(c1, c2) values(%s, %s) on duplicate key update c1=%s, c2=%s", [(1, 2)*2, (3, 4)*2])

按常規思維來看,我們已為每條記錄的寫入提供了全部需要的4個引數,sql應該可以正確被渲染、執行,但實測executemany()會報佔位符渲染失敗相關的錯誤:TypeError: not all arguments converted during string formatting

而我們使用execute()語句測試一切正常:

cursor.execute("insert into t(c1, c2) values(%s, %s) on duplicate key update c1=%s, c2=%s", (1, 2)*2)

檢視executemany的程式碼可以發現,executemany僅對insert/replace語句有加速效果,這是因為針對這兩種語句pymysql做了集體拼接,減少了多次執行的round-trip耗時,然而在集體拼接過程中,解析sql的正規表示式並沒有去解析on duplicate key update之後的佔位符,這導致這部分佔位符無法被渲染。

替代之路有兩條:

  1. 使用execute語句,pymysql的execute()經實測可以為insert into ... on duplicate key update ...語句正確渲染雙倍引數。
  2. 使用mysql VALUES()函式可以在update字句中引用insert階段的值:
cursor.executemany("insert into t(c1, c2) values(%s, %s) on duplicate key update c1=Values(c1), c2=Values(c2)", [(1, 2), (3, 4)])

我們只需要傳輸一遍欄位值即可。

相關issue: New executemany() implementation supporting UPDATE query #506

PSs: 截止2024.9.11 最新的v1.1.1版本此問題依舊。

相關文章