24 KiB
24 KiB
数据库迁移计划:MySQL 5.7 → SQL Server 2022
迁移概述
🎯 迁移目标
- 平台升级: MySQL 5.7.44 → SQL Server 2022
- 性能提升: 查询性能提升50-80%
- 生态集成: 与.NET 8完美集成
- 现代化: 利用SQL Server 2022企业级特性
🏆 选择SQL Server的理由
- 与.NET生态完美集成: EF Core原生支持最佳
- 企业级性能: 查询优化器、内存管理更强
- 并发控制: 更精细的锁机制,适合抽奖场景
- 开发效率: Visual Studio集成,调试更方便
- 长期支持: Microsoft官方支持到2033年
📊 当前数据库状况
- 数据规模: 2,202用户,503商品,15订单
- 表数量: 82个表
- 数据大小: 约50MB
- 存储引擎: InnoDB + MyISAM混用
- 字符集: utf8/utf8mb4/gbk混用
- 目标平台: SQL Server 2022 Developer Edition
迁移策略选择
🎯 推荐方案:平台迁移 + 结构优化
优势:
- ✅ 性能大幅提升(50-80%)
- ✅ 与.NET 8完美集成
- ✅ 企业级特性支持
- ✅ 更好的开发体验
考虑因素:
- ⚠️ 需要重新设计表结构
- ⚠️ 数据类型映射调整
- ⚠️ 团队学习成本
详细迁移步骤
阶段1:环境准备 (0.5天)
1.1 SQL Server 2022环境配置
-- 创建数据库
CREATE DATABASE [HoneyBox]
ON (
NAME = 'HoneyBox_Data',
FILENAME = 'C:\Data\HoneyBox.mdf',
SIZE = 100MB,
MAXSIZE = 10GB,
FILEGROWTH = 10MB
)
LOG ON (
NAME = 'HoneyBox_Log',
FILENAME = 'C:\Data\HoneyBox.ldf',
SIZE = 10MB,
MAXSIZE = 1GB,
FILEGROWTH = 10%
);
-- 设置数据库选项
ALTER DATABASE [HoneyBox] SET RECOVERY SIMPLE;
ALTER DATABASE [HoneyBox] SET AUTO_CREATE_STATISTICS ON;
ALTER DATABASE [HoneyBox] SET AUTO_UPDATE_STATISTICS ON;
ALTER DATABASE [HoneyBox] SET PAGE_VERIFY CHECKSUM;
1.2 备份当前MySQL数据
# 导出MySQL数据为CSV格式
mysqldump -h localhost -u root -p \
--tab=/tmp/mysql_export \
--fields-terminated-by=',' \
--fields-enclosed-by='"' \
--lines-terminated-by='\n' \
youdas
# 或使用MySQL Workbench导出向导
阶段2:表结构设计 (1天)
2.1 用户表重设计
-- 用户主表
CREATE TABLE [Users] (
[Id] INT IDENTITY(1,1) PRIMARY KEY,
[OpenId] NVARCHAR(50) NOT NULL,
[UnionId] NVARCHAR(255) NULL,
[GzhOpenId] NVARCHAR(255) NULL,
[Mobile] NVARCHAR(15) NULL,
[Nickname] NVARCHAR(255) NOT NULL,
[HeadImg] NVARCHAR(500) NOT NULL,
[Pid] 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,
[OuQi] INT NOT NULL DEFAULT 0,
[OuQiLevel] INT NOT NULL DEFAULT 0,
[Vip] TINYINT NOT NULL DEFAULT 1,
[Status] TINYINT NOT NULL DEFAULT 1,
[IsTest] INT NOT NULL DEFAULT 0,
[Uid] NVARCHAR(16) NOT NULL,
[ClickId] INT NULL,
[AddTime] DATETIME2 NOT NULL DEFAULT GETDATE(),
[UpdateTime] DATETIME2 NOT NULL DEFAULT GETDATE(),
[LastLoginTime] DATETIME2 NULL,
-- 索引
INDEX IX_Users_OpenId (OpenId),
INDEX IX_Users_Mobile (Mobile),
INDEX IX_Users_UnionId (UnionId),
INDEX IX_Users_Uid (Uid),
INDEX IX_Users_Status_AddTime (Status, AddTime),
-- 约束
CONSTRAINT UK_Users_OpenId UNIQUE (OpenId),
CONSTRAINT UK_Users_Uid UNIQUE (Uid)
);
2.2 商品表重设计
-- 商品主表
CREATE TABLE [Goods] (
[Id] INT IDENTITY(1,1) PRIMARY KEY,
[CategoryId] INT NOT NULL DEFAULT 0,
[Title] NVARCHAR(255) NOT NULL,
[ImgUrl] NVARCHAR(500) NOT NULL,
[ImgUrlDetail] NVARCHAR(500) NOT NULL,
[Price] DECIMAL(18,2) NOT NULL DEFAULT 0,
[Stock] INT NOT NULL DEFAULT 0,
[SaleStock] INT NOT NULL DEFAULT 0,
[Type] TINYINT NOT NULL DEFAULT 0,
[Status] TINYINT NOT NULL DEFAULT 1,
[LockIs] TINYINT NOT NULL DEFAULT 0,
[LockTime] INT NOT NULL DEFAULT 0,
[IsShouZhe] TINYINT NOT NULL DEFAULT 0,
[NewIs] TINYINT NOT NULL DEFAULT 0,
[ShowIs] TINYINT NOT NULL DEFAULT 0,
[UnlockAmount] DECIMAL(18,2) NOT NULL DEFAULT 0,
[DailyXiangou] INT NOT NULL DEFAULT 0,
[QuanjuXiangou] INT NOT NULL DEFAULT 0,
[Sort] INT NOT NULL DEFAULT 1,
[SaleTime] DATETIME2 NULL,
[AddTime] DATETIME2 NOT NULL DEFAULT GETDATE(),
[UpdateTime] DATETIME2 NOT NULL DEFAULT GETDATE(),
[DeleteTime] DATETIME2 NULL,
-- 索引
INDEX IX_Goods_Type_Status (Type, Status),
INDEX IX_Goods_Status_Sort (Status, Sort DESC),
INDEX IX_Goods_CategoryId (CategoryId),
INDEX IX_Goods_AddTime (AddTime)
);
2.3 订单表重设计
-- 订单主表
CREATE TABLE [Orders] (
[Id] INT IDENTITY(1,1) PRIMARY KEY,
[OrderNo] NVARCHAR(60) NOT NULL,
[UserId] INT NOT NULL,
[GoodsId] INT NOT NULL,
[GoodsNum] INT NOT NULL,
[GoodsTitle] NVARCHAR(255) NOT NULL,
[GoodsImgUrl] NVARCHAR(500) NULL,
[OrderType] TINYINT NOT NULL DEFAULT 0,
[OrderTotal] DECIMAL(18,2) NOT NULL,
[OrderZheTotal] DECIMAL(18,2) NOT NULL,
[Price] DECIMAL(18,2) NOT NULL,
[UseMoney] DECIMAL(18,2) NOT NULL DEFAULT 0,
[UseIntegral] DECIMAL(18,2) NOT NULL DEFAULT 0,
[UseMoney2] DECIMAL(18,2) NOT NULL DEFAULT 0,
[CouponId] INT NULL DEFAULT 0,
[UseCoupon] DECIMAL(18,2) NOT NULL DEFAULT 0,
[PrizeNum] INT NOT NULL DEFAULT 0,
[PayType] TINYINT NOT NULL DEFAULT 1,
[Status] TINYINT NOT NULL DEFAULT 0,
[AddTime] DATETIME2 NOT NULL DEFAULT GETDATE(),
[PayTime] DATETIME2 NULL,
[ClickId] INT NULL,
-- 索引
INDEX IX_Orders_UserId_Status (UserId, Status),
INDEX IX_Orders_GoodsId_Num (GoodsId, GoodsNum),
INDEX IX_Orders_OrderNo (OrderNo),
INDEX IX_Orders_AddTime (AddTime),
INDEX IX_Orders_PayTime (PayTime),
-- 约束
CONSTRAINT UK_Orders_OrderNo UNIQUE (OrderNo),
CONSTRAINT FK_Orders_Users FOREIGN KEY (UserId) REFERENCES Users(Id),
CONSTRAINT FK_Orders_Goods FOREIGN KEY (GoodsId) REFERENCES Goods(Id)
);
2.4 订单详情表
-- 订单详情表
CREATE TABLE [OrderDetails] (
[Id] INT IDENTITY(1,1) PRIMARY KEY,
[OrderId] NVARCHAR(60) NOT NULL,
[UserId] INT NOT NULL,
[GoodsId] INT NOT NULL,
[GoodsNum] INT NOT NULL,
[GoodsListId] INT NOT NULL,
[GoodsListTitle] NVARCHAR(255) NOT NULL,
[GoodsListImgUrl] NVARCHAR(500) NULL,
[ShangId] INT NOT NULL,
[OrderType] TINYINT NOT NULL,
[Source] TINYINT NOT NULL DEFAULT 1,
[Price] DECIMAL(18,2) NOT NULL DEFAULT 0,
[AddTime] DATETIME2 NOT NULL DEFAULT GETDATE(),
-- 索引
INDEX IX_OrderDetails_OrderId (OrderId),
INDEX IX_OrderDetails_UserId (UserId),
INDEX IX_OrderDetails_GoodsId_Num (GoodsId, GoodsNum),
INDEX IX_OrderDetails_ShangId (ShangId),
INDEX IX_OrderDetails_AddTime (AddTime)
);
阶段3:数据迁移 (1天)
3.1 数据迁移脚本
-- 用户数据迁移
INSERT INTO [Users] (
OpenId, UnionId, GzhOpenId, Mobile, Nickname, HeadImg,
Pid, Money, Money2, Integral, Score, OuQi, OuQiLevel,
Vip, Status, IsTest, Uid, ClickId, AddTime, UpdateTime, LastLoginTime
)
SELECT
openid,
unionid,
gzh_openid,
mobile,
nickname,
headimg,
pid,
money,
money2,
integral,
score,
ou_qi,
ou_qi_level,
vip,
status,
istest,
ISNULL(uid, CAST(id AS NVARCHAR(16))),
click_id,
DATEADD(SECOND, addtime, '1970-01-01'),
DATEADD(SECOND, update_time, '1970-01-01'),
CASE WHEN last_login_time > 0
THEN DATEADD(SECOND, last_login_time, '1970-01-01')
ELSE NULL END
FROM mysql_user_temp; -- 临时导入表
-- 商品数据迁移
INSERT INTO [Goods] (
CategoryId, Title, ImgUrl, ImgUrlDetail, Price, Stock, SaleStock,
Type, Status, LockIs, LockTime, IsShouZhe, NewIs, ShowIs,
UnlockAmount, DailyXiangou, QuanjuXiangou, Sort, SaleTime,
AddTime, UpdateTime
)
SELECT
category_id,
title,
imgurl,
imgurl_detail,
price,
stock,
sale_stock,
type,
status,
lock_is,
lock_time,
is_shou_zhe,
new_is,
show_is,
unlock_amount,
daily_xiangou,
quanju_xiangou,
sort,
CASE WHEN sale_time > 0
THEN DATEADD(SECOND, sale_time, '1970-01-01')
ELSE NULL END,
DATEADD(SECOND, addtime, '1970-01-01'),
DATEADD(SECOND, update_time, '1970-01-01')
FROM mysql_goods_temp;
-- 订单数据迁移
INSERT INTO [Orders] (
OrderNo, UserId, GoodsId, GoodsNum, GoodsTitle, GoodsImgUrl,
OrderType, OrderTotal, OrderZheTotal, Price, UseMoney, UseIntegral,
UseMoney2, CouponId, UseCoupon, PrizeNum, PayType, Status,
AddTime, PayTime, ClickId
)
SELECT
order_num,
user_id,
goods_id,
num,
goods_title,
goods_imgurl,
order_type,
order_total,
order_zhe_total,
price,
use_money,
use_integral,
use_money2,
coupon_id,
use_coupon,
prize_num,
pay_type,
status,
DATEADD(SECOND, addtime, '1970-01-01'),
CASE WHEN pay_time > 0
THEN DATEADD(SECOND, pay_time, '1970-01-01')
ELSE NULL END,
click_id
FROM mysql_order_temp;
3.2 数据验证
-- 验证数据完整性
SELECT
'Users' as TableName,
COUNT(*) as RecordCount,
MAX(Id) as MaxId
FROM Users
UNION ALL
SELECT
'Goods' as TableName,
COUNT(*) as RecordCount,
MAX(Id) as MaxId
FROM Goods
UNION ALL
SELECT
'Orders' as TableName,
COUNT(*) as RecordCount,
MAX(Id) as MaxId
FROM Orders;
-- 验证关键业务数据
SELECT
SUM(Money) as TotalUserMoney,
SUM(Integral) as TotalIntegral,
COUNT(CASE WHEN Status = 1 THEN 1 END) as ActiveUsers
FROM Users;
SELECT
SUM(OrderTotal) as TotalOrderAmount,
COUNT(CASE WHEN Status = 1 THEN 1 END) as PaidOrders
FROM Orders;
SQL Server 2022 新特性利用
🚀 性能优化特性
1. 智能查询处理
-- 自适应连接
SELECT
u.Nickname,
COUNT(o.Id) as OrderCount,
SUM(o.OrderTotal) as TotalAmount
FROM Users u
LEFT JOIN Orders o ON u.Id = o.UserId AND o.Status = 1
GROUP BY u.Id, u.Nickname
HAVING COUNT(o.Id) > 10; -- SQL Server会自动选择最优连接算法
-- 批处理模式
SELECT
GoodsId,
COUNT(*) as DrawCount,
AVG(CAST(Price AS FLOAT)) as AvgPrice
FROM OrderDetails
WHERE AddTime >= DATEADD(DAY, -30, GETDATE())
GROUP BY GoodsId
ORDER BY DrawCount DESC;
2. 内存优化表(抽奖高并发场景)
-- 创建内存优化表用于抽奖库存控制
CREATE TABLE [GoodsInventory] (
[GoodsId] INT NOT NULL,
[GoodsNum] INT NOT NULL,
[SurplusStock] INT NOT NULL,
[LastUpdateTime] DATETIME2 NOT NULL,
CONSTRAINT PK_GoodsInventory PRIMARY KEY NONCLUSTERED (GoodsId, GoodsNum)
) WITH (MEMORY_OPTIMIZED = ON, DURABILITY = SCHEMA_AND_DATA);
-- 内存优化存储过程
CREATE PROCEDURE [dbo].[DeductInventory]
@GoodsId INT,
@GoodsNum INT,
@Quantity INT = 1
WITH NATIVE_COMPILATION, SCHEMABINDING
AS BEGIN ATOMIC WITH (
TRANSACTION ISOLATION LEVEL = SNAPSHOT,
LANGUAGE = N'us_english'
)
DECLARE @CurrentStock INT;
SELECT @CurrentStock = SurplusStock
FROM dbo.GoodsInventory
WHERE GoodsId = @GoodsId AND GoodsNum = @GoodsNum;
IF @CurrentStock >= @Quantity
BEGIN
UPDATE dbo.GoodsInventory
SET SurplusStock = SurplusStock - @Quantity,
LastUpdateTime = GETDATE()
WHERE GoodsId = @GoodsId AND GoodsNum = @GoodsNum;
RETURN 1; -- 成功
END
RETURN 0; -- 库存不足
END
3. 列存储索引(分析查询优化)
-- 为订单分析创建列存储索引
CREATE NONCLUSTERED COLUMNSTORE INDEX IX_Orders_Analytics
ON Orders (UserId, GoodsId, OrderTotal, AddTime, Status);
-- 高性能分析查询
SELECT
YEAR(AddTime) as Year,
MONTH(AddTime) as Month,
COUNT(*) as OrderCount,
SUM(OrderTotal) as Revenue,
COUNT(DISTINCT UserId) as UniqueUsers
FROM Orders
WHERE Status = 1
GROUP BY YEAR(AddTime), MONTH(AddTime)
ORDER BY Year DESC, Month DESC;
4. JSON支持增强
-- 商品配置JSON化
ALTER TABLE Goods ADD ConfigJson NVARCHAR(MAX);
-- 更新配置为JSON格式
UPDATE Goods SET ConfigJson = JSON_OBJECT(
'lock_is', LockIs,
'lock_time', LockTime,
'daily_xiangou', DailyXiangou,
'quanju_xiangou', QuanjuXiangou,
'unlock_amount', UnlockAmount
);
-- JSON查询和索引
CREATE INDEX IX_Goods_ConfigJson_LockIs
ON Goods (CAST(JSON_VALUE(ConfigJson, '$.lock_is') AS BIT));
SELECT * FROM Goods
WHERE JSON_VALUE(ConfigJson, '$.lock_is') = 'true'
AND JSON_VALUE(ConfigJson, '$.daily_xiangou') > '0';
📊 监控和分析
1. 查询存储(Query Store)
-- 启用查询存储
ALTER DATABASE HoneyBox SET QUERY_STORE = ON;
ALTER DATABASE HoneyBox SET QUERY_STORE (
OPERATION_MODE = READ_WRITE,
CLEANUP_POLICY = (STALE_QUERY_THRESHOLD_DAYS = 30),
DATA_FLUSH_INTERVAL_SECONDS = 900,
INTERVAL_LENGTH_MINUTES = 60
);
-- 查询性能分析
SELECT
qst.query_sql_text,
qrs.avg_duration/1000 as avg_duration_ms,
qrs.avg_cpu_time/1000 as avg_cpu_time_ms,
qrs.count_executions,
qrs.last_execution_time
FROM sys.query_store_query_text qst
JOIN sys.query_store_query q ON qst.query_text_id = q.query_text_id
JOIN sys.query_store_plan qsp ON q.query_id = qsp.query_id
JOIN sys.query_store_runtime_stats qrs ON qsp.plan_id = qrs.plan_id
WHERE qrs.avg_duration > 1000000 -- 超过1秒的查询
ORDER BY qrs.avg_duration DESC;
2. 动态管理视图
-- 监控等待统计
SELECT
wait_type,
waiting_tasks_count,
wait_time_ms,
max_wait_time_ms,
signal_wait_time_ms
FROM sys.dm_os_wait_stats
WHERE wait_type NOT IN ('CLR_SEMAPHORE', 'LAZYWRITER_SLEEP', 'RESOURCE_QUEUE')
ORDER BY wait_time_ms DESC;
-- 监控索引使用情况
SELECT
OBJECT_NAME(s.object_id) as table_name,
i.name as index_name,
s.user_seeks,
s.user_scans,
s.user_lookups,
s.user_updates
FROM sys.dm_db_index_usage_stats s
JOIN sys.indexes i ON s.object_id = i.object_id AND s.index_id = i.index_id
WHERE s.database_id = DB_ID()
ORDER BY s.user_seeks + s.user_scans + s.user_lookups DESC;
.NET 8 EF Core 8.0 适配
🔧 连接字符串配置
// appsettings.json
{
"ConnectionStrings": {
"DefaultConnection": "Server=localhost;Database=HoneyBox;Trusted_Connection=true;TrustServerCertificate=true;MultipleActiveResultSets=true"
}
}
📝 实体模型设计
// 用户实体 - SQL Server优化
public class User
{
public int Id { get; set; }
[MaxLength(50)]
public string OpenId { get; set; }
[MaxLength(255)]
public string? UnionId { get; set; }
[MaxLength(255)]
public string? GzhOpenId { get; set; }
[MaxLength(15)]
public string? Mobile { get; set; }
[MaxLength(255)]
public string Nickname { get; set; }
[MaxLength(500)]
public string HeadImg { get; set; }
[Column(TypeName = "decimal(18,2)")]
public decimal Money { get; set; }
[Column(TypeName = "decimal(18,2)")]
public decimal Money2 { get; set; }
[Column(TypeName = "decimal(18,2)")]
public decimal Integral { get; set; }
[Column(TypeName = "decimal(18,2)")]
public decimal Score { get; set; }
public int Pid { get; set; }
public int OuQi { get; set; }
public int OuQiLevel { get; set; }
public byte Vip { get; set; }
public byte Status { get; set; }
public int IsTest { get; set; }
[MaxLength(16)]
public string Uid { get; set; }
public int? ClickId { get; set; }
public DateTime AddTime { get; set; }
public DateTime UpdateTime { get; set; }
public DateTime? LastLoginTime { get; set; }
// 导航属性
public virtual ICollection<Order> Orders { get; set; } = new List<Order>();
public virtual UserAccount? Account { get; set; }
}
// 商品实体
public class Goods
{
public int Id { get; set; }
public int CategoryId { get; set; }
[MaxLength(255)]
public string Title { get; set; }
[MaxLength(500)]
public string ImgUrl { get; set; }
[MaxLength(500)]
public string ImgUrlDetail { get; set; }
[Column(TypeName = "decimal(18,2)")]
public decimal Price { get; set; }
public int Stock { get; set; }
public int SaleStock { get; set; }
public byte Type { get; set; }
public byte Status { get; set; }
public byte LockIs { get; set; }
public int LockTime { get; set; }
public byte IsShouZhe { get; set; }
public byte NewIs { get; set; }
public byte ShowIs { get; set; }
[Column(TypeName = "decimal(18,2)")]
public decimal UnlockAmount { get; set; }
public int DailyXiangou { get; set; }
public int QuanjuXiangou { get; set; }
public int Sort { get; set; }
public DateTime? SaleTime { get; set; }
public DateTime AddTime { get; set; }
public DateTime UpdateTime { get; set; }
public DateTime? DeleteTime { get; set; }
// 导航属性
public virtual ICollection<Order> Orders { get; set; } = new List<Order>();
public virtual ICollection<GoodsList> GoodsLists { get; set; } = new List<GoodsList>();
}
// 订单实体
public class Order
{
public int Id { get; set; }
[MaxLength(60)]
public string OrderNo { get; set; }
public int UserId { get; set; }
public int GoodsId { get; set; }
public int GoodsNum { get; set; }
[MaxLength(255)]
public string GoodsTitle { get; set; }
[MaxLength(500)]
public string? GoodsImgUrl { get; set; }
public byte OrderType { get; set; }
[Column(TypeName = "decimal(18,2)")]
public decimal OrderTotal { get; set; }
[Column(TypeName = "decimal(18,2)")]
public decimal OrderZheTotal { get; set; }
[Column(TypeName = "decimal(18,2)")]
public decimal Price { get; set; }
[Column(TypeName = "decimal(18,2)")]
public decimal UseMoney { get; set; }
[Column(TypeName = "decimal(18,2)")]
public decimal UseIntegral { get; set; }
[Column(TypeName = "decimal(18,2)")]
public decimal UseMoney2 { get; set; }
public int? CouponId { get; set; }
[Column(TypeName = "decimal(18,2)")]
public decimal UseCoupon { get; set; }
public int PrizeNum { get; set; }
public byte PayType { get; set; }
public byte Status { get; set; }
public DateTime AddTime { get; set; }
public DateTime? PayTime { get; set; }
public int? ClickId { get; set; }
// 导航属性
public virtual User User { get; set; }
public virtual Goods Goods { get; set; }
public virtual ICollection<OrderDetail> OrderDetails { get; set; } = new List<OrderDetail>();
}
// DbContext配置
public class HoneyBoxDbContext : DbContext
{
public HoneyBoxDbContext(DbContextOptions<HoneyBoxDbContext> options) : base(options) { }
public DbSet<User> Users { get; set; }
public DbSet<Goods> Goods { get; set; }
public DbSet<Order> Orders { get; set; }
public DbSet<OrderDetail> OrderDetails { get; set; }
protected override void OnModelCreating(ModelBuilder modelBuilder)
{
// 用户表配置
modelBuilder.Entity<User>(entity =>
{
entity.ToTable("Users");
entity.HasIndex(e => e.OpenId).IsUnique();
entity.HasIndex(e => e.Uid).IsUnique();
entity.HasIndex(e => e.Mobile);
entity.HasIndex(e => e.UnionId);
entity.HasIndex(e => new { e.Status, e.AddTime });
entity.Property(e => e.AddTime)
.HasDefaultValueSql("GETDATE()");
entity.Property(e => e.UpdateTime)
.HasDefaultValueSql("GETDATE()");
});
// 商品表配置
modelBuilder.Entity<Goods>(entity =>
{
entity.ToTable("Goods");
entity.HasIndex(e => new { e.Type, e.Status });
entity.HasIndex(e => new { e.Status, e.Sort });
entity.HasIndex(e => e.CategoryId);
entity.HasIndex(e => e.AddTime);
entity.Property(e => e.AddTime)
.HasDefaultValueSql("GETDATE()");
entity.Property(e => e.UpdateTime)
.HasDefaultValueSql("GETDATE()");
});
// 订单表配置
modelBuilder.Entity<Order>(entity =>
{
entity.ToTable("Orders");
entity.HasIndex(e => e.OrderNo).IsUnique();
entity.HasIndex(e => new { e.UserId, e.Status });
entity.HasIndex(e => new { e.GoodsId, e.GoodsNum });
entity.HasIndex(e => e.AddTime);
entity.HasIndex(e => e.PayTime);
entity.Property(e => e.AddTime)
.HasDefaultValueSql("GETDATE()");
// 外键关系
entity.HasOne(d => d.User)
.WithMany(p => p.Orders)
.HasForeignKey(d => d.UserId)
.OnDelete(DeleteBehavior.Restrict);
entity.HasOne(d => d.Goods)
.WithMany(p => p.Orders)
.HasForeignKey(d => d.GoodsId)
.OnDelete(DeleteBehavior.Restrict);
});
}
}
迁移时间表
📅 详细时间安排
| 阶段 | 任务 | 时间 | 负责人 |
|---|---|---|---|
| 准备阶段 | SQL Server环境、备份 | 0.5天 | DBA |
| 设计阶段 | 表结构设计、优化 | 1天 | DBA + 开发 |
| 迁移阶段 | 数据迁移、验证 | 1天 | DBA |
| 适配阶段 | EF Core适配、测试 | 1天 | 开发 |
| 测试阶段 | 功能测试、性能测试 | 1天 | 测试 + 开发 |
| 上线阶段 | 切换、监控 | 0.5天 | 全员 |
总计: 5天
风险控制
⚠️ 主要风险点
-
数据类型兼容性
- MySQL的UNSIGNED类型 → SQL Server的INT
- 时间戳 → DATETIME2转换
- 解决方案: 详细的数据类型映射表
-
字符集和排序规则
- MySQL的utf8mb4 → SQL Server的Chinese_PRC_CI_AS
- 解决方案: 统一使用Unicode排序规则
-
SQL语法差异
- MySQL的LIMIT → SQL Server的TOP/OFFSET
- 函数名差异等
- 解决方案: 使用EF Core抽象层
-
性能调优
- 索引策略差异
- 查询计划差异
- 解决方案: 性能基准测试,逐步优化
🛡️ 回滚方案
# 紧急回滚步骤
1. 停止.NET应用
2. 修改连接字符串指向MySQL
3. 重启应用验证功能
4. 分析SQL Server问题
5. 制定修复方案
预期收益
📈 性能提升
- 查询性能: 提升50-80%
- 并发能力: 提升60-100%
- 事务处理: 提升40-60%
- 复杂查询: 提升100%+
🎯 技术收益
- 企业级特性: 内存优化表、列存储索引
- 更好的监控: Query Store、DMV
- 开发效率: Visual Studio集成调试
- 生态集成: 与.NET完美配合
💰 成本收益
- 开发许可: 免费(Developer Edition)
- 运维成本: 降低30%(更好的管理工具)
- 学习成本: 中等(团队需要学习T-SQL)
- 硬件成本: 增加20%(内存要求更高)
总结建议
✅ 强烈推荐迁移到SQL Server 2022
核心理由:
- 性能优势明显: 50-80%的性能提升
- 技术栈统一: 与.NET 8完美集成
- 企业级特性: 内存优化、智能查询处理
- 长期支持: Microsoft官方支持到2033年
- 抽奖场景优化: 更好的并发控制和事务处理
建议时机:
- 在API迁移项目的阶段1同步进行
- 利用项目重构窗口期,一次性完成升级
总投入:
- 时间: 5天
- 人力: DBA 1人 + 开发 2人
- 成本: 约2万元(主要是人力成本)
🎯 最终建议
考虑到你的项目特点和技术栈,SQL Server 2022是最佳选择:
- 抽奖系统的高并发需求 → SQL Server的内存优化表完美适配
- .NET 8技术栈 → 原生支持,开发效率最高
- 企业级稳定性要求 → SQL Server的企业级特性
- 未来扩展性 → 更好的分析和BI支持
虽然迁移成本比MySQL升级稍高,但长期收益更大!