MYSQL 巨大的记录并找到每个最近的点

Posted

技术标签:

【中文标题】MYSQL 巨大的记录并找到每个最近的点【英文标题】:MYSQL Huge records and find each nearest point 【发布时间】:2018-09-28 09:38:26 【问题描述】:

当我遇到 mysql 时,我遇到了一些麻烦。 我有一个存储车辆日志的表 deviceLog 包括: 1.设备ID 2.日期时间 3. 纬度 4. 经度

设备将每分钟将日志存储到数据库中。 这意味着一辆车每天有 1440 条记录。 假设我有 5000 辆汽车,那么每天表中将有大约 720 万行日志数据。

每个月我都需要生成每辆车的设备位置报告。这与另一个表名相关,即 POI (兴趣点),其中存储: 1. 地点名称 2. 纬度 3. 经度

的最终输出应该是: DeviceID、DateTimer、LocationName(根据deviceLog提供的纬度、经度)

对于 LocationName,我创建了一个调用存储过程的函数,通过发送行的纬度和经度来检索它,它将从 POI 表中返回 LocationName

CREATE DEFINER=`root`@`localhost` PROCEDURE `SPGetGeoName`(IN `xLat` DOUBLE, IN `xLon` DOUBLE, OUT `xLocationName` NVARCHAR(1500))
BEGIN

declare lon1 float; declare lon2 float;
    declare lat1 float; declare lat2 float;
    declare dist float; declare pi float;
    set pi = 3.1415926;
    set dist=1.9;
    set lon1 = xLon-dist/abs(cos(radians(xLat))*69);
    set lon2 = xLon+dist/abs(cos(radians(xLat))*69);
    set lat1 = xLat-(dist/69); set lat2 = xLat+(dist/69);

SET xLocationName = (SELECT locationName FROM poiTest 
                WHERE longitude BETWEEN lon1 AND lon2 AND 
                      latitude BETWEEN lat1 AND lat2 AND
                      3956 * 2 * ASIN(SQRT( POWER(SIN((xLat-latitude)* pi/180 / 2), 2) +COS(xLat*pi/180) * COS(latitude*pi/180) *POWER(SIN((xLon-longitude) * pi /180 / 2), 2) )) < dist 
                      ORDER BY 3956 * 2 * ASIN(SQRT( POWER(SIN((xLat-latitude)* pi/180 / 2), 2) +COS(xLat*pi/180) * COS(latitude*pi/180) *POWER(SIN((xLon-longitude) * pi /180 / 2), 2) )) ASC limit 1);


END

结果如每辆车 15 秒 1 个月,粗略计算大约需要 1 天才能生成整个报告。

有没有办法克服这个问题?

CREATE TABLE `deviceLog` (
   `tripID` int(11) NOT NULL AUTO_INCREMENT,
   `latitude` float NOT NULL,
   `longitude` double NOT NULL,
   `rssi` smallint(6) NOT NULL,
   `speed` float NOT NULL,
   `course` float NOT NULL,
   `hdop` float NOT NULL,
   `dateTimer` datetime NOT NULL,
   `gpsStat` tinyint(4) NOT NULL,
   `unitStat` varchar(12) NOT NULL,
   `battVolt` varchar(6) NOT NULL,
   `fuelLevel` varchar(6) NOT NULL DEFAULT '0',
   `fuelData` varchar(6) NOT NULL DEFAULT '0',
   `ignVolt` varchar(6) NOT NULL,
   `odoMeter` decimal(10,2) NOT NULL,
   `deviceID` varchar(16) NOT NULL,
   `chksum` varchar(2) NOT NULL,
   `resol` varchar(1024) DEFAULT NULL,
   `driverID` varchar(20) DEFAULT NULL,
   `geoFences` varchar(255) DEFAULT NULL,
   `poiLoc` varchar(255) DEFAULT NULL,
   `eventStat` varchar(2) DEFAULT NULL,
   `iostat` varchar(4) DEFAULT NULL,
   `groupID` varchar(2) DEFAULT NULL,
   PRIMARY KEY (`tripID`),
   KEY `deviceID` (`deviceID`),
   KEY `dateTimer` (`dateTimer`)
 ) ENGINE=MyISAM AUTO_INCREMENT=3423023 DEFAULT CHARSET=latin1


CREATE TABLE `poi` (
   `poiID` int(11) NOT NULL AUTO_INCREMENT,
   `type` varchar(50) NOT NULL,
   `locationName` varchar(200) NOT NULL,
   `state` varchar(50) NOT NULL,
   `city` varchar(50) NOT NULL,
   `longitude` float(10,7) DEFAULT NULL,
   `latitude` float DEFAULT NULL,
   PRIMARY KEY (`poiID`),
   KEY `lat` (`longitude`,`latitude`)
 ) ENGINE=MyISAM AUTO_INCREMENT=683606 DEFAULT CHARSET=latin1 ROW_FORMAT=DYNAMIC

【问题讨论】:

您的 POI 表中有多少条目? 嗨 Olli,大约有 550,000 个条目。 虽然数据库中的计算相当快,但一个想法可能是将一些已经预先计算的值(如果可能)存储到表本身并使用它们。它可能至少有一点帮助 与其给出答案,我建议不要为此目的使用 MySQL;既是因为您将遇到扩展问题,但主要是因为有专门为这种用例(物联网/流式传输/事件数据/时间序列)设计的专用技术堆栈。 感谢 Olli 的反馈,为了加快进程,我每天都会预先计算数据,例如:所有 2018 年 3 月 10 日之前的数据都会被处理以更新它们的位置。跨度> 【参考方案1】:

“专用堆栈”是指大量服务器。想想成本。

有几件事可以在不投入硬件的情况下完成。

请为每张桌子提供SHOW CREATE TABLE;同时,我会假设您没有(或无用的)索引。我将检查数据类型以查看可以缩小的内容 - 以节省磁盘空间和一些时间。

我不喜欢使用范围广泛的精度——DOUBLE 有 16 个有效数字; 69 只有 2 个。考虑 69.172。请参阅 RADIAN 函数代替 8 位 pi/180。

dist/abs(cos(radians(xLat))*69) 可以被评估一次(对于微小的加速)

ABS() 可能是不必要的。

没有索引,查询将扫描整个表。至少有INDEX(latitude)INDEX(longitude)。这会将工作量从 550K 测试更改为 2K。要将其缩小到大约 30 个,您需要进行大量重写,例如 http://mysql.rjweb.org/doc.php/latlng

“设备”可能有一半时间“位于”同一个“位置”。 (车辆尤其如此。)在这种情况下,开始查看设备自上次定位后是否未移动。

这带来了另一个问题——除非位置发生了显着移动,否则不要存储位置。这样可以节省一半的磁盘空间。

另一个想法——改变客户的期望。不要每分钟定位一次设备,而是每 10 分钟定位一次。仅此一项,就会将计算时间从 1 天更改为 2.4 小时。

对架构的评论:

FLOAT 占用 4 个字节;它们可以变成一些更小的数据类型吗? lat/lng 不一致。请参阅this 了解一些选择。 什么是geoFencesresol? 不要将 (m,n) 与 FLOAT 一起使用(例如 float(10,7))。

如果您要一次获取一台设备的所有数据,请更改

PRIMARY KEY (`tripID`),
KEY `deviceID` (`deviceID`),

PRIMARY KEY (`deviceID`, tripID),
KEY (`tripID`),

这将更好地利用“集群”。但您也必须更改为 InnoDB。

您需要在设备停止时消除“重复”条目。否则,您将遇到磁盘空间问题(和性能问题)。

不像 YouTube

YouTube 有不同的问题;大多数其他大人物也是如此。不要费心研究它们。

我建议你的第一个问题是数据量。

更少的列。 行数更少。 总结信息。

24 列——其中一些在几分钟内或一整天都不会改变。所以,不要一直存储它们。

拆分 24 列。主要查询是什么?需要多少 列来支持它?也就是说,从 0 列构建 up 表;与尝试减少 24 列相比,您将取得更大的进步。

每 15 秒一行。即使“设备”关闭?节省了大量资金。

重新计算设备所在城市的名称?但它通常和上次在同一个城市。检查那个first。这应该会节省大量的 CPU 时间。

使用 3 字节的 MEDIUMINT UNSIGNED 表示“城市”。这就是 poiID 应该是的,而不是 4 字节的 INT SIGNED。当您显示名称时,JOIN 将足够便宜。

老化。当然,客户需要昨天的详细信息。但也许上个月的数据可能会更粗一些?去年的更不详细——甚至可能被折腾了?

如果您要折腾“旧”数据,现在是时候使用PARTITION 表了。这样清除将是“即时的”。

等等。等等。

【讨论】:

嗨,Rick,感谢您的反馈,但是客户的期望不能降低,因为市场目前提供每 30 秒 > 10 秒的设备跟踪。我为我的第一篇文章的报告中使用的两个相关表添加了 SHOW CREATE TABLE。 我已添加到我的答案中。 嗨 Rick,1. 让我弄清楚变成更小的数据类型的影响。 2. geoFences 和 resol 没用的可以忽略。 3. 好的,注意到了。 4.我改变了主键,每400k数据它已经固定了0.01秒。 5. 我同意重复条目是现在的主要问题,我将通过里程表 + Lat Lng 弄清楚如何删除重复条目。 6.我尝试了另一种方法是每个设备表,因为所有设备日志都是集中的,我想将日志拆分回每个单独的日志以加快进程,这样其他用户就不会受到其他大数据设备的影响。 @ArthurLiew - 即使这些列是空的或 NULL,它们每行占用 2 个字节。所以考虑放弃它们。 嗨 Rick,感谢您提供的信息,我将与我的团队讨论此信息及其影响。感谢您与我分享知识。

以上是关于MYSQL 巨大的记录并找到每个最近的点的主要内容,如果未能解决你的问题,请参考以下文章

MySQL按顺序查找每组最近/最大的记录

有效地找到距另一点最近的点

mysql连接表并搜索where子句的最新记录

K个最近的点

python - 如何使用python pandas为超过1M点的点集中的每个点找到最近的8个点

在每个窗口中查找最新记录 - MariaDB/MySQL