On Thu, 2005-07-07 at 12:56 +0200, Gerhard Haering wrote:
> - is it true that after sqlite3_prepare, only the first call to
>   sqlite3_step can return SQLITE_BUSY, or can subsequent calls also
>   return SQLITE_BUSY?
> 

Yes.  I think this is true.  At least I cannot think of any
path by which you could get SQLITE_BUSY on 2nd or subsequent
calls to sqlite3_step().  All the locks required are obtained
on the very first call.

Note however that a call to sqlite3_exec() is really divided up
into multiple calls to sqlite3_prepare() and sqlite3_step(), one
each for each complete SQL statement in the argument to
sqlite3_exec.  So one statement could run to completion then a
second statement could hit a lock.  But that cannot happen when 
using sqlite3_prepare() directly because sqlite3_prepare() will
only compile one statement at a time.

> - like described in the wiki page, is it true that if sqlite3_step
>   returns SQLITE_BUSY, I need to sqlite3_reset or sqlite3_finalize to
>   avoid potential deadlocks? If that's true, I will need to
>   sqlite3_reset, then rebind the parameters again and try sqlite3_step
>   again, right?

This is *not* correct.  In fact, I do not see how calling 
sqlite3_finalize or sqlite3_reset is even helpful in resolving
the lock issue.  To avoid the deadlock, some thread needs to prepare
and run a COMMIT or ROLLBACK.  Resetting or finalizing other 
statements will not help.

A typical scenario follows.  I use the term "thread" to mean either
a traditional thread or a full blown process - both work the same
way.

  1.  Threads A and B both execute "BEGIN".  This acquires no
      locks or otherwise touches the database in any way.  It just
      turns off auto-commit mode in both threads.

  2.  Threads A and B both execute "SELECT...".  This acquires a
      shared lock for both threads.  This works fine.

  3.  Threads A and B both execute "UPDATE...".  One thread (let's
      say thread A) succeeds, the other gets an SQLITE_BUSY reply.
      The first thread gets a reserved lock before doing the update.
      a reserved lock is a kind of exclusive lock so the other
      thread blocks and has to fail.  Thread A was successful at
      the UPDATE command, but all the changes are still held in
      RAM - nothing can be written to disk until thread B releases
      its shared lock.  If the UPDATE is large so that the changes
      will not all fit in SQLite's page cache, then both threads
      will get an SQLITE_BUSY return.

  4.  Thread A tries to COMMIT.  It gets an SQLITE_BUSY return 
      because thread B is still holding a shared lock on the
      database.

At this point we are stuck.  Either thread A will need to
ROLLBACK to let thread B proceed.  Or thread B will have to either
ROLLBACK or COMMIT (the same thing in this case since thread B has
made not changes) to let thread A proceed.  One or the other of
the two threads will need to prepare and step a ROLLBACK.  Just
resetting or finalizing previous statements and retrying them
will not help.

One way to avoid this scenario is to always use BEGIN EXCLUSIVE
instead of just BEGIN.  BEGIN EXCLUSIVE always goes ahead and
gets an exclusive lock on the database file or returns SQLITE_BUSY
if it cannot.  That way, you can never get two threads holding
a shared lock and both trying to promote to an exclusive lock.
This avoids the deadlock scenario described above, but it also
reduces the amount of concurrency.  So it is a tradeoff.

-- 
D. Richard Hipp <[EMAIL PROTECTED]>

Reply via email to