У меня две таблицы:

CREATE TABLE share_prices (
    price_id int(10) unsigned NOT NULL AUTO_INCREMENT,
    price_date date NOT NULL,
    company_id int(10) NOT NULL,
    high decimal(20,2) DEFAULT NULL,
    low decimal(20,2) DEFAULT NULL,
    close decimal(20,2) DEFAULT NULL,
    PRIMARY KEY (price_id),
    UNIQUE KEY price_date (price_date,company_id),
    KEY company_id (company_id),
    KEY price_date_2 (price_date)
) ENGINE=InnoDB AUTO_INCREMENT=368586 DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_unicode_ci;

А также

CREATE TABLE rating_lookup (
    rating_id int(11) NOT NULL,
    start_date date DEFAULT NULL,
    start_price decimal(10,2) DEFAULT NULL,
    broker_id int(11) DEFAULT NULL,
    company_id int(11) DEFAULT NULL,
    end_date date DEFAULT NULL,
    PRIMARY KEY (rating_id),
    KEY idx_rating_lookup_company_id (company_id)
) ENGINE=InnoDB DEFAULT CHARSET=utf8;

Это текущий запрос:

SELECT broker_id, count(rating_id)

FROM (

    SELECT rating_lookup.*,
    share_prices.company_id as correct_company,
    share_prices.price_date,
    max(high) as peak_gain,
    ( ( ( max(high) - rating_lookup.start_price ) / rating_lookup.start_price ) * 100 ) as percent_gain

    FROM rating_lookup, share_prices

    WHERE share_prices.price_date > rating_lookup.start_date 
    AND share_prices.price_date < ifnull(end_date, curdate())
    AND share_prices.company_id = rating_lookup.company_id

    GROUP BY rating_id

    HAVING percent_gain > 5

) correct

GROUP BY broker_id

В настоящее время этот запрос занимает 10,969 секунды .

Изолированный подзапрос занимает 0,391 секунды (продолжительность) / 10,438 секунды (выборка) .

Цель запроса:

Получите общее количество правильных оценок для каждого broker_id.

Правильный рейтинг - это рейтинг, который с момента его start_price достиг + 5%.


Я хочу резко сократить время запроса, даже если реструктуризация базы данных - единственный способ.


Аппендикс

Объясните вышеприведенный запрос:

+---+---------+---------------+-------+--------------------------------------+------------+---+----------------------------------------+---------+---------------------------------+
| 1 | PRIMARY | <derived2>    | ALL   |                                      |            |   |                                        | 3894800 | Using temporary; Using filesort |
| 2 | DERIVED | rating_lookup | index | PRIMARY,idx_rating_lookup_company_id | PRIMARY    | 4 |                                        |   18200 | Using where                     |
| 2 | DERIVED | share_prices  | ref   | price_date,company_id,price_date_2   | company_id | 4 | brokermetrics.rating_lookup.company_id |     214 | Using where                     |
+---+---------+---------------+-------+--------------------------------------+------------+---+----------------------------------------+---------+---------------------------------+

share_prices ~ 375 000 строк

rating_lookup ~ 18 000 строк с 46 уникальными брокерами

2
johncarter 20 Июл 2017 в 17:49
Добавьте план объяснения запроса. Существующие индексы и приблизительные размеры таблиц также могут помочь.
 – 
Klas Lindbäck
21 Июл 2017 в 11:34

3 ответа

Я предполагаю, что цены на акции вводятся один раз в день после закрытия рынка (или пару раз в день, если вы охватываете несколько рынков).

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

2
Klas Lindbäck 21 Июл 2017 в 10:52
Спасибо Класу, проблема в том, что 5% процент_прироста будет определяемой пользователем переменной.
 – 
johncarter
21 Июл 2017 в 11:23
Вы можете предварительно рассчитать и сохранить процент прироста каждого рейтинга.
 – 
Klas Lindbäck
21 Июл 2017 в 11:28
Я понимаю, о чем вы говорите - спасибо. Это может быть ответ.
 – 
johncarter
21 Июл 2017 в 11:30
Обратите внимание, что необходимо пересчитывать только открытые рейтинги. По прошествии даты окончания процент прироста рейтинга не изменится (если вы не вмешиваетесь в исторические данные).
 – 
Klas Lindbäck
21 Июл 2017 в 11:43
1
Знание полного объема проекта; Я думаю, что процент - в день - на рейтинг был бы наиболее полезным. Спасибо за вашу помощь. Я все еще очень заинтересован в оптимизации этого запроса, но это хороший запасной вариант.
 – 
johncarter
21 Июл 2017 в 12:03
PRIMARY KEY (price_id),   -- useless
UNIQUE KEY price_date (price_date,company_id), -- could/should be PK
KEY company_id (company_id),
KEY price_date_2 (price_date)  -- redundant

->

PRIMARY KEY(price_date, company_id),
KEY company_id (company_id)

decimal(20,2) занимает 9 байтов, ни один из существующих запасов, вероятно, не превышает 6 цифр слева от десятичной точки, и не обрабатывает дешевые акции, требующие более двух десятичных знаков. Рассмотрим DECIMAL(8,2) (4 байта) или (10,4) (5 байтов). FLOAT (4 байта) позволяет избежать большинства проблем, но ограничен 7 значащими цифрами.

Меньше -> более кэшируемый -> меньше операций ввода-вывода -> быстрее.

Не ВЫБИРАЙТЕ то, что вам не нужно. Все что тебе нужно это

SELECT rating_id, broker_id

И переместите выражение в HAVING:

HAVING ((( max(high)... *100) > 5

Используйте синтаксис JOIN..ON:

  FROM  rating_lookup, share_prices
  WHERE share_prices.company_id = rating_lookup.company_id
    AND ...

->

  FROM rating_lookup AS r
  JOIN share_prices AS p
    ON p.company_id = r.company_id
  WHERE ...
1
Rick James 21 Июл 2017 в 04:49
Привет, Рик, я внес те изменения, которые имеют полный смысл - спасибо. К сожалению, это не оказывает реального влияния на производительность и увеличивает время запроса до 17,781.
 – 
johncarter
21 Июл 2017 в 09:28
Хммм .... Не могли бы вы опубликовать исправленный код, исправленный EXPLAIN и т. Д. Может быть, я смогу обнаружить свою ошибку, когда она будет в контексте остальной части вашего кода.
 – 
Rick James
21 Июл 2017 в 17:32

Расширяя ответ Класа, ниже представлена ​​схема «сводной» таблицы, которая может быть заполнена предварительно рассчитанными записями для каждого брокера, компании, за день.

Отказ от ответственности: не тестировал на реальных данных, но должен работать.

CREATE TABLE `price_summary` (
`price_id` int(10) NOT NULL,
`broker_id` int(10) NOT NULL DEFAULT '0',
`company_id` int(10) NOT NULL DEFAULT '0',
`start_date` int(10) NOT NULL DEFAULT '0',
`end_date` int(10) NOT NULL DEFAULT '0',
`peak_gain` int(10) NOT NULL DEFAULT '0',
`max_price` int(10) NOT NULL DEFAULT '0',
`percentage_gain` decimal(10,0) NOT NULL DEFAULT '0',
`updated_on` int(10) NOT NULL DEFAULT '0'
) ENGINE=InnoDB DEFAULT CHARSET=utf8;

--
-- Indexes for dumped tables
--

--
-- Indexes for table `price_summary`
--
ALTER TABLE `price_summary`
ADD PRIMARY KEY (`price_id`),
ADD UNIQUE KEY `broker_company_date` (`broker_id`,`company_id`,`start_date`) USING BTREE,
ADD KEY `broker_id` (`broker_id`),
ADD KEY `company_id` (`company_id`),
ADD KEY `start_date` (`start_date`),
ADD KEY `end_date` (`end_date`),
ADD KEY `peak_gain` (`peak_gain`),
ADD KEY `max_price` (`max_price`),
ADD KEY `percentage_gain` (`percentage_gain`);

ALTER TABLE `price_summary`
MODIFY `price_id` int(10) NOT NULL AUTO_INCREMENT; 

И образец запроса для получения желаемых записей.

SELECT
    broker_id,
    count(company_id) as company_count
FROM
    price_summary
WHERE
    start_date > {input_timestamp}
    AND
    end_date < {input_timestamp/now()}
    AND
    percentage_gain > {input_percentage}
GROUP BY
    broker_id 
1
J A 26 Июл 2017 в 18:47