事务概念

数据库操作的最小工作单元,是作为单个逻辑工作单元执行的一系列操作,经典的事务场景是转账,A(id为3)给B(id为1)转账:

update user_account set balance = balance - 1000 where user_id = 3;
update user_account set balance = balance + 1000 where user_id = 1;

这两个sql要保证必须同时成功或同时失败,否则数据将出现不一致的情况。

mysql中的事务

查看mysql事务开启状态:

show variables like 'autocommit'

默认是ON。

mysql中开启事务
会话级别
set session autocommit = on/off;  

这个是对当前会话设置自动提交,对其他会话不起作用,如果设置为off,这时候执行完sql后,当前会话都要手动加上commit才能提交事务。

手动开启

手动执行sql:

开启事务:begin / start transaction;
提交或回滚事务:commit / rollback

JDBC编程中:

connection.setAutoCommit(boolean);
connection.commit();

Spring事务AOP编程:

expression=execution(com.gpedu.dao.*.*(..))

mysql中默认是自动提交事务的。也就是在你执行sql语句的时候,它会自动在你sql前边加上beginstart transaction;在后边自动加上commit;从而事务就会自动提交。

当手动使用begin或start transaction时,mysql就会取消自动加事务,例如:

begin;
update user set name="faith" where id=1;

执行后,数据库id为1的记录并不会改变,因为这时候mysql不会自动提交,当手动执行commit之后才会进行提交。

而因为mysql自动提交事务,所以如下两个sql实际上是在两个事务中的:

update user_account set balance = balance - 1000 where userID = 3;
update user_account set balance = balance +1000 where userID = 1;

那么为了保证原子性,我们需要做如下操作:

begin;
update user_account set balance = balance - 1000 where userID = 3;
update user_account set balance = balance +1000 where userID = 1;
commit;

这样就把这两个sql放到一个事务中去了。

在jdbc中将两个sql放到一个事务中,如下:

connection.setAutoCommit(boolean);
update user_account set balance = balance - 1000 where userID = 3;
update user_account set balance = balance +1000 where userID = 1;
connection.commit();

Spring事务AOP编程,实际上也是做了手动开启操作:

expression=execution(com.faith.dao.*.*(..))

这里设置了一个切面,基于这个切面的所有方法都会被拦截,这些方法配置事务的传播性质,拦截之后,spring会在方法之前加一个切面,设置会话手动提交,例如:

connection.setAutoCommit(boolean);

然后在方法之后加一个切面,设置会话提交,例如:

connection.commit();

当catch到异常的时候,就执行

connection.rollback();

事务的特性

原子性(Atomicity)

事务是最小的工作单元,事务中的sql要么一起提交成功,要么全部失败回滚。

一致性(Consistency)

事务中操作的数据及状态改变是一致的,更新的数据必须完全符合预设的规则,不会因为事务或系统等原因导致状态的不一致。

隔离性(Isolation)

一个事务所操作的数据在提交之前,对其他事务的可见性设定。如果事务并发且相互不隔离,会导致脏读、不可重复读、幻读等系列问题。

持久性(Durability)

事务所做的修改会永久保存,不会因为系统意外导致数据的丢失。

原子性和一致性是两个不同的概念。对于原子性来说,如下的两条语句:

update user_account set balance = balance - 1000 where userID = 3;
update user_account set balance = balance +10000 where userID = 1;

只要同时执行成功或失败就是符合原子性的。

而对于一致性来说是不成立的,因为实际给1转账1000,但是1的账户上多了10000,不符合转账的规则,导致了数据的不一致性。

事务的隔离级别

SQL92 ANSI/ISO标准

http://www.contrib.andrew.cmu.edu/~shadow/sql/sql1992.txt
Read Uncommitted(读未提交)--未解决任何并发问题,可以读到其他事务未提交的数据,会导致脏读(dirty read)。Read Committed(读已提交)--解决脏读问题,一个事务开始之后,只能看到己提交的事务所做的修改,但是未解决不可重复读(nonrepeatable read)。Repeatable Read (可重复读)--解决不可重复读问题,在同一个事务中多次读取同样的数据结果是一样的,这种隔离级别未定义解决幻读的问题。Serializable(串行化)--解决所有问题,最高的隔离级别,通过强制事务的串行执行。

innodb对隔离级别的支持

隔离级别 脏读 不可重复读 幻读
读未提交 可能 可能 可能
读已提交 不可能 不可能 可能
可重复读 不可能 不可能 ==不可能==
串行化 不可能 不可能 不可能

在92标准中,可重复读级别可以不解决幻读问题,但是innodb存储引擎的可重复读解决了幻读问题。

锁用来管理不同事务对共享资源的并发访问。

表锁与行锁:

锁定粒度:表锁 > 行锁表锁直接锁定表,行锁只锁定一行。加锁效率:表锁 > 行锁直接对表加锁块,而行锁需要在表中找到指定的行记录。冲突概率:表锁 > 行锁,表锁锁定的记录更多,更容易产生冲突。并发性能:表锁 < 行锁

InnoDB存储引擎只有行锁,没有表锁,但它也能实现表锁的效果,因为它的表锁是把表中所有的行都锁一遍,就成了表锁。这个只是实现了表锁的效果,但是和真正的表锁效率相比要低下很多。

innodb的锁类型

InnoDB默认select语句不加任何锁类型,但是delete、update、insert 默认会加上X锁。

innodb共有八种锁:

共享锁(行锁):Shared Locks排它锁(行锁):Exclusive Locks意向锁共享锁(表锁):Intention Shared Locks意向锁排它锁(表锁):Intention Exclusive Locks自增锁:AUTO-INC Locks关于行锁的锁:记录锁 Record Locks间隙锁 Gap Locks临键锁 Next-key Locks
共享锁

又称为读锁,简称S锁,多个事务对于同一数据可以共享一把共享锁,持有共享锁的事务都能访问到数据,但是只能读不能修改。

共享锁示例:

begin;
select * from user WHERE id=1 LOCK IN SHARE MODE;

不执行commit操作,这时候在另一个窗口执行:

select * from user WHERE id=1;
update user set name='2' where id=1;

select操作可以查到数据,但是update会被阻塞,直到最开始申请到共享锁的事务执行commit或rollback来释放享锁,之后update才会继续执行。

排他锁

又称为写锁,简称X锁,排他锁不能与其他锁并存,如一个事务获取了某条记录的排他锁,其他事务就不能再获取该行的锁(共享锁、排他锁),即不能读不能写,只有持有排他锁的事务才可以对记录进行读取和修改(其他事务要读取数据可来自于快照)。

排它锁示例:

begin;
update user set name='2' where id=1;

不执行commit操作,这时候在另一个窗口执行:

select * from user WHERE id=1 LOCK IN SHARE MODE;
update user set name='3' where id=1;

这两条操作都会被阻塞,直至持有排它锁的事务commit或rollback之后才能继续执行。

意向共享锁(IS)

表示事务准备给数据行加入共享锁,即一个数据行加共享锁前必须先取得该表的IS锁,意向共享锁之间是可以相互兼容的。

意向排它锁(IX)

表示事务准备给数据行加入排他锁,即一个数据行加排他锁前必须先取得该表的IX锁,意向排它锁之间也是可以相互兼容的。

意向锁(IS 、IX)是InnoDB在数据操作之前自动加的,不需要用户干预,我们编码时不需要对意向锁进行特殊处理。

意向锁是表级锁,之间是相互兼容的,也就是说多个持有锁的线程,可以同时持有意向锁。比如update id=1,update id=2,他们可以同时持有意向锁。

意向锁存在的意义

只有当事务想去进行锁表时,意向锁才会发挥作用,事务会先判断意向锁是否存在,如果存在,说明此时肯定有行锁存在,这时候不能进行表锁,则可快速返回该表不能启用表锁,省略了进入底层扫描全表的数据。

自增锁

针对自增列自增长的一个特殊的表级别锁,可以使用如下语句查看自增锁的模式:

show variables like 'innodb_autoinc_lock_mode';

此参数可取的值有三个:0、1、2,默认取值1。

取值0:传统方式,串行自增,并且是连续的。这种模式下需要语句执行完成才释放自增锁,所以性能最低。例如:1、2、3、4、5、6,没有人为删除情况下,表中id字段一定是连续的。

取值1:连续方式,自增的,并且是连续的。当语句申请到自增锁就释放自增锁,自增锁就可以给其它语句使用,性能会好很多。但因为不会等待语句事务执行完毕就释放了自增锁,可能该事务回滚了,所以id可能会出现断续的情况,例如:1、2、6,8,10

2:交错方式,多语句插入数据时,有可能自增的序列号和执行先后顺不一致,并且中间可能有断裂。一次分配一批自增值,然后下个语句就再进行分配一批自增值,阻塞很小,性能很高。例如:1、2、3、6、5。

设置为2时,需要确认表是否需要连续的自增值,如果需要,就不要使用这个值。

临键锁(Next-key locks)

当sql执行按照索引进行检索,查询条件为范围查找(between and、<、>等)并且有数据命中,则此时SQL语句加上的锁为Next-key locks,锁住索引范围为记录的本区间 + 本区间下一个区间(左开右闭)。

mysql会对记录自动划分出区间,如下:

如果为1,2,4,7,10,区间则为(-&,1],(1,2],(2,4],(4,7],(7,10],(10,+&)。划分区间是依据B+树节点之间的间隙来划分的,1和2之间没有间隙,但是在树中,是两个不同的节点,它们之间是有间隙的。

update user set name=1 where id>5 and id<10

上面的sql选中的记录是id=7,这时候锁住的区间是(4,7],(7,10],(4,7]是本区间,而(7,10]是本区间的下一个区间。

锁住本区间和相邻区间就是防止幻读,例如这里的>5和<9条件,肯定是要锁定(4,7],(7,10]区间才能实现的,也就是要锁住本区间和相邻区间。

锁住区间是因为B+树的特性,如果把这个例子中的id换成age就更好理解了。

因为innodb默认隔离级别是可重复读,而前边说了innodb的可重复读还捎带解决了幻读问题,而幻读问题就是临键锁配合mvcc一起解决的。

间隙锁(Gap locks)

当sql执行按照索引进行检索,查询条件为范围查找并且查询的数据不存在,这时SQL语句加上的锁即为Gap locks,锁住索引不存在的区间(左开右开)。

只在可重复读隔离级别存在是因为innodb的可重复读解决了幻读问题。

Record locks

当sql执行按照唯一性(Primary key、Unique key)索引进行检索,查询条件为精准等值匹
配且查询的数据是存在,这时SQL语句加上的锁即为记录锁Record locks,这种情况只针对唯一索引,所以对应的是const或equ_ref级别的查询。

innodb的行锁锁住了哪些内容

得出结论之前先做个测试。例如user表中,id,name,age,create_time字段,id和name有索引,create_time没有加索引。

测试1
begin;
select * from user where id=1;

不执行commit或rollback。然后在其他线程执行:

select * from user where id=1; 阻塞
select * from user where id=2; 非阻塞
select * from user where id=3; 非阻塞
测试2
begin;
select * from user where name=‘1’;

不执行commit或rollback。然后在其他线程执行:

select * from user where name=‘1’; 阻塞
select * from user where name=‘2’; 非阻塞
select * from user where name=‘3’; 非阻塞
测试3
begin;
select * from user where create_time=1;

不执行commit或rollback。然后在其他线程执行:

select * from user where create_time=1; 阻塞
select * from user where create_time=2; 阻塞
select * from user where create_time=3; 阻塞

结论:InnoDB的行锁是通过给索引树中的索引项加锁来实现的,如果是聚集索引,那么直接锁住聚集索引的索引项,如果是非聚集索引,那么会锁住当前索引的索引项,以及对应的聚集索引中的索引项。

也就是说对于非聚集索引,会在两棵索引树中分别上锁。只有通过索引条件进行数据检索,InnoDB才使用行级锁,否则,InnoDB将使用表锁(锁住索引的所有记录)。

表锁是非常耗费性能的,所以要避免表锁。这种特性可以给平时写sql带来一些启发,例如:

delete from user where create_time;导致表锁
delete from user where id=1;行锁;

写删改的sql时候,要考虑where条件是否命中了索引,要避免表锁出现,删一条记录,却导致整张表被锁住了,这是一件很郁闷的事。

锁如何解决并发问题

脏读

加上X锁可以解决脏读。

不可重复读

模拟不可重复读:

select name from user where id=1;// name='1'
update user set name='2' where id=1; // 执行成功
select name from user where id=1;// name='2'

上面两个select的结果不同,导致了不可重复读。解决方法是给这两个select加上S锁:

select name from user where id=1 LOCK IN SHARE MODE;// name='1'
update user set name='2' where id=1; // 阻塞
select name from user where id=1 LOCK IN SHARE MODE;// name='1'

这样在当前事务执行完毕之前,不可能被其他事务更改值,从而解决了不可重复读的问题。

幻读

加临键锁可以解决幻读。

死锁

多个并发事务每个事务都持有锁,每个事务都需要再继续持有其他事务持有的锁,但谁都不释放自己手中的锁,产生锁的循环等待,这就形成了死锁。

死锁的避免
类似的业务逻辑以固定的顺序访问表和行。大事务拆小。大事务更倾向于死锁,如果业务允许,将大事务拆小。在同一个事务中,尽可能做到一次锁定所需要的所有资源,减少死锁概率。降低隔离级别,如果业务允许,将隔离级别调低也是较好的选择为表添加合理的索引。可以看到如果不走索引将会为表的每一行记录添加上锁(或者说是表锁),表锁造成的锁冲突比行锁要严重的多

Innodb事务和锁相关推荐

  1. mysql innodb禁用事务_MySQL InnoDB事务中锁问题(三)

    试想,事务如果都是串行的,那么就不需要锁了,但是性能肯定没法接受.加锁只是为了提高事务并行度,并且解决并发事务执行过程中引起的脏写.脏读.不可重复读.幻读这些问题的一种解决方案(MVCC算是一种解决脏 ...

  2. InnoDB 事务/锁/多版本分析?你了解多少?

    目录 • InnoDB事务 – 事务结构/功能 – XA事务/Group Commit – mini-transaction• InnoDB锁 – 锁结构/类型/功能 – 锁等待/死锁检测 – 自增序 ...

  3. mysql 事务 innodb 锁表_MySQL性能优化之Innodb事务系统,值得收藏

    概述 今天主要分享下Innodb事务系统的一些优化相关,以下基于mysql 5.7. Innodb中的事务.视图.多版本 1.事务 在Innodb中,每次开启一个事务时,都会为该session分配一个 ...

  4. 同时更改一条数据_数据库中的引擎、事务、锁、MVCC(二)

    二.事务 介绍锁之前,咱们先介绍一下 什么叫做事务. 事务就是一组对数据库的一系列的操作,要么同时成功,要么同时失败. 1.事务的特性(ACID): 原子性:事务是整个操作,不可分割,要么都成功,要么 ...

  5. mySQL教程 第10章 事务和锁

    第10章 事务和锁 数据库事务(Database Transaction) ,是指作为单个逻辑工作单元执行的一系列操作. 事务处理可以确保除非事务性单元内的所有操作都成功完成,否则不会永久更新面向数据 ...

  6. mysql 事务涉及锁吗_MySQL-锁机制和事务

    测试: 4.5.innodb_locks_unsafe_for_binlog innodb_locks_unsafe_for_binlog参数用来控制innodb中是否允许间隔锁,默认是OFF代表允许 ...

  7. Mysql事务和锁原理

    一.mysql事务:MySql开启事务:begin. 1.并发事务产生的读问题  1)更新丢失:后提交事务会覆盖先提交的事务.乐观锁可解决. 2)脏读:A读到B未提交update数据.不符合一致性 3 ...

  8. mysql如何实现读提交锁_MySQL学习笔记(二)—MySQL事务及锁详解

    一.事务 数组库的一组操作,要么全部成功,要么全部失败 举例:银行转账 A账户向B账户转100 A账户余额扣去100 B账户余额增加100 上述两个操作要么全部成功,要么全部失败,部分成功或失败,数据 ...

  9. sybase 事务插入时不可查询_InnoDB事务与锁

    事务 可以理解为数据库执行的一个最基础的单位,其包含有限的操作命令(crud). 事务的属性(ACID):事务必须满足四个属性,原子性(atomicity).一致性(consistency).隔离性( ...

最新文章

  1. date 的基本使用 suse
  2. 在Intellij idea 中YAML文件出现代码提示
  3. birt java api_「Birt」birt api生成报表 | 学步园
  4. Fiddler笔记(3)接口测试
  5. IIS添加对ashx文件的支持
  6. python输出名字和字数_Python字数和排名
  7. 推荐视频反馈系统设计
  8. 今秋如何让自己的C币也来个大丰收
  9. 如何从被领导到领导别人
  10. .NET Core 分析程序集更优方法,超越ReflectionOnlyLoad
  11. php 爬虫登录网站,Python爬虫模拟登录带验证码网站
  12. Python编程快速上手-让繁琐工作自动化 — 读书与代码笔记
  13. 28个在线游戏编程学习网站
  14. 通达信交易接口以什么形式执行下单的?
  15. linux系统u盘安装教程
  16. Contrastive Loss 对比损失函数
  17. Android中高德地图与百度地图坐标转换
  18. 用 dism 合并与删除 wim 映像
  19. 知识图谱系列(二):构建一个医疗知识图谱
  20. Vue系列之—文件目录详解

热门文章

  1. getReadableDatabase() 和 getWritableDatabase()的区别
  2. mysql 5.7主从延迟 相关参数配置
  3. android 决TextView中MaxLines与ellipsize=end冲突问题
  4. Launcher(待完善...)
  5. 你被人工智能给忽悠了吗?原来我们曾经认为的都是假的
  6. 学习linux之用户-文件-权限操作
  7. 1022: [SHOI2008]小约翰的游戏John【Nim博弈,新生必做的水题】
  8. 2013计算机视觉代码合集一
  9. Winfrom窗体间传值
  10. Linux date命令的用法