我的数据库中有两个表现在有数百万行,选择和插入越来越慢。
我正在使用 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
上的 FOREIGN KEY 是否正确?我知道分区只有在我们基于分区键访问时才能很好,在我的例子中,我只想通过 id 读取表。为什么分区表中的插入速度较慢?
2) 分区friend
表的最佳方法是什么,因为我想根据user_id
对 friend 进行分区,因为我想将所有用户 friend 放在同一个分区中,并且始终使用 user_id 访问它。我应该将主键放在 friend.id 上还是在主键中添加 user_id?
最佳答案
首先,如果可能,我建议您升级到 5.6.5 或更高版本的 Mysql,以确保您能够正确利用分区并获得最佳性能。由于 GA 问题,这并不总是可能的,但我的经验是 5.5 和 5.6 之间的性能存在差异,并且 5.6 提供了一些其他类型的分区。
1) 我的经验是,只要您在查询中包含您正在分区的列,插入和更新在分区集上以及选择时都会更快。如果我要求计算所有分区中的所有记录,我会看到响应较慢。这是意料之中的,因为分区的功能类似于单独的表,所以如果您有 30 个分区,则就像读取 30 个表而不仅仅是一个表。
您必须在主键中包含要分区的值,并且它必须在记录的生命周期内保持稳定。
2) 我会在主键中包含 user_id 和 id - 假设您的 friend 表 user_id 和 id 在建立记录后根本不会更改(即任何更改都将是删除/插入)。就我而言,它是“多余的”,但值得访问。是选择 user_id/id 还是 id/user_id 取决于您最频繁的访问。
最后一点。当我第一次开始将数据分成多个分区时,我尝试创建很多分区,发现只有少数几个似乎达到了最佳状态——6-12 个分区似乎最适合我。 YMMV。
关于Mysql 5.5 表分区用户和好友,我们在Stack Overflow上找到一个类似的问题: https://stackoverflow.com/questions/13587357/