1.什么是数据库事务?

事务是一个不可分割的数据库操作序列,也是数据库并发控制的基本单位,其执行的结果必须使数据库从一种一致性状态变到另一种一致性状态。事务是逻辑上的一组操作,要么都执行,要么都不执行。

2.事物的四大特性(ACID)介绍一下?

  • 原子性(Atomicity) :事务是一个原子操作单元,其对数据的修改,要么全都执行,要么全都不执行。
  • 一致性(Consistent) :在事务开始和完成时,数据都必须保持一致状态。这意味着所有相关的数据规则都必须应用于事务的修改,以保持数据的完整性。
  • 隔离性(Isolation) :数据库系统提供一定的隔离机制,保证事务在不受外部并发操作影响的“独立”环境执行。这意味着事务处理过程中的中间状态对外部是不可见的,反之亦然。
  • 持久性(Durable) :事务完成之后,它对于数据的修改是永久性的,即使出现系统故障也能够保持。

实现保证:
MySQL的存储引擎InnoDB使用重做日志保证一致性与持久性,回滚日志保证原子性,使用各种锁来保证隔离性。

3.说一下脏写、脏读、不可重读、幻读

3.1更新丢失(Lost Update)或脏写

当两个或多个事务选择同一行,然后基于最初选定的值更新该行时,由于每个事务都不知道其他事务的存在,就会发生丢失更新问题–最后的更新覆盖了由其他事务所做的更新。

3.2脏读(Dirty Reads)

一个事务正在对一条记录做修改,在这个事务完成并提交前,这条记录的数据就处于不一致的状态;这时,另一个事务也来读取同一条记录,如果不加控制,第二个事务读取了这些“脏”数据,并据此作进一步的处理,就会产生未提交的数据依赖关系。这种现象被形象的叫做“脏读”。

一句话:事务A读取到了事务B已经修改但尚未提交的数据,还在这个数据基础上做了操作。此时,如果B事务回滚,A读取的数据无效,不符合一致性要求。

3.3.不可重读(Non-Repeatable Reads)

一个事务在读取某些数据后的某个时间,再次读取以前读过的数据,却发现其读出的数据已经发生了改变、或某些记录已经被删除了!这种现象就叫做“不可重复读”。

一句话:事务A内部的相同查询语句在不同时刻读出的结果不一致,不符合隔离性

3.4.幻读(Phantom Reads)

一个事务按相同的查询条件重新读取以前检索过的数据,却发现其他事务插入了满足其查询条件的新数据,这种现象就称为“幻读”。

一句话:事务A读取到了事务B提交的新增数据,不符合隔离性

  1. 描述一下幻读:
  2. 产生的前提条件:在可重复度隔离级别下,使用“当前读”出现的一种现象
  3. 现象:能够看到其他事务插入的最新数据
  4. 描述一下当前读和快照读:
  5. 当前读:读取最新版本的数据,并对读取的记录加锁(间隙锁+行锁)(例如 select ... for update)
  6. 快照读:看不到其他事务插入的新数据(在执行select操作时生成一张快照,不是开启事务的时候,生成快照之后的变动都无感知)

4.什么是事务的隔离级别?MySQL的默认隔离级别是什么?

为了达到事务的四大特性,数据库定义了4种不同的事务隔离级别,由低到高依 次为Read uncommitted、Read committed、Repeatable read、 Serializable,这四个级别可以逐个解决脏读、不可重复读、幻读这几类问题

隔离级别 并发问题
读未提交 可能会导致脏读、幻读或不可重复读
读已提交 可能会导致幻读或不可重复读
可重复读 可能会导致幻读
可串行化 不会产⽣⼲扰
  • READ-UNCOMMITTED(读取未提交): 最低的隔离级别,允许读取尚未提交的数据变更,可能会导致脏读、幻读或不可重复读。
  • READ-COMMITTED(读取已提交): 允许读取并发事务已经提交的数 据,可以阻止脏读,但是幻读或不可重复读仍有可能发生。
  • REPEATABLE-READ(可重复读): 对同一字段的多次读取结果都是一致 的,除非数据是被本身事务自己所修改,可以阻止脏读和不可重复读,但幻读仍有可能发生。
  • SERIALIZABLE(可串行化): 最高的隔离级别,完全服从ACID的隔离级别。所有的事务依次逐个执行,这样事务之间就完全不可能产生干扰,也就是说,该级别可以防止脏读、不可重复读以及幻读。

Mysql 默认采用的 REPEATABLE_READ(可重复读)隔离级别 ,Oracle 默认采用的 READ_COMMITTED(读已提交)隔离级别

4.1.解决幻读的方法

  • 1、数据库的隔离级别默认是可重复读,可能导致幻读问题,可以将级别调整为可串行化。但是效率会大大降低
  • 2、使用MVCC解决快照幻读问题(如简单select),读取的不是最新的数据。维护一个字段作为version,这样可以控制到每次只能有一个人更新一个版本。

    1. select id from table_xx where id = ? and version = V
    2. update id from table_xx where id = ? and version = V+1
  • 3、如果需要读最新的数据,可以通过GapLock+Next-KeyLock可以解决当前读幻读问题

    1. select id from table_xx where id > 100 for update;
    2. select id from table_xx where id > 100 lock in share mode;

    5.对MySQL的锁了解吗

    当数据库有并发事务的时候,可能会产生数据的不一致,这时候需要一些机制来保证访问的次序,锁机制就是这样的一个机制。就像酒店的房间,如果大家随意进出,就会出现多人抢夺同一个房间的情况,而在房间上装上锁,申请到钥匙的人才可以入住并且将房间锁起来,其他人只有等他使用完毕才可以再次使用。

    6.隔离级别与锁的关系

  • 在读未提交(Read Uncommitted)级别下,读取数据不需要加共享锁,这样就不会跟被修改的数据上的排他锁冲突

  • 在读已提交(Read Committed)级别下,读操作需要加共享锁,但是在语句执行完以后释放共享锁;
  • 在可重复读(Repeatable Read)级别下,读操作需要加共享锁,但是在事务提交之前并不释放共享锁,也就是必须等待事务执行完毕以后才释放共享锁。
  • 可串行化(SERIALIZABLE) 是限制性最强的隔离级别,因为该级别锁定整个范围的键,并一直持有锁,直到事务完成。

7.按照锁的粒度分数据库锁有哪些?锁机制与InnoDB锁算法

可以按照锁的粒度把数据库锁分为行级锁(INNODB引擎)、表级锁(MYISAM引擎)和页级锁(BDB引擎 )。

7.1.MyISAM和InnoDB存储引擎使用的锁:

  • MyISAM采用表级锁(table-level locking)。
  • InnoDB支持行级锁(row-level locking)和表级锁,默认为行级锁

    7.2.行级锁,表级锁对比

    InnoDB⽀持⾏级锁(row-level locking)和表级锁,默认为⾏级锁
    InnoDB按照不同的分类的锁:

  • 共享/排它锁(Shared and Exclusive Locks):行级别锁,

  • 意向锁(Intention Locks),表级别锁
  • 间隙锁(Gap Locks),锁定一个区间
  • 记录锁(Record Locks),锁定一个行记录

    7.2.1.表级锁:(串行化)

    Mysql中锁定 粒度最大的一种锁,对当前操作的整张表加锁,实现简单 ,资源消耗也比较少,加锁快,不会出现死锁 。其锁定粒度最大,触发锁冲突的概率最高,并发度最低,MyISAM和 InnoDB引擎都支持表级锁。

    7.2.2.行级锁:(RR可重复读、RC读已提交)

    Mysql中锁定 粒度最小的一种锁,只针对当前操作的行进行加锁。 行级锁能大大减少数据库操作的冲突。其加锁粒度最小,并发度高,但加锁的开销也最大,加锁慢,会出现死锁。 InnoDB支持的行级锁,包括如下几种:

  • 记录锁(Record Lock): 对索引项加锁,锁定符合条件的行。其他事务不能修改和删除加锁项;

  • 间隙锁(Gap Lock): 对索引项之间的“间隙”加锁,锁定记录的范围,不包含索引项本身,其他事务不能在锁范围内插入数据。
  • Next-key Lock: 锁定索引项本身和索引范围。即Record Lock和Gap Lock的结合。可解决幻读问题。

    8.MySQL都有哪些锁呢?

    锁的粒度取决于具体的存储引擎,InnoDB实现了行级锁,页级锁,表级锁。 他们的加锁开销从大到小,并发能力也是从大到小。
    从锁的类别上来讲,有共享锁和排他锁。

  • 共享锁(Shared Lock,又称S锁、读锁。针对行锁): 当有事务对数据加读锁后,其他事务只能对锁定的数据加读锁,不能加写锁(排他锁),所以其他事务只能读,不能写。主要为了支持并发读的场景,读时不允许写操作。

    1. 加锁方式:
    2. select * from T where id=1 lock in share mode;
    3. 释放方式:
    4. commit、rollback;
  • 排他锁(EXclusive Lock),又称X锁、独占锁、写锁。针对行锁)。 当有事务对数据加写锁后,其他事务不能再对锁定的数据加任何锁,又因为InnoDB对select语句默认不加锁,所以其他事务除了不能写操作外,照样是允许读的(尽管不允许加读锁)。主要为了在事务进行写操作时,不允许其他事务修改。

    1. 加锁方式:
    2. 自动:DML语句默认加写锁
    3. 手动:select * from T where id=1 for update;
    4. 释放方式:
    5. commit、rollback;
  • 意向锁(Intention Lock,又称I锁。针对表锁):当有事务给表的数据行加了共享锁或排他锁,同时会给表设置一个标识,代表已经有行锁了,其他事务要想对表加表锁时,就不必逐行判断有没有行锁可能跟表锁冲突了,直接读这个标识就可以确定自己该不该加表锁。特别是表中的记录很多时,逐行判断加表锁的方式效率很低。而这个标识就是意向锁。(主要是为了提高加表锁的效率。)

    • 意向共享锁,IS锁,对整个表加共享锁之前,需要先获取到意向共享锁。
    • 意向排他锁,IX锁,对整个表加排他锁之前,需要先获取到意向排他锁。
      1. 加锁方式:
      2. 无法手动创建。

      9.MySQL中InnoDB引擎的行锁是怎么实现的?

      答:InnoDB是基于索引来完成行锁
      例: select * from tab_with_index where id = 1 for update;
      for update可以根据条件来完成行锁锁定,并且id是有索引键的列,如果id不是索引键那么InnoDB将完成表锁,并发将无从谈起

      10.什么是死锁?怎么解决?

      死锁是指两个或多个事务在同一资源上相互占用,并请求锁定对方的资源,从而导致恶性循环的现象。
      常见的解决死锁的方法
      1、如果不同程序会并发存取多个表,尽量约定以相同的顺序访问表,可以大大降低死锁几率。
      2、在同一个事务中,尽可能做到一次锁定所需要的所有资源,减少死锁产生概率;
      3、对于非常容易产生死锁的业务部分,可以尝试使用升级锁定颗粒度,通过表级锁定来减少死锁产生的概率; 如果业务处理不好可以用分布式事务锁或者使用乐观锁

      11.数据库的乐观锁和悲观锁是什么?怎么实现的?

  • 悲观锁:假定会发生并发冲突,屏蔽一切可能违反数据完整性的操作。在查询完数据的时候就把事务锁起来,直到提交事务。实现方式:使用数据库中的锁机制

  • 乐观锁:假设不会发生并发冲突,只在提交操作时检查是否违反数据完整性。在修改数据的时候把事务锁起来,通过version的方式来进行锁定。实现方式:一般会使用版本号机制或CAS算法实现。

乐观锁适用于写比较少的情况下(多读场景),即冲突真的很少发生的时候,这样可以省去了锁的开销,加大了系统的整个吞吐量。 但如果是多写的情况,一般会经常产生冲突,这就会导致上层应用会不断的进行 retry,这样反倒是降低了性能,所以一般多写的场景下用悲观锁就比较合适。

12.