python mysql 批量插入

首先我们看看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()

 

 

 

 

 

 

 

發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章