首先我們看看mysql的存入數據方法:
插入數據:
INSERT INTO table_name (列1, 列2,...) VALUES (值1, 值2,....)
更新數據:
UPDATE 表名稱 SET 列名稱 = 新值 WHERE 列名稱 = 某值
那我們如果需要代碼自動判斷該插入還是更新呢?
2種方法,但都有一個條件,mysql表中設置唯一索引,多字段可設置多個。
REPLACE INTO table_name (列1, 列2,...) VALUES (值1, 值2,....)
或者:
INSERT INTO table_name (列1, 列2,...) VALUES (值1, 值2,....) ON DUPLICATE KEY UPDATE 列1=值1, 列2=值2,....
這兩種方法都是在唯一索引衝突時,執行對數據的更新,區別在於
REPLACE INTO 爲DELETE該條數據後再執行INSERT操作,所以如果表中還有其他字段此次沒有插入,是會被刪掉的
而ON DUPLICATE KEY 爲INSERT衝突,則執行UPDATE操作,是會保存其他字段的
兩種方法適用於不同場景,大家可以根據需求選擇
接着是大家不得不面對的插入效率問題,在面對大量數據時,單次插入顯然滿足不了我們的需求。
但是在這裏不推薦大家使用mysql提供的批量策略,因爲其中涉及到mysql無法從本表查詢並更新本表以及大量數據帶來的mysql語句過長等問題。
推薦大家使用pymysql所提供的 executemany 方法:
cursor.executemany(sql, data)
先來看看用法:
sql格式和單條格式一樣,需要修改的地方用%s代替,數據按順序在data中以tuple格式傳入:
sql = "INSERT INTO table_name (列1, 列2,...) VALUES (%s, %s,....)"
datalist = ((值1,值2,....), (值1,值2,....), ....)
cursor.executemany(sql, datalist)
這裏需要注意一下此時並不支持生成式的格式,需要我們手動轉換一下:
好了,現在我們看看他是怎麼實現的:
先看看源碼:
RE_INSERT_VALUES = re.compile(
r"\s*((?:INSERT|REPLACE)\b.+\bVALUES?\s*)" +
r"(\(\s*(?:%s|%\(.+\)s)\s*(?:,\s*(?:%s|%\(.+\)s)\s*)*\))" +
r"(\s*(?:ON DUPLICATE.*)?);?\s*\Z",
re.IGNORECASE | re.DOTALL)
def executemany(self, query, args):
# type: (str, list) -> int
"""Run several data against one query
:param query: query to execute on server
:param args: Sequence of sequences or mappings. It is used as parameter.
:return: Number of rows affected, if any.
This method improves performance on multiple-row INSERT and
REPLACE. Otherwise it is equivalent to looping over args with
execute().
"""
"""僅對INSERT和REPLACE方法生效,其他必須通過execute()進行循環處理。"""
if not args:
return
"""正則從我們傳入的sql中取出各個參數,同時實現篩選INSERT 和 REPLACE"""
m = RE_INSERT_VALUES.match(query)
if m:
q_prefix = m.group(1) % ()
q_values = m.group(2).rstrip()
q_postfix = m.group(3) or ''
assert q_values[0] == '(' and q_values[-1] == ')'
return self._do_execute_many(q_prefix, q_values, q_postfix, args,
self.max_stmt_length,
self._get_db().encoding)
"""統計處理條數"""
self.rowcount = sum(self.execute(query, arg) for arg in args)
return self.rowcount
def _do_execute_many(self, prefix, values, postfix, args, max_stmt_length, encoding):
conn = self._get_db()
"""初始化最初sql"""
escape = self._escape_args
if isinstance(prefix, text_type):
prefix = prefix.encode(encoding)
if PY2 and isinstance(values, text_type):
values = values.encode(encoding)
if isinstance(postfix, text_type):
postfix = postfix.encode(encoding)
sql = bytearray(prefix)
"""組合第一條data"""
args = iter(args)
v = values % escape(next(args), conn)
if isinstance(v, text_type):
if PY2:
v = v.encode(encoding)
else:
v = v.encode(encoding, 'surrogateescape')
sql += v
rows = 0
"""循環添加傳入data組合成一條sql"""
for arg in args:
v = values % escape(arg, conn)
if isinstance(v, text_type):
if PY2:
v = v.encode(encoding)
else:
v = v.encode(encoding, 'surrogateescape')
"""對最大字段進行判斷,防止sql語句過長報錯"""
if len(sql) + len(v) + len(postfix) + 1 > max_stmt_length:
rows += self.execute(sql + postfix)
sql = bytearray(prefix)
else:
sql += b','
sql += v
"""執行最終的sql"""
rows += self.execute(sql + postfix)
self.rowcount = rows
return rows
我們再在所有對sql有處理的地方打印一下,看看sql的變化,我們的測試代碼:
connect = get_connect()
cursor = connect.cursor()
sql = 'INSERT INTO test (value_key, value1) VALUES (%s, %s) ON DUPLICATE KEY UPDATE value1="11111"'
data = (('123', 'abc'), ('456', 'zzz'), ('789', 'qwe'))
cursor.executemany(sql, data)
cursor.close()
connect.close()
最終打印的結果:
bytearray(b'INSERT INTO test (value_key, value1) VALUES ')
bytearray(b"INSERT INTO test (value_key, value1) VALUES (\'123\', \'abc\')")
bytearray(b"INSERT INTO test (value_key, value1) VALUES (\'123\', \'abc\'),(\'456\', \'zzz\')")
bytearray(b"INSERT INTO test (value_key, value1) VALUES (\'123\', \'abc\'),(\'456\', \'zzz\'),(\'789\', \'qwe\')")
bytearray(b'INSERT INTO test (value_key, value1) VALUES (\'123\', \'abc\'),(\'456\', \'zzz\'),(\'789\', \'qwe\') ON DUPLICATE KEY UPDATE value1="11111"')
1、可以看到在pymysql的內部,同樣也是講多條數據合併成同一條sql來運行來提高批量處理的效率,並且在同時對一些可能發生的sql錯誤進行了處理。所以在進行批量數據插入時,大家就不要自己寫批量sql了。
2、對於sql中的%s字段的解析,僅僅在代碼中的v變量中,也就是我們的values中的內容,而對於其他部分,是不支持傳入%s的!
也就是說如果我想對已有字段進行動態更新,是無法做到的:
connect = get_connect()
cursor = connect.cursor()
"""這樣是會報錯的!!"""
sql = 'INSERT INTO test (value_key, value1) VALUES (%s, %s) ON DUPLICATE KEY UPDATE value1=%s'
data = (('123', 'abc', 'abc'), ('456', 'zzz', 'zzz'), ('789', 'qwe', 'qwe'))
cursor.executemany(sql, data)
cursor.close()
connect.close()