Mysql optimization/performance, 如何高效使用limit |基于位置的选择
Mysql optimization/performance, how to use limit efficiently | location based selection
以下缩短查询 select 给定距离内的所有行 (entrys
)(从用户 e.altloc = 0 计算:location
或 e.altloc = 1: altlocation
).
我在 e.uid、al.eid、e.country、e.tmstmp 上有索引,id
是主键。
根据解释的问题,所有行都需要处理查询,而不是我喜欢限制为 2 的 2 行。
我已经阅读了这个问题,但我之前无法执行限制使用连接,因为我需要先连接位置 tables 才能执行 limit 2
否则 return 将是错误的。
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 行。
为第一个例子创建table:
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%。位置 table 都是 运行 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 添加到条目 table
线索是将 approx_lat 和 approx_lng 移动到条目 table,我只能插入 altlocation 或位置,这样我就可以在查询中删除 IF
。
HAVING distance <= 50
还有必要吗?
在查询中使用边界框。
示例(仅更改在 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。只需搜索 geolocation bounding box 即可开始。
然后,您可以通过在 approx_lat
和 approx_lng
列上添加索引来进一步提高性能。您还可以尝试添加 (approx_lat,approx_lng)
and/or (approx_lng,approx_lat)
的几个复合索引,因为优化器可以利用这些索引。但是,我强烈建议对这些进行基准测试,看看它们是否提供了任何改进。用于制作这些覆盖索引的其他列也可能有所帮助,但我目前关注的是最基本的问题。
请注意,您要优化的问题已经是一个困难的优化问题。您需要加密数据这一事实使它变得更加困难。然而,只要您可以存储这些近似值,我们就可以绕过大部分额外的困难。
我还强烈建议您将 IF
逻辑排除在 WHERE
子句之外。通过包含它,您可以强制优化器查找每条记录以查看它是否符合该条件。
一般来说,要获得良好的性能,您需要限制需要检查的记录数。 IF
语句无法优化(它不是 sargable)。这也是为什么我的答案要求您存储近似值才能有效的原因。如果必须首先解密数据,则意味着必须查找和检查每条记录。那会毁了你的表现。
另请注意,在我的示例查询中,我忽略了 WHERE
子句中的 altlocation
table。理想情况下,如果 location
和 altlocation
相同,则数据应该只有一个 table,然后从记录位置 id
作为主要位置或 "alternate".
我希望这至少能帮助你找到正确的方向。
(部分答案。)
子查询的有用提示(有时)。
- 请注意,子查询中有几个 (
uid, title, description
) 个庞大的列。
- 有一个
ORDER BY
和 LIMIT
,所以拖着它们走来走去需要一些努力。
所以,
- 在子查询中使用最少的列数,确保包含
id
. 行
- 在 子查询之后,添加一个
JOIN
(通过id
)来获取那些额外的列。
- 此外还有一个 "covering" 索引,其中包含保留在子查询中的所有列:
INDEX(approx_lat, approx_lng, tmstmp, id)
以下缩短查询 select 给定距离内的所有行 (entrys
)(从用户 e.altloc = 0 计算:location
或 e.altloc = 1: altlocation
).
我在 e.uid、al.eid、e.country、e.tmstmp 上有索引,id
是主键。
根据解释的问题,所有行都需要处理查询,而不是我喜欢限制为 2 的 2 行。
我已经阅读了这个问题,但我之前无法执行限制使用连接,因为我需要先连接位置 tables 才能执行 limit 2
否则 return 将是错误的。
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 行。
为第一个例子创建table:
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%。位置 table 都是 运行 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 添加到条目 table
线索是将 approx_lat 和 approx_lng 移动到条目 table,我只能插入 altlocation 或位置,这样我就可以在查询中删除 IF
。
HAVING distance <= 50
还有必要吗?
在查询中使用边界框。
示例(仅更改在 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。只需搜索 geolocation bounding box 即可开始。
然后,您可以通过在 approx_lat
和 approx_lng
列上添加索引来进一步提高性能。您还可以尝试添加 (approx_lat,approx_lng)
and/or (approx_lng,approx_lat)
的几个复合索引,因为优化器可以利用这些索引。但是,我强烈建议对这些进行基准测试,看看它们是否提供了任何改进。用于制作这些覆盖索引的其他列也可能有所帮助,但我目前关注的是最基本的问题。
请注意,您要优化的问题已经是一个困难的优化问题。您需要加密数据这一事实使它变得更加困难。然而,只要您可以存储这些近似值,我们就可以绕过大部分额外的困难。
我还强烈建议您将 IF
逻辑排除在 WHERE
子句之外。通过包含它,您可以强制优化器查找每条记录以查看它是否符合该条件。
一般来说,要获得良好的性能,您需要限制需要检查的记录数。 IF
语句无法优化(它不是 sargable)。这也是为什么我的答案要求您存储近似值才能有效的原因。如果必须首先解密数据,则意味着必须查找和检查每条记录。那会毁了你的表现。
另请注意,在我的示例查询中,我忽略了 WHERE
子句中的 altlocation
table。理想情况下,如果 location
和 altlocation
相同,则数据应该只有一个 table,然后从记录位置 id
作为主要位置或 "alternate".
我希望这至少能帮助你找到正确的方向。
(部分答案。)
子查询的有用提示(有时)。
- 请注意,子查询中有几个 (
uid, title, description
) 个庞大的列。 - 有一个
ORDER BY
和LIMIT
,所以拖着它们走来走去需要一些努力。
所以,
- 在子查询中使用最少的列数,确保包含
id
. 行
- 在 子查询之后,添加一个
JOIN
(通过id
)来获取那些额外的列。 - 此外还有一个 "covering" 索引,其中包含保留在子查询中的所有列:
INDEX(approx_lat, approx_lng, tmstmp, id)