数据库知识(1)

· ☕ 8 分钟 · 👻 Victor
🏷️
  • #Database
  • 数据库事务

    事务(Transaction)是由一系列对系统中数据进行访问与更新的操作所组成的一个程序执行逻辑单元。事务是DBMS中最基础的单位,事务不可分割。

    数据库事务四大特性(ACID)

    1. 原子性:不可分割,要么全部完成提交,要么全部回滚
    2. 一致性:一致性是指事务必须使数据库从一个一致性状态变换到另一个一致性状态,也就是说一个事务执行之前和执行之后都必须处于一致性状态。
    3. 隔离性:多用户一起进行事务操作互不影响,相互隔离。
    4. 持久性:一个事务一旦被提交了,那么对数据库中的数据的改变就是永久性的,即便是在数据库系统遇到故障的情况下也不会丢失提交事务的操作。

    数据库事务隔离级别

    读取的问题

    users
    id name age
    1 Joe 20
    2 Jill 25
    1. 脏读

      当一个事务允许读取另外一个事务修改但未提交的数据时,就可能发生脏读。

      脏读

      例子中,事务2修改了一行,但是没有提交,事务1读了这个没有提交的数据。现在如果事务2回滚了刚才的修改或者做了另外的修改的话,事务1中查到的数据就是不正确的了。

    2. 不可重复读

      在一次事务中,当一行数据获取两遍得到不同的结果表示发生了“不可重复读”.

      在基于锁的并发控制中“不可重复读”现象发生在当执行SELECT 操作时没有获得读锁或者SELECT操作执行完后马上释放了读锁; 多版本并发控制中当没有要求一个提交冲突(commit conflict)的事务回滚也会发生“不可重复读”现象。

      不可重复读

      这个例子中,事务2提交成功,因此他对id为1的行的修改就对其他事务可见了。但是事务1在此前已经从这行读到了另外一个“age”的值。在可序列化(SERIALIZABLE)和可重复读的隔离级别,数据库在第二次SELECT请求的时候应该返回事务2更新之前的值。在提交读和未提交读,返回的是更新之后的值,这个现象就是不可重复读。

      解决不可重复读:

      1. 要求事务2延迟到事务1提交或者回滚之后再执行。这种方式实现了T1, T2 的串行化调度。串行化调度可以支持可重复读。
      2. 另一种策略是多版本并发控制。为了得到更好的并发性能,允许事务2先提交。但因为事务1在事务2之前开始,事务1必须在其开始执行时间点的数据库的快照上面操作。当事务1最终提交时候,数据库会检查其结果是否等价于T1, T2串行调度。如果等价,则允许事务1提交,如果不等价,事务1需要回滚并抛出个串行化失败的错误
    3. 幻读

      在事务执行过程中,当两个完全相同的查询语句执行得到不同的结果集。这种现象称为“幻影读(phantom read)”

      当事务没有获取*范围锁*的情况下执行*SELECT … WHERE*操作可能会发生“幻影读”。

      “幻影读”是不可重复读的一种特殊场景:当事务1两次执行SELECT … WHERE检索一定范围内数据的操作中间,事务2在这个表中创建了(如INSERT)了一行新数据,这条新数据正好满足事务1的“WHERE”子句。

      幻读

      需要指出的是事务1执行了两遍同样的查询语句。如果设了最高的隔离级别,两次会得到同样的结果集,这也正是数据库在可序列化(SERIALIZABLE)隔离级别上需要满足的。但是在较低的隔离级别上,第二次查询可能会得到不同的结果集。

    事务隔离其实就是为了解决上面提到的脏读、不可重复读、幻读这几个问题

    隔离级别

    1. 读未提交(READ UNCOMMITTED)

      未提交读(READ UNCOMMITTED)是最低的隔离级别。允许“脏读”(dirty reads),事务可以看到其他事务“尚未提交”的修改。

    2. 读提交 (READ COMMITTED)

      在提交读(READ COMMITTED)级别中,基于锁机制并发控制的DBMS需要对选定对象的写锁一直保持到事务结束,但是读锁在SELECT操作完成后马上释放。

    3. 可重复读 (REPEATABLE READ)

      在可重复读(REPEATABLE READS)隔离级别中,基于锁机制并发控制的DBMS需要对选定对象的读锁(read locks)和写锁(write locks)一直保持到事务结束。

      为了解决不可重复读,或者为了实现可重复读,MySQL 采用了 MVVC (多版本并发控制) 的方式。

      我们在数据库表中看到的一行记录可能实际上有多个版本,每个版本的记录除了有数据本身外,还要有一个表示版本的字段,记为 row trx_id,而这个字段就是使其产生的事务的 id,事务 ID 记为 transaction id,它在事务开始的时候向事务系统申请,按时间先后顺序递增。

      版本

      按照上面这张图理解,一行记录现在有 3 个版本,每一个版本都记录这使其产生的事务 ID,比如事务A的transaction id 是100,那么版本1的row trx_id 就是 100,同理版本2和版本3。

      读提交和可重复读的时候都提到了一个词,叫做快照,学名叫做一致性视图,这也是可重复读和不可重复读的关键。可重复读是在事务开始的时候生成一个当前事务全局性的快照,而读提交则是每次执行语句的时候都重新生成一次快照。

    4. 串行化 (SERIALIZABLE)

      串行化是4种事务隔离级别中隔离效果最好的,解决了脏读、可重复读、幻读的问题,但是效果最差,它将事务的执行变为顺序执行,与其他三个隔离级别相比,它就相当于单线程,后一个事务的执行必须等待前一个事务结束。读的时候加共享锁,也就是其他事务可以并发读,但是不能写。写的时候加排它锁,其他事务不能并发写也不能并发读。

      在基于锁机制并发控制的DBMS实现可串行化,要求在选定对象上的读锁和写锁保持直到事务结束后才能释放。在SELECT 的查询中使用一个“WHERE”子句来描述一个范围时应该获得一个“范围锁”(range-locks)。这种机制可以避免“幻读”(phantom reads)现象

    并发写问题

    存在这的情况,两个事务,对同一条数据做修改。最后结果应该是哪个事务的结果呢,肯定要是时间靠后的那个。

    假设事务A执行 update 操作, update 的时候要对所修改的行加行锁,这个行锁会在提交之后才释放。而在事务A提交之前,事务B也想 update 这行数据,于是申请行锁,但是由于已经被事务A占有,事务B是申请不到的,此时,事务B就会一直处于等待状态,直到事务A提交,事务B才能继续执行,如果事务A的时间太长,那么事务B很有可能出现超时异常。如下图所示。

    并发写

    需要注意的是,加入查询的字段设置了索引,那么数据库系统可以很方便的给该行数据加上一个行锁;加入没有设置索引的话,数据库系统会直接将该表的所有行加上行锁,然后再遍历一遍,释放掉没用的行锁。

    解决幻读

    并发写问题的解决方式就是行锁,而解决幻读用的也是锁,叫做间隙锁,MySQL 把行锁和间隙锁合并在一起,解决了并发写和幻读的问题,这个锁叫做 Next-Key锁。

    在数据库中会为索引维护一套B+树,用来快速定位行记录。B+索引树是有序的,所以会把这张表的索引分割成几个区间。可能会有如下的B+树:

    数据库维护的B+树

    如图所示,分成了3 个区间,(负无穷,10]、(10,30]、(30,正无穷],在这3个区间是可以加间隙锁的。

    幻读事务处理过程

    在事务A提交之前,事务B的插入操作只能等待,这就是间隙锁起得作用。当事务A执行update user set name='风筝2号’ where age = 10; 的时候,由于条件 where age = 10 ,数据库不仅在 age =10 的行上添加了行锁,而且在这条记录的两边,也就是(负无穷,10]、(10,30]这两个区间加了间隙锁,从而导致事务B插入操作无法完成,只能等待事务A提交。不仅插入 age = 10 的记录需要等待事务A提交,age<10、10<age<30 的记录页无法完成,而大于等于30的记录则不受影响,这足以解决幻读问题了。

    这是有索引的情况,如果 age 不是索引列,那么数据库会为整个表加上间隙锁。所以,如果是没有索引的话,不管 age 是否大于等于30,都要等待事务A提交才可以成功插入。

    隔离级别vs读现象

    隔离级别 脏读 不可重复读 幻影读
    未提交读 可能发生 可能发生 可能发生
    提交读 - 可能发生 可能发生
    可重复读 - - 可能发生
    可序列化 - - -

    可序列化(Serializable)隔离级别不等同于可串行化(Serializable)。可串行化调度是避免以上三种现象的必要条件,但不是充分条件。

    “可能发生”表示这个隔离级别会发生对应的现象,“-”表示不会发生。采用哪种隔离级别要根据系统需求权衡决定,其中,可重复读是 MySQL 的默认级别。

    隔离级别vs 锁持续时间

    在基于锁的并发控制中,隔离级别决定了锁的持有时间。“C”-表示锁会持续到事务提交。 “S” –表示锁持续到当前语句执行完毕。如果锁在语句执行完毕就释放则另外一个事务就可以在这个事务提交前修改锁定的数据,从而造成混乱

    隔离级别 写操作 读操作 范围操作 (…where…)
    未提交读 S S S
    提交读 C S S
    可重复读 C C S
    可序列化 C C C

    MySQL隔离操作:

    1. 查看Mysql默认的隔离级别:
    1
    2
    3
    4
    5
    6
    7
    
    mysql> show variables like 'transaction_isolation';
    +-----------------------+-----------------+
    | Variable_name         | Value           |
    +-----------------------+-----------------+
    | transaction_isolation | REPEATABLE-READ |
    +-----------------------+-----------------+
    1 row in set, 1 warning (0.11 sec)
    

    可以发现Mysql默认的隔离级别为可重复读

    修改改数据库的隔离级别:set [作用域] transaction isolation level [事务隔离级别],具体如下:

    1
    
    SET [SESSION | GLOBAL] TRANSACTION ISOLATION LEVEL {READ UNCOMMITTED | READ COMMITTED | REPEATABLE READ | SERIALIZABLE}
    

    数据库索引(INDEX)

    数据库索引是为了增加查询速度而对表字段附加的一种标识,是对数据库表中一列或多列的值进行排序的一种结构。

    Mysql创建索引:

    1
    
    CREATE INDEX INDEX_NAME ON TABLE_NAME (COLUMN_LIST)
    

    数据库的三大范式

    第一范式

    当关系模式R的所有属性都不能再分解为更基本的数据单位时,称R是满足第一范式,即属性不可分

    第二范式

    如果关系模式R满足第一范式,并且R得所有非主属性都完全依赖于R的每一个候选关键属性,称R满足第二范式

    第三范式

    设R是一个满足第一范式条件的关系模式,X是R的任意属性集,如果X非传递依赖于R的任意一个候选关键字,称R满足第三范式,即非主属性不传递依赖于键码


    参考链接:

    1. https://www.cnblogs.com/fengzheng/p/12557762.html
    2. https://zh.wikipedia.org/wiki/%E4%BA%8B%E5%8B%99%E9%9A%94%E9%9B%A2#%E8%84%8F%E8%AF%BB
    分享

    redisread
    作者
    Victor
    Full Stack