882 lines
24 KiB
Markdown
882 lines
24 KiB
Markdown
# 数据库迁移计划: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升级稍高,但长期收益更大! |