表是根据主键顺序组织存放的,这种存储方式的表称为索引组织表。html
全部数据被逻辑的存放在一个空间中,称为表空间(tablespace),表空间又由段(segment)、区(extent)、页(page)等组成。linux
1.1 表空间算法
默认状况下,InnoDB存储引擎有一个共享表空间ibdata1,即全部数据存放在这个表空间内。若是用户启用了参数innodb_file_per_table,则每张表内的数据能够单独放到一个表空间内。单独的表空间内存放的是数据、索引和插入缓冲Bitmap页,其余的数据,如回滚(undo)信息、插入缓冲索引页、系统事务信息、二次写缓冲(double write buffer)等仍是存放在原来的共享表空间内。sql
1.2 段数据库
数据段为B+树的叶子节点。索引段为B+树的非索引节点。回滚段在第七章涉及。安全
1.3 区网络
区是由连续页组成的空间,每一个区的大小为1MB。为了保证区中页的连续性,InnoDB存储引擎一次从磁盘申请4-5个区。默认状况下,InnoDB存储引擎页的大小为16KB,即一个区一共有64个连续的页。InnoDB 1.2.x新增了参数innodb_pages_size,经过该参数能够将页设置为4K, 8K。数据结构
在每一个段开始时,先用32个页大小的碎片页来存放数据,在使用完这些页以后才是64个连续页的申请。这是由于,对于一些小表,或者undo这类段,能够在开始时申请较少的空间,节省磁盘容量的开销。函数
1.4 页性能
页是InnoDB磁盘管理的最小单位。InnoDB中常见的页类型:
1.5 行
InnoDB存储引擎中,数据是按行进行存放的。每一个页最多容许存放16KB / 2 -200 行记录。
2.1 Compact和Redundant 行记录格式 参考博客:https://www.cnblogs.com/wilburxu/p/9435818.html
2.2 行溢出数据
MySQL官方手册中定义的长度65535,单位为字节,指的是一行中全部VARCHAR列的长度总和,若是超出,则没法建立。
有这样一个问题,InnoDB存储引擎的页为16KB,即16384字节,怎么存放65532字节呢?
行数据只保存了前768字节的前缀数据,以后是偏移量,指向行溢出页,即BLOB page。
2.3 Compressed 和 Dynamic 行记录格式
InnoDB 1. 0.x 版本开始引入了新的文件格式(file format,用户能够理解为新的页格式),之前支持的 Compact 和 Redundant 格式称为 Antelope 文件格式,新的文件格式称为 Barracuda 文件格式。Barracuda 文件格式下拥有两种新的行记录格式:Compressed 和 Dynamic。新的两种记录格式对于存放在 BLOB 中的数据采用了彻底的行溢出的方式,如图所示,在数据页中只存放 20 个字节的指针,实际的数据都存放在 Off Page 中,而以前的 Compact 和 Redundant 两种格式会存放 768 个前缀字节。
Compressed 行记录格式的另外一个功能就是,存储在其中的行数据会以 zlib 的算法进行压缩,所以对于 BLOB、TEXT、VARCHAR 这类大长度类型的数据可以进行很是有效的存储。
该部分参考了博客: https://www.huaweicloud.com/articles/81e3e782d140e68cfea255e90be83889.html
下图是InnoDB数据页结构示意图,以及各部分的简单说明。
每当咱们插入一条记录,都会从Free Space部分申请一个记录大小的空间划分到User Records部分,当Free Space部分的空间所有被User Records部分替代掉以后,也就意味着这个页使用完了,若是还有新的记录插入的话,就须要去申请新的页了,这个过程的图示以下:
下面两张图显示的是一条记录被删除的过程。
delete_mask 这个属性标记着当前记录是否被删除。这些被删除的记录之因此不当即从磁盘上移除,是由于移除它们以后把其余的记录在磁盘上从新排列须要性能消耗,因此只是打一个删除标记而已。全部被删除掉的记录都会组成一个所谓的垃圾链表,在这个链表中的记录占用的空间称之为所谓的可重用空间,以后若是有新记录插入到表中的话,可能把这些被删除的记录占用的存储空间覆盖掉。
min_rec_mask B+树的每层非叶子节点中的最小记录都会添加该标记,min_rec_mask值都是0,意味着它们都不是B+树的非叶子节点中的最小记录。
n_owned 在页目录分组时使用,每一个组的最后一条记录(也就是组内最大的那条记录)的头信息中的n_owned属性表示该记录拥有多少条记录,也就是该组内共有几条记录。
heap_no 这个属性表示当前记录在本页中的位置,从图中能够看出来,咱们插入的4条记录在本页中的位置分别是:二、三、四、5。heap_no值为0和1的记录,称为伪记录或者虚拟记录。这两个伪记录一个表明最小记录,一个表明最大记录。
record_type 这个属性表示当前记录的类型,一共有4种类型的记录,0表示普通记录,1表示B+树非叶节点记录,2表示最小记录,3表示最大记录。
next_record 它表示从当前记录的真实数据到下一条记录的真实数据的地址偏移量。比方说第一条记录的next_record值为32,意味着从第一条记录的真实数据的地址处向后找32个字节即是下一条记录的真实数据。下一条记录指得并非按照咱们插入顺序的下一条记录,而是按照主键值由小到大的顺序的下一条记录。并且规定 Infimum记录(也就是最小记录) 的下一条记录就是本页中主键值最小的用户记录,而本页中主键值最大的用户记录的下一条记录就是 Supremum记录(也就是最大记录)
Page Directory(页目录)
下图是页目录的示意图。若是根据主键值查找页中某条记录呢?
经过二分法肯定该记录所在的槽,并找到该槽所在分组中主键值最小的那条记录。
经过记录的next_record属性遍历该槽所在的组中的各个记录。
InnoDB存储经过Named File Formats机制解决不一样版本下页结构兼容性问题。
推荐阅读博客: https://blog.csdn.net/w_linux/article/details/79655073
5.1 数据完整性
约束用来保证数据库中数据的完整性。完整性有如下三种形式:
1) 选择合适的数据类型确保一个数据值知足特定条件
2) 外键约束
3) 编写触发器
4) DEFAULT 约束
InnoDB存储引擎提供如下几种约束:
5.2 约束的建立和查找
约束的建立能够有如下两种方式:
5.3 约束和索引的区别
的确,当用户建立了惟一索引就建立了惟一约束。可是,约束是一个逻辑概念,是为了保证数据完整性,索引是一个数据结构,是为了提升查询效率。
5.4 对错误数据的约束(NOT NULL)
在某些默认设置下,MySQL数据库容许非法的或不正确的数据的插入或更新,经过设置参数sql_mode = 'STRICT_TRANS_TABLES' 对输入的值进行约束。
5.5 ENUM 和 SET 约束
MySQL数据库不支持传统的CHECK约束,可是经过ENUM和SET能够解决部分这样的约束需求。以下代码所示:
可是ENUM只能对离散数值进行约束,对于传统CHECK约束支持的连续值的范围约束或更复杂的约束,须要经过触发器来实现对于值域的约束。
5.6 触发器与约束
触发器的做用是在执行INSERT、DELETE和UPDATE命令以前或以后自动调用SQL命令或存储过程。MySQL目前支持FOR EACH ROW的触发方式。经过触发器,用户能够实现MySQL数据库自己并不支持的一些特性,如对于传统CHECK约束的支持、物化视图、高级复制和审计等特性。下面代码展现触发器对约束的支持。
首先建立了一张usercah_err_log来记录错误数值更新的日志。而后建立触发器,判断新、旧值之间的差值,大于原值的数据会被判断为非法的输入,将cash设置为原来的值,并将非法数据更新到usercash_err_log。
5.7 外键约束
MyISAM存储引擎自己并不支持外键,对于外键的定义只起到一个注释的做用。而InnoDB存储引擎则完整支持外键约束。
通常来讲,被引用的表称为父表,引用的表称为子表。可定义的子表操做有:
能够经过设置 foreign_key_checks = 0 在数据导入过程当中忽视外键检查。
6.1 视图的做用
在MySQL数据库中,视图(view)是一个命名的虚表,由一个SQL查询来定义,能够当作表使用。与持久表不一样的是,视图中的数据没有实际的物理存储。
视图的建立
CREATE [OR REPLACE] [ALGORITHM = {UNDEFINED | MERGE | TEMPTABLE}] [DEFINER = { user | CURRENT_USER }] [SQL SECURITY { DEFINER | INVOKER }] VIEW view_name [(column_list)] AS select_statement [WITH [CASCADED | LOCAL] CHECK OPTION]
视图的优势:
对视图进行DML操做更新视图时,更新的是其背后的基表,可是包含下列内容之一,视图不能进行DML操做:
6.2 物化视图
Oracle数据库支持物化视图——根据基表实际存在的实表,即物化视图的数据存储在非易失的存储设备上。物化视图能够用于预先计算保存多表的连接(JOIN)或汇集(GROUP BY)等耗时较多的SQL操做结果。这样,在执行复杂查询时,就能够避免进行这些耗时的操做,从而快速获得结果。物化视图有两种刷新方式:
MySQL自己并不支持物化视图,可是能够经过一些方法来部分实现物化视图的功能。
实现 ON DEMAND 功能:
有以下订单表,记录了用户采购电脑设备的信息
CREATE TABLE Orders ( order_id INT UNSIGNED NOT NULL AUTO_INCREMENT, product_name VARCHAR(30) NOT NULL, price DECIMAL(8, 2) NOT NULL, amount SMALLINT NOT NULL, PRIMARY KEY (order_id) )ENGINE = InnoDB INSERT INTO Orders VALUES (NULL, 'CPU', 135.5, 1), (NULL, 'Memory', 48.2, 3), (NULL, 'CPU', 125.6, 3), (NULL, 'CPU', 105.3, 4);
接着建立一张物化视图的基表,用来统计每件物品的信息
CREATE TABLE Orders_MV( product_name VARCHAR(30) NOT NULL, price_sum DECIMAL(8,2) NOT NULL, amount_sum INT NOT NULL, price_avg FLOAT NOT NULL, orders_cnt INT NOT NULL, UNIQUE INDEX (product_name); INSERT INTO Orders_MV SELECT product_name, SUM(price), SUM(amount), AVG(price), COUNT(*) FROM Orders GROUP BY product_name;
经过以上方式,用户就拥有了一个统计信息的物化视图。若是要实现ON DEMAND功能,只须要把表清空,从新导入数据便可。
若是要实现ON COMMIT 的物化视图,须要借助触发器来达到目的。须要对表Orders创建一个触发器:
DELIMITER $$ CREATE TRIGGER tgr_Orders_insert AFTER INSERT ON Orders FOR EACH ROW BEGIN SET @old_price_sum = 0; SET @old_amount_sum = 0; SET @old_price_avg = 0; SET @old_orders_cnt = 0; SELECT IFNULL(price_num, 0), IFNULL(amount_sum, 0), IFNULL(price_avg, 0), IFNULL(order_cnt, 0) FROM Orders_MV WHERE product_name = NEW.product_name INTO @old_price_sum, @old_amount_sum, @old_price_avg, @old_orders_cnt; SET @new_price_sum = @old_price_sum + NEW.price; SET @new_amount_sum = @old_amount_sum + NEW.amount; SET @new_orders_cnt = @old_orders_cnt +1; SET @new_price_avg = @new_price_sum / @new_orders_cnt; REPLACE INTO Orders_MV VALUES(NEW.product_name, @new_price_sum, @new_amount_sum, @new_price_avg, @new_orders_cnt); END $$ DELIMITER;
因为MySQL数据库自己并不支持物化视图,所以对于物化视图支持的查询重写功能就显得无能为力了,用户只能在应用程序端作一些控制
7.1 分区概述
MyISAM, InnoaDB, NDB等存储引擎都支持分区功能。分区的过程是将一个表或索引分解为多个更小,更可管理的部分。就访问数据库的应用而言,逻辑上讲,只有一个表或一个索引,可是物理上这个表或索引可能由数十个物理分区组成。每一个分区都是独立的对象,能够独自处理,也能够做为一个更大对象的一部分进行处理。MySQL数据库支持如下几种类型的分区:
不论建立何种类型的分区,若是表中存在主键或惟一索引时,分区列必须是惟一索引的一个组成部分,下面建立分区的SQL语句会产生错误。
7.2.1 RANGE 分区
下面的SQL语句根据id列来建立分区表,id小于10时,数据插入P0,大于等于10,小于20时,插入P1分区。
查看表在磁盘上的物理文件,启用分区以后,表再也不由一个ibd文件组成了,而是由各个分区ibd文件组成,以下面的p0.ibd, p1.ibd.
RANGE分区主要用于日期列的分区,例如对于销售类的表,能够根据年份来分区存放销售记录,这种建立方式便于对sales表的管理:
EXPLAIN PARTITIONS SELECT * FROM sales WHERE date>='2008-01-01' AND date<='2008-12-31'
SQL优化器只会去搜索P2008这个分区,而不是搜索全部的分区——称为Partition Pruning(分区修剪),查询速度会大幅提高。
优化器只能对YEAR(), TO_DAYS(), TO_SECONDS(), UNIX_TIMESTAMP()这类函数进行优化选择。
7.2.2 LIST分区
list分区中的值是离散的,示例代码以下:
用INSERT插入多行数据的过程当中遇到分区未定义的值时,MyISAM会将以前知足要求的行数据都插入,不知足要求的行数据即以后的不会插入,InnoDB将其视为一个事务,所以没有任何数据会被插入。
7.2.3 COLUMNS分区
前面介绍的RANGE, LIST, HASH和KEY分区中,数据必须是整型,若是不是整型,须要经过YEAR(), TO_DAYS(), MONTH()等函数化为整型。COLUMNS分区能够直接使用非整型的数据进行分区,分区数据类型直接比较而得。示例代码以下:
7.2.4 子分区
子分区是在分区的基础上在进行分区。MySQL数据库容许在RANGE和LIST的分区上再进行HASH 或 KEY的子分区。
7.2.5 分区中的NULL 值
对于RANGE分区,若是向分区列插入了NULL值,则MySQL数据库会将该值放入最左边的分区。
对于LIST分区,须要显式地指出那个分区中放入NULL值。
HASH 和 KEY 分区中,任何分区函数都会将含有NULL值地记录返回为0.
7.3 分区和性能
数据库的应用分为两类:一类是OLTP(在线事务处理),如blog,电子商务,网络游戏等;另外一类是OLAP(在线分析处理),如数据仓库,数据集市。在一个实际的应用环境中,可能既有OLTP应用,也有OLAP应用。如网络游戏中,玩家操做的游戏数据库应用就是OLTP的,可是游戏厂商须要对游戏产生的日志进行分析,经过分析获得的结果来更好地服务于游戏,预测玩家的行为等,而这是OLAP的应用。
对于OLAP应用,分区能够提升查询的性能。由于,OLAP应用大多数查询须要频繁扫描一张很大的表。假设有一张1亿行的表,其中有一个时间戳属性列。用户须要查询表获取一年的数据。若是按时间戳进行分区,只须要扫描相应的分区便可。
对于OLTP应用,一般不可能会获取一张大表中10%的数据,大部分都是经过索引返回几条记录。对于一张大表,通常的B+树须要2~3次的磁盘IO。
若是对主键进行查询分区是有意义的。
若是用其余索引进行查询,查询开销则大的多,对于KEY的查询须要扫描全部的10个分区,每一个分区的查询开销为2次IO,一共须要20次IO。