zoukankan      html  css  js  c++  java
  • MySQL锁机制

    数据加锁也是用来保证事务并发的重要机制。

    MySQL锁分类:

    • 从数据粒度分表锁和行锁
    • 从性能分乐观锁和悲观锁
    • 从对数据库的操作类型分读锁和写锁,读锁共享,写锁排他

    表锁:锁住整张表,加锁快,开销小,不会死锁,但是锁粒度大,锁冲突概率高,并发度很低,数据迁移时比较适用。

    表锁测试,开启两个客户端连接,A客户端加表锁,B客户端不加锁

    # A客户端加表锁
    MySQL [test]> lock table account read;
    Query OK, 0 rows affected (0.00 sec)
    # 当前session查询正常
    MySQL [test]> select * from account;
    +----+---------+
    | id | balance |
    +----+---------+
    |  1 |     400 |
    |  2 |     600 |
    |  3 |     200 |
    |  4 |    2000 |
    +----+---------+
    4 rows in set (0.00 sec)
    # 当前session插入数据会执行失败
    MySQL [test]> insert into account(id,balance) values (5,1000);
    ERROR 1099 (HY000): Table 'account' was locked with a READ lock and can't be updated
    # 释放锁的同时,外面的session的阻塞操作会执行成功
    MySQL [test]> unlock tables;
    Query OK, 0 rows affected (0.01 sec)
    # B客户端,在A客户端加表读锁之后,查询正常
    MySQL [test]> select * from account;
    +----+---------+
    | id | balance |
    +----+---------+
    |  1 |     400 |
    |  2 |     600 |
    |  3 |     200 |
    |  4 |    2000 |
    +----+---------+
    4 rows in set (0.00 sec)
    # 插入会被阻塞,直到锁被释放
    MySQL [test]> insert into account(id,balance) values (5,1000);
    Query OK, 1 row affected (1 min 13.08 sec)

    行锁:每次锁一行数据,锁粒度小,加锁慢开销大,发生锁冲突的概率低,有可能出现死锁,并发度高。innodb是支持行锁的,myisam不支持,且innodb支持事务。

    行锁测试:

    A客户端开启事务,对一行数据加锁但是不提交,select * from account where id = 1 for update;

    B客户端更新id = 1的数据会被阻塞,更新其他的数据不会阻塞

    值得注意的是,在innodb中锁是加在索引上,如果对非索引字段更新,行锁可能会变表锁
    A客户端执行:update account set balance = 1000 where name = 'a'; B客户端对该表任一行写操作都会阻塞住。
     
    行锁可以使用lock in share mode(共享锁)和for update(排它锁)
    A客户端select * from account where id = 1 for update; B客户端只能读不能写这行数据,直到A的事务提交。
     
    行锁分析:
    通过检查innodb状态变量来分析系统上的行锁的争夺情况:show status like 'innodb_row_lock%';
    Innodb_row_lock_current_waits: 当前正在等待锁定的数量
    Innodb_row_lock_time: 从系统启动到现在锁定总时间长度
    Innodb_row_lock_time_avg: 每次等待所花平均时间
    Innodb_row_lock_time_max:从系统启动到现在等待最长的一次所花时间
    Innodb_row_lock_waits: 系统启动后到现在总共等待的次数
    当高次数,平均时长也高的时候,就需要分析系统锁竞争了,着手优化。
     
     
    间隙锁(Gap Lock):锁的就是两个值之间的空隙,Mysql默认级别是repeatable-read,在此隔离级别下会生效,间隙锁在某些情况下可以解决幻读问题。
    现在account表中数据如下:
    MySQL [test]> select * from account;
    +----+---------+------+
    | id | balance | name |
    +----+---------+------+
    |  1 |     400 | a    |
    |  2 |     600 | b    |
    |  3 |     200 | c    |
    |  8 |    1000 | h    |
    | 13 |    1000 | m    |
    +----+---------+------+
    那么间隙就有 id 为 (8),(8,13),(13,正无穷) 这三个区间,
    在A客户端下执行update account set balance = 1000 where id > 1 and id < 8; 则其他客户端没法在这个范围所包含的所有行记录(包括间隙行记录)以及行记录所在的间隙里插入或修改任何数据,即id在(1,8]区间都无法修改数据。
     

    死锁:两个session互相持有且等待对方释放锁

    A客户端执行:select * from account where id = 1 for update;
    B客户端执行:select * from account where id = 2 for update;
    A客户端执行:select * from account where id = 2 for update;
    B客户端执行:select * from account where id = 1 for update;
    大多数情况mysql能够自动检测死锁并且做事务回滚,有些情况无法释放,就需要手动处理了:
     -- 查看INFORMATION_SCHEMA系统库锁相关数据表
    -- 查看事务
    select * from INFORMATION_SCHEMA.INNODB_TRX;
    -- 查看锁 select * from INFORMATION_SCHEMA.INNODB_LOCKS;
    -- 查看锁等待 select * from INFORMATION_SCHEMA.INNODB_LOCK_WAITS; -- 释放锁,trx_mysql_thread_id可以从INNODB_TRX表里查看到 kill trx_mysql_thread_id -- 查看锁等待详细信息 show engine innodb statusG;

     关于锁优化:

    1. 合理设计索引,缩小锁范围
    2. 减少范围查询,避免间隙锁
    3. 尽可能通过索引来检索数据,避免行锁升级为表锁
    4. 控制事务大小,需要加锁的sql放后面执行
    5. 尽可能低事务级别隔离
  • 相关阅读:
    Codeforces 1485C Floor and Mod (枚举)
    CodeForces 1195D Submarine in the Rybinsk Sea (算贡献)
    CodeForces 1195C Basketball Exercise (线性DP)
    2021年初寒假训练第24场 B. 庆功会(搜索)
    任务分配(dp)
    开发工具的异常现象
    Telink MESH SDK 如何使用PWM
    Telink BLE MESH PWM波的小结
    [LeetCode] 1586. Binary Search Tree Iterator II
    [LeetCode] 1288. Remove Covered Intervals
  • 原文地址:https://www.cnblogs.com/dlcode/p/14282116.html
Copyright © 2011-2022 走看看