首先我们看看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()
所以,我们需要在对sql进行一些修改,从values中取值,这样就可以实现批量update拉!:
connect = get_connect()
cursor = connect.cursor()
sql = 'INSERT INTO test (value_key, value1) VALUES (%s, %s) ON DUPLICATE KEY UPDATE value1=values(value1)'
data = (('123', 'abc'), ('456', 'zzz'), ('789', 'qwe'))
cursor.executemany(sql, data)
cursor.close()
connect.close()