目录html
一、自我介绍java
四、微服务架构的服务粒度怎么肯定,服务怎么通讯?spring
六、equals与hashcode的区别,存入集合时的判断过程编程
八、垃圾回收算法,新生代和年老代用的什么算法,为何用这个算法?session
2.一、什么事为服务架构
形象一点来讲,微服务架构就像搭积木,每一个微服务都是一个零件,并使用这个零件组装出不一样的形状。通俗来讲,微服务架构就是把一个大系统按业务功能分解成多个职责单一的小系统,并利用简单的方法使多个小系统相互协做,组成一个大系统。
若是学科派一点,微服务架构就是把因相同缘由而变化的功能聚合到一块儿,而把因不一样缘由而变化的功能分离开,并利用轻量化机制(一般为HTTP RESTful API)实现通讯。
追本溯源,Martin Folwer对微服务架构的定义是:
微服务架构是一种架构模式,它提倡将单一应用程序划分红一组小的服务,服务之间互相协调、互相配合,为用户提供最终价值。每一个服务运行在其独立的进程中,服务与服务间采用轻量级的通讯机制互相协做(一般是基于HTTP协议的RESTful API)。每一个服务都围绕着具体业务进行构建,而且可以被独立的部署到生产环境、类生产环境等。另外,对具体的服务而言,应根据业务上下文,选择合适的语言、工具对其进行构建。
对于我我的,我更喜欢一种延续性的解释,微服务架构≈ 模块化开发+分布式计算。无论微服务架构的定义怎么样,都是在描述一个核心思想:把大系统拆分红小型系统,把大事化小,以下降系统的复杂性,从而大幅下降系统建设、升级、运维的风险和成本。
顺带提一下,亚马逊创始人Jeff Bezos在2002年就说过:全部团队的模块都要以Service Interface的方式将数据和功能开放出来。不这样作的人会被炒鱿鱼。这才是思路超前的大牛。
须要注意的是“微服务”与“微服务架构”是有本质区别的。“微服务”强调的是服务的大小,它关注的是某一个点。而“微服务架构”则是一种架构思想,须要从总体上对软件系统进行通盘的考虑。
Chris Richardson说:“微服务”是一个很糟糕的名字,它致使开发人员建立了许多粒度很小的服务,每一个服务拥有一个单独的REST端点。不只如此,这个名字还暗示了微服务在开发者心目中的重要位置。例如,人们会说“咱们能够用微服务来解决这个问题”;我也看到了愈来愈多的“某某微服务框架”,而实际上,这些框架跟微服务架构不必定有太多联系,它们只是简单的Web框架(如:spring-boot)。使用“微服务架构”这个名字会更恰当些。它是一种架构风格,它把一系列协做的服务组织成一个系统来支撑业务。
1.二、常见的微服务组件
1.服务注册:服务提供方将本身调用地址注册到服务注册中心,让服务调用方可以方便地找到本身。
2.服务发现:服务调用方从服务注册中心找到本身须要调用的服务的地址。
3.负载均衡:服务提供方通常以多实例的形式提供服务,负载均衡功能可以让服务调用方链接到合适的服务节点。而且,节点选择的工做对服务调用方来讲是透明的。
4.服务网关:服务网关是服务调用的惟一入口,能够在这个组件是实现用户鉴权、动态路由、灰度发布、A/B测试、负载限流等功能。
5.配置中心:将本地化的配置信息(properties,xml, yaml等)注册到配置中心,实现程序包在开发、测试、生产环境的无差异性,方便程序包的迁移。
6.API管理:以方便的形式编写及更新API文档,并以方便的形式供调用者查看和测试。
7.集成框架:微服务组件都以职责单一的程序包对外提供服务,集成框架以配置的形式将全部微服务组件(特别是管理端组件)集成到统一的界面框架下,让用户可以在统一的界面中使用系统。
8.分布式事务:对于重要的业务,须要经过分布式事务技术(TCC、高可用消息服务、最大努力通知)保证数据的一致性。具备表明性的有spring transaction
9.调用链:记录完成一个业务逻辑时调用到的微服务,并将这种串行或并行的调用关系展现出来。在系统出错时,能够方便地找到出错点。具备表明性的有pinpoint.
10.支撑平台:系统微服务化后,系统变得更加碎片化,系统的部署、运维、监控等都比单体架构更加复杂,那么,就须要将大部分的工做自动化。如今,能够经过Docker等工具来中和这些微服务架构带来的弊端。 例如:持续集成、蓝绿发布、健康检查、性能健康等等。严重点,以咱们两年的实践经验,能够这么说,若是没有合适的支撑平台或工具,就不要使用微服务架构。
通常状况下,若是但愿快速地体会微服务架构带来的好处,使用Spring Cloud提供的服务注册(Eureka)、服务发现(Ribbon)、服务网关(Zuul) 三个组件便可以快速入门。其它组件则须要根据自身的业务选择性使用。
1.3微服务架构有哪些优点劣势?
要谈优点,就必定要有对比,咱们能够尝试着从两个维度来对比:
1.3.1单体架构和微服务架构的对比
【结论】:
n对于简单项目来讲,单体架构5胜8败。
(优点项:开发效率、上手难度、运维效率、硬件需求、项目成本;劣势项:系统设计(高内聚低耦合)、系统设计(扩展性)、需求变动响应速度、系统升级效率、知识积累、非功能需求、职责、成就感、风险)
n对于复杂项目来讲,单体架构2胜11败。
(优点项:上手难度、运维效率;劣势项:硬件需求、项目成本、开发效率、系统设计(高内聚低耦合)、系统设计(扩展性)、需求变动响应速度、系统升级效率、知识积累、非功能需求、职责、成就感、风险;)
1.3.2微服务与共享库的对比.,
1.3.3什么场景须要用微服务架构?
看了上面的比较,微服务架构能够说是以压倒性的优点赛过单体架构和共享库,会让人产生一种错觉,微服务架构就是软件开发中的银弹。
可是,正如你们所了解的,软件研发是一个系统工程,它没有银弹,不可以一招鲜吃遍天。正如当年的CMMI和敏捷方法同样,敏捷虽好,但它不必定能适用于全部的场景,它对组织环境、团队氛围、沟通方式、技术能力这些都是有一些要求的,若是用很差,反而会带来一些负面影响。
那么,咱们何时须要采用微服务呢?从我我的的经验来看,我认为有三种场景能够考虑使用微服务。
1.规模大(团队超过10人)
2.业务复杂度高(系统超过5个子模块)
3.须要长期演进(项目开发和维护周期超过半年)
首先SOA和微服务架构一个层面的东西,而对于ESB和微服务网关是一个层面的东西,一个谈的是架构风格和方法,一个谈的是实现工具或组件。
1.SOA(Service Oriented Architecture)“面向服务的架构”:他是一种设计方法,其中包含多个服务, 服务之间经过相互依赖最终提供一系列的功能。一个服务一般以独立的形式存在与操做系统进程中。各个服务之间 经过网络调用。
2.微服务架构:其实和 SOA 架构相似,微服务是在 SOA 上作的升华,微服务架构强调的一个重点是“业务须要完全的组件化和服务化”,原有的单个业务系统会拆分为多个能够独立开发、设计、运行的小应用。这些小应用之间经过服务完成交互和集成。
微服务架构 = 80%的SOA服务架构思想 + 100%的组件化架构思想 + 80%的领域建模思想
--------------------
乐观锁:得到锁后一直持有锁以防本线程再次申请该锁形成无畏的解锁再加锁开销,或者假设没有冲突而去完成同步代码块,若是冲突再循环重试,或者采起申请锁失败后不马上挂起而是稍微等待再次尝试获取的等待策略减小线程由于挂起、阻塞、唤醒(发生CPU的调度切换)而形成的开销。
偏向锁、轻量级锁(CAS轮询)、自旋锁就是基于上述思路的乐观锁。
悲观锁:悲观锁就是无论是否发生多线程冲突,只要存在这种可能,就每次访问都加锁,加锁就会致使锁之间的争夺,有争夺就会有输赢,输者等待。
syncrhoized是一种独占锁,即:占用该锁的线程才能够执行,申请该锁的线程只能挂起等待,直到占用锁的线程释放锁才唤醒,拿到锁并执行。因此syncrhoized是一种悲观锁,凡是用syncrhoized加了锁的多线程之间都会因锁的争夺而致使挂起、唤醒等开销。
在多线程的加锁机制中,JVM会首先尝试乐观锁,失败后才调用悲观锁。
先来试想一个场景,若是你想查找一个集合中是否包含某个对象,那么程序应该怎么写呢?一般的作法是逐一取出每一个元素与要查找的对象一一比较,当发现二者进行equals比较结果相等时,则中止查找并返回true,不然,返回false。可是这个作法的一个缺点是当集合中的元素不少时,譬若有一万个元素,那么逐一的比较效率势必降低很快。因而有人发明了一种哈希算法来提升从该集合中查找元素的效率,这种方式将集合分红若干个存储区域(能够当作一个个桶),每一个对象能够计算出一个哈希码,能够根据哈希码分组,每组分别对应某个存储区域,这样一个对象根据它的哈希码就能够分到不一样的存储区域(不一样的桶中)。以下图所示:
实际的使用中,一个对象通常有key和value,能够根据key来计算它的hashCode。假设如今所有的对象都已经根据本身的hashCode值存储在不一样的存储区域中了,那么如今查找某个对象(根据对象的key来查找),不须要遍历整个集合了,如今只须要计算要查找对象的key的hashCode,而后找到该hashCode对应的存储区域,在该存储区域中来查找就能够了,这样效率也就提高了不少。说了这么多相信你对hashCode的做用有了必定的了解,下面就来看看hashCode和equals的区别和联系。
在研究这个问题以前,首先说明一下JDK对equals(Object obj)和hashCode()这两个方法的定义和规范:在Java中任何一个对象都具有equals(Object obj)和hashCode()这两个方法,由于他们是在Object类中定义的。 equals(Object obj)方法用来判断两个对象是否“相同”,若是“相同”则返回true,不然返回false。 hashCode()方法返回一个int数,在Object类中的默认实现是“将该对象的内部地址转换成一个整数返回”。
下面是官方文档给出的一些说明:
下面是我查阅了相关资料以后对以上的说明作的概括总结:
1.若重写了equals(Object obj)方法,则有必要重写hashCode()方法。
2.若两个对象equals(Object obj)返回true,则hashCode()有必要也返回相同的int数。
3.若两个对象equals(Object obj)返回false,则hashCode()不必定返回不一样的int数。
4.若两个对象hashCode()返回相同int数,则equals(Object obj)不必定返回true。
5.若两个对象hashCode()返回不一样int数,则equals(Object obj)必定返回false。
6.同一对象在执行期间若已经存储在集合中,则不能修改影响hashCode值的相关信息,不然会致使内存泄露问题。
想要弄清楚以上六点,先要知道何时须要重写equals和hashCode。通常来讲涉及到对象之间的比较大小就须要重写equals方法,可是为何第一点说重写了equals就须要重写hashCode呢?实际上这只是一条规范,若是不这样作程序也能够执行,只不过会隐藏bug。通常一个类的对象若是会存储在HashTable,HashSet,HashMap等散列存储结构中,那么重写equals后最好也重写hashCode,不然会致使存储数据的不惟一性(存储了两个equals相等的数据)。而若是肯定不会存储在这些散列结构中,则能够不重写hashCode。可是我的以为仍是重写比较好一点,谁能保证后期不会存储在这些结构中呢,何况重写了hashCode也不会下降性能,由于在线性结构(如ArrayList)中是不会调用hashCode,因此重写了也没关系,也为后期的修改打了补丁。
下面来看一张对象放入散列集合的流程图:
从上面的图中能够清晰地看到在存储一个对象时,先进行hashCode值的比较,而后进行equals的比较。可能如今你已经对上面的6点概括有了一些认识。咱们还能够经过JDK中得源码来认识一下具体hashCode和equals在代码中是如何调用的。
HashSet.java
HashMap.java
最后再来看几个测试的例子吧:
测试一:覆盖equals(Object obj)但不覆盖hashCode(),致使数据不惟一性
输出结果:
[java] view plain copy
缘由分析:
(1)当执行set.add(p1)时(1),集合为空,直接存入集合;
(2)当执行set.add(p2)时(2),首先判断该对象(p2)的hashCode值所在的存储区域是否有相同的hashCode,由于没有覆盖hashCode方法,因此jdk使用默认Object的hashCode方法,返回内存地址转换后的整数,由于不一样对象的地址值不一样,因此这里不存在与p2相同hashCode值的对象,所以jdk默认不一样hashCode值,equals必定返回false,因此直接存入集合。
(3)当执行set.add(p1)时(3),时,由于p1已经存入集合,同一对象返回的hashCode值是同样的,继续判断equals是否返回true,由于是同一对象因此返回true。此时jdk认为该对象已经存在于集合中,因此舍弃。
测试二:覆盖hashCode方法,但不覆盖equals方法,仍然会致使数据的不惟一性
修改Point类:
[java] view plain copy
输出结果:
[java] view plain copy
缘由分析:
(1)当执行set.add(p1)时(1),集合为空,直接存入集合;
(2)当执行set.add(p2)时(2),首先判断该对象(p2)的hashCode值所在的存储区域是否有相同的hashCode,这里覆盖了hashCode方法,p1和p2的hashCode相等,因此继续判断equals是否相等,由于这里没有覆盖equals,默认使用'=='来判断,因此这里equals返回false,jdk认为是不一样的对象,因此将p2存入集合。
(3)当执行set.add(p1)时(3),时,由于p1已经存入集合,同一对象返回的hashCode值是同样的,而且equals返回true。此时jdk认为该对象已经存在于集合中,因此舍弃。
综合上述两个测试,要想保证元素的惟一性,必须同时覆盖hashCode和equals才行。
(注意:在HashSet中插入同一个元素(hashCode和equals均相等)时,会被舍弃,而在HashMap中插入同一个Key(Value 不一样)时,原来的元素会被覆盖。)
测试三:在内存泄露问题
[java] view plain copy
运行结果:
[java] view plain copy
缘由分析:
假设p1的hashCode为1,p2的hashCode为2,在存储时p1被分配在1号桶中,p2被分配在2号筒中。这时修改了p2中与计算hashCode有关的信息(x和y),当调用remove(Object obj)时,首先会查找该hashCode值得对象是否在集合中。假设修改后的hashCode值为10(仍存在2号桶中),这时查找结果空,jdk认为该对象不在集合中,因此不会进行删除操做。然而用户觉得该对象已经被删除,致使该对象长时间不能被释放,形成内存泄露。解决该问题的办法是不要在执行期间修改与hashCode值有关的对象信息,若是非要修改,则必须先从集合中删除,更新信息后再加入集合中。
总结:
1.hashCode是为了提升在散列结构存储中查找的效率,在线性表中没有做用。
2.equals和hashCode须要同时覆盖。
3.若两个对象equals返回true,则hashCode有必要也返回相同的int数。
4.若两个对象equals返回false,则hashCode不必定返回不一样的int数,但为不相等的对象生成不一样hashCode值能够提升哈希表的性能。
5.若两个对象hashCode返回相同int数,则equals不必定返回true。
6.若两个对象hashCode返回不一样int数,则equals必定返回false。
7.同一对象在执行期间若已经存储在集合中,则不能修改影响hashCode值的相关信息,不然会致使内存泄露问题。
程序计数器(线程私有):
是当前线程所执行的字节码的行号指示器,每条线程都要有一个独立的程序计数器,这类内存也称为“线程私有”的内存。
正在执行java方法的话,计数器记录的是虚拟机字节码指令的地址(当前指令的地址)。若是仍是Native方法,则为空。
这个内存区域是惟一一个在虚拟机中没有规定任何OutOfMemoryError状况的区域。
Java虚拟机栈(线程私有):
也是线程私有的。
每一个方法在执行的时候会建立一个栈帧,存储了局部变量表,操做数栈,动态链接,方法返回地址等。
每一个方法从调用到执行完毕,对应一个栈帧在虚拟机栈中的入栈和出栈。
一般所说的栈,通常是指虚拟机栈中的局部变量表部分。
局部变量表所需的内存在编译期间完成分配。
若是线程请求的栈深度大于虚拟机所容许的深度,则StackOverflowError。
若是虚拟机栈能够动态扩展,扩展到没法申请足够的内存,则OutOfMemoryError。
本地方法栈(线程私有):
和虚拟机栈相似,主要为虚拟机使用到的Native方法服务。
也会抛出StackOverflowError和OutOfMemoryError。
Java堆(线程共享):
被全部线程共享的一块内存区域,在虚拟机启动时建立,用于存放对象实例。
堆能够按照可扩展来实现(经过-Xmx和-Xms来控制)
当堆中没有内存能够分配给实例,也没法再扩展时,则抛出OutOfMemoryError异常。
方法区(线程共享):
被全部线程共享的一块内存区域。
用于存储已被虚拟机加载的类信息,常量,静态变量等。
这个区域的内存回收目标主要针对常量池的回收和对类型的卸载。
当方法区没法知足内存分配需求时,则抛出OutOfMemoryError异常。
在HotSpot虚拟机中,用永久代来实现方法区,将GC分代收集扩展至方法区,可是这样容易遇到内存溢出的问题。
JDK1.7中,已经把放在永久代的字符串常量池移到堆中。
JDK1.8撤销永久代,引入元空间。
1. 标记清除算法
标记清除算法是最基础的回收算法,分为标记和清除两个部分:首先标记出全部须要回收的对象,这一过程在可达性分析过程当中进行。在标记完以后统一回收全部被标记的对象。
标记清除算法有以下不足:
效率问题:
标记和清除这两个过程的效率不高
空间问题
清除以后会产生大量不连续的内存碎片,内存碎片太多会致使之后的程序运行中没法分配出较大的内存,从内不得不触发另外的垃圾回收。
如上图中,通过标记清除以后,假设有了100M空间,可是这100M是不连续的,最大的一块连续空间可能才10M,因此致使以后程序须要一块20M内存空间时就不得再也不进行一次GC来继续清理空间,效率极低。
鉴于标记清除算法有如上的缺陷,因此如今通常是用的是其的变种算法。
2. 复制算法(新生代算法)
2.1 复制算法概念
复制算法是针对Java堆中的新生代内存垃圾回收所使用的回收策略,解决了”标记-清理”的效率问题。
复制算法将堆中可用的新生代内存按容量划分红大小相等的两块内存区域,每次只使用其中的一块区域。当其中一块内存区域须要进行垃圾回收时,会将此区域内还存活着的对象复制到另外一块上面,而后再把此内存区域一次性清理掉。
这样作的好处是每次都是对整个新生代一半的内存区域进行内存回收,内存分配时也就不须要考虑内存碎片等复杂状况,只须要移动堆顶指针,按顺序分配便可。此算法实现简单,运行高效。算法的执行流程以下图 :
如今主流的虚拟机,包括HotSpot都是采用的这种回收策略进行新生代内存的回收。
2.2 新生代内存划分
新生代中98%的对象都是”朝生夕死”的,因此并不须要按照1 : 1的比例来划份内存空间,而是将内存(新生代内存)分为一块较大的Eden(伊甸园)空间和两块较小的Survivor(幸存者)空间,每次使用Eden和其中一块Survivor(两个Survivor区域一个称为From区,另外一个称为To区域)。
当进行垃圾回收时,将Eden和Survivor中还存活的对象一次性复制到另外一块Survivor空间上,最后清理掉Eden和刚才用过的Survivor空间。
当Survivor空间不够用时,则须要依赖其余内存(老年代)进行分配担保。
HotSpot默认Eden与Survivor的大小比例是8 : 1,也就是说Eden:Survivor From : Survivor To = 8:1:1。因此每次新生代可用内存空间为整个新生代容量的90%,而剩下的10%用来存放回收后存活的对象。
HotSpot实现的复制算法流程以下:
1. 当Eden区满的时候,会触发第一次Minor gc,把还活着的对象拷贝到Survivor From区;当Eden区再次触发Minor gc的时候,会扫描Eden区和From区域,对两个区域进行垃圾回收,通过此次回收后还存活的对象,则直接复制到To区域,并将Eden和From区域清空。
2. 当后续Eden又发生Minor gc的时候,会对Eden和To区域进行垃圾回收,存活的对象复制到From区域,并将Eden和To区域清空。
3. 部分对象会在From和To区域中复制来复制去,如此交换15次(由JVM参数MaxTenuringThreshold决定,这个参数默认是15),最终若是仍是存活,就存入到老年代。
发生在新生代的垃圾回收成为Minor GC,Minor GC又称为新生代GC,由于新生代对象大多都具有朝生夕灭的特性,所以Minor GC(采用复制算法)很是频繁,通常回收速度也比较快。
3. 标记整理算法(老年代回收算法)
复制算法在对象存活率较高的老年代会进行不少次的复制操做,效率很低,因此在栈的老年代不适用复制算法。
针对老年代对象存活率高的特色,提出了一种称之为”标记-整理算法”。标记过程仍与”标记-清除”过程一致,但后续步骤不是直接对可回收对象进行清理,而是让全部存活对象都向一端移动,而后直接清理掉端边界之外的内存。流程图以下:
发生在老年代的GC称为Full GC,又称为Major GC,其常常会伴随至少一次的Minor GC(并不是绝对,在Parallel Scavenge收集器中就有直接进行Full GC的策略选择过程)。Major GC的速度通常会比Minor GC慢10倍以上。
若是一个数据库声称支持事务的操做,那么该数据库必需要具有如下四个特性:
原子性是指事务包含的全部操做要么所有成功,要么所有失败回滚,这和前面两篇博客介绍事务的功能是同样的概念,所以事务的操做若是成功就必需要彻底应用到数据库,若是操做失败则不能对数据库有任何影响。
一致性是指事务必须使数据库从一个一致性状态变换到另外一个一致性状态,也就是说一个事务执行以前和执行以后都必须处于一致性状态。
拿转帐来讲,假设用户A和用户B二者的钱加起来一共是5000,那么无论A和B之间如何转帐,转几回帐,事务结束后两个用户的钱相加起来应该还得是5000,这就是事务的一致性。
隔离性是当多个用户并发访问数据库时,好比操做同一张表时,数据库为每个用户开启的事务,不能被其余事务的操做所干扰,多个并发事务之间要相互隔离。
即要达到这么一种效果:对于任意两个并发的事务T1和T2,在事务T1看来,T2要么在T1开始以前就已经结束,要么在T1结束以后才开始,这样每一个事务都感受不到有其余事务在并发地执行。
关于事务的隔离性数据库提供了多种隔离级别,稍后会介绍到。
持久性是指一个事务一旦被提交了,那么对数据库中的数据的改变就是永久性的,即使是在数据库系统遇到故障的状况下也不会丢失提交事务的操做。
例如咱们在使用JDBC操做数据库时,在提交事务方法后,提示用户事务操做完成,当咱们程序执行完成直到看到提示后,就能够认定事务以及正确提交,即便这时候数据库出现了问题,也必需要将咱们的事务彻底执行完成,不然就会形成咱们看到提示事务处理完毕,可是数据库由于故障而没有执行事务的重大错误。
以上介绍完事务的四大特性(简称ACID),如今重点来讲明下事务的隔离性,当多个线程都开启事务操做数据库中的数据时,数据库系统要能进行隔离操做,以保证各个线程获取数据的准确性,在介绍数据库提供的各类隔离级别以前,咱们先看看若是不考虑事务的隔离性,会发生的几种问题:
脏读是指在一个事务处理过程里读取了另外一个未提交的事务中的数据。
当一个事务正在屡次修改某个数据,而在这个事务中这屡次的修改都还未提交,这时一个并发的事务来访问该数据,就会形成两个事务获得的数据不一致。例如:用户A向用户B转帐100元,对应SQL命令以下
update account set money=money+100 where name=’B’; (此时A通知B) update account set money=money - 100 where name=’A’;
当只执行第一条SQL时,A通知B查看帐户,B发现确实钱已到帐(此时即发生了脏读),而以后不管第二条SQL是否执行,只要该事务不提交,则全部操做都将回滚,那么当B之后再次查看帐户时就会发现钱其实并无转。
不可重复读是指在对于数据库中的某个数据,一个事务范围内屡次查询却返回了不一样的数据值,这是因为在查询间隔,被另外一个事务修改并提交了。
例如事务T1在读取某一数据,而事务T2立马修改了这个数据而且提交事务给数据库,事务T1再次读取该数据就获得了不一样的结果,发送了不可重复读。
不可重复读和脏读的区别是,脏读是某一事务读取了另外一个事务未提交的脏数据,而不可重复读则是读取了前一事务提交的数据。
在某些状况下,不可重复读并非问题,好比咱们屡次查询某个数据固然以最后查询获得的结果为主。但在另外一些状况下就有可能发生问题,例如对于同一个数据A和B依次查询就可能不一样,A和B就可能打起来了……
幻读是事务非独立执行时发生的一种现象。例如事务T1对一个表中全部的行的某个数据项作了从“1”修改成“2”的操做,这时事务T2又对这个表中插入了一行数据项,而这个数据项的数值仍是为“1”而且提交给数据库。而操做事务T1的用户若是再查看刚刚修改的数据,会发现还有一行没有修改,其实这行是从事务T2中添加的,就好像产生幻觉同样,这就是发生了幻读。
幻读和不可重复读都是读取了另外一条已经提交的事务(这点就脏读不一样),所不一样的是不可重复读查询的都是同一个数据项,而幻读针对的是一批数据总体(好比数据的个数)。
如今来看看MySQL数据库为咱们提供的四种隔离级别:
① Serializable (串行化):可避免脏读、不可重复读、幻读的发生。
② Repeatable read (可重复读):可避免脏读、不可重复读的发生。
③ Read committed (读已提交):可避免脏读的发生。
④ Read uncommitted (读未提交):最低级别,任何状况都没法保证。
以上四种隔离级别最高的是Serializable级别,最低的是Read uncommitted级别,固然级别越高,执行效率就越低。像Serializable这样的级别,就是以锁表的方式(相似于Java多线程中的锁)使得其余的线程只能在锁外等待,因此平时选用何种隔离级别应该根据实际状况。在MySQL数据库中默认的隔离级别为Repeatable read (可重复读)。
在MySQL数据库中,支持上面四种隔离级别,默认的为Repeatable read (可重复读);而在Oracle数据库中,只支持Serializable (串行化)级别和Read committed (读已提交)这两种级别,其中默认的为Read committed级别。
在MySQL数据库中查看当前事务的隔离级别:
select @@tx_isolation;
在MySQL数据库中设置事务的隔离 级别:
set [glogal | session] transaction isolation level 隔离级别名称; set tx_isolation=’隔离级别名称;’
例1:查看当前事务的隔离级别:
例2:将事务的隔离级别设置为Read uncommitted级别:
或:
记住:设置数据库的隔离级别必定要是在开启事务以前!
若是是使用JDBC对数据库的事务设置隔离级别的话,也应该是在调用Connection对象的setAutoCommit(false)方法以前。调用Connection对象的setTransactionIsolation(level)便可设置当前连接的隔离级别,至于参数level,可使用Connection对象的字段:
在JDBC中设置隔离级别的部分代码:
后记:隔离级别的设置只对当前连接有效。对于使用MySQL命令窗口而言,一个窗口就至关于一个连接,当前窗口设置的隔离级别只对当前窗口中的事务有效;对于JDBC操做数据库来讲,一个Connection对象至关于一个连接,而对于Connection对象设置的隔离级别只对该Connection对象有效,与其余连接Connection对象无关。