Mysql优化/性能,如何有效使用限制|基于位置的选择

问题描述 投票:1回答:2

以下缩短的查询选择给定距离内的所有行(entrys)(根据用户e.altloc = 0:location或e.altloc = 1:altlocation计算)。 我有关于e.uid,al.eid,e.country,e.tmstmp的索引,而id是主键。 根据解释所有行的问题需要处理查询,而不是2行,我喜欢限制2。 我已经阅读了这个问题,但是在使用连接之前我无法进行限制,因为我需要在我可以执行limit 2之前加入位置表,否则返回将是错误的。 https://dba.stackexchange.com/questions/52079/does-using-limit-improve-the-performance-and-is-it-noticeable

查询:

SELECT 
        e.id, e.uid, e.title, e.description, l.place, l.placenonce, al.altplace, al.altplacenonce,
        IF(e.altloc=0,
            6371 * acos( cos( radians(:lat) ) * cos( radians( AES_DECRYPT(lat, UNHEX('###'), latnonce) ) ) * cos( radians( AES_DECRYPT(lng, UNHEX('###'), lngnonce) ) - radians(:lng) ) + sin( radians(:lat) ) * sin(radians(AES_DECRYPT(lat, UNHEX('###'), latnonce))) ) ,
            6371 * acos( cos( radians(:lat) ) * cos( radians( AES_DECRYPT(altlat, UNHEX('###'), altlatnonce) ) ) * cos( radians( AES_DECRYPT(altlng, UNHEX('###'), altlngnonce) ) - radians(:lng) ) + sin( radians(:lat) ) * sin(radians(AES_DECRYPT(altlat, UNHEX('###'), altlatnonce))) )
        ) AS distance
    FROM 
        entrys e 
    INNER JOIN 
        location l 
        ON l.id = e.uid 
    LEFT JOIN
        altlocation al
        ON al.eid = e.id
    WHERE 
        IF(:border = 0, e.country = :countryid, e.country != 0 )    
    HAVING 
        distance <= 50
    ORDER BY 
        e.tmstmp 
    DESC
    LIMIT 2

第二个具有固定位置的示例:

SELECT 
    s.id, s.image, s.description, s.title,      
    ( 
        6371 * acos( cos( radians(:lat) ) * cos( radians( AES_DECRYPT(l.lat, :key, l.latnonce) ) ) * cos( radians( AES_DECRYPT(l.lng, :key, l.lngnonce) ) - radians(:lng) ) + sin( radians(:lat) ) * sin(radians(AES_DECRYPT(l.lat, :key, l.latnonce))) ) 
    ) AS distance
FROM 
    sponsors s 
INNER JOIN 
    location l 
    ON l.id = s.id 
WHERE 
    s.comp = 1 OR s.comp = 3 AND s.active = 1
HAVING 
    distance <= 50
ORDER BY
    s.rotate
ASC
LIMIT 2

如果我的数据库中有数百万行,如何改进基于位置的查询?我需要输出每个查询只有2行。

为第一个示例创建表:

  CREATE TABLE `entrys` (
 `id` int(11) NOT NULL AUTO_INCREMENT,
 `uid` int(5) NOT NULL,
 `tmstmp` bigint(11) NOT NULL,
 `approx_lat` mediumint(9) NOT NULL,
 `approx_lng` mediumint(9) NOT NULL,
 `altloc` tinyint(4) NOT NULL,
 `title` varchar(70) COLLATE latin1_general_ci NOT NULL,
 `description` text COLLATE latin1_general_ci NOT NULL,
 `country` tinyint(4) NOT NULL,
 PRIMARY KEY (`id`),
 KEY `uid` (`uid`),
 KEY `tmstmp` (`tmstmp`),
 KEY `country` (`country`),
) ENGINE=MyISAM CHARSET=latin1 COLLATE=latin1_general_ci

CREATE TABLE `location` (
 `id` int(5) NOT NULL,
 `lat` varbinary(50) NOT NULL,
 `latnonce` varbinary(25) NOT NULL,
 `lng` varbinary(50) NOT NULL,
 `lngnonce` varbinary(25) NOT NULL,
 `place` tinyblob NOT NULL,
 `placenonce` tinyblob NOT NULL,
 UNIQUE KEY `id` (`id`),
 KEY `lat` (`lat`),
 KEY `lng` (`lng`)
) 

CREATE TABLE `altlocation` (
 `id` int(5) NOT NULL,
 `eid` int(5) NOT NULL,
 `altlat` varbinary(50) NOT NULL,
 `altlatnonce` varbinary(25) NOT NULL,
 `altlng` varbinary(50) NOT NULL,
 `altlngnonce` varbinary(25) NOT NULL,
 `altplace` tinyblob NOT NULL,
 `altplacenonce` tinyblob NOT NULL,
 UNIQUE KEY `eid` (`eid`),
 KEY `altlat` (`altlat`),
 KEY `altlng` (`altlng`)
)

旁注:托管引擎应该是innodb,读取率约为70%。与innodb一起运行的位置表。

向Willem Renzema致以问题答案: 这会更有效吗?

SELECT 
        e.id, e.uid, e.title, e.description, l.place, l.placenonce, al.altplace, al.altplacenonce,
        IF(e.altloc=0,
            6371 * acos( cos( radians(:lat) ) * cos( radians( AES_DECRYPT(lat, UNHEX('###'), latnonce) ) ) * cos( radians( AES_DECRYPT(lng, UNHEX('###'), lngnonce) ) - radians(:lng) ) + sin( radians(:lat) ) * sin(radians(AES_DECRYPT(lat, UNHEX('###'), latnonce))) ) ,
            6371 * acos( cos( radians(:lat) ) * cos( radians( AES_DECRYPT(altlat, UNHEX('###'), altlatnonce) ) ) * cos( radians( AES_DECRYPT(altlng, UNHEX('###'), altlngnonce) ) - radians(:lng) ) + sin( radians(:lat) ) * sin(radians(AES_DECRYPT(altlat, UNHEX('###'), altlatnonce))) )
        ) AS distance
    FROM 
        (
            SELECT id, uid, title, description
            FROM 
                entrys 
            WHERE 
                    approx_lat > :min_lat
                AND approx_lat < :max_lat
                AND approx_lng > :min_lng
                AND approx_lng < :min_lng   
            ORDER BY 
                e.tmstmp 
            DESC
            LIMIT 2

        ) AS e
    INNER JOIN 
        location l 
    ON l.id = uid 
    LEFT JOIN
        altlocation al
    ON al.eid = e.id
    HAVING 
        distance <= 50

如果我要在条目表中添加approx_lat和approx_lng 线索将移动approx_lat和approx_lng到条目表,我只能插入altlocation或位置,所以我可以摆脱查询内的IFHAVING distance <= 50still是必要的吗?

mysql optimization query-optimization query-performance sqlperformance
2个回答
2
投票

在查询中使用边界框。

示例(仅在WHERE子句中进行更改):

SELECT 
    e.id, e.uid, e.title, e.description, l.place, l.placenonce, al.altplace, al.altplacenonce,
    IF(e.altloc=0,
        6371 * acos( cos( radians(:lat) ) * cos( radians( AES_DECRYPT(lat, UNHEX('###'), latnonce) ) ) * cos( radians( AES_DECRYPT(lng, UNHEX('###'), lngnonce) ) - radians(:lng) ) + sin( radians(:lat) ) * sin(radians(AES_DECRYPT(lat, UNHEX('###'), latnonce))) ) ,
        6371 * acos( cos( radians(:lat) ) * cos( radians( AES_DECRYPT(altlat, UNHEX('###'), altlatnonce) ) ) * cos( radians( AES_DECRYPT(altlng, UNHEX('###'), altlngnonce) ) - radians(:lng) ) + sin( radians(:lat) ) * sin(radians(AES_DECRYPT(altlat, UNHEX('###'), altlatnonce))) )
    ) AS distance
FROM 
    entrys e 
INNER JOIN 
    location l 
    ON l.id = e.uid 
LEFT JOIN
    altlocation al
    ON al.eid = e.id
WHERE 
    e.country = :countryid
    AND l.approx_lat > :min_lat
    AND l.approx_lat < :max_lat
    AND l.approx_lng > :min_lng
    AND l.approx_lng < :min_long    
HAVING 
    distance <= 50
ORDER BY 
    e.tmstmp 
DESC
LIMIT 2

在执行查询之前,您可以计算:min_lat:max_lat:min_lng:max_lng。这些值将从您的:lat:lng值(在本例中为50)的所需半径生成。

究竟如何做到这一点我建议阅读其他许多答案之一,例如this one,这些答案都在互联网上。只需搜索地理位置边界框即可开始使用。

然后,您可以通过在approx_latapprox_lng列上添加索引来进一步提高性能。你也可以尝试添加几个复合索引,(approx_lat,approx_lng)和/或(approx_lng,approx_lat),因为优化器可以使用它们。但是,这些是我强烈建议的基准测试,看看它们是否提供了任何改进。制作这些覆盖索引的其他列也可能有所帮助,但我现在关注的是最基本的问题。

请注意,您尝试优化的内容已经是一个困难的优化问题。您需要加密数据这一事实使其变得更加困难。但是,只要您可以存储这些近似值,我们就可以绕过大部分额外的难度。

我还强烈建议你保持IF逻辑不在你的WHERE条款中。通过包含它,您可以强制优化器查找每条记录以查看它是否与该条件匹配。

通常,要获得良好的性能,您需要限制需要检查的记录数。 IF语句无法优化(它不是sargable)。这也是为什么我的答案要求您存储近似值以便有效的原因。如果必须首先解密数据,则意味着必须查找并检查每个记录。这将会破坏你的表现。

另请注意,在我的示例查询中,我忽略了altlocation子句中的WHERE表。理想情况下,如果locationaltlocation相同,则应该只有一个数据表,然后从记录位置id作为主要或“备用”的记录加入到该表。

我希望这至少可以帮助你找到正确的方向。


1
投票

(部分答案。)

子查询的有用提示(有时)。

  • 请注意,在子查询中有几个(uid, title, description)笨重的列。
  • 有一个ORDER BYLIMIT,所以拖拉他们需要一些努力。

所以,

  • 使用子查询中的最小列数,确保包含行id
  • 在子查询之后,添加一个JOIN(通过id)来获取那些额外的列。
  • 此外,还有一个“覆盖”索引,其中包含子查询中保留的所有列:INDEX(approx_lat, approx_lng, tmstmp, id)
© www.soinside.com 2019 - 2024. All rights reserved.