1. 为什么选择C#连接MySQL?
在.NET生态中,C#与MySQL的组合堪称经典搭配。我十年前第一次用C#连接MySQL时,市面上可选的驱动还不多,如今MySQL Connector/NET已经发展得非常成熟。这种组合特别适合需要同时利用.NET框架强大功能和MySQL开源优势的场景,比如中小型ERP系统、物联网数据采集平台等。
MySQL官方提供的Connector/NET支持最新的.NET Core和.NET 5+,这意味着我们可以在跨平台应用中使用它。相比SQL Server,MySQL的轻量级特性使其在资源受限的环境中表现更优,而C#的类型安全又能很好地弥补动态SQL可能带来的风险。
需要模型API调用? 免费领10W Token,多模型网关一键接入 Claude、DeepSeek 等主流模型。
2. 环境准备与驱动安装
2.1 选择正确的MySQL驱动版本
当前稳定版是8.0.33,但要注意.NET版本兼容性:
- .NET Framework 4.5+:可用6.x系列
- .NET Core 3.1/5/6:必须用8.x系列
安装方式有三种:
- NuGet包管理器(推荐):
bash复制
Install-Package MySql.Data -Version 8.0.33 - 官网下载DLL手动引用
- MySQL Installer集成安装
重要提示:避免混用不同版本的MySql.Data和MySqlConnector,这会导致运行时类型冲突。我曾在一个项目中同时引用了6.10.7和8.0.22,结果引发了诡异的ConnectionPool异常。
2.2 基础连接字符串配置
最小化的连接字符串示例:
csharp复制"Server=localhost;Database=testdb;Uid=root;Pwd=123456;"
生产环境建议添加这些参数:
csharp复制"Server=db01.example.com;Database=production;Uid=appuser;Pwd=Secure@123;
ConnectionTimeout=15;Pooling=true;MinPoolSize=5;MaxPoolSize=100;
CharSet=utf8mb4;SslMode=Preferred;"
参数详解:
Pooling:连接池开关(默认true)MinPoolSize:物理连接最小保持数SslMode:安全传输模式(None/Preferred/Required)CharSet:字符集(必须用utf8mb4支持完整Unicode)
3. 核心操作实战
3.1 连接管理与异常处理
正确的连接管理模板:
csharp复制using (var conn = new MySqlConnection(connString))
{
try
{
await conn.OpenAsync();
// 业务操作...
}
catch (MySqlException ex)
{
switch (ex.Number)
{
case 1042: // 无法连接
logger.Error($"数据库不可达:{ex.Message}");
break;
case 1045: // 认证失败
logger.Error($"凭据错误:检查用户名密码");
break;
case 0: // 一般错误
logger.Error($"连接字符串可能错误:{connString}");
break;
}
throw;
}
}
踩坑记录:千万不要在using块外部保持连接对象!我曾在ASP.NET应用中误将连接对象存放到静态变量,导致连接池耗尽。正确的做法是每次操作都新建连接,由连接池管理物理连接。
3.2 参数化查询实践
错误示范(SQL注入风险):
csharp复制var sql = $"SELECT * FROM users WHERE name='{userInput}'";
正确做法:
csharp复制const string sql = @"SELECT user_id, username
FROM users
WHERE email = @email
AND is_active = @active";
using var cmd = new MySqlCommand(sql, conn);
cmd.Parameters.AddWithValue("@email", email);
cmd.Parameters.AddWithValue("@active", true);
高级参数技巧:
csharp复制// 批量插入参数化
var parameters = users.Select((u,i) =>
new MySqlParameter($"@name{i}", u.Name)).ToArray();
// 枚举类型处理
cmd.Parameters.Add("@status", MySqlDbType.Int32).Value = (int)UserStatus.Active;
3.3 事务与批量操作
ACID事务示例:
csharp复制using var transaction = await conn.BeginTransactionAsync();
try
{
// 操作1:扣减库存
var updateSql = @"UPDATE inventory
SET stock = stock - @qty
WHERE product_id = @pid";
// ...执行update
// 操作2:创建订单
var insertSql = @"INSERT INTO orders(...) VALUES(...)";
// ...执行insert
await transaction.CommitAsync();
}
catch
{
await transaction.RollbackAsync();
throw;
}
批量插入优化方案(对比):
| 方法 | 1000条耗时 | 内存占用 |
|---|---|---|
| 单条INSERT循环 | 3200ms | 低 |
| 多值INSERT | 450ms | 中 |
| BulkLoader | 210ms | 高 |
| 存储过程+TVP | 180ms | 高 |
实测代码:
csharp复制// 多值INSERT示例
var values = string.Join(",",
products.Select(p => $"('{p.Name}', {p.Price})"));
await new MySqlCommand($"INSERT INTO products VALUES {values}", conn)
.ExecuteNonQueryAsync();
// BulkLoader示例
using var bl = new MySqlBulkLoader(conn)
{
TableName = "products",
FieldTerminator = ",",
LineTerminator = "\n",
FileName = tempFilePath
};
await bl.LoadAsync();
4. 性能调优与监控
4.1 连接池优化策略
连接池的黄金法则:
- 初始连接数(MinPoolSize)= 平均并发请求数 × 0.8
- 最大连接数(MaxPoolSize)= 峰值并发 × 1.2
监控命令:
sql复制SHOW STATUS LIKE 'Threads_connected';
SHOW PROCESSLIST;
4.2 查询性能分析
EXPLAIN实战:
csharp复制var explainCmd = new MySqlCommand("EXPLAIN " + query, conn);
using var reader = await explainCmd.ExecuteReaderAsync();
while (await reader.ReadAsync())
{
var analysis = new {
PossibleKeys = reader["possible_keys"],
KeyUsed = reader["key"],
RowsExamined = reader["rows"]
};
// 分析执行计划...
}
慢查询日志配置:
ini复制# my.cnf配置
slow_query_log = 1
slow_query_log_file = /var/log/mysql-slow.log
long_query_time = 1
log_queries_not_using_indexes = 1
4.3 异步编程最佳实践
错误模式:
csharp复制// 错误:混用同步异步
public List<User> GetUsers()
{
using var conn = new MySqlConnection(...);
conn.Open(); // 同步阻塞
return conn.Query<User>("SELECT...").ToList();
}
正确模式:
csharp复制public async Task<List<User>> GetUsersAsync()
{
await using var conn = new MySqlConnection(...);
// C# 8.0+异步using
await conn.OpenAsync();
return (await conn.QueryAsync<User>("SELECT...")).ToList();
}
经验之谈:在ASP.NET Core中,同步阻塞数据库调用会导致线程池饥饿。我曾有个API在高并发时响应时间从50ms暴增到5s,最终发现是有人写了同步数据库调用。
5. 高级特性集成
5.1 JSON类型支持
MySQL 8.0+的JSON操作:
csharp复制// 插入JSON
var cmd = new MySqlCommand(
@"INSERT INTO products
SET attributes = @attrs", conn);
cmd.Parameters.AddWithValue("@attrs",
JsonSerializer.Serialize(new {
color = "red",
weight = 1.5
}));
// 查询JSON路径
var json = await new MySqlCommand(
@"SELECT JSON_EXTRACT(attributes, '$.color')
FROM products", conn)
.ExecuteScalarAsync();
5.2 实体框架Core集成
DbContext配置:
csharp复制services.AddDbContext<AppDbContext>(options =>
options.UseMySql(
Configuration.GetConnectionString("MySQL"),
new MySqlServerVersion(new Version(8, 0, 33)),
opt => opt.EnableRetryOnFailure(
maxRetryCount: 5,
maxRetryDelay: TimeSpan.FromSeconds(30),
errorNumbersToAdd: null)
));
性能优化技巧:
- 批量操作使用
ExecuteUpdate/ExecuteDelete(EF Core 7+) - 避免N+1查询:使用
Include或投影查询 - 对于复杂查询,优先考虑原生SQL
5.3 分布式场景处理
读写分离配置:
csharp复制services.AddDbContext<AppDbContext>((provider, options) =>
{
var context = provider.GetRequiredService<IOperationContext>();
var connStr = context.IsReadOnly ?
Configuration["ReadReplica"] :
Configuration["Master"];
options.UseMySql(connStr, ServerVersion.AutoDetect(connStr));
});
缓存策略示例:
csharp复制public async Task<Product> GetProductWithCache(int id)
{
var cacheKey = $"product_{id}";
if (_cache.TryGetValue(cacheKey, out Product cached))
return cached;
var product = await _db.Products
.AsNoTracking()
.FirstOrDefaultAsync(p => p.Id == id);
_cache.Set(cacheKey, product, TimeSpan.FromMinutes(30));
return product;
}
6. 生产环境实战经验
6.1 连接故障转移方案
多主机连接字符串:
csharp复制"Server=primary.example.com,secondary.example.com;Database=db;
Connect Timeout=30;ConnectionProtocol=Sockets;
RetryCount=3;RetryDelay=5;"
自定义重试策略:
csharp复制public class MySqlRetryPolicy : IRetryPolicy
{
public bool ShouldRetry(MySqlException ex)
{
return ex.Number switch {
1213 => true, // 死锁
1205 => true, // 锁等待超时
1040 => true, // 连接数耗尽
_ => false
};
}
}
// 使用策略
var conn = new MySqlConnection(connStr);
conn.RetryPolicy = new MySqlRetryPolicy();
6.2 数据加密方案
透明数据加密(TDE)配置:
- 生成密钥文件:
bash复制
openssl rand -hex 32 > /etc/mysql/keyfile - 修改my.cnf:
ini复制[mysqld] early-plugin-load=keyring_file.so keyring_file_data=/etc/mysql/keyfile - 加密表:
sql复制ALTER TABLE users ENCRYPTION='Y';
应用层加密:
csharp复制// 写入时
cmd.Parameters.AddWithValue("@ssn",
AesEncrypt(user.SSN, encryptionKey));
// 读取时
var ssn = AesDecrypt(reader.GetString("ssn"), encryptionKey);
6.3 监控与告警
关键指标监控项:
| 指标 | 阈值 | 检查频率 |
|---|---|---|
| 连接池使用率 | >80% | 1分钟 |
| 查询平均耗时 | >500ms | 5分钟 |
| 活跃事务数 | >20 | 实时 |
| 复制延迟 | >30秒 | 1分钟 |
Prometheus配置示例:
yaml复制scrape_configs:
- job_name: 'mysql'
static_configs:
- targets: ['mysql-exporter:9104']
7. 典型问题排查指南
7.1 连接泄漏检测
诊断步骤:
- 查询当前连接:
sql复制SELECT * FROM performance_schema.threads WHERE PROCESSLIST_USER = 'appuser'; - 代码审查所有数据库操作是否都有using块
- 在连接字符串添加
ConnectionReset=true
7.2 死锁分析
死锁日志获取:
sql复制SHOW ENGINE INNODB STATUS;
典型解决方案:
- 调整事务隔离级别为READ COMMITTED
- 统一表访问顺序
- 减小事务粒度
7.3 字符集问题
乱码排查流程:
- 确认连接字符串有
CharSet=utf8mb4 - 检查表字符集:
sql复制SHOW CREATE TABLE problematic_table; - 验证客户端编码:
csharp复制
Console.WriteLine(Encoding.Default.WebName);
7.4 时区处理
最佳实践:
csharp复制// 连接字符串添加
"DefaultCommandTimeout=30;ConvertZeroDateTime=True;AllowZeroDateTime=False;"
// 代码中明确处理
var cmd = new MySqlCommand(
"SELECT @localTime AS time", conn);
cmd.Parameters.AddWithValue("@localTime",
DateTime.SpecifyKind(DateTime.Now, DateTimeKind.Local));
8. 扩展架构设计
8.1 分库分表方案
ShardingJDBC风格实现:
csharp复制public class OrderShardingStrategy : IShardingStrategy
{
public string GetDataSourceName(object shardingKey)
{
var orderId = (int)shardingKey;
return $"ds{orderId % 4}"; // 分4个库
}
}
// 注册策略
services.AddSingleton<IShardingStrategy, OrderShardingStrategy>();
8.2 读写分离路由
抽象数据源路由:
csharp复制public class DynamicDataSource : DbContext
{
private readonly IHttpContextAccessor _httpContext;
public DynamicDataSource(IHttpContextAccessor httpContext)
{
_httpContext = httpContext;
}
protected override void OnConfiguring(DbContextOptionsBuilder options)
{
var isRead = _httpContext.HttpContext?.Request.Method == "GET";
var connStr = isRead ? _config["ReadDB"] : _config["WriteDB"];
options.UseMySql(connStr);
}
}
8.3 数据迁移方案
Flyway式迁移:
csharp复制public class DbMigrator
{
public async Task MigrateAsync()
{
var files = Directory.GetFiles("Migrations", "*.sql")
.OrderBy(f => int.Parse(Path.GetFileNameWithoutExtension(f)));
foreach (var file in files)
{
var sql = await File.ReadAllTextAsync(file);
await using var cmd = new MySqlCommand(sql, _conn);
await cmd.ExecuteNonQueryAsync();
}
}
}
9. 安全加固措施
9.1 最小权限原则
应用账号授权示例:
sql复制CREATE USER 'appuser'@'%' IDENTIFIED BY 'Complex@123';
GRANT SELECT, INSERT, UPDATE ON db.orders TO 'appuser'@'%';
GRANT SELECT ON db.products TO 'appuser'@'%';
REVOKE ALL PRIVILEGES, GRANT OPTION FROM 'appuser'@'%';
9.2 SQL注入防御
深度防御方案:
- 使用ORM或参数化查询
- 安装MySQL企业防火墙:
sql复制
INSTALL COMPONENT "file://component_mysql_firewall"; - 应用层输入验证:
csharp复制public bool IsValidInput(string input) => !Regex.IsMatch(input, @"[;\-\-]|/\*|\*/|xp_");
9.3 审计日志配置
服务端审计:
ini复制# my.cnf
[mysqld]
plugin-load = audit_log.so
audit_log_format = JSON
audit_log_policy = ALL
客户端审计:
csharp复制public class AuditCommandInterceptor : DbCommandInterceptor
{
public override ValueTask<InterceptionResult<DbDataReader>> ReaderExecutingAsync(
DbCommand command,
CommandEventData eventData,
InterceptionResult<DbDataReader> result,
CancellationToken cancellationToken = default)
{
_logger.LogInformation($"Executing: {command.CommandText}");
return base.ReaderExecutingAsync(command, eventData, result, cancellationToken);
}
}
10. 未来演进方向
10.1 .NET 7+新特性
原生AOT兼容方案:
xml复制<ItemGroup>
<RdXmlFile Include="mysql.rd.xml" />
</ItemGroup>
<!-- mysql.rd.xml内容 -->
<Directives>
<Type Name="MySql.Data.MySqlClient.MySqlConnection" Dynamic="Required All" />
</Directives>
10.2 云原生适配
Kubernetes连接方案:
yaml复制# StatefulSet配置示例
env:
- name: DB_HOST
valueFrom:
fieldRef:
fieldPath: status.podIP
- name: DB_PORT
value: "3306"
10.3 微服务集成
Dapr状态存储配置:
yaml复制apiVersion: dapr.io/v1alpha1
kind: Component
metadata:
name: statestore
spec:
type: state.mysql
version: v1
metadata:
- name: connectionString
value: "server=mysql;port=3306;user=root;password=pass;database=dapr_store"
