Mysql事务和锁

事务ACID特性

事务由一组SQL组成,其具有以下四个特性:

  • 原子性(Atomicity):事务是一个原子操作,不可分割,要么全部执行完成,要么全部不执行;
  • 一致性(Consistent):在事务开始和结束时,数据都必须保持一致;
  • 隔离性(Isolation):事务不会收到外部操作影响,事务的中间状态对外部是不可见的,反之亦然;
  • 持久性(Durable):事务提交之后,对于数据的影响是永久性的,系统出现故障也能保持;

并发事务带来的问题

更新丢失(Lost Update)或脏写

多个事务去更新同一行数据,最后的更新覆盖了其他事务的更新,导致其他事务的更新丢失。

脏读(Dirty Read)

事务A读取到事务B已经修改但还未提交的数据。
如果事务B回滚,那事务A读取到的数据就无效了,不符合事务一致性要求。

不可重复度(NonRepeatable Read)

在事务A中,在不同时刻相同的SQL查询语句执行结果不一致,不符合隔离性

幻读(Phantom Read)

在事务A中,读到了事务B提交的新增数据,不符合隔离性

事务隔离级别

数据库提供事务隔离级别来解决并发事务带来的问题:

事务隔离级别脏读(Dirty Read)不可重复读(NonRepeatable Read)幻读(Phantom Read)
读未提交(Read Uncommitted)可能可能可能
读已提交(Read Committed)不可能可能可能
可重复读(Repeatable Read)不可能不可能可能
串行化(Serializable)不可能不可能不可能

Mysql默认事务隔离级别可重复读,Oracle默认读已提交。
Mysql设置和查看事务隔离级别

--Mysql设置事务隔离级别
set tx_isolation='Repeatable-Read';
--查看当前事务隔离级别
show variables like 'tx_isolation';

如果Spring设置了事务隔离级别,就使用Spring的,没有就使用Mysql设置的事务隔离级别。

和其他共享数据一样,Mysql中数据也需要锁机制来保证数据并发访问的一致性和有效性。
锁冲突也是影响数据库并发访问性能的重要因素。

锁分类

  • 性能维度
    • 乐观锁(数据版本号CAS对比实现)

      乐观锁认为访问数据时不会发生锁冲突,是乐观的,乐观锁会在你对数据更新时,会对比该数据之前的版本号,如果一致就修改,不一致就获取最新的值进行修改。(CAS)

    • 悲观锁

      悲观锁总是认为一定会发生锁冲突,是悲观的,所以在访问数据时先加锁。

  • 数据操作类型维度
    • 读锁(悲观锁)
    • 写锁(悲观锁)
  • 数据操作粒度维度
    • 表锁

      对整个表加锁

    • 行锁

      对当前行加锁

  • 锁的性质
    • 共享锁(Shared,S锁)

      读锁的性质是共享锁,加了读锁的数据,其他读操作也可以对这份数据进行读取。

    • 排它锁(exclusive,X锁)

      写锁的性质是排它锁,加了写锁的数据,会阻断其他读锁和写锁

表锁

对操作的整个表进行加锁,

  • 优点
    • 开销小
    • 加锁快
    • 不会出现死锁
  • 缺点
    • 锁粒度大,发生锁冲突概率高
    • 并发低
  • 应用场景
    • 整张表的数据迁移

手动加表锁

lock table <tableName> read|write, <tableName2> read|write

查看该表加过的锁

show open tables;

删除表锁

unlock tables;

行锁

对操作的数据行row加锁

  • 缺点
    • 开销大
    • 加锁慢
    • 可能会出现死锁
  • 优点
    • 锁粒度小,发生锁冲突概率低
    • 并发高

InnoDB与MyISAM最大区别

  • InnoDB支持事务

  • InnoDB支持行锁,MyISAM只支持表锁

  • MyISAM执行Select语句前,会对查询的表加读锁,执行Update、Insert和Delete操作会对表加写锁

  • InnoDB执行Select前,不会加锁(非Serializable串行隔离级别),执行Update、Insert和Delete时会对行加写锁

可重复读(Repeatable-Read)

在可重复读隔离级别下,Mysql使用了MVCC(Multi-Version Concurrency Control)多版本并发控制机制,

  • Select操作不会更新版本号,读取当前数据的历史版本,快照读
  • Insert、Update和Delete会更新版本号,读取当前数据的最新版本,当前读

间隙锁(Gap Lock)

间隙锁对操作的数据两个值之间的间隙进行加锁,在可重复读隔离级别下才会生效

Mysql在可重复读隔离级别下,利用间隙锁在某些情况下可以解决幻读问题。

比如user表中有以下这些数据:

id	name	age
1	小明	10
2	小张	11
3	小王	12
12	小红	13
35	小爱	14

当你执行update user set age = 18 where 5 < id and id < 16;,会对5-16范围内包含的所有行记录以及行记录所在的间隙加写锁。上面user表id行存在的间隙为(3, 12),(12, 35),(35, 正无穷大),也就是会对(3, 35]加写锁,最后的35是包括在内的。

临键锁(Next-Key Locks)

临键锁是行锁与间隙锁的组合。上面那个(3, 35]整个区间就可以叫做临键锁。

行锁升级为表锁

在RR(Repeatable Read)级别下,以非索引列为where条件进行更新,行锁会变成表锁。
InnoDB的行锁是针对索引加的锁,不是对记录,如果该索引失效,都会从行锁升级为表锁。

锁定某一行还可以用lock in share mode(共享锁) 和for update(排它锁)。
比如

-- 在RR隔离级别,在session1中执行,name不是索引字段,会对user表加共享锁,读锁
select * from user where name = '小红' lock in share mode;

-- 在session2中执行下面的sql不会阻塞,因为session1对user表加了共享锁,session2中的也是共享锁,读锁。
select * from user where name = '小明' lock in share mode;

-- 在session2中执行这条语句会被阻塞,因为for update是排它锁
select * from user where name = '小明' for update;

-- 执行这条更新语句也会阻塞,update会加写锁。
update user set age = 101 where name = '小明';

Mysql锁分析

可以通过以下语句分析系统行锁竞争情况

show status like 'innodb_row_lock%';
-- 结果
variable_name					value
Innodb_row_lock_current_waits	0
Innodb_row_lock_time			515817
Innodb_row_lock_time_avg		14328
Innodb_row_lock_time_max		51110
Innodb_row_lock_waits			36

各个variable_name说明:

  • Innodb_row_lock_current_waits:当前正在等待锁的数量;
  • Innodb_row_lock_time:从系统启动到现在等待锁总时间;
  • Innodb_row_lock_time_avg:每次等待锁需要的平均时间;
  • Innodb_row_lock_time_max:从系统启动到现在等待锁最大的一次时间;
  • Innodb_row_lock_waits:从系统启动到现在等待锁的总次数;

优化建议

特别注意这三个状态量的值:
Innodb_row_lock_time_avg (等待锁平均时长)
Innodb_row_lock_waits (等待锁总次数)
Innodb_row_lock_time(等待锁总时长)
尤其是等待锁总次数很高,而且每次等待锁平均时间也不小的时候,就要分析系统中为什么会出现在这么多锁等待了。

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 status\G; 

死锁

死锁就是两个请求互相持有对方等待的锁。
例如依次执行下列语句就会出现死锁:

-- 1.session1执行
select * from user where id = 1 for update;
-- 2.session2执行
select * from user where id = 2 for update;
-- 3.session1执行
select * from user where id = 2 for update;
-- 4.session2执行
select * from user where id = 1 for update;

一般情况下,Mysql可以自动检测死锁兵回滚产生死锁的那个事务,但有些情况下Mysql没办法自动检测死锁。

-- Mysql检测到死锁,回滚事务
[SQL]select * from user where id = '1' for update;
[Err] 1213 - Deadlock found when trying to get lock; try restarting transaction

锁优化

  • 尽量让数据检索走索引,避免不能走索引的情况下行锁升级表锁;
  • 合理设计索引,尽量减小锁的范围;
  • 尽量减小检索条件的范围,避免间隙锁;
  • 尽量控制事务的大小,减少锁定资源的数量和时间,涉及事务加锁的sql尽量放在事务最后执行,因为Mysql只有在需要加锁的时候才会真正开启事务, 生成事务id,刚start transaction时会生成一个临时事务id;
  • 尽可能低级别的事务隔离,比如互联网公司使用Mysql时,一般都会使用读已提交事务隔离(Read-Committed)级别而不是默认的可重复读(Repeatable-Read);
课程简介: 课程总计41课时,从什么是事务讲起,直到分布式事务解决方案,很的0基础基础与提升系列课程。对于难以理解的知识点,全部用画图+实战的方式讲解。 第一部分:彻底明白事务的四个特性:原子性、一致性、隔离性、持久性,用场景和事例来讲解。 第二部分:实战讲数据库事务的6中并发异常:回滚丢失、覆盖丢失、脏读、幻读、不可重复读、MVCC精讲。 第三部分:彻底搞清楚4种事务隔离级别:READ_UNCOMMITTED 读未提交隔离级别、READ_COMMITTED 读已提交隔离级别、REPEATABLE_READ 可重复度隔离级别、SERIALIZABLE 序列化隔离级别 第四部分:彻底搞清楚MySQL的各种:行、表、共享、排它、Next-Key、间隙、X、S、IS、IX、死、索引与、意向等。 第五部分:彻底搞清楚Spring事务的7种传播级别的原理和使用:PROPAGATION_REQUIRED、PROPAGATION_SUPPORTS、PROPAGATION_MANDATORY、PROPAGATION_REQUIRES_NEW、PROPAGATION_NOT_SUPPORTED、PROPAGATION_NEVER、PROPAGATION_NESTED分布式事务的理论基础:RPC定理、BASE理论、XA协议都是什么,原理是什么,有什么关联关系 第六部分:分布式事务的5种解决方案原理和优缺点:2PC两阶段提交法、3PC三阶段提交法、TCC事务补偿、异步确保策略、最大努力通知策略 第七部分:阿里巴巴分布式事务框架Seata:历经多年双十一,微服务分布式事务框架,用一个Nacos+Spring Cloud+Seta+MySql的微服务项目,实战讲解阿里的分布式事务技术,深入理解和学习Seata的AT模式、TCC模式、SAGA模式。 课程资料: 课程附带配套2个项目源码72页高清PDF课件一份阿里巴巴seata-1.1.0源码一份阿里巴巴seata-server安装包一份
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

cuidianjay

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值