MYSQL线上数据库大表归档方法



前言


作为一个企业或者DBA,我们通常会有这种想法,数据是一个公司的核心命脉,应该需要永久保存,很多时候DBA和开发沟通的时候,开发人员也会这么告诉我们,这份数据非常重要,数据需要永久保存。然而,如果将数据库的数据永久保存,那么迟早有一天,你会拥有一个非常大的数据库。作为一个DBA,通常为了业务对数据库的操作性能考虑和存储容量的考虑。我们会建议对数据库里大表进行数据归档,例如将使用的高频数据保留在当前表,对低频数据保留在归档表中。当然有些公司会将数据抽取到Hadoop、Hbase进行归档和离线分析,或者使用infobright作为归档引擎,这里不们不做具体深入分析。本文我们只从DBA运维人员的角度,是用MySQL现有技术来帮大家分析如何对大表进行归档,以提高数据库性能和解决数据库容量问题。

 

例子


首先我们举个大表的例子,你负责的数据库有一个交易数据存储表,已经有了200GB的数据。假设已经存储了5年的数据,但是在去年,因为业务量的增加,这个表的数据量翻了一翻。现在我们来总结一下,你现在拥有一个存储5年数据的大表,但是你的应用程序通常只需要查询1到3个月的数据,在此之前数据程序是不会去查询,可能有些时候,某个人需要定位问题或对帐会查询1年之内的数据,或者数据分析部门需要对近三年的数据进行汇总生成报表数据,基于以上考虑,为了满足这些需求,表里的数据不能丢,也不能删除 ,我们通常会将这些数据存储在1个表里,来方便给各个查询需求提供数据支持。

但是,这么庞大一张表,这种方式会极大的影响了数据库的性能,所以我们考虑可以尝试将上面三种应用查询场景的数据分别放在3个表里,来解决这种性能问题:

  1. 第一个表存放近期三个月数据,用于线上高频业务查询。

  2. 第二个归档表保存所有的历史数据用于低频查询,例如排查问题或者对账。

  3. 第三个是用于存储报告的汇总表。

有了上面这些表,我们就遵循单一责任的原则,为各种查询的需求提供最好的性能。首先我们有一个最近3个月数据的主表,可以大大提高查询性能和扩展性。举个例子,假如数据的容量在未来3-5年内每年都会翻倍,但是你的主表也只是用到这么多数据的子集。比如你三个月的主表数据为20GB,那么下一年就是40GB,再后一年也只有80GB,这些数据量也是非常容易管理的。另外随着时间的推移,软件和硬件也在不断改进和升级,所以可以通过升级和更新,可以不断保证业务的性能。我们将冷数据和热数据分别存储到不同的数据表中,这样也可以使得扩展性得到长期的保证。

 

如何实施归档


1.建立归档表_archive

这种方式是通过建立一个以_archive结尾的归档表来实施的。如果使用这种方式,那么一般需要在业务层进行查询的分离改造,比如基于我们的特定归档规则 ,对业务端核心代码改造或者使用proxy方案等来决定是使用主表还是归档表。同时我们还需要一个数据归档过程,当数据过时或者变成冷数据时,将该数据从主表迁移到归档表中。

在业务层查询时,我们可以通过时间字段来进行查询判断,例如将90天之前的数据在归档表中查询,否则就在主表查询。另一种方式可以通过增加status列去判断查询主表还是归档表,如果是inactive则查询归档表,否则就在主表查询。

2.根据日期进行分区

这种方式是通过对表进行分区来实现,虽然这是一种不同的物理数据模型,但是确实有助于将表的数据进行拆分到不同的物理磁盘,并且不需要代码的任何改造。作为DBA,一般对表进行分区是比较常用的方式,我们可以通过日期字段很容易确定哪些是冷数据,并根据日期将不同日期的时间分配到不同的分区中,在查询的时候,我们可以通过日期来从分区中快速定位到对应的数据,同时建立分区表也比较利于DBA对大表进行管理操作。

请看下面的例子,通过下面方式,我们可以将数据按照日期进行分配到正确的分区.

CREATE TABLE `largetable` (

  `id` bigint unsigned NOT NULL AUTO_INCREMENT,

 `dateCreated` datetime NOT NULL DEFAULT CURRENT_TIMESTAMP,

  `status` int default 1,
  `sometext` text,

  PRIMARY KEY (`id`,`status`)
) ENGINE=InnoDB DEFAULT CHARSET=utf8;

Query OK, 0 rows affected (0.03 sec)

mysql> alter table largetable partition by RANGE(YEAR(dateCreated)) (

-> PARTITION p2016 VALUES LESS THAN (2017),

-> PARTITION p2017 VALUES LESS THAN (2018),

-> PARTITION p2018 VALUES LESS THAN (2019),

-> PARTITION p2019 VALUES LESS THAN (2020),

-> PARTITION p2020 VALUES LESS THAN (2021),

-> PARTITION pmax VALUES LESS THAN MAXVALUE);

Query OK, 0 rows affected (0.05 sec)

Records: 0  Duplicates: 0  Warnings: 0

在上面的例子中,我们根据数据行的创建时间,按年将数据放入到不同的分区,这里需要注意的是在2020年以后,我们还需要在表里添加新的年份,当然我们可以提前加更多的分区或者部署脚本来自动化创建新的分区。

3.通过状态进行分区

我们也可以通过status状态列来进行分区,这种情况下,通常状态列会包含active/inactive两种状态,然后通过update进行状态列的更新(使用replace或者insert+delete也是可以的),将数据放入到正确的分区当中。请看下面的示例:

mysql> CREATE TABLE `largetable` (

->   `id` bigint unsigned NOT NULL AUTO_INCREMENT,

->   `dateCreated` datetime NOT NULL DEFAULT CURRENT_TIMESTAMP,

->   `status` int default 1, — default active

->   `sometext` text,

->   PRIMARY KEY (`id`,`status`)

-> ) ENGINE=InnoDB DEFAULT CHARSET=utf8;

Query OK, 0 rows affected (0.02 sec)

mysql> alter table largetable partition by list(status) (

-> partition pactive values in (1), — active

-> partition pinactive values in (2) — inactive

-> );

Query OK, 0 rows affected (0.03 sec)

Records: 0  Duplicates: 0  Warnings: 0

 

mysql> select * from largetable partition (pactive);

Empty set (0.00 sec)

mysql> select * from largetable partition (pinactive);

Empty set (0.00 sec)

mysql> insert into largetable(sometext) values (‘hello’);

Query OK, 1 row affected (0.01 sec)

mysql> select * from largetable partition (pinactive);

Empty set (0.00 sec)

mysql> select * from largetable partition (pactive);

+—-+———————+——–+———-+

| id | dateCreated         | status | sometext |

+—-+———————+——–+———-+

|  1 | 2017-10-30 10:04:03 |      1 | hello    |

+—-+———————+——–+———-+

1 row in set (0.00 sec)

 

mysql> update largetable set status = 2 where id =1 ;

Query OK, 1 row affected (0.00 sec)

Rows matched: 1  Changed: 1  Warnings: 0

 

mysql> select * from largetable partition (pactive);

Empty set (0.00 sec)

 

mysql> select * from largetable partition (pinactive);

+—-+———————+——–+———-+

| id | dateCreated         | status | sometext |

+—-+———————+——–+———-+

|  1 | 2017-10-30 10:04:03 |      2 | hello    |

+—-+———————+——–+———-+

1 row in set (0.00 sec)

4.通过ID进行分区

最后一种方式,我们介绍下通过自增ID主键列进行分区的方式,请看下面的示例:

mysql> CREATE TABLE `largetable` (

->   `id` bigint unsigned NOT NULL AUTO_INCREMENT,

->   `dateCreated` datetime NOT NULL DEFAULT CURRENT_TIMESTAMP,

->   `status` int default 1,

->   `sometext` text,

->   PRIMARY KEY (`id`)

-> ) ENGINE=InnoDB DEFAULT CHARSET=utf8;

Query OK, 0 rows affected (0.02 sec)

mysql> alter table largetable partition by RANGE(id) (

-> PARTITION p1 VALUES LESS THAN (500000000),

-> PARTITION p2 VALUES LESS THAN (1000000000),

-> PARTITION p3 VALUES LESS THAN (1500000000),

-> PARTITION p4 VALUES LESS THAN (2000000000),

-> PARTITION p5 VALUES LESS THAN (2500000000),

-> PARTITION pmax VALUES LESS THAN MAXVALUE);

Query OK, 0 rows affected (0.06 sec)

Records: 0  Duplicates: 0  Warnings: 0

上面的例子中,我们通过ID列进行了range分区,如果业务的查询基本都是主键查找,那么这种方式会比较有用。 与按照日期分区相比,按照ID分区还会有助于数据平均分配到各个分区。

最后总结


上述简单介绍了归档表和分区的一些方法,但是关键的一点是要如何选择正确的分区键和分区方式,这一点并不容易,我们需要和业务端进行充分沟通业务场景,并根据场景来选择合适的分区键,我们选择的分区键要能正确分区,并且不会降低表的查询速度。

另外一点,为了让优化器能将查询发送到正确的分区键,在创建分区表的时候,我们需要将分区键添加到主键里,并且理想情况下,该分区键能被包含在所有select/update/delete等语句的where条件里面,否则的话,你的查询将会按照顺序查找表对应的每个分区,这种情况下查询性能就没有那么好了。


  • 1
    点赞
  • 14
    收藏
    觉得还不错? 一键收藏
  • 1
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论 1
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值