《[MySQL技术内幕:SQL编程》读书笔记

《[MySQL技术内幕:SQL编程》读书笔记

2019年3月31日23:12:11

严禁转载!!!

<MySQL技术内幕:SQL编程>这本书是我比较喜欢的一位国内作者姜承尧, 早年在学mysql时就听过姜老师的开源mysql网络视频教程, 记得在视频时总是姜老师姜老师的自称, 感到十分的亲切.

这本书主要是讲mysql在应用编程时如何正确并且更高效的根据业务场景编写相应的sql语句.

从一开始对mysql的历史, 数据库类型, 分支版本, 咋一看, 还挺熟悉的, 这个逻辑顺序不就是当时视频讲课的顺序. 在本章末尾, 还推荐了几款主流的图形化SQL查询分析器.

第二章主要是介绍了各种常用的数据类型. 包括UNSIGNEDZEROFILL要注意的事情, 对于UNSIGNED, 如果没有设置sql_mode=no_unsigned_subtraction, 连个UNSIGNED
数据(小减大)的结果不是正确的负数. ZEROFILL主要是填充占位用的, 在显示的时候会在左边填充0, 实际显示还是实际大小, 只是显示不一样. 然后就是常用的时间日期数据类型了. 这里主要的问题是闰年, 星期, 跨年, 国际化这种需要注意的. datetime 不能有默认值, timestamp可以有默认值. mysql5.6前now, datetime等会截断微秒, 5.6后可以保留微秒(microsecond()函数). 再到字符类型, 字符类型主要是有固定长和变长两种, 变长的再表记录中在额外的头有这个可变数据的长度......当然比价orange人头疼的字符集和比较规则, 这里介绍的还是很清晰的.

第三章是查询处理.查询处理包括了逻辑查询和物理查询. 逻辑查询操作顺序用一图介绍. 然后就是根据操作顺序来对每一个操作展开讲解. 主要是联合, 过滤, 排序, 分组等操作. 我们要有集合的思维来看待关系型数据库.以上操作步骤是逻辑查询的, 真正的查询还要经过物理查询, 经过server层的分析器和优化器,真正构造一条执行查询语句.

第四章是子查询。子查询分为独立子查询相关子查询。独立子查询与外部查询是不相关的, 但是要明确显式定义, 如果没有显示定义子查询的列, 那么就会被mysl的优化器把子查询优化成相关子查询,相关子查询的查找速度与外部查询的匹配次数有关。因此往往就会造成查询慢的原因。这章分析和提出一些办法来如何减少子查询和外部查询的匹配次数,提高效率。第一、增加一个索引提高查询速度。第二、通过派生表来减少子查询与外部查询的匹配次数。接着介绍了EXISTS谓词和其与IN的区别,后面就是介绍派生表了。派生表用好不容易,需要经验丰富。所以平时尽量避免子查询。最后介绍了MariaDB对SEMI JSON的优化。

第五章是联接与集合操作。联接有多种联合方式,针对不同的业务选择不同的联接方式,CROSS JOININNER JOINOUTER JOINNATURAL JOINSTRAIGHT JOINSELF JOIN
NONEQUI JOINSEMI JOIN。然后介绍不同的联接算法,包括Simple Nested-Loops Join算法Block Nested-Loops Join算法Block Nested-Loops Join算法
Block Nested-Loops Join算法Classic Hash Join算法。最后又介绍了集合操作,mysql标准只有UNION DISTINCT和UNION ALL两种,但是可以在这个基础上扩展为EXCEPT和INTERSECT。

第六章是聚合和旋转操作。mysql为我们提供了一些聚合函数,并且有多种聚合,附加聚合,连续聚合等操作,通过聚合可以减少子查询的使用。然后还介绍了开放架构设计的表可以旋转,行-->列,列-->行。这种主要是用于报表输出,直观阅读。最后介绍了CUBE和ROLLUP,主要是配合GROUP BY使用,分组聚合,提高效率。

第七章是游标。从面向过程和面向集合的两个角度来展开讲解,同时给出了游标操作的步骤。但是要慎用游标,不是不用,而是在必须的场合下才使用。游标对于扫描一次表的情况是劣势的,但是如果对于一些面向集合的解决方案所需扫描成本为O(N^2)的情况,使用游标的解决方案只需O(N)。

第八章是事务编程。了解了什么事事务,事务是原子单位,要么都成功,要么都失败回滚。一般事务需要满足ACID。然后介绍了四种隔离级别,隔离级别是存储引擎层面的。InnoDB默认是REPEATABLE READ级别(可重复读),但是有了Next-Key Lock(间隙锁),可像串行化读那样。事务完全安全。接着介绍了事务有关的控制语句和隐式提交的语句。mysql的分布式事务需要XA事务的支持,异构数据库组成分布式事务需要InnoDB的隔离级别是SERIALIZABLE隔离级别。然后引出几个不好的事务编程。这一章节,分析了事务的概念和如何应用事务。

第九章是索引。索引是数据库查询的精华。正是因为有了索引,才能使查询变得更高效,使数据库应用更广泛。这章从数据结构树开始说起,到mysql底层数据结构B+树,到B+树索引的底层实现都分析了,后面还介绍了一下其他的索引实现,T树索引,哈希索引等。总的来说,这章是从底层来分析的,获益良多。

第十章是分区。谨慎选择是否要分区,因为分区并不一定能带来效率的提升的。分区是在索引的基础上对表进行水平分区。这章中介绍了RANGE、LIST、HASH、KEY、 COLUMNS五种主要的分区。


2.数据类型

  • 2.1 尽量不要使用unsigned, 在没有设置 sql_model='no_unsigned_subtraction'选项时, 两个unsigned变量相减等操作会出现结果错误

  • 2.2 zerofill 整型填充, 不足定义的字节数, 会在前面填充0, 实际显示还是实际大小, 只是显示不一样.

  • 2.3 set_model 主要是用来控制mysql的严格度

  • 2.4 datetime 不能有默认值, timestamp可以有默认值

  • 2.5 mysql5.6前now, datetime等会截断微秒, 5.6后可以保留微秒(microsecond()函数)

  • 2.6 now(), datetime(), sleep(), current_timestamp(), sysdate() sysdate返回的是当前函数执行时的时间, now是执行SQL语句的时间

  • 2.7 使用date_format() 不会走索引. do not !!! do not!!! do not!!!

3.查询处理

  • 3.1 查询流程中, 每个操作都产生一个虚拟表, 除了最后一个,其他对用户都是透明的.

  • 3.2查询流程

4.子查询

  • 4.1 子查询必须包含括号

  • 4.2 IN /// ANY/SOME /// ALL /// NOT IN/<>ALL

  • 4.3 独立子查询是不依赖外部查询而运行的子查询。易于调试。

  • 4.4 mysql优化器会对 IN 子句优化, 如果 IN 不是显式的列表定义, IN 子句会被优化成EXITS的相关子查询. (解决办法: 一般是增加多一层子查询, 减少外部查询和子查询的匹配次数)

  • 4.5 相关子查询: 引用了外部查询列的子查询, 即子查询对外部查询的每一行进行计算.

  • 4.6 对相关子查询的处理, 减少子查询与外部查询的匹配次数

错误:

正确:

然而查询速度很慢:

优化: 增加一个索引提高查询速度:

优化: 通过派生表来减少子查询与外部查询的匹配次数:

子查询优化:

  • 索引

  • 派生表

  • 使用连接(JOIN)来代替子查询

  • 4.7 EXISTS谓词

  • 4.7 EXISTS 的输入一般是子查询, 并关联到外部查询, 但不是必须的. 根据子查询返回行, 该谓词返回true或false, 不会返回unknown.

  • 4.8 EXISTS 和 IN 的执行计划是一样的, 但是 NOT EXISTS 和 NOT IN的执行计划是不一样的, 因为有UNKNOWN的存在. NOT EXISTS返回TRUE和FALSE, NOT IN返回FALSE和UNKNOWN.

  • 4.9 派生表是完全的x虚拟表, 并没有也不可能被物理化地具体化

  • 4.10 关于子查询的总结 各种连接语句

5.连接与集合操作

5.1 由于历史原因,出现了新旧查询方法:

  • select * from a, b where a.x = b.x;
  • select * from a inner join b on a.x = b.x;

5.2 JOIN方式

  • CROSS JOIN
  • INNER JOIN
  • OUTER JOIN
  • NATURAL JOIN
  • STRAIGHT JOIN
  • SELF JOIN
  • NONEQUI JOIN
  • SEMI JOIN

逻辑查询的三个步骤:

  • 产生笛卡尔积的虚拟表
  • 按照ON过滤条件进行数据的匹配操作
  • 添加外部行

CROSS JOIN:全表连接,做笛卡尔积, m * n。用于快速生成重读测试数据,作为返回结果集的行号。

INNER JOIN:不会添加外部行。过滤条件在WHERE或ON是一样的,最好是表之间的过滤是在ON,一个表的过滤在WHERE。后面不跟ON,等同于CROSS JOIN。可以使用USING( )来指定根据相同名称列来匹配。

OUTER JOIN:通过OUTER JOIN添加的保留表中存在未找到的匹配数据,对未找到并添加的外部行会用NULL填充。必须配有ON子句。可以使用USING( )来指定根据相同名称列来匹配。

  • LEFT OUTER JOIN:以左表为基准,右表去匹配。
  • RIGHT OUTER JOIN:以右表为基准,左表去匹配。

NATURAL JOIN:等同于INNER JOIN 和USING的组合。将两个表相同名称列进行匹配。

STRAIGHT JOIN:强制险读左表。一般是有经验的DBA用来设定最优路径。

SELF JOIN:同一个表不同实例之间的JOIN操作。读同一个表进行连接必须先指定表别名。主要是解决层次结构问题。

NONEQUI JOIN:包含“等于”运算符之外的运算符。

SEMI JOIN:根据一个表存在的记录去找另外一个表的相关记录。

5.3 多表联接
INNER JOIN多表查询。可以用ON()把过滤条件都放在一起。OUTER JOIN 多表的联接顺序对结果有影响。

5.4 联接算法的历史

  • MySql 5.5版本仅支持Nested-Loops Join算法
  • MySql 5.6开始支持Batched Key Access Joins算法(简称BKA)

5.5 Nested-Loops Join算法
当联接的表上有索引,是非常高效,根据B+树的特性,时间复杂度是O(N),但是如果没有索引,会被视为最坏情况,时间复杂度是O(N^2)。

5.5 Nested-Loops Join算法的分类:

  • Simple Nested-Loops Join算法
  • Block Nested-Loops Join算法

5.6 Simple Nested-Loops Join算法:从一张表中每次读取一条记录,然后将记录与嵌套表中的记录进行比较。

    For each row r in R do 
        lookup r in S index 
            If find s == r 
                Then output the tuple <r, s>

联接两个表都有索引,并且索引高度相同,优化器会选择将记录数最少的表作为外部表,因为内部表示索引扫描,只和索引的高度有关,与记录数量无关。

5.7 Block Nested-Loops Join算法
针对没有索引联接的情况,使用join buffer(联接缓冲)来减少内部循环减少读表的次数。实质是预先一次性读取比如10条记录到内存,减少读取次数来提高效率。

5.8 Block Nested-Loops Join算法
结合索引和group(额外join buffer)。后者可以提交cache命中率。

Classic Hash Join算法
mysql暂时不支持,MariaDb支持。

5.9 集合操作

  • UNION DISTINCT
    • 创建一张临时表,即虚拟表
    • 对这张临时表的列添加唯一索引
    • 将输入的数据插入临时表
    • 返回临时表
  • UNION ALL

5.10 集合操作注意
OUTFILE只能存在于最后一个select语句中,并且虽然在最后面,但是导出的事所有的结果。

UNION DISTINCT
由于有唯一索引,对性能还是影响的,所以在确定没有重复数据时,最好是用UNION ALL。

EXCEPT
找出位于第一个输入中但不位于第二个输入中的行数据。

INTERSECT
返回在两个输入中都出现的行。

6.聚合和旋转操作

  • 6.1 mysql只支持流聚合。

    • 流聚合依赖于获得的存储在GROUP BY列中的数据,如果SQL查询中包含的GROUP BY语句多于一行,流聚合会先根据GROUP BY对行进行排列。
  • 6.2 开放架构是一种用于频繁更改架构的设计模式。对于利用开放架构设计的表,一般使用Pivoting技术来查询。

  • 6.3 已知属性个数,可以用静态Pivoting。

  • 6.4 Pivoting还可以用来做格式化聚合函数,一般用于报表输出,直观。

  • 6.5 Unpivoting,Pivoting的反向操作。把行转为列。

  • 6.6 mysql仅支持CUKE,不支持ROLLUP。ROLLUP是根据维度在数据结果集中进行的聚合操作。ROLLUP的优点是一次可以取得N次GROUP BY的结果,提高查询效率。单个维度没啥优势,第一个维度优势就大了。

  • 6.7 ROLLUP不能喝ORDER BY一起使用,用LIMIT的话阅读性差,没有实际意义。

  • 6.8 CUBE只在层次上对数据聚合,对所有维度进行聚合,具有N维度的列,需要2^N次分组操作。

7游标

  • 7.1 游标不是恶魔,存在即合理。合理利用才是硬道理。

  • 7.2 游标可以在存储过程,函数,触发器和事件中使用。

  • 7.3 游标三个属性:

    • Asensitive:数据库也可以不复制数据集
    • Read Only:不可更新
    • NonScrollable:游标只能向一个方向移动,不能跳过任何行。
  • 7.4 游标的使用步骤:

    • 定义游标
    • 打开定义游标的变量
    • 从游标取得数据
    • 关闭游标
  • 7.5从游标放入数据到变量时,变量的名字不能和列的名字一样,不然会变成NULL。

8.事务编程

  • 8.1 事务概述。事务应该满足ACID。(原子性,一致性,隔离性,持久性)

    • 原子性:每条语句,或者一个事务,当做一个整体,要么成功,要么失败回滚。事务是一个不可分割的单位。
    • 一致性:事务将数据库从一个状态转变为另外一种一致的状态,数据库的完整性约束不会被改变。
    • 隔离性:别称并发控制,可串行化,锁。保证事务提交前其他事务看不见。
    • 持久性:凡是提交了的事务,都应该持久化到磁盘上,即使数据库崩溃,也能通过日志来恢复。
  • 8.2 事务的分类

    • 扁平事务
    • 带保存点的扁平事务
    • 链事务
    • 嵌套事务
    • 分布式事务

扁平事务

- 优点:使用简单,广泛使用。
- 缺点:不能提交事务的一部分,或分步骤提交。

带保存点的扁平事务

定义:除了支持扁平事务外,允许在是事务执行过程中回滚到同一事务中较早的一个状态。保存点同来通知系统记住事务当前的状态,以便以后发生错误,事务能回到该状态。
- 优点:能够灵活的回滚到某个状态,避免回滚到最初状态的消耗。

链事务

多个事务连续操作时,由于保存点是非持久的。带保存点的扁平事务,在系统崩溃时,需要重头开始执行到保存点,而不是从最近的一个保存点开始。

- 优点:多个事务时,保存点更灵活
- 缺点:事务仅能回滚到当前事务的最近保存点。

嵌套事务

不是mysql原生的,可以通过带保存点的扁平事务来模拟。但是要注意各个事务锁的问题。

所有工作由叶子点来完成,只有叶子点具有访问数据,发送消息,获取其他类型的资源。父节点或顶点节点只是起到逻辑控制,何时调用子事务的事情。

分布式事务

在分布式环境下运行的扁平事务。

8.3 事务控制语句

  • START TRANSATION / BEGIN
  • COMMIT
  • ROLLBACK
  • SAVEPOINT xxx
  • RELEASE SAVEPOINT xxx
  • ROLLBACK TO [SAVEPOINT] xxx
  • SET TRANSATION (READ UNCOMMITTED / READ COMMITTED / REPEATABLE READ / SERIALIZABLE)

在命令行下开启事务可以:

BEGIN
 。。。
END

或者

START TRANSATION
 。。。
COMMIT

但是在存储过程中,mysql数据库的分析器会自动的把BEGIN识别为BEGIN...END,所以在存储过程中只能使用START TRANSATION

  • 8.4 隐式提交的SQL语句

执行以下语句自动回提交事务

  • 8.5 事务的隔离级别
    • READ UNCOMMITTED
    • READ COMMITTED
    • REPEATABLE READ
    • SERIALIZABLE

InnoDB存储引擎支持的隔离级别是REPEATABLE READ,使用Next-Key Lock的锁算法,避免了幻读的产生。在这个级别下已经可以完全保证事务的安全性。达到SERIALIZABLE的隔离级别,但是性能比其好。

隔离级别越低,事务请求的锁越少或保持锁的时间约短。

READ COMMITTED事务隔离级别下,除了唯一性约束检查及外键约束的检查需要Gap lock,InnoDB存储引擎不会使用Gap Lock锁算法。(注意,mysql5.1以前,这个隔离级别只能工作在Replication(复制)的二进制日志为Row格式下。mysql5.1后不会了,也可以工作在STATEMENT格式下。)

最好建议是选择Row格式的二进制日志。因为记录的是行的变更,而不是SQL语句。避免出现不同步的现象

  • 8.6 分布式事务编程

分布式事务:允许多个独立的事务资源参与到一个全局的事务中。全局事务中的事务要么全都成功,要么都回滚。在使用分布式事务时,InnoDB的隔离级别必须是SERIALIZABLE的。

InnoDB支持XA事务,并通过XA事务来支持分布式事务。异构分布式数据库可以通过XA事务实现分布式事务。

XA事务:

  • 一个或多个资源管理器(提供访问事务资源的方法)

  • 一个事务管理器(协调参与全局事务中的各个事务)

  • 一个应用程序(定义事务的边界,指定全局事务中的操作)

  • 8.7 不好的事务编程习惯

    • 不要在循环中提交。无论是显式提交还是隐式提交。

    • 不要自动提交。因为不同语言的API的自动提交设置是不一样的,容易出错。

    • 不要使用自动回滚。最好是在程序中控制事务。

  • 8.8 长事务

长事务最好分为批量小事务。因为长事务如果失败,回滚的代价太大了。

9.索引

  • 9.1 缓冲池、顺序读取与随机读取

数据库一般需要持久化,持久化就要和磁盘打交道,因此就会出现访问磁盘的操作。随机访问磁盘是比较慢的,顺序访问会快很多。所以现在基本都有缓冲池的存在。缓冲池作为内存和磁盘的中间件,主要是缓存以页的方式缓存数据页,查询和修改时会先在缓存查看有没有,有就命中,效率就很高了。或者没有的话,先把数据页调出到缓存池中,再修改数据页,然后异步写入磁盘持久化。

  • 9.2 数据结构与算法

二分查找算法:

数据结构是mysql底层数据结构

  • 9.3 B+树

B+树的演变由来:二叉搜索树--->平衡二叉树--->B+树

对mysql的B+数的插入和删除操作会引起分裂和合并的产生,主要是为了维护B+索引树的平衡,不过有些时候可以通过左旋右旋来避免分裂合并。

  • 9.4 B+树索引

索引是存储引擎层面实现的,所以不同的存储引擎底层的数据结构可能是不一样的。

由于B+索引树的高扇出,所以查找速度是很快的。

B+树索引分为:聚集索引辅助索引。区别在与存放的数据内容。

聚集索引:根据主键创建的B+树,聚集索引的叶子存放的是表中的真实数据。非叶子节点存放的是目录项数据(页号等)。

辅助索引:根据索引创建的B+树。叶子节点只存放索引键值和其指向的主键。根据辅助索引查找时需要回表操作。可以存放更多键值,高度一般小于聚集索引。

  • 9.5 Cardinality

什么时候需要添加B+树索引,一般经验是在访问表很少部分行时使用才有意义。高选择性的字段才有意义。

通过show index; 查看Cardinality列的值来判断是否要加对这一列加索引。

Cardinality的统计是在存储引擎层实现的。因为每个存储引擎对B+树的实现是不同的。

Cardinality的统计一般是通过采样完成的。因为Cardinality的统计放生在insert和update操作,然而这两个操作是很频繁的,所以不会实时的更新的。

InnoDB存储引擎每次随机选择8个叶节点进行采样。所以每次查看Cardinality值可能是不相同的。采样过程:

  • 9.6 B+树索引的使用
    别盲目听从,研究业务确定是否需要索引,对哪些列做索引。

联合索引的键数量是多个,不是一个。

联合索引省掉第二个键的排列,所以有时候可以提高查询效率。

覆盖索引:从辅助索引就可以等到查询的记录,而不需要回表操作。大量减少了IO操作。

一般来说,对于诸如(a,b)这类联合索引,一般不可以选择b列来进行查询,但是在统计操作,如果是覆盖索引,优化器会优先选择。

发生隐式转换时,索引失效。

范围查找或JOIN操作,有时会不走索引,而是通过扫描聚集索引,也就是全表扫描。原因:

  • 选取的是整行信息,而覆盖索引是没有包含全部数据信息的,所以只能走全表扫描了。

你够自信可以使用 FORCE INDEX 啊。

mysql支持INDEX HINT(显式告诉优化选择指定索引)。两种情况需要:

  • USING INDEX 只是告诉优化器选择指定的索引,优化器不一定真的会选择。

  • FORCE INDEX 是强制优化器选择指定的索引。

  • 9.7 Multi-Range Read

mysql5.6后才有的。

MMR优化的目的是减少磁盘的随机访问,并且将随机访问转化为顺序访问。

反正开了MMR之后,对效率的提高是吊吊的。

  • 9.8 Index Condition PushDown
    mysql5.6后才有的。

ICP是一种根据索引来查询的优化方式

反正开了ICP之后,对效率的提高是吊吊的。

  • 9.9 T树索引

从mysql5.1开始 NDB Cluster开始使用T树。

T树不将真实数据放在节点,只是存放数据的指针。

T树结构:

T树边界定义:

查找算法:

插入算法:

删除算法:

  • 9.10 哈希索引

InnoDB存储引擎只支持自适应哈希索引(不可以人工干预,对字典类型的查找速度超快),而Memory存储引擎支持哈希索引。

哈希函数是关键。

发生碰撞,解决办法:

  • 链接法
  • 开放式向前探索法(1步跳,迭代n步跳)

查看自适应哈希索引的情况:

哈希索引只能是等值查询。因为哈希函数映射后就是一个值,就是通过比较值来得到对应的槽。

10.分区

  • 10.1 分区概述

分区是表的一种设计模式。正确的分区可以极大提高数据库的查询效率。

分区不是在存储引擎层面实现的,所以并不是Innodb独有的。mysql仅支持水平分区(将同一表中的不同行的记录分配到不同的物理文件中),并且是局部分区索引,一个区中既存放数据又存放索引。

mysql支持一下的分区方法:
- RANGE分区 (指定一个连续范围)
- LIST分区(指定一个离散范围)
- HASH分区(通过自定义函数的返回值来进行分区,返回值不能是负值)
- KEY分区(根据mysql提供的离散函数进行分区)

如果表中存在主键或唯一索引,分区列必须是唯一索引的一部分。

  • 10.2 分区类型

RANGE分区

LIST分区

在INSERT多行数据时, 如果有一行数据插入失败,MyISAM存储引擎会将之前的行数据都插入,但之后的数据不会被插入。但是InnoDB会把全部行插入当做事务,有一条失败就会全部失败,前面插入的行数据会回滚。

HASH分区

KEY分区

  • 10.3 子分区

子分区是在分区的基础上再分区。

  • 10.4 子分区中的NULL值

mysql数据库允许对NULL值分区,但是会把NULL值视为小于任何一个非NULL值。

对RANGE分区

对LIST分区

对HASH分区和KEY分区

  • 10.5 分区和性能

分区并不是一定能带来性能的提高。要合理分区。有时候有主键和索引就可以很快查询到所需数据,不是一定更分区,减少查询的数据量才是更快的。因为索引的搜索是B+树啊。

  • 10.6 在表和区间交换数据
posted @ 2019-03-31 23:14  那一抹风  阅读(1002)  评论(0编辑  收藏  举报