MySQL锁

MySQL锁

1. 范围

  • 表级锁:开销小、加锁快,发生锁冲突的概率高、并发度低,不会出现死锁(MyISAM)。
  • 行级锁:开销大、加锁慢,发生锁冲突的概率低,并发度高,会出现死锁(InnoDB)。

2. 类型(InnoDB)

  • 共享锁(S):行级,读取一行;
  • 排他锁(X):行级,更新一行;
  • 意向共享锁(IS):表级,准备加共享锁;
  • 意向排他锁(IX):表级,准备加排他锁;
  • 间隙锁(NK):行级,适用范围条件时,对范围内不存在的记录加锁。一是为了防止幻读,二是为了满足恢复和复制的需要。

MySQL锁

  1. 如果事务1加了意向共享锁(IS),想读取表里的某一行数据,此时事务2就不能加排他锁(X)。
  2. 如果事务1加了意向排他锁(IX),准备写数据,此时事务2就不能加共享锁(S)和排他锁(X)。
  3. 如果事务1加了共享锁(S),准备读取一行数据,此时事务2就不能加意向排他锁(IX)和排他锁(X),即不能写和不能准备写。
  4. 如果事务1加了排他锁(X),那么排他锁和任何情况都是互斥的。

3. 加锁

  • 增加行级锁之前,InnoDB会自动给表加意向锁;

  • 执行DML语句时,InnoDB会自动给数据加排他锁;

  • 执行DQL语句时:

    共享锁(S):SELECT … FROM … WHERE … LOCK IN SHARE MODE;

    排他锁(X):SELECT … FROM … WHERE … FOR UPDATE;

    间隙锁(NK):上述SQL采用范围条件时,InnoDB对不存在的记录自动增加间隙锁。

4. 死锁

  • 场景:

    事务1:UPDATE T SET … WHERE ID = 1; UPDATE T SET … WHERE ID = 2;

    事务2:UPDATE T SET … WHERE ID = 2; UPDATE T SET … WHERE ID = 1;

  • 解决方案:

    一般InnoDB会自动检测到,并使一个事务回滚,另一个事务继续;

    设置超时等待参数:innodb_lock_wait_timeout;

  • 避免死锁:

    不同的业务并发访问多个表时,应约定以相同的顺序来访问这些表;

    以批量的方式处理数据时,应事先对数据排序,保证线程按固定的顺序来处理数据;

    在事务中,如果要更新记录,应直接申请足够级别的锁,即排他锁。

5. 悲观锁(数据库)

数据库加的锁基本都是悲观锁,它天生认为一定会出问题,一定得先加锁,持一个悲观的态度。

如果更新数据频繁,就使用悲观锁。

6. 乐观锁(自定义)

如果更新数据不频繁,查询数据较多,就使用乐观锁。

  1. 版本号机制:

    UPDATE … SET …,VERSION=#{version+1} WHERE … AND VERSION=${version}

  2. CAS算法(Compar and Swap):

    是一种无锁的算法,该算法涉及3个操作数(内存值V、旧值A、新值B),当V等于A时,采用原子方式用B的值更新V的值。该算法通常采用自旋操作,也叫自旋锁。他的缺点是:

    ABA问题:某线程将A改为B,再改回A,则CAS会误认为A没被修改过。
    自旋操作采用循环的方式实现,若加锁时间长,则会给CPU带来巨大的开销。
    CAS只能保证一个共享变量的原子操作。

7. 索引(B+Tree)

  • 数据分块存储,每一块称为一页;
  • 所有的值都是按顺序存储的,并且每一个叶子到根的距离相同;
  • 非叶节点存储数据的边界,叶子节点存储指向数据行的指针;
  • 通过边界缩小数据的范围,从而避免全表扫描,加快了查找的速度。

MySQL锁