插入大量记录而不锁定表

时间:2016-09-09 06:09:27

标签: sql sql-server tsql sql-insert batch-insert

我正在尝试将 1,500,000 条记录插入表格中。我在插入过程中面临表锁定问题。所以我想出了下面的批量插入。

    $connect=mysqli_connect($db_server, $db_user, $db_password, $db_database);
    $query="SELECT admin FROM `$table_members` where email='user email address' ";
    $result=mysqli_query($connect, $query);
    while ($row=mysqli_fetch_assoc($result)) {
        $admin = $row['admin'];
        if ($admin=="Administrator") {
            echo "<a href=\"admin.php\">Admin Panel</a>";
        }
        else {
            echo "";
        }
    }

我在DECLARE @BatchSize INT = 50000 WHILE 1 = 1 BEGIN INSERT INTO [dbo].[Destination] (proj_details_sid, period_sid, sales, units) SELECT TOP(@BatchSize) s.proj_details_sid, s.period_sid, s.sales, s.units FROM [dbo].[SOURCE] s WHERE NOT EXISTS (SELECT 1 FROM dbo.Destination d WHERE d.proj_details_sid = s.proj_details_sid AND d.period_sid = s.period_sid) IF @@ROWCOUNT < @BatchSize BREAK END Destination上有一个聚集索引。 (proj_details_sid ,period_sid )部分只是为了限制插入的记录再次插入表

我做得对吗,这会避免表锁吗?还是有更好的方法。

注意:批次和没有批量插入时间或多或少相同

4 个答案:

答案 0 :(得分:5)

锁定升级可能与您声明的SELECT部分无关。

这是natural consequence of inserting a large number of rows

  

如果未使用ALTER TABLE SET LOCK_ESCALATION选项在表上禁用锁定升级,并且存在以下任一条件,则会触发锁定升级:

     
      
  • 单个Transact-SQL语句在单个非分区表或索引上获取至少5,000个锁。
  •   
  • 单个Transact-SQL语句在分区表的单个分区上获取至少5,000个锁,并且ALTER TABLE SET LOCK_ESCALATION选项设置为AUTO。
  •   
  • 数据库引擎实例中的锁数超过了内存或配置阈值。
  •   
     

如果由于锁冲突而无法升级锁,则数据库引擎会定期触发锁定升级,每次获取新锁1250个。

您可以通过跟踪Profiler中的锁定升级事件或仅使用不同的批处理大小尝试以下内容来轻松查看此内容。对我来说,TOP (6228)显示了6250个锁定,但是TOP (6229)随着锁定升级的开始突然下降到1个。确切的数字可能会有所不同(取决于当前可用的数据库设置和资源)。使用试错法查找为您显示锁定升级的阈值。

CREATE TABLE [dbo].[Destination]
  (
     proj_details_sid INT,
     period_sid       INT,
     sales            INT,
     units            INT
  )

BEGIN TRAN --So locks are held for us to count in the next statement
INSERT INTO [dbo].[Destination]
SELECT TOP (6229) 1,
                  1,
                  1,
                  1
FROM   master..spt_values v1,
       master..spt_values v2

SELECT COUNT(*)
FROM   sys.dm_tran_locks
WHERE  request_session_id = @@SPID;

COMMIT

DROP TABLE [dbo].[Destination] 

您正在插入50,000行,因此几乎可以肯定会尝试锁定升级。

文章How to resolve blocking problems that are caused by lock escalation in SQL Server已经很老了,但很多建议仍然有效。

  1. 将大批量操作分解为几个较小的操作(即使用较小的批量)
  2. 如果其他SPID当前持有不兼容的表锁,则不会发生锁升级 - 他们提供的示例是执行不同的会话
  3. BEGIN TRAN
    SELECT * FROM mytable (UPDLOCK, HOLDLOCK) WHERE 1=0
    WAITFOR DELAY '1:00:00'
    COMMIT TRAN 
    
    1. 通过启用跟踪标志1211禁用锁定升级 - 但这是一个全局设置,可能会导致严重问题。有一个较新的选项1224,问题较少,但仍然是全球性的。
    2. 另一种选择是ALTER TABLE blah SET (LOCK_ESCALATION = DISABLE),但这仍然不是很有针对性,因为它会影响所有针对该表的查询,而不仅仅是您的单一场景。

      所以我会选择选项1或可能选项2并打折其他选项。

答案 1 :(得分:3)

而不是检查Destination中是否存在数据,最好先将所有数据存储在临时表中,然后将批量插入Destination

参考:Using ROWLOCK in an INSERT statement (SQL Server)

DECLARE @batch int = 100
DECLARE @curRecord int = 1
DECLARE @maxRecord int

-- remove (nolock) if you don't want to have dirty read
SELECT row_number over (order by s.proj_details_sid, s.period_sid) as rownum,
       s.proj_details_sid,
       s.period_sid,
       s.sales,
       s.units
INTO #Temp
FROM   [dbo].[SOURCE] s WITH (NOLOCK)
WHERE  NOT EXISTS (SELECT 1
                   FROM   dbo.Destination d WITH (NOLOCK)
                   WHERE  d.proj_details_sid = s.proj_details_sid
                          AND d.period_sid = s.period_sid)

-- change this maxRecord if you want to limit the records to insert
SELECT @maxRecord = count(1) from #Temp

WHILE @maxRecord >= @curRecord
   BEGIN
       INSERT INTO [dbo].[Destination] 
              (proj_details_sid,
               period_sid,
               sales,
               units)
       SELECT proj_details_sid, period_sid, sales, units
       FROM #Temp
       WHERE rownum >= @curRecord and rownum < @curRecord + @batch

       SET @curRecord = @curRecord + @batch
   END

DROP TABLE #Temp

答案 2 :(得分:0)

我添加了(NOLOCK)你的目的地表 - &gt; dbo.Destination(NOLOCK)。 现在,你不会锁定你的桌子。

WHILE 1 = 1
  BEGIN
      INSERT INTO [dbo].[Destination] 
                  (proj_details_sid,
                   period_sid,
                   sales,
                   units)
      SELECT TOP(@BatchSize) s.proj_details_sid,
                             s.period_sid,
                             s.sales,
                             s.units
      FROM   [dbo].[SOURCE] s
      WHERE  NOT EXISTS (SELECT 1
                         FROM   dbo.Destination(NOLOCK) d
                         WHERE  d.proj_details_sid = s.proj_details_sid
                                AND d.period_sid = s.period_sid)

      IF @@ROWCOUNT < @BatchSize
        BREAK
  END 

答案 3 :(得分:0)

要执行此操作,您可以在select语句中使用WITH(NOLOCK)。 但是不建议在OLTP数据库上使用NOLOCK。