11

我正在处理一个大型 MySQL 数据库,我需要提高特定表的 INSERT 性能。这一行包含大约 2 亿行,其结构如下:

(一个小前提:我不是数据库专家,所以我写的代码可能是基于错误的基础。请帮助我理解我的错误:))

CREATE TABLE IF NOT EXISTS items (
    id INT NOT NULL AUTO_INCREMENT,
    name VARCHAR(200) NOT NULL,
    key VARCHAR(10) NOT NULL,
    busy TINYINT(1) NOT NULL DEFAULT 1,
    created_at DATETIME NOT NULL,
    updated_at DATETIME NOT NULL,

    PRIMARY KEY (id, name),
    UNIQUE KEY name_key_unique_key (name, key),
    INDEX name_index (name)
) ENGINE=MyISAM
PARTITION BY LINEAR KEY(name)
PARTITIONS 25;

每天我都会收到许多 csv 文件,其中每一行都由一对“name;key”组成,所以我必须解析这些文件(为每一行添加值 created_at 和 updated_at)并将这些值插入到我的表中。在这一个中,“name”和“key”的组合必须是唯一的,所以我实现了插入过程如下:

CREATE TEMPORARY TABLE temp_items (
    id INT NOT NULL AUTO_INCREMENT,
    name VARCHAR(200) NOT NULL, 
    key VARCHAR(10) NOT NULL, 
    busy TINYINT(1) NOT NULL DEFAULT 1,  
    created_at DATETIME NOT NULL, 
    updated_at DATETIME NOT NULL,  
    PRIMARY KEY (id) 
    ) 
ENGINE=MyISAM;

LOAD DATA LOCAL INFILE 'file_to_process.csv' 
INTO TABLE temp_items
FIELDS TERMINATED BY ',' 
OPTIONALLY ENCLOSED BY '\"' 
(name, key, created_at, updated_at); 

INSERT INTO items (name, key, busy, created_at, updated_at) 
(
    SELECT temp_items.name, temp_items.key, temp_items.busy, temp_items.created_at, temp_items.updated_at 
    FROM temp_items
) 
ON DUPLICATE KEY UPDATE busy=1, updated_at=NOW();

DROP TEMPORARY TABLE temp_items;

刚刚显示的代码可以让我达到我的目标,但是要完成执行,它需要大约 48 小时,这是一个问题。我认为这种糟糕的性能是由于脚本必须检查一个非常大的表(2 亿行)并且对于每个插入“名称;键”对是唯一的。

如何提高脚本的性能?

提前感谢大家。

4

5 回答 5

5

您可以使用以下方法来加快插入速度:

  1. 如果您同时从同一个客户端插入多行,请使用带有多个 VALUES 列表的 INSERT 语句一次插入多行。这比使用单独的单行 INSERT 语句要快得多(在某些情况下快很多倍)。如果要向非空表添加数据,则可以调整 bulk_insert_buffer_size 变量以使数据插入更快。

  2. 从文本文件加载表时,使用 LOAD DATA INFILE。这通常比使用 INSERT 语句快 20 倍。

  3. 利用列具有默认值的事实。仅当要插入的值与默认值不同时才显式插入值。这减少了 MySQL 必须做的解析并提高了插入速度。

参考:MySQL.com:8.2.4.1 优化 INSERT 语句

于 2016-07-23T23:15:06.490 回答
4

您在名称和大型索引上的线性键会减慢速度。

LINEAR KEY 需要在每次插入时计算。 http://dev.mysql.com/doc/refman/5.1/en/partitioning-linear-hash.html

你能给我们展示一些 file_to_process.csv 的示例数据吗?也许应该建立一个更好的模式。

编辑看起来更仔细

INSERT INTO items (name, key, busy, created_at, updated_at) 
(
    SELECT temp_items.name, temp_items.key, temp_items.busy, temp_items.created_at, temp_items.updated_at 
    FROM temp_items
) 

这将探测地创建一个磁盘临时表,这非常非常慢,所以你不应该使用它来获得更高的性能,或者你应该检查一些 mysql 配置设置,比如 tmp-table-size 和 max-heap-table-size 也许这些配置错误。

于 2013-08-11T17:02:57.917 回答
0

我想指出一个文档Speed of INSERT Statements

于 2014-02-17T09:28:03.540 回答
0

通过在 java 中思考;

  • 将对象列表划分为分区,并为每个分区生成批量插入语句。
  • 有效地利用 CPU 内核和可用的数据库连接,不错的新 Java 功能可以帮助轻松实现并行性(例如并行,forkjoin),或者您可以创建自定义线程池,优化您拥有的 CPU 内核数量,并按顺序从集中阻塞队列中提供线程调用批量插入准备好的语句。
  • 如果可能,减少目标表上的索引数。如果真的不需要外键,就放弃它。更少的索引更快的插入。
  • 避免使用除 CRUD 操作之外的 Hibernate,总是为复杂的选择编写 SQL。
  • 减少查询中的连接数,而不是强制数据库,使用 java 流进行过滤、聚合和转换。
  • 如果觉得没必要做,不要把select和inserts组合成一条sql语句
  • 添加rewriteBatchedStatements=true到您的 JDBC 字符串中,这将有助于减少应用程序和数据库之间的 TCP 级别通信。
  • 用于@Transactional执行插入批处理和自己编写回滚方法的方法。
于 2021-05-17T15:12:47.433 回答
-2

你可以使用

load data local infile ''
REPLACE
into table 

ETC...

REPLACE确保任何重复值都被新值覆盖。在最后添加一个SET updated_at=now(),你就完成了。

不需要临时表。

于 2014-05-13T10:51:14.603 回答