zoukankan      html  css  js  c++  java
  • mysql中的锁

    1.全局锁

    对整个数据库实例进行加锁

    全局读锁: Flush tables with read lock

    加锁之后,其他线程的增删改、ddl等语句将被阻塞

    使用场景:全局逻辑备份

    2.表级锁

    表锁

    lock tables … read/write

    在某个线程A中执行 lock tables t1 read, t2 write; 这个语句,则其他线程写t1、读写t2的语句都会被阻塞。同时,线程A在执行 unlock tables 之前,也只能执行读t1、读写t2的操作。

    元数据锁 MDL(metadata lock)

    不需要显式使用,当对一个表做增删改查时,加MDL读锁,当要做表结构变更时,加MDL写锁。

    • 读锁之间不互斥,因此你可以有多个线程同时对一张表增删改查。

    • 读写锁之间、写锁之间是互斥的,用来保证变更表结构操作的安全性。因此,如果有两个线程要同时给一个表加字段,其中一个要等另一个执行完才能开始执行。

    3.行锁

    由引擎层各个引擎自己实现,本文以InnoDB为例。

    在 Mysql 中,行级锁并不是直接锁记录,而是锁索引。索引分为主键索引和非主键索引两种,如果一条sql 语句操作了主键索引,Mysql 就会锁定这条主键索引;如果一条语句操作了非主键索引,MySQL会先锁定该非主键索引,再锁定相关的主键索引。

    InnoDB 行锁是通过给索引项加锁实现的,如果没有索引,InnoDB 会通过隐藏的聚簇索引来对记录加锁。也就是说:如果不通过索引条件检索数据,那么InnoDB将对表中所有数据加锁,实际效果跟表锁一样。因为没有了索引,找到某一条记录就得扫描全表,要扫描全表,就得锁定表。

    两阶段锁协议

    在InnoDB事务中,行锁是在需要的时候才加上的,但并不是不需要了就立刻释放,而是要等到事务结束时才释放。

    如果事务中需要锁多个行,就需要把最可能造成锁冲突、最可能影响并发度的锁尽量往后放,以此减少锁等待,提升并发度。

    死锁和死锁检测

    死锁:多个线程在互相等待对方的资源释放,进入死锁状态,有两种策略:

    • 一种策略是,直接进入等待,直到超时。这个超时时间可以通过参数 innodb_lock_wait_timeout 来设置。

    • 另一种策略是,发起死锁检测,发现死锁后,主动回滚死锁链条中的某一个事务,让其他事务得以继续执行。将参数 innodb_deadlock_detect 设置为on,表示开启这个逻辑。(常用)

    共享锁和排他锁

    共享锁又称为读锁,简称S锁,对某一资源加共享锁,自身可以读该资源,其他人也可以读该资源(也可以再继续加共享锁,即 共享锁可多个共存),但无法修改。要想修改就必须等所有共享锁都释放完之后。

    select * from table lock in share mode

    排他锁又称为写锁,简称X锁,顾名思义,排他锁就是不能与其他锁并存,如一个事务获取了一个数据行的排他锁,其他事务就不能再获取该行的其他锁,包括共享锁和排他锁,但是获取排他锁的事务是可以对数据就行读取和修改。update,delete,insert都会自动给涉及到的数据加上排他锁,select语句默认不会加任何锁类型,如果加排他锁可以使用select ...for update语句

    select * from table for update
    例:

    表结构和数据:

    create table t_user
    (
        user_name varchar(10) not null comment '名称',
        user_no varchar(10) not null comment '用户编号'
            primary key,
        address varchar(100) not null comment '住址',
        password varchar(10) not null comment '密码',
        age int not null comment '年龄',
        update_time timestamp default CURRENT_TIMESTAMP null on update CURRENT_TIMESTAMP comment '更新时间',
        create_time datetime default CURRENT_TIMESTAMP null comment '创建时间'
    )
    comment '用户表' charset=utf8mb4;
    ​
    create index idx_age
        on t_user (age);
        
    INSERT INTO t_user (user_name, user_no, address, password, age, update_time, create_time) VALUES ('小白', '1', '陕西', '123', 20, '2021-07-15 17:17:26', '2021-07-14 18:09:16');
    INSERT INTO t_user (user_name, user_no, address, password, age, update_time, create_time) VALUES ('小青', '2', '陕西', '123', 18, '2021-07-15 17:17:26', '2021-07-14 18:09:43');
    INSERT INTO t_user (user_name, user_no, address, password, age, update_time, create_time) VALUES ('小蓝', '3', '陕西', '123', 15, '2021-07-15 17:17:26', '2021-07-14 18:09:43');
    INSERT INTO t_user (user_name, user_no, address, password, age, update_time, create_time) VALUES ('小黄', '4', '陕西', '123', 20, '2021-07-15 17:17:26', '2021-07-14 18:09:43');
    ​

    例:主键索引加锁机制:

    T1:

    start transaction ;
    select * from t_user where user_no = '1' for update;

    T2:

    select * from t_user where user_no = '1' lock in share mode ;
    结果:T2被阻塞,T1 commit之后,T2才会执行。因为user_no为唯一索引,T1可以确定需要加锁的主键索引,因此只会锁 user_no = '1' 的一行数据,此时T2要对这行数据加共享锁,获取锁失败,直到T1释放这行数据的排他锁才可以获取并执行。

    例:不走索引加锁机制:

    T1:

    start transaction ;
    select * from t_user where user_name = '小白' for update ;

    T2:

    start transaction ;
    select * from t_user where user_name = '小蓝' lock in share mode ;

    结果:T2被阻塞,T1 commit之后,T2才会执行。因为user_name没有索引,InnoDB只能对表中的所有数据加锁,实际相当于表锁,此时T2要对任何一条数据加共享锁,都会获取失败,直到T1释放锁才会成功并执行。

    注:

    加锁的查询操作”:加过排他锁的数据行在其他事务中是不能修改的,也不能通过for updatelock in share mode的加锁方式查询,但可以直接通过select ...from...查询数据,因为普通查询没有任何锁机制

    间隙锁

    • 在RR隔离级别下才会有

    • 检索条件必须有索引(没有索引的话,mysql会全表扫描,那样会锁定整张表所有的记录,包括不存在的记录,此时其他事务不能修改不能删除不能添加)

    T1:

    start transaction ;
    select * from t_user where age = 18 for update ;

    T2:

    start transaction ;
    update t_user set age = 18 where age = 20;

    结果:T2被阻塞,T1 commit之后,T2才会执行。T1加间隙锁范围为age [15,18] 和 [18,20),T2要修改的是age = 20,修改后的age为18,在T1的加锁范围内,因此需要等待T1释放锁。如果不存在age>18的行,锁的范围为age [15,无限大)

    4.悲观锁和乐观锁(抽象出来的锁,不真实存在)

    悲观锁:通常使用排他锁来实现

    乐观锁: 一般是在该商品表添加version版本字段或者timestamp时间戳字段

    总结:对于以上,可以看得出来乐观锁和悲观锁的区别:

    悲观锁实际使用了排他锁来实现。

    乐观锁更新有可能会失败,甚至是更新几次都失败,这是有风险的。所以如果写入居多,对吞吐要求不高,可使用悲观锁。

     

  • 相关阅读:
    Intellij IDEA + Jrebel
    WebConfig配置详解大全
    .Net 获取前端传递的数据
    js 格式验证大全
    EasyUI DataGrid 时间格式化、字符串长度截取
    SQL取某个字符串最后一次出现的位置后面的字符串方法
    公民身份号码校验码算法(C#版)
    组织机构代码校验码生成算法(C#版)
    MySQL实现根据当前ID读取上一条和下一条记录
    js jquery.pagination.js分页
  • 原文地址:https://www.cnblogs.com/jiezai/p/15067229.html
Copyright © 2011-2022 走看看