diff --git a/src/backend/access/heap/heapam.c b/src/backend/access/heap/heapam.c index db443218b4..c8436b709d 100644 --- a/src/backend/access/heap/heapam.c +++ b/src/backend/access/heap/heapam.c @@ -8,7 +8,7 @@ * * * IDENTIFICATION - * $Header: /cvsroot/pgsql/src/backend/access/heap/heapam.c,v 1.107 2001/01/12 21:53:54 tgl Exp $ + * $Header: /cvsroot/pgsql/src/backend/access/heap/heapam.c,v 1.108 2001/01/15 05:29:19 tgl Exp $ * * * INTERFACE ROUTINES @@ -1534,18 +1534,20 @@ l1: } END_CRIT_SECTION(); + LockBuffer(buffer, BUFFER_LOCK_UNLOCK); + #ifdef TUPLE_TOASTER_ACTIVE /* ---------- * If the relation has toastable attributes, we need to delete - * no longer needed items there too. + * no longer needed items there too. We have to do this before + * WriteBuffer because we need to look at the contents of the tuple, + * but it's OK to release the context lock on the buffer first. * ---------- */ if (HeapTupleHasExtended(&tp)) heap_tuple_toast_attrs(relation, NULL, &(tp)); #endif - LockBuffer(buffer, BUFFER_LOCK_UNLOCK); - /* * Mark tuple for invalidation from system caches at next command boundary. * We have to do this before WriteBuffer because we need to look at the @@ -1568,7 +1570,10 @@ heap_update(Relation relation, ItemPointer otid, HeapTuple newtup, ItemId lp; HeapTupleData oldtup; PageHeader dp; - Buffer buffer, newbuf; + Buffer buffer, + newbuf; + bool need_toast, + already_marked; int result; /* increment access statistics */ @@ -1645,7 +1650,7 @@ l2: return result; } - /* XXX order problems if not atomic assignment ??? */ + /* Fill in OID and transaction status data for newtup */ newtup->t_data->t_oid = oldtup.t_data->t_oid; TransactionIdStore(GetCurrentTransactionId(), &(newtup->t_data->t_xmin)); newtup->t_data->t_cmin = GetCurrentCommandId(); @@ -1653,70 +1658,85 @@ l2: newtup->t_data->t_infomask &= ~(HEAP_XACT_MASK); newtup->t_data->t_infomask |= (HEAP_XMAX_INVALID | HEAP_UPDATED); -#ifdef TUPLE_TOASTER_ACTIVE - /* ---------- - * If this relation is enabled for toasting, let the toaster - * delete any no-longer-needed entries and create new ones to - * make the new tuple fit again. Also, if there are already- - * toasted values from some other relation, the toaster must - * fix them. - * ---------- + /* + * If the toaster needs to be activated, OR if the new tuple will not + * fit on the same page as the old, then we need to release the context + * lock (but not the pin!) on the old tuple's buffer while we are off + * doing TOAST and/or table-file-extension work. We must mark the old + * tuple to show that it's already being updated, else other processes + * may try to update it themselves. To avoid second XLOG log record, + * we use xact mgr hook to unlock old tuple without reading log if xact + * will abort before update is logged. In the event of crash prio logging, + * TQUAL routines will see HEAP_XMAX_UNLOGGED flag... + * + * NOTE: this trick is useless currently but saved for future + * when we'll implement UNDO and will re-use transaction IDs + * after postmaster startup. + * + * We need to invoke the toaster if there are already any toasted values + * present, or if the new tuple is over-threshold. */ - if (HeapTupleHasExtended(&oldtup) || - HeapTupleHasExtended(newtup) || - (MAXALIGN(newtup->t_len) > TOAST_TUPLE_THRESHOLD)) - heap_tuple_toast_attrs(relation, newtup, &oldtup); -#endif + need_toast = (HeapTupleHasExtended(&oldtup) || + HeapTupleHasExtended(newtup) || + (MAXALIGN(newtup->t_len) > TOAST_TUPLE_THRESHOLD)); - /* Find buffer for new tuple */ - - if ((unsigned) MAXALIGN(newtup->t_len) <= PageGetFreeSpace((Page) dp)) - newbuf = buffer; - else + if (need_toast || + (unsigned) MAXALIGN(newtup->t_len) > PageGetFreeSpace((Page) dp)) { - /* - * We have to unlock old tuple buffer before extending table - * file but have to keep lock on the old tuple. To avoid second - * XLOG log record we use xact mngr hook to unlock old tuple - * without reading log if xact will abort before update is logged. - * In the event of crash prio logging, TQUAL routines will see - * HEAP_XMAX_UNLOGGED flag... - * - * NOTE: this trick is useless currently but saved for future - * when we'll implement UNDO and will re-use transaction IDs - * after postmaster startup. - */ _locked_tuple_.node = relation->rd_node; _locked_tuple_.tid = oldtup.t_self; XactPushRollback(_heap_unlock_tuple, (void*) &_locked_tuple_); - TransactionIdStore(GetCurrentTransactionId(), &(oldtup.t_data->t_xmax)); + TransactionIdStore(GetCurrentTransactionId(), + &(oldtup.t_data->t_xmax)); oldtup.t_data->t_cmax = GetCurrentCommandId(); oldtup.t_data->t_infomask &= ~(HEAP_XMAX_COMMITTED | - HEAP_XMAX_INVALID | HEAP_MARKED_FOR_UPDATE); + HEAP_XMAX_INVALID | + HEAP_MARKED_FOR_UPDATE); oldtup.t_data->t_infomask |= HEAP_XMAX_UNLOGGED; + already_marked = true; LockBuffer(buffer, BUFFER_LOCK_UNLOCK); - newbuf = RelationGetBufferForTuple(relation, newtup->t_len); + + /* Let the toaster do its thing */ + if (need_toast) + heap_tuple_toast_attrs(relation, newtup, &oldtup); + + /* Now, do we need a new page for the tuple, or not? */ + if ((unsigned) MAXALIGN(newtup->t_len) <= PageGetFreeSpace((Page) dp)) + newbuf = buffer; + else + newbuf = RelationGetBufferForTuple(relation, newtup->t_len); + + /* Re-acquire the lock on the old tuple's page. */ /* this seems to be deadlock free... */ LockBuffer(buffer, BUFFER_LOCK_EXCLUSIVE); } + else + { + /* No TOAST work needed, and it'll fit on same page */ + already_marked = false; + newbuf = buffer; + } /* NO ELOG(ERROR) from here till changes are logged */ START_CRIT_SECTION(); RelationPutHeapTuple(relation, newbuf, newtup); /* insert new tuple */ - if (buffer == newbuf) - { - TransactionIdStore(GetCurrentTransactionId(), &(oldtup.t_data->t_xmax)); - oldtup.t_data->t_cmax = GetCurrentCommandId(); - oldtup.t_data->t_infomask &= ~(HEAP_XMAX_COMMITTED | - HEAP_XMAX_INVALID | HEAP_MARKED_FOR_UPDATE); - } - else + + if (already_marked) { oldtup.t_data->t_infomask &= ~HEAP_XMAX_UNLOGGED; XactPopRollback(); } + else + { + TransactionIdStore(GetCurrentTransactionId(), + &(oldtup.t_data->t_xmax)); + oldtup.t_data->t_cmax = GetCurrentCommandId(); + oldtup.t_data->t_infomask &= ~(HEAP_XMAX_COMMITTED | + HEAP_XMAX_INVALID | + HEAP_MARKED_FOR_UPDATE); + } /* record address of new tuple in t_ctid of old one */ oldtup.t_data->t_ctid = newtup->t_self; @@ -1724,7 +1744,7 @@ l2: /* XLOG stuff */ { XLogRecPtr recptr = log_heap_update(relation, buffer, oldtup.t_self, - newbuf, newtup, false); + newbuf, newtup, false); if (newbuf != buffer) { @@ -1734,6 +1754,7 @@ l2: PageSetLSN(BufferGetPage(buffer), recptr); PageSetSUI(BufferGetPage(buffer), ThisStartUpID); } + END_CRIT_SECTION(); if (newbuf != buffer) diff --git a/src/backend/access/heap/tuptoaster.c b/src/backend/access/heap/tuptoaster.c index 2787611dd0..46495404b8 100644 --- a/src/backend/access/heap/tuptoaster.c +++ b/src/backend/access/heap/tuptoaster.c @@ -8,7 +8,7 @@ * * * IDENTIFICATION - * $Header: /cvsroot/pgsql/src/backend/access/heap/tuptoaster.c,v 1.13 2000/10/23 23:42:04 tgl Exp $ + * $Header: /cvsroot/pgsql/src/backend/access/heap/tuptoaster.c,v 1.14 2001/01/15 05:29:19 tgl Exp $ * * * INTERFACE ROUTINES @@ -197,10 +197,12 @@ toast_delete(Relation rel, HeapTuple oldtup) */ for (i = 0; i < numAttrs; i++) { - value = heap_getattr(oldtup, i + 1, tupleDesc, &isnull); - if (!isnull && att[i]->attlen == -1) - if (VARATT_IS_EXTERNAL(value)) + if (att[i]->attlen == -1) + { + value = heap_getattr(oldtup, i + 1, tupleDesc, &isnull); + if (!isnull && VARATT_IS_EXTERNAL(value)) toast_delete_datum(rel, value); + } } }