MySQL锁问题,事务隔离级别
未完待续...
概述
这里专门指的是InnoDB存储引擎的锁问题和事务隔离级别。
=========================================================
锁问题现象
即并发问题,由于数据库的并发处理机制,带来的一些问题,如果是串行处理,则不会有这些问题。一般会有如脏读,不可重复读,幻读,丢失更新等锁问题。这些问题是否会发生,和当前的事务隔离级别有关系(后面会讨论)。现在,先让我们抛开事务隔离级别和引起锁问题的原因等因素,单纯的看下什么是锁问题?各种锁问题发生时的现象是咋样的?
锁问题和并发有关,因此,发生锁问题时,必定是多个(至少两个)事务因为访问的数据集产生了交集,而如果是只有一个事务,或者多个事务之间访问的数据集没有交集,那么就不会存在锁问题。脏读,不可重复读,幻读这三种锁问题一般是发生在一个事务读,另一个事务写(插入,更新,删除)的场景,而丢失更新一般发生在两事务都在更新的场景。
脏读(Dirty Read)
一个事务读取到了另外一个事务中未提交的数据。脏读,从字面意思就能想到,首先要有脏的数据,且事务读取到了这部分脏的数据。什么是脏的数据呢,那种更新了(包括删除,插入),但未提交的数据就是脏数据。如果事务A对某个数据集(比如一条数据)做了更新,但是还没提交(还没有定下来最终结果,可以理解是临时结果),本来,这种未提交的数据不应该让其他事务比如事务B看到的,只有自己(事务A)才能看到,因为对于事务B来说,这修改后的数据是脏的,但是如果事务B能看到,那么意思就是事务B读取到了脏的数据,即发生了脏读。以下两张图分别测试脏读发生时,和没有发生脏读(如上面所说,相同的场景,因为隔离级别不同,锁问题会不一样)的情况,左边的事务类似刚才提到的事务A,右边的事务类似事务B(如果没有加begin,则默认是自动打开事务,并自动提交)。
事务A先通过begin手动开启一个事务,且将id=8的这条记录的name从7修改为6,但是此时事务A并未提交,因此对于事务B来说,id=8的这条记录修改后的值(name=6)是脏的数据,事务B如果要读取本应该读取到的是修改前的值,即name=7,但事务B却读取到的值为6,即发生了脏读。
修改隔离级别后,进行相同的操作,没有发生脏读的情况。事务A将name从6修改为5,且并未提交,此时事务B读取到的仍然是修改前的值,即读取到的name值为6。
不可重复读
不可重复读,从字面意思理解需要有两者对比才会有所谓重复的概念。不可重复读专指一个事务内,对同一个数据集读取的结果不一样,造成了两次或多次读取的结果是不可以重复的。这里需要注意两点:1,是同一个事务内的两次或者多次读取结果的对比,而不是不同事务读取结果的对比(比如在上面提到的在事务A中做两次或多次的读取,而不是一次在事务A中读取,另外一次在事务B中读取);2,两地或多次读取的数据集要相同(比如在上面提到的,两地或多次都读取id=8的记录,而不是一次地区id=8的记录,另外一次读取id=7的记录)。以下两张图分别测试不可重复读发生时,和没有发生(如上面所说,相同的场景,因为隔离级别不同,锁问题会不一样)的情况,左边的事务类似刚才提到的事务A,右边的事务类似事务B(如果没有加begin,则默认是自动打开事务,并自动提交)。
事务A先通过begin手动开启一个事务,然后读取到id=8的记录的name值为5,然后等待一下,且不提交事务。此时再通过事务B将id=8的记录name值从5修改为4,并且提交(注意,这里需要提交),然后再切换到事务A,重新做一下和上次一样的查询,即,查询id=8(相同数据集)的记录,此时查询出的name变成了4,即,在事务A的两次相同的查询,结果不一样,发生了不可重复读问题。
修改隔离级别后,进行相同的操作,没有发生不可重复读的问题。事务A的两次读取的name值都一样,没有受到其他事务(事务B)修改的影响。
丢失更新
一个事务的更新操作被另外一个事务的更新操作覆盖,从而导致数据不一致。在数据库操作层面,并不会出现丢失更新的问题,因为丢失更新发生的条件是对相同数据集做更新操作,而两个事务的更新操作都需要获取X锁,因此其中一个一定会被阻塞,直到另外一个操作完成,实现更新操作的串行化,就不会存在丢失更新的问题。
丢失更新严格意义上并不算是锁问题(个人观点),而是应用代码bug在数据库上面的表现。典型的场景是两个应用并发的读取一个记录,然后将值保存在应用的内存中,然后都基于该值做计算,并更新到数据库,导致后面更新的把前面更新的覆盖了。之所以说这是代码bug,是因为,其中一个应用可能会基于一个错误的值进行计算,如,应用1读取一个值,然后基于该值计算,但是,在计算后的值最终更新到数据库前,另外一个应用已经将数据库的值做了更新,这样其实是应用1是基于一个错误的值的基础上做的计算。说的有点抽象,具体的例子到网上找个,然后结合这里的描述分析下(因为这个例子不好在数据库层面演示,所以例子就不写了,具体的场景也到网上找下)。
为了在应用层解决这个问题,从上面的分析可以看出,是在应用在读取数据,到最终将数据更新到过程中,数据被其他事务“污染”了,导致读取到内存中的数据,和数据库中的数据不一致。那么解决的办法也很明显,只要保证读取到内存的数据和数据库中的数据一致,也就是从读取开始,就将数据库中对应的行锁住,不让其他任何事务更新,直到计算后,将数据最终更新到数据库。
自然会想到,两个事务在读取时,都显示的使用X锁,即使用select ... for update; 这样,保证两个事务是完全串行话的。这里不能使用select ... lock in share mode; 会产生死锁,即使不会出现死锁,也会出现丢失更新,并不能解决问题,原因和上面一样。
丢失更新是比较容易忽视的bug,比较容易犯的错误,需要特别注意。
未完待续...
=========================================================
查看事务隔离级别
和其他参数一样,隔离级别也有范围,即,session,global,系统。查看的方式分别是:
1,查看当前session的隔离级别:SELECT @@tx_isolation;
2,查看global的隔离级别:SELECT @@global.tx_isolation;
3,查看系统的隔离级别:查看Mysql配置文件(如my.cnf)中,transaction-isolation的赋值(类似transaction-isolation = READ COMMITTED)。如果,没有transaction-isolation的赋值,则系统的隔离级别设置为默认,一般为REPEATABLE READ。
这三者的区别和作用同其他参数一样,具体看下面的设置事务隔离级别的说明。
=========================================================
设置事务隔离级别
如果不做任何设置,那么所有的隔离级别(即session,global,系统对应的隔离级别)都是默认的(一般是REPEATABLE READ)。例如,在我的机器上的msyql配置,没有做隔离级别相关的配置:
重启下mysql(消除其他global设置可能的影响,即,重启后,global设置重置为系统配置),然后新打开一个session,查看session和global的隔离级别:
可以看到,session和global的隔离级别都是“REPEATABLE READ”,这也是当前系统的默认级别。
设置系统隔离级别
如上所述,设置系统的隔离级别需要修改配置文件,然后重启系统(修改后必须重启才生效),如下,将系统的隔离级别从默认的REPEATABLE READ,设置为READ COMMITTED:
重启下,然后再重新打开一个session,查看session和global的隔离级别:
从中可以看出,隔离级别由原来默认的REPEATABLE READ变为READ COMMITTED(和系统设置的一样)。
结论:在global和session未对隔离级别做任何设置的情况下,session和global的隔离级别都和系统隔离级别一样。
设置session隔离级别
设置session的隔离级别的命令为:SET SESSION TRANSACTION ISOLATION LEVEL 级别名;
我们在设置系统隔离级别中,将系统的隔离级别设置为READ COMMITTED,下面设置session的隔离级别(设置为REPEATABLE READ),并查看设置后的session隔离级别,和global的隔离级别:
可以看出,设置后,session的隔离级别变成了REPEATABLE READ,但global的隔离级别仍然为READ COMMITTED(和系统的隔离级别一样)。从新打开一个session,看其他新的session的隔离级别:
这是一个新打开的session(和上面的不是同一个session),从中可以看出,新session的隔离级别和系统的隔离级别一样都是READ COMMITTED,并未因为第一个session的设置,而变成和第一个session一样的隔离级别。
结论:对session设置隔离级别,会改变当前sesssion的隔离级别,但不会改变global的隔离级别,也不会改变其他已存在或新打开session的隔离级别。
设置global隔离级别:
设置global的隔离级别的命令为:SET GLOBAL TRANSACTION ISOLATION LEVEL 级别名;
将global的隔离级别设置为REPEATABLE READ,查看当前session的隔离级别,可以看到,仍然为READ COMMITTED,而global的隔离级别已经变成了REPEATABLE READ。
再重新开个一session,查看该session的隔离级别,可以看到新的session的隔离级别和设置后的global的隔离级别一样。
结论:对global设置隔离级别,会改变global的隔离级别和新打开session的隔离级别,但不会改变当前sesssion的隔离级别和已经存在的session(即,在设置global的隔离级别前已经存在的session,这种测试没有做,实际和当前session的情况是一样的,因为当前session就是在global设置前就已经存在的)的隔离级别。
=========================================================
锁阻塞
因为锁的原因导致的阻塞,即,两个事务在并发执行时,每个事务都需要先获取某个锁,且这两个锁存在不兼容情况,这时,其中一个后获取锁的事务,因为需要获取的锁和另一个事务已经获取到的锁存在不兼容,便会进入锁阻塞等待,等待前一个事务释放了锁。
这里发生阻塞的必要条件:1,两个事务在访问时,都必须需要获取锁(什么操作,即sql语句需要获取锁,什么sql不需要获取锁,后面会分析),如果一个事务操作需要获取锁,另外一个事务不需要获取锁,或者两个事务都不需要获取锁,则不会产生阻塞;2,两把锁是不兼容的,两把锁是否兼容,又和锁的类型(排他锁,共享锁),以及锁定了哪部分数据集有关,这部分后面分析。
什么操作(sql)需要获取锁
上文中提到,锁阻塞发生的必要条件之一是两个事务的操作(sql)必须都需要获取某把锁(先暂时不关心获取的锁对应的数据集,即获取哪把锁,因为是否阻塞和锁住的数据集也有关系),那么什么操作会需要获取锁呢,一般是:
1,更新操作(insert,update,delete等)会自动去获取所需要的锁;
2,select 语句后面加 lock in share mode(需要获取S锁)或者for update(需要获取X锁)
而如果是普通的select语句,是不需要获取任何锁的。
实例1:测试update等操作需要加锁
左边事务(假设叫事务A)手动开启一个事务,并对id=8的记录做更新操作,且不提交事务,此时,事务A因为update操作,会自动获取X锁;接着右边事务(假设叫事务B)也对id=8的记录做更新操作,因为同样做的update操作,需要获取X锁,X锁和X锁不兼容,且操作的相同数据集,因此会导致事务B进入锁阻塞。
实例2:测试普通select操作不需要加任何锁
左边事务(假设叫事务A)手动开启一个事务,并对id=8的记录做更新操作,且不提交事务,此时,事务A因为update操作,会自动获取X锁;接着右边事务(假设叫事务B)读取,
实例3:
左
实例4:
左
实例5:
MySQL锁问题,事务隔离级别的更多相关文章
- 面试必问的MySQL锁与事务隔离级别
之前多篇文章从mysql的底层结构分析.sql语句的分析器以及sql从优化底层分析, 还有工作中常用的sql优化小知识点.面试各大互联网公司必问的mysql锁和事务隔离级别,这篇文章给你打神助攻,一飞 ...
- MySql锁和事务隔离级别
在讲mysql事物隔离级别之前,我们先简单说说mysql的锁和事务. 一:数据库锁 因为数据库要解决并发控制问题.在同一时刻,可能会有多个客户端对同一张表进行操作,比如有的在读取该行数据,其他的尝试去 ...
- MySQL锁与事务隔离级别
一.概述 1.锁的定义 锁是计算机协调多个进程或线程并发访问某一资源的机制. 在数据库中,除了传统的计算资源(如CPU.RAM.IO等)的争用以外,数据也是一种供需要用户共享的资源.如何保证数据并发访 ...
- 【MySQL】深入理解MySQL锁和事务隔离级别
先看个小案例: 话不多说,上案例,先创建一个表 mysql> CREATE TABLE IF NOT EXISTS `account`( `id` INT UNSIGNED AUTO_INCRE ...
- 深入理解mysql锁与事务隔离级别
一.锁 1.锁的定义 锁即是一种用来协调多线程或进程并发使用同一共享资源的机制 2.锁的分类 从性能上分类:乐观锁和悲观锁 从数据库操作类型上分类:读锁和写锁 从操作粒度上分类:表锁和行锁 2 ...
- 谈谈MySQL支持的事务隔离级别,以及悲观锁和乐观锁的原理和应用场景?
在日常开发中,尤其是业务开发,少不了利用 Java 对数据库进行基本的增删改查等数据操作,这也是 Java 工程师的必备技能之一.做好数据操作,不仅仅需要对 Java 语言相关框架的掌握,更需要对各种 ...
- 第36讲 谈谈MySQL支持的事务隔离级别,以及悲观锁和乐观锁的原理和应用场景
在日常开发中,尤其是业务开发,少不了利用 Java 对数据库进行基本的增删改查等数据操作,这也是 Java 工程师的必备技能之一.做好数据操作,不仅仅需要对 Java 语言相关框架的掌握,更需要对各种 ...
- mysql中不同事务隔离级别下数据的显示效果--转载
事务是一组原子性的SQL查询语句,也可以被看做一个工作单元.如果数据库引擎能够成功地对数据库应用所有的查询语句,它就会执行所有查询,如果任何一条查询语句因为崩溃或其他原因而无法执行,那么所有的语句就都 ...
- 浅谈mysql中不同事务隔离级别下数据的显示效果
事务的概念 事 务是一组原子性的SQL查询语句,也可以被看做一个工作单元.如果数据库引擎能够成功地对数据库应用所有的查询语句,它就会执行所有查询,如果任何一条查 询语句因为崩溃或其他原因而无法执行,那 ...
- 事务,Oracle,MySQL及Spring事务隔离级别
一.什么是事务: 事务逻辑上的一组操作,组成这组操作的各个逻辑单元,要么一起成功,要么一起失败. 二.事务特性(4种): 原子性 (atomicity):强调事务的不可分割:一致性 (consiste ...
随机推荐
- Opencv与Qt (一)之运行测试读取图片
刚刚在vs上装好了QT和Opencv,试一下效果把. 我简单的创建了一个label,然后使用Opencv导入图像,因为Opencv导入图像是MAT格式的,在使用Qt的时候我们要把导入的图像转换成Qim ...
- 17Linux_mariadb_PXE+Kickstart
Mariadb mariadb-client mariadb-server # yum groupinstall mariadb mariadb-client mariadb-server -y # ...
- XenApp6.5产品BUG
外网登录报错,手机登录报错问题解决: XenApp6.5产品BUG, 在WI服务器的两个web站点中修改defalut.ica文件中添加一行,CGPAddr=即可. 路径:C:\inetpub\www ...
- python setup.py 包含静态文件及模板文件
package_data 和MANIFEST.in都写,include_package_data=True https://stackoverflow.com/a/3597263/8025086
- Jenkins问题笔记
1.启动docker容器权限不足 通过如下命令启动docker容器后,使用命令"docker logs -f jenkins"查看jenkins容器日志, docker run - ...
- JavaScript中面相对象OOP
方法 方法的原型链 <html> <head> <title></title> </head> <script type=" ...
- Suse linux enterprise 11添加设置中文输入法的方法
Suse中输入法的设置没有在控制中心中,而是在应用程序里默认会安装好的SCIM输入法设置里边添加. 打开SCIM输入法设置->输入法引擎->全局设置,有很多国家的输入法可以选择,想要的找到 ...
- 有关于并发中的死锁(Deadlock)、饥饿(Starvation)、活锁(Livelock)
最近在看<实战Java高并发程序设计>,发现了之前没有接触过的几个名词. 死锁:之前在接触多线程的时候,接触过死锁的情况.死锁是线程中最糟糕的情况,如下面的图中的四辆车子一样,如果没有一辆 ...
- unittest框架(惨不忍睹低配版)
根据我上个随笔的unittest框架优化得来,虽然对于smtp模块还是有点迷糊,不过还是勉强搭建运行成功了,还是先上代码: #login_test.py import requests class L ...
- ansible自动化
一,工具与环境介绍 1.1 ansible简介 批量管理服务器的工具 无需部署agent,通过ssh进行管理 流行的自动化运维工具:https://github.com/ansible/ansib ...