【问题标题】:Mysql stored functions and groupwise min [closed]Mysql存储函数和分组最小值[关闭]
【发布时间】:2015-04-21 06:04:06
【问题描述】:

架构

数据库架构已简化
事件表
此表存储事件。

CREATE TABLE `Events` (
`event_id` bigint(20) unsigned NOT NULL AUTO_INCREMENT,
`isPublic` tinyint(1) NOT NULL DEFAULT '1',
PRIMARY KEY (`event_id`)
) ENGINE=InnoDB DEFAULT CHARSET=latin1;

地方表
存储位置的简单表。一个事件可以在多个地方进行。

CREATE TABLE `Places` (
`place_id` bigint(20) unsigned NOT NULL AUTO_INCREMENT,
`latitude` double NOT NULL,
`longitude` double NOT NULL,
PRIMARY KEY (`place_id`),
KEY `latind` (`latitude`,`longitude`)
) ENGINE=InnoDB CHARSET=latin1;

规则表
存储事件时间表的表。一个事件可以有多个时间表。所有日期均采用 unixtimestamp 格式。 Regular 表示此规则有一些重复的时间表,存储在 RegularRules 表中。

CREATE TABLE `Rules` (
`rule_id` bigint(20) unsigned NOT NULL AUTO_INCREMENT,
`start_date` int(11) NOT NULL,
`end_date` int(11) NOT NULL,
`regular` tinyint(1) NOT NULL DEFAULT '0',
PRIMARY KEY (`rule_id`),
KEY `endindx` (`end_date`)
) ENGINE=InnoDB CHARSET=latin1;

常规规则
以下列格式存储可重复计划的表。 day_start/end 表示从一天的开始 (00:00) 到事件开始的秒数。例如,事件发生在每周一的 10:00 到 18:00。我们会将start_dateend_date 存储在规则表中,这些值代表事件的时间限制。在 RegularRules 表中,mon_start 中有 36000 个,mon_end 中有 64800 个。

CREATE TABLE `RegularRules` (
`repetition_id` bigint(11) unsigned NOT NULL AUTO_INCREMENT,
`rule_id` bigint(20) unsigned NOT NULL,
`mon_start` int(11) DEFAULT NULL,
`tue_start` int(11) DEFAULT NULL,
`wed_start` int(11) DEFAULT NULL,
`th_start` int(11) DEFAULT NULL,
`fr_start` int(11) DEFAULT NULL,
`sat_start` int(11) DEFAULT NULL,
`sun_start` int(11) DEFAULT NULL,
`mon_end` int(11) DEFAULT NULL,
`tue_end` int(11) DEFAULT NULL,
`wed_end` int(11) DEFAULT NULL,
`th_end` int(11) DEFAULT NULL,
`fr_end` int(11) DEFAULT NULL,
`sat_end` int(11) DEFAULT NULL,
`sun_end` int(11) DEFAULT NULL,
PRIMARY KEY (`repetition_id`),
KEY `fk_rule_id_regularrules_idx` (`rule_id`),
CONSTRAINT `fk_rule_id_regularrules` FOREIGN KEY (`rule_id`) 
REFERENCES `Rules` (`rule_id`) ON DELETE CASCADE ON UPDATE NO ACTION
) ENGINE=InnoDB  CHARSET=latin1;

活动-地点-规则
连接上述所有表的表。

CREATE TABLE EPR (
`holding_id` bigint(30) NOT NULL AUTO_INCREMENT,
`event_id` bigint(20) unsigned NOT NULL,
`place_id` bigint(20) unsigned NOT NULL,
`rule_id` bigint(20) unsigned NOT NULL,
PRIMARY KEY (`holding_id`),
UNIQUE KEY `compound` (`place_id`,`event_id`,`rule_id`),
KEY `FK_Places-Company Events-Rules_Events_event_id` (`event_id`),
KEY `FK_Places-Company Events-Rules_Places_place_id` (`place_id`),
KEY `FK_Places-Company Events-Rules_Rules_rule_id` (`rule_id`),
CONSTRAINT `FK_Places-Company Events-Rules_Events_event_id` 
FOREIGN KEY  (`event_id`) REFERENCES `Events` (`event_id`) ON DELETE CASCADE 
ON UPDATE  CASCADE,
CONSTRAINT `FK_Places-Company Events-Rules_Rules_rule_id` 
FOREIGN KEY  (`rule_id`) REFERENCES `Rules` (`rule_id`) ON DELETE CASCADE ON  
UPDATE CASCADE,
CONSTRAINT `fk_place_id_pcerc` FOREIGN KEY (`place_id`) 
REFERENCES `Places` (`place_id`) ON DELETE CASCADE ON UPDATE CASCADE
) ENGINE=InnoDB CHARSET=latin1;

存储函数

有两个存储函数。 GETBEGINSGETENDS。参数:rule_id、timestamp、curtimestamp。Timestamp为当天的unixtimestamp,curtimestamp为当天开始的unixtimestamp。 这些功能的工作方式如下。对于每条规则,它们都返回规则的开始(开始)和结束(结束)。如果规则不可重复,它们会返回存储在规则表中的start_dateend_date。如果规则是可重复的,则它们构造 RegularRules 表中最接近的非空 day_start/day_end 的 beginsends。例如,有一个事件有 2 个规则。第一个不可重复,以 start_timestamp 开头并以 end_timestamp 结尾。第二个是可重复的,只有两个非空字段:mon_start = 36000mon_end = 64800GETBEGINS 将根据当天开始的当前 unixtimetamp 和当前 unixtimestamp 将 mon_start 转换为 unixtimestamp。 GETBEGINS 工作类似。如有必要,将提供这些功能的代码。

有问题的查询

这个查询应该返回ongoing在地理上和时间上最接近的事件。地方应该是不同的。所以查询应该为每个地方返回按时间顺序最近的事件,最后根据时间和距离对结果值进行排序,并使用一些系数(我认为排序部分将移至 PHP 等服务器端语言。如果您有关于这种排序我对任何解决方案持开放态度)。例如,附近的 10 家电影院有 5 部电影。每个电影院有 100 个时间表。查询应该为每个电影院返回按时间顺序最近的电影,然后根据时间和距离两个值对电影和电影院进行排序。

预期查询
latpoin,longpoint,r - 是传递给脚本的坐标和半径, curstamp - 一天开始的unixtimestamp, 时间戳 - 当前的 unixtimestamp

SELECT 
    epr.event_id,
    epr.place_id,
    epr.rule_id,
    (6371 * ACOS(COS(RADIANS(latpoint)) * COS(RADIANS(latitude)) *  
    COS(RADIANS(longitude) - RADIANS(longpoint)) + SIN(RADIANS(latpoint)) * 
    SIN(RADIANS(latitude)))) AS distance,
    p.latitude,
    p.longitude,
    GETBEGINS(r.rule_id, curstamp, timestamp) AS begins,
    GETENDS(r.rule_id, curstamp, timestamp) AS ends,
    MIN(ABS(GETBEGINS(r.rule_id, curstamp, timestamp) - timestamp)) AS   
    time_min
FROM
    Events e
        INNER JOIN
    EPR epr ON e.event_id = epr.event_id
        INNER JOIN
    Places p ON epr.place_id = p.place_id
        INNER JOIN
    Rules r ON epr.rule_id = r.rule_id
WHERE
    r.end_date >= timestamp
        AND latitude BETWEEN latpoint - (r / 111.045) AND latpoint + (r /   
        111.045)
        AND longitude BETWEEN longpoint - (r / (111.045 *  
        COS(RADIANS(latpoint)))) AND longpoint + (r / (111.045 * 
        COS(RADIANS(latpoint))))
        AND e.isPublic = 1
GROUP BY epr.place_id

如主题中所述,此查询混合了返回值。更具体地说,它匹配错误的 rule_id,开始,结束到 place_id 组。 此外,此查询的性能很差。表格大小:事件 - 3000 行,地点 - 8000 行,规则 18000 行,EPR-15000 行。使用索引提示 (use index compound) 和 1.2 时,这些查询大约工作 1.8 秒。不使用索引提示查询会进行全表扫描。 我已经阅读了关于这个主题的official mysql docs。然而,由于用户计算的值(GETBEGINSGETENDS),他们的解决方案并不适用。

问题

预期查询部分中提供的查询由于mysql handles group by 的方式而存在分组最小问题。所以可能的解决方案是以这种方式使函数 GETBEGINS 和 GETENDS 用户定义的聚合函数 mysql 可能会返回适当的结果?这个解决方案合乎逻辑吗? 将函数GETBEGINSGETENDS 聚合起来有帮助吗?在这种情况下mysql会返回适当的数据吗?

结论

欢迎对提供的解决方案、新解决方案、关于索引和数据库架构的 cmets 发表评论。

【问题讨论】:

    标签: mysql indexing group-by user-defined-functions stored-functions


    【解决方案1】:

    不能保证分组最大值有效。事实上,MariaDB 破坏了它,但提供了一个设置来恢复它。这就是我所指的:

    SELECT  *
        FROM  
          ( SELECT  ...  ORDER BY ... )
        GROUP BY ...
    

    您希望内部查询中每个组中的第一个(或最后一个)。问题是 SQL 可以随意优化该意图。

    文档中的 groupwise max 代码效率极低。

    为了加快查询速度,一个可能的帮助是隔离 WHERE 子句的 RulesPlaces 部分,并将其变为仅返回相应表的 PRIMARY KEY 的子查询。然后将其放入所有表的 JOIN 中(包括返回到同一个表的 JOIN)。您已经有该子查询的“覆盖索引”,因此它可以是“使用索引”(在 EXPLAIN 使用的行话中)。

    innodb_buffer_pool_size 是否设置为可用 RAM 的 70% 左右?

    BIGINT 占用 8 个字节;您可能会使用 MEDIUMINT UNSIGNED (0..16M)。更小 --> 更多可缓存 --> 更少 I/O --> 更快。​​

    lat/lng 的 DOUBLE 对占用 16 个字节。 FLOAT 对将占用 8 个字节并具有 6 英尺/2 米的分辨率。或 DECIMAL(6,4) 表示纬度,(7,4) 表示经度,7 个字节和 52 英尺/16m 分辨率。对于“商店”来说已经足够了,尤其是因为您使用“正方形”而不是“圆形”来表示距离。

    “查找最近的...”的代码很难优化。这是我想出的最好的:http://mysql.rjweb.org/doc.php/latlng

    【讨论】:

    • 瑞克,谢谢你的回答。我以前读过你的博客,也读过关于“找到最近的”的文章。很棒的材料。我已经测试了您在解决方案#1 中编写的代码,但正如我所提到的,由于使用临时/使用文件排序,它真的很慢。我不知道如何避免,因为 mysql 不支持基于函数的索引。像 innodb_buffer_pool_size 这样的调整选项将有助于缓解症状,但不能治愈问题。这里的主要优化问题不是距离问题,而是解决方案#1 中的慢排序问题和预期查询中的 mysql groupwise 问题。
    • 我会试试你关于隔离WHERE 的规则和位置部分的想法你对用户定义的函数有什么想法吗?它会帮助解决小组问题吗? GETBEGINSGETENDS 作为用户定义的函数是否有助于解决 mysql 混乱数据问题?希望收到您的来信。也许我们可以用不同的形式讨论这个问题。
    • 如果我理解正确,我尝试将大连接中的规则表和位置表交换到它们的子查询版本。它实际上有点帮助,而不是 5 秒查询工作 4.5。这仍然是不可接受的。不过还是谢谢你。
    • 存储函数——我会寻找部分或完全避免它们的方法。或者限制需要使用它们的案例数量。子查询版本——让我们看看你做了什么;也许有些事情没有像我希望的那样到位。如果您想通过电子邮件聊天,请访问 rjweb.org 的 mysrn。
    • “分组技巧”:mariadb.com/kb/en/mariadb/… 加上一个解决方案,至少对于 MariaDB 而言。
    猜你喜欢
    • 1970-01-01
    • 2016-12-10
    • 1970-01-01
    • 1970-01-01
    • 2018-10-07
    • 1970-01-01
    • 1970-01-01
    • 2013-12-18
    • 2014-11-27
    相关资源
    最近更新 更多