宝马娱乐在线112222 > 网络应用 > 宝马娱乐在线MySQL InnoDB如何保证事务特性示例详

原标题:宝马娱乐在线MySQL InnoDB如何保证事务特性示例详

浏览次数:189 时间:2020-04-28

master thread 每一秒将重做日志缓冲刷新到重做日志文件; 每个事务提交时 当重做日志缓冲池剩余空间小于1/2时

binlog redolog undolog 区别

  • binlog(二进制日志)就是典型的逻辑日志,而事务日志(redo log)则记录的物理日志

  • binlog,是mysql服务层产生的日志,常用来进行数据恢复、数据库复制,常见的mysql主从架构,就是采用slave同步master的binlog实现的, 另外通过解析binlog能够实现mysql到其他数据源(如ElasticSearch)的数据复制。

  • redo log就是保存执行的SQL语句到一个指定的Log文件,当Mysql执行recovery时重新执行redo log记录的SQL操作即可。当客户端执行每条SQL(更新语句)时,redo log会被首先写入log buffer;当客户端执行COMMIT命令时,log buffer中的内容会被视情况刷新到磁盘。redo log在磁盘上作为一个独立的文件存在,即Innodb的log文件。当数据库或主机失效重启时,会根据redo log进行数据的恢复,如果redo log中有事务提交,则进行事务提交修改数据。这样实现了事务的原子性、一致性和持久性。

  • Undo Log: 除了记录redo log外,当进行数据修改时还会记录undo log,undo log用于数据的撤回操作,它记录了修改的反向操作,比如,插入对应删除,修改对应修改为原来的数据,通过undo log可以实现事务回滚,并且可以根据undo log回溯到某个特定的版本的数据,实现MVCC。

  1. redo log 是在存储引擎层产生的,binlog是在数据库上层的一种逻辑日志,任何存储引擎均会产生binlog.
  2. binlog记录的是sql语句, 重做日志则记录的是对每个页的修改.
  3. 写入的时间点不一样. binlog 是在事务提交后进行一次写入,redo log在事务的进行中不断的被写入.
  4. redo log 是等幂操作(执行多次等于执行一次,redo log 记录<T0,A,950>记录新值,执行多少次都一样) , binlog 不一样;
    redo log 是可能是多条记录, 如:
    <T0,start>
    <Action1> ..... <ActionN>
    <t0,commit>
    既有start,又有commit 才是一条完整的redo log。才会被执行,缺失commit在恢复时是不会被执行的.
    如遇到并发写入,则redo log 还有可能是如下的情况:
    T1,T2,T1,*T2,T3,T1,*T3,*T1
    带*的是事务提交的时间. (从左到右的时间顺序)
    redo log ,每个事务对应多个日志条目. 重做日志是并发写入的. 无顺序.
    binlog,则如下:
    T1,T4,T3,T2,T8,T6,T7,T5

1, redo log(事务日志)保证事务的原子性和持久性(物理日志)
2, undo log保证事务的一致性,InnoDB的MVCC也是用undo log来实现的(逻辑日志).
3, redo log中带有有checkPoint,用来高效的恢复数据.
4, 物理日志记录的是修改页的的详情,逻辑日志记录的是操作语句. 物理日志恢复的速度快于逻辑日志.

所以如果在新session中执行如下语句都会报错[Err] 1205 - Lock wait timeout exceeded; try restarting transaction

MVCC实现

  • innodb中通过B+树作为索引的数据结构,并且主键所在的索引为ClusterIndex(聚簇索引), ClusterIndex中的叶子节点中保存了对应的数据内容。一个表只能有一个主键,所以只能有一个聚簇索引,如果表没有定义主键,则选择第一个非NULL唯一索引作为聚簇索引,如果还没有则生成一个隐藏id列作为聚簇索引
  • 除了Cluster Index外的索引是Secondary Index(辅助索引)。辅助索引中的叶子节点保存的是聚簇索引的叶子节点的值。
  • InnoDB行记录中除了刚才提到的rowid外,还有trx_id和db_roll_ptr, trx_id表示最近修改的事务的id,db_roll_ptr指向undo segment中的undo log。
  • 新增一个事务时事务id会增加,trx_id能够表示事务开始的先后顺序。

Undo log分为Insert和Update两种,delete可以看做是一种特殊的update,即在记录上修改删除标记。
update undo log记录了数据之前的数据信息,通过这些信息可以还原到之前版本的状态。
当进行插入操作时,生成的Insert undo log在事务提交后即可删除,因为其他事务不需要这个undo log。
进行删除修改操作时,会生成对应的undo log,并将当前数据记录中的db_roll_ptr指向新的undo log

宝马娱乐在线 1

意向共享锁,事务想要获取一张表的几行数据的共享锁,事务在给一个数据行加共享锁前必须先取得该表的IS锁。 意向排他锁,事务想要获取一张表中几行数据的排它锁,事务在给一个数据行加排他锁前必须先取得该表的IX锁。

Next-Key Lock

Next-Key 锁相比前两者就稍微有一些复杂,它是记录锁和记录前的间隙锁的结合,在 users 表中有以下记录:

宝马娱乐在线 2

如果使用 Next-Key 锁,那么 Next-Key 锁就可以在需要的时候锁定以下的范围:

宝马娱乐在线 3

Next-Key 锁锁定的是当前值和前面的范围。
当我们更新一条记录,比如 SELECT * FROM users WHERE age = 30 FOR UPDATE;,InnoDB 不仅会在范围 (21, 30] 上加 Next-Key 锁,还会在这条记录后面的范围 (30, 40] 加间隙锁,所以插入 (21, 40] 范围内的记录都会被锁定。
Next-Key 锁的作用其实是为了解决幻读的问题

事务A执行如下语句:

Gap Lock

记录锁是在存储引擎中最为常见的锁,除了记录锁之外,InnoDB 中还存在间隙锁(Gap Lock),间隙锁是对索引记录中的一段连续区域的锁;当使用类似 SELECT * FROM users WHERE id BETWEEN 10 AND 20 FOR UPDATE; 的 SQL 语句时,就会阻止其他事务向表中插入 id = 15 的记录,因为整个范围都被间隙锁锁定了。
虽然间隙锁中也分为共享锁和互斥锁,不过它们之间并不是互斥的,也就是不同的事务可以同时持有一段相同范围的共享锁和互斥锁,它唯一阻止的就是其他事务向这个范围中添加新的记录。

InnoDB在下面情况下会将重做日志缓冲的内容写入重做日志文件:

什么是事务

事务是一条或多条数据库操作语句的组合,具备ACID,4个特点。

  • 原子性:要不全部成功,要不全部撤销
  • 隔离性:事务之间相互独立,互不干扰
  • 一致性:数据库正确地改变状态后,数据库的一致性约束没有被破坏
  • 持久性:事务的提交结果,将持久保存在数据库中
SELECT * FROM T WHERE f_id = 3 FOR UPDATE

一致性非锁定读

总结

MVCC(Multi-Version Concurrency Control) 多版本并发控制

  • MVCC的实现,是通过保存数据在某个时间点的多版本快照来实现的.

  • InnoDB的MVCC是通过在每行记录后面保存2个隐藏的列来实现的,一列保存了行的创建时间,一列保存了行的过期时间(或删除时间).但它们都存储的是系统版本号

  • MVCC最大的作用是: 实现了非阻塞的读操作,写操作也只锁定了必要的行.

  • MYSQL的MVCC 只在 read committed 和 repeatable read 2个隔离级别下工作.

  • 在MVCC的机制下,mysql InnoDB(默认隔离级别)的增删改查变成了如下模式:

    • SELECT:
      InnoDB只查找版本早于当前事务版本的数据行(行的系统版本号小于等于事务的系统版本号)
      行的删除号要么未定义,要么大于当前事务版本号,这样可以确保事务读取到的行,在事务开始之前未被删除.

    • INSERT:
      InnoDB 为新插入的每一行保存当前系统版本号做为行版本号。

  • DELETE:
    INNODB 为删除的每一行保存当前系统版本号作为行删除标识

  • UPDATE:
    InnoDB 为插入的每一行新记录,保存当前系统版本号作为行版本号,同时保存当前系统版本号到原来的行作为行删除标识.

如果有人问你“数据库事务有哪些特性”?你可能会很快回答出原子性、一致性、隔离性、持久性即ACID特性。那么你知道InnoDB如何保证这些事务特性的吗?如果知道的话这篇文章就可以直接跳过不看啦(#^.^#)

InnoDB 锁的算法

  • Record Lock: 每个行记录的锁
  • GAP Lock: 间隙锁,锁定一个范围,但不包含记录本身.
  • Next-Key Lock: Gap Lock+Record Lock 锁定一个范围并锁定记录本身

上面所说的锁定的对象均为: 索引记录. 如果InnoDB存储引擎在建立的时候没有设置任何一个索引,那么这时,InnoDB存储引擎会使用隐式的主键进行锁定.
当查询的索引含有唯一属性时,InnoDB存储引擎会对Next-Key Lock进行优化,降级为Record Lock.

下面的2句话是InnoDB在不同隔离级别下产生"不可重复读" 和 "幻读" 和解决它 的根本原因:

  • InnoDB存储引擎默认的事务隔离级别(repeatable read)下,采用的是 Next-Key Locking的方式来加锁.
  • read committed隔离级别下采用的是: Record Lock 的方式来加锁.

对于聚集索引,其仅对id等于5的索引加上Record Lock。而对于辅助索引,其加上Next-Key Lock,锁定了范围,特别需要注意的是,InnoDB存储引擎还会对辅助索引下一个键值加上Gap Lock,即范围的锁。

锁的种类

对数据的操作其实只有两种,也就是读和写,而数据库在实现锁时,也会对这两种操作使用不同的锁;InnoDB 实现了标准的行级锁,也就是共享锁(Shared Lock)和互斥锁(Exclusive Lock);共享锁和互斥锁的作用其实非常好理解:

  • 共享锁(读锁):允许事务对一条行数据进行读取;
  • 互斥锁(写锁):允许事务对一条行数据进行删除或更新;

InnoDB主要有2种锁:行级锁,意向锁

事务并发会产生什么问题

  • 脏读:脏读就是指当一个事务正在访问数据,并且对数据进行了修改,而这种修改还没有提交到数据库中,这时,另外一个事务也访问这个数据,然后使用了这个数据,前一个事务回滚了,导致后一个事务的数据是无效的

  • 不可重复读:是指在一个事务内,多次读同一数据。在这个事务还没有结束时,另外一个事务也访问该同一数据。那么,在第一个事务中的两次读数据之间,由于第二个事务的修改,那么第一个事务两次读到的的数据可能是不一样的。这样就发生了在一个事务内两次读到的数据是不一样的,因此称为是不可重复读。

  • 幻读:是指当事务不是独立执行时发生的一种现象,例如第一个事务对一个表中的数据进行了修改,这种修改涉及到表中的全部数据行。同时,第二个事务也修改这个表中的数据,这种修改是向表中插入一行新数据。那么,以后就会发生操作第一个事务的用户发现表中还有没有修改的数据行,就好象发生了幻觉一样。

前言

并发控制机制

  • 乐观锁是一种思想,它其实并不是一种真正的『锁』,它会先尝试对资源进行修改,在写回时判断资源是否进行了改变,如果没有发生改变就会写回,否则就会进行重试,在整个的执行过程中其实都没有对数据库进行加锁;
  • 悲观锁就是一种真正的锁了,它会在获取资源前对资源进行加锁,确保同一时刻只有有限的线程能够访问该资源,其他想要尝试获取资源的操作都会进入等待状态,直到该线程完成了对资源的操作并且释放了锁后,其他线程才能重新操作资源;

行级锁:

锁的算法

Record Lock、Gap Lock 和 Next-Key Lock。

The main purpose of IX and IS locks is to show that someone is locking a row, or going to lock a row in the table.

锁的粒度

无论是共享锁还是互斥锁其实都只是对某一个数据行进行加锁,InnoDB 支持多种粒度的锁,也就是行锁和表锁;为了支持多粒度锁定,InnoDB 存储引擎引入了意向锁(Intention Lock),意向锁就是一种表级锁
与上一节中提到的两种锁的种类相似的是,意向锁也分为两种:

  • 意向共享锁:事务想要在获得表中某些记录的共享锁,需要在表上先加意向共享锁;
  • 意向互斥锁:事务想要在获得表中某些记录的互斥锁,需要在表上先加意向互斥锁;

意向锁其实不会阻塞全表扫描之外的任何请求,它们的主要目的是为了表示是否有人请求锁定表中的某一行数据。

  • 有的人可能会对意向锁的目的并不是完全的理解,我们在这里可以举一个例子:如果没有意向锁,当已经有人使用行锁对表中的某一行进行修改时,如果另外一个请求要对全表进行修改,那么就需要对所有的行是否被锁定进行扫描,在这种情况下,效率是非常低的;不过,在引入意向锁之后,当有人使用行锁对表中的某一行进行修改之前,会先为表添加意向互斥锁(IX),再为行记录添加互斥锁(X),在这时如果有人尝试对全表进行修改就不需要判断表中的每一行数据是否被加锁了,只需要通过等待意向互斥锁被释放就可以了。

为了满足事务的原子性,在操作任何数据之前,首先将数据备份到一个地方,然后进行数据的修改。如果出现了错误或者用户执行了 ROLLBACK语句,系统可以利用Undo Log中的备份将数据恢复到事务开始之前的状态。

事务隔离性由以上锁机制实现

事务的隔离性的实现原理就是锁,因而隔离性也可以称为并发控制、锁等。事务的隔离性要求每个读写事务的对象对其他事务的操作对象能互相分离。再者,比如操作缓冲池中的LRU列表,删除,添加、移动LRU列表中的元素,为了保证一致性那么就要锁的介入。

行的更新过程

  • 初始数据行

宝马娱乐在线 4

F1~F6是某行列的名字,1~6是其对应的数据。后面三个隐含字段分别对应该行的事务号和回滚指针,假如这条数据是刚INSERT的,可以认为ID为行号1,其他两个字段为空。

  • 事务1更改该行的各字段的值

宝马娱乐在线 5

当事务1更改该行的值时,会进行如下操作:
用排他锁锁定该行
记录redo log
把该行修改前的值Copy到undo log,即上图中下面的行
修改当前行的值,填写事务编号,使回滚指针指向undo log中的修改前的行

  • 事务2修改该行的值

宝马娱乐在线 6

与事务1相同,此时undo log,中有有两行记录,并且通过回滚指针连在一起。
如果undo log一直不删除,则会通过当前记录的回滚指针回溯到该行创建时的初始内容,所幸的时在Innodb中存在purge线程,它会查询那些比现在最老的活动事务还早的undo log,并删除它们,从而保证undo log文件不至于无限增长。

  • 事务提交
    当事务正常提交时Innbod只需要更改事务状态为COMMIT即可,不需做其他额外的工作,而Rollback则稍微复杂点,需要根据当前回滚指针从undo log中找出事务修改前的版本,并恢复。如果事务影响的行非常多,回滚则可能会变的效率不高,根据经验值没事务行数在1000~10000之间,Innodb效率还是非常高的。很显然,Innodb是一个COMMIT效率比Rollback高的存储引擎。据说,Postgress的实现恰好与此相反。

redo log重做日志用来保证事务的持久性 undo log回滚日志保证事务的原子性 undo log+redo log保证事务的一致性 锁用来保证事务的隔离性

MVCC过程:

  • 每行数据都存在一个版本(事务版本号),每次数据更新时都更新该版本
  • 修改时Copy出当前版本随意修改,各事务之间无干扰
  • 保存时比较版本号,如果成功(commit),则覆盖原记录;失败则放弃copy(rollback)(根据undo log ID)

就是每行都有版本号,保存时根据版本号决定是否成功,听起来含有乐观锁的味道。。。,而Innodb的实现方式是:

  • 事务以排他锁的形式修改原始数据
  • 把修改前的数据存放于undo log,通过回滚指针与主数据关联
    修改成功(commit)啥都不做,失败则恢复undo log中的数据(rollback)

二者最本质的区别是,当修改数据时是否要排他锁定,如果锁定了还算不算是MVCC?

Innodb的实现真算不上MVCC,因为并没有实现核心的多版本共存,undo log中的内容只是串行化的结果,记录了多个事务的过程,不属于多版本共存。但理想的MVCC是难以实现的,当事务仅修改一行记录使用理想的MVCC模式是没有问题的,可以通过比较版本号进行回滚;但当事务影响到多行数据时,理想的MVCC据无能为力了。

比如,如果Transaciton1执行理想的MVCC,修改Row1成功,而修改Row2失败,此时需要回滚Row1,但因为Row1没有被锁定,其数据可能又被Transaction2所修改,如果此时回滚Row1的内容,则会破坏Transaction2的修改结果,导致Transaction2违反ACID。

理想MVCC难以实现的根本原因在于企图通过乐观锁代替二段提交。修改两行数据,但为了保证其一致性,与修改两个分布式系统中的数据并无区别,而二提交是目前这种场景保证一致性的唯一手段。二段提交的本质是锁定,乐观锁的本质是消除锁定,二者矛盾,故理想的MVCC难以真正在实际中被应用,Innodb只是借了MVCC这个名字,提供了读的非阻塞而已。

Ref:
http://blog.csdn.net/tangkund3218/article/details/47904021

http://draveness.me/mysql-innodb.html

select * from T where id = 5 lock in share MODE -- 不能执行,因为事务A已经给id=5的值加上了X锁,执行会被阻塞INSERT INTO T SELECT 4,2 -- 不能执行,辅助索引的值为2,在的范围内,执行阻塞INSERT INTO T SELECT 6,5 -- 不能执行,gap锁会锁住的范围,执行阻塞

原子性和持久性的实现

redo log 称为重做日志(也叫事务日志),用来保证事务的原子性和持久性.
redo恢复提交事务修改的页操作,redo是物理日志,页的物理修改操作.
事务的提交过程如下图:

宝马娱乐在线 7

当提交一个事务时,实际上它干了如下2件事:
一: InnoDB存储引擎把事务写入日志缓冲(log buffer),日志缓冲把事务刷新到事务日志.
二: InnoDB存储引擎把事务写入缓冲池(Buffer pool).

事务日志也是写磁盘日志,为什么不需要双写技术?
因为事务日志块的大小和磁盘扇区的大小一样,都是512字节,因此事务日志的写入可以保证原子性,不需要doublewrite技术

宝马娱乐在线,undo log实现多版本并发控制来辅助保证事务的隔离性。

死锁的发生

既然 InnoDB 中实现的锁是悲观的,那么不同事务之间就可能会互相等待对方释放锁造成死锁,最终导致事务发生错误;想要在 MySQL 中制造死锁的问题其实非常容易:

宝马娱乐在线 8

先说结论:

一致性的实现

undo log 用来保证事务的一致性. undo 回滚行记录到某个特定版本,undo 是逻辑日志,根据每行记录进行记录.
undo 存放在数据库内部的undo段,undo段位于共享表空间内.
undo 只把数据库逻辑的恢复到原来的样子.
undo日志除了回滚作用之外, undo 实现MVCC,读取一行记录时,发现事务锁定,通过undo恢复到之前的版本,实现非锁定读取.

锁的算法

不加锁的读

  • 是InnoDB存储引擎下的读取数据的方式( read committed 和 repeatable read).
  • 读取mysql数据库时,如果读取的行正在执行DELETE,UPDATE等操作,这时,读取操作不会因此去等待行上的X锁释放,相反,InnoDB会读取行的一个快照数据.
    这样利用MVCC,InnoDB实现了非阻塞读的实现.极大的提高了数据库的并发性.

但在不同的事务隔离级别下读取的数据的方式也不一样:
(1). 在read committed隔离级别下:
一致性非锁定读总是读取被锁定行的最新一份快照数据. 产生了不可重复读的问题.
(2). 在repeatable read 事务隔离级别下:
一致性非锁定读总是读取事务开始时的行数据版本. 解决不可重复读的问题

注意了,如果走唯一索引,那么Next-Key Lock会降级为Record Lock,即仅锁住索引本身,而不是范围。也就是说Next-Key Lock前置条件为事务隔离级别为RR且查询的索引走的非唯一索引、主键索引。

一致性锁定读

还有一种读的方式叫: 一致性锁定读(加锁的读).
1). select .... for update. 加X锁
2). select .... lock in share mode. 加S锁

CREATE TABLE T (id int ,f_id int,PRIMARY KEY (id), KEY(f_id)) ENGINE=InnoDB DEFAULT CHARSET=utf8insert into T SELECT 1,1;insert into T SELECT 3,1;insert into T SELECT 5,3;insert into T SELECT 7,6;insert into T SELECT 10,8;

事务隔离级别,解决什么并发问题,以及存在什么并发问题

  • READ_UNCOMMITTED
    这是事务最低的隔离级别,它充许另外一个事务可以看到这个事务未提交的数据。
    解决第一类丢失更新的问题,但是会出现脏读、不可重复读、第二类丢失更新的问题,幻读 。
  • READ_COMMITTED
    保证一个事务修改的数据提交后才能被另外一个事务读取,即另外一个事务不能读取该事务未提交的数据。
    解决第一类丢失更新和脏读的问题,但会出现不可重复读、第二类丢失更新的问题,幻读问题
  • REPEATABLE_READ
    保证一个事务相同条件下前后两次获取的数据是一致的
    解决第一类丢失更新,脏读、不可重复读、第二类丢失更新的问题,但会出幻读。
  • SERIALIZABLE
    事务被处理为顺序执行。
    解决所有问题

提醒:
Mysql默认的事务隔离级别为repeatable_read

1参数默认值,表示事务提交时必须调用一次fsync操作。 0表示事务提交时,重做日志缓存并不立即写入重做日志文件,而是随着Master Thread的间隔进行fsync操作。 2表示事务提交时将重做日志写入重做日志文件,但仅写入文件系统的缓存中,不进行fsync操作。 fsync的效率取决于磁盘的性能,因此磁盘的性能决定了事务提交的性能,也就是数据库的性能。所以如果有人问你如何优化Mysql数据库的时候别忘了有硬件这一条,让他们提升硬盘配置,换SSD固态硬盘 重做日志都是以512字节进行存储的,称之为重做日志块,与磁盘扇区大小一致,这意味着重做日志的写入可以保证原子性,不需要doublewrite技术。它有以下3个特性: 重做日志是在InnoDB层产生的 重做日志是物理格式日志,记录的是对每个页的修改 重做日志在事务进行中不断被写入,而且是顺序写入

Record Lock

记录锁(Record Lock)是加到索引记录上的锁,假设我们存在下面的一张表 users:

CREATE TABLE users(
    id INT NOT NULL AUTO_INCREMENT,
    last_name VARCHAR(255) NOT NULL,
    first_name VARCHAR(255),
    age INT,
    PRIMARY KEY(id),
    KEY(last_name),
    KEY(age)
);

如果我们使用 id 或者 last_name 作为 SQL 中 WHERE 语句的过滤条件,那么 InnoDB 就可以通过索引建立的 B+ 树找到行记录并添加索引,但是如果使用 first_name 作为过滤条件时,由于 InnoDB 不知道待修改的记录具体存放的位置,也无法对将要修改哪条记录提前做出判断就会锁定整个表

本文由宝马娱乐在线112222发布于网络应用,转载请注明出处:宝马娱乐在线MySQL InnoDB如何保证事务特性示例详

关键词:

上一篇:mysql 8.0.11压缩包版本安装教程

下一篇:Mysql数据库之常用sql语句进阶与总结宝马娱乐在线