我一直在研究这个问题,我开始认为我不能提高效率,但我想问一下是否有人有任何提示。
我正在对数百万条记录进行查询,以便沿着从系统a到系统b的具有给定半径的线性列找到所有x,y,z坐标(这些是星号)。我正在运行PHP,并在结果集上完成了很多其他工作。我在大约16秒内从脚本中获得结果。查询延迟大约是这16秒中的7秒。
基本查询逻辑是:
SELECT name, coordinates, and distance from end point
FROM stars
WHERE all stars are in a column of given radius between start and end points
ORDER BY distance from end point DESC
where子句需要两个单独的计算,它们是:
计算1:
Calculate if the stars are within the space of the column using constants and x,y,z
计算2:
Limit the column radius to a given figure.
(This where clause also performs similar calculations with the same constants and x,y,z.)
where子句中的数学公式无法真正改变,它们是3D空间中柱状计算所需的公式。
查询结尾处的顺序是绝对必要的,因为结果集太大,我的脚本无法保存在内存中。我必须在脚本中以正确的顺序使用它。
在变量替换之前,查询最容易阅读:
SELECT
name,
x,
y,
z,
SQRT(
pow(`x`-" . $bx . ",2)+
pow(`y`-" . $by . ",2)+
pow(`z`-" . $bz . ",2)
) d
FROM
stars
WHERE
(((`x`*$cx+`y`*$cy+`z`*$cz)-($constant_1))/($constant_2)) between 0 and 1
AND
SQRT(((($ax + ((((`x`*$cx+`y`*$cy+`z`*$cz)-($constant_1))/($constant_2)) * $cx))-`x`)*(($ax + ((((`x`*$cx+`y`*$cy+`z`*$cz)-($constant_1))/($constant_2)) * $cx))-`x`))+((($ay + ((((`x`*$cx+`y`*$cy+`z`*$cz)-($constant_1))/($constant_2)) * $cy))-`y`)*(($ay + ((((`x`*$cx+`y`*$cy+`z`*$cz)-($constant_1))/($constant_2)) * $cy))-`y`))+((($az + ((((`x`*$cx+`y`*$cy+`z`*$cz)-($constant_1))/($constant_2)) * $cz))-`z`)*(($az + ((((`x`*$cx+`y`*$cy+`z`*$cz)-($constant_1))/($constant_2)) * $cz))-`z`)))
<=$radius
ORDER BY
SQRT(
pow(`x`-" . $bx . ",2)+
pow(`y`-" . $by . ",2)+
pow(`z`-" . $bz . ",2)
) DESC
在数据库上运行的最终查询如下所示:(为简单起见,我使用的是大量常量为0的样本数据。)
SELECT
name,
x,
y,
z,
SQRT( pow(`x`-25.21875,2)+ pow(`y`--20.90625,2)+ pow(`z`-25899.96875,2) ) d
FROM
stars
WHERE
(((`x`*25.21875+`y`*-20.90625+`z`*25899.96875)-(0))/(670809454.308))
between 0 and 1
AND
SQRT((((0 + ((((`x`*25.21875+`y`*-20.90625+`z`*25899.96875)-(0))/(670809454.308)) * 25.21875))-`x`)*((0 + ((((`x`*25.21875+`y`*-20.90625+`z`*25899.96875)-(0))/(670809454.308)) * 25.21875))-`x`))+(((0 + ((((`x`*25.21875+`y`*-20.90625+`z`*25899.96875)-(0))/(670809454.308)) * -20.90625))-`y`)*((0 + ((((`x`*25.21875+`y`*-20.90625+`z`*25899.96875)-(0))/(670809454.308)) * -20.90625))-`y`))+(((0 + ((((`x`*25.21875+`y`*-20.90625+`z`*25899.96875)-(0))/(670809454.308)) * 25899.96875))-`z`)*((0 + ((((`x`*25.21875+`y`*-20.90625+`z`*25899.96875)-(0))/(670809454.308)) * 25899.96875))-`z`)))
<=600
ORDER BY
SQRT( pow(`x`-25.21875,2)+ pow(`y`--20.90625,2)+ pow(`z`-25899.96875,2) ) DESC
我的表定义如下:
CREATE TABLE IF NOT EXISTS `stars` (
`localkey` bigint(20) NOT NULL AUTO_INCREMENT,
`id` bigint(20) NOT NULL,
`x` double NOT NULL,
`y` double NOT NULL,
`z` double NOT NULL,
`name` varchar(100) COLLATE utf8_unicode_ci NOT NULL,
PRIMARY KEY (`localkey`),
UNIQUE KEY `id` (`id`),
KEY `x` (`x`),
KEY `y` (`y`),
KEY `z` (`z`),
KEY `xyz` (`x`,`y`,`z`),
KEY `name` (`name`)
) ENGINE=MyISAM DEFAULT CHARSET=utf8 COLLATE=utf8_unicode_ci
查询结果的说明表明没有索引使用和额外的:
extra: Using where; Using filesort;
到目前为止我尝试过:
我还缺少其他选择吗?
谢谢!
答案 0 :(得分:3)
假设只有较小的记录子集匹配,您可以先通过基本的“矩形”过滤来减少数学负荷。例如没有必要为表中的每条记录执行完整的笛卡尔距离,只能扔掉大部分记录。
简单的“框”边界检查只是一个简单的减法和比较:
SELECT ...
FROM (
SELECT ...
WHERE (
(abs($x_coord - x_coordinate) <= $max_distance)
OR (abs($y_coord - y_coordinate) <= $max_distance)
)
) AS square_filter
WHERE ... full calculation here
对于你来说,你正在做三维位置,所以它有点复杂,但这应该给你基本的想法。
答案 1 :(得分:1)
除了上面提到的Marc B建议的快速预过滤的优秀建议之外,当你进行第二次传递时,你可以通过两种方式在距离公式中节省一点点计算:
1)使用(x-k)*(x-k)代替调用pow(x-k,...)
2)跳过平方根并计算距离平方。然后,您将比较所需距离的平方,只需计算一次。
答案 2 :(得分:1)
除了建议的两个伟大的数学优化之外,提高速度的最大跳跃将是最小化任何计算并减少搜索空间。这意味着空间索引。
我不是MySQL的专家,但我们的想法是在3D空间中预生成空间索引,以便大大减少搜索空间。
更准确地说,使用全表扫描,您的复杂性将变为 O(n ^ 2)。搜索所需的时间随表格的大小而增加,以及您搜索的表格下方的距离。但是,对于基于树的空间索引,可以将其缩减为 O(n log n)
考虑将空间划分为固定大小的立方体(以及立方体内的立方体)。与谷歌地图排列瓷砖的方式不同。现在有了索引,你就会得到一个虫洞&#34;根据初始坐标计算每个立方体,因为您可以计算立方体,您可以使用 O(n)时间找到星星。然后你所要做的就是在这个立方体中运行搜索。
这里有MySQL docs对空间索引的一些参考。
我在几年前的两个坐标中遇到了类似的问题处理LiDAR数据。这里是我的问题和答案的链接,可以帮助您获得一些想法:
答案 3 :(得分:0)
边界框方法甚至可能在维度的一个上使用索引。但如果按照Marc建议的方式书写则不行。代替:
`x` BETWEEN $x - $dist AND $x + $dist
一般原则是您不应该在函数中隐藏索引变量。在此示例中为ABS
。
也...
ORDER BY d -- this will avoid recomputing the SQRT
pow(y--20.90625,2)
中的双减号真的有效吗?要解决它,交换它们:
pow(-20.90625 - `y`,2)
设置起来比较麻烦,但乘以可能比POW更快:
(-20.90625 - `y`) * (-20.90625 - `y`)