I have applied a modified version of this patch, attached.   I trimmed
down the description of log_lock_waits to be more concise, and moved the
idea of using this to tune deadlock_timeout to the deadlock_timeout
section of the manual.

---------------------------------------------------------------------------

Simon Riggs wrote:
> On Mon, 2007-02-19 at 19:38 +0000, Simon Riggs wrote:
> > On Tue, 2007-02-13 at 22:19 -0500, Bruce Momjian wrote:
> > > Simon Riggs wrote:
> > > > On Thu, 2006-10-26 at 18:45 -0400, Tom Lane wrote:
> > > > > Chris Campbell <[EMAIL PROTECTED]> writes:
> > > > > > Is there additional logging information I can turn on to get more  
> > > > > > details? I guess I need to see exactly what locks both processes  
> > > > > > hold, and what queries they were running when the deadlock 
> > > > > > occurred?  
> > > > > > Is that easily done, without turning on logging for *all* 
> > > > > > statements?
> > > > > 
> > > > > log_min_error_statement = error would at least get you the statements
> > > > > reporting the deadlocks, though not what they're conflicting against.
> > > > 
> > > > Yeh, we need a much better locking logger for performance analysis.
> > > > 
> > > > We really need to dump the whole wait-for graph for deadlocks, since
> > > > this might be more complex than just two statements involved. Deadlocks
> > > > ought to be so infrequent that we can afford the log space to do this -
> > > > plus if we did this it would likely lead to fewer deadlocks.
> > > > 
> > > > For 8.3 I'd like to have a log_min_duration_lockwait (secs) parameter
> > > > that would allow you to dump the wait-for graph for any data-level locks
> > > > that wait too long, rather than just those that deadlock. Many
> > > > applications experience heavy locking because of lack of holistic
> > > > design. That will also show up the need for other utilities to act
> > > > CONCURRENTLY, if possible.
> > > 
> > > Old email, but I don't see how our current output is not good enough?
> > > 
> > >   test=> lock a;
> > >   ERROR:  deadlock detected
> > >   DETAIL:  Process 6855 waits for AccessExclusiveLock on relation 16394 of
> > >   database 16384; blocked by process 6795.
> > >   Process 6795 waits for AccessExclusiveLock on relation 16396 of database
> > >   16384; blocked by process 6855.
> > 
> > This detects deadlocks, but it doesn't detect lock waits. 
> > 
> > When I wrote that it was previous experience driving me. Recent client
> > experience has highlighted the clear need for this. We had a lock wait
> > of 50 hours because of an RI check; thats the kind of thing I'd like to
> > show up in the logs somewhere.
> > 
> > Lock wait detection can be used to show up synchronisation points that
> > have been inadvertently designed into an application, so its a useful
> > tool in investigating performance issues.
> > 
> > I have a patch implementing the logging as agreed with Tom, will post to
> > patches later tonight.
> 
> Patch for discussion, includes doc entries at top of patch, so its
> fairly clear how it works.
> 
> Output is an INFO message, to allow this to trigger
> log_min_error_statement when it generates a message, to allow us to see
> the SQL statement that is waiting. This allows it to generate a message
> prior to the statement completing, which is important because it may not
> ever complete, in some cases, so simply logging a list of pids won't
> always tell you what the SQL was that was waiting.
> 
> Other approaches are possible...
> 
> Comments?
> 
> -- 
>   Simon Riggs             
>   EnterpriseDB   http://www.enterprisedb.com
> 

[ Attachment, skipping... ]

-- 
  Bruce Momjian  <[EMAIL PROTECTED]>          http://momjian.us
  EnterpriseDB                               http://www.enterprisedb.com

  + If your life is a hard drive, Christ can be your backup. +
Index: doc/src/sgml/config.sgml
===================================================================
RCS file: /cvsroot/pgsql/doc/src/sgml/config.sgml,v
retrieving revision 1.113
diff -c -c -r1.113 config.sgml
*** doc/src/sgml/config.sgml	2 Mar 2007 23:37:22 -0000	1.113
--- doc/src/sgml/config.sgml	3 Mar 2007 18:41:13 -0000
***************
*** 2946,2951 ****
--- 2946,2966 ----
        </listitem>
       </varlistentry>
  
+      <varlistentry id="guc-log-lock-waits" xreflabel="log_lock_waits">
+       <term><varname>log_lock_waits</varname> (<type>boolean</type>)</term>
+       <indexterm>
+        <primary><varname>log_lock_waits</> configuration parameter</primary>
+       </indexterm>
+       <listitem>
+        <para>
+         Controls whether a log message is produced when a statement waits
+         longer than <xref linkend="guc-deadlock-timeout"> to acquire a
+         lock.  This is useful in determining if lock waits are causing
+         poor performance.  The default is <literal>off</>.
+        </para>
+       </listitem>
+      </varlistentry>
+ 
       <varlistentry id="guc-log-temp-files" xreflabel="log_temp_files">
        <term><varname>log_temp_files</varname> (<type>integer</type>)</term>
        <indexterm>
***************
*** 3980,3996 ****
          This is the amount of time, in milliseconds, to wait on a lock
          before checking to see if there is a deadlock condition. The
          check for deadlock is relatively slow, so the server doesn't run
!         it every time it waits for a lock. We (optimistically?) assume
          that deadlocks are not common in production applications and
          just wait on the lock for a while before starting the check for a
          deadlock. Increasing this value reduces the amount of time
          wasted in needless deadlock checks, but slows down reporting of
          real deadlock errors. The default is one second (<literal>1s</>),
          which is probably about the smallest value you would want in
!         practice. On a heavily loaded server you might want to raise it.
          Ideally the setting should exceed your typical transaction time,
!         so as to improve the odds that a lock will be released before
!         the waiter decides to check for deadlock.
         </para>
        </listitem>
       </varlistentry>
--- 3995,4012 ----
          This is the amount of time, in milliseconds, to wait on a lock
          before checking to see if there is a deadlock condition. The
          check for deadlock is relatively slow, so the server doesn't run
!         it every time it waits for a lock. We optimistically assume
          that deadlocks are not common in production applications and
          just wait on the lock for a while before starting the check for a
          deadlock. Increasing this value reduces the amount of time
          wasted in needless deadlock checks, but slows down reporting of
          real deadlock errors. The default is one second (<literal>1s</>),
          which is probably about the smallest value you would want in
!         practice.  Set <xref linkend="guc-log-lock-waits"> to log deadlock
!         checks.  On a heavily loaded server you might want to raise it.
          Ideally the setting should exceed your typical transaction time,
!         so as to improve the odds that a lock will be released before the
!         waiter decides to check for deadlock.
         </para>
        </listitem>
       </varlistentry>
Index: src/backend/storage/lmgr/deadlock.c
===================================================================
RCS file: /cvsroot/pgsql/src/backend/storage/lmgr/deadlock.c,v
retrieving revision 1.44
diff -c -c -r1.44 deadlock.c
*** src/backend/storage/lmgr/deadlock.c	5 Jan 2007 22:19:38 -0000	1.44
--- src/backend/storage/lmgr/deadlock.c	3 Mar 2007 18:41:13 -0000
***************
*** 187,199 ****
   * deadlock.  If resolution is impossible, return TRUE --- the caller
   * is then expected to abort the given proc's transaction.
   *
!  * Caller must already have locked all partitions of the lock tables.
   *
   * On failure, deadlock details are recorded in deadlockDetails[] for
   * subsequent printing by DeadLockReport().  That activity is separate
   * because we don't want to do it while holding all those LWLocks.
   */
! bool
  DeadLockCheck(PGPROC *proc)
  {
  	int			i,
--- 187,200 ----
   * deadlock.  If resolution is impossible, return TRUE --- the caller
   * is then expected to abort the given proc's transaction.
   *
!  * Caller must already have locked all partitions of the lock tables,
!  * so standard error logging/reporting code is handled by caller.
   *
   * On failure, deadlock details are recorded in deadlockDetails[] for
   * subsequent printing by DeadLockReport().  That activity is separate
   * because we don't want to do it while holding all those LWLocks.
   */
! DeadlockState
  DeadLockCheck(PGPROC *proc)
  {
  	int			i,
***************
*** 204,209 ****
--- 205,215 ----
  	nPossibleConstraints = 0;
  	nWaitOrders = 0;
  
+ #ifdef LOCK_DEBUG
+ 	if (Debug_deadlocks)
+ 		DumpAllLocks();
+ #endif
+ 
  	/* Search for deadlocks and possible fixes */
  	if (DeadLockCheckRecurse(proc))
  	{
***************
*** 217,223 ****
  		if (!FindLockCycle(proc, possibleConstraints, &nSoftEdges))
  			elog(FATAL, "deadlock seems to have disappeared");
  
! 		return true;			/* cannot find a non-deadlocked state */
  	}
  
  	/* Apply any needed rearrangements of wait queues */
--- 223,229 ----
  		if (!FindLockCycle(proc, possibleConstraints, &nSoftEdges))
  			elog(FATAL, "deadlock seems to have disappeared");
  
! 		return DS_HARD_DEADLOCK;	/* cannot find a non-deadlocked state */
  	}
  
  	/* Apply any needed rearrangements of wait queues */
***************
*** 249,255 ****
  		/* See if any waiters for the lock can be woken up now */
  		ProcLockWakeup(GetLocksMethodTable(lock), lock);
  	}
! 	return false;
  }
  
  /*
--- 255,265 ----
  		/* See if any waiters for the lock can be woken up now */
  		ProcLockWakeup(GetLocksMethodTable(lock), lock);
  	}
! 
! 	if (nWaitOrders > 0)
! 		return DS_SOFT_DEADLOCK;
! 	else
! 		return DS_DEADLOCK_NOT_FOUND;
  }
  
  /*
***************
*** 896,902 ****
  }
  
  /*
!  * Report a detected deadlock, with available details.
   */
  void
  DeadLockReport(void)
--- 906,912 ----
  }
  
  /*
!  * Report a detected DS_HARD_DEADLOCK, with available details.
   */
  void
  DeadLockReport(void)
Index: src/backend/storage/lmgr/proc.c
===================================================================
RCS file: /cvsroot/pgsql/src/backend/storage/lmgr/proc.c,v
retrieving revision 1.184
diff -c -c -r1.184 proc.c
*** src/backend/storage/lmgr/proc.c	15 Feb 2007 23:23:23 -0000	1.184
--- src/backend/storage/lmgr/proc.c	3 Mar 2007 18:41:14 -0000
***************
*** 48,53 ****
--- 48,54 ----
  /* GUC variables */
  int			DeadlockTimeout = 1000;
  int			StatementTimeout = 0;
+ bool		log_lock_waits = false;
  
  /* Pointer to this process's PGPROC struct, if any */
  PGPROC	   *MyProc = NULL;
***************
*** 979,984 ****
--- 980,986 ----
  CheckDeadLock(void)
  {
  	int			i;
+ 	DeadlockState deadlock_state = DS_DEADLOCK_NOT_FOUND;
  
  	/*
  	 * Acquire exclusive lock on the entire shared lock data structures. Must
***************
*** 1004,1063 ****
  	 * This is quicker than checking our semaphore's state, since no kernel
  	 * call is needed, and it is safe because we hold the lock partition lock.
  	 */
! 	if (MyProc->links.prev == INVALID_OFFSET ||
! 		MyProc->links.next == INVALID_OFFSET)
! 		goto check_done;
! 
! #ifdef LOCK_DEBUG
! 	if (Debug_deadlocks)
! 		DumpAllLocks();
! #endif
! 
! 	if (!DeadLockCheck(MyProc))
  	{
! 		/* No deadlock, so keep waiting */
! 		goto check_done;
! 	}
! 
! 	/*
! 	 * Oops.  We have a deadlock.
! 	 *
! 	 * Get this process out of wait state.	(Note: we could do this more
! 	 * efficiently by relying on lockAwaited, but use this coding to preserve
! 	 * the flexibility to kill some other transaction than the one detecting
! 	 * the deadlock.)
! 	 *
! 	 * RemoveFromWaitQueue sets MyProc->waitStatus to STATUS_ERROR, so
! 	 * ProcSleep will report an error after we return from the signal handler.
! 	 */
! 	Assert(MyProc->waitLock != NULL);
! 	RemoveFromWaitQueue(MyProc, LockTagHashCode(&(MyProc->waitLock->tag)));
  
! 	/*
! 	 * Unlock my semaphore so that the interrupted ProcSleep() call can
! 	 * finish.
! 	 */
! 	PGSemaphoreUnlock(&MyProc->sem);
  
! 	/*
! 	 * We're done here.  Transaction abort caused by the error that ProcSleep
! 	 * will raise will cause any other locks we hold to be released, thus
! 	 * allowing other processes to wake up; we don't need to do that here.
! 	 * NOTE: an exception is that releasing locks we hold doesn't consider the
! 	 * possibility of waiters that were blocked behind us on the lock we just
! 	 * failed to get, and might now be wakable because we're not in front of
! 	 * them anymore.  However, RemoveFromWaitQueue took care of waking up any
! 	 * such processes.
! 	 */
  
  	/*
  	 * Release locks acquired at head of routine.  Order is not critical, so
  	 * do it back-to-front to avoid waking another CheckDeadLock instance
  	 * before it can get all the locks.
  	 */
- check_done:
  	for (i = NUM_LOCK_PARTITIONS; --i >= 0;)
  		LWLockRelease(FirstLockMgrLock + i);
  }
  
  
--- 1006,1082 ----
  	 * This is quicker than checking our semaphore's state, since no kernel
  	 * call is needed, and it is safe because we hold the lock partition lock.
  	 */
! 	if (MyProc->links.prev != INVALID_OFFSET &&
! 		MyProc->links.next != INVALID_OFFSET)
! 		deadlock_state = DeadLockCheck(MyProc);
! 	
! 	if (deadlock_state == DS_HARD_DEADLOCK)
  	{
! 		/*
! 		 * Oops.  We have a deadlock.
! 		 *
! 		 * Get this process out of wait state.	(Note: we could do this more
! 		 * efficiently by relying on lockAwaited, but use this coding to preserve
! 		 * the flexibility to kill some other transaction than the one detecting
! 		 * the deadlock.)
! 		 *
! 		 * RemoveFromWaitQueue sets MyProc->waitStatus to STATUS_ERROR, so
! 		 * ProcSleep will report an error after we return from the signal handler.
! 		 */
! 		Assert(MyProc->waitLock != NULL);
! 		RemoveFromWaitQueue(MyProc, LockTagHashCode(&(MyProc->waitLock->tag)));
  
! 		/*
! 		 * Unlock my semaphore so that the interrupted ProcSleep() call can
! 		 * finish.
! 		 */
! 		PGSemaphoreUnlock(&MyProc->sem);
  
! 		/*
! 		 * We're done here.  Transaction abort caused by the error that ProcSleep
! 		 * will raise will cause any other locks we hold to be released, thus
! 		 * allowing other processes to wake up; we don't need to do that here.
! 		 * NOTE: an exception is that releasing locks we hold doesn't consider the
! 		 * possibility of waiters that were blocked behind us on the lock we just
! 		 * failed to get, and might now be wakable because we're not in front of
! 		 * them anymore.  However, RemoveFromWaitQueue took care of waking up any
! 		 * such processes.
! 		 */
! 	}
  
  	/*
  	 * Release locks acquired at head of routine.  Order is not critical, so
  	 * do it back-to-front to avoid waking another CheckDeadLock instance
  	 * before it can get all the locks.
  	 */
  	for (i = NUM_LOCK_PARTITIONS; --i >= 0;)
  		LWLockRelease(FirstLockMgrLock + i);
+ 
+ 	/*
+ 	 * Issue any log messages requested.
+ 	 *
+ 	 * Deadlock ERROR messages are issued as part of transaction abort, so 
+ 	 * these messages should not raise error states intentionally.
+ 	 */
+ 	if (log_lock_waits)
+ 	{
+ 		switch (deadlock_state)
+ 		{
+ 			case DS_SOFT_DEADLOCK:
+ 				ereport(LOG,
+ 					(errmsg("deadlock avoided by rearranging lock order")));
+ 				break;
+ 			case DS_DEADLOCK_NOT_FOUND:
+ 				ereport(LOG,
+ 					(errmsg("statement waiting for lock for at least %d ms",
+ 								DeadlockTimeout)));
+ 				break;
+ 			case DS_HARD_DEADLOCK:
+ 				break;	/* ERROR message handled during abort */
+ 			default:
+ 				break;
+ 		}
+ 	}
  }
  
  
Index: src/backend/utils/misc/guc.c
===================================================================
RCS file: /cvsroot/pgsql/src/backend/utils/misc/guc.c,v
retrieving revision 1.377
diff -c -c -r1.377 guc.c
*** src/backend/utils/misc/guc.c	23 Feb 2007 21:36:18 -0000	1.377
--- src/backend/utils/misc/guc.c	3 Mar 2007 18:41:16 -0000
***************
*** 812,817 ****
--- 812,825 ----
  #endif
  
  	{
+ 		{"log_lock_waits", PGC_SIGHUP, LOGGING_WHAT,
+ 			gettext_noop("Logs long lock wait events."),
+ 			NULL
+ 		},
+ 		&log_lock_waits,
+ 		false, NULL, NULL
+ 	},
+ 	{
  		{"log_hostname", PGC_SIGHUP, LOGGING_WHAT,
  			gettext_noop("Logs the host name in the connection logs."),
  			gettext_noop("By default, connection logs only show the IP address "
Index: src/backend/utils/misc/postgresql.conf.sample
===================================================================
RCS file: /cvsroot/pgsql/src/backend/utils/misc/postgresql.conf.sample,v
retrieving revision 1.210
diff -c -c -r1.210 postgresql.conf.sample
*** src/backend/utils/misc/postgresql.conf.sample	2 Mar 2007 23:37:23 -0000	1.210
--- src/backend/utils/misc/postgresql.conf.sample	3 Mar 2007 18:41:16 -0000
***************
*** 340,346 ****
  					# e.g. '<%u%%%d> '
  #log_statement = 'none'			# none, ddl, mod, all
  #log_hostname = off
! 
  #log_temp_files = -1			# Log temporary files equal or larger
  					# than the specified number of kilobytes.
  					# -1 disables;  0 logs all temp files
--- 340,346 ----
  					# e.g. '<%u%%%d> '
  #log_statement = 'none'			# none, ddl, mod, all
  #log_hostname = off
! #log_lock_waits = off			# Log lock waits longer than deadlock_timeout
  #log_temp_files = -1			# Log temporary files equal or larger
  					# than the specified number of kilobytes.
  					# -1 disables;  0 logs all temp files
Index: src/include/storage/lock.h
===================================================================
RCS file: /cvsroot/pgsql/src/include/storage/lock.h,v
retrieving revision 1.103
diff -c -c -r1.103 lock.h
*** src/include/storage/lock.h	5 Jan 2007 22:19:58 -0000	1.103
--- src/include/storage/lock.h	3 Mar 2007 18:41:17 -0000
***************
*** 394,399 ****
--- 394,406 ----
  	LOCKACQUIRE_ALREADY_HELD	/* incremented count for lock already held */
  } LockAcquireResult;
  
+ /* Deadlock states identified by DeadlockCheck() */
+ typedef enum
+ {
+ 	DS_DEADLOCK_NOT_FOUND,		/* no deadlock found within database server */
+ 	DS_SOFT_DEADLOCK,			/* deadlock, but lock queues rearrangeable */
+ 	DS_HARD_DEADLOCK			/* deadlock, no way out but ERROR */
+ } DeadlockState;
  
  /*
   * The lockmgr's shared hash tables are partitioned to reduce contention.
***************
*** 442,448 ****
  extern void lock_twophase_postabort(TransactionId xid, uint16 info,
  						void *recdata, uint32 len);
  
! extern bool DeadLockCheck(PGPROC *proc);
  extern void DeadLockReport(void);
  extern void RememberSimpleDeadLock(PGPROC *proc1,
  					   LOCKMODE lockmode,
--- 449,455 ----
  extern void lock_twophase_postabort(TransactionId xid, uint16 info,
  						void *recdata, uint32 len);
  
! extern DeadlockState DeadLockCheck(PGPROC *proc);
  extern void DeadLockReport(void);
  extern void RememberSimpleDeadLock(PGPROC *proc1,
  					   LOCKMODE lockmode,
Index: src/include/storage/proc.h
===================================================================
RCS file: /cvsroot/pgsql/src/include/storage/proc.h,v
retrieving revision 1.94
diff -c -c -r1.94 proc.h
*** src/include/storage/proc.h	15 Feb 2007 23:23:23 -0000	1.94
--- src/include/storage/proc.h	3 Mar 2007 18:41:17 -0000
***************
*** 127,132 ****
--- 127,133 ----
  /* configurable options */
  extern int	DeadlockTimeout;
  extern int	StatementTimeout;
+ extern bool	log_lock_waits;
  
  extern volatile bool cancel_from_timeout;
  
---------------------------(end of broadcast)---------------------------
TIP 6: explain analyze is your friend

Reply via email to