有没有办法在MySQL中优化复杂的数学查询?

时间:2016-08-05 19:40:25

标签: php mysql math optimization

我一直在研究这个问题,我开始认为我不能提高效率,但我想问一下是否有人有任何提示。

我正在对数百万条记录进行查询,以便沿着从系统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;

到目前为止我尝试过:

  • 调整各种数据类型以优化内存使用和索引(尽管我的数学使得不太可能使用索引)
  • 使用PHP循环和多个较小的查询而不是这一个较大的查询(多次查询花费的时间更长。)
  • 在运行查询之前复制到内存表(表太大而不适合内存)
  • 仅将表的一部分(localkey,x,y,z)复制到内存中。 (这很合适,但是对于其他进程来说,没有多少max_heap_size这是值得的。)

我还缺少其他选择吗?

谢谢!

4 个答案:

答案 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数据。这里是我的问题和答案的链接,可以帮助您获得一些想法:

https://gis.stackexchange.com/questions/12030/optimize-nearest-neighbor-query-on-70-million-point-cloud-on-sql-server-2008

答案 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`)