如何设置 table 行 1500 万行以上的键以获得高性能和低成本?

How to set up table keys with 15M+ rows for high performance and low cost?

我需要确保 table 在使用 Aurora(目前是小型实例)托管在 AWS 中的 MySQL 数据库中有 1500 万行的最佳性能。 table 主要用于跟踪产品单元随时间的所有权和更新时间戳,以及每个单元的其他基本信息,如序列号。

栏目如下:

UnitId, ScanTime, Model, SerialNumber, MfrTimestamp, UpdateTimestamp, CustomerId

Table创作声明

CREATE TABLE `UnitHistory` (
  `UnitId` bigint(20) NOT NULL,
  `ScanTime` int(11) NOT NULL,
  `Model` bigint(20) NOT NULL,
  `SerialNumber` int(11) NOT NULL,
  `MfrTimestamp` int(11) NOT NULL,
  `UpdateTimestamp` int(11) DEFAULT NULL,
  `CustomerId` bigint(20) DEFAULT NULL,
  PRIMARY KEY (`UnitId`,`ScanTime`)
);

随着时间的推移会添加行,但绝不会修改。

我选择 UnitId 和 ScanTime 作为主键,因为这两个加在一起足以始终保持唯一。

查询 1

我最常使用的查询理想情况下会生成特定模型的所有 UnitId 的列表,以及该单元的最新详细信息。 以下查询将起作用,但当然也会 return 比我需要的行多(冗余数据):

SELECT UnitId, SerialNumber, MfrTimestamp, UpdateTimestamp, CustomerId FROM UnitHistory WHERE Model=2500;

如果有一种方法可以限制该查询,以便对于任何给定的 UnitId,只有具有最近 ScanTime 的行被 returned,那将是理想的。 否则,我将在结果中简单地为每个 UnitId 搜索具有最新 ScanTime 的行。

查询 2

另一个非常常用的查询将为任何特定单元生成一组基本的详细信息和历史记录,如下所示:

SELECT ScanTime, SerialNumber, MfrTimestamp, UpdateTimestamp, CustomerId FROM UnitHistory WHERE UnitId=1234567;

此查询将主要用于跟踪所有权从制造商转移到客户时的变化,然后 返回制造商进行更新,然后再返回给可能不同的客户等。

总结

对于上述情况,我应该有哪些额外的密钥以确保良好的性能和低成本?

一个成本因素是我认为我的工作集应该适合 RAM 以避免大量 IOs,因为 AWS 对 IOs 收费。 我当前的数据库实例有 2 GB RAM,出于成本原因我不想升级它。

对于你的查询 1,你应该有这个索引:

ALTER TABLE UnitHistory ADD INDEX (Model, ScanTime);

获取最新的:

SELECT UnitId, SerialNumber, MfrTimestamp, UpdateTimestamp, CustomerId 
FROM UnitHistory WHERE Model=2500
ORDER BY ScanTime DESC LIMIT 1;

下面是一个使用 EXPLAIN 确认查询使用索引的演示(在索引的第一列之后命名为 "Model",因为我在测试中没有给它命名):

mysql> explain SELECT UnitId, SerialNumber, MfrTimestamp, UpdateTimestamp, CustomerId FROM UnitHistory WHERE Model=2500 order by scantime desc limit 1;
+----+-------------+-------------+------------+------+---------------+-------+---------+-------+------+----------+-------------+
| id | select_type | table       | partitions | type | possible_keys | key   | key_len | ref   | rows | filtered | Extra       |
+----+-------------+-------------+------------+------+---------------+-------+---------+-------+------+----------+-------------+
|  1 | SIMPLE      | UnitHistory | NULL       | ref  | Model         | Model | 8       | const |    1 |   100.00 | Using where |
+----+-------------+-------------+------------+------+---------------+-------+---------+-------+------+----------+-------------+

您的其他查询 1 已按主键的 left-most 列进行搜索,因此无需添加其他索引。

mysql> explain SELECT ScanTime, SerialNumber, MfrTimestamp, UpdateTimestamp, CustomerId FROM UnitHistory WHERE UnitId=1234567;
+----+-------------+-------------+------------+------+---------------+---------+---------+-------+------+----------+-------+
| id | select_type | table       | partitions | type | possible_keys | key     | key_len | ref   | rows | filtered | Extra |
+----+-------------+-------------+------------+------+---------------+---------+---------+-------+------+----------+-------+
|  1 | SIMPLE      | UnitHistory | NULL       | ref  | PRIMARY       | PRIMARY | 8       | const |    1 |   100.00 | NULL  |
+----+-------------+-------------+------------+------+---------------+---------+---------+-------+------+----------+-------+

我无法预测您的工作集是否适合 RAM,因为我不知道您的数据分布。

我假设这是一次审计 table 并且您正在读取单位? 分区 tables、拥有视图或准备好的语句是一些可能的方法。

这是 Query1 的另一种方式。创建另一个 table 就像你的 UnitHistory。 Create table UnitReadings like UnitHistory; but unitid being the primary key.

然后改变你 UnitHistory table 并在插入之前或之后添加触发器。像,

 Insert into `UnitReading`(
    UnitId,
    ScanTime,
    Model,
    SerialNumber,
    MfrTimestamp,
    UpdateTimestamp,
    CustomerId
  ) values
  (
      NEW.UnitId,
      NEW.ScanTime,
      NEW.Model,
      NEW.SerialNumber,
      NEW.MfrTimestamp,
      NEW.UpdateTimestamp,
      NEW.CustomerId
  ) ON DUPLICATE KEY UPDATE
      ScanTime          = values(ScanTime),
      Model             = values(Model), 
      SerialNumber      = values(SerialNumber),
      MfrTimestamp      = values(MfrTimestamp),
      UpdateTimestamp   = values(UpdateTimestamp),
      CustomerId        = values(CustomerId);

目标是将最新读数保持在 "header table" 中,其行数可能少于您的(读数 * 每天 * 天)行的整个历史记录.几年后,您可能会超过 1500 万行,但您的 header table 可能仍约为 1000 个单位或您正在读取的任何单位数量。使用此 header table "withing your 2GB RAM" :) :)

您可能会超出您的性能预期

不确定你是否可以实现这个,但你的想法是对的?