[sqlite] Questions about VDBE, deadlocks and SQLITE_BUSY

2005-07-07 Thread Gerhard Haering
Some pysqlite users are experiencing problems with locks not going away,
as described in http://www.third-bit.com/trac/argon/wiki/Locking

In order to fix this, I need some information:

- 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?

- 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?

How did others avoid the locking problems described above? Other people
implementing SQLite wrappers must have similar issues, I suppose ...

Cheers,

-- Gerhard
-- 
Gerhard Häring - [EMAIL PROTECTED] - Python, web  database development


signature.asc
Description: Digital signature


Re: [sqlite] Questions about VDBE, deadlocks and SQLITE_BUSY

2005-07-07 Thread D. Richard Hipp
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]