Please start any new threads on our new
site at https://forums.sqlteam.com. We've got lots of great SQL Server
experts to answer whatever question you can come up with.
| Author |
Topic |
|
zurbum
Yak Posting Veteran
55 Posts |
Posted - 2005-11-11 : 15:56:24
|
| I already posted this question in "General SQL", but haven't received an reply yet, so I am reposting it here in hope someone will be able to help me. I've created 2 tablesTEST(TEST_ID int identity, TEST_NAME varchar(20))andSAVE(SAVE_ID int identity, TEST_ID int)I filled TEST table with random data.In query analyzer I opened 3 connection and wroteBEGIN TRANSACTION TESTDECLARE @id intSET @id = (SELECT MAX(TEST_ID) FROM TEST WHERE TEST_ID NOT IN (SELECT TEST_ID FROM SAVE))SELECT * FROM TEST WHERE TEST_ID=@idINSERT INTO SAVE (TEST_ID) VALUES (@id)WAITFOR DELAY '00:00:10'COMMIT TRANSACTION TESTThe idea is to read the data with MAX(Test_id) which has not been read yet(not in SAVE table), saving this id in table SAVE and display the record. Let say if more users try to get the data at the same time, no one should receive the same data.Then I run query in QueryAnalyzer.connection 1, then 2 and then 3.The results were not as expected. In connection 2 and 3 I got the same record from table TEST. I tried with SET TRANSACTION ISOLATION LEVEL SERIALIZABLE but then the second connection was killed"Transaction (Process ID 54) was deadlocked on lock resources with another process and has been chosen as the deadlock victim. Rerun the transaction."Can anyone help me to solve this problem. Thank you. |
|
|
SamC
White Water Yakist
3467 Posts |
Posted - 2005-11-11 : 16:29:46
|
I don't think you need to bracket the following code in a transaction to get what you want.Try this:INSERT INTO SAVE (Test_ID) SELECT MAX(TEST_ID) AS MAXTEST_ID FROM TEST WHERE TEST_ID NOT IN (SELECT TEST_ID FROM SAVE)SET @id = SCOPE_IDENTITY() -- Should return the ID inserted into SAVE |
 |
|
|
zurbum
Yak Posting Veteran
55 Posts |
Posted - 2005-11-11 : 20:43:01
|
| I need to call a store procedure, which from tables gets result. The problem is that if my store procedure is called from 2 different computers in the same time(not always, but happens), both receive same record, which should not happen.Imagine a call center, where store procedure pick up phone number from a table and insert this number in called numbers table. If two computers do a select for record which is not in Already called and not in waiting quive... it happens that both received the same phone number which I don't want.With my example I have just simplified my problem. |
 |
|
|
TG
Master Smack Fu Yak Hacker
6065 Posts |
Posted - 2005-11-11 : 22:26:48
|
You're going to need a different methodology to avoid deadlocking and collisions. One way is to "reserve" a row, then get a reference to the row you just reserved, then process the row then flag it as processed. You don't need to use a "save" table for this but you could if you want to. Here is an example of what I mean:use northwindcreate table queuestatus (statusid int primary key, statusDesc varchar(25) not null)gocreate table queue (queueID int identity(1,1) primary key clustered ,queuestatusid int references queuestatus (statusid) not null ,processerid int not null ,updateDate datetime not null)gocreate nonclustered index processerid_queueStatusID on queue (processerid, queueStatusid) with fillfactor=80goinsert queuestatus (statusid,statusDesc)select 0,'unprocessed' unionselect 1,'Reserved' unionselect 2,'processed'set rowcount 100insert queue (queuestatusid, processerid, updateDate)select 0, 0, getdate()from sysobjectsset rowcount 0gocreate procedure reserveQueueRow @processerid int ,@queueid int outputasset nocount on--reserve a rowupdate queue set processerid = @processerid ,queuestatusid = 1 ,updateDate = getdate()where queueID = (select max(queueID) from queue where queuestatusid = 0 and processerid = 0)if @@rowcount = 0 return -1 --all rows processed--get the id of the reserved rowselect @queueID = queueIDfrom queuewhere processerid = @processeridand queuestatusid = 1if @queueid is null return -2 --problemreturn 0 --successfullgocreate proc queueStatus_upd@queueID int,@queuestatusID intasset nocount onupdate queue set queuestatusid = 2 --processed ,updateDate = getdate()where queueID = @queueIDgo----------------------------------------------------------------------in multiple QA windows:while 1=1begin declare @qid int ,@rc int exec @rc = reserveQueueRow @processerid = @@spid, @queueid = @qid output if @rc = -1 begin raiserror('all rows processed', 10,1) break end if @qid is null begin raiserror ('some problem', 17,1) return end exec queueStatus_upd @queueID = @qid, @queueStatusID = 2 --can also add id to another table if you want waitfor delay '00:00:00.250'end--------------------------------------------------------------------godrop proc reserveQueueRowdrop proc queueStatus_upddrop table queuedrop table queuestatusBe One with the OptimizerTG |
 |
|
|
SamC
White Water Yakist
3467 Posts |
Posted - 2005-11-12 : 13:41:53
|
quote: Originally posted by zurbum The problem is that if my store procedure is called from 2 different computers in the same time(not always, but happens), both receive same record, which should not happen.
I'm probably wrong (again), but I thought the solution I posted was atomic and would handle race conditions from multiple users.Can you devise a test to show that it isn't? To help it break, you could declare a UNIQUE CONSTRAINT on the table: SAVE(SAVE_ID int identity, TEST_ID int UNIQUE NOT NULL)Sam |
 |
|
|
TG
Master Smack Fu Yak Hacker
6065 Posts |
Posted - 2005-11-12 : 15:37:15
|
| >>I'm probably wrong (again), I don't think you're wrong, Sam. Your solution would solve the collision and deadlocking issues. However there's not that much control (the you have it currently). For instance, if all rows in Test exist in Save then max(test_id) will be NULL and the insert will still be attempted. I think if this is for controlling a call center, they will want a more robust solution that can handle all situations gracefully.Be One with the OptimizerTG |
 |
|
|
zurbum
Yak Posting Veteran
55 Posts |
Posted - 2005-11-14 : 02:15:10
|
| SamC and TG thank you very much!!! TG your approach helped, also SamC is not wrong, it is just that I need to do a lot of IFs, checking different tables... so just INSERT with SELECT won't help in my case. |
 |
|
|
zurbum
Yak Posting Veteran
55 Posts |
Posted - 2005-11-19 : 19:01:57
|
| Iam still having difficulties.I was 100% that INSERT INTO TABLE1 (A,B,C) SELECT Something1, Something2, (SELECT TOP 1 Something3 FROM (SELECT...JOIN........WHERE don't exist in TABLE1) is an atomic operation, but it is not. If I run the query at the same time from more connections, I still receive the same result. Any clues why?In MS SQL exists atomic operations? |
 |
|
|
SamC
White Water Yakist
3467 Posts |
Posted - 2005-11-19 : 22:25:49
|
quote: Originally posted by zurbum INSERT INTO TABLE1 (A,B,C) SELECT Something1, Something2, (SELECT TOP 1 Something3 FROM (SELECT...JOIN........WHERE don't exist in TABLE1)
Post your full query so there's no guessing on our part.What result exactly are you getting? |
 |
|
|
zurbum
Yak Posting Veteran
55 Posts |
Posted - 2005-11-20 : 10:56:51
|
| Here it is (simplified again)CREATE TABLE [dbo].[CALLED] ( [CALLED_ID] [int] IDENTITY (1, 1) NOT NULL , [FORCALL_ID] [int] NOT NULL ) ON [PRIMARY]GOCREATE TABLE [dbo].[FORCALL] ( [FORCALL_ID] [int] NOT NULL , [FORCALL_NUMBER] [char] (10) NOT NULL ) ON [PRIMARY]GOINSERT INTO FORCALL(FORCALL_ID, FORCALL_NUMBER) VALUES (1,'111')INSERT INTO FORCALL(FORCALL_ID, FORCALL_NUMBER) VALUES (2,'222')INSERT INTO FORCALL(FORCALL_ID, FORCALL_NUMBER) VALUES (3,'333')INSERT INTO FORCALL(FORCALL_ID, FORCALL_NUMBER) VALUES (4,'444')INSERT INTO FORCALL(FORCALL_ID, FORCALL_NUMBER) VALUES (5,'555')INSERT INTO FORCALL(FORCALL_ID, FORCALL_NUMBER) VALUES (6,'666')INSERT INTO FORCALL(FORCALL_ID, FORCALL_NUMBER) VALUES (7,'777')INSERT INTO FORCALL(FORCALL_ID, FORCALL_NUMBER) VALUES (8,'888')INSERT INTO FORCALL(FORCALL_ID, FORCALL_NUMBER) VALUES (9,'999')INSERT INTO FORCALL(FORCALL_ID, FORCALL_NUMBER) VALUES (10,'000')Now create 3 or more connections withWAITFOR TIME '12:50:00' --we want to start all at the same timeINSERT INTO CALLED (FORCALL_ID) SELECT TOP 1 FORCALL_ID FROM FORCALL FC WHERE NOT EXISTS (SELECT FORCALL_ID FROM CALLED WHERE FORCALL_ID=FC.FORCALL_ID)and start them in query analayzer and wait for time, so all will start at the same time. I've receive results that 2/3 have the same FORCALL_ID which should not happen:SELECT * FROM CALLEDCALLED_ID FORCALL_ID1 1 (this is OK)2 1 (this shouldn't happen !!!)3 2 (this is OK)This is the simplified query. In my query I have to join more tables,.... The problem is the same as in case above and that is the problem of receiving the same number to call for. |
 |
|
|
TG
Master Smack Fu Yak Hacker
6065 Posts |
Posted - 2005-11-21 : 12:36:22
|
I think the issue is that the "where not exists(subquery)" is not part of the transaction. try this with your same test:INSERT INTO CALLED (FORCALL_ID)SELECT TOP 1 fc.FORCALL_ID FROM FORCALL FC left join called ex on ex.forcall_id = fc.forcall_idwhere ex.forcall_id is null Be One with the OptimizerTG |
 |
|
|
zurbum
Yak Posting Veteran
55 Posts |
Posted - 2005-11-22 : 05:09:01
|
| TriedINSERT INTO CALLED (FORCALL_ID)SELECT TOP 1 fc.FORCALL_ID FROM FORCALL fc left join CALLED ex on ex.FORCALL_ID = fc.FORCALL_IDwhere ex.CALLED_ID is nullwithout results.Result isCALLED_ID FORCALL_ID1 1 (ok)3 1 (not ok)2 1 (not ok) |
 |
|
|
|
|
|
|
|