【问题标题】:Optimising a large SELECT/JOIN query with millions of rows优化具有数百万行的大型 SELECT/JOIN 查询
【发布时间】:2021-09-24 21:07:53
【问题描述】:

我对 MySQL 5.7 有以下查询,无论我用它做什么,它都在大约一分钟内运行,我试图更快地运行(最好将性能降低到几秒钟)。这些表中的粗略数据量为 200 万条操作、9 万条会话和 10 万条配置文件。

select 
    a.id as `action_id`,
    a.type as `event_type`,
    p.ip_address as `ip_address`,
    p.browser_string as `browser_string`,
    p.ua_device_type,
    p.ua_os_family,
    p.ua_os_name,
    p.ua_type,
    p.ua_family,
    p.ua_version,
    p.ipuas_hash,
    s.session_string,
    s.traffic_source,
    s.org_traffic_source,
    a.datetime as `timestamp`
from is_action as a
join is_session as s on a.session_id=s.id
join is_profile as p on s.profile_id=p.id

正在使用下表(删除了一些不相关的字段):

CREATE TABLE `is_profile` (
`id` int(11) NOT NULL AUTO_INCREMENT,
`ip_address` varchar(255) COLLATE utf8_unicode_ci DEFAULT NULL,
`browser_string` text COLLATE utf8_unicode_ci,
`ua_device_type` varchar(255) COLLATE utf8_unicode_ci DEFAULT NULL,
`ua_os_family` varchar(255) COLLATE utf8_unicode_ci DEFAULT NULL,
`ua_os_name` varchar(255) COLLATE utf8_unicode_ci DEFAULT NULL,
`ua_type` varchar(255) COLLATE utf8_unicode_ci DEFAULT NULL,
`ua_family` varchar(255) COLLATE utf8_unicode_ci DEFAULT NULL,
`ua_version` varchar(255) COLLATE utf8_unicode_ci DEFAULT NULL,
`ipuas_hash` varchar(255) COLLATE utf8_unicode_ci DEFAULT NULL,
`session_count` int(11) NOT NULL DEFAULT '0',
PRIMARY KEY (`id`) USING BTREE,
KEY `user_id` (`user_id`) USING BTREE,
KEY `ua_type` (`ua_type`) USING BTREE
) ENGINE=InnoDB AUTO_INCREMENT=0 DEFAULT CHARSET=utf8 COLLATE=utf8_unicode_ci

CREATE TABLE `is_session` (
`id` int(11) NOT NULL AUTO_INCREMENT,
`profile_id` int(11) NOT NULL DEFAULT '0',
`traffic_source` varchar(255) COLLATE utf8_unicode_ci DEFAULT NULL,
`session_string` varchar(255) COLLATE utf8_unicode_ci DEFAULT NULL,
`datetime` datetime DEFAULT NULL,
`session_length` int(11) NOT NULL DEFAULT '0',
`org_traffic_source` mediumtext COLLATE utf8_unicode_ci,
`org_page` mediumtext COLLATE utf8_unicode_ci,
`action_count` int(11) NOT NULL DEFAULT '0',
PRIMARY KEY (`id`) USING BTREE,
UNIQUE KEY `comp_profile` (`profile_id`,`id`) USING BTREE,
KEY `profile_id` (`profile_id`) USING BTREE,
KEY `datetime` (`datetime`) USING BTREE,
KEY `traffic_source` (`traffic_source`) USING BTREE,
KEY `session_length` (`session_length`) USING BTREE,
CONSTRAINT `fk_profile` FOREIGN KEY (`profile_id`) REFERENCES `is_profile` (`id`) ON DELETE NO ACTION ON UPDATE NO ACTION
) ENGINE=InnoDB AUTO_INCREMENT=0 DEFAULT CHARSET=utf8 COLLATE=utf8_unicode_ci

CREATE TABLE `is_action` (
`id` int(11) NOT NULL AUTO_INCREMENT,
`session_id` int(11) NOT NULL,
`type` varchar(255) COLLATE utf8_unicode_ci DEFAULT NULL,
`details` varchar(255) COLLATE utf8_unicode_ci DEFAULT NULL,
`datetime` datetime NOT NULL,
`weight` int(11) DEFAULT NULL,
PRIMARY KEY (`id`) USING BTREE,
UNIQUE KEY `comp_session` (`session_id`,`id`) USING BTREE,
KEY `session_id` (`session_id`) USING BTREE,
KEY `datetime` (`datetime`) USING BTREE,
CONSTRAINT `fk_session` FOREIGN KEY (`session_id`) REFERENCES `is_session` (`id`) ON DELETE NO ACTION ON UPDATE NO ACTION
) ENGINE=InnoDB AUTO_INCREMENT=0 DEFAULT CHARSET=utf8 COLLATE=utf8_unicode_ci

我尝试了不同类型的连接、强制索引和在相关字段上添加更多索引,甚至增加了数据库的 RAM 和 CPU,但似乎没有什么能减少查询时间。我还附上了下面EXPLAIN 的屏幕截图(被屏蔽的部分是敏感信息)。

任何帮助将不胜感激。

**编辑:**添加了表之间的外键关系并添加了我忘记添加的EXPLAIN图像。

【问题讨论】:

  • 我没有答案,只有观察 - 我们都学习了标准化,它的稳定性,它的一致性。但是,当数据变大时,我们必须退后一步,构建表以匹配查询,并蹑手蹑脚地进入代码必须确保数据一致性的令人担忧的领域。
  • 即使数据库在(相当现代的)PC 上运行,这些数据也不是“大”的。假设它在正确指定的服务器上运行,那么它应该毫无困难地处理包含数百万行的 10 甚至 100 行的表
  • 观察:在 InnoDB 中,我认为我的说法是正确的,任何 AI id 都会自动静默添加到所有索引的末尾,因此 comp_profile (profile_id,id),KEY profile_idKEY profile_id 相同。
  • 您正在返回包含大量列的数以百万计的行。这需要时间。
  • @JSBach - 不完全是。请参阅我的“答案”。

标签: mysql sql performance join


【解决方案1】:

(不是答案,而是对 JSBach 的冗长回复。)

这是一个奇怪的组合

PRIMARY KEY (`id`) USING BTREE,
UNIQUE KEY `comp_session` (`session_id`,`id`) USING BTREE,
KEY `session_id` (`session_id`) USING BTREE,

如果您希望 session_id 是唯一的,那么您失败了。使其独一无二:

PRIMARY KEY (`id`) USING BTREE,
UNIQUE KEY `comp_session` (`session_id`) USING BTREE,
-- redundant: KEY `session_id` (`session_id`) USING BTREE,

到那时,为什么不将session_id提升为PK?

返回 UNIQUE (session_id,id) -- 因为 id 已经声明为 UNIQUEbecause of the PK, this declaration provides _no_ useful uniqueness constraint. If it is primarily designed to beKEY(session_id, id), then it is redundant with KEY(session_id)`;摆脱其中一个。

PRIMARY KEY:

  • 唯一标识每一行
  • 声明列(或列组合)是唯一的
  • 是索引

UNIQUE:

  • 声明列(或列组合)是唯一的
  • 是索引

FOREIGN KEY:

  • 如果索引不存在,则声明索引
  • 提供完整性约束,当您插入另一表中没有对应行的行时提醒您
  • 可选择提供“级联”操作

(对于 OP 关于速度的问题,请参阅 Gordon 的评论。)

【讨论】:

    【解决方案2】:

    您需要在连接中使用的 FK 上定义外键约束。

    您似乎忘记了包含解释计划。

    顺便说一句,您的独特约束并没有取得太大成就,因为它们包括根据定义已经独特的 PK

    【讨论】:

    • 糟糕,忘记附上图片了。我已将外键添加到表中,并按照您的要求附上了说明。
    • 为了清楚起见,FK 是否已经存在但您没有将它们包含在您的问题中,或者您只是将它们添加到您的表中?如果您添加了它们,那么这对查询执行有什么影响?
    • 刚刚添加了它们,它们似乎并没有太大的区别。
    • 好的 - 在这种情况下,限制是您运行查询的平台的规范,您不会让它运行得更快。因为它是一个没有过滤的简单查询,所以唯一可以调整的是连接,并且由于您在其上定义了 FK,因此您无法调整其他任何内容。
    • 你不需要 fk约束
    猜你喜欢
    • 2015-08-24
    • 1970-01-01
    • 2023-01-05
    • 1970-01-01
    • 2018-12-01
    • 2015-06-27
    • 1970-01-01
    • 1970-01-01
    • 2018-01-01
    相关资源
    最近更新 更多