一、了解SQL

1、SQL 有两个重要的标准,分别是 SQL92 和 SQL99,它们分别代表了 92 年和 99 年颁布的 SQL 标准
2、SQL 语言按照功能可以划分成以下的 4 个部分:

(1)DDL,英文叫做 Data Definition Language,即数据定义语言:定义数据库对象,包括数据库、数据表和列。用它创建,删除和修改数据库和表结构。
(2)DML,英文叫做 Data Manipulation Language,即数据操作语言:用它操作和数据库相关的记录,比如增加、删除、修改数据表中的记录。
(3)DCL,英文叫做 Data Control Language,即数据控制语言:用它定义访问权限和安全级别。
(4)DQL,英文叫做 Data Query Language,即数据查询语言:用它查询想要的记录,是 SQL 语言的重中之重。

3、SQL大小写问题:

(1)表名(table1)、表别名(table1 a)、字段名(列名)、字段别名、数据库名等都小写
(2)SQL 保留字、函数名、绑定变量等都大写

二、DBMS的前世今生

1、发展起源

(1)1974 年,两个 IBM 研究员发表了一篇有关结构化英语查询语言的论文,并将这门语言命名为 SEQUEL。
SEQUEL 的语言标准是开放的,但是围绕它的商业化竞争却从来没有停止过。因为商标之争,SEQUEL 改名为 SQL。
后来有一个重量级的公司基于那篇论文研发出了商业化的数据库管理软件,即 Oracle。此后,又诞生出大家熟知的 DBMS,如 MySQL、SQL Server、PostgreSQL、DB2 和 MongoDB 等。
(2)1979 年,Oracle 2 诞生,它是第一个商用的 RDBMS(关系型数据库管理系统),随后被卖给了军方客户。随着 Oracle 软件的名气越来越大,公司也改叫 Oracle 公司。20 世纪 90 年代,Oracle 的创始人埃里森成为继比尔·盖茨之后第二富有的人,可以说 IBM 缔造了两个帝国,一个是软件业的霸主微软,另一个是企业软件市场的霸主 Oracle。
(3)MySQL 是 1995 年诞生的开源数据库管理系统,因为免费开源的特性,得到了开发者的喜爱,用户量迅速增长,成为开源数据库的 No.1。但在发展过程中,MySQL 先后两次被易手,先是在 2008 年被 SUN 收购,然后在 2010 年 SUN 被 Oracle 收购,于是 Oracle 同时拥有了 MySQL 的管理权,至此 Oracle 在数据库领域中成为绝对的领导者。
(4)不过在 Oracle 收购 MySQL 的同时,MySQL 的创造者担心 MySQL 有闭源的风险,因此创建了 MySQL 的分支项目 MariaDB,MariaDB 在绝大部分情况下都是与 MySQL 兼容的,并且增加了许多新的特性,比如支持更多的存储引擎类型。许多企业也由原来的 MySQL 纷纷转向了 MariaDB。
(5)SQL Server 是微软开发的商业数据库,诞生于 1989 年。实际上微软还推出了 Access 数据库,它是一种桌面数据库,同时具备后台存储和前台界面开发的功能,更加轻量级,适合小型的应用场景。因为后台的存储空间有限,一般只有 2G,Access 的优势在于可以在前台便捷地进行界面开发。而 SQL Server 是大型数据库,用于后台的存储和查询,不具备界面开发的功能。
(6)Oracle 更适合大型跨国企业的使用,因为他们对费用不敏感,但是对性能要求以及安全性有更高的要求,而 MySQL 更受到许多互联网公司,尤其是早期创业公司的青睐。

2、DB、DBMS、DBS的区别

(1)DB : DataBase,数据库。是存储数据的集合,可以理解为多个数据表
(2)DBMS:DataBase Management System,数据库管理系统。可以对多个数据库进行管理,所以可以理解为 DBMS = 多个数据库(DB) + 管理程序。
(3)DBS:DataBase System,数据库系统。是更大的概念,包括了数据库、数据库管理系统以及数据库管理人员 DBA。
注:虽然把 Oracle、MySQL 等称之为数据库,但确切讲,它们应该是数据库管理系统,即 DBMS。

3、主流DBMS(SQL与NoSQL阵营)
(1)关系型数据库绝对是 DBMS 的主流,其中使用最多的 DBMS 分别是 Oracle、MySQL 和 SQL Server。
(2)关系型数据库(RDBMS)就是建立在关系模型基础上的数据库,SQL 就是关系型数据库的查询语言。

扩展
1)RDBMS 采用的是 ER 图(Entity Relationship Diagram),即实体 - 关系图的方式进行设计。
2)实体 - 关系图这个模型中有 3 个要素:实体、属性、关系。
3)实体是要管理的对象,属性是标识每个实体的属性,关系是对象之间的关系。

(3)NoSQL 泛指非关系型数据库,包括了键值型数据库、文档型数据库、搜索引擎和列存储等,除此外还包括图形数据库。

扩展
1)键值型数据库通过 Key-Value 键值方式来存储数据, Key 和 Value 可以是简单的对象,也可以是复杂的对象。Redis 是最流行的键值型数据库
优点:查找速度快,优于关系型数据库.
缺点:无法使用条件过滤(比如 WHERE),如果不知道去哪里找数据,就要遍历所有的键,这会消耗大量的计算。
2)文档型数据库用来管理文档,在数据库中文档作为处理信息的基本单位,一个文档就相当于一条记录,MongoDB 是最流行的文档型数据库
3)搜索引擎是数据库检索中的重要应用,常见的全文搜索引擎有 Elasticsearch、Splunk 和 Solr。
4)列式数据库是相对于行式存储的数据库,Oracle、MySQL、SQL Server 等数据库都是采用的行式存储(Row-based),而列式数据库是将数据按照列存储到数据库中。
优点:可以大量降低系统的 I/O,适合于分布式文件系统(列式存储把一列的数据串起来进行存储,再存储下一列。相邻数据的数据类型都是一样,更容易压缩,压缩之后就自然降低了 I/O)
缺点:功能相对有限。如果对实时性要求高,则更适合行式存储
5)图形数据库,利用了图这种数据结构存储了实体(对象)之间的关系。最典型的例子就是社交网络中人与人的关系

三、SQL的执行过程

1、Oracle中SQL的执行过程

(1)语法检查:检查 SQL 拼写是否正确,如果不正确,Oracle 会报语法错误。
(2)语义检查:检查 SQL 中的访问对象是否存在。如写 SELECT 语句时,列名写错了,系统就会提示错误。语法检查和语义检查的作用是保证 SQL 语句没有错误。
(3)权限检查:看用户是否具备访问该数据的权限。
(4)共享池检查:共享池(Shared Pool)是一块内存池,主要作用是缓存 SQL 语句和该语句的执行计划。Oracle 通过检查共享池是否存在 SQL 语句的执行计划,来判断进行软解析,还是硬解析。
(5)优化器:进行硬解析,决定怎么做,如创建解析树,生成执行计划。
(6)执行器:有了解析树和执行计划,就知道了 SQL 该怎么被执行,就可以在执行器中执行语句。

扩展
1)软解析:共享池中,Oracle 先对 SQL 语句进行 Hash 运算,然后根据 Hash 值在库缓存(Library Cache)中查找是否存在 SQL 语句的执行计划,如果存在就直接拿来执行,直接进入“执行器”的环节,这 就是软解析。
2)硬解析:如果没有找到 SQL 语句和执行计划,Oracle 就需要创建解析树进行解析,生成执行计划,进入“优化器”这个步骤,这就是硬解析。
3)共享池是 Oracle 中的术语,包括了库缓存,数据字典缓冲区等。库缓存区主要缓存 SQL 语句和执行计划。数据字典缓冲区存储的是 Oracle 中的对象定义,如表、视图、索引等对象。对 SQL 语句进行解析时,如果需要相关数据,会从数据字典缓冲区中提取。
4)为了提升 SQL 的执行效率,应该尽量避免硬解析,因为 SQL 执行过程中,创建解析树,生成执行计划是很消耗资源的。
5)如何避免硬解析:绑定变量,通过不同的变量取值来改变 SQL 的执行结果。好处是提升软解析的可能性;缺点在于可能导致生成的执行计划不够优化,使用动态 SQL 的方式,因为参数不同,会导致 SQL 的执行效率不同,同时 SQL 优化也会比较困难。

2、MySQL中SQL的执行过程
MySQL 是典型的 C/S 架构,即 Client/Server 架构,服务器端程序使用的 mysqld。整体的 MySQL 流程如下图:

(1)MySQL由3层组件:
1)连接层:客户端和服务器端建立连接,客户端发送 SQL 至服务器端
2)SQL 层:对 SQL 语句进行查询处理
3)存储引擎层:与数据库文件打交道,负责数据的存储和读取
(2)SQL层结构如下:

1)查询缓存:Server 如果在查询缓存中发现了这条 SQL 语句,就直接将结果返给客户端;否则进入解析器阶段。由于查询缓存效率不高,所以在 MySQL8.0 之后就抛弃了这个功能。
2)解析器:解析器对 SQL 语句进行语法分析、语义分析。
3)优化器:优化器会确定 SQL 语句的执行路径,比如是根据全表检索,还是根据索引来检索等。
4)执行器:执行前需判断该用户是否具备权限,如果具备就执行 SQL 查询并返回结果。在 MySQL8.0 以下的版本,如果设置了查询缓存,这时会将查询结果进行缓存。

3、开源的 MySQL 允许开发人员设置自己的存储引擎,下面是一些常见的存储引擎:

(1)InnoDB 存储引擎:是 MySQL 5.5 版本之后默认的存储引擎,最大特点是支持事务、行级锁定、外键约束等。
(2)MyISAM 存储引擎:是 MySQL 5.5 版本之前是默认的存储引擎,不支持事务,也不支持外键,最大特点是速度快,占用资源少。
(3)Memory 存储引擎:使用系统内存作为存储介质,以便得到更快的响应速度。但如果 mysqld 进程崩溃,会导致所有的数据丢失,因此只有当数据是临时的情况下才使用 Memory 存储引擎。
(4)NDB 存储引擎:也叫 NDB Cluster 存储引擎,主要用于 MySQL Cluster 分布式集群环境,类似于 Oracle 的 RAC 集群。
(5)Archive 存储引擎:有很好的压缩机制,用于文件归档,在请求写入时会进行压缩,所以常用来做仓库。
扩展:数据库的设计在于表的设计,在 MySQL 中每个表的设计都可以采用不同的存储引擎,可以根据实际的数据处理需要来选择存储引擎,这也是 MySQL 的强大之处。

4、在 MySQL 中对一条 SQL 语句的执行时间进行分析

第1步:查看profiling 是否开启(开启后可以让 MySQL 收集在 SQL 执行时所使用的资源情况)
命令:mysql> select @@profiling;

第2步:开启profiling(设置为1即为开启)
命令:mysql> set profiling=1;
第3步:执行一个SQL查询
命令:mysql> select * from wucai.heros;
第4步:查看当前会话产生的所有profiles(会发现刚才执行了两次查询,Query ID 分别为 1 和 2)
命令:mysql>show profiles;

第5步:若想要获取上一次查询的执行时间
命令:mysql> show profile;

第6步:若想要查询指定的 Query ID(结果与第5步中的一样)
命令:mysql> show profile for query 2;

5、Oracle 和 MySQL执行过程异同点

(1)相同点:都是通过解析器→优化器→执行器这样的流程来执行 SQL 的。
(2)不同点:在进行 SQL 的查询上面有软件实现层面的差异。
1)Oracle 提出了共享池的概念,通过共享池来判断是进行软解析,还是硬解析。
2)MySQL 8.0 后的版本不支持查询缓存,而是直接执行解析器→优化器→执行器的流程;MySQL 提供各种存储引擎供选择,不同存储引擎有各自使用场景,可针对每张表选择适合的存储引擎。

四、DDL

1、DDL(数据定义语言) 是 DBMS 的核心组件,也是 SQL 的重要组成部分,DDL 的正确性和稳定性是整个 SQL 运行的重要基础。定义了数据库的结构和数据表的结构。
2、DDL 中,常用的功能是增删改,分别对应命令 CREATE、DROP 和 ALTER。在执行 DDL 的时候,不需要 COMMIT,就可以完成执行任务
3、数据表常见约束

(1)主键约束:主键用于唯一标识一条记录,不能重复,不能为空,即 UNIQUE+NOT NULL。一个数据表的主键只能有一个。主键可以是一个字段,也可以由多个字段复合组成。
(2)外键约束:外键确保了表与表之间引用的完整性。一个表中的外键对应另一张表的主键。外键可以重复,也可以为空。
(3)字段唯一性约束:唯一性约束表明字段在表中的数值唯一,即使有了主键,还可以对其他字段进行唯一性约束。
         扩展:唯一性约束和普通索引(NORMAL INDEX)有区别。唯一性约束相当于创建了一个约束和普通索引,目的是保证字段的正确性,而普通索引只提升数据检索的速度,并不对字段的唯一性进行约束。
(4)字段NOT NULL 约束:字段不能为空,必须有取值。
(5)字段DEFAULT:表明字段的默认值。如果在插入数据时,这个字段没有取值,就设置为默认值。
(6)字段CHECK 约束:检查特定字段取值范围的有效性,CHECK 约束的结果不能为 FALSE。

4、设计数据表的原则:“三少一多”原则

(1)数据表的个数越少越好(数据表越少,实体和联系设计得越简洁,方便理解又方便操作)
(2)数据表中的字段个数越少越好(字段个数越多,数据冗余的可能性越大)
(3)数据表中联合主键的字段个数越少越好(字段越多,占用的索引空间越大,加大理解难度,增加运行时间和索引空间)
(4)使用主键和外键越多越好(各种字段之间的关系越多,证明这些实体之间的冗余度越低,利用度越高)
.
总结:“三少一多”原则的核心就是简单可复用。
1)简单指用更少的表、更少的字段、更少的联合主键字段来完成数据表的设计。
2) 可复用指通过主键、外键的使用来增强数据表之间的复用率。因为一个主键可以理解是一张表的代表。键设计得越多,证明它们之间的利用率越高。

五、SQL语法相关知识

1、select查询时的两个顺序:
(1)关键字的顺序:SELECT … FROM … WHERE … GROUP BY … HAVING … ORDER BY …
(2)执行顺序:FROM > WHERE > GROUP BY > HAVING > SELECT的字段 > DISTINCT > ORDER BY > LIMIT

2、数据过滤

(1)比较运算符

(2)逻辑运算符

注:WHERE 子句中同时存在 OR 和 AND 时,AND 优先级更高,会先处理 AND 操,再处理 OR
(3)通配符(需要结合LIKE操作符一起用)
%:代表零个或多个字符
_:代表一个字符
注:实际操作过程中,尽量少用通配符,因为它需要消耗数据库更长的时间来进行匹配。
       即使对 LIKE 检索的字段进行了索引,索引的价值也可能会失效。如果要让索引生效,那么 LIKE 后面就不能以“%”开头,如使用LIKE '%太%'或LIKE '%太’时,索引失效,会对全表进行扫描

六、SQL函数

1、有4类内置的SQL函数:算术函数、字符串函数、日期函数、转换函数

(1)算术函数

SELECT ABS(-2),运行结果为 2
SELECT MOD(101,3),运行结果 2
SELECT ROUND(37.25,1),运行结果 37.3
.
(2)字符串函数

SELECT CONCAT(‘abc’, 123),运行结果为 abc123
SELECT LENGTH(‘你好’),运行结果为 6
SELECT CHAR_LENGTH(‘你好’),运行结果为 2
SELECT LOWER(‘ABC’),运行结果为 abc
SELECT UPPER(‘abc’),运行结果 ABC
SELECT REPLACE(‘fabcd’, ‘abc’, 123),运行结果为 f123d
SELECT SUBSTRING(‘fabcd’, 1,3),运行结果为 fab
.
(3)日期函数

SELECT CURRENT_DATE(),运行结果为 2019-04-03
SELECT CURRENT_TIME(),运行结果为 21:26:34
SELECT CURRENT_TIMESTAMP(),运行结果为 2019-04-03 21:26:34
SELECT EXTRACT(YEAR FROM ‘2019-04-03’),运行结果为 2019
SELECT DATE(‘2019-04-01 12:00:05’),运行结果为 2019-04-01
注:DATE 日期格式必须是 yyyy-mm-dd
.
(4)转换函数

SELECT CAST(123.123 AS DECIMAL(8,2)),运行结果为 123.12
SELECT COALESCE(null,1,2),运行结果为 1
注:SELECT CAST(123.123 AS INT),运行结果会报错。因为CAST 函数在转换数据类型时,不会四舍五入,如果原数值有小数,那么转换为整数类型时就会报错。
MySQL 和 SQL Server 中,可以用DECIMAL(a,b)来指定, a 代表整数部分和小数部分加起来最大的位数,b 代表小数位数
.
(5)聚集函数

注1:COUNT(xxx)会忽略值为 NULL 的数据行,而 COUNT() 只是统计数据行数,不管某个字段是否为 NULL
AVG、MAX、MIN 等也会自动忽略值为 NULL 的行,MAX 和 MIN 函数也可用于字符串类型数据的统计,如果是英文字母,则按照 A—Z 顺序排列,越往后,数值越大。如果是汉字则按照全拼拼音进行排列。
注2:MySQL 中统计数据表的行数,有三种方式:SELECT COUNT(
)、SELECT COUNT(1)、SELECT COUNT(具体字段)。COUNT()和COUNT(1)都是对所有结果进行COUNT。
三者效率:SELECT COUNT(
)>(或=) SELECT COUNT(1)> SELECT COUNT(具体字段)

2、SQL函数的优缺点
优点:使用起来方便
缺点:代码可移植性差。只有很少的函数是被 DBMS 同时支持。比如大多数 DBMS 使用(||)或者(+)来做拼接符,而 MySQL 中的字符串拼接函数为Concat()。大部分 DBMS 会有自己特定的函数

3、大小写规范问题
MySQL 在 Linux 环境下,数据库名、表名、变量名严格区分大小写,字段名忽略大小写。
MySQL 在 Windows 的环境下全部不区分大小写。

建议:
SQL 保留字(关键字)、函数名、绑定变量等都大写;
表名、表别名、字段名、字段别名、数据库名等都小写;
SQL 语句必须以分号结尾。

七、子查询

1、关联子查询、非关联子查询
(1)关联子查询:如果子查询需要执行多次,即采用循环的方式,先从外部查询开始,每次都传入子查询进行查询,然后再将结果反馈给外部,这种嵌套的执行方式就称为关联子查询
(2)非关联子查询:子查询从数据表中查询了数据结果,如果这个数据结果只执行一次,然后这个数据结果作为主查询的条件进行执行,那么这样的子查询叫做非关联子查询

2、(NOT)EXISTS子查询
EXISTS 子查询用来判断条件是否满足,满足的话为 True,不满足为 False

3、集合比较子查询

IN和EXIST区别:
SELECT * FROM A WHERE cc IN (SELECT cc FROM B)
SELECT * FROM A WHERE EXIST (SELECT cc FROM B WHERE B.cc=A.cc)
对 cc 列建立索引情况下:如果表 A 比表 B 大,则 IN 子查询效率高。如果表 A 比表 B 小,则 EXISTS 子查询效率高

八、SQL标准

1、SQL 有两个主要的标准:分别是 SQL92 和 SQL99,也分别叫做 SQL-2 和 SQL-3 标准。

2、SQL92中的5种连接方式
(1)笛卡尔积:也称为交叉连接,英文是 CROSS JOIN。假设我有两个集合 X 和 Y,那么 X 和 Y 的笛卡尔积就是 X 和 Y 的所有可能组合。
(2)等值连接:用两张表中都存在的列进行连接。也可以对多张表进行等值连接。
(3)非等值连接:进行多表查询时,条件是等号就是等值连接,其他运算符就是非等值查询。
(4)外连接:一张是主表,另一张是从表。
                      多张表外连接时,第一张表是主表,显示全部行,剩下的表则显示对应连接的信息。
                      采用(+)代表从表所在位置,SQL92 中只有左外连接和右外连接,没有全外连接。
                      注:MySQL 不支持 SQL92 的外连接
(5)自连接:查询条件使用了当前表的字段。

3、SQL99中的 种连接方式
(1)交叉连接:实际上就是SQL92 中的笛卡尔乘积,采用CROSS JOIN。
(2)自然连接:可理解为 SQL92 中的等值连接。会自动查询两张表中所有相同的字段,然后进行等值连接。采用NATURAL JOIN。
(3)ON连接:用来指定连接条件。采用JOIN…ON…。
(4)USING连接:用来指定同名字段进行等值连接。采用JOIN…USING(字段名)。
(5)外连接:有3种外连接形式。一般省略OUTER不写
        1)左外连接:LEFT JOIN 或 LEFT OUTER JOIN
        2)右外连接:RIGHT JOIN 或 RIGHT OUTER JOIN
        3)全外连接:FULL JOIN 或 FULL OUTER JOIN
(6)自连接:查询条件使用了当前表的字段。

4、SQL92和SQL99的区别
(1)内连接:包括等值连接、非等值连接和自连接。
(2)外连接:包括左外连接、右外连接和全外连接。
(3)交叉连接:也称为笛卡尔积。
(4) SQL92 进行查询时,把所有需连接的表都放到 FROM 后,然后在 WHERE 中写连接条件。而 SQL99不需要一次性把所有需要连接的表都放到 FROM 后,而是采用 JOIN 方式,每连接一张表,可以多次使用 JOIN 进行连接。
(5)建议多表连接使用 SQL99 标准,因为层次性更强,可读性更强。

5、不同 DBMS 中使用连接需要注意的地方
(1) MySQL 、Access、SQLite、MariaDB 等数据库软件不支持SQL99 标准提供的全外连接。
          Oracle、DB2、SQL Server 支持。
(2)Oracle 没有表别名 AS。使用表别名时,直接在表名后写上别名即可,如 player p,而非 player AS p。
(3)SQLite 的外连接只支持左连接。

6、需要注意连接的性能问题
(1)控制连接表的数量:多表连接相当于嵌套 for 循环,非常耗资源,会让 SQL 查询性能下降很严重,因此不要连接不必要的表。许多 DBMS 中,也会有最大连接表的限制。
(2)连接时别忘记 WHERE 语句:过滤掉不必要的数据行返回。
(3)使用自连接而不是子查询:建议使用自连接,许多 DBMS 的处理过程对于自连接的处理速度比子查询快得多。可以这样理解:子查询是通过未知表进行查询后的条件判断,而自连接是通过已知的自身数据表进行条件判断,因此大部分 DBMS 中都对自连接处理进行了优化。

九、视图

1、视图:也即虚拟表,本身不具有数据。是一张表或多张表的数据结果集。

2、视图的语法
(1)创建视图:CREATE VIEW 视图名 AS 查询语句
(2)嵌套视图:创建一张视图后,还可在它基础上继续创建视图
(3)修改视图:ALTER VIEW 视图名 AS 查询语句
(4)删除视图:DROP VIEW 视图名
注:SQLite 不支持修改视图,仅支持只读视图,即只能 CREATE VIEW 和 DROP VIEW,如果想修改视图,就要先 DROP 然后再 CREATE
注:视图是虚拟表,它只是封装了底层的数据表查询接口,因此有些 RDBMS 不支持对视图创建索引(有些 RDBMS 则支持,如新版本的 SQL Server)

3、视图的优点
(1)无法通过视图对底层数据进行修改,一定程度上保证了数据表的数据安全性。
(2)可以针对不同用户开放不同的数据查询权限。
(3)视图是对 SQL 查询的封装,将复杂的 SQL 查询简化,编写好查询后,可以直接重用它而不必知道基本的查询细节。

4、视图与临时表的区别:临时表是真实存在的数据表,但不用于长期存放数据,只为当前连接存在,连接关闭后,临时表就会自动释放。

十、存储过程(英文名:Stored Procedure)

1、定义存储过程

扩展:删除存储过程用DROP PROCEDURE;更新存储过程用ALTER PROCEDURE。

2、存储过程的结束符

使用DELIMITER来声明定义一个结束符。
SQL 默认采用(;)作为结束符,当存储过程中每一句 SQL 结束后,采用(;)作为结束符,就相当于告诉 SQL 可以执行这一句了。但存储过程是一个整体,不希望 SQL 逐条执行,而是采用存储过程整段执行,因此需要临时定义新的 DELIMITER,新的结束符可以用(//)或者($$)。

3、存储过程的3种参数类型

第1步:存储过程写为:
CREATE PROCEDURE get_hero_scores(
OUT max_max_hp FLOAT,
OUT min_max_mp FLOAT,
OUT avg_max_attack FLOAT,
s VARCHAR(255)
)
BEGIN
SELECT MAX(hp_max), MIN(mp_max), AVG(attack_max) FROM heros WHERE role_main = s INTO max_max_hp, min_max_mp, avg_max_attack;
END
.
注:max_max_hp、min_max_mp 和 avg_max_attack为3 个OUT类型;s 为 IN 类型。
查询后提取出的最大的最大生命值、最小的最大魔法值、平均最大攻击值使用INTO关键字赋值给那3个OUT类型的参数
.
第2步:调用存储过程
CALL get_hero_scores(@max_max_hp, @min_max_mp, @avg_max_attack, ‘战士’);
SELECT @max_max_hp, @min_max_mp, @avg_max_attack;

4、流控制语句
注:存储过程是由SQL语句、流控制语句组成的语句集合。
(1)BEGIN…END:中间包含多个语句,每个语句以(;)号为结束符。
(2)DECLARE:声明变量,使用位置在 BEGIN…END 语句中间,且需在其他语句使用之前进行变量。
(3)SET:对变量进行赋值。
(4)SELECT…INTO:把从数据表中查询的结果存放到变量中,也是为变量赋值。
(5)IF…THEN…ENDIF:条件判断语句,还可以在 IF…THEN…ENDIF 中使用 ELSE 和 ELSEIF 。
(6)CASE:多条件分支判断。如下:

(7)LOOP、LEAVE 和 ITERATE:LOOP 是循环语句,LEAVE 可以跳出循环,ITERATE 进入下一次循环。可以把 LEAVE 理解为 BREAK,把 ITERATE 理解为 CONTINUE。
(8)REPEAT…UNTIL…END REPEAT:循环语句,先执行一次循环,然后在 UNTIL 中进行表达式判断,如果满足条件就退出,即 END REPEAT;如果不满足条件,就继续执行循环,直到满足退出条件为止。
(9)WHILE…DO…END WHILE:循环语句,先进行条件判断,如果满足条件就进行循环,如果不满足条件就退出循环。

5、使用存储过程优缺点

(1)优点
1)提升SQL执行效率:存储过程可以一次编译多次使用。只在创造时进行编译,之后的使用都不需要重新编译
2)减少开发工作量:将代码封装成模块,可以把复杂的问题拆解成不同模块,然后模块之间可重复使用,减少开发工作量的同时还能保证代码的结构清晰
3)安全性强:设定存储过程时可以设置对用户的使用权限,和视图一样具有较强安全性
4)减少网络传输量:代码封装到存储过程中,每次使用只需调用存储过程即可,减少了网络传输量。同时在进行相对复杂的数据库操作时,原本需要使用一条一条的 SQL 语句,可能要连接多次数据库才能完成的操作,现在变成一次存储过程,只需连接一次即可
(2)缺点
1)可移植性差:不能跨数据库移植,如在 MySQL、Oracle 和 SQL Server 里编写的存储过程,换成其他数据库时都需要重新编写
2)调试困难:只有少数 DBMS 支持存储过程的调试。对于复杂的存储过程,开发和维护都不容易
3)版本管理困难:如数据表索引发生了变化,可能会导致存储过程失效。存储过程本身没有版本控制,版本迭代更新的时候很麻烦
4)不适合高并发场景:高并发场景需减少数据库压力,有时数据库会采用分库分表的方式,且对可扩展性要求很高,这种情况下,存储过程变得难以维护,增加数据库的压力就不适用了

十一、事务管理

1、InnoDB存储引擎和MyISAM存储引擎的区别: InnoDB 支持事务

2、MySQL 5.5 版本之前,默认的存储引擎是 MyISAM,5.5 版本之后默认存储引擎是 InnoDB

3、事务的特性:ACID
(1)A:原子性(Atomicity)。原子的概念是不可分割,可理解为组成物质的基本单位,也是进行数据处理操作的基本单位
(2)C:一致性(Consistency)。指数据库进行事务操作后,会由原来的一致状态,变成另一种一致状态。即事务提交后,或事务回滚后,数据库的完整性约束不能被破坏
(3)I:隔离性(Isolation)。指每个事务都彼此独立,不受到其他事务的执行影响。即一个事务在提交之前,对其他事务都不可见
(4)D:持久性(Durability)。事务提交后对数据的修改是持久性的,即使系统出故障,如系统崩溃或存储介质发生故障,数据的修改依然有效。因为当事务完成,数据库的日志会被更新,这时可以通过日志,让系统恢复到最后一次成功的更新状态
扩展:持久性是通过事务日志来保证的。日志包括了回滚日志和重做日志。当通过事务对数据进行修改时,首先会将数据库的变化信息记录到重做日志中,然后再对数据库中对应的行进行修改。这样做的好处是,即使数据库系统崩溃,数据库重启后也能找到没有更新到数据库系统中的重做日志,重新执行,从而使事务具有持久性

4、查看存储引擎是否支持事务:mysql>show engines;

5、事务常用控制语句
(1)START TRANSACTION 或 BEGIN:显式开启一个事务
(2)COMMIT:提交事务。提交后,对数据库的修改是永久性的
(3)ROLLBACK 或 ROLLBACK TO [SAVEPOINT]:回滚事务。撤销正在进行的所有没有提交的修改,或将事务回滚到某个保存点
(4)SAVEPOINT:在事务中创建保存点,方便后续针对保存点进行回滚。一个事务中可以存在多个保存点
(5)RELEASE SAVEPOINT:删除某个保存点
(6)SET TRANSACTION:设置事务的隔离级别

6、使用事务的两种方式:隐式事务和显式事务
注:隐式事务即自动提交,Oracle 默认不自动提交,需手写 COMMIT 命令,而 MySQL 默认自动提交,但可以配置 MySQL 的参数进行修改:

扩展
设置 autocommit=0 时,不论是否用 START TRANSACTION 或 BEGIN 的方来开启事务,都需用 COMMIT 提交让事务生效,使用 ROLLBACK 对事务进行回滚
设置 autocommit=1 时,每条 SQL 语句都会自动进行提交。但此时若用 START TRANSACTION 或者 BEGIN来显式开启事务,则这个事务只在 COMMIT 时才生效,在 ROLLBACK 时才回滚

7、MySQL中的completion_type 参数
(1)completion=0,是默认情况。当执行 COMMIT 的时候会提交事务,在执行下一个事务时,还需要使用 START TRANSACTION 或 BEGIN 来开启
(2)completion=1,提交事务后,相当于执行了 COMMIT AND CHAIN,即开启一个链式事务,提交事务之后会开启一个相同隔离级别的事务
(3)completion=2,COMMIT=COMMIT AND RELEASE,当提交后,会自动与服务器断开连接

8、事务并发处理时存在哪些异常:(SQL-92 标准定义)分别为脏读(Dirty Read)、不可重复读(Nonrepeatable Read)和幻读(Phantom Read)
(1)脏读:读到了其他事务还未提交的数据
(2)不可重复读:对某数据两次读取的结果不同,原因是其他事务对这个数据同时进行了修改或删除
(3)幻读:事务 A 根据条件查询得到 N 条数据,但此时事务 B 更改或增加了 M 条符合事务 A 查询条件的数据,当事务 A 再次进行查询时发现有 N+M 条数据,产生幻读

9、事务隔离级别:(SQL-92 标准定义)级别从低到高分别是:读未提交(READ UNCOMMITTED )、读已提交(READ COMMITTED)、可重复读(REPEATABLE READ)和可串行化(SERIALIZABLE)
隔离级别能解决的异常情况如下表所示:

10、查看当前会话的隔离级别:mysql>SHOW VARIABLES LIKE 'transaction_isolation';

设置当前会话的隔离级别:mysql>SET SESSION TRANSACTION ISOLATION LEVEL READ UNCOMMITTED;

11、隔离级别越低,系统吞吐量(并发程度)越大,但出现异常问题的可能性也更大。实际使用过程中需要在性能和正确性上进行权衡和取舍,没有完美的解决方案,只有适合与否

十二、游标

1、游标提供了一种灵活的操作方式,可以从数据结果集中每次提取一条数据记录进行操作。游标是面向过程的编程方式

2、SQL 中,游标是一种临时的数据库对象,可以指向存储在数据库表中的数据行指针

3、使用游标,一般5个步骤:
(1)定义游标
MySQL,SQL Server,DB2 、 MariaDB中定义语法:DECLARE cursor_name CURSOR FOR select_statement

Oracle 、 PostgreSQL中定义语法:DECLARE cursor_name CURSOR IS select_statement
(2)打开游标:OPEN cursor_name
(3)从游标中取得数据:FETCH cursor_name INTO var_name ...
说明:使用 cursor_name 这个游标读取当前行,并将数据保存到 var_name 变量中,游标指针指到下一行。如果游标读取的数据行有多个列名,则在 INTO 关键字后面赋值给多个变量名即可
(4)关闭游标:CLOSE cursor_name
(5)释放游标:DEALLOCATE cursor_name
说明:若不释放游标,游标会一直存在于内存中,直到进程结束后才自动释放。当不需要使用游标时,释放游标可减少资源浪费

十三、数据库调优

1、如何确定调优目标
目标:让数据库运行更快,即响应时间更快,同时吞吐量更大
(1)用户的反馈
(2)日志分析(数据库日志、操作系统日志)
(3)服务器资源使用监控(CPU、内存、I/O)
(4)数据库内部状况监控(活动会话、事务、锁等待)

2、数据库调优可选择的维度
(1)选择适合的 DBMS(直接决定后面的操作方式)
(2)优化表设计(直接影响后续 SQL 查询语句):采用第三范式、适当进行反范式优化、表字段数据类型选择
(3)优化逻辑查询(通过 SQL 等价变换提升查询效率)
(4)优化物理查询(通过索引和表连接方式等技术进行优化)
(5)使用 Redis 或 Memcached 作为缓存(取长补短,利用外援。 Redis 和 Memcached 都可将数据存放到内存中,大幅提升查询的效率)
(6)库级优化(垂直分库、垂直分表、水平切分表等)

十四、(反)范式优化

1、一共6种范式:级别从低到高为:1NF(第一范式)、2NF(第二范式)、3NF(第三范式)、BCNF(巴斯 - 科德范式)、4NF(第四范式)和 5NF(第五范式,又叫做完美范式)

2、范式越高阶,冗余度越低,且高阶的范式一定符合低阶范式的要求

3、一般来说数据表的设计应尽量满足 3NF

4、数据表中的键
(1)超键:能唯一标识元组的属性集
(2)候选键:若超键不包括多余属性,则这个超键就是候选键
(3)主键:从候选键中选择一个作为主键
(4)主属性:包含在任一候选键中的属性
(5)非主属性:与主属性相对,指不包含在任一候选键中的属性

5、1NF~3NF
(1)1NF:数据库表中的任何属性都是原子性的,不可再分
(2)2NF:数据表里的非主属性都要和这个数据表的候选键有完全依赖关系
扩展:非主属性未完全依赖候选键会产生的问题:
【例】一张球员比赛表 player_game,里面包含球员编号、姓名、年龄、比赛编号、比赛时间和比赛场地等属性,这里候选键和主键都为(球员编号,比赛编号)
这个数据表不满足2NF,因为存在对应关系:(球员编号) → (姓名,年龄)、(比赛编号) → (比赛时间, 比赛场地)。即候选键中的某个字段决定了非主属性,会带来如下问题:
1)数据冗余:若一个球员参加 m 场比赛,则球员的姓名和年龄就重复 m-1 次。一个比赛也可能有 n 个球员参加,比赛的时间和地点就重复了 n-1 次
2)插入异常:如果想添加一场新比赛,但还没确定参加的球员都有谁,那么就没法插入
3)删除异常:如果要删除某个球员编号,若没有单独保存比赛表的话,就会同时把比赛信息删掉
4)更新异常:如果调整了某个比赛时间,则数据表中所有这个比赛的时间都需进行调整,否则就会出现一场比赛时间不同的情况
(3)3NF:3NF 在满足 2NF 的同时,对任何非主属性都不传递依赖于候选键
【例】球员 player 表包含属性球员编号、姓名、球队名称和球队主教练。非主属性“球队教练”依赖于“球队名称”,非主属性“球队名称”依赖于“球员编号”,因此非主属性“球队教练”传递依赖于候选键“球员编号”

6、范式只是提出设计的标准,实际设计数据表时,未必要符合这些原则。一方面因为这些范式本身存在一些问题,可能会带来插入,更新,删除等异常情况,另一方面,它们也可能降低会查询效率,因为范式等级越高,设计出来的数据表就越多,进行数据查询时就可能需要关联多张表,从而影响查询效率

7、BCNF(巴斯范式):在 3NF 的基础上消除了主属性对候选键的部分依赖或者传递依赖关系

8、反范式设计:允许少量冗余,通过空间换时间

9、反范式存在的问题:数据量小的情况下,反范式不能体现性能的优势,可能还会让数据库的设计更复杂

10、反范式适用场景
(1)冗余信息有价值或者能大幅提高查询效率时,可以采取反范式优化
(2)用在数据仓库的设计中,因为数据仓库通常存储历史数据,对增删改实时性要求不强,对历史数据的分析需求强。这时适当允许数据冗余度,更方便进行数据分析

11、数据仓库和数据库在使用上的区别
(1)数据库在于捕获数据,数据仓库在于分析数据
(2)数据库对数据增删改实时性要求强,需存储在线用户数据,而数据仓库存储的一般是历史数据
(3)数据库设计需尽量避免冗余,但为提高查询效率也允许一定冗余度,而数据仓库在设计上更偏向采用反范式设计

十五、索引

1、索引的种类

(1)按功能逻辑分4种:普通索引、唯一索引、主键索引、全文索引
1)普通索引:基础索引,没有任何约束,主要用于提高查询效率
2)唯一索引:在普通索引基础上增加数据唯一性约束,一张表里可以有多个唯一索引
3)主键索引:在唯一索引基础上增加不为空约束,即 NOT NULL+UNIQUE
4)全文索引:MySQL 自带的全文索引只支持英文。通常可以采用专门的全文搜索引擎,如 ES(ElasticSearch) 和 Solr

(2)按物理实现方式分2种:聚集索引、非聚集索引
1)聚集索引:可以按照主键来排序存储数据。表中数据行按索引的排序方式进行存储,对查找行很有效。只有当表包含聚集索引时,表内的数据行才会按照索引列的值在磁盘上进行物理排序和存储。每个表只能有一个聚集索引,因为数据行只能按一个顺序存储
2)非聚集索引:也称为二级索引或辅助索引。数据库系统有单独存储空间存放非聚集索引,这些索引项按照顺序存储,但索引项指向的内容是随机存储。即系统会进行两次查找,第一次先找到索引,第二次找到索引对应的位置取出数据行。非聚集索引不会把索引指向的内容像聚集索引一样直接放到索引后面,而是维护单独的索引表(只维护索引,不维护索引指向的数据),为数据检索提供方便
扩展:聚集索引与非聚集索引在使用上的区别:
1)聚集索引的叶子节点存储的数据记录,非聚集索引的叶子节点存储的是数据位置。非聚集索引不会影响数据表的物理存储顺序
2)一个表只能有一个聚集索引,因为只能有一种排序存储方式,但可以有多个非聚集索引,即多个索引目录提供数据检索
3)使用聚集索引时数据的查询效率高,但如果对数据进行插入,删除,更新等操作,效率会比非聚集索引低

(3)按字段个数分2种:单一索引、联合索引
1)单一索引:索引列为一列
2)联合索引:多个列组合在一起创建的索引(要注意创建时的顺序)
扩展:联合索引存在最左匹配原则,即按照最左优先方式进行索引的匹配

2、索引的作用
(1)概念:是帮数据库管理系统高效获取数据的一种数据结构
(2)不足:占用存储空间、降低数据库写操作的性能、有多个索引时还会增加索引选择的时间。
(3)不需创建索引的场景:表中数据行少(少于1000行)、数据重复度大(高于10%)

十六、索引的数据结构—B+树索引

1、数据库服务器的2种存储介质
(1)内存:临时存储,容量有限,当发生意外时(如断电或发生故障重启)会造成数据丢失
(2)硬盘:永久存储介质,所以数据要保存到硬盘
扩展:索引存放在硬盘上,当在硬盘上进行查询时,就产生了硬盘的 I/O 操作。相比于内存的存取来说,硬盘的 I/O 存取消耗的时间要高很多。通过索引来查找某行数据的时,需计算产生的磁盘 I/O 次数,磁盘 I/O 次数越多,消耗的时间就越大。如果我能让索引的数据结构尽量减少硬盘的 I/O 操作,所消耗的时间就越小

2、二叉树的局限性
平衡二叉树包括:平衡二叉搜索树、红黑树、数堆、伸展树。
平衡二叉树(左子树和右子树高度差不超过1)比普通二叉树好一点,但也有树的深度较高的局限性

3、B树
(1)英文是 Balance Tree,即平衡的多路搜索树,高度远小于平衡二叉树。在文件系统和数据库系统中的索引结构经常采用 B 树来实现
(2)结构图

1)一个节点包括 M 个子节点,M 称为 B 树的阶。
2)一个磁盘块中包括了 x 个关键字,那么指针数就是 x+1
3)B 树相比于平衡二叉树,磁盘 I/O 操作要少,在数据查询中比平衡二叉树效率高

4、B+树
(1)B+ 树基于 B 树做了改进,主流的 DBMS 都支持 B+ 树索引方式,如 MySQL
(2)B+树与B树的差异:
1)有 k 个孩子的节点就有 k 个关键字。也就是孩子数量 = 关键字数,而 B 树中,孩子数量 = 关键字数 +1
2)非叶子节点的关键字会同时存在于子节点中,并且是在子节点中所有关键字的最大(或最小)
3)非叶子节点仅用于索引,不保存数据记录,跟记录有关的信息都放在叶子节点中。而 B 树中,非叶子节点既保存索引,也保存数据记录
4)所有关键字都在叶子节点出现,叶子节点构成一个有序链表,且叶子节点本身按照关键字的大小从小到大顺序链接
(3)结构图

(4)与B树的查询效率对比
1)查询效率更稳定(B+ 树每次只有访问到叶子节点才能数据,而 B 树非叶子节点也会存储数据,就造成查询效率不稳定的情况。)
2)查询效率更高(B+ 树比 B 树更矮胖(阶数更大,深度更低),查询所需的磁盘 I/O 会更少。同样的磁盘页大小,B+ 树可以存储更多的节点关键字)
3)范围查询更方便(所有关键字都在 B+ 树的叶子节点中,并通过有序链表进行链接。而 B 树中则需通过中序遍历才能完成查询范围的查找,效率低很多)

(5) InnoDB 和 MyISAM 存储引擎,默认采用 B+ 树索引,无法使用 Hash 索引。InnoDB 提供自适应 Hash 不需要手动指定。如果是 Memory/Heap 和 NDB 存储引擎,可以选择 Hash 索引

十七、Hash索引的底层原理

1、Hash 本身是一个函数,又称为散列函数,可以大幅提升检索数据的效率

2、Hash 算法是通过某种确定性算法(如 MD5、SHA1、SHA2、SHA3)将输入转变为输出。相同的输入可得到相同的输出,假设输入内容有微小偏差,在输出中通常会有不同的结果

3、Hash检索效率高于B+树。Hash一次检索就可以找到数据, B+ 树需自顶向下依次查找,多次访问节点才能找到数据,中间需多次 I/O 操作

4、Hash索引示意图

键值 key 通过 Hash 映射找到桶 bucket。桶(bucket)指的是一个能存储一条或多条记录的存储单位。一个桶的结构包含了一个内存指针数组,桶中每行数据都会指向下一行,形成链表结构,当遇到 Hash 冲突时,会在桶中进行键值的查找
扩展:若桶空间小于输入的空间,不同的输入可能会映射到同一个桶,这时就产生 Hash 冲突,如果 Hash 冲突的量很大,会影响读取的性能

5、Hash值的字节数比较少,简单的4个字节就够,多的16位或32位

6、Hash索引与B+树索引的区别
(1)Hash 索引不能范围查询,B+ 树可以。因为 Hash 索引指向的数据无序,而 B+ 树的叶子节点是个有序链表
(2)Hash 索引不支持联合索引最左侧原则(即联合索引的部分索引无法使用),而 B+ 树可以。对于联合索引来说,Hash 索引在计算 Hash 值时是将索引键合并后再一起计算 Hash 值,不会针对每个索引单独计算 Hash 值。因此如果用到联合索引的一个或者几个索引时,联合索引无法被利用
(3)Hash 索引不支持 ORDER BY 排序,因为 Hash 索引指向的数据是无序,无法起到排序优化作用,而 B+ 树索引数据有序,可起到对该字段 ORDER BY 排序优化的作用。同理,也无法用 Hash 索引进行模糊查询,而 B+ 树使用 LIKE 进行模糊查询时,LIKE 后面前模糊查询(如 % 开头)的话就可以起到优化作用

7、Hash 索引通常不会用到重复值多的列上,如列为性别、年龄的情况等

8、Hash索引适用的场景
(1)键值型数据库中使用,如Redis
(2)MySQL 中的 Memory 存储引擎支持 Hash 存储
(3) MySQL 的 InnoDB 存储引擎还有个“自适应 Hash 索引”的功能,当某个索引值使用非常频繁时,它会在 B+ 树索引的基础上再创建一个 Hash 索引,这样 B+ 树也具备了 Hash 索引的优点
扩展:
1)自适应 Hash 索引只保存热数据(经常被使用的数据),并非全表数据。因此数据量不会很大,因此自适应 Hash 也是存放到缓冲池中,进一步提升查找效率
2)查看是否开启了自适应Hash索引:mysql> show variables like '%adaptive_hash_index';

3)说明:InnoDB 本身不支持 Hash 索引,但提供自适应 Hash 索引,不需用户来操作,存储引擎会自动完成。自适应 Hash 是 InnoDB 三大关键特性之一,另两个分别是插入缓冲和二次写

十八、索引的使用原则

1、什么情况下使用索引
(1)字段的数值有唯一性的限制,如用户名:数据表中,如果某个字段是唯一性的,就可以直接创建唯一性索引,或主键索引
(2)频繁作为 WHERE 查询条件的字段,尤其在数据表大的情况下:创建普通索引就可以大幅提升数据查询的效率
(3)需要经常 GROUP BY 和 ORDER BY 的列
(4)UPDATE、DELETE 的 WHERE 条件列,一般也需创建索引
(5)DISTINCT 字段需要创建索引
(6)做多表 JOIN 连接操作时,创建索引需注意以下原则:
1)连接表的数量尽量不超过 3 张,因为每增加一张表就相当于增加了一次嵌套循环,数量级增长会非常快,严重影响查询效率
2)对 WHERE 条件创建索引,因为 WHERE 才是对数据条件的过滤
3)对用于连接的字段创建索引,且该字段在多张表中类型必须一致

2、什么情况下不需创建索引
(1)WHERE 条件(包括 GROUP BY、ORDER BY)里用不到的字段不需创建索引
(2)表记录太少,如少于 1000 个,不需要创建索引
(3)字段中有大量重复数据,不用创建索引,如性别字段
(4)频繁更新的字段不一定要创建索引。因为更新数据时,也需更新索引,如果索引太多,在更新索引时也会造成负担,从而影响效率

3、什么情况下索引失效
(1)索引进行了表达式计算,会失效
(2)索引使用函数,会失效
(3)WHERE 子句中,OR 前的条件列进行了索引,OR 后的条件列没有进行索引,会失效
(4)使用 LIKE 进行模糊查询时,前面是 %,会失效
(5)索引列尽量设置为 NOT NULL 约束,否则会失效(因为判断索引列是否为 NOT NULL,往往需要走全表扫描)
(6)使用联合索引的时候注意最左原则(一条 SQL 语句可以只使用联合索引的一部分,但需要从最左侧开始,否则会失效)
扩展:
1)联合索引(x,y,z),那么这个索引的使用顺序就很重要。如果在条件语句中只有 y 和 z,就用不上联合索引。条件语句中的字段顺序并不重要,因为在逻辑查询优化阶段会自动进行查询重写
2)如果遇到范围条件查询,如(<)(<=)(>)(>=)和 between 等,那么范围列后的列就无法使用到索引

十九、从数据页角度理解B+树查询

1、B+ 树和 Hash 索引的索引结构提供了高效的索引方式,不过这些索引信息以及数据记录都是保存在文件上的,确切说是存储在页结构中

2、数据库中的存储结构是怎么样的?
(1)数据库中,不论读一行还是多行,都是将这些行所在的页进行加载。即数据库管理存储空间的基本单位是页(Page)
(2)数据库中,存在:表空间(Tablespace)、段(Segment)、区(Extent)、页、行

一个表空间包括一个或多个段、一个段包括一个或多个区、一个区包括了多个页、一个页中包括了多个行(记录)

1)表空间(Tablespace)是逻辑容器,存储的对象是段,一个表空间可以有一个或多个段,但一个段只能属于一个表空间。数据库由一个或多个表空间组成,表空间从管理上可分为系统表空间、用户表空间、撤销表空间、临时表空间等
2)段(Segment)可以有一个或多个区,区在文件系统是一个连续分配的空间(在 InnoDB 中是连续的 64 个页),不过在段中不要求区与区之间相邻。段是数据库中的分配单位,不同类型的数据库对象以不同的段形式存在。当创建数据表、索引时,会相应创建对应的段,如创建一张表时会创建一个表段,创建一个索引时会创建一个索引段
3)区(Extent)是比页大一级的存储结构,InnoDB 存储引擎中,一个区会分配 64 个连续的页。因为 InnoDB 中页大小默认16KB,所以一个区的大小是 64*16KB=1MB

(3)InnoDB 中存在两种表空间类型:共享表空间和独立表空间。共享表空间意味着多张表共用一个表空间。独立表空间意味着每张表有一个独立的表空间,即数据和索引信息都会保存在自己的表空间中。独立表空间可在不同的数据库之间进行迁移
扩展:查看InnoDB表空间类型:mysql>show variables like 'innodb_file_per_table';

innodb_file_per_table=ON,意味着每张表都单独保存为一个.ibd 文件

3、数据页的结构
(1)页(Page)按类型分为:数据页(保存 B+ 树节点)、系统页、Undo 页和事务数据页等。数据页是最常使用的页
(2)页大小限定了表行的最大长度,不同 DBMS 的表页大小不同。如 MySQL 的 InnoDB 存储引擎,默认页大小是 16KB
查看页的大小:mysql> show variables like '%innodb_page_size%';

扩展:SQL Server 的页大小为 8KB;Oracle 中用术语“块”(Block)来代表“页”,Oralce 支持的块大小为 2KB,4KB,8KB,16KB,32KB 和 64KB
(3)数据库 I/O 操作的最小单位是页,与数据库相关的内容都存储在页结构里.
(4)数据页包括七个部分:文件头(File Header)、页头(Page Header)、最大最小记录(Infimum+supremum)、用户记录(User Records)、空闲空间(Free Space)、页目录(Page Directory)和文件尾(File Tailer)

扩展:这7个部分可以分成3个部分:
1)文件通用部分:文件头、文件尾。类似集装箱,将页内容进行封装,通过文件头和文件尾校验的方式来确保页的传输是完整的
      <1>文件头有两个字段,FIL_PAGE_PREV 和 FIL_PAGE_NEXT,作用相当于指针,分别指向上个数据页和下个数据页。连接起来的页相当于一个双向链表(采用链表的结构让数据页之间不需要是物理上的连续,而是逻辑上的连续),如下图:

      <2>文件尾的校验方式采用 Hash 算法。举个例子,当页传输时突然断电,造成该页传输的不完整,这时通过文件尾的校验和(checksum 值)与文件头的校验和做比对,如果两个值不相等则证明页的传输有问题,需要重新传输,否则认为页的传输已经完成
2)记录部分:页的主要作用是存储记录,所以“最小和最大记录”、“用户记录”占了页结构的主要空间。另空闲空间是个灵活部分,新记录插入时,会从空闲空间分配用于存储新记录
3)索引部分:这部分重点指页目录,它起到记录的索引作用,在页中,记录以单向链表的形式进行存储。单向链表的特点是插入、删除方便,但检索效率不高,最差的情况需遍历链表所有节点才能完成检索,因此在页目录中提供了二分查找方式,用来提高记录的检索效率。这个过程好比给记录创建了一个目录:
      <1> 将所有记录分成几个组,这些记录包括最小记录和最大记录,不包括标记为“已删除”的记录
      <2> 第 1 组,即最小记录所在分组只有 1 个记录;最后一组,即最大记录所在分组有 1-8 条记录;其余组记录数在 4-8 条。这样做的好处是,除第 1 组(最小记录所在组)以外,其余组记录数尽量平分
      <3> 每个组最后一条记录的头信息会存储该组一共多少条记录,作为 n_owned 字段
      <4> 页目录存储每组最后一条记录的地址偏移量,这些地址偏移量按照先后顺序存储起来,每组的地址偏移量也被称之为槽(slot),每个槽相当于指针指向了不同组的最后一个记录,如下:

页目录存储的是槽,槽相当于分组记录的索引。通过槽查找记录,实际上就是在做二分查找

4、从数据页的角度看 B+ 树是如何进行查询的
(1)B+ 树按照节点类型可以分成两部分:叶子节点(最底层节点,节点高度为 0,存储行记录)、非叶子节点,节点高度大于 0,存储索引键和页面指针,不存储行记录

(2)B+ 树中,每个节点都是一个页,新建节点时,会申请一个页空间。同一层上的节点之间,通过页结构构成一个双向链表(页文件头中的两个指针字段)。非叶子节点,包括了多个索引行,每个索引行里存储索引键和指向下一层页面的页面指针。最后是叶子节点,存储了关键字和行记录,在节点内部(即页结构的内部)记录之间是一个单向链表,对记录进行查找通过页目录采用二分查找方式来进行
(3)B+树如何进行记录检索:先从 B+ 树的根开始,逐层检索直到找到叶子节点(即数据页);将数据页加载到内存,页目录中的槽(slot)采用二分查找的方式找到一个粗略的记录分组;再在分组中通过链表遍历方式查找记录
(4)普通索引和唯一索引在查询效率上有什么不同?:唯一索引是在普通索引上增加了约束性,即关键字唯一,找到关键字就停止检索。普通索引,可能会存在用户记录中的关键字相同的情况,根据页结构的原理,当读取一条记录时,不是单独将这条记录从磁盘中读出去,而是将这个记录所在的页加载到内存中进行读取。InnoDB 存储引擎的页大小为 16KB,在一个页中可能存储着上千个记录,因此在普通索引字段上进行查找也就是在内存中多几次“判断下一条记录”的操作,对于 CPU 来说,这些操作所消耗的时间可以忽略不计。所以对一个索引字段进行检索,采用普通索引还是唯一索引在检索效率上基本上没有差别

二十、从磁盘I/O角度理解查询成本

1、数据库缓冲池
(1)作用:DBMS会申请占用内存来作为数据缓冲池,最小化磁盘活动
(2)如何读取数据:先判断读取的页面是否在缓冲池中,若存在就直接读取,否则通过内存或磁盘将页面存放到缓冲池中进行读取
(3)如何更新数据:记录进行修改时,会先修改缓冲池中页里面的记录信息,然后缓冲池采用一种checkpoint机制以一定频率刷新到磁盘上
扩展:当缓冲池不够用时,需释放掉一些不常用的页,就可以强行采用 checkpoint 方式,将不常用的脏页回写到磁盘上,再从缓冲池中将这些页释放掉。脏页(dirty page)指缓冲池中被修改过的页,与磁盘上的数据页不一致
(4)查看缓冲池大小(使用的是 InnoDB 存储引擎),通过 innodb_buffer_pool_size 变量:mysql > show variables like 'innodb_buffer_pool_size';

8388608/1024/1024=8MB
修改缓冲池大小:mysql >set global innodb_buffer_pool_size = 134217728;
再次查看缓冲池大小:mysql > show variables like 'innodb_buffer_pool_size';

查看缓冲池个数:mysql > show variables like 'innodb_buffer_pool_instances'

注:当前只有一个缓冲池。实际上innodb_buffer_pool_instances默认情况下为 8,为什么只显示只有一个呢?这里需要说明的是,如果想开启多个缓冲池,需要将innodb_buffer_pool_size参数设置为大于等于 1GB,这时innodb_buffer_pool_instances才会大于 1
(5)缓冲池不等于查询缓存,共同点是均通过缓存机制来提升效率。但缓冲池服务于数据库整体的 I/O 操作,查询缓存服务于 SQL 查询和查询结果集,因为命中条件苛刻,且只要数据表发生变化,查询缓存就会失效,因此命中率低, MySQL8.0 版本中已经弃用查询缓存功能

2、数据页加载的3种方式
如果缓冲池中没有该页数据,那么缓冲池有以下三种读取数据方式,每种方式读取效率都不同:
(1)内存读取(如果该数据存在于内存中)
(2)随机读取(如果数据没有在内存中,就需要在磁盘上对该页进行查找)
(3)顺序读取(批量读取方式,因为请求的数据在磁盘上往往是相邻存储,顺序读取可批量读取页面,一次性加载到缓冲池中就不需再对其他页面单独进行磁盘 I/O 操作了)

3、通过 last_query_cost 统计 SQL 语句的查询成本
(1)一条 SQL 查询语句在执行前需确定查询计划,如果存在多种查询计划,MySQL 会计算每个查询计划所需成本,从中选择成本最小的一个作为最终执行的查询计划
(2)通过查看当前会话中的 last_query_cost 变量值来得到当前查询成本。成本对应的是 SQL 语句需要读取的页的数量
先执行查询语句A,再查看成本:mysql> SHOW STATUS LIKE 'last_query_cost';

可看到查询语句A只需要检索1个页

没有理想的索引

1、索引片: 指SQL 查询语句在执行中需扫描的一个索引片段,根据索引片中包含匹配列的数量不同,将索引分成窄索引(包含索引列数为 1 或 2)和宽索引(包含索引列数大于 2)

2、可通过将 SELECT 中需用到的列(主键列可除外)都设置在宽索引中,避免回表扫描,提升 SQL 查询效率。
扩展:回表指数据库根据索引找到了数据行后,还需通过主键再次到数据表中读取数据的情况

3、过滤因子:联合过滤因子有更高的过滤能力。过滤因子决定了索引片的大小(这里不是窄索引和宽索引),过滤因子的条件过滤能力越强,满足条件的记录数就越少,SQL 查询需扫描的索引片就越小

4、针对 SQL 查询的理想索引设计:三星索引
(1) WHERE 条件语句中,找到所有等值谓词中的条件列,将它们作为索引片中的开始列(原理:最小化碎片)
(2)将 GROUP BY 和 ORDER BY 中的列加入到索引中(原理:避免排序)
(3)将 SELECT 字段中剩余的列加入到索引片中(原理:避免回表查询)

5、很难存在理想的索引设计:有时候并不需要完全遵循三星索引原则,原因有以下两点:
(1)三星索引让索引片变宽,这样每个页能存储的索引数据就变少,增加页加载的数量。另一个角度来看,如果数据量很大,如有 1000 万行数据,过多索引所需的磁盘空间可能会成为一个问题,对缓冲池所需空间的压力也会增加
(2)增加索引维护成本。如果为所有查询语句都设计理想的三星索引,会让数据表中索引个数过多,这样索引维护的成本也会增加

6、三星索引的优缺点
优点:不需进行回表查询,减少磁盘 I/O 次数
缺点:造成频繁的页分裂和页合并,对于数据的插入和更新来说,效率降低不少

7、设计合理的索引
(1)一张表索引个数不宜过多(先考虑在原有索引片上增加索引,即采用复合索引方式,而非新建一个新索引。另定期检查索引使用情况,很少使用的索引可及时删除,减少索引数量)
(2)索引片中,需控制索引列的数量(通常将 WHERE 里的条件列添加到索引中,SELECT 中的非条件列则不需添加。除非 SELECT 中的非条件列数少,且该字段经常用到)
(3)单列索引和复合索引的长度需控制(字符列会占用较大空间,尽量采用数值类型替代字符类型,尽量避免用字符类型做主键,同时字符字段最好只建前缀索引)
(4)没有理想的索引,只有适合的索引设计。需在索引效率和维护成本中平衡

二十二、悲观锁、乐观锁

1、锁按照锁定对象的粒度大小划分为:行锁、页锁、表锁
(1)行锁:锁定力度小,发生锁冲突概率低,实现并发度高,但锁的开销较大,加锁较慢,易出现死锁情况
(2)页锁:锁定的数据资源比行锁多,因为一个页中可以有多个行记录。当使用页锁时,会出现数据浪费现象,但这浪费最多也就一个页上的数据行。页锁开销介于表锁和行锁之间,会出现死锁,并发度一般
(3)表锁:对数据表锁定,锁定粒度大,发生锁冲突概率会较高,数据访问并发度低。好处在于对锁的使用开销小,加锁很快
扩展:
(1)还可在区和数据库的粒度上锁定数据,对应区锁和数据库锁
(2)不同的数据库和存储引擎支持的锁粒度不同:

(3)每个层级的锁数量有限制,因为锁会占用内存空间,空间大小有限。当某层级锁数量超过这个层级的阈值时,会进行锁升级。锁升级就是用更大粒度的锁替代多个更小粒度的锁,如 InnoDB 中行锁升级为表锁。好处是占用的锁空间降低,但数据的并发度也下降了

2、从数据库管理角度划分:共享锁、排它锁
(1)共享锁:也叫读锁或 S 锁,锁定的资源可被其他用户读取,但不能修改。

1)在进行SELECT时,会将对象进行共享锁锁定,数据读取完毕后,会释放共享锁,这样就可保证数据在读取时不被修改
2)给 product_comment 表上加共享锁:LOCK TABLE product_comment READ;
3)对表上的共享锁进行解锁:UNLOCK TABLE;
4)给user_id=912178 的数据行加上共享锁:SELECT comment_id, product_id, comment_text, user_id FROM product_comment WHERE user_id = 912178 LOCK IN SHARE MODE

(2)排它锁:也叫独占锁、写锁或 X 锁,锁定数据只许进行锁定操作的事务使用,其他事务无法对已锁定的数据进行查询或修改

1)给 product_comment 表加上排它锁:LOCK TABLE product_comment WRITE;
2)对表上的排它锁进行解锁:UNLOCK TABLE;
3)给user_id=912178 的数据行加上排它锁:SELECT comment_id, product_id, comment_text, user_id FROM product_comment WHERE user_id = 912178 FOR UPDATE;
.
扩展:
(1)当对数据进行更新时,即INSERT、DELETE或UPDATE时,数据库会自动使用排它锁,防止其他事务对该数据行进行操作
(2)意向锁(Intent Lock):给更大一级别的空间示意里面是否已经上过锁。
(3)如果事务想要获得数据表中某些记录的共享锁,就需在数据表上添加意向共享锁。同理,事务想要获得数据表中某些记录的排他锁,就需在数据表上添加意向排他锁。这时,意向锁会告诉其他事务已经有人锁定了表中的某些记录,不能对整个表进行全表扫描
(4)使用共享锁的时会出现死锁风险
例:客户端 1 开启事务,然后采用读锁方式对user_id=912178(有2行数据)的数据行进行查询

然后用客户端 2 开启事务,同样对user_id=912178获取读锁,理论上获取读锁后还可对数据进行修改,但执行修改的sql语句时客户端 2 会一直等待,因为客户端 1 也获取了该数据的读锁,不需客户端 2 对该数据进行修改。这时客户端 2 会提示等待超时,重新执行事务:

3、从程序员角度划分:乐观锁、悲观锁
(1)乐观锁(Optimistic Locking):认为对同一数据的并发操作不会总发生,属小概率事件,不用每次都对数据上锁,即不采用数据库自身的锁机制,而是通过在程序上采用版本号机制或时间戳机制实现

1)乐观锁的版本号机制:在表中设计一个版本字段 version,第一次读时,会获取 version 字段的取值。然后对数据进行更新或删除操作时,会执行UPDATE … SET version=version+1 WHERE version=version。此时如果已经有事务对这条数据进行了更改,修改就不会成功
2)乐观锁的时间戳机制:和版本号机制一样,在更新提交时,将当前数据的时间戳和更新之前取得的时间戳进行比较,如果两者一致则更新成功,否则就是版本冲突

(2)悲观锁(Pessimistic Locking):对数据被其他事务的修改持保守态度,会通过数据库自身的锁机制来实现,从而保证数据操作的排它性

(3)乐观锁和悲观锁的适用场景:
1)乐观锁适合读操作多的场景,相对来说写的操作较少。优点在于程序实现,不存在死锁问题,但适用场景也相对乐观,因为它阻止不了除程序外的数据库操作
2)悲观锁适合写操作多的场景,因为写操作具有排它性。采用悲观锁方式,可在数据库层面阻止其他事务对该数据的操作权限,防止读 - 写和写 - 写的冲突
扩展:乐观锁和悲观锁并不是锁,而是锁的设计思想

4、可采取一些方法避免死锁的发生:
(1)如果事务涉及多个表,操作较复杂,那么可尽量一次锁定所有资源,而不是逐步来获取,这样可减少死锁概率
(2)如果事务需更新表中大部分数据,数据表又较大,这时可采用锁升级方式,如将行锁升级为表锁,减少死锁概率
(3)不同事务并发读写多张表,可约定访问表的顺序,采用相同顺序降低死锁概率
扩展:
(1)采用乐观锁是不会发生死锁的
(2)MySQL MyISAM 存储引擎中不会出现死锁,因为 MyISAM 总是一次性获得全部的锁,这样的话要么全部满足可执行,要么就全部等待

5、 InnoDB 和 MyISAM 的取舍问题
(1)InnoDB 支持事务和行级锁,是 MySQL 默认存储引擎;MyISAM 只支持表级锁,不支持事务,更适合读取数据库的情况
(2)事务处理应用,需要选择 InnoDB;小型应用,需大量的 SELECT 查询,可考虑 MyISAM

二十三、MVCC

1、MVCC (Multiversion Concurrency Control):多版本并发控制技术。通过数据行的多个版本管理来实现数据库的并发控制,它的思想就是保存数据的历史版本。这样就可通过比较版本号决定数据是否显示出来

2、快照读和当前读
快照读:读取的是快照数据。不加锁的简单的 SELECT 都属于快照读
当前读:读取最新数据,而非历史版本数据。加锁的 SELECT,或对数据进行增删改都会进行当前读

3、InnoDB 中的 MVCC 的实现机制
(1)事务版本号:每开启一个事务,都会从数据库中获得一个事务 ID(即事务版本号),事务 ID 是自增长的,通过 ID 大小,可判断事务的时间顺序
(2)行记录的隐藏列:

1)db_row_id:隐藏的行 ID,用来生成默认聚集索引。如果创建数据表时没有指定聚集索引,InnoDB 就用这个隐藏 ID 来创建聚集索引。采用聚集索引方式可提升数据查找效率
2)db_trx_id:操作这个数据的事务 ID,即最后一个对该数据进行插入或更新的事务 ID
3)db_roll_ptr:回滚指针,指向这个记录的 Undo Log 信息

(3)Undo Log:InnoDB 将行记录快照保存在了 Undo Log 里,可以在回滚段中找到它们,如下图:

回滚指针将数据行的所有快照记录通过链表结构串联起来,每个快照记录都保存了当时的 db_trx_id,即那个时间点操作这个数据的事务 ID。这样如果想找历史快照,就可通过遍历回滚指针方式进行查找

4、Read View是如何工作的
(1)Read View 保存当前事务开启时所有活跃(还没提交)的事务列表,可理解为 Read View 保存了不该让这个事务看到的其他的事务 ID 列表
(2) Read VIew 中几个重要的属性:

1)trx_ids:系统当前正活跃的事务 ID 集合
2)low_limit_id:活跃的事务中最大的事务 ID
3)up_limit_id:活跃的事务中最小的事务 ID
4)creator_trx_id,创建这个 Read View 的事务 ID

(3)一些概念:

假设当前有事务 creator_trx_id 想读取某个行记录,这个行记录的事务 ID 为 trx_id,会出现以下几种情况:
1)trx_id < 活跃的最小事务 ID(up_limit_id),即这个行记录在这些活跃的事务创建前就已经提交,那这个行记录对该事务可见
2)trx_id > 活跃的最大事务 ID(low_limit_id),即该行记录在这些活跃的事务创建后才创建,那这个行记录对当前事务不可见
3)up_limit_id < trx_id < low_limit_id,即该行记录的事务 trx_id 在目前 creator_trx_id 这个事务创建时,可能还处于活跃状态,因此需在 trx_ids 集合中遍历,如果 trx_id 存在于 trx_ids 集合中,证明这个事务 trx_id 还处于活跃状态,不可见。否则,trx_id 不存在于 trx_ids 集合中,证明事务 trx_id 已经提交,该行记录可见
.
例:查询一条记录的时候,系统如何通过多版本并发控制技术找到它:
1)先获取事务自己的版本号,也就是事务 ID
2)获取 Read View
3)查询得到的数据,与 Read View 中的事务版本号进行比较
4)如果不符合 ReadView 规则,就需从 Undo Log 中获取历史快照
5)最后返回符合规则的数据
总结:InnoDB 中,MVCC 通过 Undo Log + Read View 进行数据读取,Undo Log 保存历史快照,而 Read View 规则帮助判断当前版本的数据是否可见

5、特别说明
(1)隔离级别为读已提交:(同样的)每次查询语句都会重新获取一次 Read View,如果 Read View 不同,就可能产生不可重复读或幻读的情况
(2)隔离级别为可重复读:避免了不可重复读,因为一个事务只在第一次 SELECT 时获取一次 Read View,后面所有的 SELECT 都复用这个 Read View

6、InnoDB 如何解决幻读
(1)InnoDB 三种行锁:

1)记录锁(Record Locking):对单个行记录添加锁
2)间隙锁(Gap Locking):锁住一个范围(索引之间的空隙),但不包括记录本身。可防止幻读
3)Next-Key 锁:锁住一个范围,同时锁定记录本身,相当于间隙锁 + 记录锁,可解决幻读
说明:在读已提交的情况下,即使采用了 MVCC 方式也会出现幻读。例如同时开启事务 A 和事务 B,事务 A 进行某个条件范围的查询,读取时采用排它锁,事务 B 增加一条符合该条件范围的数据并提交,然后事务 A 再次查询该条件范围的数据,会发现结果集中多出一个符合条件的数据,就出现了幻读。
出现幻读的原因是在读已提交的情况下,InnoDB 只采用记录锁。

(2)隔离级别为可重复读时,InnoDB 会采用 Next-Key 锁机制,解决幻读问题

二十四、查询优化器是如何工作的

1、一条 SQL 语句的执行需要经历的环节:

语法分析检查 SQL 拼写和语法是否正确,语义检查检查 SQL 中访问对象是否存在。语法检查和语义检查保证 SQL 语句没有错误,最终得到一棵语法分析树,然后经过查询优化器得到查询计划(即查询树,由一系列物理操作符按照一定运算关系组成),最后交给执行器执行

2、查询优化器中可分为逻辑查询优化和物理查询优化2个阶段
(1)逻辑查询优化:改变 SQL 语句内容来使 查询更高效,为物理查询优化提供更多的候选执行计划。
(2)采用方式:对 SQL 语句进行等价变换,对查询进行重写(其数学基础是关系代数),对条件表达式进行等价谓词重写、条件简化,对视图进行重写,对子查询进行优化,对连接语义进行了外连接消除、嵌套连接消除等
(3)逻辑查询优化是基于关系代数进行的查询重写,而关系代数每一步都对应着物理计算,这些计算存在多种算法,需计算各种物理路径的代价,从中选择代价最小的作为执行计划。在这个阶段,对于单表和多表连接的操作,需高效地使用索引,提升查询效率

这两个阶段中,查询重写属于代数级、语法级的优化,属于逻辑范围内的优化;而基于代价的估算模型是从连接路径中选择代价最小的路径,属于物理层面的优化

3、查询优化器的两种优化方式
(1)基于规则的优化器(RBO,Rule-Based Optimizer):在优化器里嵌入规则来判断 SQL 查询符合哪种规则,按照相应规则来制定执行计划,同时采用启发式规则去掉明显不好的存取路径
(2)基于代价的优化器(CBO,Cost-Based Optimizer):根据代价评估模型,计算每条可能执行计划的代价,即 COST,从中选择代价最小的作为执行计划。相比于 RBO ,CBO 对数据更敏感,因为会利用数据表中统计信息来做判断,针对不同数据表,查询得到的执行计划可能不同,因此制定出来的执行计划也更符合数据表实际情况

4、CBO 如何统计代价
能调整的代价模型的参数有哪些: MySQL 中的COST Model,COST Model是优化器用来统计各种步骤的代价模型,5.7.10 版本后,MySQL 引入两张数据表,里面规定了各步骤预估的代价(Cost Value) ,可以从mysql.server_cost和mysql.engine_cost这两张表中获得这些步骤的代价:

SQL > SELECT * FROM mysql.server_cost

1)disk_temptable_create_cost:临时表文件(MyISAM 或 InnoDB)的创建代价,默认值20
2)disk_temptable_row_cost:临时表文件(MyISAM 或 InnoDB)的行代价,默认值 0.5
3)key_compare_cost:键比较的代价。键比较的次数越多,这项的代价越大,是一个重要的指标,默认值 0.05
4)memory_temptable_create_cost:内存中临时表的创建代价,默认值 1
5)memory_temptable_row_cost,内存中临时表的行代价,默认值 0.1
6)row_evaluate_cost:统计符合条件的行代价,符合条件的行数越多,这一项的代价越大,这是个重要的指标,默认值 0.1
.
SQL > SELECT * FROM mysql.engine_cost

ngine_cost主要统计了页加载的代价
1)io_block_read_cost:从磁盘中读取一页数据的代价,默认是 1
2)memory_block_read_cost:从内存中读取一页数据的代价,默认是 0.25
.
扩展:
<1> 修改代价参数:如将io_block_read_cost参数设置为 2.0:
UPDATE mysql.engine_cost SET cost_value = 2.0 WHERE cost_name = 'io_block_read_cost'; FLUSH OPTIMIZER_COSTS;

<2> 专门针对某个存储引擎,如 InnoDB 存储引擎设置io_block_read_cost,比如设置为 2:
INSERT INTO mysql.engine_cost(engine_name, device_type, cost_name, cost_value, last_update, comment) VALUES ('InnoDB', 0, 'io_block_read_cost', 2, CURRENT_TIMESTAMP, 'Using a slower disk for InnoDB'); FLUSH OPTIMIZER_COSTS;

5、代价模型如何计算
(1)可简单地认为,总执行代价等于 I/O 代价 +CPU 代价。PAGE FETCH 是 I/O 代价,即页面加载代价,包括数据页和索引页加载的代价。W*(RSI CALLS) 是 CPU 代价。W 是个权重因子,表示CPU到 I/O 之间转化的相关系数,RSI CALLS 代表CPU的代价估算,包括键比较(compare key)和行估算(row evaluating)的代价

(2)MySQL5.7 版本后,代价模型又进行了完善,不仅考虑了 I/O 和 CPU 开销,还对内存计算和远程操作的代价进行了统计,即总代价计算公式演变成:
总代价 = I/O 代价 + CPU 代价 + 内存代价 + 远程代价

二十五、使用性能分析工具定位SQL执行慢的原因

1、数据库服务器的优化步骤
整个流程划分成观察(Show status)、行动(Action)两部分。字母 S 部分代表观察(会使用相应的分析工具),字母 A 部分代表行动(对应分析可以采取的行动)

(1) S1 部分,需观察服务器状态是否存在周期性波动。如果存在,可能是周期性节点的原因,如双十一、促销活动等。这样的话,可以通过 A1 (加缓存,或更改缓存失效策略)步骤解决
(2)如果缓存策略没有解决,或不是周期性波动原因,就需进一步分析查询延迟和卡顿的原因。进入 S2 这一步,需开启慢查询。慢查询可以帮助定位执行慢的 SQL 语句。可通过设置long_query_time参数定义“慢”的阈值,如果 SQL 执行时间超过了long_query_time,则认为是慢查询。当收集上来这些慢查询后,就可通过分析工具对慢查询日志进行分析
(3) S3 步骤中,就知道了执行慢的 SQL 语句,就能针对性地用 EXPLAIN 查看对应 SQL 语句的执行计划,或用 SHOW PROFILE 查看 SQL 中每个步骤的时间成本。这样就可以了解 SQL 查询慢是因为执行时间长,还是等待时间长
(4)如果是等待时间长,就进入 A2 (调优服务器的参数)步骤,如适当增加数据库缓冲池等。如果是执行时间长,就进入 A3 步骤,考虑是索引设计问题?还是查询关联的数据表过多?还是因为数据表字段设计问题导致了这一现象。然后在这些维度上进行对应调整
(5)如果 A2 和 A3 都不能解决问题,就需考虑数据库自身 SQL 查询性能是否达到瓶颈,如果没有达到瓶颈,就需重新检查,重复以上步骤。如果已经达到瓶颈,进入 A4 阶段,需考虑增加服务器,采用读写分离架构,或考虑对数据库分库分表(如垂直分库、垂直分表和水平分表等)
总结:上面数据库调优流程为3种分析工具,也可理解为3个步骤:慢查询、EXPLAIN 和 SHOW PROFILE

2、使用慢查询定位执行慢的 SQL

(1)查看慢查询是否已开启:mysql > show variables like '%slow_query_log';

OFF表示目前为关闭状态
(2)开启慢查询日志:mysql > set global slow_query_log='ON'; 再次查询

查看到慢查询分析为ON,即开启。且文件保存在 DESKTOP-4BK02RP-slow 文件中
(3)查看慢查询的时间阈值设置:mysql > show variables like '%long_query_time%';

(4)修改慢查询时间阈值(如设置为3s):mysql > set global long_query_time = 3;

扩展:使用 MySQL 自带的 mysqldumpslow 工具统计慢查询日志(这个工具是个 Perl 脚本,需先安装好 Perl),mysqldumpslow 命令具体参数如下:
(1)-s:采用 order 排序方式,排序方式有以下几种: c(访问次数)、t(查询时间)、l(锁定时间)、r(返回记录)、ac(平均查询次数)、al(平均锁定时间)、ar(平均返回记录数)和 at(平均查询时间)。其中 at 为默认排序方式。
(2)-t:返回前 N 条数据
(3)-g:后面可以是正则表达式,对大小写不敏感
例:如想按照查询时间排序,查看前两条 SQL 语句:perl mysqldumpslow.pl -s t -t 2 "C:\ProgramData\MySQL\MySQL Server 8.0\Data\DESKTOP-4BK02RP-slow.log"

查询时间大于慢查询时间阈值的 SQL 语句会保存在慢查询日志中,可以通过 mysqldumpslow 工具提取出来想要查找的 SQL 语句了

3、使用 EXPLAIN 查看执行计划
要了解 product_comment 和 user 表进行联查时采用的执行计划:EXPLAIN SELECT comment_id, product_id, comment_text, product_comment.user_id, user_name FROM product_comment JOIN user on product_comment.user_id = user.user_id

EXPLAIN 可以帮助了解数据表的读取顺序、SELECT 子句的类型、数据表的访问类型、可使用的索引、实际使用的索引、使用的索引长度、上一个表的连接匹配条件、被优化器查询的行的数量以及额外信息(如是否使用外部排序,是否使用临时表等)等
1)SQL 执行顺序是根据 id 从大到小执行,即 id 越大越先执行,id 相同时从上到下执行
2)数据表的访问类型所对应的 type 列有以下几种情况:

其中:
1)all 是最坏情况(采用全表扫描方式)。
2) index 和 all 差不多,只不过 index 对索引表进行全扫描,好处是不再需要对数据进行排序,但开销依然很大(如果在 Extral 列看到 Using index,说明采用了索引覆盖,即索引可覆盖所需的 SELECT 字段,就不需进行回表,这样就减少了数据查找开销)
3) range 表示采用索引范围扫描,从这一级开始,索引作用会越来越明显,因此需尽量让 SQL 查询可以使用到 range 这一级及以上的 type 访问方式
4)index_merge 表示查询使用了两个或以上的索引,最后取交集或并集
5)ref 表示采用非唯一索引,或者是唯一索引的非唯一性前缀
6)eq_ref 是使用主键或唯一索引时产生的访问方式,通常使用在多表联查中
7)const 表示使用主键或唯一索引(所有的部分)与常量值进行比较(注:const 类型和 eq_ref 都使用主键或唯一索引,区别是,const 与常量进行比较,查询效率更快,eq_ref 通常用于多表联查中)
8)system 一般用于 MyISAM 或 Memory 表,属 const 类型的特例,当表只有一行时连接类型为 system
说明:效率从低到高依次为 all < index < range < index_merge < ref < eq_ref < const/system。在查看执行计划时,通常希望执行计划至少可使用到 range 级别以上的连接方式,如果只使用到了 all 或 index 连接方式,可以从 SQL 语句和索引设计角度上进行改进

4、使用 SHOW PROFILE 查看 SQL 的具体执行成本
(1)查看profiling是否开启(OFF表示关闭,未开启):mysql > show variables like 'profiling';

(2)开启profiling:mysql > set profiling = 'ON';

(3)查看当前会话有哪些 profiles(有几个查询):mysql > show profiles;

(4)查看上一个查询的开销:mysql > show profile;

查看指定Query ID的开销:如 show profile for query 2查询结果是一样的

二十六、数据库的主从同步

1、不是所有应用都需对数据库进行主从架构设置,毕竟设置架构本身有成本,如果目的在于提升数据库高并发访问效率,首先需考虑如何优化 SQL 和索引,其次是采用缓存策略,如使用 Redis,通过 Redis 高性能的优势将热点数据保存在内存数据库中,提升读取效率,最后才是对数据库采用主从架构,进行读写分离。按这个顺序的优化方式,使用和维护的成本是由低到高

2、主从同步的作用
(1)提高数据库的吞吐量
(2)读写分离。通过主从复制方式来同步数据,然后通过读写分离提高数据库并发处理能力
注:一份数据被放到多个数据库中,其中一个数据库是 Master 主库,其余的多个数据库是 Slave 从库。当主库进行更新时,会自动将数据复制到从库,而在客户端读取数据时,会从从库中读取,即采用读写分离方式
(3)数据备份。通过主从复制将主库数据复制到从库上,相当于一种热备份机制,即在主库正常运行情况下进行的备份,不会影响到服务
(4)具有高可用性。刚才的数据备份实际上是一种冗余机制,通过这种冗余方式可换取数据库的高可用性,即当服务器出现故障或宕机情况下,可切换到从服务器上,保证服务正常运行

3、主从同步的原理
(1)主从同步的原理是基于 Binlog 进行数据同步的。Binlog 二进制日志,记录了对数据库进行更新的事件。
(2)主从复制过程基于 3 个线程来操作,一个主库线程,两个从库线程

1)二进制日志转储线程(Binlog dump thread)是主库线程。当从库线程连接时,主库可将二进制日志发送给从库,当主库读取事件时,会在 Binlog 上加锁,读取完成后,再将锁释放掉
2)从库 I/O 线程会连接到主库,向主库发送请求更新 Binlog。这时从库的 I/O 线程就可读取到主库的二进制日志转储线程发送的 Binlog 更新部分,并拷贝到本地形成中继日志(Relay log)
3)从库 SQL 线程会读取从库中的中继日志,并执行日志中的事件,从而将从库中的数据与主库保持同步

注:主从同步的内容是二进制日志(Binlog),虽叫二进制日志,实际上存储的是一个个事件(Event),这些事件分别对应着数据库的更新操作,如 INSERT、UPDATE、DELETE 等。另还需注意的是,不是所有版本的 MySQL 都默认开启服务器的二进制日志,在进行主从同步的时候,我们需要先检查服务器是否已经开启了二进制日志。

4、解决主从同步的数据一致性问题
(1)方法 1:异步复制

异步模式是客户端提交 COMMIT 之后不需等从库返回任何结果,而是直接将结果返回给客户端,好处是不会影响主库写的效率,但可能存在主库宕机,而 Binlog 还没有同步到从库的情况,即此时的主库和从库数据不一致,这种复制模式下的数据一致性最弱

(2)方法 2:半同步复制

1)MySQL5.5 版本后开始支持半同步复制方式。原理是客户端提交 COMMIT 后不直接将结果返回给客户端,而是等待至少有一个从库接收到了 Binlog,且写入到中继日志中,再返回给客户端。好处就是提高了数据的一致性,当然相比异步复制,至少多增加了一个网络连接的延迟,降低了主库写的效率
2) MySQL5.7 版本中还增加了rpl_semi_sync_master_wait_for_slave_count参数,可以对应答的从库数量进行设置,默认为 1,即只要有 1 个从库进行了响应,就可返回给客户端。如果将这个参数调大,可提升数据一致性强度,但会增加主库等待从库响应的时间

(3)组复制

1)组复制技术,简称 MGR(MySQL Group Replication)。是 MySQL 5.7.17 版本中推出的新的数据复制技术,基于 Paxos 协议的状态机复制
2)异步复制和半同步复制都无法最终保证数据一致性问题,半同步复制是通过判断从库响应个数来决定是否返回给客户端,数据一致性相比异步复制有提升,但仍无法满足对数据一致性要求高的场景,如金融领域。MGR 弥补了这两种复制模式的不足
3)MGR 如何工作的:先将多个节点组成一个复制组,在执行读写(RW)事务时,需通过一致性协议层(Consensus 层)的同意,即读写事务想要提交,必须经过组里“大多数人”(对应 Node 节点)的同意,大多数指同意的节点数需大于(N/2+1),这样才可进行提交,而不是原发起方一个说了算。而针对只读(RO)事务则不需要经过组内同意,直接 COMMIT 即可

二十七、数据库没有备份,没有使用Binlog情况下,如何恢复数据

1、InnoDB 存储引擎的表空间
(1)InnoDB 存储引擎的文件格式是.ibd 文件,数据按照表空间(tablespace)进行存储,分为共享表空间和独立表空间
(2)查看表空间存储方式:mysql > show variables like 'innodb_file_per_table';

(3)共享表空间:InnoDB 存储的表数据都会放到共享表空间,即多个数据表共用一个表空间,同时表空间也会自动分成多个文件存放到磁盘上
优点:单个数据表大小可突破文件系统大小的限制,最大可达到 64TB,即 InnoDB 存储引擎表空间的上限
缺点:多个数据表存放到一起,结构不清晰,不利于数据找回,同时将所有数据和索引都存放到一个文件中,会使共享表空间的文件很大

(4)独立表空间:每个数据表都有自己的物理文件,即 table_name.ibd 的文件,这个文件中保存了数据表中的数据、索引、表的内部数据字典等信息
优点:每张表都相互独立,不会影响其他数据表,存储结构清晰,利于数据恢复,同时数据表还可在不同数据库之间迁移

2、没有做过备份,也没有开启 Binlog ,.ibd 文件损坏了,数据如何找回
说明:
1)使用 Percona Data Recovery Tool for InnoDB 工具,能使用工具修复是因为使用 DELETE 时是逻辑删除。 InnoDB 的页结构,在保存数据行时有个删除标记位,对应的是页结构中的 delete_mask 属性,该属性为 1 时标记了记录已被逻辑删除,实际上并不是真的删除。不过当有新记录插入时,被删除的行记录可能会被覆盖。所以当发生了 DELETE 误删时,要第一时间停止对误删除的表进行更新和写入,及时将.ibd 文件拷贝出来并进行修复
2)如果已开启 Binlog,就可使用闪回工具,如 mysqlbinlog 或 binlog2sql,它们是基于 Binlog 来做的闪回。原理是因为 Binlog 文件本身保存了数据库更新的事件(Event),通过这些事件可以重现数据库所有更新变化,即 Binlog 回滚

找回步骤如下:
1)查看innodb_force_recovery参数当前的值:mysql > show variables like 'innodb_force_recovery';

需要将innodb_force_recovery参数设置为 1,启动数据库。如果数据表不能正常读取,需要调大参数直到能读取数据为止。通常设置为1即可。找到[mysqld]位置,在下面增加一行innodb_force_recovery=1:

说明:
<1> 值默认为0表示不进行强制恢复。当需要强制恢复时,可将innodb_force_recovery设置为 1,表示即使发现了损坏页也可继续让服务运行,这样就可以读取数据表,并对当前损坏的数据表进行分析和备份。
<2> 通常innodb_force_recovery参数设为 1,只要能正常读取数据表即可。但如果参数设为 1 后还无法读取数据表,可以将参数逐一增加,如 2、3 等。一般不需将参数设置到 4 或以上,因为这可能对数据文件造成永久破坏。另当innodb_force_recovery设置为大于 0 时,相当于对 InnoDB 进行了写保护,只能进行 SELECT 读取操作,还是有限制的读取,对 WHERE 条件以及 ORDER BY 都无法进行操作

2)备份数据表:备份数据前,准备一个新的数据表,需使用 MyISAM 存储引擎(mysql > CREATE TABLE 新表(id int) ENGINE=MyISAM;)。原因是InnoDB 存储引擎已经写保护了,无法将数据备份出来。然后将损坏的 InnoDB 数据表备份到新的 MyISAM 数据表中(mysql > INSERT INTO 新表 SELECT * FROM 旧表 LIMIT 99;

3)删除旧表,改名新表:数据备份完成后,删除掉原有损坏的 InnoDB 数据表(mysql > DROP TABLE 旧表;),将新表进行改名(mysql > RENAME TABLE 新表 to 旧表;

4)关闭innodb_force_recovery,并重启数据库:innodb_force_recovery大于 1 时有很多限制,需要将该功能关闭,然后重启数据库,并将数据表的 MyISAM 存储引擎更新为 InnoDB 存储引擎(mysql > ALTER TABLE 新表 engine = InnoDB;

说明:人工恢复损坏的 ibd 文件中的数据,不一定是 100% 找回,而是把正确的数据页中的记录备份出来,尽可能恢复原有的数据表。

二十八、SQL注入

SQL 注入也叫 SQL Injection,指将非法 SQL 命令插入到 URL 或 Web 表单中进行请求,这些请求被服务器认为是正常的 SQL 语句进行执行。即如果要进行 SQL 注入,可以将想要执行的 SQL 代码隐藏在输入的信息中,而机器无法识别出来这些内容是用户信息,还是 SQL 代码,在后台处理过程中,这些输入的 SQL 语句会显现出来并执行,从而导致数据泄露,甚至被更改或删除

二十九、初识Redis

1、 Redis :全称是 REmote DIctionary Server,属于键值(key-value)数据库,使用哈希表存储键值和数据,其中 key 作为唯一标识,且 key 和 value 可以是任何内容,不论是简单的对象还是复杂的对象都可存储。查询效率非常高,根据官方提供的数据,Redis 每秒最多处理的请求可达到 10 万次

2、Redis快的原因
(1)和 SQLite 一样,采用 ANSI C 语言编写,底层代码执行效率高,依赖性低,因为使用 C 语言开发的库没有太多运行时(Runtime)依赖,且系统的兼容性好,稳定性高
(2)基于内存的数据库,可以避免磁盘 I/O,因此 Redis 也称为缓存工具
(3)数据结构简单,采用 Key-Value 方式存储,即使用 Hash 结构进行操作,复杂度为 O(1)
(4)采用单进程单线程模型,避免了上下文切换和不必要的线程之间引起的资源竞争
(5)在技术上采用多路 I/O 复用技术。多路指多个 socket 网络连接,复用指复用同一个线程。采用多路 I/O 复用技术可以在同一个线程中处理多个 I/O 请求,尽量减少网络 I/O 的消耗,提升使用效率

2、Redis的数据类型:Redis 支持字符串、哈希、列表、集合、有序集合等
(1)字符串类型:对应的结构是 key-value
设置某个键的值:set key value
获取某个键的值:get key

(2)哈希(hash)类型:提供了字段和字段值的映射,对应结构是 key-field-value
设置某个键的值:hset key field value
同时将多个 field-value 设置给某个键 key的值:hmset key field value [field value...]
获取某个键的值:hget key field
一次获取某个键的多个 field 字段值:hmget key field[field...]

(3)字符串列表(list):是一个双向链表结构,可以向列表两端添加元素,时间复杂度都为 O(1),同时也可以获取列表中的某个片段
向列表左侧添加元素:LPUSH key value [...]
向列表右侧添加元素:RPUSH key value [...]
获取列表中某一片段的内容:LRANGE key start stop

(4)字符串集合(set):是字符串类型的无序集合,与列表(list)的区别在于集合中的元素是无序的,同时元素不能重复
在集合中添加元素:SADD key member [...]
在集合中删除某元素:SREM key member [...]
获取集合中所有的元素:SMEMBERS key
判断集合中是否存在某个元素:SISMEMBER key member

(5)有序字符串集合(SortedSet,简称 ZSET):理解成集合的升级版。ZSET 是在集合基础上增加了一个分数属性,这个属性在添加修改元素时可以被指定。每次指定后,ZSET 会按照分数来进行自动排序,即在给集合 key 添加 member 时,可以指定 score。
有序集合与列表有一定相似性,如这两种数据类型都是有序,都可获得某一范围的元素。但它俩在数据结构上有很大不同,列表 list 是通过双向链表实现,操作左右两侧的数据时会非常快,而对于中间的数据操作则相对较慢。有序集合采用 hash 表的结构来实现,读取排序在中间部分的数据也会很快。同时有序集合可通过 score 来完成元素位置的调整,但如果想对列表进行元素位置的调整则比较麻烦
1)在有序集合中添加元素和分数:ZADD key score member [...]
如给 heroScore 集合添加下面 5 个英雄的 hp_max 数值:

写成:ZADD heroScore 8341 zhangfei 7107 guanyu 6900 liubei 7516 dianwei 7344 lvbu
2)获取某个元素的分数:ZSCORE key member
如想获取 guanyu 的分数:ZSCORE heroScore guanyu
3)删除一个或多元素:ZREM key member [member ...]
如想删除 guanyu 这个元素:ZREM heroScore guanyu
4)获取某个范围的元素列表。
如果分数从小到大进行排序:ZRANGE key start stop [WITHSCORES]
如果分数从大到小进行排序:ZREVRANGE key start stop [WITHSCORES]
WITHSCORES 是个可选项,如果使用 WITHSCORES 会将分数一同显示出来
如想查询 heroScore 这个有序集合中分数排名前 3 的英雄及数值:ZREVRANGE heroScore 0 2 WITHSCORES

(7)除上述5 种数据类型外,Redis 还支持位图(Bitmaps)数据结构,在 2.8 版本之后,增加了基数统计(HyperLogLog),3.2 版本之后加入了地理空间(Geospatial)以及索引半径查询功能,在 5.0 版本引用了数据流(Streams)数据类型

【MySQL】数据库知识总结相关推荐

  1. 肝了三天的四万字MySQL数据库知识总结

    mysql数据库知识梳理总结 即使再小的帆也能远航~ 一. 目录 数据库介绍 数据库安装 SQL概念 DDL DML操作表中的数据 DQL单表查询 数据库备份和还原 DCL 数据库表的约束 表与表之间 ...

  2. mysql数据库知识解析(一)

    文章目录 1. 数据库的介绍和环境搭建 1.1 数据库介绍 1.1.1 数据存储 1.1.2 理解数据库 1.1.3 MySQL 1.2 环境搭建 2. 数据类型及约束 2.1 SQL介绍&常 ...

  3. MySQL数据库知识汇总

    MySQL总结汇总,整理了包括MySQL数据库的基础知识,SQL优化.事务管理以及一些常见的问题,包含了作为一个Java工程师在面试中需要用到或者可能用到的基础性知识.一来为了加深学习印象,二来为以后 ...

  4. 学习C++项目——mysql 数据库知识学习(关于 mysql 8.0 版以后基础部分学习)

    学习数据库知识 一.思路和学习方法   本文学习于:B站平台UP主 IT 小当家,学习 MySQL 数据库,里面仅仅用于自己学习,进行复现,并加以自己的一些学习过程和理解,如果有侵权会删除.因为 Or ...

  5. Mysql数据库知识总结

    Mysql学习资料参考: MySQL操作笔记(五万字详解)_Coder Xu的博客-CSDN博客 MySQL数据库面试题(2020最新版)_ThinkWon的博客-CSDN博客_mysql数据库面试题 ...

  6. MySQL数据库知识体系

    MySQL为关系型数据库 文章目录 一 数据库介绍 1 相关定义 数据(data) 数据库(database.DB) 数据库管理系统(DataBase Management System , DBMS ...

  7. MySQL数据库知识学习(五)读写分离与分库分表策略

    通过数据库锁及存储引擎的学习,我们知道数据库在数据操作过程中为了保证数据的一致性是会给表或行加锁的,在网站发展的初期,由于没有太多访问量,一般来讲只需要一台服务器就够了,这的操作也不会有什么问题.但随 ...

  8. MYSQL数据库知识大全以及表达式实例

    目录 基本DDL语句-建表语句CREATE TABLE 基本DML语句-INSERT/UPDATE/DELECT 基本DQL语句-SELECT DDL语句-建表语句CREATE TABLE 1.数据库 ...

  9. mysql 9.0创建数据库_数据库基础学习——MySQL数据库知识小结(9)

    1 MySQL 中的约束 1.1约束类型 • 非空约束(not null) • 唯一性约束(unique) • 主键约束(primary key) PK • 外键约束(foreign key) FK ...

  10. 全网最详细,MySQL数据库知识总结,你要的我都有......

    目录:导读 一.数据库基础 1.用户 2.权限 二.数据库基本语法 1.引擎 2.四大SQL语句 3.DDL 4.DML 三.查询基础操作 1.排序语句 2.关键字 3.查询条件 四.约束 1.主键( ...

最新文章

  1. 联合国召开会议讨论“杀手机器人”问题
  2. 汇编环境搭建(vs2010(2012)+masm32)
  3. 把Nginx注册成Windows 系统服务(转载)
  4. 13.JAVA之GUI编程将程序打包jar
  5. win7计算机菜单,教您win7右键菜单设置方法
  6. linux系统用w程序,Linux w命令
  7. altas(ajax)控件(二):悬浮面板控件AlwaysVisibleControl
  8. vue导出excel表格
  9. python 直方图匹配_python库skimage 绘制直方图;绘制累计直方图;实现直方图匹配(histogram matching)...
  10. 淘宝用户api 如何获得App Key和API Secret
  11. 图片太大,怎么压缩图片大小?
  12. 艾永亮:苹果缺乏创新能力?打造超级产品是未来增长的关键
  13. 现代软件工程 怎么教好课 (读书笔记)
  14. Fiddler抓包工具常见功能介绍,还不会的进来看
  15. 自动化测试框架详解【2022】
  16. Synergy v1.10版本跨平台鼠键共享资源
  17. mat 释放_cv :: Mat内存即使在调用release()后也不会释放?
  18. 首席新媒体黎想教程:SEO中的反向链接是什么意思?
  19. 世界最迷人的白色海岸线
  20. HealthKit Swift 教程: workout

热门文章

  1. ngx.print与ngx.say
  2. 一场夜雨的误会?^_^
  3. IDEA出现java: 错误: 不支持发行版本 15
  4. Kafka的高性能设计
  5. LVI-SAM LIS系统 utility.h 代码阅读 附录
  6. 关于重申快件出、入仓录单扫描、问题件处理等操作流程的通知
  7. 直流无刷电机与空心杯电机的区别
  8. C/C++环境搭建(CodeBlocks)(献给不会HelloWorld的童鞋)
  9. JS--你没玩过的激流勇进
  10. (二)GL 简单绘制