从时间戳排序的大表优化SQL查询

时间:2014-11-10 14:42:29

标签: mysql database query-optimization

我们有一个包含以下表结构的大表:

CREATE TABLE `location_data` (
  `id` int(20) NOT NULL AUTO_INCREMENT,
  `dt` timestamp NOT NULL DEFAULT CURRENT_TIMESTAMP,
  `device_sn` char(30) NOT NULL,
  `data` char(20) NOT NULL,
  `gps_date` datetime NOT NULL,
  `lat` double(30,10) DEFAULT NULL,
  `lng` double(30,10) DEFAULT NULL,
  PRIMARY KEY (`id`),
  KEY `dt` (`dt`),
  KEY `data` (`data`),
  KEY `device_sn` (`device_sn`,`data`,`dt`),
  KEY `device_sn_2` (`device_sn`,`dt`)
) ENGINE=MyISAM AUTO_INCREMENT=721453698 DEFAULT CHARSET=latin1

我们多次执行过查询,例如:

SELECT * FROM location_data WHERE device_sn = 'XXX' AND data = 'location' ORDER BY dt DESC LIMIT 1;

OR

SELECT * FROM location_data WHERE device_sn = 'XXX' AND data = 'location' AND dt >= '2014-01-01 00:00:00 ' AND dt <= '2014-01-01 23:00:00' ORDER BY dt DESC;

我们一直在通过以下几种方式对其进行优化:

  1. FORCE INDEX上添加索引并使用device_sn
  2. 根据日期将表格分成多个表格(例如location_data_20140101)并预先检查是否存在基于特定日期的数据,我们将单独拉出该特定表格。此表由cron每天创建一次,该特定日期的location_data中的数据将被删除。
  3. 表格location_data为HIGH WRITE且LOW READ。

    然而,很少一次,查询运行速度很慢。我想知道是否有其他方法/方法/重组数据,使我们能够基于给定的device_sn以连续的日期方式读取数据。

    任何提示都受到欢迎。

    EXPLAIN STATEMENT 1ST QUERY:

    +----+-------------+--------------+------+----------------------------+-----------+---------+-------------+------+-------------+
    | id | select_type | table        | type | possible_keys              | key       | key_len | ref         | rows | Extra       |
    +----+-------------+--------------+------+----------------------------+-----------+---------+-------------+------+-------------+
    |  1 | SIMPLE      | location_dat | ref  | data,device_sn,device_sn_2 | device_sn | 50      | const,const |    1 | Using where |
    +----+-------------+--------------+------+----------------------------+-----------+---------+-------------+------+-------------+
    

    EXPLAIN STATEMENT 2nd QUERY:

    +----+-------------+--------------+-------+-------------------------------+------+---------+------+------+-------------+
    | id | select_type | table        | type  | possible_keys                 | key  | key_len | ref  | rows | Extra       |
    +----+-------------+--------------+-------+-------------------------------+------+---------+------+------+-------------+
    |  1 | SIMPLE      | test_udp_new | range | dt,data,device_sn,device_sn_2 | dt   | 4       | NULL |    1 | Using where |
    +----+-------------+--------------+-------+-------------------------------+------+---------+------+------+-------------+
    

2 个答案:

答案 0 :(得分:0)

索引device_sndevice_sndatadt)很好。 MySQL应该使用它而无需执行任何FORCE INDEX。您可以通过运行&#34;解释选择...&#34;

来验证它

但是,您的表是MyISAM,它只支持表级锁。如果表写得很重,那可能会很慢。我建议将其转换为InnoDB。

答案 1 :(得分:0)

好的,我会提供我知道的信息,这可能无法解答您的问题,但可以提供一些见解。

InnoDB与MyISAM之间存在一定的差异。忘记全文索引或空间索引,其巨大差异在于它们的运行方式。

与MyISAM相比,InnoDB有几个很棒的功能。 首先,它可以将它使用的数据集存储在RAM中。这就是数据库服务器带有大量RAM的原因 - 因此可以快速完成I / O操作。例如,如果在RAM中而不是在HDD上具有索引,则索引扫描会更快,因为在HDD上查找数据比在RAM中执行数据要慢几个数量级。同样适用于全表扫描。 使用InnoDB时控制此变量的变量称为innodb_buffer_pool_size。默认情况下,如果我没有弄错,它的大小为8 MB。我个人将此值设置为高,有时甚至高达可用RAM的90%。通常,当这个值得到优化时 - 很多人都会体验到令人难以置信的速度提升。

另一件事是InnoDB是一个交易引擎。这意味着它会告诉您写入磁盘成功或失败,这将是100%正确。 MyISAM不会这样做,因为它不会强迫操作系统强制硬盘永久提交数据。这就是为什么有时候记录在使用MyISAM时丢失的原因,它认为数据是因为操作系统说实际操作系统试图优化写入而硬盘驱动器可能会丢失缓冲区数据,因此不会写下来。 OS尝试优化写入操作并使用HDD的缓冲区来存储更大的数据块,然后在单个I / O中将其刷新。然后会发生什么事情,你无法控制 数据的写入方式。 使用InnoDB,您可以启动一个事务,执行100个INSERT查询然后提交。这将有效地强制硬盘驱动器使用1个I / O一次刷新所有100个查询。如果每个INSERT长度为4 KB,则其中100个为400 KB。这意味着您将使用400kb的磁盘带宽和1个I / O操作,剩余的I / O将可用于其他用途。这就是插件的优化方式。

接下来是基数低的索引 - 基数是索引列中的一些唯一值。对于主键,此值为1.它也是最高值。基数较低的索引是您有一些不同值的列,例如yesno或类似值。如果索引的基数太低,MySQL将更喜欢全表扫描 - 它的速度要快得多。另外,强制MySQL不想使用的索引可能(并且可能会)减慢速度 - 这是因为当使用索引搜索时,MySQL会逐个处理记录。当它进行表扫描时,它可以一次读取多个记录并避免处理它们。如果这些记录按顺序写在机械磁盘上​​,则可以进一步优化。

TL; DR:

  • 在可以分配足够RAM的服务器上使用InnoDB
  • innodb_buffer_pool_size的值设置得足够大,以便您可以分配更多资源以加快查询速度
  • 尽可能使用SSD
  • 尝试将多个INSERT包装到事务中,以便您可以更好地利用硬盘驱动器的带宽和I / O
  • 避免索引与行数相比具有较低唯一值计数的列 - 它们只是浪费空间(尽管有例外)