Download & Extend

Wrong database transaction handling: stalling transactions

Project:Drupal Commerce
Version:7.x-1.x-dev
Component:Commerce
Category:bug report
Priority:normal
Assigned:Unassigned
Status:active

Issue Summary

In the DrupalCommerceEntityController::buildQuery() method:

<?php
   
if (isset($this->entityInfo['locking mode']) && $this->entityInfo['locking mode'] == 'pessimistic') {
     
// In pessimistic locking mode, we issue the load query with a FOR UPDATE
      // clause. This will block all other load queries to the loaded objects
      // but requires us to start a transaction.
     
if (empty($this->controllerTransaction)) {
       
$this->controllerTransaction = db_transaction();
      }

     
$query->forUpdate();

     
// Store the ids of the entities in the lockedEntities array for later
      // tracking, flipped for easier management via unset() below.
     
$this->lockedEntities += array_flip($ids);
    }
?>

This also happens when loading entities, DrupalDefaultEntityController::load():

<?php
   
// Load any remaining entities from the database. This is the case if $ids
    // is set to FALSE (so we load all entities), if there are any ids left to
    // load, if loading a revision, or if $conditions was passed without $ids.
   
if ($ids === FALSE || $ids || $revision_id || ($conditions && !$passed_ids)) {
     
// Build the query.
     
$query = $this->buildQuery($ids, $conditions, $revision_id);
     
$queried_entities = $query
       
->execute()
        ->
fetchAllAssoc($this->idKey);
    }
?>

Which then directly returns the entities:

<?php
   
return $entities;
?>

The transaction is never commited because of this: $this->controllerTransaction.

Each time someone loads an entity via a commerce controller, the full page rendering will happen in one unique giant database transaction, with a FOR UPDATE row locking! This may explain some slowness of Commerce.

I experienced it doing heavy import jobs, the first load attempt using any commerce controller will put a transaction right at the beginning and never be released (commit). I was injecting 3000+ line items into 300+ orders, in my case, this doesn't slow things that much because I currently have only one parallel thread on my development box, the horrible side effect is that I cannot watch what's happening for debug purposes while injecting my stuff: I need to be able wether or not I need a transaction, and if I need one, I will place it in my business code myself.

Comments

#1

Correction: it's only doing this for orders, still happens potentially since the cart is itself an order (I didn't tested it thought).

#2

Other correction: the giant transaction will happen only if the order is not saved (which happens most of the case).

#3

Other correction: I surely understand why the pessimistic locking serve the Commerce framework here, but by the roleplay of various concurrent modules (rules, migrate, views, etc...) you may end up locking a lock of orders in a lot of threads, pretty much all the time: there is probably a way to avoid this in some cases.

nobody click here