HaniBlindBox/docs/数据库迁移详细文档/阶段3-表结构创建和数据迁移.md
2026-01-02 01:00:52 +08:00

16 KiB
Raw Blame History

阶段3表结构创建和数据迁移

阶段概述

时间: 2天
目标: 在SQL Server中创建新表结构执行数据迁移和验证
优先级: P0 (最高优先级)

详细任务清单

3.1 表结构创建 (0.5天)

任务描述

在SQL Server中创建所有核心业务表的新结构

具体工作

  • 执行表结构创建脚本
  • 创建索引和约束
  • 配置外键关系
  • 验证表结构正确性

表结构创建脚本

1. 用户相关表
-- 用户主表
CREATE TABLE users (
    id INT IDENTITY(1,1) PRIMARY KEY,
    open_id NVARCHAR(50) NOT NULL,
    union_id NVARCHAR(255) NULL,
    gzh_open_id NVARCHAR(255) NULL,
    mobile NVARCHAR(15) NULL,
    nickname NVARCHAR(255) NOT NULL,
    head_img NVARCHAR(500) NOT NULL,
    parent_id INT NOT NULL DEFAULT 0,
    money DECIMAL(18,2) NOT NULL DEFAULT 0,
    money2 DECIMAL(18,2) NOT NULL DEFAULT 0,
    integral DECIMAL(18,2) NOT NULL DEFAULT 0,
    score DECIMAL(18,2) NOT NULL DEFAULT 0,
    ou_qi INT NOT NULL DEFAULT 0,
    ou_qi_level INT NOT NULL DEFAULT 0,
    vip_level TINYINT NOT NULL DEFAULT 1,
    status TINYINT NOT NULL DEFAULT 1,
    is_test INT NOT NULL DEFAULT 0,
    uid NVARCHAR(16) NOT NULL,
    click_id INT NULL,
    created_at DATETIME2 NOT NULL DEFAULT GETDATE(),
    updated_at DATETIME2 NOT NULL DEFAULT GETDATE(),
    last_login_at DATETIME2 NULL
);

-- 创建索引
CREATE UNIQUE INDEX ix_users_open_id ON users(open_id);
CREATE UNIQUE INDEX ix_users_uid ON users(uid);
CREATE INDEX ix_users_mobile ON users(mobile);
CREATE INDEX ix_users_union_id ON users(union_id);
CREATE INDEX ix_users_status_created ON users(status, created_at);
CREATE INDEX ix_users_parent_id ON users(parent_id);

-- 用户账户表 CREATE TABLE user_accounts ( id INT IDENTITY(1,1) PRIMARY KEY, user_id INT NOT NULL, account_token NVARCHAR(255) NOT NULL, token_num NVARCHAR(50) NOT NULL, token_time BIGINT NOT NULL, last_login_time BIGINT NOT NULL, last_login_ip BIGINT NOT NULL, last_login_ip_str NVARCHAR(45) NOT NULL, ip_province NVARCHAR(50) NULL, ip_city NVARCHAR(50) NULL, ip_adcode NVARCHAR(20) NULL, created_at DATETIME2 NOT NULL DEFAULT GETDATE(), updated_at DATETIME2 NOT NULL DEFAULT GETDATE() );

-- 创建索引和外键 CREATE INDEX ix_user_accounts_user_id ON user_accounts(user_id); CREATE INDEX ix_user_accounts_token ON user_accounts(account_token); ALTER TABLE user_accounts ADD CONSTRAINT fk_user_accounts_user_id FOREIGN KEY (user_id) REFERENCES users(id);

-- 用户登录日志表 CREATE TABLE user_login_logs ( id INT IDENTITY(1,1) PRIMARY KEY, user_id INT NOT NULL, platform NVARCHAR(50) NOT NULL, ip_address NVARCHAR(45) NOT NULL, location NVARCHAR(100) NULL, device NVARCHAR(100) NULL, device_info NVARCHAR(500) NULL, login_time DATETIME2 NOT NULL DEFAULT GETDATE() );

-- 创建索引 CREATE INDEX ix_user_login_logs_user_id ON user_login_logs(user_id); CREATE INDEX ix_user_login_logs_login_time ON user_login_logs(login_time); CREATE INDEX ix_user_login_logs_platform ON user_login_logs(platform);


##### 2. 商品相关表
```sql
-- 商品主表
CREATE TABLE goods (
    id INT IDENTITY(1,1) PRIMARY KEY,
    category_id INT NOT NULL DEFAULT 0,
    title NVARCHAR(255) NOT NULL,
    img_url NVARCHAR(500) NOT NULL,
    img_url_detail NVARCHAR(500) NOT NULL,
    price DECIMAL(18,2) NOT NULL DEFAULT 0,
    stock INT NOT NULL DEFAULT 0,
    sale_stock INT NOT NULL DEFAULT 0,
    type TINYINT NOT NULL DEFAULT 0,
    status TINYINT NOT NULL DEFAULT 1,
    is_lock TINYINT NOT NULL DEFAULT 0,
    lock_time INT NOT NULL DEFAULT 0,
    is_discount TINYINT NOT NULL DEFAULT 0,
    is_new TINYINT NOT NULL DEFAULT 0,
    is_show TINYINT NOT NULL DEFAULT 0,
    unlock_amount DECIMAL(18,2) NOT NULL DEFAULT 0,
    daily_limit INT NOT NULL DEFAULT 0,
    global_limit INT NOT NULL DEFAULT 0,
    sort_order INT NOT NULL DEFAULT 1,
    sale_time DATETIME2 NULL,
    created_at DATETIME2 NOT NULL DEFAULT GETDATE(),
    updated_at DATETIME2 NOT NULL DEFAULT GETDATE(),
    deleted_at DATETIME2 NULL
);

-- 创建索引
CREATE INDEX ix_goods_type_status ON goods(type, status);
CREATE INDEX ix_goods_status_sort ON goods(status, sort_order DESC);
CREATE INDEX ix_goods_category_id ON goods(category_id);
CREATE INDEX ix_goods_created_at ON goods(created_at);
CREATE INDEX ix_goods_unlock_amount ON goods(unlock_amount);

-- 商品奖品列表
CREATE TABLE goods_items (
    id INT IDENTITY(1,1) PRIMARY KEY,
    goods_id INT NOT NULL,
    box_number INT NOT NULL,
    prize_id INT NOT NULL,
    title NVARCHAR(255) NOT NULL,
    stock INT NOT NULL DEFAULT 0,
    surplus_stock INT NOT NULL DEFAULT 0,
    img_url NVARCHAR(500) NOT NULL,
    goods_type TINYINT NOT NULL DEFAULT 1,
    price DECIMAL(18,2) NOT NULL DEFAULT 0,
    market_price DECIMAL(18,2) NOT NULL DEFAULT 0,
    real_probability DECIMAL(8,4) NOT NULL DEFAULT 0,
    parent_item_id INT NOT NULL DEFAULT 0,
    sale_time DATETIME2 NULL,
    sort_order INT NOT NULL DEFAULT 1,
    created_at DATETIME2 NOT NULL DEFAULT GETDATE(),
    updated_at DATETIME2 NOT NULL DEFAULT GETDATE()
);

-- 创建索引和外键
CREATE INDEX ix_goods_items_goods_box ON goods_items(goods_id, box_number);
CREATE INDEX ix_goods_items_prize_id ON goods_items(prize_id);
CREATE INDEX ix_goods_items_parent_id ON goods_items(parent_item_id);
CREATE INDEX ix_goods_items_surplus_stock ON goods_items(surplus_stock);
ALTER TABLE goods_items ADD CONSTRAINT fk_goods_items_goods_id 
    FOREIGN KEY (goods_id) REFERENCES goods(id);
3. 订单相关表
-- 订单主表
CREATE TABLE orders (
    id INT IDENTITY(1,1) PRIMARY KEY,
    order_no NVARCHAR(60) NOT NULL,
    user_id INT NOT NULL,
    goods_id INT NOT NULL,
    box_number INT NOT NULL,
    goods_title NVARCHAR(255) NOT NULL,
    goods_img_url NVARCHAR(500) NULL,
    order_type TINYINT NOT NULL DEFAULT 0,
    order_total DECIMAL(18,2) NOT NULL,
    discount_total DECIMAL(18,2) NOT NULL,
    final_price DECIMAL(18,2) NOT NULL,
    used_money DECIMAL(18,2) NOT NULL DEFAULT 0,
    used_integral DECIMAL(18,2) NOT NULL DEFAULT 0,
    used_money2 DECIMAL(18,2) NOT NULL DEFAULT 0,
    coupon_id INT NULL DEFAULT 0,
    used_coupon DECIMAL(18,2) NOT NULL DEFAULT 0,
    draw_count INT NOT NULL DEFAULT 0,
    pay_type TINYINT NOT NULL DEFAULT 1,
    status TINYINT NOT NULL DEFAULT 0,
    click_id INT NULL,
    created_at DATETIME2 NOT NULL DEFAULT GETDATE(),
    paid_at DATETIME2 NULL
);

-- 创建索引和外键
CREATE UNIQUE INDEX ix_orders_order_no ON orders(order_no);
CREATE INDEX ix_orders_user_status ON orders(user_id, status);
CREATE INDEX ix_orders_goods_box ON orders(goods_id, box_number);
CREATE INDEX ix_orders_created_at ON orders(created_at);
CREATE INDEX ix_orders_paid_at ON orders(paid_at);
CREATE INDEX ix_orders_status ON orders(status);

ALTER TABLE orders ADD CONSTRAINT fk_orders_user_id 
    FOREIGN KEY (user_id) REFERENCES users(id);
ALTER TABLE orders ADD CONSTRAINT fk_orders_goods_id 
    FOREIGN KEY (goods_id) REFERENCES goods(id);

-- 订单详情表 (抽奖结果)
CREATE TABLE order_items (
    id INT IDENTITY(1,1) PRIMARY KEY,
    order_id NVARCHAR(60) NOT NULL,
    user_id INT NOT NULL,
    goods_id INT NOT NULL,
    box_number INT NOT NULL,
    goods_item_id INT NOT NULL,
    item_title NVARCHAR(255) NOT NULL,
    item_img_url NVARCHAR(500) NULL,
    prize_id INT NOT NULL,
    order_type TINYINT NOT NULL,
    source TINYINT NOT NULL DEFAULT 1,
    item_price DECIMAL(18,2) NOT NULL DEFAULT 0,
    created_at DATETIME2 NOT NULL DEFAULT GETDATE()
);

-- 创建索引和外键
CREATE INDEX ix_order_items_order_id ON order_items(order_id);
CREATE INDEX ix_order_items_user_id ON order_items(user_id);
CREATE INDEX ix_order_items_goods_box ON order_items(goods_id, box_number);
CREATE INDEX ix_order_items_prize_id ON order_items(prize_id);
CREATE INDEX ix_order_items_created_at ON order_items(created_at);

ALTER TABLE order_items ADD CONSTRAINT fk_order_items_user_id 
    FOREIGN KEY (user_id) REFERENCES users(id);
ALTER TABLE order_items ADD CONSTRAINT fk_order_items_goods_id 
    FOREIGN KEY (goods_id) REFERENCES goods(id);

3.2 数据迁移执行 (1天)

任务描述

执行MySQL到SQL Server的数据迁移

具体工作

  • 创建临时导入表
  • 执行数据转换和导入
  • 处理数据类型转换
  • 修复数据一致性问题

数据迁移脚本

1. 用户数据迁移
-- 创建临时导入表
CREATE TABLE temp_mysql_users (
    id INT,
    openid NVARCHAR(50),
    unionid NVARCHAR(255),
    gzh_openid NVARCHAR(255),
    mobile NVARCHAR(15),
    nickname NVARCHAR(255),
    headimg NVARCHAR(500),
    pid INT,
    money DECIMAL(18,2),
    money2 DECIMAL(18,2),
    integral DECIMAL(18,2),
    score DECIMAL(18,2),
    ou_qi INT,
    ou_qi_level INT,
    vip TINYINT,
    status TINYINT,
    istest INT,
    uid NVARCHAR(16),
    click_id INT,
    addtime BIGINT,
    update_time BIGINT,
    last_login_time BIGINT
);

-- 导入CSV数据到临时表
BULK INSERT temp_mysql_users
FROM 'C:\Export\user.csv'
WITH (
    FIELDTERMINATOR = ',',
    ROWTERMINATOR = '\n',
    FIRSTROW = 2,
    CODEPAGE = '65001'
);

-- 迁移用户数据
INSERT INTO users (
    open_id, union_id, gzh_open_id, mobile, nickname, head_img,
    parent_id, money, money2, integral, score, ou_qi, ou_qi_level,
    vip_level, status, is_test, uid, click_id,
    created_at, updated_at, last_login_at
)
SELECT 
    openid,
    NULLIF(unionid, ''),
    NULLIF(gzh_openid, ''),
    NULLIF(mobile, ''),
    nickname,
    headimg,
    ISNULL(pid, 0),
    ISNULL(money, 0),
    ISNULL(money2, 0),
    ISNULL(integral, 0),
    ISNULL(score, 0),
    ISNULL(ou_qi, 0),
    ISNULL(ou_qi_level, 0),
    ISNULL(vip, 1),
    ISNULL(status, 1),
    ISNULL(istest, 0),
    ISNULL(uid, CAST(id AS NVARCHAR(16))),
    click_id,
    -- 时间戳转换
    CASE WHEN addtime > 0 
         THEN DATEADD(SECOND, addtime, '1970-01-01') 
         ELSE GETDATE() END,
    CASE WHEN update_time > 0 
         THEN DATEADD(SECOND, update_time, '1970-01-01') 
         ELSE GETDATE() END,
    CASE WHEN last_login_time > 0 
         THEN DATEADD(SECOND, last_login_time, '1970-01-01') 
         ELSE NULL END
FROM temp_mysql_users
WHERE openid IS NOT NULL AND openid != '';

-- 清理临时表
DROP TABLE temp_mysql_users;
2. 商品数据迁移
-- 创建临时导入表
CREATE TABLE temp_mysql_goods (
    id INT,
    category_id INT,
    title NVARCHAR(255),
    imgurl NVARCHAR(500),
    imgurl_detail NVARCHAR(500),
    price DECIMAL(18,2),
    stock INT,
    sale_stock INT,
    type TINYINT,
    status TINYINT,
    lock_is TINYINT,
    lock_time INT,
    is_shou_zhe TINYINT,
    new_is TINYINT,
    show_is TINYINT,
    unlock_amount DECIMAL(18,2),
    daily_xiangou INT,
    quanju_xiangou INT,
    sort INT,
    sale_time BIGINT,
    addtime BIGINT,
    update_time BIGINT,
    delete_time BIGINT
);

-- 导入CSV数据
BULK INSERT temp_mysql_goods
FROM 'C:\Export\goods.csv'
WITH (
    FIELDTERMINATOR = ',',
    ROWTERMINATOR = '\n',
    FIRSTROW = 2,
    CODEPAGE = '65001'
);

-- 迁移商品数据
INSERT INTO goods (
    category_id, title, img_url, img_url_detail, price, stock, sale_stock,
    type, status, is_lock, lock_time, is_discount, is_new, is_show,
    unlock_amount, daily_limit, global_limit, sort_order,
    sale_time, created_at, updated_at, deleted_at
)
SELECT 
    ISNULL(category_id, 0),
    title,
    imgurl,
    imgurl_detail,
    ISNULL(price, 0),
    ISNULL(stock, 0),
    ISNULL(sale_stock, 0),
    ISNULL(type, 0),
    ISNULL(status, 1),
    ISNULL(lock_is, 0),
    ISNULL(lock_time, 0),
    ISNULL(is_shou_zhe, 0),
    ISNULL(new_is, 0),
    ISNULL(show_is, 0),
    ISNULL(unlock_amount, 0),
    ISNULL(daily_xiangou, 0),
    ISNULL(quanju_xiangou, 0),
    ISNULL(sort, 1),
    -- 时间戳转换
    CASE WHEN sale_time > 0 
         THEN DATEADD(SECOND, sale_time, '1970-01-01') 
         ELSE NULL END,
    CASE WHEN addtime > 0 
         THEN DATEADD(SECOND, addtime, '1970-01-01') 
         ELSE GETDATE() END,
    CASE WHEN update_time > 0 
         THEN DATEADD(SECOND, update_time, '1970-01-01') 
         ELSE GETDATE() END,
    CASE WHEN delete_time > 0 
         THEN DATEADD(SECOND, delete_time, '1970-01-01') 
         ELSE NULL END
FROM temp_mysql_goods
WHERE title IS NOT NULL AND title != '';

-- 清理临时表
DROP TABLE temp_mysql_goods;

3.3 数据验证和修复 (0.5天)

任务描述

验证迁移数据的完整性和一致性,修复发现的问题

具体工作

  • 执行数据完整性检查
  • 验证业务逻辑一致性
  • 修复数据不一致问题
  • 生成迁移报告

数据验证脚本

1. 基础数据验证
-- 验证记录数量
SELECT 
    'users' as table_name,
    COUNT(*) as record_count,
    MAX(id) as max_id,
    MIN(created_at) as min_time,
    MAX(created_at) as max_time
FROM users
UNION ALL
SELECT 
    'goods' as table_name,
    COUNT(*) as record_count,
    MAX(id) as max_id,
    MIN(created_at) as min_time,
    MAX(created_at) as max_time
FROM goods
UNION ALL
SELECT 
    'orders' as table_name,
    COUNT(*) as record_count,
    MAX(id) as max_id,
    MIN(created_at) as min_time,
    MAX(created_at) as max_time
FROM orders;

-- 验证关键业务数据
SELECT 
    '用户总数' as metric,
    COUNT(*) as value
FROM users
UNION ALL
SELECT 
    '活跃用户数' as metric,
    COUNT(*) as value
FROM users WHERE status = 1
UNION ALL
SELECT 
    '商品总数' as metric,
    COUNT(*) as value
FROM goods
UNION ALL
SELECT 
    '上架商品数' as metric,
    COUNT(*) as value
FROM goods WHERE status = 1
UNION ALL
SELECT 
    '订单总数' as metric,
    COUNT(*) as value
FROM orders
UNION ALL
SELECT 
    '已支付订单数' as metric,
    COUNT(*) as value
FROM orders WHERE status = 1;
2. 数据一致性验证
-- 验证用户余额一致性
SELECT 
    '用户余额总计' as metric,
    CAST(SUM(money) AS NVARCHAR(50)) as value
FROM users
UNION ALL
SELECT 
    '用户积分总计' as metric,
    CAST(SUM(integral) AS NVARCHAR(50)) as value
FROM users
UNION ALL
SELECT 
    '订单金额总计' as metric,
    CAST(SUM(order_total) AS NVARCHAR(50)) as value
FROM orders WHERE status = 1;

-- 验证外键关系
SELECT 
    '订单用户关联异常' as check_name,
    COUNT(*) as error_count
FROM orders o
LEFT JOIN users u ON o.user_id = u.id
WHERE u.id IS NULL
UNION ALL
SELECT 
    '订单商品关联异常' as check_name,
    COUNT(*) as error_count
FROM orders o
LEFT JOIN goods g ON o.goods_id = g.id
WHERE g.id IS NULL;

验收标准

表结构验收

  • 所有核心表创建成功
  • 索引和约束配置正确
  • 外键关系建立完成
  • 表命名符合snake_case规范

数据迁移验收

  • 用户数据迁移完成2,201条记录
  • 商品数据迁移完成503条记录
  • 订单数据迁移完成15条记录
  • 其他核心表数据迁移完成

数据质量验收

  • 记录数量与源数据一致
  • 关键业务数据总和一致
  • 外键关系完整性验证通过
  • 数据格式验证通过
  • 时间数据转换正确

风险点和注意事项

数据迁移风险

  1. 字符编码问题: 中文数据可能出现乱码
  2. 时间戳转换: Unix时间戳转换可能出错
  3. 数据类型溢出: 某些数值可能超出SQL Server类型范围
  4. NULL值处理: MySQL和SQL Server的NULL处理差异

性能风险

  1. 大表迁移: 大表迁移可能耗时较长
  2. 索引重建: 迁移后索引需要重建和优化
  3. 统计信息: 需要更新表统计信息
  4. 查询计划: 新环境的查询计划可能不同

解决方案

  1. 分批迁移: 大表分批次迁移
  2. 字符集统一: 统一使用UTF-8编码
  3. 充分测试: 在测试环境充分验证
  4. 监控日志: 密切监控迁移过程日志

下一阶段准备

为阶段4准备的内容

  • 应用程序连接配置
  • EF Core实体模型
  • API接口适配
  • 性能基准测试

交接文档

  • 表结构创建脚本
  • 数据迁移执行日志
  • 数据验证报告
  • 问题修复记录

阶段3完成标志: 所有核心表结构创建完成,数据迁移执行成功,数据验证通过,为应用程序集成做好准备。