繁体   English   中英

有什么方法可以优化MySQL中的复杂数学查询?

[英]Is there any way to optimize a complex math query in MySQL?

我一直在研究这个问题,并且开始认为我无法再提高效率,但是我想问一下是否有人提出任何建议。

我正在对数百万条记录运行查询,以查找从给定半径的从系统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留给其他进程的用处很小,这是不值得的。)

我还有其他选择吗?

谢谢!

假设只匹配较小的记录子集,则可以通过首先执行基本的“矩形”过滤来减少数学负担。 例如,对于表中的每条记录执行完整的笛卡尔距离是没有意义的,只是要丢弃其中的大部分。

一个简单的“盒子”边界检查只是一个简单的减法和比较:

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

当然,您正在做3D位置,所以它要复杂一些,但这应该为您提供基本概念。

除了上面Marc B提出的关于快速预滤波的出色建议之外,当您进行第二次遍历时,您还可以通过两种方式在距离公式中节省一些计算:

1)使用(xk)*(xk)而不是调用pow(xk,...)

2)跳过平方根并计算平方的距离。 然后,您可以将其与所需距离的平方进行比较,该距离只需计算一次即可。

除了建议的两项重大数学优化外,提高速度的最大飞跃就是减少任何计算并减少搜索空间 这意味着空间索引。

我不是MySQL方面的专家,但是我的想法是您在3D空间中预先生成空间索引,从而大大减少了搜索空间。

更精确地说,通过全表扫描,您的复杂度变为O(n ^ 2) 搜索所需的时间随着表的大小以及搜索表的向下位置而增加。 但是,基于树的空间索引可以将其减少为O(n log n)

考虑将空间划分为固定大小的多维数据集(以及多维数据集中的多维数据集)。 与Google地图如何排列图块没什么不同。 现在,有了索引,您就可以基于初始坐标对每个多维数据集都有一个“虫洞”,因为您可以计算出可以找到O(n)时间的恒星的多维数据集。 然后,您要做的就是在此多维数据集中运行搜索。

这是MySQL文档中有关空间索引的一些参考。

几年前,我在处理两个坐标中的LiDAR数据时遇到了类似的问题。 这是我的问题和答案的链接,可以帮助您获得一些想法:

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

边界框方法甚至可以在其中一个维度上使用索引。 但是,如果不是按照马克的建议写的话。 代替:

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

暂无
暂无

声明:本站的技术帖子网页,遵循CC BY-SA 4.0协议,如果您需要转载,请注明本站网址或者原文地址。任何问题请咨询:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM