Хобрук: Ваш путь к мастерству в программировании

Пользователь и друзья раздела таблицы Mysql 5.5

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

Я использую spring + hibernate + mysql 5.5 и читаю о сегментировании, а также о разбиении таблицы, и мне нравится идея разбиения моих таблиц,

Моя текущая структура БД похожа на

CREATE TABLE `user` (
  `id` BIGINT(20) NOT NULL,
  `name` VARCHAR(255) DEFAULT NULL,
  `email` VARCHAR(255) DEFAULT NULL,
  `location_id` bigint(20) default NULL,
  `updated_time` TIMESTAMP DEFAULT CURRENT_TIMESTAMP ON UPDATE CURRENT_TIMESTAMP,
  PRIMARY KEY  (`id`),
  KEY `FK3DC99772C476E06B` (`location_id`),
  CONSTRAINT `FK3DC99772C476E06B` FOREIGN KEY (`location_id`) REFERENCES `places` (`id`) 
) ENGINE=INNODB DEFAULT CHARSET=utf8


CREATE TABLE `friends` (
  `id` BIGINT(20) NOT NULL AUTO_INCREMENT,
  `user_id` BIGINT(20) DEFAULT NULL,
  `friend_id` BIGINT(20) DEFAULT NULL,
  `updated_time` TIMESTAMP DEFAULT CURRENT_TIMESTAMP ON UPDATE CURRENT_TIMESTAMP,
  PRIMARY KEY  (`id`),
  UNIQUE KEY `unique_friend` (`user_id`,`friend_id`)
) ENGINE=INNODB DEFAULT CHARSET=utf8

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

CREATE TABLE `user_partition` (
  `id` BIGINT(20) NOT NULL,
  `name` VARCHAR(255) DEFAULT NULL,
  `email` VARCHAR(255) DEFAULT NULL,
  `location_id` bigint(20) default NULL,
  `updated_time` TIMESTAMP DEFAULT CURRENT_TIMESTAMP ON UPDATE CURRENT_TIMESTAMP,
  PRIMARY KEY  (`id`),
  KEY `FK3DC99772C476E06B` (`location_id`) 
) ENGINE=INNODB DEFAULT CHARSET=utf8
PARTITION BY HASH(id DIV 100000)
PARTITIONS 30;

Я создал процедуры для загрузки данных в две таблицы и проверки производительности двух таблиц.

DELIMITER //
CREATE PROCEDURE load_partition_table()
BEGIN
DECLARE v INT DEFAULT 0;
    WHILE v < 1000000
    DO
    INSERT INTO user_partition (id,NAME,email)
    VALUES (v,CONCAT(v,' name'),CONCAT(v,'@yahoo.com')),
    (v+1,CONCAT(v+1,' name'),CONCAT(v+1,'@yahoo.com')),
    (v+2,CONCAT(v+2,' name'),CONCAT(v+2,'@yahoo.com')),
    (v+3,CONCAT(v+3,' name'),CONCAT(v+3,'@yahoo.com')),
    (v+4,CONCAT(v+4,' name'),CONCAT(v+4,'@yahoo.com')),
    (v+5,CONCAT(v+5,' name'),CONCAT(v+5,'@yahoo.com')),
    (v+6,CONCAT(v+6,' name'),CONCAT(v+6,'@yahoo.com')),
    (v+7,CONCAT(v+7,' name'),CONCAT(v+7,'@yahoo.com')),
    (v+8,CONCAT(v+8,' name'),CONCAT(v+8,'@yahoo.com')),
    (v+9,CONCAT(v+9,' name'),CONCAT(v+9,'@yahoo.com'))
    ;
    SET v = v + 10;
    END WHILE;
    END
    //

CREATE PROCEDURE load_table()
BEGIN
DECLARE v INT DEFAULT 0;
    WHILE v < 1000000
    DO
    INSERT INTO user (id,NAME,email)
    VALUES (v,CONCAT(v,' name'),CONCAT(v,'@yahoo.com')),
    (v+1,CONCAT(v+1,' name'),CONCAT(v+1,'@yahoo.com')),
    (v+2,CONCAT(v+2,' name'),CONCAT(v+2,'@yahoo.com')),
    (v+3,CONCAT(v+3,' name'),CONCAT(v+3,'@yahoo.com')),
    (v+4,CONCAT(v+4,' name'),CONCAT(v+4,'@yahoo.com')),
    (v+5,CONCAT(v+5,' name'),CONCAT(v+5,'@yahoo.com')),
    (v+6,CONCAT(v+6,' name'),CONCAT(v+6,'@yahoo.com')),
    (v+7,CONCAT(v+7,' name'),CONCAT(v+7,'@yahoo.com')),
    (v+8,CONCAT(v+8,' name'),CONCAT(v+8,'@yahoo.com')),
    (v+9,CONCAT(v+9,' name'),CONCAT(v+9,'@yahoo.com'))
    ;
    SET v = v + 10;
    END WHILE;
    END
    //

Результаты были неожиданными, вставка/выбор в таблице без разделов давала лучшие результаты.

mysql> select count(*) from user_partition;
+----------+
| count(*) |
+----------+
|  1000000 |
+----------+
1 row in set (0.40 sec)

mysql> select count(*) from user;
+----------+
| count(*) |
+----------+
|  1000000 |
+----------+
1 row in set (0.00 sec)


mysql> call load_table();
Query OK, 10 rows affected (20.31 sec)

mysql> call load_partition_table();
Query OK, 10 rows affected (21.22 sec)

mysql> select * from user where id = 999999;
+--------+-------------+------------------+---------------------+
| id     | name        | email            | updated_time        |
+--------+-------------+------------------+---------------------+
| 999999 | 999999 name | [email protected] | 2012-11-27 08:06:54 |
+--------+-------------+------------------+---------------------+
1 row in set (0.00 sec)

mysql> select * from user_no_part where id = 999999;
+--------+-------------+------------------+---------------------+
| id     | name        | email            | updated_time        |
+--------+-------------+------------------+---------------------+
| 999999 | 999999 name | [email protected] | 2012-11-27 08:03:14 |
+--------+-------------+------------------+---------------------+
1 row in set (0.00 sec)

Итак, два вопроса

1) Как лучше всего разделить таблицу user, чтобы вставки и выборки также выполнялись быстро, а удаление FOREIGN KEY на location_id было правильным? Я знаю, что раздел может быть хорошим, только если мы получаем доступ на основе ключа раздела. В моем случае я хочу читать таблицу только по идентификатору. почему вставки медленнее в таблице разделов?

2) Как лучше всего разделить таблицу friend, так как я хочу разделить друзей на основе user_id, так как хочу поместить всех друзей пользователей в один раздел и всегда получать к нему доступ, используя user_id. Должен ли я удалить первичный ключ на friend.id или добавить user_id в первичный ключ?


  • Пост не показывает заметной разницы в производительности; разница в одну секунду при вставке миллиона записей довольно незначительна (~ 0,5%). 09.12.2012
  • Насколько я знаю, чтобы ускорить SELECT, индексирование является правильным решение. Есть некоторые советы по ускорению INSERT, Кроме того, основная линия заключается в том, чтобы делать объемные вставки. 09.12.2012

Ответы:


1

Во-первых, я бы порекомендовал, если это возможно, обновить Mysql до 5.6.5 или более поздней версии, чтобы убедиться, что вы используете преимущества разбиения правильно и с максимальной производительностью. Это не всегда возможно из-за проблем с GA, но мой опыт показывает, что между 5.5 и 5.6 была разница в производительности, а 5.6 предлагает некоторые другие типы разбиения.

1) Мой опыт показывает, что вставки и обновления выполняются быстрее в разделенных наборах, а также выбираются, ПОКА ВЫ ВКЛЮЧАЕТЕ СТОЛБЦ, КОТОРЫЙ ВЫ РАЗДЕЛЯЕТЕ В ЗАПРОС. Если я запрашиваю подсчет всех записей во всех разделах, я вижу более медленные ответы. Этого следовало ожидать, потому что разделы функционируют КАК отдельные таблицы, поэтому, если у вас есть 30 разделов, это похоже на чтение 30 таблиц, а не только одной.

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

2) Я бы включил user_id и id в первичный ключ, предполагая, что таблицы ваших друзей user_id и id вообще не меняются после установления записи (т. е. любое изменение будет удалением/вставкой). В моем случае это было «избыточно», но более чем стоило доступа. Выберете ли вы user_id/id или id/user_id, зависит от вашего наиболее частого доступа.

Последнее замечание. Я пытался создать МНОГО разделов, когда впервые начал разбивать свои данные на разделы, и обнаружил, что лишь немногие из них, казалось, попали в золотую середину — 6-12 разделов, казалось, работали лучше всего для меня. YMMV.

05.12.2012
  • Спасибо, но тогда зачем вставлять в таблицу разделов больше времени в моем тестовом примере, я делаю что-то не так. Обычно я выбираю друзей на основе некоторого user_id и никогда не использовал поле id 06.12.2012
  • Мне нужно посмотреть, как выглядит ваша вставка, но я согласен с вами, что это займет меньше времени, а не больше. Я предполагаю, что вы пробовали их в последней версии MySQL? 06.12.2012
  • mysql версии 5.5 я использовал 07.12.2012
  • В 5.5 обрезка не делалась на INSERT, отсюда и медлительность. 26.11.2015

  • 2

    <сильный>1. Используйте этот SQL-запрос для выбора таблицы и исключения всех столбцов, кроме идентификатора:

    Я отвечаю, что вам нужно:

    Я предлагаю вам удалить FOREIGN KEY и PRIMARY KEY

    Я знаю, что это сумасшествие, но они могут запросить у компьютера текущий идентификатор, последний идентификатор, следующий идентификатор, и это займет больше времени, чем создание идентификатора вручную. в противном случае вы можете создать int id вручную с помощью java.

    используйте этот SQL-запрос для быстрой вставки:

    INSERT INTO user (id,NAME,email)
    VALUES ('CREATE ID WITH JAVA', 'NAME', '[email protected]')
    

    Я не могу решить, мой запрос может работать быстрее или нет...

    Поскольку все зависит от производительности вашего компьютера, убедитесь, что вы используете его на сервере, потому что сервер может быстро выполнить все задачи.

    а для выбора на странице, где находится информация о профиле, вам понадобится одна строка для одного пользователя, определенного в идентификаторе профиля.

    используйте предел mysql, если вам нужен только один и если вам нужно более одного... Просто измените значения предела, подобные этому, для одной строки:

    select * from user where id = 999999 limit 1;
    

    и для семи рядов:

    select * from user where id = 999999 limit 7;
    

    Я думаю, что этот запрос будет работать быстрее, чем без limit, и помните, что limit может работать и с insert

    <сильный>2. Для дружеского раздела: ответ — удалить первичный ключ

    Таблица без первичного ключа не проблема

    Еще раз создайте идентификатор с помощью java... java разработан, чтобы быть быстрее в интерфейсе, и ваш код включает while, и java может это сделать. Например, вам нужно получить все данные о ваших друзьях... используйте этот запрос, чтобы работать быстрее:

    select fr.friend_id, usr.* from friends as fr INNER JOIN user as usr 
    ON dr.friend_id = usr.id
    where fr.user_id = 999999 LIMIT 10;
    

    и я думаю, что этого достаточно, извините, я могу объяснить только mysql, а не java. Потому что я не эксперт в java, но я разбираюсь в этом.

    07.12.2012
  • демонстративно это помогло, но не дало полного ответа, можете ли вы обновить свой ответ для 1 и 2. 07.12.2012
  • Во-первых, какой язык программирования вы использовали? 07.12.2012
  • java, но вы можете использовать любой язык, так как это вопрос mysql 07.12.2012
  • можете ли вы обновить ответ 1 и 2, чтобы я мог отметить ответ и дать вам награду 07.12.2012
  • сбросить первичный_ключ?? это какая-то ересь. Без выбора индекса будет значительно медленнее, и если вы собираетесь сгенерировать идентификатор в Java, может случиться так, что 2 пользователя попытаются вставить строки с одним и тем же идентификатором, mysql все время сохраняет значение для нового идентификатора в памяти, поэтому будет достаточно быстро, чтобы оставить это задача для dbms. 09.12.2012
  • Удаление AUTO_INCREMENT ПК на friends приведет к тому, что индекс UNIQUE станет ПК. Это намного эффективнее как для INSERTs, так и для SELECTs. INSERT: В вашем коде есть 2 BTrees, которые нужно обновить INSERT; без id есть только 1. SELECT: выборка через вторичный индекс требует затем обращения к PK. 26.11.2015

  • 3

    1) Если вы всегда (или в основном) используете только идентификатор для выбора данных, очевидно, что это поле следует использовать в качестве основы для условия разделения. Поскольку это число, нет необходимости в хеш-функции, просто используйте разделение диапазона . Сколько разделов создать (какие числа выбрать в качестве границ) вам нужно найти самостоятельно, но, как упоминал @TJChambers ранее, около 8-10 должно быть достаточно эффективно.

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

    Если бы у вас была, например, таблица с локализациями GPS, и вы разделили ее по широте и долготе, вы могли бы увидеть разницу во вставке, если бы, например, каждый раздел был другим континентом. И разница была бы видна, если бы у вас была таблица с некоторыми случайными (реальными) данными и вы вставляли некоторые случайные значения, не линейные.

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

    @TJChambers писал об этом до меня: ваш запрос должен работать на всех разделах (это похоже на работу со многими таблицами), поэтому он увеличивает время. Попробуйте использовать где работать с данными только из одного раздела, чтобы увидеть разницу.

    например запустить:

    select count(*) from user_partition where id<99999;
    

    и

    select count(*) from user where id<99999;
    

    Вы увидите разницу.

    2) Это сложно. Невозможно разделить его без избыточности данных (по крайней мере, мне не приходит в голову идея), но если время доступа (выбор скорости) является наиболее важным, лучшим способом может быть разделение его так же, как пользовательская таблица (диапазон на один из идентификаторов) и вставьте 2 строки для каждого отношения (a, b) и (b, a). Это удвоит количество строк, но если вы разделите более чем на 4 части, вы все равно будете работать с меньшим количеством записей на запрос, и у вас будет только одно условие для проверки отсутствия необходимости или.

    Я тестировал его с этой схемой

    CREATE TABLE `test`.`friends` (
    `a` INT NOT NULL ,
    `b` INT NOT NULL ,
    INDEX ( `a` ),
    INDEX ( `b` )
    ) ENGINE = InnoDB;
    
    CREATE TABLE `test`.`friends_part` (
    `a` INT NOT NULL ,
    `b` INT NOT NULL ,
    INDEX ( `a` , `b` )
    ) ENGINE = InnoDB
    PARTITION BY RANGE (a) (
        PARTITION p0 VALUES LESS THAN (1000),
        PARTITION p1 VALUES LESS THAN (2000),
        PARTITION p2 VALUES LESS THAN (3000),
        PARTITION p3 VALUES LESS THAN (4000),
        PARTITION p4 VALUES LESS THAN (5000),
        PARTITION p5 VALUES LESS THAN (6000),
        PARTITION p6 VALUES LESS THAN (7000),
        PARTITION p7 VALUES LESS THAN (8000),
        PARTITION p8 VALUES LESS THAN (9000),
        PARTITION p9 VALUES LESS THAN MAXVALUE
    );
    
    delimiter //
    DROP procedure IF EXISTS fill_friends//
    create procedure fill_friends()
    begin
        declare i int default 0;
        declare a int;
        declare b int;
        while i<2000000
        do
        set a = rand()*10000;
        set b = rand()*10000;
        insert into friends values(a,b);
        set i = i + 1;
        end while;
    end
    //
    delimiter ;
    
    delimiter //
    DROP procedure IF EXISTS fill_friends_part//
    create procedure fill_friends_part()
    begin
        insert into friends_part (select a,b from friends);
        insert into friends_part (select b as a, a as b from friends);
    end
    //
    delimiter ;
    

    Я выполнил следующие запросы:

    select * from friends where a=317 or b=317;
    

    набор результатов: 475 раз: 1,43, 0,02, 0,01

    select * from friends_part where a=317;
    

    набор результатов: 475 раз: 0,10, 0,00, 0,00

    select * from friends where a=4887 or b=4887;
    

    набор результатов: 483 раза: 1,33, 0,01, 0,01

    select * from friends_part where a=4887;
    

    набор результатов: 483 раза: 0,06, 0,01, 0,00

    Я не беспокоился об уникальности данных, но в вашем примере вы можете использовать уникальный индекс. Также я использовал движок InnoDB, но MyISAM лучше, если большинство запросов выбираются и вы не собираетесь делать много операций записи. Нет большой разницы для 2-го и 3-го прогона, вероятно, из-за кэширования, но есть видимая разница для 1-го прогона. Это быстрее, потому что мы нарушаем одно из основных правил проектирования баз данных, но цель оправдывает средства, поэтому это может быть хорошим решением для действительно больших таблиц. Если у вас будет менее 1 млн записей, я думаю, вы сможете выжить без разделения.

    09.12.2012
    Новые материалы

    Dall-E 2: недавние исследования показывают недостатки в искусстве, созданном искусственным интеллектом
    DALL-E 2 — это всеобщее внимание в индустрии искусственного интеллекта. Люди в списке ожидания пытаются заполучить продукт. Что это означает для развития креативной индустрии? О применении ИИ в..

    «Очень простой» эволюционный подход к обучению с подкреплением
    В прошлом семестре я посетил лекцию по обучению с подкреплением (RL) в моем университете. Честно говоря, я присоединился к нему официально, но я редко ходил на лекции, потому что в целом я нахожу..

    Освоение информационного поиска: создание интеллектуальных поисковых систем (глава 1)
    Глава 1. Поиск по ключевым словам: основы информационного поиска Справочная глава: «Оценка моделей поиска информации: подробное руководство по показателям производительности » Глава 1: «Поиск..

    Фишинг — Упаковано и зашифровано
    Будучи старшим ИТ-специалистом в небольшой фирме, я могу делать много разных вещей. Одна из этих вещей: специалист по кибербезопасности. Мне нравится это делать, потому что в настоящее время я..

    ВЫ РЕГРЕСС ЭТО?
    Чтобы понять, когда использовать регрессионный анализ, мы должны сначала понять, что именно он делает. Вот простой ответ, который появляется, когда вы используете Google: Регрессионный..

    Не зря же это называют интеллектом
    Стек — C#, Oracle Опыт — 4 года Работа — Разведывательный корпус Мне пора служить Может быть, я немного приукрашиваю себя, но там, где я живу, есть обязательная военная служба на 3..

    LeetCode Проблема 41. Первый пропущенный положительный результат
    LeetCode Проблема 41. Первый пропущенный положительный результат Учитывая несортированный массив целых чисел, найдите наименьшее пропущенное положительное целое число. Пример 1: Input:..