历史拉链表实战

历史拉链表是一种用于数据仓库的存储方式,有效解决海量历史数据存储问题。本文介绍了其应用场景、实现步骤,包括表结构设计、全量初始装载、增量数据处理等,并通过示例数据展示如何进行数据刷新。

历史拉链表是一种数据模型,主要是针对数据仓库设计中表存储数据的方式而定义的。所谓历史拉链表,就是指记录一个事物从开始一直到当前状态的所有变化信息。拉所有记录链表可以避免按每一天存储造成的海量存储问题,同时也是处理缓慢变化数据的一种常见方式。

应用场景

现假设有如下场景:一个企业拥有5000万会员信息,每天有20万会员资料变更,需要在数仓中记录会员表的历史变化以备分析使用,即每天都要保留一个快照供查询,反映历史数据的情况。在此场景中,需要反映5000万会员的历史变化,如果保留快照,存储两年就需要2X365X5000W条数据存储空间,数据量为365亿,如果存储更长时间,则无法估计需要的存储空间。而利用拉链算法存储,每日只向历史表中添加新增和变化的数据,每日不过20万条,存储4年也只需要3亿存储空间。

实现步骤

在拉链表中,每一条数据都有一个生效日期(effective_date)和失效日期(expire_date)。假设在一个用户表中,在2019年11月8日新增了两个用户,如下表所示,则这两条记录的生效时间为当天,由于到2019年11月8日为止,这两条就还没有被修改过,所以失效时间为一个给定的比较大的值,比如:3000-12-31

member_id phoneno create_time update_time
10001 13300000001 2019-11-08 3000-12-31
10002 13500000002 2019-11-08 3000-12-31

第二天(2019-11-09),用户10001被删除了,用户10002的电话号码被修改成13600000002.为了保留历史状态,用户10001的失效时间被修改为2019-11-09,用户10002则变成了两条记录,如下表所示:

member_id phoneno create_time update_time
10001 13300000001 2019-11-08 2019-11-09
10002 13500000002 2019-11-08 2019-11-09
10002 13600000002 2019-11-09 3000-12-31

第三天(2019-11-10),又新增了用户10003,则用户表数据如小表所示:

member_id phoneno create_time update_time
10001 13300000001 2019-11-08 2019-11-09
10002 13500000002 2019-11-08 2019-11-09
10002 13600000002 2019-11-09 3000-12-31
10003 13300000006 2019-11-10 3000-12-31

如果要查询最新的数据,那么只要查询失效时间为3000-12-31的数据即可,如果要查11月8号的历史数据,则筛选生效时间<= 2019-11-08并且失效时间>2019-11-08的数据即可。如果查询11月9号的数据,那么筛选条件则是生效时间<=2019-11-09并且失效时间>2019-11-09

表结构

  • MySQL源member表
CREATE TABLE member(
             member_id VARCHAR ( 64 ),
             phoneno VARCHAR ( 20 ), 
             create_time datetime,
             update_time datetime )
<think>我们面对的是一个宽(字段多)且数据量大的A,以及它的拉链B,需要添加字段但遇到困难(加不动)。DBA不建议直接加字段可能是因为以下原因: 1. 直接ALTER TABLE ADD COLUMN在数据量极大的情况下会锁,导致长时间阻塞写操作(甚至读操作,取决于存储引擎)。 2. 对于拉链B(通常存储历史变化),数据量可能更大,加字段同样面临上述问题,甚至更严重。 3. 结构变更可能引起复制延迟(如果使用了主从复制)。 结合大型公司实战经验,解决方案通常包括: ### 解决方案1:使用在线DDL工具(推荐) - **原理**:利用MySQL5.6+的在线DDL特性(如InnoDB支持)或第三方工具(如Percona Toolkit的pt-online-schema-change,gh-ost)在不停机的情况下修改结构。 - **步骤**(以gh-ost为例): 1. 创建影子(与原结构相同,并加上新字段)。 2. 将原数据逐步复制到影子(同时增量同步变更)。 3. 切换(原子操作,重命名)。 - **优点**:几乎不停机,对业务影响小。 - **注意**:需要评估磁盘空间(因为会创建新),并在业务低峰期操作。 ### 解决方案2:通过新增扩展(解耦) - **原理**:不直接修改原,而是创建新存储新增字段,并通过外键或业务逻辑关联。 - **步骤**: 1. 创建新`A_extra`,包含主键(与原A一致)和新增字段。 2. 业务代码修改:读取数据时JOIN新,写入时同时更新原和新(或异步更新)。 - **优点**: - 完全避免锁原。 - 适用于后续频繁变更字段的场景(弹性好)。 - **缺点**:增加业务逻辑复杂性,查询需要关联。 ### 解决方案3:分步执行(适合非紧急场景) - **原理**:将加字段操作拆分为多个低风险步骤(仅适用于MySQL8.0以下版本,8.0支持INSTANT ADD COLUMN): 1. 先添加一个允许为NULL的新字段(速度相对较快)。 2. 在业务低峰期分批次更新该字段的默认值(避免单次事务过大)。 - **注意**:此方法在MySQL5.6+的InnoDB引擎上,如果字段允许NULL,则添加操作可以INPLACE(避免重建),但仍然需要较长时间(数据量大)。在MySQL8.0+中,添加字段(尤其是最后位置)可以瞬间完成(INSTANT算法),但需要确认版本和条件。 ### 解决方案4:使用JSON字段(灵活扩展) - **原理**:添加一个JSON类型的字段`extra`,将后续新增的非强类型需求字段存入JSON。 - **步骤**: ```sql ALTER TABLE A ADD extra JSON DEFAULT NULL; ``` - **优点**:避免频繁ALTER TABLE,后续新增字段只需修改业务逻辑(存取JSON中的键值)。 - **缺点**: - 无法在JSON字段上直接建索引(但可以创建虚拟列和索引)。 - 查询和更新效率略低,且业务逻辑复杂。 ### 实战建议优先级 1. **评估MySQL版本**:如果是8.0+,优先确认是否支持INSTANT ADD COLUMN(添加列到末尾)。 2. **优先在线DDL工具**:选择gh-ost或pt-online-schema-change,由DBA操作。 3. **考虑未来扩展性**:如果后续经常需要加字段,采用方案2(扩展)或方案4(JSON字段)。 ### 注意事项 - **拉链B**:同样采用上述方案,但需注意拉链通常有大量历史数据,在线DDL耗时更长,建议分批迁移。 - **测试**:任何操作前在测试环境验证。 - **监控**:操作时严密监控数据库负载。 §§相关问题§§ 1. 在使用gh-ost进行在线DDL时,如何避免主从复制延迟问题? 2. 对于JSON字段存储扩展数据,如何在MySQL中高效地创建索引进行查询优化? 3. 在分库分架构下,如何安全高效地给大添加字段? 4. 如果采用新增扩展方案,如何保证原和扩展之间的数据一致性? 5. MySQL 8.0的INSTANT ADD COLUMN功能有哪些使用限制? </think>### 针对MySQL宽加字段难题的实战解决方案 在大型公司处理超宽(如100+列)和超大(TB级)的加字段问题时,通常采用以下经过验证的解决方案,避免长时间锁或业务中断: --- #### **方案1:在线DDL工具(首选推荐)** **工具选择**: - **Percona的pt-online-schema-change**:通过触发器实现 - **GitHub的gh-ost**:基于binlog的无触发器方案(更安全) ```bash # gh-ost示例命令 gh-ost \ --alter="ADD COLUMN new_column INT DEFAULT 0" \ --database=your_db --table=A \ --host=master_host --user=dba --password=xxx \ --assume-rbr --execute ``` **优势**: - 无需锁(增量数据同步) - 可暂停/恢复,实时监控进度 - 自动切换流量(阿里云/腾讯云DMS已集成) --- #### **方案2:逻辑分法(业务解耦)** **实施步骤**: 1. **创建新**:`CREATE TABLE A_new LIKE A;` 2. **新增字段**:对新执行`ALTER TABLE ADD COLUMN` 3. **数据迁移**: ```sql INSERT INTO A_new SELECT *, NULL AS new_col FROM A WHERE id BETWEEN 1 AND 1000000; -- 分批次 ``` 4. **双写切换**: - 应用层同时写A和A_new - 迁移完成后用视图路由流量 ```sql CREATE VIEW A_view AS SELECT * FROM A_new UNION ALL SELECT *, NULL FROM A WHERE id NOT IN (SELECT id FROM A_new); ``` --- #### **方案3:扩展方案(冷热分离)** **架构设计**: ```mermaid graph LR A[主] -->|主键| B[扩展] B -->|存储新增字段| C[业务层JOIN] ``` **操作**: 1. 创建扩展: ```sql CREATE TABLE A_extra ( id BIGINT PRIMARY KEY, new_col1 INT, new_col2 VARCHAR(20), FOREIGN KEY (id) REFERENCES A(id) ) ENGINE=InnoDB; ``` 2. 业务改造: - 读写分离:主读基础字段,扩展读新增字段 - 写入时原子事务更新两 --- #### **方案4:JSON字段扩展法(动态Schema)** **适用场景**:非强查询需求的字段 ```sql ALTER TABLE A ADD COLUMN extended_data JSON DEFAULT NULL; -- 仅需一次DDL -- 插入数据 UPDATE A SET extended_data = JSON_SET(extended_data, '$.new_field', 123) WHERE id = 100; ``` **优化**: ```sql -- 创建虚拟索引列 ALTER TABLE A ADD COLUMN new_field_virtual INT GENERATED ALWAYS AS (extended_data->>'$.new_field') VIRTUAL, ADD INDEX idx_new_field (new_field_virtual); ``` --- ### 拉链B的特殊处理 1. **分区操作**:按时间分区后逐分区加字段 ```sql ALTER TABLE B PARTITION BY RANGE (end_date) (...); ALTER TABLE B REORGANIZE PARTITION p2023 INTO ( PARTITION p2023 VALUES LESS THAN ('2024-01-01') ENGINE=InnoDB ); ``` 2. **增量同步**:使用Debezium+ Kafka捕获变更,写入带新字段的新 --- ### 大型公司最佳实践 1. **腾讯游戏日志**:采用gh-ost为800亿行加字段,耗时6小时(业务无感知) 2. **阿里订单**:使用扩展方案,年增字段30+不影响主 3. **银行核心系统**:JSON方案存储动态业务属性,结合虚拟列索引 > **紧急建议**:优先使用gh-ost(避免触发器问题),若字段为NULL且引擎为InnoDB,MySQL 8.0的INSTANT ADD COLUMN可在0.1s内完成(需验证版本支持)
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值