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()

 

 

 

 

 

 

 

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