У меня проблема с моей таблицей базы данных mysql. У меня более 20 миллионов строк в таблице. Структура таблицы показана ниже. Основная проблема заключается в том, что запросы занимают очень много времени для выполнения (некоторые запросы занимают более 20 секунд). Я использую индексы, где я могу, однако многие запросы используют диапазон дат и с диапазоном дат мои индексы не работают. Также в запросах я использую почти каждый столбец. Что мне нужно изменить в моей таблице данных, чтобы повысить эффективность?
'history' (
'id' int(11) NOT NULL AUTO_INCREMENT,
'barcode' varchar(100) DEFAULT NULL,
'bag' varchar(100) DEFAULT NULL,
'action' int(10) unsigned DEFAULT NULL,
'place' int(10) unsigned DEFAULT NULL,
'price' decimal(10,2) DEFAULT NULL,
'old_price' decimal(10,2) DEFAULT NULL,
'user' int(11) DEFAULT NULL,
'amount' int(10) DEFAULT NULL,
'rotation' int(10) unsigned DEFAULT NULL,
'discount' decimal(10,2) DEFAULT NULL,
'discount_type' tinyint(2) unsigned DEFAULT NULL,
'original' int(10) unsigned DEFAULT NULL,
'was_in_shop' int(10) unsigned DEFAULT NULL,
'cate' int(10) unsigned DEFAULT NULL COMMENT 'grupe',
'sub_cate' int(10) unsigned DEFAULT NULL,
'comment' varchar(255) DEFAULT NULL,
'helper' varchar(255) DEFAULT NULL,
'ywd' varchar(255) DEFAULT NULL,
'created_at' timestamp NULL DEFAULT NULL,
'updated_at' timestamp NULL DEFAULT NULL ON UPDATE CURRENT_TIMESTAMP,
'deleted_at' timestamp NULL DEFAULT NULL
)
PRIMARY KEY ('id'),
KEY 'barcode' ('barcode') USING BTREE,
KEY 'action' ('action') USING BTREE,
KEY 'original' ('original') USING BTREE,
KEY 'created_at' ('created_at') USING BTREE,
KEY 'bag' ('bag') USING BTREE
ENGINE=InnoDB
Некоторые из моих запросов:
select SUM(amount) as amount,
SUM(comment) as price,
cate
from 'history'
where ( 'action' = '4'
and 'place' = '28'
and 'created_at' >= '2018-04-01 00:00:00'
and 'created_at' <= '2018-04-30 23:59:59'
)
and 'history'.'deleted_at' is null
group by 'cate';
select cate,
SUM(amount) AS kiekis,
SUM(IF(discount>0,(price*amount)-discount,(price*amount))) AS suma,
SUM(IF(discount>0,IF(discount_type=1,(discount*price)/100,discount),0)) AS nuolaida
from 'history'
where ( 'history'.'action' = '4'
and 'history'.'created_at' >= '2018-01-01 00:00:00'
and 'history'.'created_at' <= '2018-01-23 23:59:59'
)
and LENGTH(barcode) > 7
and 'history'.'deleted_at' is null
group by 'cate';
INDEX(a), INDEX(b)
выполняет некоторые цели, но "составной" INDEX(a,b)
лучше обслуживает некоторые запросы.
where ( 'action' = '4'
and 'place' = '28'
and 'created_at' >= '2018-04-01 00:00:00'
and 'created_at' <= '2018-04-30 23:59:59'
)
and 'history'.'deleted_at' is null
потребности
INDEX(action, place, -- first, but in either order
deleted_at,
created_at) -- last
Я предпочитаю писать диапазон дат таким образом:
and 'history'.'created_at' >= '2018-04-01'
and 'history'.'created_at' < '2018-04-01' + INTERVAL 1 MONTH
Это намного проще, чем заниматься високосным годом, концом года и т.д. И он работает "правильно" для DATE
, DATETIME
, DATETIME(6)
, TIMESTAMP
и TIMESTAMP(6)
.
За это
where ( 'history'.'action' = '4'
and 'history'.'created_at' >= '2018-01-01 00:00:00'
and 'history'.'created_at' <= '2018-01-23 23:59:59'
)
and LENGTH(barcode) > 7
and 'history'.'deleted_at' is null
Я бы постарался это как наиболее вероятно:
INDEX(action, deleted_at, created_at) -- in this order
У вас нет отдельных таблиц для отдельных лет. Если вы удалите старые данные, рассмотрите PARTITION BY RANGE(TO_DAYS(...))
, чтобы получить скорость DROP PARTITION
. (Но это еще одна дискуссия.)
Ваш первый запрос лучше написан как:
select SUM(h.amount) as amount,
SUM(h.comment) as price,
h.cate
from history h
where h.action = 4 and
h.place = 28 and
h.created_at >= '2018-04-01' and
h.created_at < '2018-05-01' and
h.deleted_at is null
group by h.cate;
Зачем?
place
и action
- это числа. Сравнение должно состоять из числа. Типы смешивания могут препятствовать использованию индексов. Затем для этого запроса разумным индексом является history(action, place, created_at, deleted_at)
.
Итак, я бы начал с индексов с несколькими столбцами.
Если у вас по-прежнему возникают проблемы с производительностью, вам следует рассмотреть возможность разделения данных на основе даты created_at
.
Если бы я был в вашей ситуации, я бы назвал имя вычисленной базы данных. Под этим я имею в виду несколько таблиц history_X, где X - это int, связанный с контентом.
Поскольку это таблица истории, можно ли включить часть даты в название?
Вы сказали, что используете диапазоны для поиска данных, поэтому, если бы вы использовали год в названии таблицы, вы могли бы
Затем вы можете выполнить поиск с таблицей, которая относится к вашему диапазону дат.
Если вам нужна дата из диапазона, который распространяется на таблицы, тогда вы можете использовать запрос UNION для объединения двух наборов результатов в один.
id
в запросах, а это просто суррогатный ключ? Если это так, то вы можете вместо этого сделатьid
уникальным ограничением и изменить свой первичный ключ на что-то, что поможет больше при извлечении данных обратно. Первичный ключ определяет, в каком порядке физически хранятся данные (кластеризованный индекс), поэтому, если вы всегда запрашиваете штрих-код, то может быть, имеет смысл использовать его в качестве первичного ключа?