本文的示例代码参考laravel-lock
目录
MySQL
docker run --name laravel-lock -p 3306:3306 -e MYSQL_ROOT_PASSWORD=secret -d mysql:5.7.17
docker exec -i laravel-lock mysql -uroot -psecret <<< "CREATE DATABASE IF NOT EXISTS homestead DEFAULT CHARSET utf8 COLLATE utf8_general_ci;"
Project
Composer
composer create-project laravel/laravel laravel-lock --prefer-dist "5.5.*" && cd laravel-lock
sed -i "" "s/DB_USERNAME=homestead/DB_USERNAME=root/g" .env
Migration
php artisan make:model Models/Machine -m
vim database/migrations/*_create_machines_table.php
<?php
use Illuminate\Database\Migrations\Migration;
use Illuminate\Database\Schema\Blueprint;
use Illuminate\Support\Facades\Schema;
class CreateMachinesTable extends Migration
{
public function up()
{
Schema::create('machines', function (Blueprint $table) {
$table->increments('id');
$table->string('name')->index()->comment('机械名称');
$table->timestamps();
});
}
public function down()
{
Schema::dropIfExists('machines');
}
}
php artisan migrate
Seeder
php artisan make:seed MachinesTableSeeder
vim database/seeds/MachinesTableSeeder.php
<?php
use App\Models\Machine;
use Illuminate\Database\Seeder;
class MachinesTableSeeder extends Seeder
{
public function run()
{
$machine = new Machine();
$machine->name = 'machine1';
$machine->save();
$machine = new Machine();
$machine->name = 'machine2';
$machine->save();
}
}
vim database/seeds/DatabaseSeeder.php
<?php
use Illuminate\Database\Seeder;
class DatabaseSeeder extends Seeder
{
public function run()
{
// $this->call(UsersTableSeeder::class);
$this->call(MachinesTableSeeder::class);
}
}
php artisan db:seed
Pessimistic Lock
Pessimistic Lock is when you lock the record for your exclusive use until you have finished with it
Lock In Share Mode
# MySQL connection 1
mysql root@localhost:(none)> USE homestead;
You are now connected to database "homestead" as user "root"
Time: 0.012s
mysql root@localhost:homestead> BEGIN;
Query OK, 0 rows affected
Time: 0.001s
mysql root@localhost:homestead> SELECT * FROM machines WHERE id = 1 LOCK IN SHARE MODE;
+----+----------+---------------------+---------------------+
| id | name | created_at | updated_at |
+----+----------+---------------------+---------------------+
| 1 | machine1 | 2018-10-11 12:20:58 | 2018-10-11 12:20:58 |
+----+----------+---------------------+---------------------+
1 row in set
Time: 0.027s
# MySQL connection 2
mysql root@localhost:(none)> USE homestead;
You are now connected to database "homestead" as user "root"
Time: 0.007s
mysql root@localhost:homestead> SELECT * FROM machines WHERE id = 1;
+----+----------+---------------------+---------------------+
| id | name | created_at | updated_at |
+----+----------+---------------------+---------------------+
| 1 | machine1 | 2018-10-11 12:20:58 | 2018-10-11 12:20:58 |
+----+----------+---------------------+---------------------+
1 row in set
Time: 0.018s
mysql root@localhost:homestead> UPDATE machines SET name='machine22' WHERE id=2;
Query OK, 1 row affected
Time: 0.046s
mysql root@localhost:homestead> SELECT * FROM machines;
+----+-----------+---------------------+---------------------+
| id | name | created_at | updated_at |
+----+-----------+---------------------+---------------------+
| 1 | machine1 | 2018-10-11 12:20:58 | 2018-10-11 12:20:58 |
| 2 | machine22 | 2018-10-11 12:20:59 | 2018-10-11 12:20:59 |
+----+-----------+---------------------+---------------------+
2 rows in set
Time: 0.023s
mysql root@localhost:homestead> UPDATE machines SET name='machine11' WHERE id=1;
# pending
# MySQL connection 1
mysql root@localhost:homestead> COMMIT;
Query OK, 0 rows affected
Time: 0.005s
# MySQL connection 2
mysql root@localhost:homestead> UPDATE machines SET name='machine11' WHERE id=1;
Query OK, 1 row affected
Time: 11.423s
TRUNCATE machines;
php artisan db:seed
LOCK IN SHARE MODE locks only for write
For Update
# MySQL connection 1
mysql root@localhost:(none)> USE homestead;
You are now connected to database "homestead" as user "root"
Time: 0.005s
mysql root@localhost:homestead> BEGIN;
Query OK, 0 rows affected
Time: 0.005s
mysql root@localhost:homestead> SELECT * FROM machines FOR UPDATE;
+----+----------+---------------------+---------------------+
| id | name | created_at | updated_at |
+----+----------+---------------------+---------------------+
| 1 | machine1 | 2018-10-10 07:22:53 | 2018-10-10 07:22:53 |
| 2 | machine2 | 2018-10-10 07:22:53 | 2018-10-10 07:22:53 |
+----+----------+---------------------+---------------------+
2 rows in set
Time: 0.015s
# MySQL connection 2
mysql root@localhost:(none)> USE homestead;
You are now connected to database "homestead" as user "root"
Time: 0.007s
mysql root@localhost:homestead> SELECT * FROM machines FOR UPDATE;
# pending
# MySQL connection 1
mysql root@localhost:homestead> COMMIT;
Query OK, 0 rows affected
Time: 0.001s
# MySQL connection 2
mysql root@localhost:homestead> SELECT * FROM machines FOR UPDATE;
+----+----------+---------------------+---------------------+
| id | name | created_at | updated_at |
+----+----------+---------------------+---------------------+
| 1 | machine1 | 2018-10-10 07:22:53 | 2018-10-10 07:22:53 |
| 2 | machine2 | 2018-10-10 07:22:53 | 2018-10-10 07:22:53 |
+----+----------+---------------------+---------------------+
2 rows in set
Time: 38.915s
FOR UPDATE also prevents them from being selected
Table Lock vs Record Lock
# MySQL connection 1
mysql root@localhost:(none)> USE homestead;
You are now connected to database "homestead" as user "root"
Time: 0.012s
mysql root@localhost:homestead> BEGIN;
Query OK, 0 rows affected
Time: 0.001s
mysql root@localhost:homestead> SELECT * FROM machines LOCK IN SHARE MODE;
+----+----------+---------------------+---------------------+
| id | name | created_at | updated_at |
+----+----------+---------------------+---------------------+
| 1 | machine1 | 2018-10-11 12:53:19 | 2018-10-11 12:53:19 |
| 2 | machine2 | 2018-10-11 12:53:19 | 2018-10-11 12:53:19 |
+----+----------+---------------------+---------------------+
2 rows in set
Time: 0.024s
# MySQL connection 2
mysql root@localhost:(none)> USE homestead;
You are now connected to database "homestead" as user "root"
Time: 0.007s
mysql root@localhost:homestead> UPDATE machines SET name='machine11' WHERE id=1;
# pending
mysql root@localhost:homestead> UPDATE machines SET name='machine22' WHERE id=2;
# pending
行级锁都是基于索引的 如果一条SQL语句用不到索引是不会使用行级锁的 此时会使用表级锁把整张表锁住
Optimistic Lock
Optimistic Lock is a strategy where you read a record, take note of a version number (other methods to do this involve dates, timestamps or checksums/hashes) and check that the version hasn't changed before you write the record back
Composer
composer require reshadman/laravel-optimistic-locking
Migration
php artisan make:migration update_machines_table_add_lock_version
vim database/migrations/*_update_machines_table_add_lock_version.php
<?php
use Illuminate\Database\Migrations\Migration;
use Illuminate\Database\Schema\Blueprint;
use Illuminate\Support\Facades\Schema;
class UpdateMachinesTableAddLockVersion extends Migration
{
public function up()
{
Schema::table('machines', function (Blueprint $table) {
$table->integer('lock_version')->unsigned()->nullable();
});
}
public function down()
{
Schema::table('machines', function (Blueprint $table) {
$table->dropColumn('lock_version');
});
}
}
vim app/Models/Machine.php
<?php
namespace App\Models;
use Illuminate\Database\Eloquent\Model;
use Reshadman\OptimisticLocking\OptimisticLocking;
class Machine extends Model
{
use OptimisticLocking;
protected $fillable = ['name'];
}
Unit Test
php artisan make:test MachineTest --unit
vim tests/Unit/MachineTest.php
<?php
namespace Tests\Unit;
use App\Models\Machine;
use Reshadman\OptimisticLocking\StaleModelLockingException;
use Tests\TestCase;
class MachineTest extends TestCase
{
public function test_throws_exception_on_concurrent_change()
{
$truth = Machine::create([
'name' => 'machine3',
]);
$first = Machine::find($truth->id);
$second = Machine::find($truth->id);
$this->expectException(StaleModelLockingException::class);
$first->name = $wantedTitle = 'machine31';
$this->assertTrue($first->save());
try {
$second->name = 'machine32';
$second->save();
} catch (StaleModelLockingException $e) {
$fetchedAfterFirstUpdate = Machine::find($truth->id);
$this->assertEquals($fetchedAfterFirstUpdate->name, $wantedTitle);
$this->assertEquals($fetchedAfterFirstUpdate->lock_version, $first->lock_version);
$this->assertEquals($fetchedAfterFirstUpdate->lock_version, $truth->lock_version + 1);
throw $e;
}
}
}
brew install phpunit
phpunit
PHPUnit 7.4.0 by Sebastian Bergmann and contributors.
... 3 / 3 (100%)
Time: 252 ms, Memory: 18.00MB
OK (3 tests, 7 assertions)
# MySQL connection
mysql root@localhost:(none)> USE homestead;
You are now connected to database "homestead" as user "root"
Time: 0.007s
mysql root@localhost:homestead> SELECT * FROM machines;
+----+-----------+---------------------+---------------------+--------------+
| id | name | created_at | updated_at | lock_version |
+----+-----------+---------------------+---------------------+--------------+
| 1 | machine1 | 2018-10-10 07:22:53 | 2018-10-10 07:22:53 | <null> |
| 2 | machine2 | 2018-10-10 07:22:53 | 2018-10-10 07:22:53 | <null> |
| 3 | machine31 | 2018-10-12 01:36:20 | 2018-10-12 01:36:20 | 2 |
+----+-----------+---------------------+---------------------+--------------+
3 rows in set
Time: 0.013s
网友评论