文章阅读——Mysql技术内幕

mysql官方文档:https://dev.mysql.com/doc

 

第一章 mysql体系结构和存储引擎

1.1 定义数据库和实例

数据库:物理操作系统文件或其他形式文件类型的集合。

实例:MySQL数据库由后台线程和一个共享内存区构成。共享内区可以被后台运行的线程共享。数据库实例才是真正用于操作数据库文件的。实例在系统上表现为一个进程。

ps:实例与数据库通常是一一对应的,但在集群环境下可能会出现一个数据库被多个数据实例使用的情况。

 

1.2 MySQL体系结构

文章阅读——Mysql技术内幕_第1张图片

由图知,MySQL组成包括:

连接池组件、管理服务和工具组件、SQL接口组件、查询分析器组件、优化器组件、缓冲组件、插件式存储引擎、物理文件。

MySQL区别与其他数据库的特点:基于表的插件式存储引擎。

 

1.3 MySQL存储引擎(MySQL数据库的核心)

存储引擎的好处:每个存储引擎都有各自的特点,能够根据具体的应用建立不同存储引擎表。

 

1.3.1 InnoDB存储引擎(MySQL默认)

InnoDB支持事务,设计目标主要是面向在线事务处理(OLTP)的应用。

特点:行锁设计,支持外键,支持全文索引(1.2版本)。默认读操作不会产生锁(一致性非锁定读)。

功能:它使用MVCC(多版本并发控制)来获得高并发性,实现了SQL的四种隔离级别,默认为REAPETABLE。使用next-keylocking的策略来避免幻读的产生。除此之外,它还提供了插入缓冲、二次写、自适应性哈希索引、预读等功能。

存储方式:聚集(clustered)的方式,每张表的存储都是按主键的顺序进行存放。若无主键,则其会为每一行生成一个6字节的ROWID,以此作为主键。

 

1.3.2 MyISAM引擎

MyISAM不支持事务、表锁设计,支持全文索引,主要面向OLAP(联机分析处理)的应用。

特点:它的缓冲池只缓存索引文件,数据文件的缓存由操作系统完成。

 

1.3.3 NDB引擎

NDB引擎是一个集群存储引擎,为share nothing的集群架构。

特点:所有的数据存放在内存中(MySQL5.1开始,可将非索引文件放在磁盘上),因此主键查找的速度极快,可通过添加NDB 数据存储节点线性地提高数据库性能。

问题:NDB存储引擎的连接(JOIN)操作是在MySQL数据库层完成的,这意味着复杂的连接操作将很慢,开销巨大。

 

1.3.4 Memory存储引擎(HEAP存储引擎)

它将表中的数据存放在内存中,适合用于存储临时数据的临时表,以及数据仓库中的纬度表。默认使用Hash索引。

缺点:只支持表锁,并发性能较差。存储varchar是是按照char来存储的,浪费空间。

 

不同存储引擎间的比较图:

文章阅读——Mysql技术内幕_第2张图片

 

第二章 InnoDB引擎

2.3 InnoDB体系架构

InnoDB存储池中有多个内存块,可以认为这些内存块组成了一个内存池,负责如下工作:

维护所有进程/线程需要访问的多个数据结构

缓存磁盘上的数据,方便快速地读取,同时在对磁盘文件的数据修改之前在这里缓存

重做日志(rodo log)缓冲 等等。

后台线程的作用:主要负责刷新内存池中的数据,保证缓冲池中的内存缓存是最新的;将已修改的数据文件刷新到磁盘文件中,同时保证在数据库发生异常的情况下InnoDB能够恢复到正常运行状态。

文章阅读——Mysql技术内幕_第3张图片

 

 

2.3.1 后台线程

InnoDB存储引擎是多线程模型,因此其后台有多个不同的线程。

1.Master Thread(核心的后台线程)

主要负责将缓冲池中的数据异步刷新到磁盘,保证数据的一致性。包括脏页的刷新、合并插入缓冲、UNDO页的回收等。

2.IO Thread

在InnoDB引擎中大量使用了AIO(Async IO)来处理IO请求。IO Thread的主要工作是处理这些IO请求的回调。

3.Purge Thread

事务被提交后,其所使用的undo log可能不再需要。故需要PurgeThread来回收已经分配并使用的undo页。

4.Page Cleaner Thread

在InnoDB版本1.2.x后引进。作用是将之前版本中脏页的刷新操作都放入到单独的线程中来完成。目的是为了减轻原Master Thread的工作和对于用户存储线程的阻塞。

 

2.3.2 内存

1.缓冲池

是什么:缓冲池简单来说是一块内存区域,通过内存的速度弥补磁盘速度较慢对数据库性能的影响。数据库中的页的读取,是先看缓冲池中是否存在该页,若有,则直接读取;数据库中页的修改,首先修改在缓冲池中的页,然后再以一定的频率刷新到磁盘上。(触发机制为CheckPoint)

存储对象:文章阅读——Mysql技术内幕_第4张图片

从InnoDB 1.0.x版本开始,允许有多个缓冲池实例,每个页根据Hash平均分配到不同缓冲池实例中。

 

2.LRU List、Free List和Flush List

(本节解答InnoDB存储引擎如何对内存区域进行管理)

1)LRU(Latest Recent Used) List:用来管理已经读取的页。缓冲池一般使用LRU算法来进行管理。即最频繁使用的页在LRU的前端,最少使用的页在LRU的后端。当缓冲池不能存取新的页时,将首先释放LRU List的后端页。

InnoDB使用的是改良版的LRU算法,它加入了midPoint位置,还引入了innodb_old_blocks_time。

midPoint:该位置默认在LRU列表尾端的37%的位置,新读取到的页都放在这个位置。在该位置前的列表,称为new 列表,否则称为old列表。可以简单理解为,new列表中的都是最为活跃的热点数据。

使用midPoint的原因:若直接将读取到的页放入到LRU的首部,那么某些SQL操作可能会使缓冲池中的页被刷出,从而影响缓冲池的效率。常见的这类操作为索引或数的扫描操作,这类操作一般要访问很多页,但只是访问一次,并不是真正的热点数据。若直接放入LRU列表的首部,则可能会导致需要的热点数据被刷新出,下次要访问时又要在磁盘中读取,影响效率。

innodb_old_blocks_time:表示页读取到mid位置后需要等待多久才能被加入LRU列表的热端。

2)Free List:用来管理未读取的页。当数据库刚启动时,LRU List是空的,页都存放在Free List中。

LRU 与 Free List的关系:当需要从缓冲池中分页时,先从Free List中查找是否有空闲的页,有则将其从Free List中删去。否则,根据LRU算法,删去LRU尾端的页,将该内存空间分配给新的页。当页从LRU中old的部分加入到new的部分时,称为page made young;而因为innodb_old_blocks_time的设置导致页没有从old部分移动到new的部分的操作称为 page not made young。

3)压缩页的内存分配

unzip_LRU管理的是非16KB的页,LRU列表中包含了unzip_LRU的页。首先,在unzip_LRU中对不同压缩页大小的页进行分别管理。其次,通过伙伴算法进行内存的分配。

举例:现需要从缓冲池中申请页为4KB的大小,过程如下:

1)检查4KB的unzip_LRU列表,检查是否有可用的空闲页;

2)若有,则直接使用;

3)否则,检查8KB的unzip_LRU列表;

4)若能够得到空闲页,将页分成两个4KB的页,存放到4KB的unzip_LRU列表中;

5)若不能,则从LRU列表中申请一个16KB的页,分成一个8KB和两个4KB的页,分别存放进unzip_LRU列表中。

 

4)Flush List:在LRU中被修改后的页,称为脏页(dirty page)。即缓冲池中的页的数据和磁盘中的不一致。这时数据库会通过CHECKPOINT机制将脏页刷新会磁盘,Flush中存放的就是脏页列表。

PS:脏页既存在于LRU List中,也存在于 Flush List中。二者互不影响。

 

3.重做日志缓冲

在通常情况下,8MB的重做日志缓冲足以满足绝大部分的应用。因为在以下3种情况下,重做日志缓冲中的内容会刷新到外部磁盘的重做日志中:

1)Master Thread每秒会刷新一次;

2)每个事务提交后会刷新一次;

3)当redo log的空间小于1/2时,会刷新一次。

 

4.额外的内存池

在InnoDB中,对内存的管理是通过一种称为内存堆的方式进行的。在对一些数据结构本身的内存进行分配时,需要从额外的内存池中进行申请,申请不了再从缓冲池中申请。

 

2.4 CHECKPOINT技术

1.checkpoint(检查点)技术解决的问题:

1)缩短数据库的恢复时间;

2)缓冲池不够用时,将脏页刷新到内存磁盘;

3)重做日志不可用时,刷新脏页;

2.CHECKPOINT的分类

1)Sharp Checkpoint:它发生在数据库关闭时将所有脏页刷新在磁盘。

2)Fuzzy Checkpoint:它发生在数据库运行时,它包括四种。分别是Master Thread Checkpoint、FLUSH_LRU_LIST Checkpoint、Dirty page too much Checkpoint、Async/Sync Flush Checkpoint(重做日志不可用时)

 

2.5 Master Thread 的工作方式

2.5.1 InnoDB 1.0.x版本之前的Master Thread

masterThread 具有最高的线程优先级别。

1.组成:主循环(loop)、后台循环(background loop)、刷新循环(flush loop)、暂停循环(suspend loop)。

1)loop:包括每秒的操作和每十秒的操作,由线程的sleep()控制,故这里的每秒和每十秒是不精确的。

2)background loop:当前没有用户活动或者数据库关闭时会切换到此线程。

3)flush loop:需要刷新页的时候切换到此线程。

4)suspend loop:若flush loop中也没事做,则会切换到此线程,将Master Thread 挂起。或者用户启用了InnoDB存储引擎,但是没有使用InnoDB存储引擎的表,也是此状态。

 

2.5.2 InnoDB 1.2.x版本之前的Master Thread

1.提供了参考参数innodb_io_capacity(改变硬编码带来的性能损失);

2.修改了innodb_max_dirty_pages_pct默认值为75(加快刷新脏页的频率、并且保证IO负载);

3.增加参数innodb_adaptive_flushing(自适应性刷新,该值影响每秒刷新脏页的数量);

4.引入innodb_purge_batch_size(该值控制每次full purge 时回收的Undo页的数量)

 

2.5.3 InnoDB 1.2.x版本之后的Master Thread

1.把每十秒和每秒的操作分别封装到两个函数内;

2.对于刷新脏页的操作,从Master Thread 中分离到一个单独的 Page cleaner Thread中操作,进一步提高系统的并发性。

 

2.6 InnoDB关键特性

包括插入缓冲(Insert Buffer)、两次写(Double Write)、自适应性哈希索引(Adaptive Hash Index)、异步IO(Async IO)、刷新邻接页(Flush Neighbor Page)。

 

2.6.1 插入缓冲

1.Insert Buffer

并非缓冲池的一个组成部分,而是物理页的一个组成部分。

使用场景:非唯一辅助索引的插入操作。

作用:对于非聚集索引的插入或更新操作,并非每一次都直接插入到索引页中。而是先判断插入的非聚集索引页是否在缓冲池中,若在直接插入;否则,先放到一个Insert Buffer对象中。然后再以一定的频率和情况进行Insert Buffer和辅助索引页子节点的合并操作,通常把多个插入合并为一个操作(因为在一个索引页中),则大大提高了非聚集索引插入的性能。

限制条件:1)索引是辅助(secondary)索引;2)索引不是唯一的。(看不懂这一点)

2.Change Buffer

在1.0.x版本后引入,可将其是为Insert Buffer的升级。从该版本开始,对应的DML操作)——INSERT、DELETE、UPDATE都能进行缓冲,分别是 insert buffer、delete buffer、purge buffer。

3.Insert Buffer的内部实现

内部结构:B+树,现在的版本中,全局只有一颗树,放在共享表空间(ibdatal)中。非叶节点存储的是查询的search key(键值);叶子节点存放的是对应的辅助索引页中的记录。

4.Merge Insert Buffer

可能发生Merge Insert Buffer的情况:

1)辅助索引页被读取到缓冲池中;2)Insert Buffer Bitmap 页追踪到该辅助索引页已无可用空间时;

3)Master Thread。(每秒或每十秒)

 

2.6.2 两次写

两次写带给InnoDB的是数据页的可靠性。

部分写失效:当发生数据库宕机时,可能InnoDB引擎正在写入某个页到表中,而这个页只写了一部分。例如16KB的页,只写了4KB。

double write的含义:在发生写失效时,先通过页的副本来还原页,再进行重组日志。(因为当页是损坏时,重做是无意义的。)

double write的组成(每部分均为2MB):1)内存中的double write buffer;2)磁盘上共享表空间的连续的128个页,即两个区;

                                       文章阅读——Mysql技术内幕_第5张图片

                                                                                 (double write 架构图)

 

2.6.3 自适应性哈希索引(AHI)

含义:InnoDB会监控对表上各索引页的查询。如果观察到建立哈希索引能够带来速度提升,则建立哈希索引。AHI是通过缓冲池的B+树页建造出来的,并不需要对全表构建。它的思想是数据库自优化

要求:1)AHI要求对这个页的连续访问模式必须是一致的,即查询的条件一样。(譬如WEHRE a = xxx)

2)以该模式访问了一百次;3)页通过该模式访问了N次,其中N=页中记录*1/16。

注意,HASH索引只能用来搜索等值查询 (即a=xxx)

 

2.6.4 异步IO(AIO)

含义:用户可以在发出一个IO请求后立即再发出另一个IO请求,当全部IO发送完毕后,等到所有IO操作的完成。不用等待一个IO操作完成后才能继续后面的操作。

优点:1)多个IO请求同时进行操作;2)可以进行IO Merge 操作,即将多个IO合并成一个IO,提高IOPS的性能。

 

2.6.5 刷新邻接页

工作原理:当刷新一个脏页时,InnoDB引擎会检查该页所在区(extent)的所有页,如果是脏页,则一起进行刷新。

好处:通过AIO可以把多个IO合并成一个IO操作,对于传统机械硬盘有很大好处(读写慢);l对于SSD则建议关闭。

 

2.7 启动、关闭和恢复

1.影响关闭的参数——innodb_fast_shutdown,该参数值可取0、1、2,默认为1。

0:表示数据库关闭时,InnoDB需完成所有的full purge 和 merge insert buffer,并将所有的脏页刷新回磁盘。

1:不需完成full purge 和 merge insert buffer。

2:上述操作都不执行,而是将日志都写入日志文件。

 

2.影响恢复的参数——innodb_force_recovery,该参数值可以是0-6,默认为0。

0:代表当发生需要恢复时,进行所有的恢复操作;当不能进行有效恢复时,把错误写入到错误日志中。

PS:当设置了大于0的参数后,用户不能对表进行DML操作。

 

第三章 文件

本章将分析构成Mysql和InnoDB存储引擎表的各种类型文件。它们包括:参数文件、日志文件、socket文件、pid文件、MySQL表结构文件、存储引擎文件。

 

3.1 参数文件

作用:告诉MySQL实例启动时可以在哪里找到数据库文件,并且指定某些初始化参数,这些参数定义了某种内存结构的大小等设置,还会介绍各种参数的类型。

与oracle的区别:oracle在没有参数文件时不能装载,而MySQL可以。但MySQL没有MySQL架构时则不可以启动(mysql.host)

 

3.1.1 什么是参数

简单来说,可以看成一个键/值对。(例如innodb_buffer_pool_size:1G)

PS:Oracle具有隐藏参数,供Oracle“内部人员”使用。SQL Server也有类似参数。(不建议使用)

 

3.1.2 参数类型

分为两类,动态参数和静态参数。

动态参数:可以在MySQL实例运行中更改。

静态参数:在整个实例生命周期内都不得进行更改。

 

3.2 日志文件

日志文件记录了MySQL数据库的各种类型活动。常见的日志文件有:错误日志、二进制日志、慢查询日志、查询日志。

 

3.2.1错误日志

该文件不仅记录了所有的错误信息,也记录一些警告信息或正确的信息。

 

3.2.2 慢查询日志

slow log 可帮助DBA定位可能存在问题的SQL语句,从而进行SQL语句层面的优化。默认为不开启。

 

3.2.3 查询日志

查询日志记录了所有对MySQL数据库请求的信息,无论这些请求是否得到了正确的执行。默认文件名为:主机名.log

 

3.2.4 二进制日志

(看不懂这里面的参数的介绍啥的)

二进制日志记录了对MySQL数据库执行更改的所有操作,但不包括SELECT 和 SHOW这类操作。(因为并没更改数据)然而,若操作本身没有导致数据库更改,该操作也可能会被写入二进制日志。默认不启动。可用来进行point in time的恢复以及复制环境的搭建。

作用:

1)恢复:某些数据的恢复需要二进制日志。

2)复制(replication):通过复制和执行二进制日志使一台远程的MySQL数据库(一般为slave或standby)与一台MySQL数据库(一般为master或primary)进行同步。

3)审计(audit):用户可以通过二进制日志中的信息进行审计,判断是否有对数据库进行注入的攻击。

 

3.3 套接字文件

在UNIX系统下本地连接MySQL可以使用UNIX域套接字方式,这种方式需要一个套接字文件,该文件可由参数socket控制。名为mysql.sock。

 

3.4 pid文件

当MySQL实例启动时,会将自己的进程ID写入到pid文件。该文件可由参数pid_file控制,文件名为主机名.pid。

 

3.5 表结构定义文件

该文件记录了该表的表结构定义,还存放视图的定义,后缀为frm。

 

(以上均为MySQL数据库本身的文件)

 

3.6 InnoDB存储引擎文件

包括重做日志文件、表空间文件。

 

3.6.1 表空间文件(tablespace file)

InnoDB采用将存储的数据按表空间进行存放的设计。默认配置下为10MB,名为ibdata1的文件。也可以通过设置innodb_file_per_table来为每个表生成独立的.idb文件。

                    文章阅读——Mysql技术内幕_第6张图片

                                                                       (InnoDB表存储引擎文件)

 

3.6.2 重做日志文件

重做日志文件记录了InnoDB存储引擎的事务文件。

与二进制日志的区别:

1)二进制日志会记录所有与MySQL相关的日志记录,包括InnoDB等其他存储引擎的日志。而重做日志只记录有关该存储引擎本身的事务日志。

2)记录的内容不同。二进制日志记录的是关于一个事务的具体操作,而重做日志文件记录的是关于每个页的更改物理情况。

3)写入时间不同。二进制文件只在事务提交前写入,只记录一次。而在事务进行的过程中,则会不断有重做日志条目被写入重做日志。

重做日志条目:

从左往右分别是:重做日志的类型;表空间的ID(采用压缩的方式);页的偏移量(压缩);重做日志的数据部分。

 

重做日志的写入过程:先写入重做日志缓冲,再按一定顺序写入重做日志。

文章阅读——Mysql技术内幕_第7张图片

 

第四章 表

简单地说,表就是关于特定实体的数据集合。

4.1 索引组织表

1.含义:根据主键顺序组织存放的表。

在InnoDB存储引擎中,每张表都会有主键。若用户没有显式创建,则InnoDB会按照如下规则创建:

1)首先判断表中是否有唯一的非空索引,若有则作为主键;

2)若无则自动创建一个6字节大小的指针。

若表中有多个唯一的非空索引,则选取建表时第一个显式定义的作为主键(根据的是定义索引的顺序,非列的顺序)。

 

4.2 InnoDB逻辑存储结构

所有数据均被放在表空间,表空间又由段、区、页(块)组成。

文章阅读——Mysql技术内幕_第8张图片

 

4.2.1 表空间

在默认情况下InnoDB存储引擎有一个共享表空间ibdata1,所有数据都存放在这里。如果启用了innodb_file_per_table,每张表的表空间存放的只有数据,其他的如回滚信息、插入缓冲索引页、系统事务信息、二次写缓冲还是放在共享表空间。

共享表空间的增大:InnoDB不会在执行rollback的时候去收缩这个空间,故会增大。

共享表空间的不再增大:虽然它不会收缩空间,但是它会判断这些undo信息是否还需要,若不需要,则会将这些空间标记为可用,供下次undo使用。

 

4.2.2 段(segment)

表是由段组成的, 常见的段有数据段、索引段、回滚段等。

由于InnoDB存储引擎表是索引组织的,因此数据即索引,所以索引即数据(看不懂)。数据段为B+树的叶子节点,索引段为B+树的非索引节点。

对段的管理是由引擎自身完成的。

 

4.3.2 区(extent)

区由连续的页组成,任何情况下的区均为1MB。为了保证页的连续性,每次从磁盘中申请4~5个区。

为了节省空间,表中先用32个碎片页存储数据,等到用完再采用区的方式申请页。

 

4.2.4 页

页是InnoDB磁盘管理的最小单位。常见的页类型有:数据页、undo页、系统页、事务数据页等等。

 

4.2.5 行

InnoDB存储引擎是面向列的,即数据是按行存放的。每个页存放的行记录有硬性规定,最多允许存放16KB/2-200行,即7992行记录。

 

4.3 InnoDB行记录格式

有Compact和Redundant两种格式。MySQL5.1中默认的是compact格式。

 

4.3.1 Compact行记录格式

1.设计目标:高效地存储数据。

2.格式:

在Compact格式下NULL值不占用任何空间,它只占用一个标志位。

 

4.3.2 Redundant行记录格式

 Redundant是5.0之前的行格式,MySQL5.0支持它是为了兼容之前的页格式。

1. 存储格式:

2.与Compact的不同:Redundant格式下的varchar类型的NULL不占空间,但CHAR类型的NULL占空间。

 

4.3.3 行溢出数据

InnoDB可以将一条记录的真正数据存放在数据存储页面的外面。一般认为BLOG、LOG这类大对象会如此,但不绝对。(VARCHAR类型也有可能会)

1.存放:一般情况下,数据都是存放页类型为B-tree node里面,当发生行溢出时,数据存放在页类型为Uncompress BLOG中。

文章阅读——Mysql技术内幕_第9张图片

要判断是否会产生行溢出数据,要看一个一个页是否能存放两条记录。

 

4.3.3 Compressed和Dynamic行记录格式

上述的两种旧格式统称为Antelope文件(页)格式,新的文件格式称为Barracuda文件格式,包含Compressed和Dynamic。

区别:1)新的格式对于存放在BLOG中的数据采取了完全的行溢出的方式。

文章阅读——Mysql技术内幕_第10张图片

2)存储在Compressed格式中的行数据会以zlib算法进行压缩,对于BLOG、TEXT、VARCHAR这类大长度的数据会进行有效的存储。

 

4.3.5 CHAR的行结构存储

从MySQL4.1开始,CHAR(N)中的N指的是字符长度,并非字节长度。这意味在不同的字符集中,在CHAR内部的存储中,它可能是不定长的。因此,对于多字节编码的CHAR类型数据的存储,InnoDB存储在内部视为变长字符类型。可认为在多字节字符集的情况下,CHAR和VARCHAR的实际行存储基本是无区别的。

 

4.4 InnoDB数据页结构

1.组成:文件头、页头、In'fimun和Supremum Records、用户记录(行记录)、空闲空间、页目录、文件结尾信息。

文章阅读——Mysql技术内幕_第11张图片

 

4.4.1 File Header

它用于记录页的一些头信息。

文章阅读——Mysql技术内幕_第12张图片

 

4.4.2 Page Header

它用来记录数据页的状态信息。

文章阅读——Mysql技术内幕_第13张图片
 

 

4.4.3 Infimum 和 Supremum Record

在InnoDB存储引擎中,每个数据页中有两个虚拟的行记录,用于限定记录的边界。Infimum用于记录比任何主键值都要小的值,Supremum用于记录比任何可能大的值还要大的值。

     文章阅读——Mysql技术内幕_第14张图片

 

4.4.4 行记录和空闲空间(User Record and Free space)

行记录为实际存储行记录的内容;空闲空间是个链表数据结构,当记录被删除后,它会被加入到空闲链表中。

 

4.4.5 Page Directory

它存放了页的相对位置(并非偏移量),有时候这些记录指针称为Slots(槽)或目录槽(Directory Slots)。InnoDB中的槽是一个稀疏目录,一个槽中可能包含多个记录。

PS:B+树索引本身并不能找到具体的一条记录,它能找到的只是记录的所在页。数据库把页加载进内存,然后再通过Page Directory进行二叉查找记录。

 

4.4.6 File Trailer(页尾)

它用来保证页的完整性。它一共八字节,前四字节是checksum值,后四字节和File Header中的FIL_PAGE_LSN相同。将这两个值与File Header中的相应值进行比较,来确保页的完整性。(checksum的比较并非简单的等值比较)
 

 

4.5 Name File Formats 机制

作用:用来解决不同版本下页结构兼容性的问题。

1.0.x之前的文件(页)格式称为Antelope,1.0.x之后的文件格式定义为Barracuda。Barracuda包含Antelope。

文章阅读——Mysql技术内幕_第15张图片

 

4.6 约束

现如今几乎所有关系型数据库都提供了约束机制,该机制确保数据完整性。

4.6.1 数据完整性

一般来说,数据完整性有以下三种形式:

1)实体完整性保证表中有一个主键。

2)域完整性保证数据每列的值满足特定的条件。

3)参照完整性保证两张表中的关系。

 

4.6.2 约束的创建和查找

可采用以下两种形式:1)表建立时就进行约束定义;2)利用ALTER TABLE命令创建约束。

 

4.6.3 约束和索引的区别

约束和索引的创建方法一致,创建了一个唯一索引就创建了一个唯一约束。它们的区别在于,约束更是一个逻辑的概念,用以确保数据完整性;而索引是一个数据结构,既有逻辑上的概念,在数据库上还代表着物理存储的方式

 

4.6.4 对错误数据的约束

在某些默认设置下,MySQL允许某些非法或者不正确的数据进行插入或更新。如果用户希望数据库不是警告而是报错,可以设置sql_mode=STRICT_TRANS_TABLE。

 

4.6.5 ENUM和SET约束

MySQL不支持CHECK约束,但可以通过ENUM和SET解决。

 

4.6.6 触发器约束

触发器作用:在执行INSERT、UPDATE、DELETE之前或之后自动调用SQL命令或存储过程。可通过触发器完成对CHECK的支持、物化视图、高级复制、审计等功能特性。

 

4.6.7 外键约束

外键用来保证参照完整性。

例子:FOREIGN KEY (parent_id) REFERENCES parent(id)

一般来说,称被引用的表为父表,引用的表为子表。当父表发生DELTE 或 UPDATE时,子表操作可定义为:CASECADE(跟随)、SET NULL(置空)、NO ACTION (报错)、RESTRICT(报错)。RESTRCT为默认。

与Oracle对比:1)Oracle中有一种称为延时检查(deferred check)的外键约束,而MySQL中的外键约束都是即时检查的。

2)在Oracle中,建立外键,一定要给此列加上索引。否则会出现由于没有设置索引导致行锁升级为表锁引发的锁等待和死锁问题。InnoDB则自动给我们加上了索引。

缺陷:在数据导入时,外键往往会导致在外键约束的检查上花费大量时间。

 

4.7 视图

在MySQL中,视图是一个虚表。它没有实际的物理存储。

 

4.7.1 视图的作用

视图的主要用途之一是做一个抽象装置,它在一定程度上起到一个安全层的作用。(程序并不关心基表的结构,只需要按照视图来读取和更新数据)一般称可更新的视图为 可更新视图。

 

4.7.2  物化视图

Oracle支持物化视图,该视图是根据基表实际存在的实表。

作用: 可用于预先计算并保存多表的链接(JOIN)和和聚集(GROUP BY)等耗时较多的SQL操纵结果。

好处:对于一些复杂的统计类查询能直接得到结果。

创建方式:两种。1)BUILD IMMEDIATE;(默认创建方式,创建时就生成数据) 2)BUILD DEFERED

 

4.8 分区表

分区:将一个表或一个索引分解为更小、更可管理的部分。

分区的作用:主要用于数据库高可用性的管理。

水平分区:将同一个表中不同行的记录分配到不同的物理文件中。

垂直分区:将同一表中不同列的记录分配到不同的物理文件中。

局部分区索引:一个分区中及既存放了数据,又存放了索引。

全局分区:数据存放在各个分区中,但是所有数据的索引存放在一个对象中。

MySQL数据库支持的是水平分区和局部分区索引,目前M有SQL支持RANGE、LIST、HASH、KEY等几种类型的分区。

分区列:若表中存在唯一索引或主键,分区列必须是唯一索引中的一部分。如不存在,则任何一个列均可。

 

4.8.2 分区类型

RANGE、LIST、HASH、KEY、COLUMNS四种。

RANGE:最常用。行数据基于属于一个连续区间的列值被放入分区。主要用于日期列的分区,例如对于销售类的表,可以根据年来分区存放销售记录。

 

LIST:同RANGE分区类型,但LIST分区面向的是离散的值。

 

HASH:根据用户自定义的表达式的返回值来分区,但是返回值不能为负值。它的目的是将数据均匀地分布到预先定义的各个分区中,保证各分区的数据量大致是一样的。

PS:MySQL还支持一种叫LINEAR HASH的算法,它使用的算法更加复杂。与HASH相比,它的优点是增加、删除、合并和拆分分区更快,有利于处理含有大量数据的表;缺点是它各个分区间的数据分布可能不太均衡。

 

KEY:根据MySQL提供的哈希函数举行分区。

 

前面四种分区的条件是数据必须是整形,而COLUMNS可直接使用非整型的数据进行分区。

COLUMNS:它可视作RANGE和LIST的一种进化。它支持所有的整性、日期类型、字符串类型。

 

4.8.3 子分区

含义:在分区的基础上再进行分区,也称为复合分区。MySQL允许在RANGE和LIST分区上再进行HASH和KEY的子分区。

注意问题:1)每个子分区的数量必须相同;2)要在一个分区表的任何分区上用SUBPARTITION来明确定义任何分区,就必须要定义所有子分区;3)每个SUBPARTITION子句必须包括子分区的一个名字;4)子分区的名字必须是唯一的。

 

4.8.4 分区中的NULL值

MySQL允许对NULL分区,MySQL的分区总是把NULL视小于任何一个非NULL的值,这和MySQL处理NULL值的ORDER BY 是一样的。

对于RANGE分区:NULL值会被放入最左侧的分区。即删除p0这个分区,会删除掉NULL值。

对于LIST分区:必须显式地指出NULL值放入哪个分区,否则会报错。

HASH和KEY:任何分区函数会将含有NULL值的记录返回为0.

 

4.8.5 分区和性能

 数据库的应用分类:两类,分别是OLTP(在线事务处理),如Blog、电子商务、网络游戏等;以及OLAP(在线分析处理),如数据仓库、数据集市。

对于OLAP来说,分区可以很好地提高查询的性能;对于OLTP,分区应该十分小心,因为它通常不可能会获取一张大表中的10%的记录,B+树会很好地完成操作,不需要分区的操作。反而要注意因为不当的分区带来的性能问题。

 

4.8.6 在表和分区间交换数据

ALTER TABLE  .... EXCHANGE PARTITION 允许分区或子分区中的数据与另一个非分区的表中进行数据交换。

使用所需满足条件:1)两个表必须具有相同的结构,且要交换的表不能有分区;2)在非分区表的数据必须在交换的分区定义内;3)被交换的表不能有外键或其他表对它的外键引用;4)用户需要有ALTER、INSER、CREATE、DROP的权限;5)使用该语句时不会触发两个表的触发器;6)AUTOINCREMENT将被重置。

 

第五章 索引和算法

 

5.1 InnoDB存储引擎索引概述

InnoDB支持以下几种常见索引:B+树索引、全文索引、哈希索引(自适应,不能人为添加)。

 

5.3 B+树

在说到B+树和B-树前,先说一下它们最主要的区别,以及为什么B+树适用于文件系统:(个人理解)

B+树的非叶子节点只存储key,只有叶子节点存储data。叶子节点包含了这棵树的索引键值,不存储指针。

由于索引一般都很大,以索引文件的形式存储着,这也意味着I/O操作。故树越矮,I/O次数越少。而B+树非叶子节点只存储索引,相比B-树,它会矮很多,减少很多I/O操作,故它对于文件系统而言更为合适。

B+树索引并不能找到记录具体所在行,只能找到所在页。需要把所在页导入内存,再继续查找。

1.含义:B+树是为磁盘或其他直接存取辅助设备设计的一种平衡查找树。在B+树中,所有的记录节点都是按键值的大小顺序存放在同一层的叶子节点上,由各叶子节点指针进行链接。

文章阅读——Mysql技术内幕_第16张图片

 

5.3.1 B+树的插入

1. 插入的三种情况:

文章阅读——Mysql技术内幕_第17张图片

无论如何拆分,B+树都会保持平衡。但为了保持平衡,会做大量的拆分页操作。而页的操作也意味着磁盘的操作,故应该尽量减少。因此,B+树也提供了类似二叉平衡树的旋转功能。

 

5.3.2 B+树的删除操作

B+树使用填充因子(fill factor)来控制删除树的删除变化,50%是填充因子可设的最小值。(就是类似于B-树的下限)。

1.删除的三种情况:

文章阅读——Mysql技术内幕_第18张图片

 

5.4 B+树索引

1.B+树索引本质:B+树在数据库中的实现。

2.B+树索引特点:高扇出性,故在数据库中,B+树一般的高度为2-4层。即查找某一键值的行记录时最多只需要2-4次IO,而一般的机械磁盘可以做到1s100次I/O,故只需要0.02~0.04s。

3.索引分类:分为聚集索引和辅助索引(非聚集索引)。它们的区别是叶子节点存放的是否是一整行的信息。

 

*5.4.1 聚集索引(Clustered Index)

1.含义:聚集索引就是按照每张表的主键构造一棵B+树,同时叶子节点中存放即为整张表的行记录数据,也将聚集索引的叶子节点称为数据页。根据聚集索引扫描即表扫描。

2.特点:每个表只能有一个聚集索引,因为实际的数据页只能在一棵B+树上排序。在多数情况下,查询优化器倾向于聚集索引。因为聚集索引能够在B+树的叶子节点上直接找到数据。此外,由于定义了数据的逻辑结构,聚集索引能够特别快地访问针对范围值的查询。

一个存放实例:非叶子节点存放的是键值和偏移量,而叶子节点存放的是一个完整的行记录。

文章阅读——Mysql技术内幕_第19张图片

 

3.好处:1)在逻辑上存储连续,物理上不用。有两点原因:①页通过双向链表链接,且页按照主键的顺序排序;② 每个页中的记录也是通过双向链表进行维护的。

2)对于主键的排序查找和范围查找非常快。

 

5.4.2 辅助索引(Secondary Index)

1.内容:叶子节点并不包含行记录的全部记录。叶子节点除了包含键值以外,每个叶子节点的索引行中还包含了一个书签(bookmap),该书签用来告诉InnoDB存储引擎哪里可以找到与索引相对应的行数据。而这个书签就是相应行数据的聚集索引键。

2.特点:每个表上可有多个辅助索引;

文章阅读——Mysql技术内幕_第20张图片

 

5.4.3 B+树索引的分裂(最难的操作)

(这个我看不懂它是怎么操作的,它的分裂在数据顺序插入的情况下并不是像B-树的选取中间节点分裂的)

1.选择分裂点和分裂方向:InnoDB中的Page Header中有以下几个部分来保存插入的顺序信息:PAGE_LAST_INSERT、PAGE_DIRECTION、PAGE_N_DIRECTION。

若插入是随机的,则选取页的中间记录为分裂点;若往同一个方向进行插入的记录数量为5,并且目前已经定位到的记录(InnoDB引擎进行插入时,首先要进行定位,定位到的记录为待插入记录的前一条记录)之后还有3条记录,则分裂点的记录为定位到的记录后的第三条记录,否则分裂点就是待插入的记录。

情况一向右分裂的例子:

文章阅读——Mysql技术内幕_第21张图片

情况二向右分裂的例子:

文章阅读——Mysql技术内幕_第22张图片

分裂后:

文章阅读——Mysql技术内幕_第23张图片

 

5.4.4 B+树索引的管理

1.索引管理

1)创建和删除:索引的创建和删除有两种方法,一种是ALTER TABLE;一种是CREATE/DROP INDEX。

2)查看索引信息:可以用SHOW INDEX FROM 命令查看。其中会列出许多值,而Cardinality的值非常关键。

Cardinality:表示索引中唯一值的数目的估计数。Cardinality表的行数应该尽可能接近1,如果非常小,那么用户可以考虑是否删除该索引。

优化器会根据Cardinality值来判断是否使用这个索引。但这个值并不是实时更新的,如果需要更新,可以使用ANALYSE TABLE命令。当Cardinality为NULL,可能发生索引建立了但没有被使用的情况。

 

2.Fast Index Creation(FIC,快速索引创建)

1)Mysql5.5之前建立索引和删除索引等操作,MySQL的操作过程:首先建立一张临时表;把原数据导入临时表;删除原表;临时表重命名为原表名。

缺点:时间长,索引修改时数据库不可用。

2)5.5后FIC的操作:对创建索引的表添加一个S锁,不用重建表;对于删除索引,更新内部视图对表的索引定义即可。

缺点:创建过程中同样不可进行写操作;FIC仅限于辅助索引。

3)Online Schema Change(OSC,在线架构改变)

含义:指在事务的创建中,可以有读写事务对表进行操作。它有PHP实现。

缺点:对其进行修改的表一定要有主键,不能有外键和触发器。

4)Online DDL(在线数据定义)

含义:其允许在辅助索引创建的同时,允许INSERT、UPDATE、DELETE等DML操作。

实现原理:在执行创建或删除时,把DML操作日志写入到一个缓存中。待完成索引的创建再重做应用到表上,以此达到数据的一致性。

注意:由于此种方式是通过重做日志达到数据库的一致性,所以SQL优化器不会选择创建中的索引。

 

5.5 Cardinality值

5.5.1 什么是Cardinality值

1.B+树索引的使用:当字段具有高选择性时,适合使用此索引

高选择性:若某个字段的取值很广,例如姓名,几乎没有重复,则称此字段具有高选择性;而某一字段的取值范围很小,例如性别,只有M和F,则称其具有低选择性。

如何判断选择性的高低?通过Cardinality字段。

2.Cardinality含义:此值表示索引中不重复记录数量的预估值。在实际引用中,cardinality/n_rows_in_tables应尽可能接近1.若非常小,则用户需要考虑是否还有必要创建该索引。

 

5.5.2 InnoDB存储引擎的 Cardinality统计

1.统计存放:由于各种引擎对于B+树索引的实现都不同,所以该值的统计是放在存储引擎层进行的。

2.统计方法:采样。取得B+树索引中叶子节点的数量A,默认InnoDB引擎对X个叶子节点进行采用,则Px为第x个页不同记录的个数,则Cardinality=(P1+……+Px)*A/x。

3.更新策略:Cardinality值的更新发生在INSERT和UPDATE两个操作中,它分为两种策略:

1)表中1/16的数据已发生过变化;

2)stat_modified_counter>2 000 000 000。

第二种策略考虑的是,对表中某一行数据频繁进行更新操作的情况。stat_modified_counter是表示发生变化的次数。

 

*5.6 B+树索引的使用

5.6.1 不同应用中B+树索引的使用

1.对于OLTP:在OLTP应用中,查询操作一只是从数据库中取小部分数据,例如根据主键值取的用户信息。在这种情况下,索引的建立是有意义的。

2.对于OLAP:在OLAP应用中,一般要访问表中的大量数据。因此在OLAP中添加的索引应该是宏观的信息,而不是微观的。例如不需要在OLAP对姓名进行索引;对于OLAP的复杂操作,索引又变得有意义,但是链接操作若使用的是Hash join,则又变得不是非常重要了。故在OLAP中,需要根据实际情况来添加索引。不过一般都会对时间字段进行索引。这是因为大多数统计都需要根据时间来对数据进行筛选。

 

5.6.2 联合索引

1.含义:对表上的多个列进行索引。它的创建方法与单个索引创建相同。

2.联合索引的内部存储:联合索引也是一棵B+树,但是它的键值>=2

                            文章阅读——Mysql技术内幕_第24张图片

例如,对于查询SELECT * FROM TABLE WHERE a = xxx and b = xxx,可以使用联合索引(a,b);对于a的单独查询也可以使用(a,b)索引;但对于b的单独查询不可以,因为b的数据在叶子节点上的值并非顺序排放的。

3.联合索引的好处:联合索引已经对第二个键值进行了排序。例如某程序要查询用户的购物记录,并取出最近三次的购物记录。设置联合索引为(userid,buy_date)。此时联合索引就可以避免多一次的排序操作,因为索引本身在叶子节点中就已经排序了。(即id为1,2...,X的用户,它们各自的购物记录都按照时间从大到小顺序排好了。)

4.总结

联合索引(a,b,c)实质是根据列a、b、c进行排序,即a排好序后,b根据a排序,c又根据b排序。即通过下列语句都可以使用联合索引直接得到结果:

SELECT ... FROM TABLE WHERE a = xxx ORDER BY b

SELECT ... FROM TABLE WHERE a = xxx and b = xxx ORDER BY c

不可以直接得到结果,还要进行一次file sort排序:

SELECT ... FROM TABLE WHERE b = xxx ORDER BY c

SELECT ... FROM TABLE where a = xxx ORDER BY c

因为索引(a,c)并未排序。

 

5.6.3 覆盖索引(索引覆盖)

1.含义:即从辅助索引中即可得到查询的记录,而不需要通过聚集索引查询。

2.好处:1)辅助索引远小于聚集索引,可减少大量的I/O操作。

e.g.  SELECT primary key2,key2 FROM TABLE WHERE key1 = xxx  可仅通过一次辅助联合索引搜索完成。

2)对某些统计问题而言,辅助索引可减少大量的I/O操作。

e.g. SELECT COUNT(*) FROM  buy_log;

SELECT COUNT(*) FROM buy_log WEHRE buy_date >= xxx AND buy_date <= xxx

在buy_log上有联合索引(user_id,buy_date),但是诸如(a,b)这样的联合索引,一般是不可以选择列b中的所谓的查询条件的。但是如果是统计操作,并且是覆盖索引的,则优化器会进行选择该联合索引

 

5.6.4 优化器选择不使用索引的情况

在某些情况下,优化器会通过直接进行全表的扫描获得数据。这种情况多发生在范围查找、JOIN链接操作等。

e.g. SELECT * FROM TABLE WHERE orderid > 10000 AND orderid <=102000; (orderid 为索引)

原因:用户需要的是整行信息,而在对orderid索引查询之后,还有进行一次书签访问才能得到所需数据。书签访问的数据是无序的,书签访问则变成了磁盘上的离散读操作。当访问量少时,还是会选择辅助索引;但当数据量占据表中20%以上时,就会选择聚集索引(即全表扫描),因为聚集索引是顺序读,远快于离散读。

 

5.6.5 索引提示(INDEX HINT)

1.含义:显式地告诉优化器使用哪个索引。

2.使用场景:1)优化器错误地选择了某个索引,导致SQL语句运行的很慢;

2)某SQL语句可以选择的索引很多,此时优化器选择执行计划时间的花销可能会大于SQL语句本身。

3.使用:可以在sql语句中使用user index(x) 或者是 force index(x)。使用user index(x),优化器最终还是会再根据自己的判断选择;而force index(x)则是一定选择指定的那个索引。

 

5.5.6 Multi-Range Read 优化(MRR)

1.目的:减少磁盘的随机访问,并且将随机访问转化为较为顺序的数据访问。它可适用于range,ref,eq_ref的查询。

2.好处:1)MMR使数据访问变得较为顺序。在查询辅助索引时,首先根据查询得到的结果,根据主键进行排序。再按照主键排序结果进行书签查找;

2)减少缓冲池中页被替换的次数;3)批量处理对键值的查询操作,可直接过滤掉不符合条件的数据。(可参考P225的例子)

 

5.6.7 Index Condition Pushdown(ICP)优化

1.含义:对于WHERE 条件的过滤,ICP优化会在取出索引的同时判断是否可以过滤,也就是将WEHRE的部分过滤操作放到了存储引擎层。不进行优化的时候是先查找记录,再进行过滤。它支持range、ref、eq_ref、ref_or_null 类型的查询。

 

*5.7 哈希算法

5.7.1 哈希表

1.含义:也称散列表,由直接寻址表改进而来。利用哈希函数h根据关键字k计算出槽的位置,函数h将关键字域U映射到哈希表T[0..m-1]的槽位上。(此处的m并非一个很大的数)

哈希函数:一般来说,都将关键字转为自然数,然后通过乘法、除法或全域散列来实现。数据库用的是除法散列的方法。即h(k)=k mod m

2.碰撞解决:在数据库中,一般采用最简单的碰撞解决技术——链接法。

链接法:把散列到同一个槽中的所有元素都放到一个链表上。

 

5.7.2 InnoDB存储引擎中的哈希函数

InnoDB使用哈希算法来对字典进行查找。对于缓冲池页的hash表来说,页的关键字K=space_id<<20+space_id+offset,m取为略大于2倍的缓冲池页数量的质数。

 

5.7.3 自适应哈希索引

它采用的哈希表实现方式和上述一样。不同的是,这是由数据库进行创建和使用的,DBA不能对其进行干预。它对于字典类型的查找非常迅速,且它只能用于搜索等值的查询。默认为开启。

e.g. SELECT * FROM TABLE WHERE index_clo = 'xxx'

 

5.8 全文检索(Full-Text Search)

5.8.1 概述

1.全文检索含义:它是指将存储于数据库中的整本书或整篇文章中的任意内容信息查找出来的技术。

 

5.8.2 倒排索引

全文检索通常用倒排索引实现。

1.含义:倒排索引同B+树索引一样,也是一种引结构。它在辅助表(auxiliary table)中存储了单词与单词在一个或多个文档中的所在位置之间的映射。这通常利用关联数组实现。

2.表现形式:通常有两种。1)inverted file index{单词,单词所在的文档的id};2)full inverted index{单词,{单词所在的文档id,在具体文档中的位置}}。

 

5.8.3 InnoDB全文索引

InnoDB从1.2.x版本开始支持全文检索的技术,采用地使full inverted index。

1、存储方式:在InnoDB中,把(id,position)作为一个ilist,故在全文检索表中,有两个字段,一个是word字段,一个是ilist字段。并且可以在word字段上设有索引。因为ilist字段中有position,故可以进行Proximity Search,MyISAM不支持此特性。

2、辅助表:有六张,目前每张表根据word的Latin编码进行分区。表存储在磁盘上。InnoDB引擎会定时对其进行批量更新。

3、全文检索索引缓存(FTS Index Cache):

1)作用:提高全文检索的性能;

2)存储结构:其为红黑树结构,根据(word,ilist)排序。

3)与存储表的关系:当对全文检索进行查询时,辅助表首先会将全文检索索引缓存中的word合并到辅助表中,再进行查询。

4、全文检索文档ID(FTS Document ID):

1)含义:与word进行映射的列。类型一定要为BIGINT UNSIGNED NOT NULL。InnoDB引擎会自行创建。

2)作用:对于删除操作,并不删除辅助表中的记录,只删除缓存中的记录。而对于辅助表中被删除的记录,它会其相应的FTS Document ID保存到DELETED auxiliary table中。

故文档中的DML操作实际上不删除索引中的数据。而随着应用程序的允许,索引就变得非常庞大。(可选择手动删除)

5、stopword列表:其表示该列表中的word不需要对其进行索引分词的操作。(e.g. the这个单词,因为其无任何意义)

innoDB有一张默认的stopword表。

6、InnoDB全文检索的限制:1)每张表只能有一个全文检索的索引;

2)由多列组合而成的全文检索的索引列必须使用相同的字符集和排序规则;

3)不支持没有单词界定符(delimiter)的语言,如中文、日语、韩语等。

 

5.8.4 全文检索

MySQL支持全文检索的查询。其语法为MATCH() ... AGAINST() 。

MATCH指定了需要被查询的列,AGAINST指定查询的方法。以下为对各种查询模式的介绍。

1. Natural Language(默认):其表示查询带有word的文档。

2.Boolean:当使用该修饰符时,查询字符串的前后字符会有特殊的含义。例如要求查询hot字符前面不能有wind,后面一定要有test等。

3.Query Expansion(拓展查询):这种查询关键在于查询的关键词太短,用户需要implied knowledge(隐含知识)时进行。

e.g. 对于单词database的查询,用户可能希望查询的是不仅包含database的文档,且还包含mysql、oracle等等单词的文档。

 

第六章 锁

6.1 什么是锁

锁是数据库区别与文件系统的一个关键特性。

1.锁的作用:支持对共享资源进行并发访问,提供数据的完整性和一致性。

2.实现:不同的数据库系统锁的实现都不同。对于MyISAM,其锁是表锁设计;Microsoft SQL Server,支持乐观并发和悲观并发,但其实现的方式和InnoDB不同,其会有锁升级,但InnoDB不会。(InnoDB不需要锁升级,因为一个锁和多个锁的开销是相同的。)

InnoDB下的锁:其实现与Oracle非常相似,提供一致性的非锁定读、行级锁支持。

 

6.2 lock和latch

在数据库中,lock和latch都可以被称为锁。但是二者含义截然不同。

1.latch:其一般被称为闩锁(轻量级锁),因为其要求锁定的时间非常短。其目的是保证并发线程操作临界资源的正确性。

2.lock和latch的区别:

文章阅读——Mysql技术内幕_第25张图片

 

6.3 InnoDB存储引擎中的锁

6.3.1 锁的类型

1.类型:1)共享锁(S Lock):允许事务读一行数据;

2)排他锁(X Lock):允许事务删除或更新一行数据。(1.2均为行级锁)

2. 锁兼容(Lock compatible):锁被获取后仍可被多次获取。S锁仅和S锁兼容,X锁与任何锁都不兼容。

3. 多粒度锁定:这种锁定允许事务在行级上的锁和表级上的锁同时存在。

4.意向锁

意向锁为表级别的锁,相比较于行级锁为更粗粒度的锁。而对细粒度的对象加锁时,首先要对粗粒度的对象加锁。

如,需要对页上的记录r上X锁,则需要先对数据库A、表、页上意向锁IX。

1)目的:为了支持多粒度锁定。能够在一个事务中揭示下一行将被请求的锁类型。

2)类型:① 意向共享锁:事务想要获取一张表中某几行的共享锁;②意向排他锁:事务想要获取一张表中某几行的排他锁。

图为InnoDB中各种锁的兼容性:

文章阅读——Mysql技术内幕_第26张图片

 

6.3.2 一致性非锁定读(consistent nolocking read)(默认读取方式)

1.含义:指InnoDB通过行多版本控制的方式来读取当前执行时间数据库中行的数据。如果读取的行正在执行DELETE或UPDATE操作,这时读取操作不会因此去等待行上锁的释放。相反地,InnoDB会去读取行的一个快照数据。

快照数据:1)含义:是指行之前版本的数据,该实现是通过undo段完成的。而undo用来在事务中回滚数据,因此快照数据本身无何开销。此外,读取快照数据不需要上锁;2)一个行记录可能有不止一个快照数据,此种技术称为多行版本技术;由此带来的并发控制,称为多版本并发控制(Multi Version Concurrency Control,MVCC)。

2.优点:极大地提高了数据库的并发性。

 

6.3.3 一致性锁定读

1.使用场景:用户需要显式地对数据库读取操作进行加锁以保证数据逻辑的一致性。

2.加锁语句:对于SELECT 语句支持两种。1)SELECT ... FOR UPDATE;(加X锁)2)SELECT ... LOCK IN SHARE MODE。(加S锁)

这两个语句的使用必须在一个事务中,事务完成了,锁就释放了。

 

6.3.4 自增长与锁

1.AUTO-INC Locking:在InnoDB的内存结构中,每个含有自增长值的表都有一个自增长计数器。插入操作会依据这个自增长技术器值加1赋予自增长列。这个实现方式称作AUTO-INC Locking。这种锁其实是采用一种特殊的表锁机制,在完成对自增长插入的SQL语句后立即释放。

2.AUTO-INC Locking的不足:1)对于有自增长值的列的并发插入性能较差,事务必须等待前一个插入的完成;

2)对于INSERT ... SELECT  的大数据量的插入会影响插入的性能,因为另一个事务中的插入会被阻塞。

为了提高性能,从InnoDB 5.1.22 版本开始,InnoDB提供了一种轻量级互斥量的自增长实现机制。同时还引入了一个参数innodb_autonic_lock_mode来控制自增长的模式。

3.插入类型

文章阅读——Mysql技术内幕_第27张图片

4.参数innodb_autonic_lock_mode的说明

文章阅读——Mysql技术内幕_第28张图片

 

6.3.5 外键和锁

对于外键值的插入和更新,首先要SELECT父表。对于父表的SELECT操作,为了保持父表和子表数据一致性,使用的是对父表加S锁。若此时父表已经加X锁,则字表的操作会被阻塞。

 

6.4 锁的算法

6.4.1 锁的3种算法

1. Record Lock :单个行纪录上的锁

2. Gap Lock:间隙锁,锁定一个范围,但不包含纪录本身。

3. Next-key Lock:Gap+Record Lock,锁定一个范围,且锁定纪录本身。在此算法下,InnoDB对应行的查询都是采用此种算法。

 

4.Next-key Locking

1)含义:采用Next-key Lock的锁定技术。其设计目的是为了解决Phantom Problem。这种技术是为谓词锁(predict lock)的一种改进。

2)与previous-key lock的区别:next-key locking技术,锁定的区间是前开后闭,而previous-key lock则相反。

 

5.phantom problem(幻读问题)

1)含义:在同一事务中,连续执行两次同样的SQL语句可能会导致不同的结果。

2)导致原因:gap lock。

(看书不是很懂,看着像是说gap lock 导致了幻读,然后在官方文档里面找到了这个说法,Gap locking can be disabled as discussed in Section 15.6.1, “InnoDB Locking”. This may cause phantom problems because other sessions can insert new rows into the gaps when gap locking is disabled. 所以是没有gap lock会导致幻读。)

3)关闭gap lock:两种方式。① 将事务的隔离级别设置为READ COMMITTED;

② 将参数innodb_locks_unsafe_for_binlog 设置为1。

在上述配置中,除了外键约束和唯一性检查仍需要gap lock,其他情况仅用Record Lock进行锁定。上述设置破坏了事务的隔离性,对于replication,还可能会造成主从数据的不一致。

4)解决方法

在默认的事务隔离系统中(REAPETABLE READ),采用Next-Key Locking来避免幻读问题。

 

6.5 锁问题

三种问题,脏读,不可重复读,丢失更新。

 

6.5.1 脏读

1.脏数据:事务对缓冲池中行纪录的修改,并且还没有被提交。

2.脏读:即在不同的事务中,当前的事务可以读到另外事务未提交的数据。即读到脏数据。

3.发生条件:事务的隔离级别为READ UNCOMMITTED。

 

6.5.2 不可重复读

1.不可重复读:在一个事务内两次读到的数据不一样。其违反了一致性。

2.与脏读的区别:脏读是读到未提交的数据,不可重复读读到的是已经提交的数据。

 

6.5.3 丢失更新

1.含义:一个事务的更新操作被另一个事务的更新操作所覆盖,从而导致数据的不一致。

2.导致丢失的操作

在当前数据库的任何隔离级别下,都不会导致数据库理论意义上的丢失更新问题,只会导致逻辑意义上的丢失更新问题。

例如:事务t1查询一行数据,放入内存,并显示给终端用户user1 ---> 事务t2也查询该行数据,放入内存,显示给终端用户user2 ---> user1修改纪录并更新数据库  ----> user2修改纪录并更新数据库。(P274页有具体例子,可看)

3.解决方案:让事务在此种情况下操作串行化。既在上述四个步骤中,1)中加一个排他X锁,在3)中也加一个排他X锁。这样,)的操作就要等待1和3完成了。

 

6.6 阻塞

1.含义:一个事务中锁需要等待另一个事务中的锁释放资源。

在INNODB中,innodb_lock_wait_timeout 用来控制等待时间;innodb_rollback_on_timeout用来设定在等待超时时对进行中的事务是否回滚(默认为OFF,不会滚,但这是非常危险的)

 

6.7 死锁

1.概念:它是指两个或两个以上的事务在执行过程中,因争夺锁资源而造成的一种互相等待的现象。

2.解决办法

1)超时:即两个事务互相等待时,当一个等待时间超过阈值,其中一个事务进行回滚,则另一个事务能进行下去。虽然简单,但若超时的事务所占权重比大,回滚此事务不合适。

2)wait for graph(等待图)(常用)

该方法要求数据库保存两种信息,分别是锁的信息链表和事务等待链表。根据这两种信息构造出等待图,若该图存在回路,则说明存在死锁。此时一般选择回滚undo量最小的事务来姐解除死锁。

它通常采用深度优先算法来实现。

 

3.死锁概率

死锁概率一般是非常小的。

 

4.死锁原因

1)在Oracle中:常见原因是没有对外键添加索引

2)事务持有了待插入纪录的下一个纪录的X锁,但是在等待队列中存在一个S锁的请求,也可能发生死锁。(P283例子)

 

6.8 锁升级(P284)

1.概念:所升级是指将当前锁的粒度降低。

2.作用:保护系统资源,防止系统使用太多资源来维护锁,一定程度上提高效率。(锁的粒度越大,并发越小,开销小)

3.innoDB中的锁升级

PS:根据书里的讲述,个人的理解是,不是不能进行锁升级,是InnoDB设计的时候觉得没必要。它认为即使锁很多个表(很多页)和锁行的开销不大。

InnoDB中不存在锁升级。因为其不是根据纪录来产生行锁的,而是根据每个事务访问的每个页对锁进行管理的,采用的是位图的方式。故锁住多少纪录开销都是一样的。(这里我的理解是,锁一张表可能和锁一行的开销一样,但是多张表的开销还是有差异的)

 

*第七章 事务

事务是数据库区别于文件系统的重要特性之一。

数据库引入事务的主要目的,是为了确保其把数据库从一种一致状态转换到另一种一致的状态。

事务有四个特性(ACID),原子性(automicity),一致性(consistency),隔离性(isolation),持久性(durability)。

 

7.1 认识事务

7.1.1 概述

1.是什么:事务是访问并更新数据库中各种数据项的一个程序执行单元。它可以由一条SQL语句组成,也可以由一组复杂的SQL语句组成。

2.四个特性简介

原子性:原子性指整个数据库事务是不可分割的工作单位。即要么都不做,要么都做完。

一致性:一致性指事务将数据库从一种状态转变为下一种一致的状态。例如,一个表中姓名字段设定为唯一约束,即不能重复。但在一事务的提交或事务操作发生回滚后,姓名变得不唯一了。这就破坏了事务的一致性要求。

隔离性:隔离性又称为可串行化、锁、并发控制等。事务的隔离性要求每个读写事务的对象对其他事务的操作对象能互相分离,即该事务提交前对其他事务不可见,通常这使用锁来实现。

持久性:即事务一旦提交,就是永久性的。即使发生宕机等故障,数据库也能将数据恢复。持久性保证数据库的高可靠性(非高可用性)。

 

7.1.2 事务的分类

可分为扁平事务、带有保存点的扁平事务、链事务、嵌套事务、分布式事务。

 

1.扁平事务(flat transaction)

扁平事务是最简单的事务,也是实际生产使用最频繁的事务。

是什么:在扁平事务中,所有操作都处于同一层次,由BEGIN WORK 开始,由COMMIT WORK /ROLLBACK WORK 结束。期间的操作是原子的,要么都执行,要么都不执行。因此扁平事务是应用程序成为原子操作的基本组成模块。

执行情况:有三种

文章阅读——Mysql技术内幕_第29张图片

 

不足:它的主要限制是不能提交或者回滚事务的一部分,或分几个步骤提交。

 

2.带保存点的扁平事务(Flat  Transcations with Savepoint)

是什么:除了支持扁平事务的操作外,它还允许事务执行过程中回滚到较早的一个状态。保存点用来通知系统应该记住事务当前的状态,以便当之后发生错误时,事务能够回到保存点当时的状态。

注意,保存点并非持久的,它具有易失性。这意味着,当数据库系统发生崩溃时,保存点会丢失。

可参考:https://dev.mysql.com/doc/refman/8.0/en/savepoint.html

 

3.链事务(Chained Transaction)

是什么:可视为保存点模式的一个变种。它的思想是,在提交一个事务时,释放不需要的数据对象,将必要的处理上下文隐式地传给下一个要开始的事务。注意,提交事务操作和下一个事务的开始将合并为一个原子操作。

工作方式:文章阅读——Mysql技术内幕_第30张图片

与带保存点的扁平事务的区别:

带保存点的扁平事务能够回滚到任意正确的保存点(用户指定保存点),而链事务中的回滚仅限于当前事务,且只能恢复到最近的一个保存点;

链事务在执行COMMIT后释放了当前事务所持有的锁;而带有保存点的扁平事务不影响迄今为止所持有的锁。

 

4.嵌套事务

是什么:嵌套事务是一个层级结构框架。由一个顶层事务控制着各个层次的事务。顶层事务之下嵌套着的各层事务称为子事务,其控制着每一个局部的变换。

层次结构图

文章阅读——Mysql技术内幕_第31张图片

 

5.分布式事务

是什么:通常是一个在分布式环境下运行的扁平事务,因此需要根据数据所在位置访问网络中的不同节点。

 

InnoDB出了嵌套事务,都支持。

 

7.2 事务的实现

redo log(重做日志)保证事务的原子性和持久性。undo log 保证事务的一致性。

 

7.2.1 redo

1.基本概念

重做日志用来实现事务的持久性,其由两部分组成:具有易失性的重做日志缓冲和持久性的重做日志文件

 

2.log block

1)是什么:重做日志缓存和文件都是以块的方式保存的,称为重做日志块,每块的大小为512字节。

由于其大小和磁盘扇区的大小一样,故它的写入可以保证原子性,不用doublewrite技术。

2)组成:由日志块头(12)、日志本身(492)和日志块尾(8)组成。结构图如下

文章阅读——Mysql技术内幕_第32张图片

 

3.log group

1)是什么:log group为重做日志组,其中有多个重做日志文件。其为逻辑概念。

 

4.重做日志格式(基于页)

1)通用的头部格式:

文章阅读——Mysql技术内幕_第33张图片

 

5.LSN(Log Sequence Number)

它表示的含义有:重做日志写入的总量、checkpoint的位置、页的版本。

 

6.恢复

InnoDB在启动时不管上次数据库是否正常关闭,都会尝试进行恢复。

因为重做日志纪录的是物理日志,它的恢复速度会比逻辑日志(二进制日志等)快很多。

 

7.2.2 undo

1.基本概念:纪录事务的初始状态,用于事务失败时进行回滚的日志。

2.存放位置:存放在数据库内部的一个特殊段,叫undo段。undo段位于共享表空间。

3.作用:① 事务回滚。把数据库逻辑地恢复到原来的样子。undo是逻辑日志,故所有修改只是被逻辑地取消了,但是数据结构和页本身在回滚后可能不大相同。② 实现MVCC。当用户读取一行纪录时,若该纪录已经被其他事务占用,当前事务可以通过undo来读取之前的行版本,以此来实现非锁定读取。

4.undo log 也会产生redo log。

5.undo log 格式

undo log 分为insert undo log 和 update undo log。

insert undo log:纪录insert 操作中产生的 undo log。该undo log 可在事务提交后立刻删除,不需进行purge操作。

undate undo log:纪录delete 和 update 操作产生的undo log。该undo log 可能要提供MVCC机制,故提交时要放入undo log 链表,等待purge 线程进行最后的删除。

 

 

 

 

 

 

你可能感兴趣的:(文章阅读)