目录
1、四大特性
2、事务引发的问题
3、事务控制演进
3.1、排队
3.2、排它锁
3.3、读写锁
3.4、MVCC
4、事务的隔离级别
4.1、四种隔离级别
4.2、事务隔离级别和锁的关系
4.3、MySQL隔离级别控制
5、锁机制和实战
5.1、锁分类
5.1.1、按操作粒度分类
5.1.2、按操作类型分类
5.1.3、按操作性能分类
5.2、行锁原理(InnoDb)
5.2.1、常见SQL加锁
5.2.2、举例
5.3、悲观锁
5.3.1、表级锁
5.3.2、共享锁(行级锁,读锁)
5.3.3、排它锁(行级锁,写锁)
5.4、乐观锁
5.4.1、实现原理
5.5、死锁与解决方案
5.5.1、表锁死锁
5.5.2、行锁死锁
5.5.3、共享锁转换为排它锁
5.5.4、锁排查
逻辑上的一组操作,要么都执行,要么都不执行。原子性、一致性、隔离性、持久性。
一个事务读取了另一个事务尚未提交的数据。
初值age=10,事务A修改age=20,事务B读取age=20,事务A回滚age=10。
两个事务同时更新一行数据,最后一个事务的更新会覆盖前一个事务的更新。
事务A更新age=50 ->> 事务B更新age=30。事务A的更新被覆盖。
事务多次读取同一个数据,数据总量不一致。(前后多次读取,数据总量不一致)
事务A查询数据总量100条,事务B新增数据100条,数据A查询总量为200条。
事务多次读取同一条数据,数据内容不一样。(前后多次读取,数据内容不一样)
事务A读age=10,事务B修改age=20,事务A读age=20。
不可重复读的重点是修改,幻读的重点是新增或删除。
排队->排它锁->读写锁->MVCC。
所有事务操作依次排队处理。串行化,效率低。
互斥锁,如果事务涉及到相同数据,先进入的事务给数据加锁,其他事务被阻塞。
读读之间不加锁,读写、写读、写写之间加排它锁。
MVCC多版本并发控制_零点冰.的博客-CSDN博客
Read uncommitted(读未提交)
一个事务可以读取另一个未提交事务的数据。
脏读、不可重复读、幻读。
Read committed(读已提交)
若存在事务A进行更新操作时,读事务B会等到事务A提交后才能读取数据。
不可重复读、幻读。
Repeatable read(重复读)
在开始读取数据(开启事务)后,不再允许修改(update)操作,但不能阻止insert操作。
幻读。
Serializable(串行化)
事务串行化执行。
Mysql的InnoDB引擎使用的是Repeatable read(重复读)
SQL Server和Oracle默认隔离级别为Read Committed(读已提交)
show variables like 'tx_isolation';或select @@tx_isolation;
行锁又分为共享锁和排他锁。
行锁->记录锁 + 间隙锁 + (记录锁+范围锁)
InnoDB行锁是通过对索引数据页上的记录加锁实现的,主要实现算法有 3 种:Record Lock、Gap Lock 和 Next-key Lock。
锁的是索引叶子节点的next指针,或者说间隙锁是一个在索引记录之间的间隙上的锁;
解决了mysql重复读级别下的幻读问题;
在RR隔离级别,InnoDB对于记录加锁行为都是先采用Next-Key Lock,但是当SQL操作含有唯一索引时,Innodb会对Next-Key Lock进行优化,降级为RecordLock,仅锁住索引本身而非范围。
(RR隔离级别,优先加记录锁和间隙锁,当SQL有唯一索引时,才降级为记录锁)。
普通select查询不加锁,insert语句加记录锁,其余SQL优先使用Next-key Lock锁,有唯一索引时降级为记录锁。
以“update t1 set name=‘XX’ where id=10”操作为例,举例子分析下 InnoDB 对不同索引的加锁行为,以RR隔离级别为例。
加锁行为:仅在id=10的主键索引记录上加写锁
加锁行为:先在唯一索引id上加X锁,然后在id=10的主键索引记录上加写锁。
加锁行为:对满足id=10条件的记录和主键分别加X锁,然后在(6,c)-(10,b)、(10,b)-(10,d)、(10,d)-(11,f)范围分别加Gap Lock
加锁行为:表里所有行和间隙都会加X锁。(当没有索引时,会导致全表锁定,因为InnoDB引擎锁机制是基于索引实现的记录锁定)。
数据处理时,每次都锁定当前数据。
悲观锁:行锁、表锁、读锁、写锁、共享锁、排它锁。
锁整张表,并发度低。
表级读锁:当前表追加read锁,当前连接和其他的连接都可以读操作;但是当前连接增删改操作会报错,其他连接增删改会被阻塞。
表级写锁:当前表追加write锁,当前连接可以对表做增删改查操作,其他连接对该表所有操作都被阻塞(包括查询)。
行锁-读锁,多个事务对于同一数据可以共享一把锁,都能访问到数据,但是只能读不能修改。使用共享锁的方法是在select ... lock in share mode,只适用查询语句。
事务使用了共享锁(读锁),只能读取,不能修改,修改操作被阻塞。
行锁-写锁,互斥,针对同一行数据,不同事务不能同时进行读/写操作。
使用排他锁的方法是在SQL末尾加上for update,innodb引擎默认会在update,delete语句加上for update。行级锁的实现其实是依靠其对应的索引,所以如果操作没用到索引的查询,那么会锁住全表记录。
事务使用了排他锁(写锁),当前事务可以读取和修改,其他事务不能修改,也不能获取记录锁(select... for update)。如果查询没有使用到索引,将会锁住整个表记录。
不加锁,而是在事务提交时,再去判断数据是否有冲突。实现关键点在于冲突的检测。
先给数据表增加一个版本(version) 字段,每操作一次,将那条记录的版本号加 1。version是用来查看被读的记录有无变化,作用是防止记录在业务处理期间被其他事务修改。
与使用version版本字段相似,同样需要给在数据表增加一个字段,字段类型使用timestamp时间戳。也是在更新提交的时候检查当前数据库中数据的时间戳和自己更新前取到的时间戳进行对比,如果一致则提交更新,否则就是版本冲突,取消操作。
用户A访问表A(锁住了表A),然后又访问表B;另一个用户B访问表B(锁住了表B),然后企图访问表A;这时用户A由于用户B已经锁住表B,它必须等待用户B释放表B才能继续,同样用户B要等用户A释放表A才能继续,这就死锁就产生了。
用户A--》A表(表锁)--》B表(表锁)
用户B--》B表(表锁)--》A表(表锁)
程序bug,无法解决,只能调整程序逻辑。对于数据库的多表操作时,尽量按照相同的顺序进行处理,尽量避免同时锁定两个资源。
在事务中执行了一条没有索引条件的查询,引发全表扫描,把行级锁上升为全表记录锁定(等价于表级锁),多个这样的事务执行后,就很容易产生死锁和阻塞,最终应用系统会越来越慢,发生阻塞或死锁。
SQL语句中不要使用太复杂的关联多表的查询;使用explain“执行计划"对SQL语句进行分析,对于有全表扫描和全表锁定的SQL语句,建立相应的索引进行优化。
两个事务分别想拿到对方持有的锁,互相等待,于是产生死锁。
在同一个事务中,尽可能做到一次锁定所需要的所有资源
按照id对资源排序,然后按顺序进行处理
事务A 查询一条纪录,然后更新该条纪录;此时事务B 也更新该条纪录,这时事务B 的排他锁由于事务A 有共享锁,必须等A 释放共享锁后才可以获取,只能排队等待。事务A 再执行更新操作时,此处发生死锁,因为事务A 需要排他锁来做更新操作。但是,无法授予该锁请求,因为事务B 已经有一个排他锁请求,并且正在等待事务A 释放其共享锁。
事务A: select * from dept where deptno=1 lock in share mode; //共享锁,1
update dept set dname='java' where deptno=1;//需将共享锁升级为排他锁,但步骤二等待共享锁中,无法升级,造成死锁3
事务B: update dept set dname='Java' where deptno=1;//由于1有共享锁,没法获取排他锁,需等待,2
解决方案:
对于按钮等控件,点击立刻失效,不让用户重复点击,避免引发同时对同一条记录多次操作;
使用乐观锁进行控制。乐观锁机制避免了长事务中的数据库加锁开销,大大提升了大并发量下的系统性能。需要注意的是,由于乐观锁机制是在我们的系统中实现,来自外部系统的用户更新操作不受我们系统的控制,因此可能会造成脏数据被更新到数据库中。
show engine innodb status\G
show status like 'innodb_row_lock%'Innodb_row_lock_current_waits:当前正在等待锁的数量Innodb_row_lock_time:从系统启动到现在锁定总时间长度Innodb_row_lock_time_avg: 每次等待锁的平均时间Innodb_row_lock_time_max:从系统启动到现在等待最长的一次锁的时间Innodb_row_lock_waits:系统启动后到现在总共等待的次数
以上内容为个人学习理解,如有问题,欢迎在评论区指出。
部分内容截取自网络,如有侵权,联系作者删除。
下一篇:BMS 信息资源e分享平台