事务&锁
本文最后更新于 46 天前,如有失效请评论区留言。

事务介绍

什么是事务

事务就相当于一个盛放sql的容器
​事务中的sql要么全部执行成功,要么所有已经修改的操作都混滚到原来的状态,即一条sql也别想执行成功

为何要用事务

目的1:一致性,单个事务执行失败可以回退
目的2:隔离性,多个事务的操作互不影响

事务四大特性

1、原子性(Atomicity):
2、一致性(Consistency):
3、隔离性(Isolation):
4、持久性(Durability):

try:
begin;
update user set balance=900 where name='wsb'; 
update user set balance=1010 where name='egon'; 
uppdate user set balance=1090 where name='ysb'; 
rollback;
commit;

mysql事务的3种运行方式

自动提交(默认)

隐式开启,隐式提交

隐式提交

隐式开启,显式提交

set autocommit=0;

触发条件:

  • 在事务中用了DDL/DCL会自动提交
  • 在事务中手动执行begin会自动提交
  • 执行 lock table /unlock tables 会自动提交
  • 导数据时自动提交(load data infile)
  • select ... for update 加排他锁时自动提交
  • autocommit=1 时自动提交

显式提交

显式开启,显式提交

begin;
rollback; 事务会结束
commit;

事务的保存点(快照)

保存点类似快照,但有点区别。事务结束后(rollback/commit)保存点自动删除。

操作

创建:
savepoint myp1;
savepoint myp2;
使用
rollback to myp1;

事务适用原则

1.保持事务短小
2.尽量避免事务中rollback
3.尽量避免savepoint
4.显式声明打开事务
5.默认情况下,依赖于悲观锁,为吞吐量要求苛刻的事务考虑乐观锁
6.锁的行越少越好,锁的时间越短越好

数据库读现象(读问题)

有什么问题:在并发场景下,数据库读现象是数据不安全的一种体现
读现象:在高并发情况下,即多个并发的事务同时操作一份数据,如果加锁不隔离,会引发一些奇怪的读现象

脏读:1 跟 2 两个事务都还没有commit
问题:1读到了2 的临时数据,也就是脏数据

不可重复读:1 没有commit,2 commit
问题:1前后两次读,读到了2事务提交前和提交后的数据,出现结果不一致。

幻读:1commit, 2 也commit
问题:1 提交后发现数据没有按预期变化,出现了幻觉

数据库的锁机制(面试重点)

锁的作用

锁:一种并发控制机制,通过锁定目标数据,将并发的操作顺序化,保障事务的隔离性、一致性。
以互斥锁为例:让多个并发的任务同一时间只有一个运行(注意这不是串行),牺牲了效率但换来数据安全。

锁的优缺点

优点: 保障并发场景下的数据安全
缺点:降低了效率,带来锁冲突问题

所以我们在使用锁时应该尽可能缩小锁的范围,即锁住的数据越少越好,并发能力越高

锁的分类

​ 按照粒度:行级锁(共享/排他)、表级锁(读/写锁)、页级锁。
​ 按照级别:共享锁、排他锁
​ 按照使用方式分:乐观锁、悲观锁

按粒度分类

行级锁

行级锁:mysql 加锁的最细粒度

  • 但开销大,加锁慢
  • 会死锁
  • 锁冲突概率低,并发高

行级锁分为 行共享读 和 行独占写 (共享锁和排他锁)

select ... lock in share mode; # 共享锁
select ... for update; # 排他锁

互斥锁:独占写(X)

  • 加了互斥锁,该行不能再被添加任何锁
  • 写行为默认自动添加排他锁(insert,update,delete),即多个事务不能同时对一行进行写操作
  • 读操作可以手动加排他锁select ... for update

共享锁:共享读(S)

  • 加了共享锁,该行只能再被加共享锁,或者不加锁。
  • 读行为默认不加锁,我们可以自己加锁

注意:

  • 加锁是为了限制他人,自己可正常读写。
  • 互斥锁限制加任何锁,锁冲突概率低
  • 同一行被加了多个共享锁,会导致全都不能写,变成死锁。(mysql会自动回退冲突事务,解决死锁)

表级锁

表级锁:mysql 加锁的最大粒度

  • 开销小、加锁快
  • 没有死锁
  • 锁冲突概率高,并发最低

表锁分为 表共享读 和 表独占写 (读锁和写锁)

lock table t1 write; # 写锁
lock table t1 read   # 读锁
unlog tables;

页级锁(了解)

页级锁:粒度介于行级锁和表级锁中间(折中)

  • 比表锁慢,比行锁快
  • 比表锁冲突少,比行锁冲突多
  • 比表锁死锁少,比行锁死锁多
  • 比表锁并发高,比行锁并发低

死锁

死锁:两个事务互相锁住对方,导致双阻塞。
恢复:两种情况

  • 一方事务主动退出,释放锁;
  • 一方事务超时,被innodb强行终止事务。

间隙锁

innoDB 的锁机制

innoDB 支持行级锁(默认)/表级锁。

innoDB锁分类(锁模式):共享锁,排他锁、意向共享锁、意向排他锁。
加锁的逻辑:锁兼容, 则可以为事务加锁,锁冲突,则必须等待锁释放。

表锁范围大,无死锁问题,效率低,并发低;
行锁范围小,有死锁问题,效率高,并发高。

innoDB中的锁其实锁的是索引,而非记录本身。如果sql

  • 操作了主键,则锁主键索引
  • 操作了二级索引,则先锁二级索引,再锁主键索引
  • 如果没有索引,则通过隐藏的主键对记录加锁(如果条件没有用到索引,会触发全表扫描,此时将对所有记录加锁,即行锁升级为表锁)

行锁表锁的使用区分

命中聚簇索引时:行锁
命中二级索引时:行锁
未命中索引时:表锁

锁冲突:innoDB是锁的是索引,只要命中相同索引就会发生锁冲突

执行计划:条件用索引 != 一定走索引,要看执行计划是不是用到了索引。

锁索引

innodb存储引擎的锁机制锁的是索引树,因为所有的行都被索引树组织起来了。
命中索引则通过索引锁定行,简称为锁索引;

  • 命中聚集索引:就通过聚集索引本身来锁定行即可;
  • 命中二级索引:先锁二级索引,再锁聚簇索引

如果没有命中索引,会把所有行都锁住,相当于表锁。
无论用什么索引条件,只要最后指向同一行,就会有锁冲突的可能。
命中了索引才会锁行不锁表,而不是说有索引就锁行。

锁三种算法(针对排他锁)

  • record lock:行锁

  • gap lock:间隙锁用于防止幻读,禁止操作间隙(如插入行),innodb无需指定,自动添加

  • next-key lock:结合行锁和间隙锁,锁住行和行前/后的间隙。

  • next-key是以key值为中心的左开右闭区间,可以取到左边的间隙,右边的间隙和右边的key值。

    如果有:16,18,20:则next-key作用在18上,会锁住 (16,18](18,20]

  • 间隙锁锁的是叶子节点的间隙,是按大小排序的,而不是锁表中两行的中间,这个容易有误区。

  • 间隙锁是根据叶子节点已经分配好的,范围查询条件会匹配到间隙锁。

  • 如果命中的索引不是唯一索引,用的是next-key lock,

总结:

  • 如果没有命中索引,无论你筛选出哪一行,都会将整张表锁住
  • 如果命中了非唯一索引,并且是等值查询,会锁行还有间隙
  • 如果命中了非唯一索引,但是是范围查询,会锁行还有间隙
  • 如果命中了唯一索引,并且是等值查询,只会锁定行
  • 如果命中了唯一索引,并且是范围查询,会锁行还有间隙

死锁

发生死锁的情况:两个事务互相锁住了对方要操作的行

高并发场景下,命中了辅助索引,会先锁定辅助索引,再锁定聚集索引。update/insert/delete 操作不加锁住where条件扫描过的所有行,还会锁定相邻的间隙。即next-key lock。

死锁怎么处理:InnoDB一般都可以检测到,并使一个事务释放锁回退,另一个获取锁完成事务。

死锁怎么避免

事务内降低死锁思路:

  • 一次性锁定需要的所有资源,避免被别的事务锁定;
  • 死锁高频区,尝试升级为表锁;

事务间降低死锁思路:

  • 不同程序通过相同的顺序访问表
  • 程序处理表时先对数据排序,保障固定的加锁顺序

什么时候用表锁

1、更新大部分数据时,锁了超过一半数据以上
2、事务涉及到很多表,可以考虑用表锁,但非常影响性能

行锁优化建议

  • 尽量走索引检索减少锁资源使用
  • 合理设计索引,缩小锁范围
  • 减少检索条件,减少锁资源使用
  • 控制事务大小,减少锁资源使用
  • 降低事务隔离级别,较少锁资源使用

乐观锁和悲观锁

悲观锁:认为并发一定会有数据冲突,默认加上悲观锁(排他锁),不允许被其他事务更改。
乐观锁:认为并发不会导致数据冲突,并不会加锁,只是再提交事务时验证数据是否被更改。

  • 通常通过数据版本标识,是人为在表中增加一个字段,主动判断。
  • 每次修改完数据后更新该版本号

互联网高并发架构中,悲观锁很少用了,乐观锁可以支持更高的并发。

事务隔离级别

四种事务隔离级别

特点 脏读 不可重复读 幻读
读未提交 1能读2事务未提交的数据
读已提交 1能读2事务已提交的数据
可重复读 默认级别:看到的已有数据为快照,但能实时看到新增的行
串行化 事务完全隔离,按顺序执行,查询也不准

修改事务隔离级别

事务隔离级别有会话级别和全局级别

select @@global.transaction_isolation
select @@session.transacion_isolation

set session transaction isolaiton level read uncommited;
set global transaction isolaiton level repeatable read;

mvcc 机制(事务隔离的底层原理)

MVCC和锁机制是InnoDB中控制并发的两大机制。逻辑相对。

MVCC 的读操作:

  • 快照读:开启事务就默认做一次快照,事务中读快照数据,不加锁
  • 当前读:读实时数据,加锁

快照读:普通读

当前读:

  • select .. lock in share mode
  • select .. for update
  • insert
  • update
  • delete

为什么写也算读:先读后写,写前必须先读到。

版权声明:除特殊说明,博客文章均为cuckooyang原创,依据CC BY-SA 4.0许可证进行授权,转载请附上出处链接及本声明。 | 博客订阅:RSS | 广告招租:留言板 | 博客VPS |
暂无评论

发送评论 编辑评论


				
|´・ω・)ノ
ヾ(≧∇≦*)ゝ
(☆ω☆)
(╯‵□′)╯︵┴─┴
 ̄﹃ ̄
(/ω\)
∠( ᐛ 」∠)_
(๑•̀ㅁ•́ฅ)
→_→
୧(๑•̀⌄•́๑)૭
٩(ˊᗜˋ*)و
(ノ°ο°)ノ
(´இ皿இ`)
⌇●﹏●⌇
(ฅ´ω`ฅ)
(╯°A°)╯︵○○○
φ( ̄∇ ̄o)
ヾ(´・ ・`。)ノ"
( ง ᵒ̌皿ᵒ̌)ง⁼³₌₃
(ó﹏ò。)
Σ(っ °Д °;)っ
( ,,´・ω・)ノ"(´っω・`。)
╮(╯▽╰)╭
o(*////▽////*)q
>﹏<
( ๑´•ω•) "(ㆆᴗㆆ)
😂
😀
😅
😊
🙂
🙃
😌
😍
😘
😜
😝
😏
😒
🙄
😳
😡
😔
😫
😱
😭
💩
👻
🙌
🖕
👍
👫
👬
👭
🌚
🌝
🙈
💊
😶
🙏
🍦
🍉
😣
Source: github.com/k4yt3x/flowerhd
颜文字
Emoji
小恐龙
花!
上一篇
下一篇