MySql事务4种隔离级别以及悲观锁和乐观锁

前言:在那鬼公司呆着发现自己居然把事务给搞明白了。

缘由:公司做的一个项目在进行首页内容显示的时候发现查询结果特别慢,有时候需要一到五分钟才能显示出结果。于是乎,我就顺着SQL语句查询慢的原因找了下去,找到了结果最有可能的应该是出现了锁和死锁的问题。由于开发的项目存在一个高并发的情况,所以顺着高并发找到了事务的隔离级别以及悲观锁和乐观锁。

SQL语句查询慢的48大原因    https://blog.csdn.net/findmyself_for_world/article/details/49978279

借鉴:脏读、不可重复读 共享锁、悲观锁 和 事务五种隔离级别

事务的ACDI

  1. 原子性 atomiciy 
    一个事务必须保证其中的操作要么全部执行,要么全部回滚,不存在只执行了一部分这种情况出现
  2. 一致性 consistency
    数据必须保证从一种一致性的状态转换为另一种一致性状态
  3. 隔离性 isolation
    在一个事务未执行完毕时,通常会保证其他session无法看到这个事务的执行过程
  4. 持久性 durability
    事务一旦commit,则数据就会保存下来,即使提交完之后系统崩溃,数据也不会丢失

事务的隔离级别

  1. READ UNCOMMITTED(未提交读,可脏读)
        事务中的修改,即使没有提交,对其他会话也是可见的,可以读取未提交的数据--脏读。脏读会导致很多问题,一般不适用这个隔离级别
  2. READ COMMMITTED(提交读或不可重复读,幻读)
        保证一个事务修改的数据提交后才能被另一个事务读取,另外一个事务不能读取该事务未提交的数据,可以避免脏读,但是会出现不可重复读幻读的问题(既锁定正在读取的行)
  3. REPEATABLE READ(可重复读)
        一个事务中多次执行统一读SQL,返回结果一样,这个隔离级别解决了脏读的问题,幻读问题。innodb中使用next-key锁对当前读进行加锁,锁住行以及可能产生幻读的插入位置,阻止新的数据插入产生幻读
  4. SERIALIZABLE(可串行化)
        最强的隔离级别,通过给事务中每次读取的行加锁,保证不产生幻读问题,但是会导致大量超时以及锁争用问题。

解释:   

  • 脏读:脏读就是指当一个事务正在访问数据,并且对数据进行了修改,而这种修改还没有提交到数据库,这时,另一个事务也访问了这条数据,然后使用了这个数据,比如事务一执行下面的sql语句:
    >>>select name from table where id = 1
    >>>小白
    >>>begin
    >>>update table  set name='小黑' where id=1 

    此时sql语句虽然执行了update语句,但未commit
    此时事务二执行了一条查询语句:
    >>>select name from table where id =1
    此时返回的结果将会是小黑
    这就是所谓的脏读
    举个例子
      张三的工资为5000,事务A中把他的工资改为8000,但事务A尚未提交。
      与此同时,
      事务B正在读取张三的工资,读取到张三的工资为8000。
      随后,
      事务A发生异常,而回滚了事务。张三的工资又回滚为5000。
      最后,
      事务B读取到的张三工资为8000的数据即为脏数据,事务B做了一次脏读。

  • 不可重复读
    是指在一个事务内,多次读取同一数据。在这个事务还没有结束时,另外一个事务也访问了同一数据,那么在第一个事务中的两次读数据之间,由于第二个事务的修改,那么第一个事务两次督导的数据可能是不一样的,这样就发生了在一个事务内两次读到的数据是不一样的,因此称为是不可重复读。
    举个例子
    在事务A中,读取到张三的工资为5000,操作没有完成,事务还没提交。
      与此同时,
      事务B把张三的工资改为8000,并提交了事务。
      随后,
      在事务A中,再次读取张三的工资,此时工资变为8000。在一个事务中前后两次读取的结果并不致,导致了不可重复读。
  • 幻读
    是指当事务不是独立执行时发生的一种现象,例如第一个事务对一个表中的数据进行了修改,这种修改涉及到表中的全部数据行,同时,第二个事务也修改了这个表中的数据,这种修改是向表中插入一行新数据。那么以后就会发生操作第一个事务的用户发现表中还有没有修改的数据行
    举个例子
    目前工资为5000的员工有10人,事务A读取所有工资为5000的人数为10人。
      此时,
      事务B插入一条工资也为5000的记录。
      这是,事务A再次读取工资为5000的员工,记录为11人。此时产生了幻读。

悲观锁和乐观锁   

悲观锁(Pessimistic Lock)

      悲观锁,正如其名,它指的是对数据被外界(包括本系统当前的其他事务,以及来自外部系统的事务处理)修改持保守态度,因此在整个数据处理过程中,将数据处于锁定状态。悲观锁的实现,往往依靠数据库提供的锁机制(也只有数据库层提供的锁机制才能真正保证数据访问的排他性,否则,即使在本系统中实现了加锁机制,也无法保证外部系统不会修改数据)。

举个例子

首先在mysql数据库里创建一个ta表,然后创建两个字段id和num,并增加一个数据。

然后在mysql下创建两个界面,第一个执行

>>>begin

>>>select * from ta for update

此时在第二个界面执行

>>>update ta set num = 10 where id =1;

此时你会发现update语句无法正常执行,这是因为悲观锁将这行数据锁住了,在没有commit这条数据之前,别的事务是无法修改这条数据的。

最后我们在第一个界面执行

>>>commit

发现我们在第二个界面执行的update语句能够正常执行了。


乐观锁(Optimistic Lock)
      相对悲观锁而言,乐观锁机制采取了更加宽松的加锁机制。悲观锁大多数情况下依靠数据库的锁机制实现,以保证操作最大程度的独占性。但随之而来的就是数据库性能的大量开销,特别是对长事务而言,这样的开销往往无法承受。

     乐观锁机制在一定程度上解决了这个问题。乐观锁,大多是基于数据版本( Version )记录机制实现。何谓数据版本?即为数据增加一个版本标识,在基于数据库表的版本解决方案中,一般是通过为数据库表增加一个 “version” 字段来实现。读取出数据时,将此版本号一同读出,之后更新时,对此版本号加一。此时,将提交数据的版本数据与数据库表对应记录的当前版本信息进行比对,如果提交的数据版本号大于数据库表当前版本号,则予以更新,否则认为是过期数据。

举个例子

假设数据库中帐户信息表中有一个 version 字段,当前值为 1 ;而当前帐户余额字段( balance )为 $100 。 操作员 A 此时将其读出

( version=1 ),并从其帐户余额中扣除 $50( $100-$50 )。 2 在操作员 A 操作的过程中,操作员 B 也读入此用户信息( version=1 ),并 从其帐

户余额中扣除 $20 ( $100-$20 )。 3 操作员 A 完成了修改工作,将数据版本号加一( version=2 ),连同帐户扣 除后余额( balance=$50 ),提交

至数据库更新,此时由于提交数据版本大 于数据库记录当前版本,数据被更新,数据库记录 version 更新为 2 。 4 操作员 B 完成了操作,也将版本号加一

( version=2 )试图向数据库提交数 据( balance=$80 ),但此时比对数据库记录版本时发现,操作员 B 提交的 数据版本号为 2 ,数据库记录当前版

本也为 2 ,不满足 “ 提交版本必须大于记 录当前版本才能执行更新 “ 的乐观锁策略,因此,操作员 B 的提交被驳回。这样,就避免了操作员 B 用基于

version=1 的旧数据修改的结果覆盖操作 员 A 的操作结果的可能。 从上面的例子可以看出,乐观锁机制避免了长事务中的数据库加锁开销(操作员 A


和操作员 B 操作过程中,都没有对数据库数据加锁),大大提升了大并发量下的系 统整体性能表现。 需要注意的是,乐观锁机制往往基于系统中的数据存储

逻辑,因此也具备一定的局 限性,如在上例中,由于乐观锁机制是在我们的系统中实现,来自外部系统的用户 余额更新操作不受我们系统的控制,因此可能

会造成脏数据被更新到数据库中。在 系统设计阶段,我们应该充分考虑到这些情况出现的可能性,并进行相应调整(如将乐观锁策略在数据库存储过程中实

现,对外只开放基于此存储过程的数据更新途 径,而不是将数据库表直接对外公开)。 Hibernate 在其数据访问引擎中内置了乐观锁实现。如果不用考虑外

部系统对数 据库的更新操作,利用 Hibernate 提供的透明化乐观锁实现,将大大提升我们的 生产力。

结语: 到这里,关于事务的内容基本讲完了,大家对事务是不是也有了一个初步的了认识呢,下面引用几个前辈们案例。


后续:

案例一:订票系统案例,某航班只有一张机票,假定有1w个人打开你的网站来订票,问你如何解决并发问题(可扩展到任何高并发网站要考虑的并发读写问题)

    问题,1w个人来访问,票没出去前要保证大家都能看到有票,不可能一个人在看到票的时候别人就不能看了。到底谁能抢到,那得看这个人的“运气”(网络快慢等)其次考虑的问题,并发,1w个人同时点击购买,到底谁能成交?总共只有一张票。

首先我们容易想到和并发相关的几个方案 :锁同步同步更多指的是应用程序的层面,多个线程进来,只能一个一个的访问,java中指的是syncrinized关键字。锁也有2个层面,一个是java中谈到的对象锁,用于线程同步;另外一个层面是数据库的锁;如果是分布式的系统,显然只能利用数据库端的锁来实现。

假定我们采用了同步机制或者数据库物理锁机制,如何保证1w个人还能同时看到有票,显然会牺牲性能,在高并发网站中是不可取的。使用hibernate后我们提出了另外一个概念:乐观锁悲观锁(即传统的物理锁);采用乐观锁即可解决此问题。乐观锁意思是不锁定表的情况下,利用业务的控制来解决并发问题,这样即保证数据的并发可读性又保证保存数据的排他性,保证性能的同时解决了并发带来的脏数据问题。hibernate中如何实现乐观锁:前提:在现有表当中增加一个冗余字段,version版本号, long类型

原理:

1)只有当前版本号》=数据库表版本号,才能提交

2)提交成功后,版本号version ++

实现很简单:在ormapping增加一属性optimistic-lock="version"即可,以下是样例片段

<hibernate-mapping>

<class name="com.insigma.stock.ABC" optimistic-lock="version" table="T_Stock" schema="STOCK">


案例二、股票交易系统、银行系统,大数据量你是如何考虑的

首先,股票交易系统的行情表,每几秒钟就有一个行情记录产生,一天下来就有(假定行情3秒一个) 股票数量×20×60*6 条记录,一月下来这个表记录数量多大? oracle中一张表的记录数超过100w后 查询性能就很差了,如何保证系统性能?再比如,中国移动有上亿的用户量,表如何设计?把所有用于存在于一个表么?

所以,大数量的系统,必须考虑表拆分-(表名字不一样,但是结构完全一样),通用的几种方式:(视情况而定)

1)按业务分,比如 手机号的表,我们可以考虑 130开头的作为一个表,131开头的另外一张表 以此类推

2)利用oracle的表拆分机制做分表

3)如果是交易系统,我们可以考虑按时间轴拆分,当日数据一个表,历史数据弄到其它表。这里历史数据的报表和查询不会影响当日交易。

当然,表拆分后我们的应用得做相应的适配。单纯的or-mapping也许就得改动了。比如部分业务得通过存储过程等

此外,我们还得考虑缓存

这里的缓存,指的不仅仅是hibernate,hibernate本身提供了一级二级缓存。这里的缓存独立于应用,依然是内存的读取,假如我们能减少数据库频繁的访问,那对系统肯定大大有利的。比如一个电子商务系统的商品搜索,如果某个关键字的商品经常被搜,那就可以考虑这部分商品列表存放到缓存(内存中去),这样不用每次访问数据库,性能大大增加。简单的缓存大家可以理解为自己做一个hashmap,把常访问的数据做一个keyvalue是第一次从数据库搜索出来的值,下次访问就可以从map里读取,而不读数据库;专业些的目前有独立的缓存框架比如memcached 等,可独立部署成一个缓存服务器。

常见的提高高并发下访问的效率的手段

首先要了解高并发的的瓶颈在哪里?

     1、可能是服务器网络带宽不够

     2.可能web线程连接数不够

     3.可能数据库连接查询上不去。

     根据不同的情况,解决思路也不同。

  1. 像第一种情况可以增加网络带宽,DNS域名解析分发多台服务器。

  2. 负载均衡,前置代理服务器nginx、apache等等

  3. 数据库查询优化,读写分离,分表等等

最后我也复制一些在高并发下面需要常常需要处理的内容:

  • 尽量使用缓存,包括用户缓存,信息缓存等,多花点内存来做缓存,可以大量减少与数据库的交互,提高性能。

  • 用jprofiler等工具找出性能瓶颈,减少额外的开销。

  • 优化数据库查询语句,减少直接使用hibernate等工具的直接生成语句(仅耗时较长的查询做优化)。

  • 优化数据库结构,多做索引,提高查询效率。

  • 统计的功能尽量做缓存,或按每天一统计或定时统计相关报表,避免需要时进行统计的功能。

  • 能使用静态页面的地方尽量使用,减少容器的解析(尽量将动态内容生成静态html来显示)。

  • 解决以上问题后,使用服务器集群来解决单台的瓶颈问题。








猜你喜欢

转载自blog.csdn.net/sinat_39634657/article/details/80992648