我应该重新设计我的“事件”表

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)

结果查询会很快并且不需要全表扫描吗?如果是这样,这似乎是一个更好的设置。

Ric*_*mes 5

INDEX(result, event_type, event_timestamp)消除了对INDEX(result)和的需要INDEX(result, event_type)

盲目使用(255)会伤害索引和查询。修剪回现实的限制。

按照您建议的方式拆分表格没有任何帮助,并且会损害大多数查询。特别是,您将无法有效地使用多个索引,也无法使用“复合”索引(因为它会涉及多个表)。另一方面,如果您的“日志”变得太大,那么这种“标准化”将大大减少磁盘占用空间。这本身对性能有一些积极的影响。

难道不是正常化“连续”的值,如timestamp。这会严重损害性能,因为在“范围”值上建立索引变得不切实际。

LEFT除非您需要“正确”的表是可选的,否则不要使用。 LEFT有时意味着必须首先扫描“左”表。在您的示例中,这将导致对events.

如果您更改LEFT JOINJOIN(在最后一个示例中),则优化器将在表中进行选择以决定从哪个表开始。这与相关列上的单列索引的原始情况等效(但速度较慢)。

“低基数”列 (statusresult) 本身几乎没有用索引。它们在“复合”索引的第一列时有效。

大多数表的实际应用的查询数量有限。如果你说你的表需要很多不同的查询,那么我的建议如下:

  • 监控人们想要的查询。跟踪列的典型组合。
  • 实现一些 2 列和 3 列索引。
  • 确保索引中的第一列是用 测试(在WHERE)的列=。如果还有一个“范围”(如您的示例中的timestamp),则将其放在最后。(在多个范围测试中下注。)
  • 请记住,ANDinginWHERE中的事物顺序无关紧要,但 anINDEX中的列顺序却很重要。
  • 有关创建最佳索引的更多信息:http : //mysql.rjweb.org/doc.php/index_cookbook_mysql

MySQL 没有实现“位图”索引;他们很少值得付出努力。MySQL 确实实现了“索引合并相交”(for ANDs),这是一种模拟复合索引的笨拙方式。“索引合并联合”(for ORs)有时很方便OR;但UNION很可能一样好。

你的看起来像是一个“数据仓库”应用程序。最好的加速方法是构建和维护汇总表。对于您的一个示例,每日计数的摘要按resultevent_type将小得多,查询速度也快得多。(10 倍加速是很有可能的。)此外,在摘要表上有不同的索引是可行的,从而在一定程度上打破了您当前的日志堵塞。(您可以SUM通过小计获得总计COUNT。)