mysql优化系列(二十)- mysql基础复习(二)

    科技2025-04-27  27

    文章目录

    前言1、事务的实现1.1 事务的特性(ACID)1.2 事务的语法1.3 事务生命周期1.4 事务重做日志与回滚日志1.4.1 重做日志1.4.1.1 持持久久化化1.4.1.2 重做日志实现持久化1.4.2 回滚日志1.4.2.1 原子性1.4.2.2 回滚日志实现原子性 1.4.3 重做日志与回滚日志 2、mysql锁机制2.1 概念2.2 锁类型2.3 innoDB锁2.4 innoDB锁类型2.5 锁对于语句的加锁2.5.1 排它锁 3、innodb索引结构4、b-tree索引与b+tree的区别4.1 b-tree索引4.2 b+tree索引


    前言

    温故而知新


    1、事务的实现

    事务即 transaction,是数据库系统区别于文件系统的重要特性之一。在文件系统中,如果我们正在写文件,但是操作系统崩溃了,那么文件中的数据可能会丢失。但是数据库可以通过事务机制来确保这一点。

    事务的必要性

    假设有两个用户,不妨记为 a 和 b,假设 a 要给 b 转账 1000 元,那么只需要在表中把 a 对应的记录的相应字段减去 1000,给 b 对应的记录的相应 字段增加 1000 即可。 但是当我们给 a 对应的记录减去 1000,但是当执行给 b 对应的记录增加 1000 的时候,服务器突然出现了一些问题,导致该 SQL 并未顺利执行就宕机 了。 如果不启用事务,那么结果就是 a 对应的记录减少了 1000,但是 b 对应的记录并没有增加 1000,这是不符合常理的,于是,我们引入了事务机制来保 证它的可靠性。

    数据库系统引入事务的主要目的:

    事务会把数据库从一种一致性状态转换为另一种一致性状态。在数据库提交的时候,可以确保要么所有提交都保存,要么所有修改都不保存。 事务可以用 来保证数据库的完整性:要么都做,要么都不做。

    1.1 事务的特性(ACID)

    事务要求 ACID 的特性,即:原子性、一致性、隔离性、持久性。所谓原子性,是指整个数据库的每个事务都是不可分割的单位。只有事务中的所有 SQL 语句都执行成功,才算整个事务成功,事务才会被提交。如果事务 中任何一个 SQL 语句执行失败,整个事务都应该被回滚。所谓一致性,是指将数据库从一种一致性状态转换为下一种一致性状态。不允许数据库中的数据出现新老数据都有的情况,要么都是老数据,要么都是新数 据。用更书面化的表达就是:数据的完整性约束没有被破坏。所谓隔离性,是指一个事务的影响在该事务提交前对其他事务都不可见,它通过锁机制来实现。所谓持久性,是指事务一旦被提交,其结果就是永久性的。即使发生宕机等故障,数据库也能将数据恢复。

    1.2 事务的语法

    在 MySQL 命令行的默认设置下,事务是自动提交的,即执行了SQL 语句之后会马上执行 commit 操作,我们可以设置 set autocommit=0 来禁用当前回话 的自动提交。还可以用 begin 、start transaction 来显式的开始一个事务。commit 在默认设置下是等价于 commit work 的,表示提交事务。rollback 在默认设置下等价于 rollback work,表示事务回滚。savepoint xxx 表示定义一个保存点,在一个事务中可以有多个保存点。release savepoint xxx 表示删除一个保存点,当没有该保存点的时候执行该语句,会抛出一个异常。rollback to [savepoint] xxx 表示回滚到某个保存点。

    1.3 事务生命周期

    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日志序列号)。

    1.4 事务重做日志与回滚日志

    -- 查看事务日志 : show engine innodb status\G; -- 查看日志文件设置状态 show variables like 'innodb_%';

    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

    1.4.1 重做日志

    1.4.1.1 持持久久化化

    事务被提交,数据一定会被写入到数据库中并持久存储起来,通常来说当事务已经被提交之后,就无法再次回滚了。

    1.4.1.2 重做日志实现持久化

    与原子性一样,事务的持久性也是通过日志来实现的,MySQL 使用重做日志(redo log)实现事务的持久性,重做日志由两部分组成,一是内存中的重做日志缓 冲区,因为重做日志缓冲区在内存中,所以它是易失的,另一个就是在磁盘上的重做日志文件,它是持久的。 当我们在一个事务中尝试对数据进行写时,它会先将数据从磁盘读入内存,并更新内存中缓存的数据,然后生成一条重做日志并写入重做日志缓存,当事务真正 提交时,MySQL 会将重做日志缓存中的内容刷新到重做日志文件,再将内存中的数据更新到磁盘上,图中的第 4、5 步就是在事务提交时执行的。

    重做日志执行时间

    在mysql中事务执行commit提交了之后,但是服务器挂了,数据还没有写入磁盘,在mysql重启服务之后会重新执行这个重做日志写入数据。

    1.4.2 回滚日志

    1.4.2.1 原子性

    通俗的解释就是;一条绳子上的蚂蚱 专业点:事务就是一系列的操作,要么全部都执行,要么都不执行

    1.4.2.2 回滚日志实现原子性

    想要保证事务的原子性,就需要在异常发生时,对已经执行的操作进行回滚,而在 MySQL 中,恢复机制是通过回滚日志(undo log)实现的,所有事务进行的 修改都会先记录到这个回滚日志中,然后在对数据库中的对应行进行写入。 注意:系统发生崩溃、数据库进程直接被杀死后,当用户再次启动数据库进程时, 还能够立刻通过查询回滚日志将之前未完成的事务进行回滚,这也就需要回滚 日志必须先于数据持久化到磁盘上,是我们需要先写日志后写数据库的主要原 因。 在日志文件中:在事务中使用的每一条 INSERT 都对应了一条 DELETE,每一条 UPDATE 也都对应一条相反的 UPDATE 语句。

    回滚日志执行时间

    手动执行回滚命令时会执行如果程序在事务执行之后,提交命令执行之前出现了异常,在下次mysql服务重启的时候会执行

    1.4.3 重做日志与回滚日志

    到现在为止我们了解了 MySQL 中的两种日志,回滚日志(undo log)和重做日志(redo log);在数据库系统中,事务的原子性和持久性是由事务日志 (transaction log)保证的,在实现时也就是上面提到的两种日志,前者用于对事务的影响进行撤销,后者在错误处理时对已经提交的事务进行重做,它们能保 证两点:

    发生错误或者需要回滚的事务能够成功回滚(原子性);在事务提交后,数据没来得及写会磁盘就宕机时,在下次重新启动后能够成功恢复数据(持久性); 在数据库中,这两种日志经常都是一起工作的,我们 可以将它们整体看做一条事务日志,其中包含了事务的 ID、修改的行元素以及修改前后的值。

    2、mysql锁机制

    2.1 概念

    在开发多用户、数据库驱动的应用时,相当大的一个难点就是解决并发性的问题,目前比较常用的解决方案就是锁机制。锁机制也是数据库系统区别于文件系统的一个关键特性。InnoDB 存储引擎和 MyISAM 存储引擎使用的是完全不同的策略,我们必须分开来讲。

    2.2 锁类型

    相比其他数据库而言,MySQL 的锁机制比较简单,而且不同的存储引擎支持不同的锁机制。MyISAM 和 Memory 存储引擎使用的是表级锁,BDB 引擎使用的是页级锁,也支持表级锁。由于 BDB 引擎基本已经成为历史,因此就不再介绍了。InnoDB 存储引擎既支持行级锁,也支持表级锁,默认情况下使用行级锁。所谓表级锁,它直接锁住的是一个表,开销小,加锁快,不会出现死锁的情况,锁定粒度大,发生锁冲突的概率更高,并发度最低。所谓行级锁,它直接锁住的是一条记录,开销大,加锁慢,发生锁冲突的概率较低,并发度很高。所谓页级锁,它是锁住的一个页面,在 InnoDB 中一个页面为16KB,它的开销介于表级锁和行级锁中间,也可能会出现死锁,锁定粒度也介于表级锁和行级 锁中间,并发度也介于表级锁和行级锁中间。仅仅从锁的角度来说,表级锁更加适合于以查询为主的应用,只有少量按照索引条件更新数据的应用,比如大多数的 web 应用。行级锁更适合大量按照索引条件并发更新少量不同的数据,同时还有并发查询的应用,比如一些在线事务处理系统,即 OLTP。

    2.3 innoDB锁

    InnoDB 与 MyISAM 的相当大的两点不同在于: (1) 支持事务 (2) 采用行级锁行级锁本身与表级锁的实现差别就很大,而事务的引入也带来了很多新问题,尤其是事务的隔离性,与锁机制息息相关。对于事务的基本操作,对于不同隔离级别可能引发的问题,像脏读、不可重复读等问题我们上一节就已经举例说明了,这里就不再赘述了。数据库实现事务隔离的方式,基本可以分为两种: (1) 在操纵数据之前,先对其加锁,防止其他事务对数据进行修改。这就需要各个事务串行操作才可以实现。 (2) 不加任何锁,通过生成一系列特定请求时间点的一致性数据快照,并通过这个快照来提供一致性读取。上面的第二种方式就是数据多版本并发控制,也就是多版本数据库,一般简称为 MVCC 或者 MCC,它是 Multi Version Concurrency Control 的简写。数据库的事务隔离越严格,并发的副作用就越小,当然付出的代价也就越大,因为事务隔离机制实质上是使得事务在一定程度上”串行化”,这与并行是矛盾 的。

    2.4 innoDB锁类型

    InnoDB 实现了下面两种类型的锁:

    (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 来获得排他锁,它会拒绝其他事务在其上加其他锁。

    2.5 锁对于语句的加锁

    2.5.1 排它锁

    排它锁锁加锁测试:

    --给`user`表id为1的数据加排它锁 start transaction;--开启事务 select * from `user` where id=1 for update;--给id为1的数据加排它锁 commit;--提交事务 rollback;--回滚事务

    上面的结果我们并不能看出什么效果,但是如果我们同时开启两个session,一起去给同一数据加锁时排它锁的效果就非常明显了,如下测试: 上面的测试结果表名,当前事务给一行数据加排他锁,那么其他事务将不能在对数据做任何操作,即:不能读不能写,也不能与其他锁一起使用

    当前事务给一行数据加共享锁,那么其他事务可以加共享锁,但不能加排它锁。即:能读不能写,可以与共享锁一起使用,但不能与 排它锁一起使用

    3、innodb索引结构

    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查询数据(这 个过程也可以称之为回表)

    4、b-tree索引与b+tree的区别

    4.1 b-tree索引

    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。

    4.2 b+tree索引

    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进行两种查找运算:一种是对于主键的范围查找和分页查找,另一种是从根节点开始,进行随机查找。


    Processed: 0.009, SQL: 8