在巨大的表上添加有效索引

时间:2017-02-27 11:06:53

标签: mysql multithreading indexing

我有一个超过34M行(并且还在增长)的MySQL数据库表。

CREATE TABLE `sensordata` (
  `userID` varchar(45) DEFAULT NULL,
  `instrumentID` varchar(10) DEFAULT NULL,
  `utcDateTime` datetime DEFAULT NULL,
  `dateTime` datetime DEFAULT NULL,
  `data` varchar(200) DEFAULT NULL,
  `dataState` varchar(45) NOT NULL DEFAULT 'Original',
  `gps` varchar(45) DEFAULT NULL,
  `location` varchar(45) DEFAULT NULL,
  `speed` varchar(20) NOT NULL DEFAULT '0',
  `unitID` varchar(5) NOT NULL DEFAULT '1',
  `parameterID` varchar(5) NOT NULL DEFAULT '1',
  `originalData` varchar(200) DEFAULT NULL,
  `comments` varchar(45) DEFAULT NULL,
  `channelHashcode` varchar(12) DEFAULT NULL,
  `settingHashcode` varchar(12) DEFAULT NULL,
  `status` varchar(7) DEFAULT 'Offline',
  `id` int(11) NOT NULL AUTO_INCREMENT,
  PRIMARY KEY (`id`),
  UNIQUE KEY `id_UNIQUE` (`id`)
) ENGINE=InnoDB AUTO_INCREMENT=98772 DEFAULT CHARSET=utf8

我每分钟从多个线程(至少400个线程)访问此表,以将数据插入表中。 随着表的增长,读取和写入数据的速度变慢了。一个SELECT查询过去需要大约25秒,然后我添加了一个唯一索引

UNIQUE INDEX idx_userInsDate ( userID,instrumentID,utcDateTime)

这将读取时间从25秒减少到几毫秒,但由于必须更新每条记录的索引,因此增加了插入时间。 另外,如果我从多个线程运行SELECT查询,同时查询需要很长时间才能返回数据。

这是一个示例查询

Select dateTime from sensordata WHERE userID = 'someUserID' AND instrumentID = 'someInstrumentID' AND dateTime between 'startDate' AND 'endDate' order by dateTime asc;

请有人帮助我,改进表架构或添加有效索引以提高性能。

提前谢谢

3 个答案:

答案 0 :(得分:1)

首先:避免索引的varchars,特别是ID。 varchar中的每个字符位置都在内部生成一个自己的索引条目!

2nd:你的select使用dateTime,你的索引设置为utcDateTime。它只接受userID和instrumentID并忽略utcDateTime-Part。

建议:更改id的数据类型并更改索引以匹配查询(dateTime,而不是utcDateTime)

使用索引会降低插入时的性能,不幸的是,现在没有像mysql中索引的填充因子那样。因此,您可以做的最好的事情是尝试尽可能小的索引。

具有随机访问权限的高负载数据库的另一种方法是:写入未编制索引的表,从索引表中读取。在给定的时间,构建索引并交换表(可能需要第三个表来创建索引,而其他的则保持不变)。

答案 1 :(得分:1)

这里的指数不是错误的。它是您的数据类型。随着磁盘上数据的大小增加,所有操作的速度都会降低。索引当然可以帮助加快选择 - 只要你的数据结构合理 - 但似乎它不是

CREATE TABLE `sensordata` (
  `userID` int,  /* shouldn't this have a foreign key constraint? */
  `instrumentID` int,
  `utcDateTime` datetime DEFAULT NULL,
  `dateTime` datetime DEFAULT NULL,
/* what exactly are you putting here? Are you sure it's not causing any reduncy? */
  `data` varchar(200) DEFAULT NULL, 
 /* your states will be a finite number of elements. They can be represented by constants in your code or a set of values in a related table */
  `dataState` int,
/* what's this? Sounds like what you are saving in location */
  `gps` varchar(45) DEFAULT NULL,
  `location` point,
  `speed` float,
  `unitID` int DEFAULT '1',
/* as above */
  `parameterID` int NOT NULL DEFAULT '1',
/* are you sure this is different from data? */
  `originalData` varchar(200) DEFAULT NULL,
  `comments` varchar(45) DEFAULT NULL,
  `channelHashcode` varchar(12) DEFAULT NULL,
  `settingHashcode` varchar(12) DEFAULT NULL,
/* as above and isn't this the same as */
  `status` int,
  `id` int(11) NOT NULL AUTO_INCREMENT,
  PRIMARY KEY (`id`),
  UNIQUE KEY `id_UNIQUE` (`id`)
) ENGINE=InnoDB AUTO_INCREMENT=98772 DEFAULT CHARSET=utf8

答案 2 :(得分:1)

PRIMARY KEY UNIQUE密钥。抛弃多余的UNIQUE(id)

是否id被其他任何表引用?如果没有,那么一起摆脱它。而只是

PRIMARY KEY ( userID, instrumentID, utcDateTime)

if 该三元组保证是唯一的。您提到了DST - 使用数据类型TIMESTAMP而不是DATETIME。这样做,如果需要,您可以转换为DATETIME,从而消除其中一列。

一个索引(PK)几乎没有空间,因为它是"聚集的"使用InnoDB中的数据。

你的桌子非常胖,所有VARCHARs。例如,status可以缩减为1字节的ENUM。其他人可以正常化。 speed之类的内容可以是4字节FLOAT或更小DECIMAL,具体取决于您需要的范围和精度。

对于34M宽的行,您最近可能已经超出了RAM的可缓存性。通过使行更窄,您将推迟溢出。

为什么要攻击索引?在允许插入行之前,会检查每个UNIQUE(包括PRIMARY)索引。通过将其降至1指数,可以最大限度地降低成本。 (InnoDB确实需要PRIMARY KEY。)

INT是4个字节。你有十亿个乐器吗?也许instrumentID可能是SMALLINT UNSIGNED,这是2个字节,最大为64K?考虑所有其他ID。

你有400 INSERTs /分钟,对吗?那不错。如果你达到400 /秒,我们需要有不同的谈话。

("填充因子"在MySQL中不可调,因为它没有太大区别。)

你有多少内存? innodb_buffer_pool_size的设置是什么?最佳值约为可用 RAM的70%。

让我们看看你的主要问题;可能还有其他问题需要解决。