Mysql 5.5表分区用户和朋友

时间:2012-11-27 15:21:29

标签: mysql partitioning sharding database-partitioning

我的数据库中有两个表现在有数百万行,选择和插入越来越慢。

我正在使用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 | 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上的FOREIGN KEY是正确的?我知道分区只有在我们访问分区键的基础上才能好,在我的情况下我只想通过id读取表。为什么在分区表中插入速度较慢?

2)分区friend表的最佳方法是什么,因为我想在user_id的基础上对朋友进行分区,因为他们希望将所有用户朋友放在同一个分区中并始终使用user_id访问它。我应该将主键放在friend.id上还是在主键中添加user_id?

3 个答案:

答案 0 :(得分:4)

首先,我建议您尽可能升级到5.6.5或更高版本的Mysql,以确保您正确地利用分区并获得最佳性能。由于GA问题,这并不总是可行的,但我的经验是5.5和5.6之间的性能存在差异,5.6提供了一些其他类型的分区。

1)我的经验是,在分区集上插入和更新速度更快,并且选择AS长,因为您包括在“查询”中进行分区的列。如果我要求计算所有分区中的所有记录,我会看到响应较慢。这是可以预料到的,因为分区正在运行LIKE单独的表,所以如果你有30个分区就像读30个表而不只是一个。

您必须在主键中包含要分区的值,并且它必须在记录的生命周期内保持稳定。

2)我会在主键中包含user_id和id - 假设你的朋友表user_id和id在记录建立后根本没有改变(即任何改变都是删除/插入)。在我的情况下,它是“多余的”,但更值得访问。您选择user_id / id还是id / user_id取决于您最常访问的内容。

最后一点。当我第一次开始将数据分成分区时,我试图创建很多分区,并且发现只有少数分区似乎达到了最佳位置--6-12个分区似乎对我来说效果最好。因人而异。

答案 1 :(得分:1)

<强> 1。使用此sql查询选择表格除了所有列,但id:

除外

我回答你的需要:

我建议您删除FOREIGN KEYPRIMARY KEY

我知道这很疯狂,但是他们可以让计算机知道当前的id,最后一个id,下一个id和这个wlll需要多长时间才能手动创建id。 另外,您可以通过java手动创建int id。

使用此sql查询快速插入:

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

我无法确定我的查询是否可以更快地运行...

因为所有都取决于您的计算机性能,请确保您在服务器上使用它,因为服务器可以快速完成所有任务。

并且对于选择,在页面中,个人资料信息位于您需要一行用于在个人资料ID中定义的一个用户。

使用mysql限制,如果你只需要一个,如果你需要多个... 只需像这样更改限制值 一排:

select * from user where id = 999999 limit 1;

和七行:

select * from user where id = 999999 limit 7;

我认为此查询比没有limit的查询更快 并记住限制也适用于insert

<强> 2。对于朋友分区: 答案是删除主键

没有主键的表没问题

再次使用java创建id ... 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的专家,但我理解它

答案 2 :(得分:0)

1)如果您始终(或大部分)仅使用id来选择数据,那么使用此字段作为分区条件的基础是显而易见的。因为它是数字,所以不需要哈希函数只需使用range partitioning。您需要自己找到多少个分区(选择哪些数字作为边框),但正如@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)这个很难。没有数据冗余就没有办法对它进行分区(至少我不介意)但是如果访问时间(选择速度)是最重要的,最好的方法可能是以与用户表相同的方式对其进行分区(范围在其中一个id)并为每个关系插入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会更好。 由于缓存,第二次和第三次运行没有太大区别,但第一次运行有明显差异。它更快,因为我们打破了数据库设计的主要规则之一,但最终证明了手段,因此它可能是真正大表的好解决方案。如果您的记录少于1M,我认为您可以在没有分区的情况下生存。

相关问题