Як пришвидшити запити у великій таблиці на 220 мільйонів рядків (дані 9 гігів)?


31

Питання:

У нас є соціальний сайт, де члени можуть оцінювати один одного за сумісність або відповідність. Ця user_match_ratingsтаблиця містить понад 220 мільйонів рядків (9 гиг даних або майже 20 гігів в індексах). Запити щодо цієї таблиці зазвичай відображаються в slow.log (поріг> 2 секунди) і є найчастіше запитуваним повільним запитом у системі:

Query_time: 3  Lock_time: 0  Rows_sent: 3  Rows_examined: 1051
"select rating, count(*) as tally from user_match_ratings where rated_user_id = 395357 group by rating;"

Query_time: 4  Lock_time: 0  Rows_sent: 3  Rows_examined: 1294
"select rating, count(*) as tally from user_match_ratings where rated_user_id = 4182969 group by rating;"

Query_time: 3  Lock_time: 0  Rows_sent: 3  Rows_examined: 446
"select rating, count(*) as tally from user_match_ratings where rated_user_id = 630148 group by rating;"

Query_time: 5  Lock_time: 0  Rows_sent: 3  Rows_examined: 3788
"select rating, count(*) as tally from user_match_ratings where rated_user_id = 1835698 group by rating;"

Query_time: 17  Lock_time: 0  Rows_sent: 3  Rows_examined: 4311
"select rating, count(*) as tally from user_match_ratings where rated_user_id = 1269322 group by rating;"

Версія MySQL:

  • версія протоколу: 10
  • версія: 5.0.77-лог
  • версія bdb: Програмне забезпечення Sleepycat: Berkeley DB 4.1.24: (29 січня 2009 р.)
  • версія для компіляції версій: x86_64 version_compile_os: redhat-linux-gnu

Інформація про таблицю:

SHOW COLUMNS FROM user_match_ratings;

Дає:

╔═══════════════╦════════════╦════╦═════╦════════╦════════════════╗
 id             int(11)     NO  PRI  NULL    auto_increment 
 rater_user_id  int(11)     NO  MUL  NULL                   
 rated_user_id  int(11)     NO  MUL  NULL                   
 rating         varchar(1)  NO       NULL                   
 created_at     datetime    NO       NULL                   
╚═══════════════╩════════════╩════╩═════╩════════╩════════════════╝

Зразок запиту:

select * from mutual_match_ratings where id=221673540;

дає:

╔═══════════╦═══════════════╦═══════════════╦════════╦══════════════════════╗
 id         rater_user_id  rated_user_id  rating  created_at           
╠═══════════╬═══════════════╬═══════════════╬════════╬══════════════════════╣
 221673540  5699713        3890950        N       2013-04-09 13:00:38  
╚═══════════╩═══════════════╩═══════════════╩════════╩══════════════════════╝

Покажчики

У таблиці встановлено 3 індекси:

  1. єдиний індекс на rated_user_id
  2. складений індекс на rater_user_idіcreated_at
  3. складений індекс на rated_user_idіrater_user_id
показати індекс від user_match_ratings;

дає:

╔════════════════════╦════════════╦═══════════════════════════╦══════════════╦═══════════════╦═══════════╦═════════════╦══════════╦════════╦═════════════════════════╦════════════╦══════════════════╗
 Table               Non_unique  Key_name                   Seq_in_index  Column_name    Collation  Cardinality  Sub_part  Packed  Null                     Index_type  Comment          
╠════════════════════╬════════════╬═══════════════════════════╬══════════════╬═══════════════╬═══════════╬═════════════╬══════════╬════════╬═════════════════════════╬════════════╬══════════════════╣
 user_match_ratings  0           PRIMARY                    1             id             A          220781193    NULL      NULL    BTREE                                                 
 user_match_ratings  1           user_match_ratings_index1  1             rater_user_id  A          11039059     NULL      NULL    BTREE                                                 
 user_match_ratings  1           user_match_ratings_index1  2             created_at     A          220781193    NULL      NULL    BTREE                                                 
 user_match_ratings  1           user_match_ratings_index2  1             rated_user_id  A          4014203      NULL      NULL    BTREE                                                 
 user_match_ratings  1           user_match_ratings_index2  2             rater_user_id  A          220781193    NULL      NULL    BTREE                                                 
 user_match_ratings  1           user_match_ratings_index3  1             rated_user_id  A          2480687      NULL      NULL    BTREE                                                 
╚════════════════════╩════════════╩═══════════════════════════╩══════════════╩═══════════════╩═══════════╩═════════════╩══════════╩════════╩═════════════════════════╩════════════╩══════════════════╝

Навіть із показниками ці запити повільні.

Моє запитання:

Чи розділення цієї таблиці / даних на іншу базу даних на сервері, яка має достатню кількість оперативної пам’яті для зберігання цих даних у пам'яті, прискорить це запити? Чи є все-таки щось, що таблиці / індекси створені, що ми можемо вдосконалити, щоб зробити ці запити швидшими?

На даний момент у нас є 16 Гб пам'яті; однак ми розглядаємо або модернізувати існуючу машину до 32 ГБ, або додати нову машину принаймні стільки, можливо, і твердотільні накопичувачі.


1
Ваше запитання неймовірне. Я дуже зацікавлений у вашому поточному рішенні, що як вам вдалося отримати результат за <= 2 секунди? Тому що у мене є одна таблиця, яка містить всього 20 мільйонів записів, і все одно вона займає 30 секунд SELECT QUERY. Скажіть, будь ласка? PS Ваше запитання змусило мене приєднатися до цієї спільноти (y);)
NullPointer

2
Подивіться на індекси таблиці, в яку ви запитуєте .. часто можна вдосконалити запити, створивши відповідний індекс. Не завжди, але часто зустрічаються випадки, коли запити робляться швидко шляхом надання індексу проти стовпців у пункті де в запиті. Особливо, якщо стіл зростає і більший.
Ranknoodle

Звичайно @Ranknoodle. Дякую. Я перевірю відповідно.
NullPointer

Відповіді:


28

Думки про проблему, викинуті у випадковому порядку:

  • Очевидний індекс для цього запиту: (rated_user_id, rating). Запит, який отримує дані лише для одного мільйона користувачів та потребує 17 секунд, робить щось не так: читати з (rated_user_id, rater_user_id)індексу, а потім читати з таблиці значення (сотні до тисяч) для ratingстовпця, як ratingнемає в жодному індексі. Отже, запит повинен прочитати багато рядків таблиці, які знаходяться в різних місцях диска.

  • Перш ніж почати додавати численні індекси в таблиці, спробуйте проаналізувати продуктивність всієї бази даних, всього набору повільних запитів, ще раз вивчіть вибір типів даних, двигун, який ви використовуєте, та налаштування конфігурації.

  • Розгляньте можливість переходу до нової версії MySQL, 5.1, 5.5 або навіть 5.6 (також: версії Percona та MariaDB.) Деякі переваги як помилок виправлено, оптимізатор покращився, і ви можете встановити низький поріг для повільних запитів менше ніж на 1 секунду (наприклад, 10 мілісекунд). Це дасть вам набагато кращу інформацію про повільні запити.

  • Вибір для типу даних ratingє дивним. VARCHAR(1)? Чому ні CHAR(1)? Чому ні TINYINT? Це дозволить заощадити трохи місця як у таблиці, так і в індексах, які (будуть) містити цей стовпець. Для стовпчика varchar (1) потрібен ще один байт над char (1), і якщо вони є utf8, стовпцям (var) char буде потрібно 3 (або 4) байти замість 1 (tinyint).


2
Скільки впливає на продуктивність або витрачається на зберігання у%, якщо ви використовуєте неправильний тип даних?
FlyingAtom

1
@FlyingAtom Це залежить від випадку, але для деяких індексованих стовпців, які все ще потрібно сканувати (наприклад, коли у вас немає пункту де, але ви лише отримуєте цей стовпець), двигун може вирішити сканувати індекс замість таблиці, і якщо ви оптимізуєте тип даних на половину розміру, то сканування було б удвічі швидшим, а відповідь - удвічі меншим. Якщо ви все ще скануєте таблицю замість індексу (наприклад, коли ви отримуєте більше стовпців, а не лише тих, що є в індексі), вигоди будуть менш значущими.
Себастьян Гріньолі

-1

Я обробляв таблиці для німецького уряду з часом 60 мільйонів записів.

У нас було багато цих таблиць.

І нам потрібно було багато разів знати загальні рядки з таблиці.

Після розмови з програмістами Oracle та Microsoft ми були не такі щасливі ...

Тож ми, група програмістів баз даних, вирішили, що в кожну таблицю записується один завжди запис, в якому зберігається загальна кількість записів. Ми оновили це число, залежно від рядків INSERT або DELETE.

Ми спробували всі інші способи. Це, безумовно, найшвидший спосіб.

Ми використовуємо цей спосіб зараз з 1998 року і ніколи не мали жодної неправильної кількості рядків у всіх наших багатомільйонних таблицях записів.


7
Я б запропонував розглянути деякі особливості, впроваджені за останні 18 років. Серед інших, count(*)має деякі вдосконалення.
дезсо

Звідки ви знаєте, що ви ніколи не мали неправильного числа, якщо не могли їх порахувати? uhmmmm ...
Tonca

-3

Я спробую розділити на рейтингові типи, наприклад:

взаємний_матч_ратівс_, взаємний_мач_ратівс_ і т.д.

Ви повинні виконати запит для кожного типу, але, можливо, це швидше, ніж в інший спосіб. Спробувати.

Це передбачає, що у вас є фіксована кількість типів рейтингу, і що вам не потрібна ця таблиця для інших запитів, які були б найгіршими з цією новою структурою.

Якщо це так, вам слід шукати інший підхід або підтримувати дві копії таблиці (початкова таблиця та розділена), якщо це доступно з точки зору простору та ремонтопридатності (або логіки програми).

Використовуючи наш веб-сайт, ви визнаєте, що прочитали та зрозуміли наші Політику щодо файлів cookie та Політику конфіденційності.
Licensed under cc by-sa 3.0 with attribution required.