Deeply understand the principles and usage of Yii2.0 optimistic and pessimistic locks, and deeply understand yii2.0

Source: Internet
Author: User

Deeply understand the principles and usage of Yii2.0 optimistic and pessimistic locks, and deeply understand yii2.0

This article introduces the principles and usage of Yii2.0 optimistic and pessimistic locks, and shares them with you as follows:

Web applications often face multi-user environments. In this case, concurrent write control becomes a skill that almost every developer must master.

In a concurrent environment, Dirty Read, Unrepeatable Read, Phantom Read, and Lost update may occur. You can search for the specific performance.

To address these problems, mainstream databases provide a lock mechanism and introduce the concept of transaction isolation level. We will not explain it here. We will search for these keywords, and there will be a lot of online information.

However, in the specific development process, there are two methods to solve the concurrency conflict: Pessimistic lock and optimistic lock.

Optimistic lock

Optimistic locking shows a bold and pragmatic attitude. The premise of Optimistic Locking is that there is a low probability of conflict in actual application. His design and implementation are straightforward. Currently, optimistic locks play an absolute advantage in Web applications.

Therefore, Yii also provides optimistic lock support for ActiveReocrd.

According to Yii official documentation, optimistic locks are used in four steps:

  • Add a field to the table to be locked to indicate the version number. Of course, the corresponding Model should also be added for this field and make appropriate adjustments. For example, this field must be added to rules.
  • Reload the yii \ db \ ActiveRecord: optimisticLock () method and return the field name in the previous step.
  • In the record modification page form, add a <input type = "hidden"> version number of the record used for temporary reading.
  • To save the code, use try... catch to check whether an yii \ db \ StaleObjectException exception can be caught. If yes, it indicates that the record has been modified during this modification. In simple response, you can make corresponding prompts. Smart points can be merged without conflict or a diff page is displayed.

In essence, optimistic locks do not use the database lock mechanism as pessimistic locks do. The optimistic lock adds a count field to the table to indicate the number of changes to the current record (version number ).

Then, the optimistic lock is implemented by comparing the version number before update and deletion.

Declare version number field

The version number is the root of Optimistic Locking. So the first step is to tell Yii which field is the version number field. Yii \ db \ BaseActiveRecord is responsible for this:

public function optimisticLock(){  return null;}

This method returns null, indicating no optimistic lock is used. In our Model, we need to reload this. Returns a string that indicates the field used to identify the version number. For example:

public function optimisticLock(){  return 'ver';}

It indicates that the current ActiveRecord contains a ver field, which can be used for optimistic locks. Then how does Yii implement optimistic locks by using this ver field?

Update process

Specifically, the update process after optimistic locks is like this process:

  1. Read the record to be updated.
  2. Modify the record according to the user's wishes. Of course, the ver field will not be modified at this time. This field is meaningless to users.
  3. Before saving the record, read the record's ver field again and compare it with the previously read value.
  4. If the server is different, it indicates that this record has been modified by others during the user modification process. So, we need to give a prompt.
  5. If the server is the same, this record has not been modified. Then, verify the SERVER + 1 and save the record. This completes the record update. At the same time, the version number of the record is also added with 1.

Because the update process of ActiveRecord must be called eventually yii\db\BaseActiveRecord::updateInteranl() , Of course, the code that handles optimistic locks is also hidden in this method:

Protected function updateInternal ($ attributes = null) {if (! $ This-> beforeSave (false) {return false ;} // obtain the fields to be updated and the new field values. $ values = $ this-> getDirtyAttributes ($ attributes); if (empty ($ values )) {$ this-> afterSave (false, $ values); return 0;} // use the primary key of the original ActiveRecord as the condition for updating the record. // That is to say, only one record is allowed. $ Condition = $ this-> getOldPrimaryKey (true); // obtain the field name of the version number field, such as ver $ lock = $ this-> optimisticLock (); // If optimisticLock () if null is returned, the optimistic lock is not enabled. If ($ lock! = Null) {// $ this-> $ lock here is the meaning of $ this-> ver. // here, ver + 1 is used as one of the fields to be updated. $ Values [$ lock] = $ this-> $ lock + 1; // here, the old version number is used as another condition for updating $ condition [$ lock] = $ this-> $ lock;} $ rows = $ this-> updateAll ($ values, $ condition); // If the optimistic lock is enabled but the update is not completed, or the number of updated records is 0; // It indicates that the ver does not match, if the record has been modified, an exception is thrown. If ($ lock! = Null &&! $ Rows) {throw new StaleObjectException ('the object being updated is outdated. ');} $ changedAttributes = []; foreach ($ values as $ name => $ value) {$ changedAttributes [$ name] = isset ($ this-> _ oldAttributes [$ name])? $ This-> _ oldAttributes [$ name]: null; $ this-> _ oldAttributes [$ name] = $ value ;}$ this-> afterSave (false, $ changedAttributes ); return $ rows ;}

From the code above, we can easily conclude that:

  1. When optimisticLock () returns null, the optimistic lock is not enabled.
  2. The version number only increases or decreases.
  3. There are two conditions for optimistic locks. One is that the primary key must exist, and the other is that the update must be completed.
  4. When optimistic lock is enabled, StaleObjectException is thrown in only the following two cases:
    1. After a record is deleted, the update fails because the primary key does not exist.
    2. The version number has changed and does not meet the second update condition.

Deletion Process

Compared with the update process, the optimistic lock in the delete process is simpler and better understood. The code is still in yii \ db \ BaseActiveRecord:

Public function delete () {$ result = false; if ($ this-> beforeDelete () {// delete an SQL statement, the WHERE section is the primary key $ condition = $ this-> getOldPrimaryKey (true); // obtain the field name of the version number field, such as ver $ lock = $ this-> optimisticLock (); // if optimistic lock is enabled, add a condition in the WHERE section, version number if ($ lock! = Null) {$ condition [$ lock] = $ this-> $ lock;} $ result = $ this-> deleteAll ($ condition); if ($ lock! = Null &&! $ Result) {throw new StaleObjectException ('the object being deleted is outdated. ') ;}$ this-> _ oldAttributes = null; $ this-> afterDelete ();} return $ result ;}

The deletion process is much simpler than the update process. The only difference is that the step of Version Number + 1 is omitted. Are all deleted. What is the significance of Version Number + 1?

Optimistic lock failure

Optimistic locks are invalid, which is a small probability event and can only appear when multiple conditions are used together. For example:

  1. The application uses its own policy to manage the primary key ID. For example, it is common to take the maximum value + 1 of the current ID field as the new ID.
  2. The default version number field ver is 0.
  3. User A reads A record and prepares to modify it. This record is exactly the record with the largest ID and has not been modified before. The default value is 0 for ver.
  4. After user A completes reading, user B deletes the record. Then, user C inserts a new record.
  5. In this case, the ID of the newly inserted record is the same as the ID of the record read by user A, and both versions have the default value 0.
  6. After user A completes operations on user C, the modification is completed and saved. User A is successfully saved because both the ID and ver can be matched. However, the records inserted by user C are overwritten.

The root cause of the failure of the optimistic lock is that the primary key ID management policy used by the application is not compatible with the optimistic lock to a very small extent.

It is okay to separate the two. After the combination, it seems that there is no problem. But the bug becomes a bug, and it is precisely because of its concealment that the trap can be put to death.

This problem can be avoided by using the timestamp as the version number field. However, if the timestamp is not accurate enough, for example, in milliseconds, there is still a possibility of failure in high concurrency or even a coincidence. However, if high-precision timestamps are used, the cost is too high.

The timestamp is not more reliable than the integer type. The problem still comes back to using a rigorous primary key generation policy.

Pessimistic lock

As its name suggests, the pessimistic locking reflects a cautious attitude. The process is as follows:

  1. Before modifying any record, try to add exclusive locking to the record ).
  2. If the lock fails, the record is being modified, so the current query may have to wait or throw an exception. The specific response method is determined by the developer based on actual needs.
  3. If the lock is successful, you can modify the record and unlock the record after the transaction is completed.
  4. If there are other operations that modify or apply exclusive locks to the record, they will wait for us to unlock or directly throw an exception.

Pessimistic locks are indeed rigorous, effectively ensuring data consistency. There are many mature solutions for C/S applications. However, his shortcomings are as obvious as those with advantages:

  1. Pessimistic locks apply to reliable persistent connections such as C/S applications. It is not applicable to HTTP connections of Web applications.
  2. The use of the lock means performance loss, especially in the case of high concurrency and long lock duration. The performance bottleneck of Web applications lies mostly in the database, and pessimistic locks are used to further tighten the bottleneck.
  3. It is difficult to design and implement the unlock mechanism when an exception is terminated, and the cost is high.
  4. Under a rigorous design, it may be inexplicable and hard to be found, which makes it a headache to give a slap in the face of a deadlock.

In general, pessimistic locks are not suitable for Web applications. The Yii team also thinks that implementation of pessimistic locks is too troublesome. Therefore, ActiveRecord does not provide pessimistic locks.

As Ruby on rails, one of the components of Yii, his ActiveReocrd model provides pessimistic locks, but it is also very troublesome to use.

Pessimistic lock implementation

Although there are many deficiencies in the pessimistic lock on Web applications, implementing the pessimistic lock also requires various troubles. However, when the user proposes that he wants to use a pessimistic lock, the codoon with his teeth is worse, that is, biting his teeth is also about to chew this bone.

For a typical Web application, a commonly used method is provided to implement pessimistic locks.

First, add a field such as locked_at in the table to indicate the time when the current record is locked. If it is 0, the record is not locked, or think this is the lock that was added in 1970.

To modify a record, first check whether the difference between the current time and the locked_at field exceeds the predefined T Length, such as 30 min and 1 h.

If the value does not exceed, it indicates that someone is modifying the record. We cannot open (read) it for modification. Otherwise, it can be modified. We first save the current timestamp to the locked_at field of the record. If someone wants to modify the record within the subsequent duration T, the record will fail to be read due to lock failure and thus cannot be modified.

When we save the changes, we need to compare the current locked_at. Only when locked_at is consistent can we think that the lock we just added is saved. Otherwise, the locked_at value is set to 0 after the lock is applied or the lock is being modified.

This is mainly because our modification duration is too long and exceeds the predefined T. The original locks are automatically unlocked, and other users can re-add their own locks after the lock time is T. In other words, the pessimistic lock degrades to an optimistic lock.

The general principle code is as follows:

// Pessimistic lock AR base class. The AR that requires the pessimistic lock can be derived from this class PLockAR extends \ yii \ db \ BaseActiveRecord {// declare the tag field used by the pessimistic lock, the function is similar to the public function pesstimisticLock () {return null;} of the optimisticLock () method. // defines the maximum lock duration. After the lock duration is exceeded, it is automatically unlocked. Public function maxLockTime () {return 0;} // attempts to lock. If the lock is successful, true public function lock () {$ lock = $ this-> pesstimisticLock () is returned (); $ now = time (); $ values = [$ lock => $ now]; // the following two sentences are updated with the primary key, the last lock time has exceeded the specified length. $ condition = $ this-> getOldPrimaryKey (true); $ condition [] = ['<', $ lock, $ now-$ this-> maxLockTime ()]; $ rows = $ this-> updateAll ($ values, $ condition); // if the lock fails, false if (! $ Rows) {return false;} return true;} // overload updateInternal () protected function updateInternal ($ attributes = null) {// These are the same as the original code if (! $ This-> beforeSave (false) {return false;} $ values = $ this-> getDirtyAttributes ($ attributes); if (empty ($ values )) {$ this-> afterSave (false, $ values); return 0 ;}$ condition = $ this-> getOldPrimaryKey (true ); // change the field $ lock = $ this-> pesstimisticLock (); // If $ lock is null, do not enable the pessimistic lock. If ($ lock! = Null) {// when saving, set the ID field to 0 $ values [$ lock] = 0; // here, the original ID field value is used as another condition for updating $ condition [$ lock] = $ this-> $ lock ;} $ rows = $ this-> updateAll ($ values, $ condition); // If the pessimistic lock is enabled, but the update is not completed, or the number of updated records is 0; // It indicates that the previous lock has expired automatically, and the record is being modified. // or the modification has been completed, so an exception is thrown. If ($ lock! = Null &&! $ Rows) {throw new StaleObjectException ('the object being updated is outdated. ');} $ changedAttributes = []; foreach ($ values as $ name => $ value) {$ changedAttributes [$ name] = isset ($ this-> _ oldAttributes [$ name])? $ This-> _ oldAttributes [$ name]: null; $ this-> _ oldAttributes [$ name] = $ value ;}$ this-> afterSave (false, $ changedAttributes ); return $ rows ;}}

The above code compares optimistic locks, the main difference is:

  1. A new locking method is added to obtain the maximum lock duration.
  2. When saving, the ID field is no longer set to + 1, but the ID field is set to 0.

For more information, see the following code:

// Derived from PLockAR model class Post extends PLockAR {// overload defines the pessimistic lock identification field, such as locked_at public function pesstimisticLock () {return 'locked _ ';} // reload defines the maximum lock duration, for example, 1 hour public function maxLockTime () {return 3600000 ;}} // try to lock class SectionController extends Controller {public function actionUpdate ($ id) {$ model = $ this-> findModel ($ id) before modification ); if ($ model-> load (Yii: $ app-> request-> post () & $ model-> save () {return $ thi S-> redirect (['view', 'id' => $ model-> id]);} else {// Add a lock to determine if (! $ Model-> lock () {// lock failed //......} return $ this-> render ('update', ['model' => $ model,]) ;}}

The pessimistic lock implemented by the above method avoids the use of the database's own lock mechanism, which fits the characteristics of Web applications and has certain applicability, but there are also some defects:

  1. The maximum allowed lock length may cause some side effects. It may take a long time to edit the abnormally unlocked record. When the time is set to be short, it is often degraded into an optimistic lock.
  2. Timestamp accuracy. If the precision is not enough, then when locking, we discuss the optimistic lock failure memory with us, in the same vulnerability.
  3. In this way, the lock is only at the application layer, not at the database layer. If any write operation exists for the database outside the application. This locking mechanism is invalid.

The above is all the content of this article. I hope it will be helpful for your learning and support for helping customers.

Contact Us

The content source of this page is from Internet, which doesn't represent Alibaba Cloud's opinion; products and services mentioned on that page don't have any relationship with Alibaba Cloud. If the content of the page makes you feel confusing, please write us an email, we will handle the problem within 5 days after receiving your email.

If you find any instances of plagiarism from the community, please send an email to: info-contact@alibabacloud.com and provide relevant evidence. A staff member will contact you within 5 working days.

A Free Trial That Lets You Build Big!

Start building with 50+ products and up to 12 months usage for Elastic Compute Service

  • Sales Support

    1 on 1 presale consultation

  • After-Sales Support

    24/7 Technical Support 6 Free Tickets per Quarter Faster Response

  • Alibaba Cloud offers highly flexible support services tailored to meet your exact needs.