Dav*_*ave 2 mysql database-design optimization
我的系统需要存储一个仅附加的事件日志。目前我有一个数据库表,将所有相关数据存储在一个表中:
CREATE TABLE `events` (
`event_id` VARCHAR(255) NOT NULL PRIMARY KEY,
`event_type` VARCHAR(255) NOT NULL,
`event_timestamp` DATETIME,
`group_id` VARCHAR(255),
`person_id` VARCHAR(255),
`client_id` VARCHAR(255),
`name` VARCHAR(768),
`result` VARCHAR(255),
`status` VARCHAR(255),
`logged_at` DATETIME,
`severity` VARCHAR(255),
`message` LONGTEXT,
INDEX `event_type_index` (`event_type`),
INDEX `event_timestamp_index` (`event_timestamp`),
INDEX `group_id_index` (`group_id`),
INDEX `person_id_index` (`person_id`),
INDEX `client_id_index` (`client_id`),
INDEX `name_index` (`name`),
INDEX `result_index` (`result`),
INDEX `status_index` (`status`),
INDEX `logged_at_index` (`logged_at`),
) ENGINE=InnoDB DEFAULT CHARACTER SET=utf8mb4 COLLATE=utf8_general_ci
Run Code Online (Sandbox Code Playgroud)
但是我注意到在 WHERE 子句中具有多个属性的查询仍然很慢。例如:
SELECT
count(e.event_id) as total
FROM events e
WHERE
e.result='Success' AND
e.event_type='some_silly_event' AND
e.event_timestamp > '2019-01-01 00:00:00'
Run Code Online (Sandbox Code Playgroud)
一种解决方案是创建一个如下所示的索引:
CREATE INDEX successful_silly_events
ON events (result,event_type,event_timestamp);
Run Code Online (Sandbox Code Playgroud)
这种方法的缺点似乎是创建索引需要很长时间,而且只会加速这个查询。如果我用不同的列在这个表上创建一个不同的查询,我会回到第一个。
如果从一开始就将事件表拆分为多个表,我会得到更好的服务吗?例如:
CREATE TABLE `events` (
`event_id` VARCHAR(255) NOT NULL,
`logged_at` DATETIME,
`severity` VARCHAR(255),
`message` LONGTEXT,
PRIMARY KEY (event_id),
INDEX `logged_at_index` (`logged_at`),
) ENGINE=InnoDB DEFAULT CHARACTER SET=utf8mb4 COLLATE=utf8_general_ci
CREATE TABLE `event_types` (
`event_id` VARCHAR(255) NOT NULL,
`event_type` VARCHAR(255) NOT NULL,
PRIMARY KEY event_id REFERENCES events(event_id)
INDEX `event_type_index` (`event_type`),
) ENGINE=InnoDB DEFAULT CHARACTER SET=utf8mb4 COLLATE=utf8_general_ci
CREATE TABLE `event_timestamps` (
`event_id` VARCHAR(255) NOT NULL,
`event_timestamp` VARCHAR(255) NOT NULL,
PRIMARY KEY event_id REFERENCES events(event_id)
INDEX `event_timestamp_index` (`event_timestamp`),
) ENGINE=InnoDB DEFAULT CHARACTER SET=utf8mb4 COLLATE=utf8_general_ci
CREATE TABLE `event_groups` (
`event_id` VARCHAR(255) NOT NULL,
`group_id` VARCHAR(255) NOT NULL,
PRIMARY KEY event_id REFERENCES events(event_id)
INDEX `group_id_index` (`group_id`),
) ENGINE=InnoDB DEFAULT CHARACTER SET=utf8mb4 COLLATE=utf8_general_ci
Run Code Online (Sandbox Code Playgroud)
对于我通常会在事件表上编入索引的所有其他事件属性,依此类推。这样,我可以构造一个类似的查询:
SELECT
count(e.event_id) as total
FROM events e
LEFT JOIN event_results er ON e.event_id=er.event_id
LEFT JOIN event_types ety ON e.event_id=et.event_id
LEFT JOIN event_timestamps eti ON e.event_id=et.event_id
WHERE
er.result='Success' AND
ety.event_type='some_silly_event' AND
eti.event_timestamp > '2019-01-01 00:00:00'
Run Code Online (Sandbox Code Playgroud)
结果查询会很快并且不需要全表扫描吗?如果是这样,这似乎是一个更好的设置。
INDEX(result, event_type, event_timestamp)
消除了对INDEX(result)
和的需要INDEX(result, event_type)
。
盲目使用(255)
会伤害索引和查询。修剪回现实的限制。
按照您建议的方式拆分表格没有任何帮助,并且会损害大多数查询。特别是,您将无法有效地使用多个索引,也无法使用“复合”索引(因为它会涉及多个表)。另一方面,如果您的“日志”变得太大,那么这种“标准化”将大大减少磁盘占用空间。这本身对性能有一些积极的影响。
难道不是正常化“连续”的值,如timestamp
。这会严重损害性能,因为在“范围”值上建立索引变得不切实际。
LEFT
除非您需要“正确”的表是可选的,否则不要使用。 LEFT
有时意味着必须首先扫描“左”表。在您的示例中,这将导致对events
.
如果您更改LEFT JOIN
为JOIN
(在最后一个示例中),则优化器将在表中进行选择以决定从哪个表开始。这与相关列上的单列索引的原始情况等效(但速度较慢)。
“低基数”列 (status
和result
) 本身几乎没有用索引。它们在“复合”索引的第一列时有效。
大多数表的实际应用的查询数量有限。如果你说你的表需要很多不同的查询,那么我的建议如下:
WHERE
)的列=
。如果还有一个“范围”(如您的示例中的timestamp
),则将其放在最后。(在多个范围测试中下注。)ANDing
inWHERE
中的事物顺序无关紧要,但 anINDEX
中的列顺序却很重要。MySQL 没有实现“位图”索引;他们很少值得付出努力。MySQL 确实实现了“索引合并相交”(for ANDs
),这是一种模拟复合索引的笨拙方式。“索引合并联合”(for ORs
)有时很方便OR
;但UNION
很可能一样好。
你的看起来像是一个“数据仓库”应用程序。最好的加速方法是构建和维护汇总表。对于您的一个示例,每日计数的摘要按result
和event_type
将小得多,查询速度也快得多。(10 倍加速是很有可能的。)此外,在摘要表上有不同的索引是可行的,从而在一定程度上打破了您当前的日志堵塞。(您可以SUM
通过小计获得总计COUNT
。)
归档时间: |
|
查看次数: |
138 次 |
最近记录: |