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
之後的佔位符,這導致這部分佔位符無法被渲染。
替代之路有兩條:
- 使用execute語句,pymysql的execute()經實測可以為
insert into ... on duplicate key update ...
語句正確渲染雙倍引數。 - 使用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版本此問題依舊。