温故而知新
事务的必要性
假设有两个用户,不妨记为 a 和 b,假设 a 要给 b 转账 1000 元,那么只需要在表中把 a 对应的记录的相应字段减去 1000,给 b 对应的记录的相应 字段增加 1000 即可。 但是当我们给 a 对应的记录减去 1000,但是当执行给 b 对应的记录增加 1000 的时候,服务器突然出现了一些问题,导致该 SQL 并未顺利执行就宕机 了。 如果不启用事务,那么结果就是 a 对应的记录减少了 1000,但是 b 对应的记录并没有增加 1000,这是不符合常理的,于是,我们引入了事务机制来保 证它的可靠性。
数据库系统引入事务的主要目的:
事务会把数据库从一种一致性状态转换为另一种一致性状态。在数据库提交的时候,可以确保要么所有提交都保存,要么所有修改都不保存。 事务可以用 来保证数据库的完整性:要么都做,要么都不做。
MySQL的checkpoint https://www.cnblogs.com/lintong/p/4381578.html
checkpoint,即检查点。在undolog中写入检查点,表示在checkpoint前的事务都已经完成commit或者rollback了,也就是检查点前面的事务已经不存在数据一 致性的问题了(此处暂时不会深入解释)
Innodb的事务日志是指Redo log,简称Log,保存在日志文件ib_logfile里面(去mysql数据目录下看下)。
Innodb还有另外一个日志Undo log,但Undo log是存 放在共享表空间里面的(ibdata*文件,存储的是check point日志序列号)。
innodb_log_files_in_group:DB中设置几组事务日志,默认是2; innodb_log_group_home_dir:事务日志存放目录,不设置,ib_logfile0…存在在数据文件目录下 Innodb存储引擎可将所有数据存放于ibdata*的共享表空间,也可将每张表存放于独立的.ibd文件的独立表空间
注意:在MySQL中对于数据来说, 最为重要的是日志文件
redo log => ib_logfile0 undo log => ibdata
事务被提交,数据一定会被写入到数据库中并持久存储起来,通常来说当事务已经被提交之后,就无法再次回滚了。
与原子性一样,事务的持久性也是通过日志来实现的,MySQL 使用重做日志(redo log)实现事务的持久性,重做日志由两部分组成,一是内存中的重做日志缓 冲区,因为重做日志缓冲区在内存中,所以它是易失的,另一个就是在磁盘上的重做日志文件,它是持久的。 当我们在一个事务中尝试对数据进行写时,它会先将数据从磁盘读入内存,并更新内存中缓存的数据,然后生成一条重做日志并写入重做日志缓存,当事务真正 提交时,MySQL 会将重做日志缓存中的内容刷新到重做日志文件,再将内存中的数据更新到磁盘上,图中的第 4、5 步就是在事务提交时执行的。
重做日志执行时间
在mysql中事务执行commit提交了之后,但是服务器挂了,数据还没有写入磁盘,在mysql重启服务之后会重新执行这个重做日志写入数据。
通俗的解释就是;一条绳子上的蚂蚱 专业点:事务就是一系列的操作,要么全部都执行,要么都不执行
想要保证事务的原子性,就需要在异常发生时,对已经执行的操作进行回滚,而在 MySQL 中,恢复机制是通过回滚日志(undo log)实现的,所有事务进行的 修改都会先记录到这个回滚日志中,然后在对数据库中的对应行进行写入。 注意:系统发生崩溃、数据库进程直接被杀死后,当用户再次启动数据库进程时, 还能够立刻通过查询回滚日志将之前未完成的事务进行回滚,这也就需要回滚 日志必须先于数据持久化到磁盘上,是我们需要先写日志后写数据库的主要原 因。 在日志文件中:在事务中使用的每一条 INSERT 都对应了一条 DELETE,每一条 UPDATE 也都对应一条相反的 UPDATE 语句。
回滚日志执行时间
手动执行回滚命令时会执行如果程序在事务执行之后,提交命令执行之前出现了异常,在下次mysql服务重启的时候会执行到现在为止我们了解了 MySQL 中的两种日志,回滚日志(undo log)和重做日志(redo log);在数据库系统中,事务的原子性和持久性是由事务日志 (transaction log)保证的,在实现时也就是上面提到的两种日志,前者用于对事务的影响进行撤销,后者在错误处理时对已经提交的事务进行重做,它们能保 证两点:
发生错误或者需要回滚的事务能够成功回滚(原子性);在事务提交后,数据没来得及写会磁盘就宕机时,在下次重新启动后能够成功恢复数据(持久性); 在数据库中,这两种日志经常都是一起工作的,我们 可以将它们整体看做一条事务日志,其中包含了事务的 ID、修改的行元素以及修改前后的值。(1)共享锁(S):允许一个事务去读一行,阻止其他事务获得相同数据集的排他锁。 (2)排他锁(X):允许获得排他锁的事务更新数据,阻止其他事务获得相同数据集的共享读锁和排他写锁。
InnoDB 还有两种意向锁,即 Intention Lock,这两种锁都是表锁。意向锁是内部使用的,它是 InnoDB 内部加的,不用用户干预,意向锁分类如下:(1)共享锁(S):允许一个事务去读一行,阻止其他事务获得相同数据集的排他锁。 (2)排他锁(X):允许获得排他锁的事务更新数据,阻止其他事务获得相同数据集的共享读锁和排他写锁。
这里有个锁兼容和冲突的概念,如果在加一个锁的时候,另一个锁可以加上去,那么就是锁兼容。 如果加上一个锁之后,拒绝其他的锁加上,那么就是锁冲突。
各种锁的兼容冲突情况如下:(1)X 和所有锁都冲突 (2)IX 兼容 IX 和 IS (3)S 兼容 S 和 IS (4)IS 兼容 IS、IX 和 S
如果一个事务请求的锁模式与当前的锁兼容,InnoDB 就将请求的锁授予该事务,如果两者是冲突的,那么该事务就要等待锁释放。对于 update、delete、insert 语句,InnoDB 会自动给设计到的数据集加排他锁即 X。对于 select 语句,InnoDB 不会加任何锁。我们可以使用如下语句来显式的给数据集加锁:(1)共享锁(S):select * from t1 where … lock in share mode; (2)排他锁(X):select * from t1 where … for update;
我们可以用 select …in share mode 来获得共享锁,主要用在数据依存关系时来确认某行记录是否存在,并确认没有人对这个记录进行 update 或者 delete 操 作。我们可以使用 select… for update 来获得排他锁,它会拒绝其他事务在其上加其他锁。排它锁锁加锁测试:
--给`user`表id为1的数据加排它锁 start transaction;--开启事务 select * from `user` where id=1 for update;--给id为1的数据加排它锁 commit;--提交事务 rollback;--回滚事务上面的结果我们并不能看出什么效果,但是如果我们同时开启两个session,一起去给同一数据加锁时排它锁的效果就非常明显了,如下测试: 上面的测试结果表名,当前事务给一行数据加排他锁,那么其他事务将不能在对数据做任何操作,即:不能读不能写,也不能与其他锁一起使用
当前事务给一行数据加共享锁,那么其他事务可以加共享锁,但不能加排它锁。即:能读不能写,可以与共享锁一起使用,但不能与 排它锁一起使用
B-tree索引是MySQL数据库中使用最为频繁的索引类型,特别是在innodb中经常使用;在其他数据库中b-tree索引也同样是作为最主要的索引类型的,这主要是因 为b-tree索引的存储结构在数据库的数据检索中有着非常优异的表现。 如上就是tree的基本结构对于这种结构来说到任何一个节点的最大路径的长度都是完全相同的,但是对于数据库来说常用的主要是B-tree索引,这种索引的特点就 是会把实际需要的数据都存放与tree的节点中,也就是说每一个节点中保存了索引的数值又加上了实际所以对应的数 据(其实就是地址) 而innodb中的b-tree也就是这种结构,不过在这个基础上还是做了一点的额外的操作并称之为b+tree,主要的操作点,在节点处不再存放数据,多放在了树的根 处,而且同时还存放了到其他位置的地址。 在innodb存储引擎中,存在两种不同形式的索引,一种是Cluster形式的主键索引,另外的一种则是和其他存储引擎存放形式基本相同的普通B-tree索引,这种索 引在innodb存储引擎中被称为Secondary index(二级索引)。 两种索引类型的关系:他们两者的区别主要是在于他们存储的数据不一样,主键索引在leaf nodes 存放的是page页地址,而二级索引存放的是对应的主键id,在 结构上没太多的区别 图示: 所以在innodb中如果通过主键来访问数据效率是非常高的,而如果是通过secondary index 来访问数据的话,会先根据二级Btree获取到id再根据id查询数据(这 个过程也可以称之为回表)
B-Tree是为磁盘等外存储设备设计的一种平衡查找树。因此在讲B-Tree之前先了解下磁盘的相关知识。
系统从磁盘读取数据到内存时是以磁盘块(block)为基本单位的,位于同一个磁盘块中的数据会被一次性读取出来,而不是需要什么取什么。
InnoDB存储引擎中有页(Page)的概念,页是其磁盘管理的最小单位。InnoDB存储引擎中默认每个页的大小为16KB,可通过参数innodb_page_size将页的大小 设置为4K、8K、16K,在MySQL中可通过如下命令查看页的大小:
mysql> show variables like "innodb_page_size"; +------------------+-------+ | Variable_name | Value | +------------------+-------+ | innodb_page_size | 16384 | +------------------+-------+ 1 row in set (0.02 sec)而系统一个磁盘块的存储空间往往没有这么大,因此InnoDB每次申请磁盘空间时都会是若干地址连续磁盘块来达到页的大小16KB。InnoDB在把磁盘数据读入到 磁盘时会以页为基本单位,在查询数据时如果一个页中的每条数据都能有助于定位数据记录的位置,这将会减少磁盘I/O次数,提高查询效率。
B-Tree结构的数据可以让系统高效的找到数据所在的磁盘块。为了描述B-Tree,首先定义一条记录为一个二元组[key, data] ,key为记录的键值,对应表中的主键 值,data为一行记录中除主键外的数据。对于不同的记录,key值互不相同。 每个节点占用一个盘块的磁盘空间,一个节点上有两个升序排序的关键字和三个指向子树根节点的指针,指针存储的是子节点所在磁盘块的地址。两个关键词划 分成的三个范围域对应三个指针指向的子树的数据的范围域。以根节点为例,关键字为17和35,P1指针指向的子树的数据范围为小于17,P2指针指向的子树的数 据范围为17~35,P3指针指向的子树的数据范围为大于35。
B+Tree是在B-Tree基础上的一种优化,使其更适合实现外存储索引结构,InnoDB存储引擎就是用B+Tree实现其索引结构。
从上一节中的B-Tree结构图中可以看到每个节点中不仅包含数据的key值,还有data值。而每一个页的存储空间是有限的,如果data数据较大时将会导致每个节点 (即一个页)能存储的key的数量很小,当存储的数据量很大时同样会导致B-Tree的深度较大,增大查询时的磁盘I/O次数,进而影响查询效率。在B+Tree中,所 有数据记录节点都是按照键值大小顺序存放在同一层的叶子节点上,而非叶子节点上只存储key值信息,这样可以大大加大每个节点存储的key值数量,降低 B+Tree的高度。
B+Tree相对于B-Tree有几点不同:
非叶子节点只存储键值信息。 所有叶子节点之间都有一个链指针。 数据记录都存放在叶子节点中。 将上一节中的B-Tree优化,由于B+Tree的非叶子节点只存储 键值信息,假设每个磁盘块能存储4个键值及指针信息,则变成B+Tree后其结构如下图所示:
通常在B+Tree上有两个头指针,一个指向根节点,另一个指向关键字最小的叶子节点,而且所有叶子节点(即数据节点)之间是一种链式环结构。因此可以对 B+Tree进行两种查找运算:一种是对于主键的范围查找和分页查找,另一种是从根节点开始,进行随机查找。