Mysql оптимизация / производительность, как эффективно использовать лимит |выбор на основе местоположения - PullRequest
0 голосов
/ 11 сентября 2018

В следующем сокращенном запросе выбираются все строки (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`)
)

sidenote: движок для записей, вероятно, должен быть innodb, прочитав ~ 70%. Таблицы местоположений работают с innodb.

РЕДАКТИРОВАТЬ вопрос к Виллему Рензема для его ответа:
Будет ли так эффективнее?

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

Если бы я добавил прибл. И ок. В таблицу входов
Подсказка будет перемещаться приблизительно в плоскость ввода и указывать приблизительную ширину и приблизительную величину, чтобы я мог вставить только ИЛИ расположение или только местоположение, чтобы я мог избавиться от IF внутри запроса.

HAVING distance <= 50 все еще необходимо?

Ответы [ 2 ]

0 голосов
/ 03 октября 2018

(Частичный ответ.)

Полезный совет для подзапросов (иногда).

  • Обратите внимание, что в подзапросе есть несколько (uid, title, description) громоздких столбцов.
  • Есть ORDER BY и LIMIT, поэтому для их перемещения требуется некоторое усилие.

Итак,

  • Используйте минимальное количество столбцов в подзапросе, обязательно указав строку id.
  • После подзапроса добавьте JOIN (через id), чтобы получить эти дополнительные столбцы.
  • Кроме того, имеется индекс покрытия, содержащий все столбцы, которые остаются в подзапросе: INDEX(approx_lat, approx_lng, tmstmp, id)
0 голосов
/ 12 сентября 2018

Используйте ограничивающий прямоугольник в своем запросе.

Пример (в предложении 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).

Как именно это сделать, я предлагаю прочитать один из многих других ответов, таких как этот , которые есть по всему Интернету. Чтобы начать, просто найдите ограничивающую рамку геолокации .

Затем вы можете еще больше повысить производительность, добавив индекс по столбцам approx_lat и approx_lng. Вы также можете попробовать добавить пару составных индексов (approx_lat,approx_lng) и / или (approx_lng,approx_lat), так как оптимизатор МОЖЕТ использовать их. Тем не менее, это те вещи, которые я настоятельно рекомендовал бы для сравнительного анализа, чтобы увидеть, если они дают какие-либо улучшения или нет. Дополнительные столбцы для создания этих покрывающих индексов также могут помочь, но в данный момент я сосредотачиваюсь на самых основных проблемах.

Обратите внимание, что то, что вы пытаетесь оптимизировать, уже является сложной задачей оптимизации. Тот факт, что вам нужно зашифровать ваши данные, делает это еще сложнее. Однако, пока вы можете хранить эти приблизительные значения, мы можем обойти большую часть этой дополнительной сложности.

Я бы также настоятельно рекомендовал вам не включать логику IF в ваше предложение WHERE. Включая это, вы заставляете оптимизатор искать каждую запись, чтобы увидеть, соответствует ли она этому условию.

В целом, чтобы получить хорошую производительность, вам нужно ограничить количество записей, которые необходимо проверить. Оператор IF не может быть оптимизирован (это не sargable ). Вот почему мой ответ требует, чтобы вы хранили приблизительные значения, чтобы быть эффективными. Если данные должны быть расшифрованы в первую очередь, это означает, что каждая отдельная запись должна быть просмотрена и проверена. Это то, что убьет вашу производительность.

Также обратите внимание, что в моем примере запроса я проигнорировал таблицу altlocation в предложении WHERE. В идеале, если location и altlocation идентичны, у вас должна быть только одна таблица для данных, а затем ПРИСОЕДИНИТЕСЬ к этой таблице из записи, которая записывает местоположение id как первичное или "альтернативное". ».

Надеюсь, это поможет вам в правильном направлении.

Добро пожаловать на сайт PullRequest, где вы можете задавать вопросы и получать ответы от других членов сообщества.
...