You need to enable JavaScript to run this app.
最新活动
大模型
产品
解决方案
定价
生态与合作
支持与服务
开发者
了解我们

SQL Server插入少量数据后插入速度变慢问题求助

解决SQL Server批量插入后期速度骤降的问题

首先得明确你的核心问题:用Web API往SQL Server 2008 R2的3-4张表里插300条记录,前20-30条秒级完成,后面单条居然要几秒甚至几分钟,但删除已插数据后重新调用API,300条全插完只要2-3秒。结合我处理过的类似问题,给你梳理几个大概率的原因和对应的解决办法:

一、事务日志拖慢了速度

SQL Server插入数据时会持续写事务日志,如果你的数据库是完整恢复模式又没定期做日志备份,日志文件会不断膨胀,后期写入时不仅要等日志扩容,磁盘IO压力也会飙升。而删除重插时,日志可能已经被截断或者有足够预留空间,所以速度快很多。

解决办法:

  • 如果你不需要精确到时间点的恢复,把数据库改成简单恢复模式
    ALTER DATABASE YourDBName SET RECOVERY SIMPLE;
    
  • 要是必须用完整恢复模式,记得定时做事务日志备份,让SQL Server能自动截断日志;紧急情况下可以手动收缩日志(别频繁用):
    USE YourDBName;
    BACKUP LOG YourDBName WITH TRUNCATE_ONLY;
    DBCC SHRINKFILE (YourDBLogFileName, 1);
    

二、索引维护的开销越来越大

每插一条数据,SQL Server都要更新表上的所有索引。随着数据量增加,索引页分裂的概率会越来越高,每次插入都要折腾多个索引页,耗时自然就上去了。而删除重插时,索引还处于比较紧凑的状态,页分裂少,所以更快。

解决办法:

  • 批量插入前先禁用非聚集索引,插完再重建,能省不少维护时间:
    -- 禁用索引
    ALTER INDEX ALL ON YourTableName DISABLE;
    -- 执行你的插入操作
    -- 重建索引
    ALTER INDEX ALL ON YourTableName REBUILD;
    
  • 检查一下表上的索引是不是太多了,把那些非必要的索引删掉,减少插入时的负担。

三、连接和事务管理不合理

看你的API代码片段,大概率是单条插入用了独立的连接或事务。单条事务的日志写入和锁开销会随着数据量累积,后期就越来越慢;而删除重插时可能是一次性批量处理,效率高很多。

解决办法:

  • 一定要用using包裹SqlConnection,确保连接能正确回收到连接池里(.NET默认开连接池,但错误的连接释放会导致池耗尽):
    using (var conn = new SqlConnection(YourConnString))
    {
        conn.Open();
        // 执行插入逻辑
    }
    
  • 把300条数据的插入放在单个事务里,减少事务日志的写入次数和锁的竞争:
    using (var conn = new SqlConnection(YourConnString))
    {
        conn.Open();
        using (var tran = conn.BeginTransaction())
        {
            try
            {
                foreach (var record in YourDataList)
                {
                    // 用同一个连接和事务执行插入命令
                    using (var cmd = new SqlCommand(InsertSql, conn, tran))
                    {
                        // 设置参数,执行命令
                        cmd.ExecuteNonQuery();
                    }
                }
                tran.Commit();
            }
            catch (Exception ex)
            {
                tran.Rollback();
                throw;
            }
        }
    }
    

四、磁盘IO瓶颈

如果数据文件和日志文件在同一个物理磁盘上,后期插入时磁盘读写竞争会非常激烈,导致速度骤降。而删除重插时,磁盘缓存可能还保留了部分数据,所以速度会快一些。

解决办法:

  • 把数据文件和日志文件分开存到不同的物理磁盘上,分散IO压力;
  • 打开性能监视器(PerfMon),看看PhysicalDisk% Disk TimeAvg. Disk Sec/Write指标,如果持续超过80%,说明磁盘性能不够,考虑换成SSD。

五、锁和阻塞问题

后期插入时,可能有其他会话在读取这些表的数据,导致插入操作被阻塞。而删除重插时表数据少,锁冲突的概率低,所以速度快。

解决办法:

  • 用SQL Server Profiler或者Extended Events监控一下阻塞情况,找到阻塞的会话并处理;
  • 开启READ_COMMITTED_SNAPSHOT隔离级别,减少读写之间的阻塞:
    ALTER DATABASE YourDBName SET READ_COMMITTED_SNAPSHOT ON;
    

额外的优化建议

直接用SqlBulkCopy来做批量插入,比单条循环插入效率高N倍,300条数据完全适合用这个方式:

using (var conn = new SqlConnection(YourConnString))
{
    conn.Open();
    using (var bulkCopy = new SqlBulkCopy(conn))
    {
        bulkCopy.DestinationTableName = "YourTableName";
        // 映射源列和目标列
        bulkCopy.ColumnMappings.Add("SourceColumn1", "DestColumn1");
        bulkCopy.ColumnMappings.Add("SourceColumn2", "DestColumn2");
        // 传入DataTable或者IDataReader
        bulkCopy.WriteToServer(YourDataTable);
    }
}

最后检查下你的API代码里有没有额外的冗余逻辑,比如每次插入都做了不必要的查询或计算,这些也可能累计拖慢速度。

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

火山引擎 最新活动