14

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

我正在使用 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以便插入和选择也变得快速并且删除 FOREIGN KEYlocation_id是正确的?我知道只有在基于分区键的基础上访问分区才能很好,在我的情况下,我只想通过 id 读取表。为什么在分区表中插入速度较慢?

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

4

3 回答 3

4

首先,如果可能,我建议您升级到 5.6.5 或更高版本的 Mysql,以确保您能够正确利用分区并获得最佳性能。由于 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。

于 2012-12-05T18:32:32.717 回答
1

1.使用此sql查询选择表并排除所有列,除了id:

我回答你需要什么:

我建议你删除FOREIGN KEYPRIMARY KEY

我知道这很疯狂,但他们可以让计算机知道当前 id、最后一个 id、下一个 id 是什么,这比手动创建 id 需要更长的时间。其他方式您可以int通过 java 手动创建 id 。

使用此 sql 查询快速插入:

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

我无法决定我的查询是否可以更快地工作......

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

对于选择,在配置文件信息所在的页面中,您需要为配置文件 ID 中定义的一个用户设置一行。

如果您只需要一个并且需要多个,请使用 mysql limit ......只需为一行更改这样的限制值:

select * from user where id = 999999 limit 1;

对于七行:

select * from user where id = 999999 limit 7;

我认为这个查询会比没有查询更快limit ,记住 limit 也可以insert使用

2.朋友分区: 答案是drop主键

没有主键的表没问题

再一次,使用 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专家,但我了解它

于 2012-12-07T06:37:08.337 回答
0

1)如果您总是(或大部分)仅使用 id 来选择数据,那么显然使用此字段作为分区条件的基础。由于它是数字,因此不需要散列函数只需使用范围分区。您需要自己找到要创建多少个分区(选择哪些数字作为边界),但正如@TJChambers 之前提到的那样,大约 8-10 应该足够有效。

插入速度较慢,因为您测试错误。您只需在没有任何随机性的情况下一个接一个地插入 1000000 行,唯一的区别是分区表 mysql 需要计算哈希,这是额外的时间。但是在您的情况下,id 是分区条件的基础您将永远不会通过插入获得任何东西,因为所有新行都在表的末尾。

例如,如果您有带有 GPS 本地化的表并按 lat 和 lon 对其进行分区,例如,如果每个分区是不同的大陆,您可能会看到插入的差异。如果您有一个包含一些随机(真实)数据的表并且插入一些非线性的随机值,则会看到差异。

您对分区表的选择较慢,因为您再次测试错误。

@TJChambers 在我之前写过,您的查询需要在所有分区上工作(就像处理许多表一样),所以它延长了时间。尝试使用 where to work with 来自一个分区的数据来查看差异。

例如运行:

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 引擎,但是如果大多数查询都是 select 并且您不会进行很多写入,MyISAM 会更好。第 2 次和第 3 次运行可能因为缓存没有太大区别,但第 1 次运行有明显差异。它更快,因为我们打破了数据库设计的主要规则之一,但最终证明了这种方法的合理性,因此对于真正的大表来说它可能是一个很好的解决方案。如果您将拥有少于 1M 的记录,我认为您可以在不分区的情况下生存。

于 2012-12-09T09:37:53.877 回答