2011-01-12 2 views
4

У меня есть таблица «items» mysql с двумя целыми полями: seid и tiid
Таблица имеет около 35000000 записей, поэтому она очень большая.Получить счетчик совпадений в запросе на большой таблице очень медленно

seid tiid 
----------- 
1  1 
2  2 
2  3 
2  4 
3  4 
4  1 
4  2 

В таблице есть первичный ключ в обоих полях, индекс на сейфе и индекс на tiid.

Кто-то типа в 1 или более значении tiid, и теперь я хотел бы получить seid с большинством результатов.

Например, когда кто-то вводит 1,2,3, я хотел бы получить результат 2 и 4. Они оба имеют 2 совпадения по значениям tiid.

Мой запрос до сих пор:

SELECT COUNT(*) as c, seid 
    FROM items 
WHERE tiid IN (1,2,3) 
GROUP BY seid 
HAVING c = (SELECT COUNT(*) as c, seid 
       FROM items 
      WHERE tiid IN (1,2,3) 
      GROUP BY seid 
      ORDER BY c DESC 
      LIMIT 1) 

Но этот запрос экстремально медленно, из-за большого стола.

Кто-нибудь знает, как построить лучший запрос для этой цели?

+0

Как обновить данные? Можно ли хранить скопления? – Matthew

+0

Это очень похоже на [этот вопрос] (http://stackoverflow.com/q/2015406/119477), как ни странно, принятый ответ OP был в порядке с потерей связей для max –

+0

, какой тип двигателя вы используете, а какой два поля имеют самую низкую мощность - есть что-то типа type_id? –

ответ

2

Так что я нашел 2 решения, 1-ый:

SELECT c,GROUP_CONCAT(CAST(seid AS CHAR)) as seid_list 
FROM (
    SELECT COUNT(*) as c, seid FROM items 
    WHERE tiid IN (1,2,3) 
    GROUP BY seid ORDER BY c DESC 
) T1 
GROUP BY c 
ORDER BY c DESC 
LIMIT 1; 
+---+-----------+ 
| c | seid_list | 
+---+-----------+ 
| 2 | 2,4  | 
+---+-----------+ 

Edit:

EXPLAIN SELECT c,GROUP_CONCAT(CAST(seid AS CHAR)) as seid_list FROM ( SELECT COUNT(*) as c, seid FROM items  WHERE tiid IN (1,2,3)  GROUP BY seid ORDER BY c DESC) T1 GROUP BY c ORDER BY c DESC LIMIT 1; 
+----+-------------+------------+-------+------------------+---------+---------+------+------+-----------------------------------------------------------+ 
| id | select_type | table  | type | possible_keys | key  | key_len | ref | rows | Extra              | 
+----+-------------+------------+-------+------------------+---------+---------+------+------+-----------------------------------------------------------+ 
| 1 | PRIMARY  | <derived2> | ALL | NULL    | NULL | NULL | NULL | 3 | Using filesort           | 
| 2 | DERIVED  | items  | range | PRIMARY,tiid_idx | PRIMARY | 4  | NULL | 4 | Using where; Using index; Using temporary; Using filesort | 
+----+-------------+------------+-------+------------------+---------+---------+------+------+-----------------------------------------------------------+ 

Re-Edit:

Это первое решение имеет одну проблему , с миллиардами строк результат поле может быть слишком большим.Так вот еще одно решение, которое избежать, а эффект двойной радуги, применяя clasical максимального запоминания/сверяться с переменной MySql:

SELECT c,seid 
    FROM (
    SELECT c,seid,CASE WHEN @mmax<=c THEN @mmax:=c ELSE 0 END 'mymax' 
    FROM (
     SELECT COUNT(*) as c, seid FROM items WHERE tiid IN (1,2,3) 
     GROUP BY seid 
     ORDER BY c DESC 
    ) res1 
    ,(SELECT @mmax:=0) initmax 
    ORDER BY c DESC 
) res2 WHERE mymax>0; 
+---+------+ 
| c | seid | 
+---+------+ 
| 2 | 4 | 
| 2 | 2 | 
+---+------+ 

объяснить:

+----+-------------+------------+--------+------------------+---------+---------+------+------+-----------------------------------------------------------+ 
| id | select_type | table  | type | possible_keys | key  | key_len | ref | rows | Extra              | 
+----+-------------+------------+--------+------------------+---------+---------+------+------+-----------------------------------------------------------+ 
| 1 | PRIMARY  | <derived2> | ALL | NULL    | NULL | NULL | NULL | 3 | Using where            | 
| 2 | DERIVED  | <derived4> | system | NULL    | NULL | NULL | NULL | 1 | Using filesort           | 
| 2 | DERIVED  | <derived3> | ALL | NULL    | NULL | NULL | NULL | 3 |               | 
| 4 | DERIVED  | NULL  | NULL | NULL    | NULL | NULL | NULL | NULL | No tables used           | 
| 3 | DERIVED  | items  | range | PRIMARY,tiid_idx | PRIMARY | 4  | NULL | 4 | Using where; Using index; Using temporary; Using filesort | 
+----+-------------+------------+--------+------------------+---------+---------+------+------+-----------------------------------------------------------+ 
+0

Интересный подход для получения списка в одном заявлении. – RichardTheKiwi

+0

интересно? Я выиграл у вас :-) – regilero

+0

«выиграл»? расслабляющий помощник. для SO есть больше, чем «выигрыш». +1 для использования одного запроса – RichardTheKiwi

0

Если я понять ваши требования, вы можете попробовать что-то вроде этого

select seid, tiid, count(*) from items where tiid in (1,2,3) 
group by seid, tiid 
order by seid 
+0

OP хочет элементы с max (count (*)) –

1

Предварительно рассчитать количество всех уникальных значений tiid и хранить их.

Обновить этот счет ежечасно, ежедневно или еженедельно. Или попробуйте сохранить правильность подсчета, обновив их. Это устраняет необходимость делать подсчет. Подсчеты всегда медленны.

2

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

DROP temporary table if exists TMP_COUNTED; 

create temporary table TMP_COUNTED 
select seid, COUNT(*) as C 
from items 
where tiid in (1,2,3) 
group by seid; 

CREATE INDEX IX_TMP_COUNTED on TMP_COUNTED(C); 

SELECT * 
FROM TMP_COUNTED 
WHERE C = (SELECT MAX(C) FROM seid) 
+0

Это аналогичный подход к тому, что я имел в виду. Найти самый большой счет не сложно, что будет медленно, так как поиск всех идентификаторов имеет тот же самый счет. Использование временной таблицы или двухэтапного процесса должно быть самым быстрым. – Matthew

1

У меня есть таблица под названием PRODUCT_CATEGORY, который имеет составной первичный ключ, состоящий из 2-х целого числа без знака полех и без каких-либо дополнительных вторичных индексов:

create table product_category 
(
prod_id int unsigned not null, 
cat_id mediumint unsigned not null, 
primary key (cat_id, prod_id) -- note the clustered composite index !! 
) 
engine = innodb; 

в таблице в настоящее время имеет 125 миллионов строк

select count(*) as c from product_category; 
c 
= 
125,524,947 

со следующими индексными/кардинальностями:

show indexes from product_category; 

Table    Non_unique Key_name Seq_in_index Column_name Collation Cardinality 
=====    ========== ======== ============ =========== ========= =========== 
product_category 0   PRIMARY    1 cat_id  A   1162276 
product_category 0   PRIMARY    2 prod_id  A   125525826 

Если я выполнить запрос, похожий на твой (первый запуск ничего не кэшируется и с холодными/пустыми буферами):

select 
prod_id, count(*) as c 
from 
product_category 
where 
    cat_id between 1600 and 2000 -- using between to include a wider range of data 
group by 
prod_id 
having c = (
    select count(*) as c from product_category 
    where cat_id between 1600 and 2000 
    group by prod_id order by c desc limit 1 
) 
order by prod_id; 

I получить следующие результаты:

(cold run) 
+---------+---+ 
| prod_id | c | 
+---------+---+ 
| 34957 | 4 | 
| 717812 | 4 | 
| 816612 | 4 | 
| 931111 | 4 | 
+---------+---+ 
4 rows in set (0.18 sec) 

(2nd run) 
+---------+---+ 
| prod_id | c | 
+---------+---+ 
| 34957 | 4 | 
| 717812 | 4 | 
| 816612 | 4 | 
| 931111 | 4 | 
+---------+---+ 
4 rows in set (0.14 sec) 

объяснить план выглядит следующим образом:

+----+-------------+------------------+-------+---------------+---------+---------+------+--------+-----------------------------------------------------------+ 
| id | select_type | table   | type | possible_keys | key  | key_len | ref | rows | Extra              | 
+----+-------------+------------------+-------+---------------+---------+---------+------+--------+-----------------------------------------------------------+ 
| 1 | PRIMARY  | product_category | range | PRIMARY  | PRIMARY | 3  | NULL | 194622 | Using where; Using index; Using temporary; Using filesort | 
| 2 | SUBQUERY | product_category | range | PRIMARY  | PRIMARY | 3  | NULL | 194622 | Using where; Using index; Using temporary; Using filesort | 
+----+-------------+------------------+-------+---------------+---------+---------+------+--------+-----------------------------------------------------------+ 

Если я запускаю запрос regilero в:

SELECT c,prod_id 
    FROM (
    SELECT c,prod_id,CASE WHEN @mmax<=c THEN @mmax:=c ELSE 0 END 'mymax' 
    FROM (
     SELECT COUNT(*) as c, prod_id FROM product_category WHERE 
     cat_id between 1600 and 2000 
     GROUP BY prod_id 
     ORDER BY c DESC 
    ) res1 
    ,(SELECT @mmax:=0) initmax 
    ORDER BY c DESC 
) res2 WHERE mymax>0; 

Я получаю следующие результаты:

(cold) 
+---+---------+ 
| c | prod_id | 
+---+---------+ 
| 4 | 931111 | 
| 4 | 34957 | 
| 4 | 717812 | 
| 4 | 816612 | 
+---+---------+ 
4 rows in set (0.17 sec) 

(2nd run) 
+---+---------+ 
| c | prod_id | 
+---+---------+ 
| 4 | 34957 | 
| 4 | 717812 | 
| 4 | 816612 | 
| 4 | 931111 | 
+---+---------+ 
4 rows in set (0.13 sec) 

Explain, план выглядит следующим образом:

+----+-------------+------------------+--------+---------------+---------+---------+------+--------+-----------------------------------------------------------+ 
| id | select_type | table   | type | possible_keys | key  | key_len | ref | rows | Extra              | 
+----+-------------+------------------+--------+---------------+---------+---------+------+--------+-----------------------------------------------------------+ 
| 1 | PRIMARY  | <derived2>  | ALL | NULL   | NULL | NULL | NULL | 92760 | Using where            | 
| 2 | DERIVED  | <derived4>  | system | NULL   | NULL | NULL | NULL |  1 | Using filesort           | 
| 2 | DERIVED  | <derived3>  | ALL | NULL   | NULL | NULL | NULL | 92760 |               | 
| 4 | DERIVED  | NULL    | NULL | NULL   | NULL | NULL | NULL | NULL | No tables used           | 
| 3 | DERIVED  | product_category | range | PRIMARY  | PRIMARY | 3  | NULL | 194622 | Using where; Using index; Using temporary; Using filesort | 
+----+-------------+------------------+--------+---------------+---------+---------+------+--------+-----------------------------------------------------------+ 

Наконец, попробовав кибервик I Подход:

drop procedure if exists cyberkiwi_variant; 

delimiter # 

create procedure cyberkiwi_variant() 
begin 

create temporary table tmp engine=memory 
select prod_id, count(*) as c from 
product_category where cat_id between 1600 and 2000 
group by prod_id order by c desc; 

select max(c) into @max from tmp; 

select * from tmp where c = @max; 

drop temporary table if exists tmp; 

end# 

delimiter ; 

call cyberkiwi_variant(); 

Я получаю следующие результаты:

(cold and 2nd run) 
+---------+---+ 
| prod_id | c | 
+---------+---+ 
| 816612 | 4 | 
| 931111 | 4 | 
| 34957 | 4 | 
| 717812 | 4 | 
+---------+---+ 
4 rows in set (0.14 sec) 

Explain, план выглядит следующим образом:

+----+-------------+------------------+-------+---------------+---------+---------+------+--------+-----------------------------------------------------------+ 
| id | select_type | table   | type | possible_keys | key  | key_len | ref | rows | Extra              | 
+----+-------------+------------------+-------+---------------+---------+---------+------+--------+-----------------------------------------------------------+ 
| 1 | SIMPLE  | product_category | range | PRIMARY  | PRIMARY | 3 | NULL | 194622 | Using where; Using index; Using temporary; Using filesort | 
+----+-------------+------------------+-------+---------------+---------+---------+------+--------+-----------------------------------------------------------+ 

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

Надеюсь, что это поможет - http://dev.mysql.com/doc/refman/5.0/en/innodb-index-types.html

+0

спасибо за тест на реальные данные, не могли бы вы попробовать мой первый запрос с GROUP_CONCAT, я никогда не использовал его в производстве, и мне интересно, как он будет работать - от 0,14 до 0,18, конечно :-) -? – regilero

+0

Главным моментом моего ответа всегда был дизайн таблицы, т. Е. Индексированные кластерные индексы innodb без каких-либо других поддерживающих ключей, которые могут быть такими быстрыми, по сравнению с тем, что я могу только догадываться, - это некластеризованная реализация myisam - кто говорит, что mysiam быстрее читать, чем innodb ?? : P –

+1

runtime group_concat был таким же, как и остальные :) –

Смежные вопросы