MySql中幾種批量更新的方法

通常情況下,我們會使用以下SQL語句來更新字段值:

UPDATE mytable SET myfield='value' WHERE other_field='other_value';

但是,如果你想更新多行數據,並且每行記錄的各字段值都是各不一樣,你會怎麼辦呢?剛開始你可能會想到使用循環執行多條UPDATE語句的方式,就像以下的python程序示例:

for x in xrange(10):
    sql = ''' UPDATE mytable SET myfield='value' WHERE other_field='other_value';  '''

這種方法並沒有什麼任何錯誤,並且代碼簡單易懂,但是在循環語句中執行了不止一次SQL查詢,在做系統優化的時候,我們總是想盡可能的減少數據庫查詢的次數,以減少資源佔用,同時可以提高系統速度

1、replace into 批量更新(UNIQUE索引或PRIMARY KEY)

replace into test_tbl (id,dr) values (1,'2'),(2,'3'),...(x,'y');

2、insert into ...on duplicate key update批量更新

  • 導致在一個UNIQUE索引或PRIMARY KEY中出現重複值,則在出現重複值的行執行UPDATE。如果不會導致唯一值列重複的問題,則插入新行。 
  • values(col_name)函數只是取當前插入語句中的插入值,並沒有累加功能。
  • 每次更新都會更新該表的自增主鍵ID,如果更新頻率很快,會導致主鍵ID自增的很快,過段時間就超過數字類型的的範圍了,解決方法:innodb_autoinc_lock_mode=0
  • 事務隔離級別REPEATABLE-READ下會發生死鎖。
創建案例表 word_count_0626(單詞計數表)
  CREATE TABLE IF NOT EXISTS word_count_0626 (
  	id int(11) NOT NULL AUTO_INCREMENT,
  	word varchar(64) NOT NULL,
  	count int(11) DEFAULT 0,
  	date date NOT NULL,
  	PRIMARY KEY (id),
  	UNIQUE KEY word (word, date)
  ) ENGINE=InnoDB DEFAULT CHARSET=utf8;
  
1. 執行第一次:(首次數據庫表中沒有數據,正常插入)
  insert into word_count_0626 (word, count, date) values 
  ('a',5,curdate()) 
  on duplicate key update count=values(count);
  # 結果顯示:
  id   word    count    date 
  1    a       5        2019-06-26
  
2. 執行第二次:(與第一次的唯一(word,date)衝突,執行更新)
  insert into word_count_0626 (word, count, date) values 
  ('a',6,curdate()) 
  on duplicate key update count=values(count);
  # 結果顯示:
  id   word    count    date 
  1    a       6        2019-06-26  (更新)
  
3. 執行第三次:
  insert into word_count_0626 (word, count, date) values 
  ('a',6,curdate()-1),    // 取前一天,不會衝突
  on duplicate key update count=values(count);
  # 結果顯示:
  id   word    count    date 
  3    a       6        2019-06-25  (新插入,ID跳躍了)

 

final public function updateBatch($tableName, array $data): bool
{
    $result = false;
    $tableName = $tableName ?: $this->tableName;
    if (!empty($data) && is_array($data) && count($data) !== count($data, COUNT_RECURSIVE)) {
        try {
            $keys = array_keys(current($data));
            $items = $updates = [];
            foreach ($data as $item) {
                if (array_keys($item) === $keys) {
                    $items[] = sprintf("('%s')", implode("','", array_values($item)));
                } else {
                    $items = false;
                    break;
                }
                foreach ($keys as $keyName) {
                    $updates[] = "`$keyName`='{$item[$keyName]}'";
                }
            }
            $sql = sprintf('insert into `%s` (`%s`) values %s ON DUPLICATE KEY UPDATE %s', $tableName, implode('`,`', $keys), implode(',', $items), implode(',', $updates));
            if (!empty($items)) {
                $this->conn->exec($sql);
                $result = true;
            }
        } catch (\Exception $exception) {
            die($exception->getMessage());
        }
    }

    return $result;
}

3、使用mysql 自帶case when構建批量更新

UPDATE book
SET Author = CASE id 
    WHEN 1 THEN '黃飛鴻' 
    WHEN 2 THEN '方世玉'
    WHEN 3 THEN '洪熙官'
END
WHERE id IN (1,2,3)

程序實現:case when的方式經過測試建議將修改記錄條數控制在1W左右,不要超過2W,否則會耗費的時間也是成倍增加的

<?php
/**
 * 批量更新函數
 * @param $data array 待更新的數據,二維數組格式
 * @param $table string 更新的表名
 * @param string $field string 值不同的條件,默認爲id
 * @param array $params array 值相同的條件,鍵值對應的一維數組
 * @return bool|string
 */
function batchUpdateUseCaseWhen($data, $table, $field, $params = [])
{
    if (!is_array($data) || !$field || !is_array($params)) {
        return false;
    }

    $updates = parseUpdate($data, $field);
    $where = parseParams($params);

    // 獲取所有鍵名爲$field列的值,值兩邊加上單引號,保存在$fields數組中
    $fields = array_column($data, $field);
    $fields = implode(',', array_map(function ($value) {
        return "'" . $value . "'";
    }, $fields));

    $sql = sprintf("UPDATE `%s` SET %s WHERE `%s` IN (%s) %s", $table, $updates, $field, $fields, $where);

    return $sql;
}


/**
 * 將二維數組轉換成CASE WHEN THEN的批量更新條件
 * @param $data array 二維數組
 * @param $field string 列名
 * @return string sql語句
 */

function parseUpdate($data, $field)
{
    $sql = '';
    $keys = array_keys(current($data));
    foreach ($keys as $column) {
        $sql .= sprintf("`%s` = CASE `%s`", $column, $field);
        foreach ($data as $line) {
            $sql .= sprintf("WHEN '%s' THEN '%s'", $line[$field], $line[$column]);
        }

        $sql .= "END,";
    }

    return rtrim($sql, ',');
}


/**
 * 解析where條件
 * @param $params
 * @return array|string
 */
function parseParams($params)
{
    $where = [];
    foreach ($params as $key => $value) {
        $where[] = sprintf("`%s` = '%s'", $key, $value);
    }

    return $where ? ' AND ' . implode(' AND ', $where) : '';
}

以上方案大大減少了數據庫的查詢操作次數,大大節約了系統資源 不過這個有個缺點 : 要注意的問題是SQL語句的長度,需要考慮程序運行環境所支持的字符串長度,當然這也可以更新mysql的設置來擴展。

show variables like 'max_allowed_packet';

vim /etc/my.cnf
[mysqld]
max_allowed_packet = 100M

4 創建臨時表,先更新臨時表,然後從臨時表中聯表update

create temporary table tmp(id int(4) primary key,dr varchar(50));
insert into tmp values  (0,'gone'), (1,'xx'),...(m,'yy');
update test_tbl, tmp set test_tbl.dr=tmp.dr where test_tbl.id=tmp.id;

注意:這種方法需要用戶有temporary 表的create 權限。臨時表在建立連接時可見,關閉時表結構和表數據都沒了;臨時表可以通過show create table table_name查看

一般在數據量比較大的查詢中,用in()等查詢條件,會嚴重影響查詢效率。

這時可以用  create temporary table table_name select id,name from table    創建臨時表

5. insert into table select * from table_b

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