侧边栏壁纸
博主头像
孤星博主等级

行动起来,活在当下

  • 累计撰写 15 篇文章
  • 累计创建 10 个标签
  • 累计收到 0 条评论

目 录CONTENT

文章目录

MySQL锁

孤星
2023-09-09 / 0 评论 / 0 点赞 / 104 阅读 / 8668 字


MySQL锁分类

粒度区分

  • 全局锁

  • 表级锁

  • 页级锁

  • 行级锁

模式区分

  • 乐观锁

  • 悲观锁

属性区分

  • 共享锁

  • 排它锁

状态区分

  • 意向共享锁

  • 意向排它锁

算法分类

  • 间隙所

  • 临键锁

  • 记录锁

    mysql_lock1_669bc88ff18fc.jpg

全局锁,表级锁,页级锁,行级锁

全局锁

  1. 概念

    全局锁就是对整个数据库实例加锁。

  2. 应用场景

    全库逻辑备份(mysqldump),也就是把整库每个表都 select 出来存成文本。

  3. 实现方式

    MySql提供的全局加锁方法,命令:Flush tables with read lock(FTWRL)

    当需要让整个库只读状态时,可以使用此命令,其他线程将会阻塞,数据更新(增删改),数据定义(表新建,修改等)和更新事务的语句

  4. 风险点

    如果在主库上备份,那么在备份期间都不能执行更新,业务基本上就能停止。

    如果在从库上备份,那么备份期间从库不能执行主库同步过来的binlog,会导致主从延迟。

  5. 解决办法

    mysqldump使用参数--single-transaction,启动一个事务,确保拿到一致性视图。而由于MVCC的支持,这个过程中数据是可以正常更新的。

表级锁

  1. 概念

    当前操作的整张表加锁,最常使用的 MyISAM 与 InnoDB 都支持表级锁定。

    MySQL 里面表级别的锁有两种:一种是表锁,一种是元数据锁(meta data lock,MDL)。

  2. 实现方式

    表锁:

    lock tables t_user READ; // 只读

    lock tables t_user WRITE; // 可读,可写

    解锁:

    UNLOCK TABLE;

  3. 风险点

    给一个表加字段,或者修改字段,或者加索引,需要扫描全表的数据。在对大表操作的时候,肯定会特别小心,以免对线上服务造成影响。而实际上,即使是小表,操作不慎也会出问题。

  4. 解决办法

    首先我们要解决长事务,事务不提交,就会一直占着 MDL 锁。在 MySQL 的information_schema 库的 innodb_trx 表中,你可以查到当前执行中的事务。如果你要做 DDL 变更的表刚好有长事务在执行,要考虑先暂停 DDL,或者 kill 掉这个长事务。这也是为什么需要在低峰期做ddl 变更。

页级锁

  1. 概念

    页级锁是 MySQL 中锁定粒度介于行级锁和表级锁中间的一种锁。表级锁速度快,但冲突多,行级冲突少,但速度慢。因此,采取了折衷的页级锁,一次锁定相邻的一组记录。BDB 引擎支持页级锁。

行级锁

  1. 概念

    行级锁是粒度最低的锁,发生锁冲突的概率也最低、并发度最高。但是加锁慢、开销大,容易发生死锁现象。

    MySQL中只有InnoDB支持行级锁,行级锁分为共享锁和排他锁。

  2. 实现方式

    在MySQL中,行级锁并不是直接锁记录,而是锁索引。索引分为主键索引和非主键索引两种,如果一条sql语句操作了主键索引,MySQL就会锁定这条主键索引;如果一条语句操作了非主键索引,MySQL会先锁定该非主键索引,再锁定相关的主键索引。在UPDATE、DELETE操作时,MySQL不仅锁定WHERE条件扫描过的所有索引记录,而且会锁定相邻的键值,即所谓的next-key locking。

乐观锁和悲观锁

乐观锁

  1. 概念

    乐观锁是相对悲观锁而言的,乐观锁假设数据一般情况下不会造成冲突,所以在数据进行提交更新的时候,才会正式对数据的冲突与否进行检测,如果发现冲突了,则返回给用户错误的信息,让用户决定如何去做。

  2. 应用场景

    适用于读多写少,因为如果出现大量的写操作,写冲突的可能性就会增大,业务层需要不断重试,会大大降低系统性能。

  3. 实现方式

    一般使用数据版本(Version)记录机制实现,在数据库表中增加一个数字类型的“version”字段来实现。当读取数据时,将version字段的值一同读出,数据每更新一次,对此version值加一。当我们提交更新的时候,判断数据库表对应记录的当前版本信息与第一次取出来的version值进行比对,如果数据库表当前版本号与第一次取出来的version值相等,则予以更新,否则认为是过期数据。

    mysql_lock2_669bc57f66869.jpg

  4. 实战

    订单order表中id,status,version分别代表订单ID,订单状态,版本号。

    1.查询订单信息

    select id,status,versionfrom order where id=#{id};

    2.用户支付成功

    3.修改订单状态

    update set status=支付成功,version=version+1 where id=#{id} and version=#{ version};

悲观锁

  1. 概念

    悲观锁,正如其名,具有强烈的独占和排他特性,每次去拿数据的时候都认为别人会修改,对数据被外界(包括本系统当前的其他事务,以及来自外部系统的事务处理)修改持保守态度,因此,在整个数据处理过程中,将数据处于锁定状态。

  2. 应用场景

    适用于并发量不大、写入操作比较频繁、数据一致性比较高的场景。

  3. 实现方式

    在MySQL中使用悲观锁,必须关闭MySQL的自动提交,set autocommit=0。共享锁和排它锁是悲观锁的不同的实现,它俩都属于悲观锁的范畴。

  4. 实战

    商品goods表中id,name,number分别代表商品ID,商品名称,商品库存。

    1.开启事务并关闭自动提交

    setautocommit=0;

    2.查询商品信息

    selectid,name,number from goods where id=1 for update;

    3.用户下单,生成订单

    4.修改商品库存

    updateset number= number-1 where id=1;

    5.提交事务

    commit;

    说明:select...for update是MySQL提供的实现悲观锁的方式,属于排它锁,在goods表中,id为1的那条数据就被当前事务锁定了,其它的要执行selectid,name,number from goods where id=1for update;的事务必须等本次事务提交之后才能执行。这样我们可以保证当前的数据不会被其它事务修改。

    注意:此时MySQL InnoDB默认行级锁。行级锁都是基于索引的,如果一条SQL语句用不到索引是不会使用行级锁的,会使用表级锁把整张表锁住。

共享锁和排它锁

共享锁(Shared Lock)

  1. 概念

    共享锁,又称之为读锁,简称S锁,当事务A对数据加上读锁后,其他事务只能对该数据加读锁,不能做任何修改操作,也就是不能添加写锁。只有当事务A上的读锁被释放后,其他事务才能对其添加写锁。

  2. 应用场景

    共享锁主要是为了支持并发的读取数据而出现的,读取数据时,不允许其他事务对当前数据进行修改操作,从而避免”不可重读”的问题的出现。

    适合于两张表存在关系时的写操作,拿mysql官方文档的例子来说,一个表是child表,一个是parent表,假设child表的某一列child_id映射到parent表的c_child_id列,那么从业务角度讲,此时我直接insert一条child_id=100记录到child表是存在风险的,因为刚insert的时候可能在parent表里删除了这条c_child_id=100的记录,那么业务数据就存在不一致的风险。正确的方法是再插入时执行select * from parent where c_child_id=100 lock in share mode,锁定了parent表的这条记录,然后执行insert into child(child_id)values (100)就不会存在这种问题了。

  3. 实现方式

    select …lock in share mode

    加锁:select * from T where id=1 lock in share mode

    释放:commit、rollback

排它锁(EXclusive Lock)

  1. 概念

    排它锁,又称之为写锁,简称X锁,当事务对数据加上写锁后,其他事务既不能对该数据添加读写,也不能对该数据添加写锁,写锁与其他锁都是互斥的。只有当前数据写锁被释放后,其他事务才能对其添加写锁或者是读锁。

    MySQL InnoDB引擎默认update,delete,insert都会自动给涉及到的数据加上排他锁,select语句默认不会加任何锁类型。

  2. 应用场景

    写锁主要是为了解决在修改数据时,不允许其他事务对当前数据进行修改和读取操作,从而可以有效避免”脏读”问题的产生。

  3. 实现方式

    select …for update

    自动:DML语句默认加写锁

    加锁(手动):select * from T where id=1 for update

    释放:commit、rollback

意向共享锁和意向排它锁(Intention Lock)

  1. 概念

    意向锁是表锁,为了协调行锁和表锁的关系,支持多粒度(表锁与行锁)的锁并存。

  2. 作用

    当有事务A有行锁时,MySQL会自动为该表添加意向锁,事务B如果想申请整个表的写锁,那么不需要遍历每一行判断是否存在行锁,而直接判断是否存在意向锁,增强性能。

  3. 意向锁的兼容互斥性

    mysql_lock3_669bc581ef7a5.jpg

  4. 实战注意

    这里的排它 / 共享锁指的都是表锁!!!意向锁不会与行级的共享 / 排它锁互斥!!

0

评论区