February 24, 2021 at 9:37 pm
Hi all,
In this, very simplified scenario, we have a table with two fields and few records:
create table log_test1
(c1 int,
c2 int)
go
insert into log_test1
values
(1,11),
(2,12)
go
In a stored procedure, we select max from one of the columns, add one, and insert back into the table:
select @v1 = max(c1)
from log_test1
set @v2 = @v1 + 1
insert into log_test1
select @v2, @v3
But the problem is, if at the same time another process will try to execute the same S.P., we will have inconsistent records, because the same value for @v2 will be used for different records.
How to prevent this? One thought is to enclose this code into a transaction, with setting trans isolation level to serializable. Will it work? Is there a better way to handle it, for example with a row-level locking?
Thanks
February 24, 2021 at 9:59 pm
Look at using OUTPUT instead of a SELECT/INSERT method (https://docs.microsoft.com/en-us/sql/t-sql/queries/output-clause-transact-sql?view=sql-server-ver15)
Jeffrey Williams
“We are all faced with a series of great opportunities brilliantly disguised as impossible situations.”
― Charles R. Swindoll
How to post questions to get better answers faster
Managing Transaction Logs
February 24, 2021 at 10:00 pm
As a thought, what about if you switched C1 to be an IDENTITY value or a SEQUENCE or a calculated column and not insert it via a stored procedure but have it auto-generated?
The above is all just my opinion on what you should do.
As with all advice you find on a random internet forum - you shouldn't blindly follow it. Always test on a test server to see if there is negative side effects before making changes to live!
I recommend you NEVER run "random code" you found online on any system you care about UNLESS you understand and can verify the code OR you don't care if the code trashes your system.
February 24, 2021 at 11:00 pm
begin transaction
select @v1 = max(c1)
from log_test1 with(XLOCK)
set @v2 = @v1 + 1
insert into log_test1
select @v2, @v3
commit transaction
February 25, 2021 at 5:45 am
Is this atomic in this situation? (My recollection is that it is NOT, without appropriate LOCK hint)
insert into log_test1
select(SELECT MAX(c1)+1 FROM log_test1)
, @v3
February 25, 2021 at 10:04 am
Firstly, you need to correctly handle the NULL returned by MAX if the table is empty.
Secondly, take a look at this article on concurrency by Gail Shaw.
You will probably end up with something like this
INSERT INTO log_test1
SELECT ISNULL( (SELECT MAX(c1)FROM log_test1 WITH (XLOCK, HOLDLOCK)), 0 ) + 1
, @v3;
February 25, 2021 at 5:13 pm
Thanks all for replies.
Jonathan, your solution works. The only downside is that it creates blockings. But this is what was expected.
February 25, 2021 at 11:50 pm
I have to agree with Brian... what is the problem with using something that guarantees you won't have a problem with concurrency such as an IDENTITY column or a SEQUENCE? For that matter, the content of this column shouldn't actually matter for anything other than uniquely identifying a row so why not use a GUID?
--Jeff Moden
Change is inevitable... Change for the better is not.
February 26, 2021 at 2:19 am
GUIDS are big, fat and ugly... they bloat indexes. 😀
__________________________________________________
Against stupidity the gods themselves contend in vain. -- Friedrich Schiller
Stop, children, what's that sound? Everybody look what's going down. -- Stephen Stills
February 26, 2021 at 2:47 am
GUIDS are big, fat and ugly... they bloat indexes. 😀
Yep... but they work a whole lot better than trying to gerry rig a concurrent MAX solution for this and that's my point... even a GUID is a better solution that using MAX to "get the next id".
The really cool part is that you don't need a "Central Authority" to get one like you would with IDENTITY or SEQUENCE.
--Jeff Moden
Change is inevitable... Change for the better is not.
February 26, 2021 at 3:18 am
Have you tried using a CREATE SEQUENCE construct?
Please post DDL and follow ANSI/ISO standards when asking for help.
February 26, 2021 at 2:17 pm
No. And frankly, I never worked with it. Can you please give more details of how I can use it?
Thanks
February 26, 2021 at 2:43 pm
https://www.red-gate.com/simple-talk/sql/learn-sql-server/sql-server-sequence-basics/
Please post DDL and follow ANSI/ISO standards when asking for help.
February 26, 2021 at 2:51 pm
Thanks all for replies.
Jonathan, your solution works. The only downside is that it creates blockings. But this is what was expected.
Ideally you would just have the first column in table log_test1 defined as an IDENTITY column. Then you wouldn't have to do anything. Your code would then just look like this:
insert into log_test1(col2)
select @v3
Is there any reason why you can't just change the column type on this table to be an identity?
Viewing 15 posts - 1 through 15 (of 16 total)
You must be logged in to reply to this topic. Login to reply