MYSQL 巨大的记录并找到每个最近的点
MYSQL Huge records and find each nearest point
遇到MySQL时遇到了一些麻烦。
我有一个 table deviceLog 存储车辆日志包括:
1.设备ID
2.日期时间
3.纬度
4. 经度
设备将每分钟将日志存储到数据库中。
这意味着一辆车每天有 1440 条记录。
假设我有 5000 辆车,每天 table 中总计有大约 720 万行日志数据。
每个月我都需要生成每辆车的设备位置报告。与另一个 table 名称相关的 POI(兴趣点)存储:
1.地点名称
2.纬度
3. 经度
最终的输出应该是:
DeviceID、DateTimer、LocationName(基于 deviceLog 提供的纬度、经度)
对于 LocationName,我创建了一个调用存储过程的函数,通过发送行的纬度和经度来检索它,它将 return 来自 POI 的 LocationName table
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
1 个月每辆车 15 秒的结果,粗略计算大约需要 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
"dedicated stacks",它们意味着很多服务器。想想成本。
有几件事可以在不使用硬件的情况下完成。
请为每个 table 提供 SHOW CREATE TABLE
;同时,我假设您没有(或无用的)索引。我将检查数据类型以查看可以缩小的内容 - 以节省磁盘 space 和一些时间。
我不喜欢使用广泛的精度 -- DOUBLE
有 16 位有效数字; 69
只有 2 个。考虑 69.172
。请参阅 RADIAN
函数代替 8 位数字 pi/180。
dist/abs(cos(radians(xLat))*69)
可以评估一次(用于微小的加速)
ABS()
可能是不必要的。
没有索引,查询将扫描整个 table。至少有INDEX(latitude)
和INDEX(longitude)
。这会将工作量从 550K 测试减少到 2K。要将其缩小到 30 个左右,您需要进行重大重写,al a http://mysql.rjweb.org/doc.php/latlng
可能有一半时间 'device' 是 'at' 相同的 'location'。 (对于车辆尤其如此。)在这种情况下,开始 查看设备自上次定位后是否没有移动。
这带来了另一个问题 -- 不要存储位置,除非它有明显的移动。这将节省一半的磁盘 space.
另一个想法 -- 改变客户的期望。不要每分钟定位一次设备,而是每 10 分钟定位一次。仅此一项,就会将计算时间从 1 天更改为 2.4 小时。
架构评论:
FLOAT
占用4个字节;它们可以变成一些更小的数据类型吗? lat/lng 不一致。请参阅 this 了解一些选择。
- 什么是
geoFences
和 resol
?
- 不要将 (m,n) 与 FLOAT 一起使用(例如 float(10,7))。
如果您一次获取一台设备的所有数据,则更改
PRIMARY KEY (`tripID`),
KEY `deviceID` (`deviceID`),
至
PRIMARY KEY (`deviceID`, tripID),
KEY (`tripID`),
这将更好地利用 "clustering"。但是你也必须换成InnoDB。
您需要在设备停止时删除 'duplicate' 个条目。否则,您将遇到磁盘 space 问题(和性能问题)。
不像 YouTube
YouTube 有不同的问题;对于大多数其他大人物来说也是如此。不用费心去研究它们了。
我建议你的第一个问题是数据量。
- 列较少。
- 行数较少。
- 汇总信息。
24 列 -- 其中一些在几分钟内或一整天都不会更改。所以,不要一直存储它们。
拆分 24 列。主要查询是什么?需要 几个 列来支持它?也就是说,从 0 列构建 up the table;与尝试减少 24 列相比,您会取得更快的进步。
每 15 秒一行。即使 'device' 已关闭?节省了大量资金。
重新计算设备所在城市的名称?不过和上次一样通常在同一个城市。首先检查。这应该可以节省很多 CPU 时间。
对 'city' 使用 3 字节 MEDIUMINT UNSIGNED
。这就是 poiID
应该是什么,而不是 4 字节 INT SIGNED
。 JOIN
显示名字就够便宜了。
老化。当然,客户需要昨天的详细信息。但也许上个月的数据会更糟?去年的更不详细 -- 甚至可能被扔掉了?
如果你会折腾'old'数据,现在是PARTITION
table的时候了。这样清除将是 'instantaneous'.
等等等
遇到MySQL时遇到了一些麻烦。 我有一个 table deviceLog 存储车辆日志包括: 1.设备ID 2.日期时间 3.纬度 4. 经度
设备将每分钟将日志存储到数据库中。
这意味着一辆车每天有 1440 条记录。
假设我有 5000 辆车,每天 table 中总计有大约 720 万行日志数据。
每个月我都需要生成每辆车的设备位置报告。与另一个 table 名称相关的 POI(兴趣点)存储: 1.地点名称 2.纬度 3. 经度
最终的输出应该是: DeviceID、DateTimer、LocationName(基于 deviceLog 提供的纬度、经度)
对于 LocationName,我创建了一个调用存储过程的函数,通过发送行的纬度和经度来检索它,它将 return 来自 POI 的 LocationName table
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
1 个月每辆车 15 秒的结果,粗略计算大约需要 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
"dedicated stacks",它们意味着很多服务器。想想成本。
有几件事可以在不使用硬件的情况下完成。
请为每个 table 提供 SHOW CREATE TABLE
;同时,我假设您没有(或无用的)索引。我将检查数据类型以查看可以缩小的内容 - 以节省磁盘 space 和一些时间。
我不喜欢使用广泛的精度 -- DOUBLE
有 16 位有效数字; 69
只有 2 个。考虑 69.172
。请参阅 RADIAN
函数代替 8 位数字 pi/180。
dist/abs(cos(radians(xLat))*69)
可以评估一次(用于微小的加速)
ABS()
可能是不必要的。
没有索引,查询将扫描整个 table。至少有INDEX(latitude)
和INDEX(longitude)
。这会将工作量从 550K 测试减少到 2K。要将其缩小到 30 个左右,您需要进行重大重写,al a http://mysql.rjweb.org/doc.php/latlng
可能有一半时间 'device' 是 'at' 相同的 'location'。 (对于车辆尤其如此。)在这种情况下,开始 查看设备自上次定位后是否没有移动。
这带来了另一个问题 -- 不要存储位置,除非它有明显的移动。这将节省一半的磁盘 space.
另一个想法 -- 改变客户的期望。不要每分钟定位一次设备,而是每 10 分钟定位一次。仅此一项,就会将计算时间从 1 天更改为 2.4 小时。
架构评论:
FLOAT
占用4个字节;它们可以变成一些更小的数据类型吗? lat/lng 不一致。请参阅 this 了解一些选择。- 什么是
geoFences
和resol
? - 不要将 (m,n) 与 FLOAT 一起使用(例如 float(10,7))。
如果您一次获取一台设备的所有数据,则更改
PRIMARY KEY (`tripID`),
KEY `deviceID` (`deviceID`),
至
PRIMARY KEY (`deviceID`, tripID),
KEY (`tripID`),
这将更好地利用 "clustering"。但是你也必须换成InnoDB。
您需要在设备停止时删除 'duplicate' 个条目。否则,您将遇到磁盘 space 问题(和性能问题)。
不像 YouTube
YouTube 有不同的问题;对于大多数其他大人物来说也是如此。不用费心去研究它们了。
我建议你的第一个问题是数据量。
- 列较少。
- 行数较少。
- 汇总信息。
24 列 -- 其中一些在几分钟内或一整天都不会更改。所以,不要一直存储它们。
拆分 24 列。主要查询是什么?需要 几个 列来支持它?也就是说,从 0 列构建 up the table;与尝试减少 24 列相比,您会取得更快的进步。
每 15 秒一行。即使 'device' 已关闭?节省了大量资金。
重新计算设备所在城市的名称?不过和上次一样通常在同一个城市。首先检查。这应该可以节省很多 CPU 时间。
对 'city' 使用 3 字节 MEDIUMINT UNSIGNED
。这就是 poiID
应该是什么,而不是 4 字节 INT SIGNED
。 JOIN
显示名字就够便宜了。
老化。当然,客户需要昨天的详细信息。但也许上个月的数据会更糟?去年的更不详细 -- 甚至可能被扔掉了?
如果你会折腾'old'数据,现在是PARTITION
table的时候了。这样清除将是 'instantaneous'.
等等等