如何解决MySQL中的性能分组问题?

如何解决MySQL中的性能分组问题?,mysql,performance,Mysql,Performance,我正在为家电产品建立物联网系统 我的数据表已创建为 mysql> SHOW CREATE TABLE DataM1\G *************************** 1. row *************************** Table: DataM1 Create Table: CREATE TABLE `DataM1` ( `sensor_type` text, `sensor_name` text, `timestamp` datetime DE

我正在为家电产品建立物联网系统

我的数据表已创建为

mysql> SHOW CREATE TABLE DataM1\G
*************************** 1. row ***************************
   Table: DataM1
Create Table: CREATE TABLE `DataM1` (
  `sensor_type` text,
  `sensor_name` text,
  `timestamp` datetime DEFAULT NULL,
  `data_type` text,
  `massimo` float DEFAULT NULL,
  `minimo` float DEFAULT NULL,
  KEY `timestamp_id` (`timestamp`) USING BTREE,
  KEY `super_index_id` (`timestamp`,`sensor_name`(11),`data_type`(11)) USING BTREE
) ENGINE=InnoDB DEFAULT CHARSET=utf8
问题是

SELECT 
  sensor_type, sensor_name, timestamp, data_type, 
  MAX(massimo) as massimo, MIN(minimo) as minimo 
FROM DataM1 
  WHERE timestamp >= NOW() - INTERVAL 1 HOUR 
  GROUP BY timestamp, sensor_type, sensor_name, data_type;
现在的问题是,当表达到400万(几天)行时,查询需要50秒以上的时间

编辑:解释结果如下:

           id: 1
    select_type: SIMPLE
          table: DataM1
     partitions: p0,p1,p2,p3,p4,p5,p6
           type: range
  possible_keys: timestamp_id,super_index_id
            key: timestamp_id
        key_len: 6
            ref: NULL
           rows: 1
       filtered: 100.00
          Extra: Using index condition; Using temporary; Using filesort
编辑:回复的示例行为:

*************************** 418037. row ***************************
sensor_type: SEN
sensor_name: SEN_N2
  timestamp: 2016-10-16 17:28:48
  data_type: flow_rate
    massimo: 17533.8
     minimo: 17533.5
编辑:我已经规范化了时间戳、传感器类型、传感器名称和数据类型的值,并创建了一个_视图以方便数据的使用:

CREATE VIEW `_view` AS (
  select (
    select `vtmp`.`timestamp` from `timestamp` `vtmp` where (`vtmp`.`no` = `pm`.`timestamp`)) AS `timestamp`,(
      select `vtmp`.`sensor_type` from `sensor_type` `vtmp` where (`vtmp`.`no` = `pm`.`sensor_type`)) AS `sensor_type`,(
        select `vtmp`.`sensor_name` from `sensor_name` `vtmp` where (`vtmp`.`no` = `pm`.`sensor_name`)) AS `sensor_name`,(
          select `vtmp`.`data_type` from `data_type` `vtmp` where (`vtmp`.`no` = `pm`.`data_type`)) AS `data_type`,
          `pm`.`massimo` AS `massimo`,
          `pm`.`minimo` AS `minimo` 
          from `datam1` `pm` order by `pm`.`timestamp` desc);
有没有办法加快索引、分片和/或分区的速度?
还是重新考虑将不同表中的信息分开的表更好?如果是的话,在这种情况下,有人能提出他的最佳做法吗

通过在用于排序的列上添加复合索引,您可以通过查询来加快组的速度:

GROUP BY timestamp, sensor_type, sensor_name, data_type;
匹配项:

ADD KEY `group_index` (`timestamp`, `sensor_type`(11), `sensor_name`(11), `data_type`(11)) 
还要注意上述索引中的(11):

对于文本列,MySQL需要限制这些列的内容以进行索引。您还可以通过选择更合适的数据类型来加快查询速度,如传感器和数据类型的INT(您只有几个不同的类型,是吗?)和传感器名称的VARCHAR(128)


当然,更改数据布局也会给您带来一些好处。将传感器信息(类型+名称)存储在不同的表中,然后将其与数据表中的传感器id链接。这样,只需要对单个INT列进行排序(=分组),这比对两个文本列进行排序要好得多

我认为这就是这样的用例,当您有这么多数据时,最好的解决方案可能是使用noSQL数据库,并在存储数据之前执行一些聚合。你可以看看,然后

但是,为了回答您的问题,我将使用我的系统所需的最小粒度预先计算数据聚合(您可以每10分钟计算一次聚合),然后您将能够对较小数量的数据执行查询

  • 不要使用“前缀”索引,如
    传感器名称(11)
    ;它很少有帮助,有时也有伤害
  • 如果传感器名称和类型以及数据类型不能超过255个字符,请不要使用
    TEXT
    ;相反,
    VARCHAR(…)
    具有一些现实的限制
  • 规范化传感器名称和类型以及数据类型——我假设它们重复了很多次<代码>枚举是一个合理的选择
  • 密钥(时间戳)和密钥(时间戳,…)是冗余的;放弃前者
  • 您的表需要一个
    主键
    。如果没有列(或列集)是唯一的,则使用
    自动增量
  • 也许您不想使用准确的时间戳启动
    分组。也许缩短到一小时?例如,
    CONCAT(左(timestamp,13),':xx')
    将产生类似于
    2016-10-16 20:xx
    的结果
  • 查询花费很长时间的主要原因是它要输出418K行。那么多行你怎么办?我看不到
    限制
    ,也看不到
    订购。这种情况还会继续吗
  • 分区和分片对速度没有任何帮助

这些建议将在不同方面有所帮助。一旦您修复了大部分问题,我们就可以讨论如何使用汇总表来获得10倍的加速。

这个答案讨论了如何构建一个汇总表

CREATE TABLE Summary (
    -- The primary key:
    hr DATETIME  NOT NULL  COMMENT "Start of hour",
    sensor_type ...,
    sensor_name ...,
    -- The aggregates being collected:
    num_readings SMALLINT UNSIGNED NOT NULL,
    sum_reading FLOAT NOT NULL,  -- (maybe)
    min_reading FLOAT NOT NULL,
    max_reading FLOAT NOT NULL,
    PRIMARY KEY(hr, sensor_type, sensor_name),
    INDEX(sensor_name, hour)   -- Maybe you want to look up by sensor?
) ENGINE=InnoDB;
每小时,用类似的内容填充它

INSERT INTO Summary
    (hr, sensor_type, sensor_name, num_readings,
     sum_reading, min_reading, max_reading)
    SELECT
        FROM_UNIXTIME(3600 * (FLOOR(UNIX_TIMESTAMP() / 3600) - 1)),   -- start of prev hour
        sensor_type,
        sensor_name,
        COUNT(*),   -- how many readings were taken in the hour.
        SUM(??),  -- maybe this is not practical, since you seem to have pairs of readings
        MAX(massimo),
        MIN(minimo)
    FROM DataM1
    WHERE `timestamp` >= FROM_UNIXTIME(3600 * (FLOOR(UNIX_TIMESTAMP() / 3600) - 1))
      AND `timestamp`  < FROM_UNIXTIME(3600 * (FLOOR(UNIX_TIMESTAMP() / 3600)));
六月份全年:

    WHERE timestamp >= '2016-06-01'
      AND timestamp  < '2016-06-01' + INTERVAL 1 MONTH
其中时间戳>='2016-06-01'
时间戳<'2016-06-01'+间隔1个月

注:获得平均值的简单方法是对平均值进行平均。但数学上正确的方法是求和并除以计数之和。因此,我加入了
sum\u reading
num\u readings
。另一方面,当平均诸如天气读数之类的数据时,通常会得到每天的平均值,然后是几天的平均值。我将让您决定什么是“正确的”。

您应该发布解释结果。其他一些信息,如最近一小时内的行数,也会很有帮助。可能还有一些示例数据(只有几行)要查看,您的数据是什么样子的。@PaulSpiegel这里是解释结果:id:1选择类型:简单表:DataM1分区:p0,p1,p2,p3,p4,p5,p6类型:范围可能的键:timestamp\u id,super\u index\u id key:timestamp\u id key\u len:6 ref:NULL行:1筛选:100.00额外:使用索引条件;使用临时设备;使用filesort@PaulSpiegel上一小时内的行数为60分钟*60秒*8个传感器*4个数据类型=115200,因此文本列似乎只包含短字符串。一个快速的修复方法是将它们更改为VARCHAR(100)之类的东西。并根据GROUPBY子句创建索引。您也可以尝试使用ENUM而不是VARCHAR。为什么您需要每秒为每个传感器收集传感器值以实现简单的家庭自动化?你不能简单地减少数据量吗?这对我不起作用,因为当
选择计数(*)
为20000时,这样的
组索引的基数为17376。所以性能没有大的提高。不过,我认为值得尝试一下你关于整数排序/分组的建议。@sfiore-17376 vs 20000——这说明时间戳几乎是唯一的。那么,为什么要费心做
分组呢?
?谢谢你,瑞克,我认为你的帖子给了我很多改进的地方。尽管如此,
ENUM
并不是一种可行的方法,因为必须具备的行为是以即插即用的方式接受新传感器。你有时间讨论汇总表吗?你有好的参考链接吗?新的传感器——创建另一个带有id和传感器名称的表;“正常化”。使id
TINYINT无符号
(1字节中最多255个)或
SMALLINT无符号
(2字节中最多65K个)。完成。我这样做:
createtabletimestamp(no-BIGINT(20)notnull自动增量,timest
    WHERE timestamp >= '2016-06-01'
      AND timestamp  < '2016-06-01' + INTERVAL 1 MONTH