HaniBlindBox/docs/数据库迁移计划.md
2026-01-02 01:00:52 +08:00

882 lines
24 KiB
Markdown
Raw Blame History

This file contains ambiguous Unicode characters

This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.

# 数据库迁移计划MySQL 5.7 → SQL Server 2022
## 迁移概述
### 🎯 迁移目标
- **平台升级**: MySQL 5.7.44 → SQL Server 2022
- **性能提升**: 查询性能提升50-80%
- **生态集成**: 与.NET 8完美集成
- **现代化**: 利用SQL Server 2022企业级特性
### 🏆 选择SQL Server的理由
1. **与.NET生态完美集成**: EF Core原生支持最佳
2. **企业级性能**: 查询优化器、内存管理更强
3. **并发控制**: 更精细的锁机制,适合抽奖场景
4. **开发效率**: Visual Studio集成调试更方便
5. **长期支持**: 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环境配置
```sql
-- 创建数据库
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数据
```bash
# 导出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 用户表重设计
```sql
-- 用户主表
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 商品表重设计
```sql
-- 商品主表
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 订单表重设计
```sql
-- 订单主表
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 订单详情表
```sql
-- 订单详情表
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 数据迁移脚本
```sql
-- 用户数据迁移
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 数据验证
```sql
-- 验证数据完整性
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. 智能查询处理
```sql
-- 自适应连接
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. 内存优化表(抽奖高并发场景)
```sql
-- 创建内存优化表用于抽奖库存控制
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. 列存储索引(分析查询优化)
```sql
-- 为订单分析创建列存储索引
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支持增强
```sql
-- 商品配置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)
```sql
-- 启用查询存储
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. 动态管理视图
```sql
-- 监控等待统计
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 适配
### 🔧 连接字符串配置
```csharp
// appsettings.json
{
"ConnectionStrings": {
"DefaultConnection": "Server=localhost;Database=HoneyBox;Trusted_Connection=true;TrustServerCertificate=true;MultipleActiveResultSets=true"
}
}
```
### 📝 实体模型设计
```csharp
// 用户实体 - 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天
## 风险控制
### ⚠️ 主要风险点
1. **数据类型兼容性**
- MySQL的UNSIGNED类型 → SQL Server的INT
- 时间戳 → DATETIME2转换
- 解决方案: 详细的数据类型映射表
2. **字符集和排序规则**
- MySQL的utf8mb4 → SQL Server的Chinese_PRC_CI_AS
- 解决方案: 统一使用Unicode排序规则
3. **SQL语法差异**
- MySQL的LIMIT → SQL Server的TOP/OFFSET
- 函数名差异等
- 解决方案: 使用EF Core抽象层
4. **性能调优**
- 索引策略差异
- 查询计划差异
- 解决方案: 性能基准测试,逐步优化
### 🛡️ 回滚方案
```bash
# 紧急回滚步骤
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
**核心理由**:
1. **性能优势明显**: 50-80%的性能提升
2. **技术栈统一**: 与.NET 8完美集成
3. **企业级特性**: 内存优化、智能查询处理
4. **长期支持**: Microsoft官方支持到2033年
5. **抽奖场景优化**: 更好的并发控制和事务处理
**建议时机**:
- 在API迁移项目的**阶段1**同步进行
- 利用项目重构窗口期,一次性完成升级
**总投入**:
- 时间: 5天
- 人力: DBA 1人 + 开发 2人
- 成本: 约2万元主要是人力成本
### 🎯 最终建议
考虑到你的项目特点和技术栈,**SQL Server 2022是最佳选择**
1. **抽奖系统的高并发需求** → SQL Server的内存优化表完美适配
2. **.NET 8技术栈** → 原生支持,开发效率最高
3. **企业级稳定性要求** → SQL Server的企业级特性
4. **未来扩展性** → 更好的分析和BI支持
虽然迁移成本比MySQL升级稍高但长期收益更大