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

多客户端线程并发调用SQL命令阻塞问题及方案咨询

你的方案思路没问题,绝非小题大做!

首先得给你吃个定心丸:用单线程串行处理SQL操作的思路非常合理,完全不是小题大做。多线程直接怼SQL会带来一堆麻烦——比如你遇到的连接占用问题,还有潜在的数据一致性风险(比如同时更新同一条记录导致的丢失更新)、数据库连接池过载等。串行化处理是低成本解决这类并发问题的经典方案。

不过你的现有实现确实有可以简化的空间,尤其是那个冗长的switch语句,还有手动维护队列的逻辑都能优化:


优化方向1:用线程安全队列替代手动维护的列表

你手动维护m_queuedEvents、m_executingEvents两个列表,还要写MoveQueuedEventsToExecuting来转移任务,这部分可以直接用.NET自带的BlockingCollection<T>——它本身就是线程安全的阻塞队列,自带从队列取元素的阻塞等待逻辑,不用自己写锁和转移代码。

优化方向2:用字典映射替代switch语句

把CommonSQL.Commands和对应的处理方法直接绑定到字典里,新增命令时只需要在字典里加一行映射,不用修改处理逻辑的代码,符合开闭原则。

优化后的完整示例代码

首先定义一个封装SQL任务的类,把命令、参数、回调打包在一起:

public class SqlTask
{
    public CommonSQL.Commands Command { get; set; }
    public object[] Parameters { get; set; }
    public Action<object[]> Callback { get; set; }
}

然后重构SQLThread类:

class SQLThread
{
    // 线程安全的阻塞队列,自动处理多线程添加/取出任务
    private static readonly BlockingCollection<SqlTask> _taskQueue = new BlockingCollection<SqlTask>();
    
    // 命令与处理方法的映射表,替代switch
    private static readonly Dictionary<CommonSQL.Commands, Func<object[], object[]>> _commandHandlers = new()
    {
        { CommonSQL.Commands.SetEntityCoordinates, CommonSQL.SetEntityCoodinates },
        { CommonSQL.Commands.GetMapData, CommonSQL.GetMapData },
        { CommonSQL.Commands.GetEntityCoordinates, CommonSQL.GetEntityCoordinates },
        { CommonSQL.Commands.GetEntityID, CommonSQL.GetEntityID },
        { CommonSQL.Commands.GetPlayerIDAndPassword, CommonSQL.GetPlayerIDAndPassword },
        { CommonSQL.Commands.CreateAccount, CommonSQL.CreateAccount },
        { CommonSQL.Commands.CreateEntity, CommonSQL.CreateEntity }
    };

    // 后台处理线程,静态构造函数自动启动
    private static readonly Task _processingTask;

    static SQLThread()
    {
        // 用LongRunning标记,让线程池优先分配专用线程
        _processingTask = Task.Factory.StartNew(ProcessTasks, TaskCreationOptions.LongRunning);
    }

    // 对外暴露的添加任务方法
    public static void QueueSqlTask(CommonSQL.Commands command, object[] parameters, Action<object[]> callback)
    {
        _taskQueue.Add(new SqlTask 
        { 
            Command = command, 
            Parameters = parameters, 
            Callback = callback 
        });
    }

    private static void ProcessTasks()
    {
        try
        {
            // GetConsumingEnumerable会阻塞直到队列有任务,或者队列被标记为完成
            foreach (var task in _taskQueue.GetConsumingEnumerable())
            {
                if (_commandHandlers.TryGetValue(task.Command, out var handler))
                {
                    try
                    {
                        var result = handler(task.Parameters);
                        task.Callback?.Invoke(result);
                    }
                    catch (Exception ex)
                    {
                        Console.WriteLine($"执行SQL命令 {task.Command} 出错: {ex.Message}");
                        // 可以在这里给回调传递错误信息,方便上层处理
                        task.Callback?.Invoke(new object[] { ex });
                    }
                }
                else
                {
                    Console.WriteLine($"没有为命令 {task.Command} 注册处理方法");
                }
            }
        }
        catch (Exception ex)
        {
            Console.WriteLine($"SQL处理线程异常终止: {ex.Message}");
        }
    }

    // 程序退出时调用,停止处理线程
    public static void StopProcessing()
    {
        _taskQueue.CompleteAdding();
        _processingTask.Wait();
    }
}

额外提醒:尽早解决SQL注入问题

虽然你说先解决并发问题,但还是建议尽快把SQL语句改成参数化查询——现在的string.Format写法风险太高,后续改起来更麻烦。比如把你的示例改成:

public static void SetEntityCoodinates(int x, int y, int ID)
{
    var query = @"UPDATE entities SET CellPosX = @x, CellPosY = @y WHERE entities.ID = @id;";
    // 这里假设SQLReader.RunQuery支持接受参数化查询的参数(比如SqlParameter数组)
    var parameters = new[]
    {
        new SqlParameter("@x", x),
        new SqlParameter("@y", y),
        new SqlParameter("@id", ID)
    };
    SQLReader.RunQuery(query, SQLReader.Stream.INPUT, parameters);
}

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

相关产品推荐
方舟 Agent Plan

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

最近更新时间:2026.05.13 09:26:15