You need to enable JavaScript to run this app.
优惠活动
大模型
产品
解决方案
定价
更多

如何在数据库连接数有限时扩展C#回合制游戏TCP服务器

解决MySQL "Too Many Connections" 问题的C#服务器扩展方案

核心思路

解决这个问题的关键是结合数据库连接池优化+异步请求限流队列,既利用连接池复用连接,又通过并发控制确保同时执行的数据库操作数不超过MySQL允许的连接上限。

第一步:优化MySQL连接池配置

首先确保你的连接字符串正确配置了连接池参数(以MySqlConnector为例),这是基础:

// 连接字符串示例,根据你的MySQL max_connections调整Max Pool Size
string connectionString = "server=localhost;database=game_db;user=root;password=xxx;" +
                          "Max Pool Size=100;" + // 设为MySQL max_connections的70%-80%,留余量给其他连接
                          "Min Pool Size=10;" +
                          "Connection Timeout=15;" +
                          "Pooling=true;"; // 必须开启连接池(默认开启)

MySQL默认max_connections是151,所以Max Pool Size建议设为100-120,避免占满所有数据库连接。

第二步:实现带并发限制的数据库请求队列

通过SemaphoreSlim控制同时执行的数据库操作数,确保不超过连接池上限。我们可以把这个限流逻辑封装到仓储层的基类中,避免重复代码。

1. 仓储基类(带限流)

public abstract class BaseRepository
{
    private readonly SemaphoreSlim _dbSemaphore;
    protected readonly string _connectionString;

    // 通过DI注入SemaphoreSlim,限制并发数等于连接池Max Pool Size
    public BaseRepository(string connectionString, SemaphoreSlim dbSemaphore)
    {
        _connectionString = connectionString;
        _dbSemaphore = dbSemaphore;
    }

    protected async Task<T> ExecuteWithLimitAsync<T>(Func<MySqlConnection, Task<T>> dbOperation)
    {
        await _dbSemaphore.WaitAsync();
        try
        {
            using var conn = new MySqlConnection(_connectionString);
            await conn.OpenAsync();
            return await dbOperation(conn);
        }
        finally
        {
            _dbSemaphore.Release();
        }
    }

    // 无返回值的重载
    protected async Task ExecuteWithLimitAsync(Func<MySqlConnection, Task> dbOperation)
    {
        await _dbSemaphore.WaitAsync();
        try
        {
            using var conn = new MySqlConnection(_connectionString);
            await conn.OpenAsync();
            await dbOperation(conn);
        }
        finally
        {
            _dbSemaphore.Release();
        }
    }
}

2. 具体仓储实现(比如账号系统)

public class AccountRepository : BaseRepository, IAccountRepository
{
    public AccountRepository(IConfiguration config, SemaphoreSlim dbSemaphore)
        : base(config.GetConnectionString("GameDb"), dbSemaphore)
    {
    }

    // 调用存储过程查询账号
    public async Task<Account> GetAccountByIdAsync(int accountId)
    {
        return await ExecuteWithLimitAsync(async conn =>
        {
            using var cmd = new MySqlCommand("sp_GetAccountById", conn);
            cmd.CommandType = CommandType.StoredProcedure;
            cmd.Parameters.AddWithValue("@AccountId", accountId);

            using var reader = await cmd.ExecuteReaderAsync();
            if (await reader.ReadAsync())
            {
                return new Account
                {
                    Id = reader.GetInt32("Id"),
                    Username = reader.GetString("Username"),
                    // 其他字段...
                };
            }
            return null;
        });
    }

    // 插入账号的存储过程调用
    public async Task<int> CreateAccountAsync(string username, string passwordHash)
    {
        return await ExecuteWithLimitAsync(async conn =>
        {
            using var cmd = new MySqlCommand("sp_CreateAccount", conn);
            cmd.CommandType = CommandType.StoredProcedure;
            cmd.Parameters.AddWithValue("@Username", username);
            cmd.Parameters.AddWithValue("@PasswordHash", passwordHash);
            
            var outputParam = new MySqlParameter("@NewAccountId", MySqlDbType.Int32)
            {
                Direction = ParameterDirection.Output
            };
            cmd.Parameters.Add(outputParam);

            await cmd.ExecuteNonQueryAsync();
            return (int)outputParam.Value;
        });
    }
}

3. 依赖注入配置(比如ASP.NET Core或自定义DI容器)

// 在Startup.cs或DI初始化代码中
services.AddSingleton<SemaphoreSlim>(sp => 
    new SemaphoreSlim(initialCount: 100, maxCount: 100)); // 对应连接池的Max Pool Size

services.AddScoped<IAccountRepository, AccountRepository>();
services.AddScoped<IMatchHistoryRepository, MatchHistoryRepository>();
// 其他仓储和服务的注册...

额外优化建议

  • 批量处理非实时请求:比如日志记录、对战历史统计,不需要实时写入的可以攒一批再调用存储过程批量插入,减少请求次数。
    // 示例:批量日志插入
    public async Task BulkInsertLogsAsync(List<GameLog> logs)
    {
        await ExecuteWithLimitAsync(async conn =>
        {
            using var cmd = new MySqlCommand("sp_BulkInsertLogs", conn);
            cmd.CommandType = CommandType.StoredProcedure;
            // 传入表值参数(MySQL需要先定义用户自定义类型)
            var logsParam = cmd.Parameters.AddWithValue("@Logs", logs.ToDataTable());
            logsParam.MySqlDbType = MySqlDbType.Structured;
            await cmd.ExecuteNonQueryAsync();
        });
    }
    
  • 避免长时间持有连接:确保所有数据库操作都用using语句管理连接,操作完成后立即释放回连接池。
  • 动态调整并发数:可以根据服务器负载和数据库连接状态,动态调整SemaphoreSlim的并发上限,避免资源浪费。

内容的提问来源于stack exchange,提问作者user10266595

相关产品推荐
方舟 Agent Plan

超全模态模型 × Harness 升级,最新支持 Deepseek-V4.1-Flash、GLM-5.3 系列、Doubao-Seedream-5.0-pro、Kimi-K3 (部分), 限时 9.9 元起

最近更新时间:2026.07.19 08:17:41