Showing posts with label solve. Show all posts
Showing posts with label solve. Show all posts

Friday, March 30, 2012

Puzzled by concurrent update

I have a puzzle in my mind here. I will thank anyone who can solve my puzzle.

I am not familiar with SQL and its theories behind, so please bear with me if I am asking a stupid newbie question.

My puzzle is generally a problem of generating sequence numbers. The following SQL is only a stripped down version - it fetches the max number, add 1 to it and updates the table with the new number.

DECLARE @.max int

SELECT @.max = MAX(next_number) + 1 from sequence_numbers

UPDATE sequence_numbers SET next_number = @.max WHERE next_number = @.max

Now if user1 gets 100 and user2 also gets 100 and they both try to update the table, what would happen? I fear that the result would be 101.

One of my coworker thinks that adding 'WHERE next_number = @.max' can solve the conflict - user2 will fail. His reasoning is like this:

After user1 updates the table, next_number would be 101 and user2's update will fail because his WHERE criteria is still 100.

But I think user2 still sees the old data (100) and still succeeds and thus both users update the table with number 101.

Thanks.

If you want to use a sequence table, you have to lock this table during the update. I wouldn′t use separate statement though the thing that you described first will happen.

You should either do that within a transaction while locking the table or do it within one statement using locking mechanism and queying this with your update statement:

UPDATE sequence_numbers
SET next_number =
( SELECT MAX(next_number) + 1 from sequence_numbers (TABLOCK) )

HTH, Jens Suessmeyer.

http://www.sqlserver2005.de

|||

Hi Neo,

One of the cruical components of DBMS(Database Management System) is the concurrency control (CC) subsytem. It ensures that concurrent execution of transactions against DB doesn't violate the consistency of the database. While the transactions execute concurrently in real, the CC component ensures that the database state is AS IF THEY EXECUTED SEQUENTIALLY.
In two words, the server "locks" rows that some transaction modifies in order to prevent others from reading/writing those rows, until the first transaction commits.
If you need serious understanding about how all this works, open books online, and look for concurrency control. There's a lot of material there, from concurrency control basics up to some advanced issues.
About your co-workers explanation: Imagine that the updates are done not in form "where next_number=@.max" but "where row_id=something". By the model proposed by your friend, this update will succeed, because the first update hasn't modified the row_id. In fact it doesn't-the explanation is wrong.

To Jens: I guess that under default serializability level, that is, read commited, second update just cannot read the modified rows, because they will be locked with EXCLUSIVE lock, which blocks any reading transactions. If the first transaction reads data(read lock), the second one reads(read lock), then both will be denied an exclusive access to the rows needed to update(this is a deadlock).

In either case the execution will be serializable, and there is no need for a TABLOCK, there is no need for explicit locking. Please correct me if I am wrong.

|||

I looked thru SQL BOL on Concurrency Control. But reading those technical explanation does not lead me to solve my puzzle. Can anyone explain more to me or direct to some 'newbie' level articles on CC (if such articles exist)? Thanks.

|||

You need to use a locking hint to single thread access to the table. There is a good article here: http://blogs.msdn.com/sqlcat/archive/2006/04/10/572848.aspx from the Microsoft SQL Server Development Customer Advisory Team.

You have the basics, and the rest is covered in that article.

|||

I read the blog, but it does not answer my problem.

Option 1 is only for 'low volume' - how low? Does 'low 'mean that if it is used in higher volum there will be concurrent issues, such as two users get duplicate seq number?

Option 2 is not very pratical.

|||

The tablock was useless, you are right, I was thinking one step further, updating the sequence table (if that would be the requirement) to ensure that nobody else will query meanwhile the sequence table. I saw that this was not a requirement, changed the query and forgot about the tablock.

To the original poster: if you don′t want to reset (pushing the sequence number one step further) you can use the update with the inline select, that should do the trick.

No deadlock will occur if two are using the same update command, because the only locked table will be the updated one. After this is not released the Select statement won′t take place.


HTH, Jens Suessmeyer.

http://www.sqlserver2005.de

|||

The problem with the first action is that you have to single thread access to the table of sequence numbers in a manner that will be slower than using an identity column. You will not get duplicates, but every user will have to wait.

The biggest problem comes in that when you lock the row to get the max, it has to stay locked until you do the update. But, what if this transaction is part of a greater transaction. Then all processes have to wait until the whole transaction completes because the UPDATE operation causes an exclusive lock that keeps the next user from asking for the max (to avoid dups).

Option 2 beats that by using INSERT operations with identity values that do not block one another and do not require an EXCLUSIVE lock that will block other rows from inserting with the next new value. You can get gaps if transactions are rolled back, but it will work nicely.

The second option can be done with very little work just like the first, though it will look messier.

|||

Thanks for all your replies. They are useful to me.

But how about my original question. Will the two users both succeed in updating (note tha next_number = @.max criteria)?

|||

You can use the TSQL update extension like:

UPDATE sequence_numbers

SET @.max = next_number = next_number + 1

WHERE name = @.blah

The above increments next_number by one and assigns the final result to max in one statement. There is no concurrency issue and the statement itself is serialized due to the exclusive lock on the table or row.

You code doesn't serialize access to getting current maximum number so it will not work.

Putting 'NA' in a Money Datatype Column

Hello,
Can anyone solve this prob...
BACKGROUND:
I have a column in my query that is calculated from money datatypes. To
avoid the divide by zero error I created a nice inline case statement that
places something else in that field instead. It works great with any number
of my choice. For example I can put 100 everywhere that this calculated
field would've had a divide by zero error. HOWEVER, I want to put the word
'NA' in that field when this happens. But I can't because the datatype is
wrong:
ERROR:
Hence the error:Implicit conversion from data type char to money is not
allowed.
PROBLEM:
So is there a way to legally cast or convert so that I can put 'NA' in this
field instead of a number like zero or a hundred?
Here is my attempt so far (doesn't work):
CASE WHEN [expression] = 0 THEN CAST('NA' AS CHAR(2)) ELSE [other
expression]
Thanks.
--
Alex A.You will need to cast the entire field as varchar.
Another option would be to return NULL when the expression = 0 and have the
presentation tier (Crystal Reports, Excel, ASP.NET, etc.) display NULL as
NA.
"Alex A." <AlexA@.discussions.microsoft.com> wrote in message
news:12E0480D-DB52-4664-A2E2-F9D335FE5BB5@.microsoft.com...
> Hello,
> Can anyone solve this prob...
> BACKGROUND:
> I have a column in my query that is calculated from money datatypes. To
> avoid the divide by zero error I created a nice inline case statement that
> places something else in that field instead. It works great with any
> number
> of my choice. For example I can put 100 everywhere that this calculated
> field would've had a divide by zero error. HOWEVER, I want to put the
> word
> 'NA' in that field when this happens. But I can't because the datatype is
> wrong:
> ERROR:
> Hence the error:Implicit conversion from data type char to money is not
> allowed.
> PROBLEM:
> So is there a way to legally cast or convert so that I can put 'NA' in
> this
> field instead of a number like zero or a hundred?
> Here is my attempt so far (doesn't work):
> CASE WHEN [expression] = 0 THEN CAST('NA' AS CHAR(2)) ELSE [other
> expression]
> Thanks.
> --
> Alex A.
>|||WKidd,
Thanks for the post...
I can't cast the whole field because if it is not a divide by zero situation
I need the dollar amount to calculate... But I like the Null idea. Anyone
have the syntax in mind for that? Can I just put the word NULL after the
THEN statement?
"WKidd" wrote:

> You will need to cast the entire field as varchar.
> Another option would be to return NULL when the expression = 0 and have th
e
> presentation tier (Crystal Reports, Excel, ASP.NET, etc.) display NULL as
> NA.
> "Alex A." <AlexA@.discussions.microsoft.com> wrote in message
> news:12E0480D-DB52-4664-A2E2-F9D335FE5BB5@.microsoft.com...
>
>|||Look up hopw to use a NULL in SQL. But I would also get rid of the
MONEY datatype.
The MONEY datatype has rounding errors. Using more than one operation
(multiplication or division) on money columns will produce severe
rounding errors. A simple way to visualize money arithmetic is to place
a ROUND() function calls after every operation. For example,
Amount = (Portion / total_amt) * gross_amt
can be rewritten using money arithmetic as:
Amount = ROUND(ROUND(Portion/total_amt, 4) * gross_amt, 4)
Rounding to four decimal places might not seem an issue, until the
numbers you are using are greater than 10,000.
BEGIN
DECLARE @.gross_amt MONEY,
@.total_amt MONEY,
@.my_part MONEY,
@.money_result MONEY,
@.float_result FLOAT,
@.all_floats FLOAT;
SET @.gross_amt = 55294.72;
SET @.total_amt = 7328.75;
SET @.my_part = 1793.33;
SET @.money_result = (@.my_part / @.total_amt) * @.gross_amt;
SET @.float_result = (@.my_part / @.total_amt) * @.gross_amt;
SET @.Retult3 = (CAST(@.my_part AS FLOAT)
/ CAST( @.total_amt AS FLOAT))
* CAST(FLOAT, @.gross_amtAS FLOAT);
SELECT @.money_result, @.float_result, @.all_floats;
END;
@.money_result = 13525.09 -- incorrect
@.float_result = 13525.0885 -- incorrect
@.all_floats = 13530.5038673171 -- correct, with a -5.42 error|||For divisions, I usually write:
SELECT A / NULLIF(B, 0)
This will yield NULL if B is 0, because Anything / NULL yields NULL.
Regarding the precision of the money data type, you may want to do an
implicit (or an explicit) conversion to the decimal (or the float) data
type, like this:
DECLARE @.gross_amt MONEY,
@.total_amt MONEY,
@.my_part MONEY;
SET @.gross_amt = 55294.72;
SET @.total_amt = 7328.75;
SET @.my_part = 1793.33;
SELECT CAST((1. * @.my_part / @.total_amt) * @.gross_amt AS money)
The correct result would have been 13530.5038673170731707317073170(...)
but converted back to the money datatype it is 13530.5039
Razvan

Monday, March 26, 2012

Push referenced records

Hi,
I am facing the following problem. Please help me to solve it.
My question is: How to force the replication engine to include not only
the updated records but the relevant records as well?
I am using merge replication (SQL2kSP3 with SQL2kCE) with row filtering.
I many cases i have to replicate tables with "many to many"
relationship, where i operate the connection table (BOOKPERSON in the
example bellow). All the rows are filtered by PERSON_ID through out the
database.
Eg.
PERSON records to send:
select * from person where person_id = HOST_NAME()
BOOK records to send:
select * from book inner join bookperson on book.book_id =
bookperson.book_id and bookperson.person_id = HOST_NAME()
BOOKPERSON records to send:
select * from bookperson where person_id = HOST_NAME()
My problem is the following. The newly initialized subsciption
replicates fine, but in case of inserting 1 row to the table BOOKPERSON
(which means associating a book with a person) causes the replication to
fail, because the it replicates only the inserted BOOKPERSON record, and
does not replicate the the relevant BOOK record that is referenced now
by the BOOKPERSON record.
So my question is: How to force the replication engine to include not
only the updated records but the relevant records as well?
Thanks in advance
Pierre
The relevant schema is as follows:
CREATE TABLE Book (
BOOK_ID int not null,
BOOK_TITLE varchar(30) not null,
constraint PK_BOOK primary key clustered (BOOK_ID)
)
CREATE TABLE Person (
PERSON_ID int not null,
PERSON_NAME varchar(30) not null,
constraint PK_PERSON primary key clustered (PERSON_ID)
)
CREATE TABLE BookPerson (
BOOK_ID int not null,
PERSON_ID int not null,
constraint PK_BOOKPERSON primary key clustered (BOOK_ID, PERSON_ID)
)
ALTER TABLE BookPerson
ADD CONSTRAINT FK_BOOKPERSON foreign key (BOOK_ID)
references BOOK (BOOK_ID)
ALTER TABLE BookPerson
ADD CONSTRAINT FK_PERSONBOOK foreign key (PERSON_ID)
references PERSON (PERSON_ID)
Pierre,
presumably the personid value for a bookperson and book are not really
referring to the same entity (a person can be related to an individual book
as eg an author and as a reader), otherwise the related book would already
be replicated? In that case I'd say that the filter clause on the Books
article is incorrect. The simplest way would be to drop this filter and
replicate all the books.
HTH,
Paul Ibison
(recommended sql server 2000 replication book:
http://www.nwsu.com/0974973602p.html)
|||Paul,
I think I forgot to tell that we have thousands of records in the "BOOK"
table, and our subscribers have very limited storage capacity (~1000
thousand book records).
The relation between the two tables (person, book) is not like "authors
of a book" but "books 'thouched' by person", so there is a real M:N
relationship.
Meanwhile found the solution to my problem. I think I have to create
triggers (for insert, delete and update) on the table BookPerson which
will use a stored procedure (sp_mergedummyupdate) to force the
replication engine to put the relevant book records into the publication.
Am I right?
regards
Pierre
|||Pierre,
this sounds correct. Actually I misread your original post and didn't notice
the inner join, so was thinking that the PersonID was a FK directly to the
books table as an author. You'll need to make sure that the FK relationship
is 'Not For Replication', as you can't guarantee the replication order (in
SQL 2005 the default is PK then FK records but not in SQL 2000).
Regards,
Paul Ibison
|||Thank you for the tip, i`ll try it.
pierre
|||Paul,
we have tried to script the references with the "NOT FOR REPLICATION" option
and it caused SQL Server CE to completly break down at replication. After a
quick search on MSDN we found a reported bug saying that SQL Server CE
doesn't support the option mentioned above. (to be specfic SQL Server CE
supports NOT FOR REPLICATION, but there is a bug so we can't use it.)
If I'm not mistaken we have to drop all our references in these
circustances. Am I right? Is there any way to control the order of bulk
inserts at replication. (after PDA downloaded the appropriate snapshot, it
starts to bulk insert & update the rows)
regards
Pierre
"Paul Ibison" wrote:

> Pierre,
> this sounds correct. Actually I misread your original post and didn't notice
> the inner join, so was thinking that the PersonID was a FK directly to the
> books table as an author. You'll need to make sure that the FK relationship
> is 'Not For Replication', as you can't guarantee the replication order (in
> SQL 2005 the default is PK then FK records but not in SQL 2000).
> Regards,
> Paul Ibison
>
>
|||Pierre,
can you post up your reference for this CE bug (I'll put it on my website).
I haven't seen this reference, but if that is the case, you could increase
the -UploadGenerationsPerBatch
and the -DownloadGenerationsPerBatch parameters (to the max of 2000) to
avoid splitting parent and child changes across generation batches.
See http://support.microsoft.com/default...b;EN-US;308266 for more
info.
HTH,
Paul Ibison
(recommended sql server 2000 replication book:
http://www.nwsu.com/0974973602p.html)
|||BUG: NOT FOR REPLICATION Clause Causes SQL Server CE Replication to Fail
http://support.microsoft.com/default...b;en-us;300597
...however SQL Server CE Books Online says that "NOT FOR REPLICATION" is not
supported in case of foreign key constriants...
anyway, thanks for the tip, i'll try that.
pierre
sql