Subject | Re: [IBO] Table locking with Delphi/IBObjects |
---|---|
Author | olgreyfox15 |
Post date | 2008-10-07T16:07:51Z |
--- In IBObjects@yahoogroups.com, Helen Borrie <helebor@...> wrote:
Firebird or Interbase, although with Firebird and also with IBO you
*can* do updates or deletes with one or another form of pessimistic
locking.
transaction isolation is to make each transaction see the database
state consistently from beginning to end. Thus, there is NO WAY you
can see (and thus check) what is happening in other transactions
during the course of your transaction. (This capability is known as
Dirty Read and is a total no-no for ACID-compliant database systems!)
ability to react to the results of transactions that were committed
after your transaction began.
IBO).
another transaction has a more recent version pending (pending =
changes posted but not yet committed or rolled back).
WAIT (IBO property LockWait).
rolls back and will then try to commit.
practice, WAIT is fairly pointless in Concurrency isolation since the
conflict can't be resolved unless the competing transaction rolls back.
been posted by another transaction, your transaction will write your
changes to disk in a new record version that only your transaction and
the Fb engine can see. As soon as that happens, the record is locked
and no other transaction can change that record - it will be yours
until you commit it or roll it back. In the Delphi environment, this
part of the operation is referred to as Post. If you can't get that
lock, because another transaction has a change pending to that record
(has "posted" a new version) , then your transaction won't be allowed
to post.
The effect of this is that, as soon as you call the Edit or Delete
method of the dataset, IBO will attempt to post a dummy update to that
record and thus either secure a lock or get a conflict exception. It
handles the conflict exception by preventing the dataset from going
into Edit or Delete mode.
will either succeed if there is no conflict or fail if there is either
a conflict *or* any another transaction that is in ReadCommitted
isolation. For this reason (and other reasons, natch!!) you
shouldn't, for example, run reports in ReadCommitted isolation because
that will prevent anyone from inserting until the report's transaction
is finished.
transactions as ReadCommitted, RecVersion(false) LockWait(True) and
MAKE SURE YOU DON'T ALLOW TRANSACTIONS TO GO LONG!! However, if you
want User B to make a fresh try once the conflict situation has gone
then use LockWait(False) and put a retry loop on the Post operation.
I favour the second option because users will do what users will do,
like going off to lunch leaving locks everywhere.
thorough job of explaining my problem. The scenario is two or more
users simultaneously entering item bids at a charity/benefit auction
(not an online auction). Normally, before posting the entry, the
application (in an "IsSold" function) checks AT_STATEMENT to see if
the given item is already in the table. In single user mode, or if
people are doing their entries with some time between, this is no
problem. But in the boundary case in which two users hit "Enter"
simultaneously, it has been possible to enter the same item twice.
This is what I'm trying to prevent. So, in this boundary case (which
admittedly should be rare but is still possible, given Murphy's Law),
if User A and User B both input the same item and then simultaneously
hit "Enter", someone (say User A) gets the Insert/ExecSQL first and
posts a record. User B's entry, if it doesn't (somehow) wait for User
A's transaction to complete and then do an "IsSold" query, can
blithely enter a second record, effectively "selling" the same item
twice.
So (if I understand your answer) I don't think what I want to do is a
"Dirty Read" ... I want to do a read after User A's entry completes &
posts/commits.
My transaction is set to Isolation=tiCommitted, RecVersion=True,
LockWait=False.
By trial-and-error, I've learned the following:
1) Since normal transaction processes only block users from a given
row (or rows), it doesn't prevent users from changing or inserting two
different rows (usually a good thing).
2) Because User A & B's simultaneous entry results in two different
rows, I gather I need some sort of "table lock" to prevent this.
3) I got from one entry in the IBObjects help that I could accomplish
this with a query "Select <field> from AT_STATEMENTS" tied to this
transaction. (I call it qLkATStmt). So, if I understand correctly, if
this query is Open, it will have the effect of a table lock; and if it
is Closed, and the transaction committed or rolled back, that the
table lock is off and we revert to strictly record locking.
4) Since I wrote my original question, by trial and error I've learned
that whatever lock-conflict problems I've been having stemmed from
failure to "clean up after myself" (i.e., force closure on qLkATStmt
and do something to refresh the transaction).
Am I on the right track, or is there a better way?
>for the API changes in Fb 2.x.
> At 09:47 7/10/2008, Lane Campbell wrote:
> >I,m working on an application that uses Delphi7, IBObjects 4.6, and
> >Firebird 2.1.
>
> You need IBO 4.8 for Fb 2.1 because IBO 4.6 doesn't have any support
>you can set for transactions. You don't do explicit locking with
> >I can have multiple users simultaneously inserting
> >records to a table (AT_STATEMENT) from different PCs on the network. I
> >need to temporarily prevent User B from inserting until User A's
> >insert is completed & committed. Then User B's application needs to
> >check the data state before doing its insert. In absence of better
> >knowledge, I'm assuming this needs a table lock on AT_STATEMENT. If
> >so, what is the best way to do this? The way I'm doing it works only
> >in one specific instance, and seems to deadlock (or lock conflict) any
> >subsequent inserts/deletes from other places in the application (other
> >forms, other queries, etc.)
> >
> >I know Delphi fairly well; but transaction processing is relatively
> >new to me.
>
> Frankly, you really need to understand the various attributes that
Firebird or Interbase, although with Firebird and also with IBO you
*can* do updates or deletes with one or another form of pessimistic
locking.
>to check the data state before doing its insert". The whole point of
> I don't understand what you mean by "then User B's application needs
transaction isolation is to make each transaction see the database
state consistently from beginning to end. Thus, there is NO WAY you
can see (and thus check) what is happening in other transactions
during the course of your transaction. (This capability is known as
Dirty Read and is a total no-no for ACID-compliant database systems!)
>Fb gets to Dirty Read. What this gives your transaction is the
> What you do have is Read Committed isolation, which is as close as
ability to react to the results of transactions that were committed
after your transaction began.
>Record Version and No Record Version (see the RecVersion property of
> With Read Committed, you have two conflict resolution options:
IBO).
> -- When RecVersion is true (sets the RECORD_VERSION attribute) thetransaction see the latest committed version of the record, even if
another transaction has a more recent version pending (pending =
changes posted but not yet committed or rolled back).
> -- With RecVersion false (sets the NO_RECORD_VERSION attribute) theeffect depends on whether your lock resolution setting is WAIT or NO
WAIT (IBO property LockWait).
> ---- If LockWait is true, your ReadCommitted RecVersion(false)transaction will wait until the conflicting transaction commits or
rolls back and will then try to commit.
> ---- If LockWait is false then your transaction will exceptimmediately and report the conflict.
>However, you can set a Concurrency transaction to wait or not. In
> RecVersion has no effect on Snapshot (Concurrency) transactions.
practice, WAIT is fairly pointless in Concurrency isolation since the
conflict can't be resolved unless the competing transaction rolls back.
>long as there is no conflict with an operation on a record that has
> Do you understand the two-step post-commit sequence of Firebird? As
been posted by another transaction, your transaction will write your
changes to disk in a new record version that only your transaction and
the Fb engine can see. As soon as that happens, the record is locked
and no other transaction can change that record - it will be yours
until you commit it or roll it back. In the Delphi environment, this
part of the operation is referred to as Post. If you can't get that
lock, because another transaction has a change pending to that record
(has "posted" a new version) , then your transaction won't be allowed
to post.
>PessimisticLocking attribute of the *dataset* (your query) to True.
> IBO offers a way to deal with this situation: you can set the
The effect of this is that, as soon as you call the Edit or Delete
method of the dataset, IBO will attempt to post a dummy update to that
record and thus either secure a lock or get a conflict exception. It
handles the conflict exception by preventing the dataset from going
into Edit or Delete mode.
>the optimistic locking design of Fb/IB, all inserts can be posted and
> PessimisticLocking has no effect on Insert mode though. Because of
will either succeed if there is no conflict or fail if there is either
a conflict *or* any another transaction that is in ReadCommitted
isolation. For this reason (and other reasons, natch!!) you
shouldn't, for example, run reports in ReadCommitted isolation because
that will prevent anyone from inserting until the report's transaction
is finished.
>virtually no situation where a table lock makes sense.
> There are transaction settings that allow table locks but there is
>proceed with its intentions once User A has finished, then set up both
> You wrote:
> > I need to temporarily prevent User B from inserting until User A's
> >insert is completed & committed.
>
> It depends on your objective. If you are happy for User B to
transactions as ReadCommitted, RecVersion(false) LockWait(True) and
MAKE SURE YOU DON'T ALLOW TRANSACTIONS TO GO LONG!! However, if you
want User B to make a fresh try once the conflict situation has gone
then use LockWait(False) and put a retry loop on the Post operation.
I favour the second option because users will do what users will do,
like going off to lunch leaving locks everywhere.
>You had questions as to my intent, and I admit, I didn't do the most
> Helen
>
thorough job of explaining my problem. The scenario is two or more
users simultaneously entering item bids at a charity/benefit auction
(not an online auction). Normally, before posting the entry, the
application (in an "IsSold" function) checks AT_STATEMENT to see if
the given item is already in the table. In single user mode, or if
people are doing their entries with some time between, this is no
problem. But in the boundary case in which two users hit "Enter"
simultaneously, it has been possible to enter the same item twice.
This is what I'm trying to prevent. So, in this boundary case (which
admittedly should be rare but is still possible, given Murphy's Law),
if User A and User B both input the same item and then simultaneously
hit "Enter", someone (say User A) gets the Insert/ExecSQL first and
posts a record. User B's entry, if it doesn't (somehow) wait for User
A's transaction to complete and then do an "IsSold" query, can
blithely enter a second record, effectively "selling" the same item
twice.
So (if I understand your answer) I don't think what I want to do is a
"Dirty Read" ... I want to do a read after User A's entry completes &
posts/commits.
My transaction is set to Isolation=tiCommitted, RecVersion=True,
LockWait=False.
By trial-and-error, I've learned the following:
1) Since normal transaction processes only block users from a given
row (or rows), it doesn't prevent users from changing or inserting two
different rows (usually a good thing).
2) Because User A & B's simultaneous entry results in two different
rows, I gather I need some sort of "table lock" to prevent this.
3) I got from one entry in the IBObjects help that I could accomplish
this with a query "Select <field> from AT_STATEMENTS" tied to this
transaction. (I call it qLkATStmt). So, if I understand correctly, if
this query is Open, it will have the effect of a table lock; and if it
is Closed, and the transaction committed or rolled back, that the
table lock is off and we revert to strictly record locking.
4) Since I wrote my original question, by trial and error I've learned
that whatever lock-conflict problems I've been having stemmed from
failure to "clean up after myself" (i.e., force closure on qLkATStmt
and do something to refresh the transaction).
Am I on the right track, or is there a better way?