关于forall产生的死锁

[复制链接]
查看11 | 回复6 | 2007-7-3 10:06:32 | 显示全部楼层 |阅读模式
有这样一个表test,需要成批的插入数据;表中列a要求唯一;对于a对应的值,如果表中没有则插入数据,有则更新
create table test(a varchar2(100) not null,b varchar2(10) not null);
create unique index un_test_a on test(a);
我编写了如下的包,
create or replace package p_test is
TYPE Varchar_Array IS TABLE OF VARCHAR(4000) INDEX BY BINARY_INTEGER;
TYPE Integer_Array IS TABLE OF INTEGER INDEX BY BINARY_INTEGER;
PROCEDURE Proc_test
(
Arr_IndexIN OUT Varchar_Array,
Arr_CountIN OUT Varchar_Array,
Arr_Errors IN OUT Varchar_Array
);
end p_test;
create or replace package body p_test is
PROCEDURE Proc_Test
(
Arr_IndexIN OUT Varchar_Array,
Arr_CountIN OUT Varchar_Array,
Arr_Errors IN OUT Varchar_Array --用于存放出错
) IS
errors NUMBER;
dml_errors EXCEPTION;
err_index INTEGER := 1;
PRAGMA EXCEPTION_INIT(dml_errors, -24381);
BEGIN
FORALL i IN Arr_Index.FIRST .. Arr_Index.LAST SAVE EXCEPTIONS
UPDATE Test a

SET a.b= a.b || Arr_Count(i)

WHERE a.a = Arr_Index(i);
COMMIT WRITE BATCH NOWAIT;---允许丢部分数据
FOR i IN Arr_Index.FIRST .. Arr_Index.LAST LOOP

IF SQL%BULK_ROWCOUNT(i) = 0 THEN

BEGIN

INSERT INTO Test

VALUES (Arr_Index(i),Arr_Count(i));

COMMIT WRITE BATCH NOWAIT;

EXCEPTION

WHEN OTHERS THEN

arr_errors(err_index) := TO_CHAR(i) || '|' || SQLERRM;

err_index := err_index + 1;

END;

END IF;
END LOOP;
EXCEPTION
WHEN dml_errors THEN
errors := SQL%BULK_EXCEPTIONS.COUNT;
FOR i IN 1..errors LOOP

arr_errors(err_index) := TO_CHAR(i) || '|' || TO_CHAR(SQL%BULK_EXCEPTIONS(i).ERROR_INDEX) || '|' || SQLERRM(-SQL%BULK_EXCEPTIONS(i).ERROR_CODE);

err_index := err_index + 1;
END LOOP;
END Proc_Test;
end p_test;
使用php调用这个包,循环调用,在每次执行完之后都会提交。语句如下
.......
for ($j=0;;$j++){
.......
$stmt = @oci_parse($conn, "BEGIN P_Test.Proc_Test(:arr_index,:arr_count,:arr_errors); END;");
if ($stmt){

oci_bind_array_by_name($stmt, ":arr_index", $arr_in_index, $arr_realcount, -1, SQLT_CHR);
oci_bind_array_by_name($stmt, ":arr_count", $arr_in_count, $arr_realcount, -1, SQLT_INT);
oci_bind_array_by_name($stmt, ":arr_errors", $arr_errors, $arr_realcount, 4000, SQLT_CHR);
if (!@oci_execute($stmt)){
//错误处理
$e = @oci_error($stmt);
$errorcount = $errorcount + count($arr_errors);
}
@oci_commit($conn);
}else{
//未连接到数据库
$e = @oci_error($stmt);
}//if ($stmt)
}
经过使用发现,这样会产生死锁,而且是持续的锁,只要发生,以后会继续锁住。请问一下,怎样避免这种情况的发生,或者怎样调整设计,来实现这种类似merge语句的情况?表中每天的数据量在百万左右,每五分钟处理一次,更新(插入)几万条。谢谢!
回复

使用道具 举报

千问 | 2007-7-3 10:06:32 | 显示全部楼层
大家没有遇到过类似的问题?还是通过减少每次提交的数量来实现?这样的话forall的效率就体现不出来了 :(
回复

使用道具 举报

千问 | 2007-7-3 10:06:32 | 显示全部楼层
原帖由 wwx 于 2008-8-19 16:27 发表
有这样一个表test,需要成批的插入数据;表中列a要求唯一;对于a对应的值,如果表中没有则插入数据,有则更新
create table test(a varchar2(100) not null,b varchar2(10) not null);
create unique index un_test_a on test(a);
我编写了如下的包,
create or replace package p_test is
TYPE Varchar_Array IS TABLE OF VARCHAR(4000) INDEX BY BINARY_INTEGER;
TYPE Integer_Array IS TABLE OF INTEGER INDEX BY BINARY_INTEGER;
PROCEDURE Proc_test
(
Arr_IndexIN OUT Varchar_Array,
Arr_CountIN OUT Varchar_Array,
Arr_Errors IN OUT Varchar_Array
);
end p_test;
create or replace package body p_test is
PROCEDURE Proc_Test
(
Arr_IndexIN OUT Varchar_Array,
Arr_CountIN OUT Varchar_Array,
Arr_Errors IN OUT Varchar_Array --用于存放出错
) IS
errors NUMBER;
dml_errors EXCEPTION;
err_index INTEGER := 1;
PRAGMA EXCEPTION_INIT(dml_errors, -24381);
BEGIN
FORALL i IN Arr_Index.FIRST .. Arr_Index.LAST SAVE EXCEPTIONS
UPDATE Test a

SET a.b= a.b || Arr_Count(i)

WHERE a.a = Arr_Index(i);
COMMIT WRITE BATCH NOWAIT;---允许丢部分数据
FOR i IN Arr_Index.FIRST .. Arr_Index.LAST LOOP

IF SQL%BULK_ROWCOUNT(i) = 0 THEN

BEGIN

INSERT INTO Test

VALUES (Arr_Index(i),Arr_Count(i));

COMMIT WRITE BATCH NOWAIT;

EXCEPTION

WHEN OTHERS THEN

arr_errors(err_index) := TO_CHAR(i) || '|' || SQLERRM;

err_index := err_index + 1;

END;

END IF;
END LOOP;
EXCEPTION
WHEN dml_errors THEN
errors := SQL%BULK_EXCEPTIONS.COUNT;
FOR i IN 1..errors LOOP

arr_errors(err_index) := TO_CHAR(i) || '|' || TO_CHAR(SQL%BULK_EXCEPTIONS(i).ERROR_INDEX) || '|' || SQLERRM(-SQL%BULK_EXCEPTIONS(i).ERROR_CODE);

err_index := err_index + 1;
END LOOP;
END Proc_Test;
end p_test;
使用php调用这个包,循环调用,在每次执行完之后都会提交。语句如下
.......
for ($j=0;;$j++){
.......
$stmt = @oci_parse($conn, "BEGIN P_Test.Proc_Test(:arr_index,:arr_count,:arr_errors); END;");
if ($stmt){

oci_bind_array_by_name($stmt, ":arr_index", $arr_in_index, $arr_realcount, -1, SQLT_CHR);
oci_bind_array_by_name($stmt, ":arr_count", $arr_in_count, $arr_realcount, -1, SQLT_INT);
oci_bind_array_by_name($stmt, ":arr_errors", $arr_errors, $arr_realcount, 4000, SQLT_CHR);
if (!@oci_execute($stmt)){
//错误处理
$e = @oci_error($stmt);
$errorcount = $errorcount + count($arr_errors);
}
@oci_commit($conn);
}else{
//未连接到数据库
$e = @oci_error($stmt);
}//if ($stmt)
}
经过使用发现,这样会产生死锁,而且是持续的锁,只要发生,以后会继续锁住。请问一下,怎样避免这种情况的发生,或者怎样调整设计,来实现这种类似merge语句的情况?表中每天的数据量在百万左右,每五分钟处理一次,更新(插入)几万条。谢谢!


表的数据量总共有多大?
看这个功能好像有点数据仓库方面的。
一天就有百万,那表很大了。
ETL工具比用SQL来做很方便。
而且这个SQL还有数据丢失,数据质量肯定有问题啊。
回复

使用道具 举报

千问 | 2007-7-3 10:06:32 | 显示全部楼层
一个表里每天大概要增加百万条记录;使用分区表来划分数据,每天增加的数据,只跟当天的其他数据有关,与其他时间的数据无关。因为数据量大,提交太频繁会导致日志写等待,而且数据重要性不高,允许有部分数据丢失,所以采用了COMMIT WRITE BATCH NOWAIT
回复

使用道具 举报

千问 | 2007-7-3 10:06:32 | 显示全部楼层
收藏了。。。
回复

使用道具 举报

千问 | 2007-7-3 10:06:32 | 显示全部楼层
你说的死锁是deadlock? 如果是那么ORACLE会检测到并自动解开。
有多少个进程同时在执行这个存储过程?能否先INSERT到临时表再执行MERGE INTO?
回复

使用道具 举报

千问 | 2007-7-3 10:06:32 | 显示全部楼层
对,就是ORA-00060错误。检查发现了,上面的程序有逻辑错误,导致出错时候没有提交。现在修改成这样子,但是仍然无法避免死锁
create or replace package body p_test is
PROCEDURE Proc_Test
(
Arr_IndexIN OUT Varchar_Array,
Arr_CountIN OUT Varchar_Array,
Arr_Errors IN OUT Varchar_Array --用于存放出错
) IS
errors NUMBER;
dml_errors EXCEPTION;
err_index INTEGER := 1;
PRAGMA EXCEPTION_INIT(dml_errors, -24381);
BEGIN
BEGIN

FORALL i IN Arr_Index.FIRST .. Arr_Index.LAST SAVE EXCEPTIONS

UPDATE Test a

SET a.b= a.b || Arr_Count(i)
WHERE a.a = Arr_Index(i);

COMMIT WRITE BATCH NOWAIT;---可以丢部分数据
EXCEPTION

WHEN dml_errors THEN

errors := SQL%BULK_EXCEPTIONS.COUNT;

FOR i IN 1..errors LOOP

arr_errors(err_index) := TO_CHAR(i) || '|' || TO_CHAR(SQL%BULK_EXCEPTIONS(i).ERROR_INDEX) || '|' || SQLERRM(-SQL%BULK_EXCEPTIONS(i).ERROR_CODE);

err_index := err_index + 1;

END LOOP;

---这里也进行提交

COMMIT WRITE BATCH NOWAIT;
END;
---插入数据
FOR i IN Arr_Index.FIRST .. Arr_Index.LAST LOOP

IF SQL%BULK_ROWCOUNT(i) = 0 THEN

BEGIN

INSERT INTO Test

VALUES (Arr_Index(i),Arr_Count(i));

COMMIT WRITE BATCH NOWAIT;

EXCEPTION

--其实这里还有问题,如果其他用户已经先插入了数据,这里应该再判断是否重复,再进行更新

WHEN OTHERS THEN

arr_errors(err_index) := TO_CHAR(i) || '|' || SQLERRM;

err_index := err_index + 1;

COMMIT WRITE BATCH NOWAIT;--提交

END;

END IF;
END LOOP;
EXCEPTION
--这里应该是没有作用的
WHEN dml_errors THEN
errors := SQL%BULK_EXCEPTIONS.COUNT;
FOR i IN 1..errors LOOP

arr_errors(err_index) := TO_CHAR(i) || '|' || TO_CHAR(SQL%BULK_EXCEPTIONS(i).ERROR_INDEX) || '|' || SQLERRM(-SQL%BULK_EXCEPTIONS(i).ERROR_CODE);

err_index := err_index + 1;
END LOOP;
COMMIT WRITE BATCH NOWAIT;
END Proc_Test;
end p_test;
比如我在表test中插入数据:
insert into test values('a','a');
insert into test values('b','b');
insert into test values('c','c');
insert into test values('d','d');
commit;
然后在一个连接着执行
update test set b='d' where a='d';
再执行程序,分别更新a\b\c\d\的值
再在原来的连接中执行 update test set b='a' where a='a';
死锁就发生了。
而我是无法保证两个不同的程序中传入的数据的顺序的 :(
回复

使用道具 举报

您需要登录后才可以回帖 登录 | 立即注册

本版积分规则

主题

0

回帖

4882万

积分

论坛元老

Rank: 8Rank: 8

积分
48824836
热门排行