Транзакции в Drupal 7

14.02.2014
Автор:

Транзакция (англ. transaction) - это группа последовательных операций с базой данных, которая представляет собой логическую единицу работы с данными. Транзакция может быть выполнена полностью и успешно, сохраняя целостность данных и независимо от транзакций, которые выполняются параллельно, или же не выполнена вообще, в таком случае она не должна вызвать никаких изменений.

Начиная с 7-мой версии, в Drupal появилась возможность поддержки транзакций, включая базы данных, которые их не поддерживают. Однако, если попытаться выполнить две транзакции одновременно, то их выполнение может стать сложным. В этом случае их поведение будет зависеть от типа базы данных.

Такая же проблема возникает с вложенностью и в C/C++. Если код уже заблокировал таблицу A и попытается заблокировать ее повторно, то вы попадете в тупик. Если вы напишете код, который будет проверять, нет ли блокировки, и только при её отсутствии повторять попытку, то таких недоразумений можно будет избежать. Однако, это может привести к преждевременному снятию блокировки.

В SQL существует аналогичная проблема. Если, например, ваш код выполняется в транзакции и в этот момент начинается новая, то мы можем получить непредсказуемый результат выполнения текущей операции и начала новой.

Java подошла к проблеме вложенности с блокировкой иным образом. Реализуя поддержку вложенности структуры, Java позволяет обозначить функции как "синхронизированные", что заставляет последнюю либо ждать до тех пор, пока блокировка станет возможной, либо снять блокировку, когда она больше не нужна.

Хотя просто прописать функцию "транзакция" в PHP невозможно, однако можно следовать логике вложенности Java с помощью объектов из конструкторов и деструкторов. С этой целью в Drupal был разработан класс-оболочка для создания и управления транзакциями в базах данных - class DatabaseTransaction

Он используется в функции "$txn = db_transaction(); в качестве первой операции, чтобы сделать текущую функцию, в которой она вызывается, транзакцией.

Для того, чтобы начать новую транзакцию, необходимо просто прописать $txn = db_transaction( );  в собственном коде. Транзакция будет оставаться открытой до тех пор, пока переменная $txn будет оставаться в области исполнения. Когда переменная $txn будет уничтожена, транзакция будет выполнена. Если ваша транзакция является вложенностью другой, то Drupal выполнит каждую операцию и завершит внешнюю транзакцию только тогда, когда все объекты выйдут из области исполнения, т.е. все запросы пройдут успешно.

В том же случае, если один из запросов в транзакции не удается выполнить, происходит "откат" всех изменений, так называемый "rollback" - возврат данных в первоначальное состояние перед началом транзакции.

Пример:

<?php
function my_transaction_function() {
  // The transaction opens here.
  $transaction = db_transaction();
  try {
	$id = db_insert('example')
  	->fields(array(
    	'field1' => 'mystring',
    	'field2' => 5,
  	))
  	->execute();
	my_other_function($id);
	return $id;
  }
  catch (Exception $e) {
	$transaction->rollback();
	watchdog_exception('my_type', $e);
  }
  // $transaction goes out of scope here.  Unless it was rolled back, 
  // it gets automatically commited here.
}


function my_other_function($id) {
  // The transaction is still open here.
  if ($id % 2 == 0) {
	db_update('example')
  	->condition('id', $id)
  	->fields(array('field2' => 10))
  	->execute();
  }
}
?>

Если же появилась необходимость завершить одну из транзакций преждевременно, то для этого нужно "уничтожить" переменную $transaction с помощью функции unset().

<?php
  unset($transaction);
?>

Поработаем с транзакциями на примере.

Представим ситуацию, когда есть необходимость обработать большое количество данных для пользователей, операция для которых должна быть сгруппирована. Задача: снятие и зачисление средств из внутренних счетов пользователей на основе записей во временной таблице.

<?php
  function update_users_accounts() {
    // The transaction opens here.
    $accounts_transaction = db_transaction();
    try {
      // Select all users, that should be processed.
      $query = db_select(‘account_tmp_table’, 'at');
      $query->fields('at', array('uid', ‘decrease’, ‘increase’));
      $query->condition('at.timestamp', $time, '>');
      $result = $query->execute();
      while($record = $result->fetchAssoc()) {
        // Remove money from  user account.
        decrease_users_accounts($record[‘uid’], $record[‘decrease’]);
        // Add money to user account.
        increase_users_accounts($record[‘uid’], $record[‘increase’]);
        // Delete temp record from table.
        delete_users_account_record($record[‘uid’]);
      }	
      return TRUE;
    }
    catch (Exception $e) {
      $transaction->rollback();
      watchdog_exception('users_accounts', $e);
    }
  // $transaction goes out of scope here.  Unless it was rolled back, 
  // it gets automatically commited here.
  }

  function decrease_users_accounts($uid, $decrease) {
    // The transaction opens here.
    // Get current user account.
    $current_account = get_user_account($uid);
    $updated_account = $current_account - $decrease;
    // Update user's account record.
    db_update('account_data_table')
      ->condition('uid', $uid)
      ->fields(array('account' => $updated_account))
      ->execute();
  }
  
  function increase_users_accounts($uid, $increase) {
    // Get current user account.
    $current_account = get_user_account($uid);
    $updated_account = $current_account + $increase;
    // Update user's account record.
    db_update('account_data_table')
      ->condition('uid', $uid)
      ->fields(array('account' => $updated_account))
      ->execute();
  }
  
  function delete_users_account_record($uid) {
    // The transaction opens here.
    db_delete('account_tmp_table')
      ->condition('uid', $uid)
      ->execute();
  }
}

?>

Вариант реализации через транзакции сделает невозможным изменение счета пользователя другим функционалом при выполнении этого кода, а также в случае возникновения ошибок во время работы обеспечит сохранение целостности выполнения операции с помощью вызова метода rollback, который вернет все данные в исходное состояние.

В данной статье рассмотрена возможность поддержки транзакций при разработке сайтов на Drupal. Для более подробного ознакомления можно воспользоваться документацией.

2 votes, Rating: 5

Также по теме

1

Модуль Migrate позволяет импортировать содержимое сайта в Drupal из других источников. Узнайте как установить и настроить этот инструмент для корректной работы.

2

В этой статье речь пойдёт о том, как установить ядро Drupal 8 как сабмодуль. Мы не будем рассматривать само понятие сабмодулей, их преимущества/недостатки, а лишь покажем в деталях, как подобную...

3

Не так давно мы рассматривали, как создать ctools тип контента для модуля Panels. На этот раз пришел черед другого...

4

Apps -  это модуль, который можно позиционировать как следующий шаг в развитии...

5

Задача импорта контента часто бывает нетривиальной. Писать импорт "с нуля" для каждого случая далеко не оптимальный вариант, поэтому мы рекомендуем использовать уже существующие решения, например...

Need a quote? Let's discuss the project

Are you looking for someone to help you with your Drupal Web Development needs? Let’s get in touch and discuss the requirements of your project. We would love to hear from you.

Join the people who have already subscribed!

Want to be aware of important and interesting things happening? We will inform you about new blog posts on Drupal development, design, QA testing and more, as well news about Drupal events.

No charge. Unsubscribe anytime