美文网首页
MySQL锁初探

MySQL锁初探

作者: 诺之林 | 来源:发表于2018-10-12 09:46 被阅读5次

    本文的示例代码参考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
    

    参考

    相关文章

      网友评论

          本文标题:MySQL锁初探

          本文链接:https://www.haomeiwen.com/subject/dlppaftx.html