Tra*_*vis 21 sql sql-server oracle queue
关于如何在Oracle和SQL Server中实现类似队列的表(锁定特定行,选择一定数量的行,以及跳过当前锁定的行),有几个 问题.
N假设至少有N行符合条件,我怎样才能保证检索到某些number()行?
从我所看到的,Oracle WHERE在确定要跳过哪些行之前应用谓词.这意味着如果我想从表中拉出一行,并且两个线程同时执行相同的SQL,则一个将接收该行,另一个将接收一个空结果集(即使有更多符合条件的行).
这违背了SQL Server如何显示处理UPDLOCK,ROWLOCK并READPAST锁定提示.在SQL Server中,TOP神奇地似乎限制了成功获得锁定后的记录数.
ORACLE
CREATE TABLE QueueTest (
ID NUMBER(10) NOT NULL,
Locked NUMBER(1) NULL,
Priority NUMBER(10) NOT NULL
);
ALTER TABLE QueueTest ADD CONSTRAINT PK_QueueTest PRIMARY KEY (ID);
CREATE INDEX IX_QueuePriority ON QueueTest(Priority);
INSERT INTO QueueTest (ID, Locked, Priority) VALUES (1, NULL, 4);
INSERT INTO QueueTest (ID, Locked, Priority) VALUES (2, NULL, 3);
INSERT INTO QueueTest (ID, Locked, Priority) VALUES (3, NULL, 2);
INSERT INTO QueueTest (ID, Locked, Priority) VALUES (4, NULL, 1);
Run Code Online (Sandbox Code Playgroud)
在两个单独的会话中,执行:
SELECT qt.ID
FROM QueueTest qt
WHERE qt.ID IN (
SELECT ID
FROM
(SELECT ID FROM QueueTest WHERE Locked IS NULL ORDER BY Priority)
WHERE ROWNUM = 1)
FOR UPDATE SKIP LOCKED
Run Code Online (Sandbox Code Playgroud)
请注意,第一个返回一行,第二个会话不返回一行:
第1节
ID ---- 4
第二节
ID ----
SQL SERVER
CREATE TABLE QueueTest (
ID INT IDENTITY NOT NULL,
Locked TINYINT NULL,
Priority INT NOT NULL
);
ALTER TABLE QueueTest ADD CONSTRAINT PK_QueueTest PRIMARY KEY NONCLUSTERED (ID);
CREATE INDEX IX_QueuePriority ON QueueTest(Priority);
INSERT INTO QueueTest (Locked, Priority) VALUES (NULL, 4);
INSERT INTO QueueTest (Locked, Priority) VALUES (NULL, 3);
INSERT INTO QueueTest (Locked, Priority) VALUES (NULL, 2);
INSERT INTO QueueTest (Locked, Priority) VALUES (NULL, 1);
Run Code Online (Sandbox Code Playgroud)
在两个单独的会话中,执行:
BEGIN TRANSACTION
SELECT TOP 1 qt.ID
FROM QueueTest qt
WITH (UPDLOCK, ROWLOCK, READPAST)
WHERE Locked IS NULL
ORDER BY Priority;
Run Code Online (Sandbox Code Playgroud)
请注意,两个会话都返回不同的行.
第1节
ID ---- 4
第二节
ID ---- 3
我怎样才能在Oracle中获得类似的行为?
Gar*_*ers 14
"根据我的看法,Oracle在确定要跳过的行之前应用WHERE谓词."
对.这是唯一可能的方式.在确定结果集之前,不能跳过结果集中的行.
答案是不要限制SELECT语句返回的行数.您仍然可以使用FIRST_ROWS_n提示来指示优化器,您将不会获取完整的数据集.
调用SELECT的软件应该只选择前n行.在PL/SQL中,它会是
DECLARE
CURSOR c_1 IS
SELECT /*+FIRST_ROWS_1*/ qt.ID
FROM QueueTest qt
WHERE Locked IS NULL
ORDER BY PRIORITY
FOR UPDATE SKIP LOCKED;
BEGIN
OPEN c_1;
FETCH c_1 into ....
IF c_1%FOUND THEN
...
END IF;
CLOSE c_1;
END;
Run Code Online (Sandbox Code Playgroud)
Ste*_*ell 10
Gary Meyers发布的解决方案是关于我能想到的所有,而不是使用AQ,它可以为您提供所有这些以及更多功能.
如果你真的想避开PLSQL,你应该能够将PLSQL转换为Java JDBC调用.您需要做的就是准备相同的SQL语句,执行它然后继续对其进行单行读取(或N行读取).
http://download.oracle.com/docs/cd/B10501_01/java.920/a96654/resltset.htm#1023642上的Oracle文档提供了一些如何在语句级别执行此操作的线索:
要设置查询的提取大小,请在执行查询之前在语句对象上调用setFetchSize().如果将获取大小设置为N,则每次访问数据库时都会获取N行.
所以你可以在Java中编写类似的内容(在伪代码中):
stmt = Prepare('SELECT /*+FIRST_ROWS_1*/ qt.ID
FROM QueueTest qt
WHERE Locked IS NULL
ORDER BY PRIORITY
FOR UPDATE SKIP LOCKED');
stmt.setFetchSize(10);
stmt.execute();
batch := stmt.fetch();
foreach row in batch {
-- process row
}
commit (to free the locks from the update)
stmt.close;
Run Code Online (Sandbox Code Playgroud)
根据以下评论,建议使用ROWNUM来限制收到的结果,但在这种情况下不起作用.考虑这个例子:
create table lock_test (c1 integer);
begin
for i in 1..10 loop
insert into lock_test values (11 - i);
end loop;
commit;
end;
/
Run Code Online (Sandbox Code Playgroud)
现在我们有一个包含10行的表.请注意,我已经以相反的顺序小心地插入了行,包含10的行是第一行,然后是9等.
假设您想要前5行,按升序排序 - 即1到5.您的第一次尝试是这样的:
select *
from lock_test
where rownum <= 5
order by c1 asc;
Run Code Online (Sandbox Code Playgroud)
结果如下:
C1
--
6
7
8
9
10
Run Code Online (Sandbox Code Playgroud)
这显然是错误的,几乎每个人都犯了一个错误!查看查询的解释计划:
| Id | Operation | Name | Rows | Bytes | Cost (%CPU)| Time |
---------------------------------------------------------------------------------
| 0 | SELECT STATEMENT | | 5 | 65 | 4 (25)| 00:00:01 |
| 1 | SORT ORDER BY | | 5 | 65 | 4 (25)| 00:00:01 |
|* 2 | COUNT STOPKEY | | | | | |
| 3 | TABLE ACCESS FULL| LOCK_TEST | 10 | 130 | 3 (0)| 00:00:01 |
---------------------------------------------------------------------------------
Predicate Information (identified by operation id):
---------------------------------------------------
2 - filter(ROWNUM<=5)
Run Code Online (Sandbox Code Playgroud)
Oracle从下到上执行计划 - 请注意rownum上的过滤器是在排序之前执行的,Oracle按照它找到的顺序获取行(在这里插入它们的顺序{10,9,8,7,6}) ,在获得5行后停止,然后对该集进行排序.
因此,要获得正确的前5个,您需要先进行排序,然后使用内联视图进行排序:
select * from
(
select *
from lock_test
order by c1 asc
)
where rownum <= 5;
C1
--
1
2
3
4
5
Run Code Online (Sandbox Code Playgroud)
现在,为了最终达到目的 - 你能将更新跳过锁定在正确的位置吗?
select * from
(
select *
from lock_test
order by c1 asc
)
where rownum <= 5
for update skip locked;
Run Code Online (Sandbox Code Playgroud)
这给出了一个错误:
ORA-02014: cannot select FOR UPDATE from view with DISTINCT, GROUP BY, etc
Run Code Online (Sandbox Code Playgroud)
尝试将for update移动到视图中会出现语法错误:
select * from
(
select *
from lock_test
order by c1 asc
for update skip locked
)
where rownum <= 5;
Run Code Online (Sandbox Code Playgroud)
唯一可行的是以下内容,它会带来错误的结果:
select *
from lock_test
where rownum <= 5
order by c1 asc
for update skip locked;
Run Code Online (Sandbox Code Playgroud)
事实上,如果您在会话1中运行此查询,然后在第二个会话中再次运行它,会话2将给出零行,这真的是错误的!
所以,你可以做什么?打开游标并从中获取所需的行数:
set serveroutput on
declare
v_row lock_test%rowtype;
cursor c_lock_test
is
select c1
from lock_test
order by c1
for update skip locked;
begin
open c_lock_test;
fetch c_lock_test into v_row;
dbms_output.put_line(v_row.c1);
close c_lock_test;
end;
/
Run Code Online (Sandbox Code Playgroud)
如果在会话1中运行该块,它将在第一行锁定时打印出"1".然后在会话2中再次运行它,它会在跳过第1行时打印"2"并获得下一个免费的.
这个例子在PLSQL中,但是在Java中使用setFetchSize你应该能够得到完全相同的行为.
| 归档时间: |
|
| 查看次数: |
18426 次 |
| 最近记录: |