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. 尽可能低事务级别隔离
  • 相关阅读:
    一天一个 Linux 命令(44):ifstat 命令
    Java集合框架示意图
    Java中String类常见问题汇总
    一天一个 Linux 命令(43):netstat 命令
    Windows系统下,如何设置maven字符编码
    Java文件操作编程
    Java 注解(Annotation)
    Linux Centos7.4 更新Java jdk版本
    Java基础(6)Java数据类型扩展
    Windows系统下Elasticsearch7.15.2单服务器配置多节点
  • 原文地址:https://www.cnblogs.com/dlcode/p/14282116.html
Copyright © 2011-2022 走看看