Як пришвидшити запити у великій таблиці на 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.