我正在尝试将 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 )
部分只是为了限制插入的记录再次插入表
我做得对吗,这会避免表锁吗?还是有更好的方法。
注意:批次和没有批量插入时间或多或少相同
答案 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已经很老了,但很多建议仍然有效。
BEGIN TRAN
SELECT * FROM mytable (UPDLOCK, HOLDLOCK) WHERE 1=0
WAITFOR DELAY '1:00:00'
COMMIT TRAN
另一种选择是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。