假设我在MySQL中有以下表格:
CREATE TABLE `events` (
`pv_name` varchar(60) COLLATE utf8mb4_unicode_ci NOT NULL,
`time_stamp` bigint(20) unsigned NOT NULL,
`event_type` varchar(40) COLLATE utf8mb4_unicode_ci NOT NULL,
`value` text CHARACTER SET utf8mb4 COLLATE utf8mb4_bin,
`value_type` varchar(40) COLLATE utf8mb4_unicode_ci DEFAULT NULL,
`value_count` bigint(20) DEFAULT NULL,
`alarm_status` varchar(40) COLLATE utf8mb4_unicode_ci DEFAULT NULL,
`alarm_severity` varchar(40) COLLATE utf8mb4_unicode_ci DEFAULT NULL,
PRIMARY KEY (`pv_name`,`time_stamp`)
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_unicode_ci ROW_FORMAT=COMPRESSED;
有没有办法用索引或其他方法改进以下查询?
SELECT DISTINCT events.pv_name
FROM events
WHERE events.time_stamp > t0_in AND events.time_stamp < t1_in
AND (events.value IS NULL OR events.alarm_severity = 'INVALID');
t0_in
和t1_in
是传递给定义查询的存储过程的参数。
使用EXPLAIN运行查询时:
+----+-------------+--------+-------+---------------+---------+---------+------+----------+-------------+
| id | select_type | table | type | possible_keys | key | key_len | ref | rows | Extra |
+----+-------------+--------+-------+---------------+---------+---------+------+----------+-------------+
| 1 | SIMPLE | events | index | PRIMARY | PRIMARY | 250 | NULL | 12724016 | Using where |
+----+-------------+--------+-------+---------------+---------+---------+------+----------+-------------+
在数据库上运行查询,在1分50.93秒内返回102620行
更新
为简单起见,假设表格如下:
CREATE TABLE `events` (
`pv_name` varchar(60) COLLATE utf8mb4_unicode_ci NOT NULL,
`time_stamp` bigint(20) unsigned NOT NULL,
`value_valid` tinyint(1) NOT NULL,
PRIMARY KEY (`pv_name`,`time_stamp`)
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_unicode_ci ROW_FORMAT=COMPRESSED;
是否可以添加适当的索引,以便以下或等效查询使用松散的索引扫描优化?
SELECT DISTINCT events.pv_name
FROM events
WHERE events.time_stamp > t0_in AND events.time_stamp < t1_in
AND events.value_valid = 0);
更新
如果我在time_stamp
上添加索引,我会:
mysql> EXPLAIN SELECT DISTINCT events.pv_name FROM events WHERE events.time_stamp > 0 AND events.time_stamp < 11426224880000000000 AND (events.value IS NULL OR events.alarm_severity = 'INVALID');
+----+-------------+--------+-------+--------------------+---------+---------+------+----------+-------------+
| id | select_type | table | type | possible_keys | key | key_len | ref | rows | Extra |
+----+-------------+--------+-------+--------------------+---------+---------+------+----------+-------------+
| 1 | SIMPLE | events | index | PRIMARY,time_stamp | PRIMARY | 250 | NULL | 13261211 | Using where |
+----+-------------+--------+-------+--------------------+---------+---------+------+----------+-------------+
在数据库上运行此查询将在30.44秒内返回11511行。
mysql> EXPLAIN SELECT DISTINCT events.pv_name FROM events FORCE INDEX (time_stamp) WHERE events.time_stamp > 0 AND events.time_stamp < 11426224880000000000 AND (events.value IS NULL OR events.alarm_severity = 'INVALID');
+----+-------------+--------+-------+--------------------+------------+---------+------+---------+-----------------------------------------------------+
| id | select_type | table | type | possible_keys | key | key_len | ref | rows | Extra |
+----+-------------+--------+-------+--------------------+------------+---------+------+---------+-----------------------------------------------------+
| 1 | SIMPLE | events | range | PRIMARY,time_stamp | time_stamp | 8 | NULL | 6630605 | Using index condition; Using where; Using temporary |
+----+-------------+--------+-------+--------------------+------------+---------+------+---------+-----------------------------------------------------+
在数据库上运行此查询会在2分20.41秒内返回11511行。
更新
根据建议我将表格改为:
CREATE TABLE `events` (
`pv_name` varchar(60) COLLATE utf8mb4_unicode_ci NOT NULL,
`time_stamp` bigint(20) unsigned NOT NULL,
`event_type` enum('add','init','update','disconnect','remove') COLLATE utf8mb4_unicode_ci NOT NULL,
`value` text CHARACTER SET utf8mb4 COLLATE utf8mb4_bin,
`value_type` varchar(40) COLLATE utf8mb4_unicode_ci DEFAULT NULL,
`value_count` bigint(20) DEFAULT NULL,
`alarm_status` enum('NO_ALARM','READ','WRITE','HIHI','HIGH','LOLO','LOW','STATE','COS','COMM','TIMEOUT','HWLIMIT','CALC','SCAN','LINK','SOFT','BAD_SUB','UDF','DISABLE','SIMM','READ_ACCESS','WRITE_ACCESS') COLLATE utf8mb4_unicode_ci DEFAULT NULL,
`alarm_severity` enum('NO_ALARM','MINOR','MAJOR','INVALID') COLLATE utf8mb4_unicode_ci DEFAULT NULL,
PRIMARY KEY (`pv_name`,`time_stamp`),
KEY `event_type` (`event_type`,`time_stamp`),
KEY `alarm_severity` (`alarm_severity`,`time_stamp`)
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_unicode_ci ROW_FORMAT=COMPRESSED;
和查询:
SELECT DISTINCT events.pv_name
FROM events
WHERE events.time_stamp > 0 AND events.time_stamp < 1426224880000000000
AND alarm_severity = 'INVALID'
UNION
SELECT DISTINCT events.pv_name
FROM events
WHERE events.time_stamp > 0 AND events.time_stamp < 1426224880000000000
AND event_type = 'add'
UNION
SELECT DISTINCT events.pv_name
FROM events
WHERE events.time_stamp > 0 AND events.time_stamp < 1426224880000000000
AND event_type = 'disconnect'
UNION
SELECT DISTINCT events.pv_name
FROM events
WHERE events.time_stamp > 0 AND events.time_stamp < 1426224880000000000
AND event_type = 'remove';
对查询运行说明给出:
+----+--------------+----------------+-------+-----------------------------------+----------------+---------+------+--------+-------------------------------------------+
| id | select_type | table | type | possible_keys | key | key_len | ref | rows | Extra |
+----+--------------+----------------+-------+-----------------------------------+----------------+---------+------+--------+-------------------------------------------+
| 1 | PRIMARY | events | range | PRIMARY,event_type,alarm_severity | alarm_severity | 10 | NULL | 101670 | Using where; Using index; Using temporary |
| 2 | UNION | events | range | PRIMARY,event_type,alarm_severity | event_type | 9 | NULL | 994652 | Using where; Using index; Using temporary |
| 3 | UNION | events | range | PRIMARY,event_type,alarm_severity | event_type | 9 | NULL | 73660 | Using where; Using index; Using temporary |
| 4 | UNION | events | range | PRIMARY,event_type,alarm_severity | event_type | 9 | NULL | 136348 | Using where; Using index; Using temporary |
| NULL | UNION RESULT | <union1,2,3,4> | ALL | NULL | NULL | NULL | NULL | NULL | Using temporary |
+----+--------------+----------------+-------+-----------------------------------+----------------+---------+------+--------+-------------------------------------------+
在数据库上运行查询会在1分钟内返回112620行2.45秒
答案 0 :(得分:1)
如果您的数据没有太多数据,这不是非常具体,但我希望您仍然会觉得它很有用。
为了保持最佳性能,您应该始终确保您的索引适合您的RAM。通常情况可能如此,但当表格开始处于数百万行的数量级时,值得一看。你可以在SO question上找到很多关于如何处理的信息。它为什么如此重要 ?好吧,我不知道它是如何在内部工作的,但索引很可能存储在硬盘上,这将是太棒了。或者它也可以刷新索引的第一部分然后加载剩余的RAM等等。无论如何,它会很长,如果你可以简单地避免它(通过增加引擎可以使用的RAM),就这样做。
您已经使用了主键这是一件好事,但您也可以使用分区。这个想法非常简单,它不会将其存储在单个表中,而是自动相当于只包含某些值范围的子表(它比这更复杂,但是让我们这样做了。说现在的价值范围)。使用SELECT,UPDATE或DELETE时,它们都是透明的,因此您的请求不会涉及重构。我建议你看看这个非常简洁的演示文稿about partitions。文档在这方面也非常棒。例如,您将看到可以使用不同大小的分区。例如,如果您根据时间戳进行分区,并且您知道最新数据的访问方式比旧数据更频繁,则可以在过去7天内创建7个分区,然后在前4周内创建4个分区,然后再创建12个分区。过去12个月的分区等等。但是这需要对你的结论进行一些分析。
对于前一点并且因为它更清洁,我强烈建议将时间戳的bigint
类型更改为真实的日期/时间mysql类型,如@Huy Nguyen建议的那样。作为结束语,他关于alarm_status
和alarm_severity
的评论很好,如果这只能取一组定义的值,你应该切换到int类型,这样可以更有效地使用它们在密钥和分区中。
关于您的更新,我并不是非常精通宽松的索引扫描优化,但在value_valid, time_stamp
上添加密钥似乎减少了使用的行数(来自explain命令)并且系统地选择的密钥(而不是已定义的主键)。我的数据集非常少,因此值得尝试一下您的数据。要使用您已定义的主键来对数字进行通话,我会在示例查询中key_len: 250, rows:242
,使用我的附加键:key_len:9, rows:106
答案 1 :(得分:0)
您应该在
上添加索引events.time_stamp
也可能对
上的索引有用events.alarm_severity
答案 2 :(得分:0)
同时添加
INCLUDE(events.pv_name)
到索引所以它不会进行表扫描
答案 3 :(得分:0)
一些可能的提示,按理论上的改进:
尝试MYSQL在SELECT之前锁定表,然后解锁 表。我相信锁定表会加速它,因为它没有 必须担心在选择时对表的更新,以及为此 可以更有效地获取数据。
我认为在BEGIN / COMMIT事务序列中使用它可能会在某些情况下提高速度,但通常使用INSERT / UPDATES而不是SELECTS。
也可以帮助制作这些索引:time_stamp,value, alarm_severity。
如果可能,将alarm_severity从varchar(40)更改为char(40)。 CHAR比VARCHAR更快搜索,但占用更多空间。 或者将alarm_severity更改为整数而不是字符串 可以更快地编入索引。或者添加一个附加字段 像alarm_severity_code这样的整数对应物会更快 索引和搜索。
您为alarm_severity创建的索引可以限制为10 人物左右。我相信它会使搜索更快 (取决于您的数据集),但仍然允许最多40个字符 领域。如果这些值类似于'INVALID',那么10应该是好的 足以索引。
也许添加一个可以索引的“has_value”字段,而不是搜索 其中value为NULL,因为value不可索引。这需要 在添加/编辑记录时分配值。
time_stamp真的需要成为big_int吗?它可能更多 高效只是为了使用时间戳数据类型。
是否必须是ROW_FORMAT = COMPRESSED?这听起来像是 将其减慢以在查询数据时解压缩数据。
因此建议的表结构可能如下:
CREATE TABLE IF NOT EXISTS `events` (
`pv_name` varchar(60) COLLATE utf8mb4_unicode_ci NOT NULL,
`time_stamp` timestamp NOT NULL DEFAULT '0000-00-00 00:00:00',
`event_type` varchar(40) COLLATE utf8mb4_unicode_ci NOT NULL,
`has_value` int(11) NOT NULL DEFAULT '0',
`value` text CHARACTER SET utf8mb4 COLLATE utf8mb4_bin,
`value_type` varchar(40) COLLATE utf8mb4_unicode_ci DEFAULT NULL,
`value_count` bigint(20) DEFAULT NULL,
`alarm_status` varchar(40) COLLATE utf8mb4_unicode_ci DEFAULT NULL,
`alarm_severity` char(40) COLLATE utf8mb4_unicode_ci DEFAULT NULL,
PRIMARY KEY (`pv_name`,`time_stamp`),
KEY `time_stamp` (`time_stamp`),
KEY `alarm_severity` (`alarm_severity`(10)),
KEY `has_value` (`has_value`)
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_unicode_ci ROW_FORMAT=COMPRESSED;
它确实降低了磁盘空间与速度的关系。您还可以将数据集分解为单独的表,一个用于特定值或某个alarm_severity,因此每个查询都可以在较小的表上。
答案 4 :(得分:0)
绩效改进
&#34;索引扫描&#34;反对PRIMARY
,所以它实际上是一个表扫描,这是最慢的方式。
你需要
INDEX(time_stamp)
PRIMARY KEY(pv_name, time_stamp)
没有用,因为前导字段(pv_name
)对WHERE
或GROUP BY
或ORDER BY
。
警告:如果无法切换到新索引,您可能需要使用&#34;准备&#34;在SP。
alarm_severity
上的索引无济于事,因为它隐藏在OR
。
你可以交换PRIMARY KEY
中字段的顺序,但这可能会影响其他查询,而ALTER
会花费很长时间。< / p>
更好的改进(除了它不会起作用)
由于OR
,WHERE
的这一部分无法优化:
AND (events.value IS NULL OR
events.alarm_severity = 'INVALID')
有一个希望:将OR
变为UNION
:
( SELECT DISTINCT events.pv_name
FROM events
WHERE events.time_stamp > t0_in
AND events.time_stamp < t1_in
AND events.value IS NULL
)
UNION DISTINCT
( SELECT DISTINCT events.pv_name
FROM events
WHERE events.time_stamp > t0_in
AND events.time_stamp < t1_in
AND events.alarm_severity = 'INVALID'
);
并添加
INDEX(alarm_severity, time_stamp) -- in that order
INDEX(value , time_stamp) -- in that order
但是这是一个很大但是没有成功,因为value
是TEXT
。如果value
可以更改为VARCHAR(191)
,那么它就可以了。更好的是ENUM
。 (不,&#34;前缀索引&#34;不够聪明。)
<强>辩驳强>
是的,索引应该适合RAM。但通常你没有选择权。
PARTITIONing
很少有用。在这种情况下,我不认为 。
我可以将event_type,value_type,alarm_status和alarm_severity更改为枚举类型。
做吧!假设这是一个非常大的表,那会大大缩小表的大小,使其更快 - 特别是如果它现在受到I / O限制。
不同大小的 PARTITIONs
- 这很好,但是有一个&#34;问题&#34;当你需要4个星期到1个月(或其他)。它有效地阻止了整合期间的活动。并且,由于其他(性能)原因需要不超过大约50个分区,因此最终将需要卷起来。
innodb_buffer_pool_size
应设置为可用 RAM的70%左右。这是最重要的可调参数。
纳秒 - 检查数据;我怀疑你有重复。虽然应该足够精确,但提供时钟的算法是什么?它可能允许重复。 (我不太担心它的8个字节。)
对于InnoDB,请在适当的情况下使用BEGIN
... COMMIT
进行交易完整性。请勿使用LOCK TABLES
。
value
和alarm_severity
上的各个索引对此查询无效。 (但time_stamp
很有用。)
&#34;将varchar(40)更改为char(40)&#34; - 不!几乎没有CHAR
更好的情况。而不是在这种情况下。
KEY alarm_severity (alarm_severity(10))
- 前缀索引几乎从来没有用处。特别是当它是VARCHAR
时,价值通常都很短。
答案 5 :(得分:0)
加速大型表上的大型查询的另一种方法是构建和维护一个&#34; Summary表&#34;。
让我们说你通常想看看&#34;小时&#34; (而不是几天或几个月等)。此查询的摘要表(以及许多其他查询)类似于
CREATE TABLE foo (
hr MEDIUMINT UNSIGNED NOT NULL, -- derived from time_stamp; see below
alarm_severity ... -- preferably an ENUM, not VARCHAR
event_type ...
pv_name ...
ct INT UNSIGNED -- if you want to know how many
PRIMARY KEY(hr, alarm_severity, event_type)
) ENGINE=InnoDB;
每小时结束后:
INSERT INTO foo
SELECT FLOOR(time_stamp / 3600e9),
alarm_severity, event_type, pv_name,
COUNT(*)
FROM events
WHERE time_stamp >= ... -- start of previous hour
AND time_stamp < ... -- and end
GROUP BY 1,2,3,4;
然后原始查询变为
SELECT DISTINCT pv_name
FROM foo
WHERE hr >= t0_in / 3600e9
AND hr < t1_in / 3600e9
AND ( alarm_severity = 'INVALID'
OR event_type IN ('add', 'disconnect', 'remove')
);
最终SELECT
将轻松不到1秒。但它要求插入后数据不会改变等等。
您之前有AND value IS NULL
。这可能会添加到INSERT..SELECT
中,或者您可能需要value_is_null
作为foo
及其PK中的真/假标记。