473,598 Members | 2,916 Online
Bytes | Software Development & Data Engineering Community
+ Post

Home Posts Topics Members FAQ

Am I locking more than I need to?

Right now performance isn't a problem, but this question has me curious:

Let's say I have a shopping cart system where there is a "products"
table that contains all possible products, and an "cart_items " table
that stores how many of each product are in each cart.

The obvious (or the first thing that came to my mind) would look
something like this:

create table products (
id serial primary key,
...
);

create table cart_items (
id serial primary key,
cart_id int references ...,
prod_id int references product(id),
quantity int
);

The problem is, when you add the first item to "cart_items " you have to
do an INSERT with a quantity of 1, but after that you need to do
UPDATEs. That would seem to create a potential race condition, so in
order for that to work it would seem you would need to do an ACCESS
EXCLUSIVE lock on the table to make sure no other process was reading
the table at the same time.

Assuming my logic above is correct, there are two other ways I thought
to do it, but both seem considerably more redundant:

(1) I could just get rid of the "quantity" attribute and just insert a
record for each product, then do a view that aggregates the products of
the same prod_id and cart_id with count().

(2) Every time I add a product I could add a record with a quantity of 0
for each cart in existance, and every time I add a cart I could add a
record with a quantity of 0 for each product.

Is there some better solution that I'm missing? It seems like a simple
problem, but right now I'm doing the full table lock to be on the safe
side. Maybe there's some solution involving check constraints?

Regards,
Jeff Davis
---------------------------(end of broadcast)---------------------------
TIP 5: Have you checked our extensive FAQ?

http://www.postgresql.org/docs/faqs/FAQ.html

Nov 23 '05 #1
13 1640
The world rejoiced as jd**********@em pires.org (Jeff Davis) wrote:
The problem is, when you add the first item to "cart_items " you have to
do an INSERT with a quantity of 1, but after that you need to do
UPDATEs. That would seem to create a potential race condition, so in
order for that to work it would seem you would need to do an ACCESS
EXCLUSIVE lock on the table to make sure no other process was reading
the table at the same time.


Various sorts of race conditions are possible in multi-user
multi-tasking systems; what _actual_ problem are you expecting to have
here?

What I would expect is that putting a unique index onto cart_items
based on (cart_id, prod_id) would prevent getting the confusing
situation of having multiple quantities of a single product in a
single cart.

I imagine that is the best thing to try to prevent, and that is
readily done without any "locks" by adding a UNIQUE constraint. But
perhaps I am imagining a different error condition.

Can you describe the nature of the error condition that you are
thinking about? That may help indicate what foreign key checks and/or
uniqueness constraints might be worth adding.
--
let name="cbbrowne" and tld="ntlug.org" in String.concat "@" [name;tld];;
http://cbbrowne.com/info/internet.html
This login session: only $23.95!
Nov 23 '05 #2
On Thursday May 20 2004 8:19, Jeff Davis wrote:

create table products (
id serial primary key,
...
);

create table cart_items (
id serial primary key,
cart_id int references ...,
prod_id int references product(id),
quantity int
);

The problem is, when you add the first item to "cart_items " you have to
do an INSERT with a quantity of 1, but after that you need to do
UPDATEs. That would seem to create a potential race condition, so in
order for that to work it would seem you would need to do an ACCESS
EXCLUSIVE lock on the table to make sure no other process was reading
the table at the same time.


I'm not sure what potential race condition you see since you haven't said
much about how your transactions fit in here. But I would suggest you go
with your first design and don't worry about any explicit locking
unless/until it clearly becomes a problem. I've built numerous things
similar to this, and in my experience, PostgreSQL is very good about
managing the locking in an intelligent manner if your transactions are
reasonably grouped.

HTH.
---------------------------(end of broadcast)---------------------------
TIP 8: explain analyze is your friend

Nov 23 '05 #3
I'm not sure what potential race condition you see since you haven't said
much about how your transactions fit in here. But I would suggest you go
with your first design and don't worry about any explicit locking
unless/until it clearly becomes a problem. I've built numerous things
similar to this, and in my experience, PostgreSQL is very good about
managing the locking in an intelligent manner if your transactions are
reasonably grouped.

HTH.


client1=> BEGIN;
-- test to see if there's already a record there. If so, UPDATE
-- if not, INSERT
client1=> SELECT * from cart_items where cart_id=X AND prod_id=Y;
-- no record, so INSERT
client1=> INSERT into cart_items(cart _id,prod_id,qua ntity)
VALUES(X,Y,1);
client2=> SELECT * from cart_items where cart_id=X AND prod_id=Y;
-- still no record, since client1 didn't commit yet
client1=> COMMIT;
-- now client2 needs to insert
client2=> INSERT into cart_items(cart _id,prod_id,qua ntity)
VALUES(X,Y,1);
client2=> COMMIT;
-- Oops, now there are two records in there.

That's the condition I was worried about.

Thanks,
Jeff Davis

---------------------------(end of broadcast)---------------------------
TIP 7: don't forget to increase your free space map settings

Nov 23 '05 #4
Various sorts of race conditions are possible in multi-user
multi-tasking systems; what _actual_ problem are you expecting to have
here?

I posted the condition as a reply to Ed L., and copied it to the bottom
of this message.
What I would expect is that putting a unique index onto cart_items
based on (cart_id, prod_id) would prevent getting the confusing
situation of having multiple quantities of a single product in a
single cart.


It looks like you knew what I was referring to anyway, and the UNIQUE
constraint looks like another good solution. It would make the second
transaction unable to commit, allowing the application to detect the
error and send an update.

One thing though, it would seem that it would have to be in the
application code, since if I make a user-defined function I couldn't
have a transaction inside it (at least until the 2PC patch makes it into
a release). So, in a user-defined function I couldn't detect the error,
because it would abort the outer transaction, right?

So, it seems a little back-and-forth with the application would be
required if using a unique constraint. It certainly seems like a
performance win for concurrent access though (not that performance is
currently a problem for me).

Thanks,
Jeff Davis

client1=> BEGIN;
-- test to see if there's already a record there. If so, UPDATE
-- if not, INSERT
client1=> SELECT * from cart_items where cart_id=X AND prod_id=Y;
-- no record, so INSERT
client1=> INSERT into cart_items(cart _id,prod_id,qua ntity)
VALUES(X,Y,1);
client2=> SELECT * from cart_items where cart_id=X AND prod_id=Y;
-- still no record, since client1 didn't commit yet
client1=> COMMIT;
-- now client2 needs to insert
client2=> INSERT into cart_items(cart _id,prod_id,qua ntity)
VALUES(X,Y,1);
client2=> COMMIT;
-- Oops, now there are two records in there.


---------------------------(end of broadcast)---------------------------
TIP 1: subscribe and unsubscribe commands go to ma*******@postg resql.org

Nov 23 '05 #5
Clinging to sanity, jd**********@em pires.org (Jeff Davis) mumbled into her beard:
Various sorts of race conditions are possible in multi-user
multi-tasking systems; what _actual_ problem are you expecting to have
here?
I posted the condition as a reply to Ed L., and copied it to the bottom
of this message.


I saw that, yes.
What I would expect is that putting a unique index onto cart_items
based on (cart_id, prod_id) would prevent getting the confusing
situation of having multiple quantities of a single product in a
single cart.


It looks like you knew what I was referring to anyway, and the
UNIQUE constraint looks like another good solution. It would make
the second transaction unable to commit, allowing the application to
detect the error and send an update.


Right.
One thing though, it would seem that it would have to be in the
application code, since if I make a user-defined function I couldn't
have a transaction inside it (at least until the 2PC patch makes it
into a release). So, in a user-defined function I couldn't detect
the error, because it would abort the outer transaction, right?
That seems to be the right understanding. The exception handling does
need to be in the application. And the right response may be, for a
web app, to, at that point, simply stop, pull the "cart" contents as
they are now, and then report back to the user:

- Problem: Attempt to simultaneously request multiple quantities of
Product Foo (Could someone be messing with your cart???)

- Here's what's in your cart right now...
So, it seems a little back-and-forth with the application would be
required if using a unique constraint. It certainly seems like a
performance win for concurrent access though (not that performance
is currently a problem for me).


Well, I'm not sure what the likely alternatives are, without, let's
say, creating a lockable table for each 'cart.' And that would seem
likely to have pretty heavy effects on the application, too.

Whether you "lock" or "detect errors" seems like a "six of one, half a
dozen of the other" to me, and the latter is likely to be WAY more
efficient :-).
--
wm(X,Y):-write(X),write( '@'),write(Y). wm('cbbrowne',' acm.org').
http://www.ntlug.org/~cbbrowne/sap.html
"You can only examine 10 levels of pushdown, because that's all the
fingers you have to stick in the listing."
-- Anonymous programmer - "TOPS-10 Crash Analysis Guide"
Nov 23 '05 #6
On Friday May 21 2004 12:50, Jeff Davis wrote:

client1=> BEGIN;
-- test to see if there's already a record there. If so, UPDATE
-- if not, INSERT
client1=> SELECT * from cart_items where cart_id=X AND prod_id=Y;
-- no record, so INSERT
client1=> INSERT into cart_items(cart _id,prod_id,qua ntity)
VALUES(X,Y,1);
client2=> SELECT * from cart_items where cart_id=X AND prod_id=Y;
-- still no record, since client1 didn't commit yet
client1=> COMMIT;
-- now client2 needs to insert
client2=> INSERT into cart_items(cart _id,prod_id,qua ntity)
VALUES(X,Y,1);
client2=> COMMIT;
-- Oops, now there are two records in there.

That's the condition I was worried about.


Ah, I see. I second Christopher Browne's comments on the unique index (I
assumed you were doing that) and the ease of checking errors in the app.
If you don't have transactions spanning multiple pageviews and you don't
have multiple people modifying the same shopping cart at the same time, it
would seem this is a non-issue. But I guess you could try to explicitly
lock the table. I've never done it that way, instead preferring like C.B.
to enforce integrity at the schema level with the unique index and having
the app handle return values, errors, etc. (In DBI, you need to set a flag
to have it allow you to handle the error vs. aborting. RaiseError,
maybe?). Maybe its wise to systematically handle all DB errors, but I
suspect you'll never see this one occur.
---------------------------(end of broadcast)---------------------------
TIP 8: explain analyze is your friend

Nov 23 '05 #7
pg***@bluepolka .net ("Ed L.") writes:
On Friday May 21 2004 12:50, Jeff Davis wrote:

client1=> BEGIN;
-- test to see if there's already a record there. If so, UPDATE
-- if not, INSERT
client1=> SELECT * from cart_items where cart_id=X AND prod_id=Y;
-- no record, so INSERT
client1=> INSERT into cart_items(cart _id,prod_id,qua ntity)
VALUES(X,Y,1);
client2=> SELECT * from cart_items where cart_id=X AND prod_id=Y;
-- still no record, since client1 didn't commit yet
client1=> COMMIT;
-- now client2 needs to insert
client2=> INSERT into cart_items(cart _id,prod_id,qua ntity)
VALUES(X,Y,1);
client2=> COMMIT;
-- Oops, now there are two records in there.

That's the condition I was worried about.


Ah, I see. I second Christopher Browne's comments on the unique
index (I assumed you were doing that) and the ease of checking
errors in the app. If you don't have transactions spanning multiple
pageviews and you don't have multiple people modifying the same
shopping cart at the same time, it would seem this is a non-issue.
But I guess you could try to explicitly lock the table. I've never
done it that way, instead preferring like C.B. to enforce integrity
at the schema level with the unique index and having the app handle
return values, errors, etc. (In DBI, you need to set a flag to have
it allow you to handle the error vs. aborting. RaiseError, maybe?).
Maybe its wise to systematically handle all DB errors, but I suspect
you'll never see this one occur.


I think it's just wishful thinking to hope that there's anything that
is _fundamentally_ a lot better than having the UNIQUE index, and
recovering from the "not unique" errors that may arise.

- If you lock the table, then that adds some _new_ error conditions
that can occur, namely that an update may get blocked because the
table is locked. Coping with that requires some application code.

- The other approach one might _imagine_ would be useful would be to
run queries that are always checking to see if the inserts look like
they'll be unique. Unfortunately, that CAN'T work, because multiple
connections involve multiple transaction contexts.

I can think of three other approaches:

1. You create a temp table for each cart, and somehow tie the cart to
a single persistent connection. It is _impossible_ for another
connection to interfere, because other connections can't even
see the cart.

If you can associate a process with each cart, and can accept the
overheads of having a DB connection for each cart that is in
progress, this ought to be pretty slick. Cart tables pass in and
out of existence, cleaning themselves up as needed. Quite cool.
But you can't ever use connection pooling, which may be
unacceptable...

2. You don't insert directly into the cart/product table; you insert
into a "product request" table, that is a queue of requests.
There's a big literature on this; look up "Message Queueing,"
and perhaps look at IBM's product MQSeries. (Microsoft made a
clone called "MSMQ.")

A single serial process periodically goes through that queue, and
cleanly moves the data in the queue into the cart/product table.

That means there's some asynchronicity; data may stay in the queue
for a while, which may be a problem. Furthermore, there is an
efficiency loss because every insert has to be done twice; once
into the queue, and then once into the "real" table.

3. Look up the notion of "Opportunis tic locking."

This is pretty big these days in Java and Smalltalk applications;
I won't bother explaining it Yet Again. If your application is
getting hammered because big long expensive transactions doing
lots of updates are failing at COMMIT point due to uniqueness
constraints, OL can cut the cost.

All these approaches have a big impact on application design. And I
don't see them being _fundamentally_ better than just using the UNIQUE
index.
--
let name="cbbrowne" and tld="acm.org" in String.concat "@" [name;tld];;
http://cbbrowne.com/info/advocacy.html
"Ahhh. A man with a sharp wit. Someone ought to take it away from him
before he cuts himself." -- Peter da Silva
Nov 23 '05 #8
That seems to be the right understanding. The exception handling does
need to be in the application. And the right response may be, for a
web app, to, at that point, simply stop, pull the "cart" contents as
they are now, and then report back to the user:

- Problem: Attempt to simultaneously request multiple quantities of
Product Foo (Could someone be messing with your cart???)

- Here's what's in your cart right now...

Interesting. I suppose in my application it probably is a good idea to
give an error, seeing as one physical person can't do anything quickly
enough to violate the UNIQUE.

What if postgres were to have nested transactions (I misstated above as
2PC for some reason)? Would it be desirable to do the checking on the
server side in a function (attempt to insert, and if we get a unique
constraint violation we update) rather than the application?
Well, I'm not sure what the likely alternatives are, without, let's
say, creating a lockable table for each 'cart.' And that would seem
likely to have pretty heavy effects on the application, too.

Whether you "lock" or "detect errors" seems like a "six of one, half a
dozen of the other" to me, and the latter is likely to be WAY more
efficient :-).


One thing that I didn't think of before is this: if I have a table of
all the carts, then could I do a "SELECT ... WHERE cart_id=X FOR UPDATE"
right before I did the test to see whether I should insert or update?
That would basically be a row lock on just the cart I'm modifying,
preventing other concurrent accesses (assuming that they are also trying
to "SELECT ... FOR UPDATE") from locking the same cart, right? But it
would allow other carts to be modified without waiting. Is this a viable
solution?

Thanks,
Jeff

---------------------------(end of broadcast)---------------------------
TIP 3: if posting/reading through Usenet, please send an appropriate
subscribe-nomail command to ma*******@postg resql.org so that your
message can get through to the mailing list cleanly

Nov 23 '05 #9
Don't you still have the possibility for a race-condition?

Scenario:

SELECT ... WHERE cart_id=X FOR UPDATE

IF (NOT FOUND) THEN
BEGIN
--Here is where nothing is locked.
--No way to guarantee no one else will create a record before we do.
INSERT ...
END;
END IF;

Once client commits - assuming UNIQUE is enforced - one of the INSERT
transactions with fail. Again, have to be handled client-side.

<|};-)>

-----Original Message-----
From: pg************* ****@postgresql .org
[mailto:pg****** ***********@pos tgresql.org] On Behalf Of Jeff Davis
Sent: Friday, May 21, 2004 1:25 PM
To: Christopher Browne
Cc: PostgreSQL General
Subject: Re: [GENERAL] Am I locking more than I need to?
That seems to be the right understanding. The exception handling does
need to be in the application. And the right response may be, for a
web app, to, at that point, simply stop, pull the "cart" contents as
they are now, and then report back to the user:

- Problem: Attempt to simultaneously request multiple quantities of
Product Foo (Could someone be messing with your cart???)

- Here's what's in your cart right now...

Interesting. I suppose in my application it probably is a good idea to give
an error, seeing as one physical person can't do anything quickly enough to
violate the UNIQUE.

What if postgres were to have nested transactions (I misstated above as 2PC
for some reason)? Would it be desirable to do the checking on the server
side in a function (attempt to insert, and if we get a unique constraint
violation we update) rather than the application?
Well, I'm not sure what the likely alternatives are, without, let's
say, creating a lockable table for each 'cart.' And that would seem
likely to have pretty heavy effects on the application, too.

Whether you "lock" or "detect errors" seems like a "six of one, half a
dozen of the other" to me, and the latter is likely to be WAY more
efficient :-).


One thing that I didn't think of before is this: if I have a table of all
the carts, then could I do a "SELECT ... WHERE cart_id=X FOR UPDATE" right
before I did the test to see whether I should insert or update? That would
basically be a row lock on just the cart I'm modifying, preventing other
concurrent accesses (assuming that they are also trying to "SELECT ... FOR
UPDATE") from locking the same cart, right? But it would allow other carts
to be modified without waiting. Is this a viable solution?

Thanks,
Jeff

---------------------------(end of broadcast)---------------------------
TIP 3: if posting/reading through Usenet, please send an appropriate
subscribe-nomail command to ma*******@postg resql.org so that your
message can get through to the mailing list cleanly

---------------------------(end of broadcast)---------------------------
TIP 6: Have you searched our list archives?

http://archives.postgresql.org

Nov 23 '05 #10

This thread has been closed and replies have been disabled. Please start a new discussion.

Similar topics

4
4513
by: Michael Chermside | last post by:
Ype writes: > For the namespaces in Jython this 'Python internal thread safety' > is handled by the Java class: > > http://www.jython.org/docs/javadoc/org/python/core/PyStringMap.html > > which has almost all of it public methods Java synchronized: > > http://cvs.sourceforge.net/viewcvs.py/jython/jython/org/python/core/PyStringMap.
4
3493
by: Sam | last post by:
Hello everyone, I have around 20 reports in an ASP web-application which connects to a SQL Server 2000 dB, executes stored procedures based on input parameters and returns the data in a nice tabular format. The data which is used in these reports actually originates from a 3rd party accounting application called Exchequer. I have written a VB application (I call it the extractor) which extracts data from Exchequer and dumps the same...
2
1336
by: spammy | last post by:
hi all, im trying to establish whether i have a race condition or critical section in the following. i have a dataaccess class that continually retireves a table from a sqlserver (which may be slow). it provides a cached copy of this table to clients, for them to read from (note: there are no writes). the idea being if a particular table retrival takes time, the class would still provide a cached copy. now the code:
2
3134
by: Peter | last post by:
(I've tried this Q in the ms forums without response. See how it goes here.) Using A2003 but I guess this is not version-specific... If I want to implement row/record level locking on a split database where do I need to set this option. Is it sufficient to set it in the frontend database for record-level locking to apply to the backend tables. Or must I also set it in the backend mdb database instead or as well (which is never...
11
1555
by: WXS | last post by:
Using lock(this) has been much maligned since someone external to your object can lock causing possible deadlock and forcing you to now create an extra object lock_=new object(); in any classes using locking with nothing better to lock on. How about supporting a protected property on System.Object as SyncObj (so it is really an internal locking object rather than this object) or something like that that can be locked on. Perhaps the C#...
10
2963
by: Ami | last post by:
Hello everyone, I have developed a small access application and now I need to lock my forms against unwanted edits. I have used the code by Allen Browne I found here http://allenbrowne.com/ser-56.html and it works great, but I need to unlock and lock my main form and subform separately, using two different buttons.
7
2849
by: Shak | last post by:
Hi all, I'm trying to write a thread-safe async method to send a message of the form (type)(contents). My model is as follows: private void SendMessage(int type, string message) { //lets send the messagetype via async NetworkStream ns = client.GetStream(); //assume client globally accessible
9
2640
by: master | last post by:
Actually, it is not only the record locking, what I need, and nobody seems to descibe this. Imagine the following scenario. There is a database with, say 10000 records with some unvalidated data. And there is an Intranet ASP.NET application which is an interface to the database in question... and there are 100 pretty girls eager to... uhmm... use the application and validate the data of course ;-). The task is to enable the data...
0
4094
by: Cindy Huyser | last post by:
I have an Access 2000 database behind a threaded Java application that that can have have concurrent access to the same table (but not the same record). The database is set up for shared access with "No locks" as the default, so that optimistic record locking should take place, and "Open databases using record-level locking" is selected. Each Java thread gets a new connection to the database, then updates its record. One of my users has...
0
8392
jinu1996
by: jinu1996 | last post by:
In today's digital age, having a compelling online presence is paramount for businesses aiming to thrive in a competitive landscape. At the heart of this digital strategy lies an intricately woven tapestry of website design and digital marketing. It's not merely about having a website; it's about crafting an immersive digital experience that captivates audiences and drives business growth. The Art of Business Website Design Your website is...
1
8046
by: Hystou | last post by:
Overview: Windows 11 and 10 have less user interface control over operating system update behaviour than previous versions of Windows. In Windows 11 and 10, there is no way to turn off the Windows Update option using the Control Panel or Settings app; it automatically checks for updates and installs any it finds, whether you like it or not. For most users, this new feature is actually very convenient. If you want to control the update process,...
0
8262
tracyyun
by: tracyyun | last post by:
Dear forum friends, With the development of smart home technology, a variety of wireless communication protocols have appeared on the market, such as Zigbee, Z-Wave, Wi-Fi, Bluetooth, etc. Each protocol has its own unique characteristics and advantages, but as a user who is planning to build a smart home system, I am a bit confused by the choice of these technologies. I'm particularly interested in Zigbee because I've heard it does some...
0
6711
agi2029
by: agi2029 | last post by:
Let's talk about the concept of autonomous AI software engineers and no-code agents. These AIs are designed to manage the entire lifecycle of a software development project—planning, coding, testing, and deployment—without human intervention. Imagine an AI that can take a project description, break it down, write the code, debug it, and then launch it, all on its own.... Now, this would greatly impact the work of software developers. The idea...
1
5847
isladogs
by: isladogs | last post by:
The next Access Europe User Group meeting will be on Wednesday 1 May 2024 starting at 18:00 UK time (6PM UTC+1) and finishing by 19:30 (7.30PM). In this session, we are pleased to welcome a new presenter, Adolph Dupré who will be discussing some powerful techniques for using class modules. He will explain when you may want to use classes instead of User Defined Types (UDT). For example, to manage the data in unbound forms. Adolph will...
0
3894
by: TSSRALBI | last post by:
Hello I'm a network technician in training and I need your help. I am currently learning how to create and manage the different types of VPNs and I have a question about LAN-to-LAN VPNs. The last exercise I practiced was to create a LAN-to-LAN VPN between two Pfsense firewalls, by using IPSEC protocols. I succeeded, with both firewalls in the same network. But I'm wondering if it's possible to do the same thing, with 2 Pfsense firewalls...
1
2410
by: 6302768590 | last post by:
Hai team i want code for transfer the data from one system to another through IP address by using C# our system has to for every 5mins then we have to update the data what the data is updated we have to send another system
1
1500
muto222
by: muto222 | last post by:
How can i add a mobile payment intergratation into php mysql website.
0
1245
bsmnconsultancy
by: bsmnconsultancy | last post by:
In today's digital era, a well-designed website is crucial for businesses looking to succeed. Whether you're a small business owner or a large corporation in Toronto, having a strong online presence can significantly impact your brand's success. BSMN Consultancy, a leader in Website Development in Toronto offers valuable insights into creating effective websites that not only look great but also perform exceptionally well. In this comprehensive...

By using Bytes.com and it's services, you agree to our Privacy Policy and Terms of Use.

To disable or enable advertisements and analytics tracking please visit the manage ads & tracking page.