mysql-事务隔离级别以及锁详解

目录

一、事务的隔离级别

什么是事务、事务有哪些特性

持久性:事务一旦提交,它对数据库中的数据的改变就是永久性的 事务并发情况下可能会产生的问题有哪些

事务的隔离级别有哪些?默认的隔离级别是什么?

在设置可重复读的情况下,如何保证修改的是最新的数据?

mysql中设置事务隔离级别以及事务的操作

二、锁

说说mysql中有那些锁?

innodb有哪些行锁算法

innodb如何选择行锁与表锁

什么是死锁?怎么解决死锁问题?

事务与锁状态信息查看


一、事务的隔离级别

什么是事务、事务有哪些特性

事务:一个或一组sql语句组成的一个执行单元,这个执行单元里面的sql要么都执行,要么都不执行

四大特性:

原子性:

原子性指事务时一个不可分割的工作单位,事务中的操作要么都执行要么都不执行,原子性由undo log 日志实现

一致性:
从一个正确的状态,迁移到另一个正确的状态.什么叫正确的状态呢?就是当前的状态满足预定的约束就叫做正确的状态.,一致性由其他三个特性和业务代码正确性来实现(例如在业务代码中,一行代码修改了A的金额减少500,然后再修改B的金额中写入try,catch中,出现了异常,然而没有再catch语句中抛出异常,spring无法检测到,于是不对事务进行回滚,导致了最终结果不一致性)

隔离性:
事务的隔离性是指一个事务的执行不能被其他事务干,即一个事务内部的操作以及使用的数据对并发的其他事务时隔离的,并发执行的各个事务之前互不干扰,隔离性由mysql各种锁以及mvcc机制实现

持久性:
事务一旦提交,它对数据库中的数据的改变就是永久性的,持久性有redo log 日志实现
 
事务并发情况下可能会产生的问题有哪些

脏读:
对于两个事务T1、T2,T1读取了已经被T2更新但还没提交的字段之后,若T2回滚,T1读取的内容就是临时且无效的

不可重复读:
对于两个事务T1、T2,T1读取了一个字段,然后T2更新了该字段之后,T1再读取一次该字段,值不相同

幻读:
对于两个事务T1、T2,T1从一个表中读取了一个字段,然后T2再在该表中插入一些新数据,之后T1再次读取同一个表,就会多出几行(对与可重复读隔离级别下,假设在A事务进行了一次查询select * ,有三条记录,然后在B事务新增一条数据id为4,然后再在A事务select * 仍然是三条事务,但是如果这时候再A事务中修改了id为4的记录,也就是修改刚新增的数据,那么再查询一次select *  就会出现四条数据,这就导致了幻读的情况)


事务的隔离级别有哪些?默认的隔离级别是什么?

mysql-事务隔离级别以及锁详解_第1张图片

READ UNCOMMITTED  读未提交,该模式下会出现并发事务的所有可能出现的问题
READ COMMITTED  读已提交,能够成功解决脏读现象(不支持间隙锁)
REPEATABLE READ(默认的隔离级别)  可重复读,能够成功解决不可重复读现象,可能会解决幻读现象(通过间隙锁),性能几乎不受影响(select语句通过mvcc维护,不加锁)
SERIALIZABLE 串行化,能够解决并发事务下的所有问题,通过禁止另一个事务增删改的方式,效率非常低下,在分布式事务下使用(对于所有的操作都加锁处理,对于所有的读操作都会加上读锁,写操作所有隔离级别都会默认加写锁)

  • oracle支持两种隔离级别:READ COMMITED、SERIALIZABLE,默认是 READ  COMMITED
  • mysql支持四种隔离级别:(以上四种),默认是REPEATABLE READ

在设置可重复读的情况下,如何保证修改的是最新的数据?

根据可重复读的特性,在一个事务中通过MVCC机制多次读取到的值一定是相同的,而可能此时真实的数据以及被修改了,那么怎么保证修改的数据是最新的呢?

改变以往通过java代码获取当前值然后作为参数修改数据的形式,将数据的变更和获取融合在一起

update student set score = score+ 5 ;

因为在mysql的mvcc机制的控制下,select读取的实际是历史版本,而增删改会读取当前版本

mysql中设置事务隔离级别以及事务的操作

  • SET autocommit = 0;  关闭自动提交
  • set tx_isolation='read-uncommitted';设置读未提交
  • set tx_isolation='read-committed';设置读已提交隔离级别
  • SET tx_isolation = 'repeatable-read';   设置可重复读隔离级别
  • set tx_isolation='serializable';设置串行化隔离级别
  • start TRANSACTION ;开启事务
  • commit;  提交提交事务
  • rollback ;回滚事务
  • SELECT @@tx_isolation 查看当前会话隔离级别
  • select @@global.tx_isolation; 查看系统隔离级别

二、锁

说说mysql中有那些锁?

按操作分类:

共享锁:也叫读锁。针对同一数据,多个事务读取操作可以同时加锁互不影响,但是不能修改数据。

innodb 共享锁:sql+ lock in share mode;例 select * from student lock in share mode;

        当开启共享锁的时候,当前事务可以查询数据、修改数据,并且可以更换锁的类型;其他事务可以对该行进行共享锁查询,但是不能加排他锁查询,也不能修改数据。

myisam 读锁:加锁:lock table 表名  read;解锁:unlock   tables;

        可以多个会话同时对一个表添加读锁,但是不能修改数据以及添加写锁。同一个会话添加读锁后也不能修改当前表的数据,

排他锁:也叫做写锁。当前操作没完成时,会阻断其他操作的加锁读取和修改数据。

innodb 排他锁:sql+for update  例 select * from student for update;

        当开启排他锁时,当前事务可以查找数据和修改数据,也可以修改锁的类型,但是即便从排他锁变成共享锁,其他事务也不能加锁查询,本质上还是排他锁,其他事务只能查询数据,不能加锁也不能修改数据。

myisam 写锁:lock table 表名 write ;解锁:unlock tables; 

        当前会话可以查询和修改数据,其他会话不能查询和修改数据

按粒度分类:

表级锁:锁住整个表,开销小,加锁快,锁粒度大,发生锁冲突概率高,并发力度低,不会出现死锁现象。
          myisam存储引擎支持的就是表锁,innodb存储引擎也支持表锁,但是默认的是行锁

行级锁:锁住当前行,开销大,加锁慢,锁粒度小,发生锁冲突概率低,并发度高,会出现死锁现象。
           innodb存储引擎默认支持行锁,但是需要用索引当作检索条件才能施加行锁(命中数据增加行锁,如果不命中数据则增加间隙锁(RR隔离级别)),否则会从行锁升级为表锁(RR级别会升级成表锁,RC级别不会,因为在RR级别上要保证可重复读,所以在遍历扫描数据的时候,为了防止扫描过的数据被其他数据修改或间隙被插入数据,从而导致数据不一致,索引mysql就把所有扫描过的数据和间隙都加上锁)

意向锁:意向锁主要是针对表锁的,主要是为了提高加表锁的效率,是mysql自己加的锁,当事务给表的数据行加了共享锁或者排他锁的时候,同时会给表设置一个表示,代表这张表已经加了行锁,其他事务想要加表锁的时候,就不必要逐行判断有没有行锁可能对表锁造成冲突,直接读取这个标记就知道该不该枷锁,而这个标记就是意向锁

按照使用方式:

悲观锁:对数据被外界修改保持保守状态,认为数据随时会被修改,整个数据处理过程中需要将数据加锁,悲观锁一般都是依靠关系型数据库提供的锁机制,我们之前学过的锁(共享锁,排他锁,读锁,写锁)都是悲观锁;用于写多读少


乐观锁:每次自己操作数据的时候认为没有人会修改他,所以不去加锁,但是在更新的时候去判断再次期间数据有没有被修改,需要用户自己去实现,不会发生抢占资源,只有在提交操作的时候检查是否违反数据完整性;用于读多写少
方式:给数据表添加一个version列,每次 更新后都将这个列的值加一,读取数据时,将版本号取出来,在执行更新的时候,比较版本号,如果相同则执行,如果不相同说明这条数据已经发生变化了,用户自行根据这个通知来决定怎么处理,比如重新更新一次或者放弃更新。
 


 

innodb有哪些行锁算法


Record lock:单个行记录上的锁

Gap lock:

间隙锁:锁定一个范围,不包括记录本身(在RR隔离级别下才会出现
当我们用范围条件而不是相等条件检索数据,并请求共享或排他锁时,InnoDB会给符合条件的已有数据记录的索引项加锁;也会对范围所包含的所有行记录(包括间隙行记录)以及行记录所在的间隙加锁

“间隙”细节点:

例如:

这里有一个h表

mysql-事务隔离级别以及锁详解_第2张图片

 执行sql语句  UPDATE h set `name` = '111'  where hm>5 and hm<8

间隙锁中的间隙指定并不是5-8之间,而是根据表数据中针对hm这一列数据来看,表中有三条数据,对应的间隙应该是两两之间的间隔,例如1-4之间有个2和3,间隙就是(1,4),4-9之间有5、6、7、8,间隙就是(4,9),而9之后还有还有可扩展的无穷多个数,间隙就是(9,+∞)

所以这里所锁住的内容是hm>5 和hm <8 之间的数所在的间隙,也就是6,7所在的间隙,就是在(4,9)中。 再来看一条sql  UPDATE h set `name` = '111'   where hm>7 and hm<11.

这条sql会直接锁住两个间隙,8处于间隙(4,9)中,所以这个间隙会锁住,而10处于间隙(9,+∞)中,所以这个间隙也会被锁住

关闭间隙锁的方法

  • 1、将事务隔离级别设置为read committed;
  • 2、将参数innodb_locks_unsafe_for_binlog设置为1

Next-key lock:

临键锁:

Next-Key Locks是行锁与间隙锁的组合。例如上面的例子(4,9)在临键锁上是(4,9],又例如sql

UPDATE h set `name` = '111'  where hm>4 and hm<=9,临键锁上是(4,9]

innodb如何选择行锁与表锁

显示指定:

行锁: sql语句+ lock in share mode

表锁: sql语句+ for update

由innodb自动选择:

行锁:当sql语句where条件中的列是索引字段,使用的就是行锁

表锁:当sql语句where条件中的列不是索引字段,加表锁

什么是死锁?怎么解决死锁问题?


死锁是指两个或多个事务在同一资源上相互占用,并请求锁定对方的资源,从而导致恶性循环的现象。

常见的解决死锁的方法

  • 如果不同程序会并发存取多个表,尽量约定以相同的顺序访问表,可以大大降低死锁机会。
  • 在同一个事务中,尽可能做到一次锁定所需要的所有资源,减少死锁产生概率;
  • 对于非常容易产生死锁的业务部分,可以尝试使用升级锁定颗粒度,通过表级锁定来减少死锁产生的概率;
  • 如果业务处理不好可以用分布式事务锁或者使用乐观锁
  • 大多数情况mysql可以自动检测死锁并回滚产生死锁的那个事务,但是有些情况mysql没法自动检测死锁

读操作是否需要加事务?

读操作是否需要加事务需要根据具体的业务场景,如果不需要查询同一时间维度的数据,那么可以不用加事务,但是如果需要查询同一时间维度的数据,那么需要加可重复读隔离界别的事务(RR)

事务与锁状态信息查看

‐‐ 查看事务时间超过60秒的事务
select * from INFORMATION_SCHEMA.INNODB_TRX WHERE TIMESTAMPDIFF(SECOND ,trx_started,NOW()) > '60'
‐‐ 查看锁
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

大事务的影响

1、并发情况下,容易造成数据库连接池被撑爆

2、锁定的数据过多,容易造成大量阻塞和锁超时

3、执行时间长,容易造成主从延迟

4、回滚所需要的时间比较长

5、undo log 膨胀

6、容易导致死锁

事务优化实践原则

1、对于RC事务级别,可以将查询等数据准备操作放到事务外进行

2、事务中避免进行远程调用,远程调用要设置超时时间,防止事务等待太久

3、一次事务避免处理太多数据,可以拆分成多个事务分次处理

4、更新等涉及加锁的操作尽可能放在事务靠后的位置(因为更新操作是已经存在的数据,有可能其他事务还会使用这条记录,那么会造成等待)

5、能异步处理的尽可能异步处理

6、对于简单的业务,应用业务代码来保证数据准确性,不通过事务

你可能感兴趣的:(Mysql,mysql,数据库)