Mysql 5.5 Table partition пользователь и друзья

у меня есть две таблицы в моей БД, которые имеют миллионы строк, выбор и вставка становятся все медленнее и медленнее.

я использую 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 | 999999@yahoo.com | 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 | 999999@yahoo.com | 2012-11-27 08:03:14 |
+--------+-------------+------------------+---------------------+
1 row in set (0.00 sec)

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

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

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

3 ответов


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

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

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

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

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


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

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

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

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

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

INSERT INTO user (id,NAME,email)
VALUES ('CREATE ID WITH JAVA', 'NAME', 'EMAIL@YAHOO.COM')

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

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

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

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

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, но я понимаю об этом


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

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

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

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

@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 engine, но MyISAM лучше, если большинство запросов выбраны, и вы не собираетесь делать много записей. Нет большой разницы для 2-го и 3-го запуска, вероятно, из-за кэширования, но есть видимая разница для 1-го запуска. Это быстрее, потому что мы нарушаем одно из основных правил проектирования баз данных, но цель оправдывает средства, поэтому это может быть хорошим решением для очень большие столы. Если у вас будет меньше 1M записей, я думаю, вы сможете выжить без разделения.