Mysql 5.5 Пользователь и друзья раздела разделов - программирование
Подтвердить что ты не робот

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

У меня есть две таблицы в моем db, у которых теперь есть миллионы строк, выбор и вставка становятся все медленнее и медленнее.

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

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

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 верное? Я знаю, что раздел может быть хорошим, только если мы получим доступ к базе ключа раздела. В моем случае я хочу читать таблицу только по id. почему в таблице разделов медленнее вставлены вставки?

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

4b9b3361

Ответ 1

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

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

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

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

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

Ответ 2

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

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

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

Я знаю, что это сумасшествие, но они могут попросить компьютер знать, что текущий идентификатор, последний id, следующий id и этот wlll занимают много времени, чем создавать идентификатор вручную. другим способом вы можете создать 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 и помните, что предел может работать и с 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, но я понимаю об этом

Ответ 3

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

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

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

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

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

например run:

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 миллиона записей, я думаю, вы сможете выжить без разделения.