文章

learn - 项目架构演变-03(分表分库篇)

数据存储,在设计存储表之前,你有没有考虑过增长量的情况?

learn - 项目架构演变-03(分表分库篇)

创作背景

我负责的项目中,有很多是从0到1,也有很多是大型项目。

本篇主要是为了当项目初期未搭建或购买分布式数据库时,用小的成本暂时支撑当前项目发展而创作的。

提前规划好自己的存储结构分库分表,对后期业务增长技术支撑上也有很大帮助的:

1.对数量级O(1)的可以是单表,也可以是小规模的分表,例如一个面向C端的产品运营1-2年后用户量可达到千万;

2.对数量级O(n)的可以进行分表,一般划分16-128之间,例如用户与xx的关系存储;

3.对数量级O(n*n) 的一定要分表,一般开系数的大小,如果不确定暂时64张或128即可,例如个体与xx的多个属性之间的关系。

另外创建的所有数据库尽量提前指定默认字符集,否则会使用安装数据库时候给配置的默认字符集。

CREATE DATABASE 
IF NOT EXISTS open_test
CHARACTER SET utf8mb4 COLLATE utf8mb4_unicode_ci;
-- CHARACTER SET utf8 COLLATE utf8_general_ci;
-- 必须用utf8mb4,要不然存不下[emj]的宽字符

如果不小心创建的表已经用于生产了,我们还可以补救回来:

1
2
3
4
5
6
7
8
9
10
11
-- 修改库默认字符集
ALTER SCHEMA `nexus_dev`  CHARACTER SET utf8mb4 COLLATE utf8mb4_general_ci;

-- 修改表默认字符集
ALTER TABLE article CONVERT TO CHARACTER SET utf8mb4 COLLATE utf8mb4_general_ci;

-- 实在不行,那暂时停掉该业务几分钟,来拷贝数据到新表
-- 创建一个字符集正确的表 (因为每张表的单行总字节不超过一页数据,16k,如果字段varchar过多,可能转不过来,那就换一张表拷贝,或者更改列长度以后,再修改)
INSERT INTO `ai_test_task_2``id`... SELECT `id`...  FROM ai_test_task ;
rename table ai_test_task to ai_test_task_back;
rename table ai_test_task_2 to ai_test_task;

使用存储过程来创建同类表(table)

案例:用户账单


--
-- 用户账单
--


DELIMITER $$
DROP PROCEDURE IF EXISTS `create_bill_table`$$
CREATE PROCEDURE `create_bill_table`()
BEGIN
    SET @i = 0;
    WHILE @i < 64
        DO
            SET @table_name = CONCAT('user_bill_', @i);
            SET @create_sql = CONCAT('CREATE TABLE ',  @table_name,
                                     '(`id` bigint(20) UNSIGNED NOT NULL AUTO_INCREMENT COMMENT "自增主键",',
                                     '`user_id` bigint(20) UNSIGNED NOT NULL COMMENT "用户UID,分号器下发ID",',
                                     '`bill_type` int NOT NULL  DEFAULT 0 COMMENT "账单类型,1金币支出,2金币收入",',
                                     '`sub_type` int NOT NULL  DEFAULT 0 COMMENT "子类型,根据bill_type决定不同的枚举值",',
                                     '`amount`  INT UNSIGNED NOT NULL DEFAULT 0 COMMENT "交易数量金币数/美分",',
                                     ' `before_amount`  INT UNSIGNED NOT NULL DEFAULT 0 COMMENT "交易之前的数量,例如金币变动前",',
                                     '`create_time` datetime NOT NULL  DEFAULT CURRENT_TIMESTAMP COMMENT "创建时间",',
                                     ' `extra` varchar(256)  NOT NULL  DEFAULT "" COMMENT "其他扩展信息,根据bill_type定",',
                                     'PRIMARY KEY (`id`) USING BTREE,',
                                     'KEY `idx_user_bill_sub` (`user_id`, `bill_type` , `sub_type`) USING BTREE',
                                     ') ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COMMENT="用户账单记录";'
                );
            PREPARE stmt from @create_sql;
            EXECUTE stmt;

            SET @i = @i + 1;

        END WHILE;

END $$

DELIMITER  ;

CALL create_bill_table();
DROP PROCEDURE IF EXISTS `create_bill_table`;

使用存储过程来创建视图(view)

当我们允许在后台使用时,由于数据分散到不同的表上面,我们可以创建一个合并的视图提供查询(虽然改方案会影响查询速度,但是给内部后台使用也足够了)

DELIMITER $$
DROP PROCEDURE IF EXISTS `create_view_user_bill`$$
CREATE PROCEDURE `create_view_user_bill`()
BEGIN
    SET @i = 0;
    SET @view_sql='CREATE OR REPLACE  VIEW view_user_bill AS  ';
    WHILE @i < 63
        DO
            SET @table_name = CONCAT('user_bill_', @i);
            SET @view_sql = CONCAT(  @view_sql , '  SELECT * FROM ',  @table_name,' union ');



            SET @i = @i + 1;

        END WHILE;
    SET @table_name = CONCAT('user_bill_', @i);
    SET @view_sql = CONCAT(  @view_sql , '  SELECT * FROM ',  @table_name);
    PREPARE stmt from @view_sql;
    EXECUTE stmt;
END $$

DELIMITER  ;

CALL create_view_user_bill();
DROP PROCEDURE IF EXISTS `create_view_user_bill`;

总结

在项目初期,当1、2个人参与布局话可能没时间直接上微服务的话(加班加点加机器加预算加薪水的话我觉得可以一步到位),但可以提前把地基给打牢固,省的以后留下对数据迁移时的风险。

本文由作者按照 CC BY 4.0 进行授权