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

    MySQL锁机制

    MySQL都有什么锁

    MySQL有三种锁的级别:页级、表级、行级。

    • 表级锁:开销小,加锁快;不会出现死锁;锁定粒度大,发生锁冲突的概率最高,并发度最低。
    • 行级锁:开销大,加锁慢;会出现死锁;锁定粒度最小,发生锁冲突的概率最低,并发度也最高。
    • 页面锁:开销和加锁时间界于表锁和行锁之间;会出现死锁;锁定粒度界于表锁和行锁之间,并发度一般

    一. get_lock 锁机制的分析

    这个锁是应用程序级别的,在不同的mysql会话之间使用。它只是个名字锁

    • 语句

    开启一个以key为名的锁

    select  GET_LOCK('key', timeOut) from tableName; 	# key:自定义字符串 timeout:超时时间
    

    关闭该Key的锁

    select RELEASE_LOCK('key') from tableName;
    
    • 概述
    1. get_lock会按照key来加锁,别的客户端再以同样的key加锁时就加不了了,处于等待状态。
    2. 当调用release_lock来释放上面加的锁或客户端断线了,上面的锁才会释放,其它的客户端才能进来。
    • 例子
    1. # 加锁
    2. select GET_LOCK('key', 100) from user;
    3. # 解锁
    4. select RELEASE_LOCK('key') from user;

    二.行级锁

    由于InnoDB预设是Row-Level Lock(行级锁),所以只有「明确」的指定主键

    共享锁:

    SELECT * FROM tableName  LOCK IN SHARE MODE;
    

    排它锁:

    SELECT * FROM tableName FOR UPDATE;
    

    三.死锁

    所谓死锁<DeadLock>: 是指两个或两个以上的进程在执行过程中,

    因争夺资源而造成的一种互相等待的现象,若无外力作用,它们都将无法推进下去.

    此时称系统处于死锁状态或系统产生了死锁,这些永远在互相等竺的进程称为死锁进程.

    表级锁不会产生死锁.所以解决死锁主要还是针对于最常用的InnoDB.

    死锁的关键在于:两个(或以上)的Session加锁的顺序不一致。

    那么对应的解决死锁问题的关键就是:让不同的session加锁有次序

    在information_schema下面有三张表:INNODB_TRX、INNODB_LOCKS、INNODB_LOCK_WAITS,通过这三张表,可以更简单地监控当前的事务并分析可能存在的问题。

    1. trx_id:InnoDB存储引擎内部唯一的事物ID
    2. trx_status:当前事务的状态
    3. trx_status:事务的开始时间
    4. trx_requested_lock_id:等待事务的锁ID
    5. trx_wait_started:事务等待的开始时间
    6. trx_weight:事务的权重,反应一个事务修改和锁定的行数,当发现死锁需要回滚时,权重越小的值被回滚
    7. trx_mysql_thread_id:MySQL中的进程ID,与show processlist中的ID值相对应
    8. trx_query:事务运行的SQL语句

    没有提交事务的解决办法

    1.  
    2. 查看当前连接Id(线程Id)
    3. select connection_id();
    4.  
    5. 查询 正在执行(未提交)的事务:
    6. SELECT trx_started,trx_mysql_thread_id FROM information_schema.INNODB_TRX;--这里只查了线程ID和开始时间
    7. SELECT * FROM information_schema.INNODB_TRXG
    8.  
    9. 通过查看events_statements_current表可看到每一个session正在执行的sql,哪怕它依旧执行完成了,只是没有提交。
    10. 这里可以看到执行的SQL语句
    11. select* from performance_schema.events_statements_currentG
    12.  
    13. 查看正在锁的事务
    14. SELECT * FROM INFORMATION_SCHEMA.INNODB_LOCKS;
    15.  
    16. 查看等待锁的事务
    17. SELECT * FROM INFORMATION_SCHEMA.INNODB_LOCK_WAITS;
    18.  
    19. 获取所有处理线程
    20. show processlist;
    21. show full processlist;
    22.  
    23. 杀死指定线程的Id
    24. kill Id
    25.  

    经过我好几天反复的测试发现

    在PHP(测试版本PHP 7.2.9)的pdo中连接MySQL时发生死锁时后面执行的进程返回Null,事务自动回滚, 不会造成事务僵死.

    但是在MySQL-Client(MySQL测试版本5.6.41)中执行,死锁会造成事务僵死.

    比如在PHP中我用PDO操作

    1. 我发送一个请求A首先开启事务,查询锁定到了UID1用户的资产,然后sleep(5),去所动UID2的资产
    2. 我发送一个请求B开启事务,查询锁定了UID2用户的资产,然后sleep(5),去所动UID1的资产
    3. 这时候A正确返回,而B的第二个查询查询则返回了一个null
    4. 然后我继续请求,开启一个事务无查询锁定UID1和UID2的资产,没有任何问题.

    但是在MySQL-Client中,像上面操作会提示Deadlock found when trying to get lock; try restarting transaction

    并且在随后的操作中,在其他窗口包括PHP中取尝试查询锁定该用户,都会一直卡住很久然后提示Deadlock found when trying to get lock; try restarting transaction

    不论隔了多久,再次尝试开启事务锁定UID1和UID2都会这个样子.

    当然以上测试我都没有commit或rollback过.当然这是因为我一直没有关闭事务,一直占用着这两个用户.实际我们用的时候不管成功还是失败我们都要提交一下事务.

    如果一个连接中开启事务切未显示提交或回滚,在不考虑其他因素的前提下,确实应该是只有在连接断开的时候才会回滚。

    如果出现锁等待,则是根据innodb_rollback_on_timeout参数进行下一步动作,如果此参数是off,则会回滚上一条语句,不会提交或回滚,直到显示提交或回滚(事务一直存在,直到连接断开)。

    如果此参数是on,在超时后会回滚整个事务。

    例如:

    将MySQL-client开两个窗口

    执行顺序

    1. 执行下面两个两个部分的begin
    2. 执行下面两个第一部分和第二部分的第一个SQL和第二个SQL(按顺序)即两者的第一个for udpate 和update操作.
    3. 执行SELECT trx_started,trx_mysql_thread_id FROM information_schema.INNODB_TRX;可以看到有两个事务
    4. 执行下面两个第一部分和第二部分的第二个SQL和第二个SQL(按顺序)即两者的第二个for update
    5. 执行SELECT trx_started,trx_mysql_thread_id FROM information_schema.INNODB_TRX;可以看到只有一个事务存活了.
    6. 执行最后一句,commit.
    7. 执行SELECT trx_started,trx_mysql_thread_id FROM information_schema.INNODB_TRX;发现没有任何事务了.

    这时候会发现,MySQL返回错误信息

    Deadlock found when trying to get lock; try restarting transaction
    

    执行第一部分SQL

    1. begin
    2.  
    3. select `bac_balance` from user_finance where uid=1 for update
    4. update `user_finance` set `bac_balance`=`bac_balance`+100 where uid=1;
    5.  
    6. select `bac_balance` from user_finance where uid=2 for update
    7.  
    8. commit
    9.  

    第二部分SQL

    1. begin
    2.  
    3. select `bac_balance` from user_finance where uid=2 for update
    4. update `user_finance` set `bac_balance`=`bac_balance`+100 where uid=2;
    5.  
    6.  
    7. select `bac_balance` from user_finance where uid=1 for update
    8.  
    9. commit

    有人说, 并不会导致死锁等待, 而是innodb会将执行自动回滚.

    InnoDB会在死锁的时候自动回滚影响小的事务(我的测试结果是回滚后面执行的事务).

    但是我们在开发的时候肯定要避免死锁这种情况,

    上面的运行结果 第一部分运行成功.而第二部分提示死锁且事务回滚.

    这是因为,在第一部分的最后一句执行锁UID2的时候会一直等待,等到innodb_lock_wait_timeout设置的时间超时,

    而这时候立刻执行第二部分的最后一句,去锁UID2,会马上返回死锁提示,并且回滚事务.所以第一部分成功了.

    如果成功的这个事务不提交的话,连接关闭的时候会回滚事务.

    工作中遇到的案例

    实际上有上百个撮合引擎, 这里假设有两个A&B错误引擎

    假设平台对UID 1,2,3 用户进行进行撮合.两个撮合引擎同时运行.

    • A撮合引擎: 从UID为1的用户扣除100USDT,分别为UID2和UID3的用户加20USDT.
    • B撮合引擎: 从UID为3的用户扣除10BTC,分别为UID1和UID2的用户各加5BTC.

    理论上,瞬间就会形成死锁(同时开始资产操作是有可能的);

    1. A撮合首先对UID1用户的资产进行select *from finance where uid=1 for update加锁
    2. 同时B撮合也对UID3用户的资产进行select *from finance where uid=3 for update加锁
    3. 这时候A撮合继续开始加锁UID2和UID3,B撮合继续加锁UID1和UID2
    4. 这时候A撮合发现UID3已被锁定,所以会等待B执行完;B撮合发现UID1被锁定,等待A运行完.
    5. 这时候撮合A和撮合B就会进入锁等待状态, 相互等待对方的锁执行完毕.形成死锁

    上面这个情况是比较常见的,当时我们想到了两种方案.

      1. 在指定for update 加锁时,对加锁用户进行排序.

    从大到小或从小到大都可以.但是要所有撮合都保持顺序一致.这样在加锁时就不会出现反方向加锁导致冲突的情况.

    比如,我从1-10加锁,你从10-1加锁,必然会死锁.而我们都从1-10 加锁的话.必然不会有这种情况

      1. 在指定for update 加锁时使用in()将所有要加锁的用户,一次性加锁.

    Select * from finance where id in (1,2,3) for update

    在in里面的列表值mysql是会自动从小到大排序,加锁也是一条条从小到大加的锁

    百度来的案例

    泽锦

    https://www.cnblogs.com/zejin2008/p/5262751.html
    

    案例一:

    1. 需求:将投资的钱拆成几份随机分配给借款人。
    2.  
    3. 起初业务程序思路是这样的:
    4.  
    5. 投资人投资后,将金额随机分为几份,然后随机从借款人表里面选几个,然后通过一条条select for update 去更新借款人表里面的余额等。
    6.  
    7.  
    8.  
    9. 抽象出来就是一个session通过for循环会有几条如下的语句:
    10.  
    11. Select * from xxx where id='随机id' for update
    12.  
    13.  
    14.  
    15. 基本来说,程序开启后不一会就死锁。
    16.  
    17. 这可以是说最经典的死锁情形了。
    18.  
    19.  
    20.  
    21. 例如两个用户同时投资,A用户金额随机分为2份,分给借款人1,2
    22.  
    23. B用户金额随机分为2份,分给借款人2,1
    24.  
    25. 由于加锁的顺序不一样,死锁当然很快就出现了。
    26.  
    27.  
    28.  
    29. 对于这个问题的改进很简单,直接把所有分配到的借款人直接一次锁住就行了。
    30.  
    31. Select * from xxx where id in (xx,xx,xx) for update
    32.  
    33. in里面的列表值mysql是会自动从小到大排序,加锁也是一条条从小到大加的锁
    34.  
    35.  
    36. 例如(以下会话id为主键):
    37.  
    38. Session1:
    39.  
    40. mysql> select * from t3 where id in (8,9) for update;
    41.  
    42. +----+--------+------+---------------------+
    43.  
    44. | id | course | name | ctime |
    45.  
    46. +----+--------+------+---------------------+
    47.  
    48. | 8 | WA | f | 2016-03-02 11:36:30 |
    49.  
    50. | 9 | JX | f | 2016-03-01 11:36:30 |
    51.  
    52. +----+--------+------+---------------------+
    53. rows in set (0.04 sec)
    54.  
    55.  
    56.  
    57.  
    58.  
    59. Session2:
    60.  
    61. select * from t3 where id in (10,8,5) for update;
    62.  
    63. 锁等待中……
    64.  
    65. 其实这个时候id=10这条记录没有被锁住的,但id=5的记录已经被锁住了,锁的等待在id=8的这里。
    66.  
    67.  
    68.  
    69. 不信请看
    70.  
    71. Session3:
    72.  
    73. mysql> select * from t3 where id=5 for update;
    74.  
    75. 锁等待中
    76.  
    77.  
    78.  
    79. Session4:
    80.  
    81. mysql> select * from t3 where id=10 for update;
    82.  
    83. +----+--------+------+---------------------+
    84.  
    85. | id | course | name | ctime |
    86.  
    87. +----+--------+------+---------------------+
    88.  
    89. | 10 | JB | g | 2016-03-10 11:45:05 |
    90.  
    91. +----+--------+------+---------------------+
    92. row in set (0.00 sec)
    93.  
    94.  
    95.  
    96. 在其它session中id=5是加不了锁的,但是id=10是可以加上锁的。

    案例2:

    在开发中,经常会做这类的判断需求:根据字段值查询(有索引),如果不存在,则插入;否则更新。

    1.  
    2. 以id为主键为例,目前还没有id=22的行
    3.  
    4. Session1:
    5.  
    6. select * from t3 where id=22 for update;
    7.  
    8. Empty set (0.00 sec)
    9.  
    10.  
    11.  
    12. session2:
    13.  
    14. select * from t3 where id=23 for update;
    15.  
    16. Empty set (0.00 sec)
    17.  
    18.  
    19.  
    20. Session1:
    21.  
    22. insert into t3 values(22,'ac','a',now());
    23.  
    24. 锁等待中……
    25.  
    26.  
    27.  
    28. Session2:
    29.  
    30. insert into t3 values(23,'bc','b',now());
    31.  
    32. ERROR 1213 (40001): Deadlock found when trying to get lock; try restarting transaction
    33.  
    34.  
    35. 当对存在的行进行锁的时候(主键),mysql就只有行锁。
    36.  
    37. 当对未存在的行进行锁的时候(即使条件为主键),mysql是会锁住一段范围(有gap锁)
    38.  
    39.  
    40.  
    41.  
    42.  
    43. 锁住的范围为:
    44.  
    45. (无穷小或小于表中锁住id的最大值,无穷大或大于表中锁住id的最小值)
    46.  
    47.  
    48.  
    49. 如:如果表中目前有已有的id为(11 , 12)
    50.  
    51. 那么就锁住(12,无穷大)
    52.  
    53. 如果表中目前已有的id为(11 , 30)
    54.  
    55. 那么就锁住(11,30)
    56.  
    57.  
    58.  
    59. 对于这种死锁的解决办法是:
    60.  
    61. insert into t3(xx,xx) on duplicate key update `xx`='XX';
    62.  
    63.  
    64.  
    65. 用mysql特有的语法来解决此问题。因为insert语句对于主键来说,插入的行不管有没有存在,都会只有行锁。
    66.  
    67.  
    68.  
    69.  

    案例3:

    直接上情景:

    1. mysql> select * from t3 where id=9 for update;
    2.  
    3. +----+--------+------+---------------------+
    4.  
    5. | id | course | name | ctime |
    6.  
    7. +----+--------+------+---------------------+
    8.  
    9. | 9 | JX | f | 2016-03-01 11:36:30 |
    10.  
    11. +----+--------+------+---------------------+
    12. row in set (0.00 sec)
    13.  
    14.  
    15.  
    16. Session2:
    17.  
    18. mysql> select * from t3 where id<20 for update;
    19.  
    20. 锁等待中
    21.  
    22.  
    23.  
    24. Session1:
    25.  
    26. mysql> insert into t3 values(7,'ae','a',now());
    27.  
    28. ERROR 1213 (40001): Deadlock found when trying to get lock; try restarting transaction

    转载于:https://my.oschina.net/chinaliuhan/blog/3065290

  • 相关阅读:
    poj 1087 A Plug for UNIX
    poj 1149 : PIGS
    自己制作的我们学校的校园无线网自动登录程序(C#实现)
    poj 1067取石子(威佐夫博奕)
    poj 1088滑雪
    SQL Server 2005 系统数据介绍:sys.dm_exec_requests
    一票难求:我为铁道部献计献策!
    Integration Services 学习(7):包部署
    Integration Services 学习(8):事务
    Integration Services包部署常见问题汇总
  • 原文地址:https://www.cnblogs.com/siweiyidu/p/13156035.html
Copyright © 2011-2022 走看看