如何优化2.5亿行的Mysql数据库批量插入和选择

Ken*_*dde 5 mysql innodb database-design optimization

我是一名学生,他的任务是设计传感器数据数据库。我的大学目前有一个大型数据库,里面充满了这些数据,但存储的很多内容都是不必要的。他们希望我从现有数据库中提取一些字段,并将其插入到一个新数据库中,该新数据库只包含“必需品”。我需要从旧行中提取每一行,并每天获取一次新数据。

  • 有1500个传感器。
  • 他们每分钟生成一个读数。
  • 每天约210万次阅读
  • 当前数据库约有 2.5 亿行。

将执行的查询通常是在给定时间跨度之间选择一组传感器的传感器读数。

我最初对于大量数据带来的复杂性还很天真,所以我严重低估了这项任务所需的时间。因此,加上我无法从家里访问服务器,我在这里寻求帮助和意见。

最初的设计如下所示:

CREATE TABLE IF NOT EXISTS SENSORS (
    ID smallint UNSIGNED NOT NULL AUTO_INCREMENT,
    NAME varchar(500) NOT NULL UNIQUE,
    VALUEFACETS varchar(500) NOT NULL,
    PRIMARY KEY (ID)
); 

CREATE TABLE IF NOT EXISTS READINGS (
    ID int UNSIGNED AUTO_INCREMENT,
    TIMESTAMP int UNSIGNED INDEX NOT NULL,
    VALUE float NOT NULL,
    STATUS int NOT NULL,
    SENSOR_ID smallint UNSIGNED NOT NULL,
    PRIMARY KEY (ID),
    FOREIGN KEY (SENSOR_ID) REFERENCES SENSORS(ID)
);
Run Code Online (Sandbox Code Playgroud)

设计问题

我的第一个问题是我是否应该为读数保留自动递增密钥,或者在 TIMESTAMP(UNIX 纪元)和 SENSOR_ID 上使用复合密钥是否会更有利?

这个问题既适用于我每天必须插入 210 万行的事实,也适用于我想要针对上述查询进行优化的事实。

初始批量插入:

经过大量的试验和错误并在网上找到指南后,我发现使用 load infile 插入最适合此目的。我编写了一个脚本,该脚本将从旧数据库中选择 500 000 行,并将它们(全部 2.5 亿行)写入 csv 文件,如下所示:

TIMESTAMP,SENSOR_ID,VALUE,STATUS
2604947572,1399,96.434564,1432543
Run Code Online (Sandbox Code Playgroud)

然后我的计划是使用 GNU 排序对其进行排序,并将其拆分为包含 100 万行的文件。

在插入这些文件之前,我将删除 TIMESTAMP 上的索引,并运行以下命令:

SET FOREIGN_KEY_CHECKS = 0;
SET UNIQUE_CHECKS = 0;
SET SESSION tx_isolation='READ-UNCOMMITED';
SET sql_log_bin = 0;
Run Code Online (Sandbox Code Playgroud)

插入后,我当然会恢复这些更改。

  • 这个计划到底可行吗?

  • 如果我根据 SENSOR_ID 和 TIMESTAMP 而不是 TIMESTAMP 和 SENSOR_ID 对 csv 进行排序,可以加快插入速度吗?

  • 批量插入后重新打开索引后,每天可以插入200万行吗?

  • 是否可以使用常规插入语句进行每日插入,或者我必须使用 load infile 才能跟上
    输入负载?

我的cnf

除以下配置外,所有配置均为默认配置:

  innodb_flush_log_at_trx_commit=2
  innodb_buffer_pool_size=5GB
  innodb_flush_method=O_DIRECT
  innodb_doublewrite = 0
Run Code Online (Sandbox Code Playgroud)

为了这个特定目的我还需要任何其他优化吗?

服务器有 8GB RAM。mysqld 版本 8.0.22 Ubuntu 20.04

任何想法、想法或意见将不胜感激。

Ric*_*mes 1

“传感器”数据集的一般建议:

  • 最小化数据类型
  • 最小化索引
  • 批量插入
  • 每秒 25 行速度很快,但不需要更剧烈的步骤

规格:

  • STATUS int NOT NULL - 4字节?它可能有什么价值?(如果可行,请将其缩小。)
  • 我认为这PRIMARY KEY(sensor_id, timestamp)将是独特且充分的。然后彻底摆脱id。结果是减少了一个需要更新的二级索引。我建议对列按此顺序。但真正的选择需要基于SELECTs将要执行的操作。
  • 将所有 1500 行收集到一个INSERT. 或者使用LOAD DATA INFILE(除非这需要更多的磁盘命中)。也就是说,你INSERT每分钟就会有一个。单个线程跟上应该没有问题。“持续”加载,我认为等到一天结束没有任何好处。(或者我错过了什么?)
  • 调试你的代码,然后去掉FOREIGN KEY; FK 检查需要额外的努力。
  • 将数百万行放入表中的初始加载数据——这可能会达到一些超时和/或缓冲区限制。在这种情况下,将其分成块 - 但不是 1M 行;相反,可能是 10K 行。并以单线程方式进行。如果您尝试以多线程方式进行操作,我会犹豫是否会遇到可能遇到的问题。此外,它可能主要受 I/O 限制,因此无法从多线程中受益太多。
  • autocommit开启;这样每个块都会被提交。否则,重做日志将变得巨大,占用额外的磁盘空间并减慢速度。
  • 预排序数据——这对一些人有帮助。在您的原始架构中,按辅助键排序(timestamp);意志AUTO_INCREMENT会照顾自己。如果您删除 auto_inc,然后按 排序PRIMARY KEY(sensor_id, timestamp)(如果您接受我的建议)。
  • PRIMARY KEY加载数据时将其放置到位。否则,在构建 PK 时需要复制该表。
  • 如果有辅助密钥,ALTER TABLE .. ADD INDEX ..则在初始加载后。
  • 设置看起来不错。不过,我会继续说innodb_doublewrite下去——这可以防止罕见但灾难性的数据损坏。

与您提供的链接相关的评论:

  • 该链接已有 8 年历史。但其中大部分仍然有效。(不过,我不同意一些细节。)
  • 如果您最终需要删除旧数据,请立即计划。请参阅此时间序列,通过使用以下命令可以大大加快每月删除旧数据的速度PARTITION BY RANGE(): http: //mysql.rjweb.org/doc.php/partitionmaint 注意:我建议的 PK 是按天分区的正确选择(或周或月)。
  • 分区的唯一性能优势(基于到目前为止所讨论的内容)是当涉及到DELETEingvia时DROP PARTITION。没有SELECTs可能跑得更快(或更慢)。
  • 如果您使用mysqldump,只需使用默认值即可。这将产生易于管理的INSERTs大量行。TSV/CSV 不是必需的,除非您来自其他来源。
  • 我不知道他在 LOAD FILE 和批量插入以及每个块的行数之间的争论。也许他的二级索引会减慢速度。“Change buffer”是一种二级索引的写缓存。一旦满了,插入必然会减慢,等待更新被刷新。通过摆脱二级索引,这个问题就消失了。或者,通过ADD INDEX在加载数据后使用,可以推迟费用。

捕获一个损坏的 cron 作业...

如果您有一个 cron 作业根据源中的时间戳捕获“昨天”的数据,则仍然存在 cron 作业失败或什至不运行的风险(当您的服务器在作业应该运行时关闭时) )。

使用我推荐的 PK(sensor, ts),以下方法相当有效:

SELECT sensor, MAX(ts) AS max_ts
    FROM dest_table GROUP BY sensor;
Run Code Online (Sandbox Code Playgroud)

效率来自于在桌子上跳来跳去,只跳到 1500 个位置。

在 cron 作业开始时,运行此命令(在目标上)以“找出您停止的位置”:

SELECT MAX(max_ts) AS left_off FROM
       ( SELECT sensor, MAX(ts) AS max_ts
            FROM dest_table GROUP BY sensor ) AS x;
Run Code Online (Sandbox Code Playgroud)

然后从源中获取新行

    WHERE ts > left_off
      AND ts < CURDATE()
Run Code Online (Sandbox Code Playgroud)

通常情况下,left_off会在昨天早上午夜之前不久。有打嗝的时候就会提前一天。

您还可以使用该子查询来查看是否有任何传感器离线以及何时离线。