高性能mysql第三版读书笔记

数据库 waitig 407℃ 百度已收录 0评论

第1章 MySQL 架构与历史

MySQL最重要、最与众不同的特性是它的存储引擎架构,这种架构的设计将查询处理(Query Processing)以及其他系统任务(Server Task)和数据的存储/提取相分离。这种处理和存储分离的设计可以在使用时根据性能、特性,以及其他需求来选择数据存储的方式。

1.1 MySQL逻辑架构

image

1.1.1 连接管理与安全性

每个客户端连接都会在服务器进程中拥有一个线程,这个连接的查询只会在这个单独的线程中执行。服务器会负责缓存线程,因此不需要为每一个新建的连接创建或者销毁线程(也可以使用线程池)。当客户端(应用)连接到MySQL服务器时,服务器需要对其进行认证。

1.1.2 优化与执行

MySQL会解析查询,并创建内部数据结构(解析树)然后对其进行各种优化,包括重写查询、决定表的读取顺序,以及选择合适的索引等。对于select语句,在解析查询之前,服务器会先检查查询缓存,如果有则直接返回查询缓存中的结果集。

1.2 并发控制

MySQL在两个层面控制并发:服务器层与存储引擎层。

每种MySQL存储引擎都可以实现自己的锁策略和锁粒度。

1.2.1 读写锁

在处理并发读或者写时,可以通过实现一个由两种类型的锁组成的锁系统来解决问题。这两种类型的锁通常被称为共享锁和排它锁,也叫读锁和写锁。读锁是共享的,或者说是互相不阻塞的。写锁则是排他的,也就是说一个写锁会阻塞其他的读锁和写锁。

1.2.2 锁粒度

表锁:表锁是MySQL中最基本的锁策略,并且是开销最小的策略。服务器会为诸如ALTER TABLE之类的语句使用表锁,而忽略存储引擎的锁机制。

行级锁:行级锁可以最大程度地支持并发处理(同时也带来了最大的锁开销)。InnoDB和XtraDB,以及其他一些存储引擎中实现了行级锁。行级锁只在存储引擎层实现,而MySQL服务器层没有实现。服务器层完全不了解存储引擎中的锁实现。

1.3 事务

事务就是一组原子性的SQL查询,或者说一个独立的工作单元。事务内的语句,要么全部执行成功,要么全部执行失败。

ACID表示原子性(atomicity)、一致性(consistency)、隔离性(isolation)和持久性(durability)。

image

1.3.1 隔离级别

image

image

image

1.3.2 死锁

数据库系统实现了各种死锁检测和死锁超时机制。死锁发生以后,只有部分或者完全回滚其中一个事务,才能打破死锁。对于事务型的系统,这是无法避免的,所以应用程序在设计时必须考虑如何处理死锁。

1.3.3 事务日志

事务日志持久以后,内存中被修改的数据在后台可以慢慢地刷回到磁盘。目前大多数存储引擎都是这样实现的,我们通常称之为预写式日志,修改数据需要写两次磁盘。

1.3.4 MySQL中的事务

MySQL提供了两种事务型的存储引擎:InnoDB和NDB Cluster。

MySQL默认采用自动提交模式。也就是说,如果不显示地开始一个事务,则每个查询都被当作一个事务执行提交操作。

修改AUTOCOMMIT对非事务型的表,比如MyISAM或者内存表,不会有任何影响。对这类表来说,没有COMMIT或者ROLLBACK的概念。

MySQL服务器层不管理事务,事务是由下层的存储引擎实现的。如果在事务中混合使用了事务型和非事务型的表(例如InnoDB和MyISAM表),在正常提交的情况下不会有什么问题,但是如果该事务需要回滚,非事务型的表上的变更就无法撤销。

1.4 多版本并发控制

MySQL的大多数事务型存储引擎实现的都不是简单的行级锁。基于提升并发性能的考虑,它们一般都同时实现了多版本并发控制(MVCC)。

InnoDB的MVCC,是通过在每行记录后面保存两个隐藏的列来实现的。这两个列一个保存了行的创建时间,一个保存了行的过期时间(或删除时间)。当然存储的并不是实际的时间值,而是系统版本号。每开始一个新的事务,系统版本号都会自动递增。事务开始时刻的版本号会作为事务的版本号,用来和查询到的每行记录的版本号进行比较。下面看一下再REPEATABLE READ隔离级别下,MVCC具体是如何操作的:

image

MVCC只在REPEATABLE READ和READ COMMITTED两个隔离级别下工作。

1.5 MySQL的存储引擎

1.5.1 InnoDB存储引擎

是MySQL的默认事务型引擎,最广泛使用的引擎。被设计来处理大量的短期事务,短期事务大部分情况下是正常提交的,很少会被回滚。InnoDB的性能和自动崩溃恢复特性,使得它再非事务型的需求中也很流行。

InnoDB的数据存储在表空间中,表空间是由InnoDB管理的一个黑盒子。

InnoDB采用MVCC来支持高并发,并且实现了四个标准的隔离级别。其默认级别是REPEATABLE READ(可重复读),并且通过间隙锁策略防止幻读的出现。间隙锁使得InnoDB不仅仅锁定查询涉及的行,还会对索引中的间隙进行锁定,以防止幻影行的插入。

InnoDB表是基于聚簇索引建立的。不过它的二级索引中必须包含主键列,所以如果主键列很大的话,其他的所有索引都会很大。

1.5.2 MyISAM存储引擎

MyISAM提供了大量的特性,包括全文索引、压缩、空间函数等,但它不支持事务和行级锁,而且有一个毫无疑问的缺陷就是崩溃后无法安全恢复。

MyISAM对整张表加锁,而不是针对行。

延迟更新索引键策略,不会立刻将修改的索引数据写入磁盘,而是先存在内存中的键缓冲区。

MyISAM压缩表:压缩表不能修改(除非先解压缩),压缩表的索引也是只读的。

1.5.3 MySQL內建的其他存储引擎

见书中。

1.5.6 转换表的引擎

第一种方法:

ALTER TABLE mytable ENGINE = InnoDB;

缺点是执行时间长,会按行将数据从原表复制到一张新的表中,消耗很大的I/O,且会对原表上读锁。

第二种方法:使用mysqldump工具将数据导出到文件,然后修改文件中CREATE TABLE语句的存储引擎选项。

第三种方法:创建一个新表,然后使用INSETR…SELECT语法导入数据

CREATE TABLE innodb_table LIKE myisam_table;
ALTER TABLE innodb_table ENGINE = InnoDB;
INSERT INTO innodb_table SELECT * FROM myisam_table

如果原表数据量很大,可以使用事务分批导入,也可以对原表加锁,确保数据一致。

第2章 MySQL 基准测试

2.4基准测试工具

2.4.1集成式测试工具

ab、http_load、JMeter

2.4.2单组件式测试工具

mysqlslap、MySQL Benchmark Suite(sql-bench)、Percona’s TPCC-MySQL Toll、sysbench

以后如果有需要测试时,再查看此章

第3章 服务器性能剖析

3.1 性能优化简介

我们将性能定义为完成某件任务所需要的时间度量,换句话说,性能即响应时间。我们假设性能优化就是在一定的工作负载下尽可能地降低响应时间。

3.3 剖析MySQL查询

3.3.2 剖析单条查询

使用SHOW PROFILE

默认是禁用的,但可以通过服务器变量在会话(连接)级别动态地修改。

mysql>SET profiling = 1;

然后,在服务器上执行的所有语句,都会测量其耗费的时间和其他一些查询执行状态变更相关的数据。使用示例:

mysql> SHOW PROFILES;

上面语句会列出查询的响应时间,如果要查询某一条查询的细粒度的信息,可以这样:

mysql> SHOW PROFILE FOR QUERY 1;

如果想按照消耗时间排序,可以这样:

set @query_id = 1;
SELECT STATE,SUM(DURATION) AS Total_R,
    ROUND(
        100 * SUM(DURATION) /
        (SELECT SUM(DURATION)
        FROM INFORMATION_SCHEMA.PROFILING
        WHERE QUERY_ID = @query_id
       ),2) AS Pct_R,
    COUNT(*) AS Calls,
    SUM(DURATION) / COUNT(*) AS "R/Call"
FROM INFORMATION_SCHEMA.PROFILING
WHERE QUERY_ID = @query_id
GROUP BY STATE
ORDER BY Total_R DESC;
SHOW STATUS

如果执行SHOW GLOBAL STATUS,则可以查看服务器级别的从服务器启动时开始计算的查询次数统计。

SHOW STATUS的大部分结果都只是一个计数器,可以显示某些活动如读索引的频繁程度,但无法给出消耗了多少时间。

示例如下:

FLUSH STATUS;
SELECT * FROM mytable;
SHOW STATUS WHERE Variable_name LIKE 'Handler%' OR Variable_name LIKE 'Created%';

EXPLAIN是通过估计得到的结果,而通过计数器则是实际的测量结果。

3.4 诊断间歇性问题

间歇性问题比如系统偶尔停顿或者慢查询,很难诊断。

3.4.1 单条查询问题还是服务器问题

使用SHOW GLOBAL STATUS

这个方法实际就是以较高的频率比如每秒一次SHOW GLOBAL STATUS命令捕获数据,问题出现时,可以通过某些计数器(比如Threads_running、Threads_connected、Questions和Queries)的“尖刺”或“凹陷”来发现。

使用SHOW PROCESSLIST

这个方法是通过不停地捕获SHOW PROCESSLIST的输出,来观察是否有大量线程处于不正常的状态或者有其他不正常的特征。

这里只做简单的记录,真正需要的时候,可以再到书里查看一下相关脚本的策略等。

第4章 Schema 与数据类型优化

4.1 选择优化的数据类型

更小的通常更好

一般情况下,应该尽量使用可以正确存储数据的最小数据类型。更小的数据类型通常更快,因为它们占用更少的磁盘、内存和CPU缓存,并且处理时需要的CPU周期也更少。

简单就好

简单数据类型的操作通常需要更少的CPU周期

尽量避免NULL

通常情况下最好指定列为NOT NULL,除非真的需要存储NULL值

4.1.1 整数类型

有两种类型的数字:整数和实数。如果存储整数,可以使用这几种整数类型:TINYINT,SMALLINT,MEDIUMINT,INT,BIGINT。分别使用8,16,24,32,64位存储空间。整数类型有可选的UNSIGNED属性,表示不允许负值,这大致可以使正数的上限提高一倍。
你的选择决定MySQL是怎么在内存和磁盘中保存数据的。然而,整数计算一般使用64位的BIGINT整数,即使在32位环境也是如此。
MySQL可以为整数类型指定宽度,例如INT(11),对大多数应用这是没有意义的:它不会限制值的合法范围,只是规定了MySQL的一些交互工具用来显示字符的个数。对于存储和计算来说,INT(1)和INT(20)是相同的。

4.1.2 实数类型

实数是带有小数部分的数字。FLOAT和DOUBLE类型支持使用标准的浮点运算进行近似计算。DECIMAL类型用于存储精确的小数。
浮点和DECIMAL类型都可以指定精度。对于DECIMAL列,可以指定小数点前后所允许的最大位数。
因为需要额外的空间和计算开销,所以应该尽量只在对小数进行精确计算时才使用DECIMAL——例如存储财务数据。但在数据量比较大的时候,可以考虑使用BIGINT代替DECIMAL,将需要存储的货币单位根据小数的位数乘以相应的倍数即可。

4.1.3 字符串类型

VARCHAR和CHAR是两种最主要的字符串类型,下面的描述假设使用的存储引擎是InnoDB或MyISAM。

VARCHAR

VARCHAR类型用于存储可变长字符串,是最常见的字符串数据类型。它比定长类型更节省空间,因为它仅使用必要的空间(例如越短的字符串使用空间越少)。但是VARCHAR需要使用1或2个额外字节记录字符串的长度。
VARCHAR节省了存储空间,所以对性能也有帮助。但是由于行是变长的,在UPDATE时可能使行变得比原来更长,这就导致需要做额外的工作。
下面这些情况使用VARCHAR是合适的:字符串列的最大长度比平均长度大很多;列的更新很少,所以碎片不是问题;使用了像UTF-8这样复杂的字符集,每个字符都使用不同的字节数进行存储。

CHAR

CHAR类型是定长的:MySQL总是根据定义的字符串长度分配足够的空间。CHAR适合存储很短的字符串,或者所有值都接近同一个长度。对于经常变更的数据,CHAR也比VARCHAR更好,因为定长的CHAR类型不容易产生碎片。对于非常短的列,CHAR比VARCHAR在存储空间上也更有效率(不需要额外记录字符串长度)。

使用VARCHAR(5)和VARCHAR(200)存储’hello’的空间开销是一样的,但是更长的列会消耗更多的内存,因为MySQL通常会分配固定大小的内存块来保存内部值。尤其是使用内存临时表进行排序或操作时会特别糟糕。在利用磁盘临时表进行排序时也同样糟糕。

BINARY和VARBINARY

与CHAR和VARCHAR类似的类型还有BINARY和VARBINARY,它们存储的是二进制字符串,存储的是字节码而不是字符。二进制比较的优势并不仅仅体现在大小写敏感上,MySQL比较BINARY字符串时,每次按一个字节,并且根据该字节的数值进行比较。因此,二进制比较比字符比较简单很多,所以也就更快。

BLOB和TEXT类型

BLOG和TEXT都是为存储很大的数据而设计的字符串数据类型,分别采用二进制和字符方式存储。实际上,它们分别属于两组不同的数据类型家族:字符类型是TINYTEXT,SMALLTEXT,TEXT,MEDIUMTEXT,LONGTEXT;对应的二进制类型是TINYBLOB,SMALLBLOB,BLOB,MEDIUMBLOB,LONGBLOB。BLOB是SMALLBLOB的同义词,TEXT是SMALLTEXT的同义词。与其他类型不同,MysQL把每个BLOB和TEXT值当做一个独立的对象处理。

使用枚举(ENUM)代替字符串类型

有时候可以使用枚举列代替常用的字符串类型。MySQL在内部会将每个值再列表中的位置保存为整数,并且在表的.frm文件中保存“数字-字符串”映射关系的“查找表”。枚举字段是按照内部存储的整数而不是定义的字符串进行排序的。枚举最不好的地方是,字符串列表是固定的,添加或删除字符串必须使用ALTER TABLE。

4.1.4 日期和时间类型

MySQL可以使用许多类型来保存日期和时间值,例如YEAR和DATE。MySQL能存储的最小时间粒度为秒。

DATETIME

这个类型能保存大范围的值,从1001年到9999年,精度为秒,它使用8个字节的存储空间。

TIMESTAMP

TIMESTAMP类型保存了从1970年1月1日午夜(格林尼治标准时间)以来的秒数,它和UNIX时间戳相同。TIMESTAMP只使用4个字节的存储空间,因此它的范围比DATETIME小得多:只能表示从1970年到2038年。

如果需要存储比秒更小粒度的日期和时间值怎么办?MySQL目前没有提供合适的数据类型,但是可以使用自己的存储格式:可以使用BIGINT类型存储微妙级别的时间戳,或使用DOUBLE存储秒之后的小数部分。

4.1.5 位数据类型

BIT

可以使用BIT列在一列中存储一个或多个true/false值。MySQL把BIT当做字符串处理,而不是数字类型。然而,在数字上下文的场景中检索时,结果将是位字符串转换成的数字。比如一个值b’00111001’在数字上下文得到的是二进制值的57,而在直接检索时得到的是ASCII码为57的字符“9”。令人费解,所以,对于大部分应用,最好避免使用这种类型。

SET

如果需要保存很多true/false值,可以考虑合并这些列到一个SET数据类型,它在MySQL内部是以一系列打包的位的集合来表示的。

在整数列上进行按位操作

一种替代SET的方式是使用一个整数包装一系列的位。一个包装位的应用的例子是保存权限的访问控制列表(ACL)。

4.1.6 选择标识符

选择标识列的类型时,不仅仅需要考虑存储类型,还需要考虑MySQL对这种类型怎么执行计算和比较。一旦选定了一种类型,要确保在所有关联表中都使用同样的类型。类型之间需要精确匹配,包括像UNSIGNED这样的属性。混用不同数据类型可能导致性能问题,即使没有性能影响,在比较操作时隐式类型转换也可能导致很难发现的错误。在可以满足值的范围的需求,并且预留未来增长空间的前提下,应该选择最小的数据类型。

整数类型

整数通常是标识列最好的选择,因为它们很快并且可以使用AUTO_INCREMENT

ENUM和SET类型

对于标识列来说,ENUM和SET类型通常是一个糟糕的选择。ENUM和SET列适合存储固定信息,例如有序的状态、产品类型、人的性别。

字符串类型

如果可能,应该避免使用字符串类型作为标识列,因为它们很消耗空间,并且通常比数字类型慢。

4.1.7 特殊类型数据

某些类型的数据并不直接与内置类型一样。低于秒级精度的时间戳就是一个例子。另一个例子是IPv4地址,应该使用无符号整数存储IP地址。使用INET_ATON()和INET_NTOA()函数在字符串和数字两种表示方法之间转换

4.2 MySQL schema设计中的陷阱

太多的列

MySQL的存储引擎API工作时需要在服务器层和存储引擎层之间通过行缓冲格式拷贝数据,然后在服务器层将缓冲内容解码成各个列。从行缓冲中将编码过的列转换成行数据结构的操作代价是非常高的。

太多的关联

一个粗略的经验法则,如果希望查询执行得快且并发性好,单个查询最好在12个表以内做关联。

枚举

注意防止过度使用枚举

变相的枚举

枚举列允许在列中存储一组定义值中的单个值,集合(SET)列则允许在列中存储一组定义值中的一个或多个值,有时候可能容易导致混乱。

NULL值

尽量避免使用null,如果需要存空值时,可以使用某个特殊值代替。但是在确实需要时也不要害怕使用null。

4.3 范式和反范式

4.3.1 范式的优缺点

优点:

范式化的更新操作通常比反范式化要快。
当数据较好地范式化时,就只有很少或者没有重复数据,所以只需要修改更少的数据。
范式化的表通常更小,可以更好地放在内存里,所以执行操作会更快。
很少有多余的数据意味着检索列表数据时更少需要DISTINCT或者GROUP BY语句。

缺点:范式化设计的schema的缺点是通常需要关联,这不但代价昂贵,也可能使一些索引策略无效。例如,范式化可能将列存放在不同的表中,而这些列如果在一个表中本可以属于同一个索引。

4.3.2 反范式的优缺点

反范式化的schema因为所有数据都在一张表中,可以很好地避免关联。如果不需要关联表,则对大部分查询最差的情况——即使表没有使用索引——是全表扫描。单独的表也能使用更有效的索引策略。

4.3.3 混用范式化和反范式化

在实际应用中经常需要混用

4.4 缓存表和汇总表

我们用术语“缓存表”来表示存储那些可以比较简单地从schema其他表获取(但是每次获取的速度比较慢)数据的表(例如,逻辑上冗余的数据)。而术语“汇总表”(或“累积表”)时,则保存的是使用GROUP BY语句聚合数据的表(例如,数据不是逻辑上冗余的)。

4.4.1 物化视图

物化视图实际上是预先计算并且存储在磁盘上的表,可以通过各种各样的策略刷新和更新。

4.4.2 计数器表

可以用这种表缓存一个用户的朋友数、文件下载次数等。创建一张独立的表存储计数器通常是个好主意,这样可使计数器表小且快。要获得更高的并发性能,也可以将计数器保存在多行中,每次随机选择一行进行更新,查询时聚合所有行。

4.5 加快ALTER TABLE操作的速度

MySQL的ALTER TABLE操作的性能对大表来说是个大问题。MySQL执行大部分修改表结构操作的方法是用新的结构创建一个空表,从旧表中查出所有数据插入新表,然后删除旧表。一般而言,大部分ALTER TABLE操作将导致MySQL服务中断。
常见场景中能使用的技巧只有两种:一种是先在一台不提供服务的机器上执行ALTER TABLE操作,然后和提供服务的主库进行切换;另外一种技巧是“影子拷贝”,即用要求的表结构创建一张和源表无关的新表,然后通过重命名和删表操作交换两张表。
但也不是所有ALTER TABLE操作都会引起表重建,例如下面例子:

ALTER TABLE mytable MODIFY COLUMN mycolumn TINYINT(3);  ## 慢,需要重建表
ALTER TABLE mytable ALTER COLUMN mycolumn TINYINT(3);   ## 快,不需要重建表,而是直接修改了.frm文件

4.5.1 只修改.frm文件

下面这些操作是有可能不需要重建表的:

移除(不是增加)一个列的AUTO_INCREMENT属性
增加、移除、或更改ENUM和SET常量。如果移除的是已经有行数据用到其值的常量,查询将返回空串。

基本的技术是为想要的表结构创建一个新的.frm文件,然后用它替换旧的,像下面这样:

1、创建一张有相同结构的空表,并进行所需要的修改
2、执行FLUSH TABLES WITH READ LOCK。这将会关闭所有正在使用的表,并且禁止任何表被打开
3、交换.frm文件
4、执行UNLOCK TABLES来释放第2步的读锁

4.5.2 快速创建MyISAM索引

为了高效载入数据到MyISAM表,有个常用的技巧是先禁用索引、载入数据,再重新启用索引:

ALTER TABLE mytable DISABLE KEYS;
-- load the data
ALTER TABLE mytable ENABLE KEYS;

这个办法对唯一索引无效,因为DISABLE KEYS只对非唯一索引有效。MyISAM会在内存中构造唯一索引,并且为载入的每一行检查唯一性。

现代版本的InnoDB中有个类似的技巧:先删除所有的非唯一索引,然后增加新的列,最后重新创建删除掉的索引。

用上一节修改.frm文件的方法也可以:

1、用需要的表结构创建一张表,但是不包括索引
2、载入数据到表中以构建.MYD文件
3、按照需要的结构创建另外一张空表,这次要包含索引。这会创建需要的.frm和.MYI文件
4、获取读锁并刷新表
5、重命名第二张表的.frm和.MYI文件,让MySQL认为是第一张表的文件
6、释放读锁
7、使用REPAIR TABLE来重建表的索引。该操作会通过排序来构建所有索引,包括唯一索引

第5章 创建高性能的索引

5.1 索引基础

在MySQL中,存储引擎在查找时首先在索引中找到对应值,然后根据匹配的索引记录找到对应的数据行。

5.1.1 索引的类型

在MySQL中,索引是在存储引擎层而不是服务器层实现的。

B-Tree索引

谈论索引时,如果没有特别指明类型,那多半说的是B-Tree索引,它使用B-Tree数据结构来存储数据(InnoDB也是使用这个)。B-Tree通常意味着所有的值都是按顺序存储的,并且每一个叶子页到根的距离相同。B-Tree对索引列是顺序组织存储的,所以很适合查找范围数据。

InnoDB索引的工作方式:

image

假如有表People,建立了一个索引key(last_name,first_name,dob),则索引的组织方式如下:

image

注意,索引对多个值进行排序的依据是CREATE TABLE语句中定义索引时列的顺序。

使用B-Tree索引的查询类型
全值匹配

全值匹配指的是和索引中的所有列进行匹配

匹配最左前缀

前面提到的索引可用于查找所有姓为Allen的人,即只使用索引的第一列

匹配列前缀

也可以只匹配某一列的值的开头部分。例如前面索引可用于查找所有以J开头的姓的人。这里也只使用了索引的第一列。

匹配范围值

例如前面索引可用于查找姓在Allen和Barry之间的人。这里也只使用了索引的第一列

精确匹配某一列并范围匹配另外一列

例如前面索引可用于查找姓为Allen,并且名字是以字母K开头的人

只访问索引的查询

B-Tree通常可以支持“只访问索引的查询”,即查询只需要访问索引,而无须访问数据行

因为索引树种的节点是有序的,所以除了按值查找以外,索引还可以用于查询中的ORDER BY操作(按顺序查找

下面是一些关于B-Tree索引的限制:

如果不是按照索引的最左列开始查找,则无法使用索引
查询时不能跳过索引中的列,即不能只使用索引中的第一列和第三列
如果查询中有某个列的范围查询,则其右边所有列都无法使用索引优化查询
哈希索引

哈希索引基于哈希表实现,只有精确匹配索引所有列的查询才有效。对于每一行数据,存储引擎都会对所有的索引列计算一个哈希码。如果多个列的哈希值相同,索引会以链表的方式存放多个记录指针到同一个哈希条目中。在MySQL中,只有Memory引擎显式支持哈希索引。

哈希索引的限制:

哈希索引只包含哈希值和行指针,而不存储字段值,不能使用索引中的值避免读取行
哈希索引数据并不是按照索引值顺序存储的,所以也就无法用于排序
哈希索引也不支持部分索引列匹配查找,因为哈希索引始终是使用索引列的全部内容来计算哈希值的
哈希索引只支持等值比较查询,包括=、IN()、<=>。不支持任何范围查询,例如WHERE price > 100
访问哈希索引的数据非常快,除非有很多哈希冲突,这个时候会遍历链表
如果哈希冲突很多的话,一些索引维护操作的代价也会很高。例如当删除某一【索引的哈希冲突很多的】行时,需要遍历该链表来删除相应数据

InnoDB引擎有一个特殊的功能叫做“自适应哈希索引”。当InnoDB注意到某些索引值被使用得非常频繁时,会在内存中基于B-Tree索引之上再创建一个哈希索引。

创建自定义哈希索引:如果存储引擎不支持哈希索引,则可以模拟像InnoDB一样创建哈希索引,思路为在B-Tree基础上创建一个伪哈希索引的列,查找时带上这个列的hash值作为条件

空间数据索引(R-Tree)

MyISAM表支持空间索引,可以用作地理数据存储

全文索引

全文索引是一种特殊类型的索引,它查找的是文本中的关键词,而不是直接比较索引中的值。全文索引适用于MATCH AGAINST操作,而不是普通的WHERE条件操作

5.2 索引的优点

索引可以让服务器快速地定位到表的指定位置。

最常见的B-Tree索引,按照顺序存储数据,所以可以用来做ORDER BY和GROUP BY操作。因为索引中存储了实际的列值,所以某些查询只使用索引就能够完成全部查询。

总结下来索引有如下三个优点:

1、索引大大减少了服务器需要扫描的数据量
2、索引可以帮助服务器避免排序和临时表
3、索引可以将随机I/O变为顺序I/O

对于非常小的表,大部分情况下简单的全表扫描更高效。对于中到大型的表,索引就非常有效。但对于特大型的表,建立和使用索引的代价将随之增长。如果表的数量特别多,可以建立一个元数据信息表。

5.3 高性能的索引策略

5.3.1 独立的列

“独立的列”是指索引列不能是表达式的一部分,也不能是函数的参数。例如下面这个查询无法使用actor_id列的索引:

SELECT actor_id FROM mytable WHERE actor_id +1 = 5;

我们应该养成简化WHERE条件的习惯,始终将索引列单独放在比较符号的一侧。

5.3.2 前缀索引和索引选择性

有时候需要索引很长的字符串,这会让索引变得大且慢。通常可以索引开始的部分字符,这样可以大大节约索引空间,从而提高索引效率。但这样也会降低索引的选择性。索引的选择性是指,不重复的索引值(也称为基数)和数据表的记录总数(#T)的比值,范围从1/#T到1之间。索引的选择性越高则查询效率越高。唯一索引的选择性是1,这是最好的索引选择性,性能也是最好的。
为了决定前缀的合适长度,需要找到最常见的值出现的次数,然后和最常见的前缀列表进行比较,如果次数接近了,则选择性会比较高。计算合适的前缀长度的另一个办法就是计算完整列的选择性,并使前缀的选择性接近于完整列的选择性。
下面语句演示如何创建前缀索引:

ALTER TABLE mytable ADD KEY (city(7));

有时候后缀索引也有用途,MySQL原生不支持,但是可以把字符串反转后存储,并基于此建立前缀索引。

5.3.3 多列索引

在多个列上建立独立的单独索引大部分情况下并不能提高MySQL的查询性能。MySQL.0和更新版本引入了一种叫“索引合并”的策略,一定程度上可以使用表上的多个单列索引来定位指定的行。

5.3.4 选择合适的索引列顺序

正确的顺序依赖于使用该索引的查询,并且同时需要考虑如何更好地满足排序和分组的需要。当不需要考虑排序和分组时,将选择性最高的列放在前面通常是很好的。

5.3.5 聚簇索引

聚簇索引并不是一种单独的索引类型,而是一种数据存储方式。InnoDB的聚簇索引实际上在同一个结构中保存了B-Tree索引和数据行。当表有聚簇索引时,它的数据行实际上存放在索引的叶子页中。因为无法同时把数据行存放在两个不同的地方,所以一个表只能有一个聚簇索引。
InnoDB通过主键聚集数据。如果没有定义主键,InnoDB会选择一个唯一的非空索引代替。如果没有这样的索引,会隐式定义一个主键来作为聚簇索引。

聚簇索引优点:

可以把相关数据保存在一起
数据访问更快
使用覆盖索引扫描的查询可以直接使用页节点中的主键值

聚簇索引的缺点:

聚簇数据最大限度地提高了I/O密集型应用的性能,但如果全部数据在内存中,访问顺序就不那么重要了,聚簇索引也就没什么优势了
插入速度严重依赖于插入顺序
更新聚簇索引列的代价很高,因为会强制InnoDB将每个被更新的行移动到新的位置
基于聚簇索引的表在插入新行,或者主键被更新需要移动的时候,可能面临“页分裂”的问题
聚簇索引可能导致全表扫描变慢,尤其是行比较稀疏,或者由于页分裂导致数据存储不连贯的时候
二级索引可能比想象中更大,因为在二级索引的叶子节点包含了引用行的主键列
二级索引访问需要两次索引查找,而不是一次

下面是聚簇和非聚簇表存储方式对比:

image

5.3.6 覆盖索引

如果一个索引包含(或者说覆盖)所有需要查询的字段的值,我们就称之为“覆盖索引”。
不是所有类型的索引都可以成为覆盖索引。覆盖索引必须要存储索引列的值,而哈希索引、空间索引和全文索引等都不存储索引列的值,所以MySQL只能使用B-Tree索引做覆盖索引。

5.3.7 使用索引扫描来做排序

如果索引不能覆盖查所需的全部列,就不得不每扫描一条索引记录就都回表查询一次对应的行。这基本都是随机I/O,因此按索引顺序读取数据的速度通常要比顺序地全表扫描慢。
MySQL可以使用同一个索引既满足排序,又用于查找行。因此,如果可能,设计索引时应该尽可能地同时满足这两种任务。
只有当索引的列顺序和ORDER BY子句的顺序完全一致,并且所有列的排序方向都一样时,MySQL才能够使用索引来对结果做排序。如果需要关联多张表,则只有当ORDER BY子句引用的字段全部为第一个表时才能使用索引做排序。

5.3.8 压缩(前缀压缩)索引

MyISAM使用前缀压缩来减小索引的大小,从而让更多的索引可以放入内存,默认只压缩字符串。压缩方式是,先完全保存索引块中的第一个值,然后将其他值和第一个值进行比较得到相同前缀的字节数和剩余的不同后缀部分,把这部分存储起来即可。因为每个值的压缩前缀都依赖前面的值,所以MyISAM查找时无法使用二分查找而只能从头开始扫描。如果是倒叙扫描效率更低。

5.3.9 冗余和重复索引

重复索引是指在相同的列上按照相同的顺序创建的相同类型的索引。冗余索引和重复索引有一些不同,如果创建了索引(A,B),再创建索引(A)就是冗余索引,因为这只是前一个索引的前缀索引。
重复索引要删除,冗余索引看情况,一般也要删除,除非是出于性能方面的考虑(比如如果扩展已有的索引会导致其变得太大)

5.3.10 未使用的索引

这种索引建议删除

5.3.11 索引和锁

索引可以让查询锁定更少的行。

5.4 索引案例学习

5.4.1 支持多种过滤条件

在某个例子中,将(sec,country)列作为索引。sex的选择性很低,但是只有两个值,如果某个查询不限制性别,可以通过在查询条件中新增AND SEX IN(‘M’,’F’)来让MySQL使用该索引。

尽可能将需要做范围查询的列放到索引的后面,以便优化器能使用尽可能多的索引列。

5.4.2 避免多个范围条件

如果有多个范围查询,我们可以将其中的一个范围查询转换为一个简单的等值比较。

5.4.3 优化排序

下面这个分页查询会很慢:

SELECT cols FROM mytable WHERE sex='m' ORDER BY rating LIMIT 100000, 10;

优化这类索引的一个比较好的策略是使用延迟关联,通过使用覆盖索引查询返回需要的主键,再跟进这些主键关联原表获得需要的行,这样可以减少MySQL扫描那些需要丢弃的行数,示例如下:

SELECT cols FROM mytable INNER JOIN(
    SELECT <primary key cols> FROM mytable
    WHERE x.sex='m' ORDER BY rating LIMIT 100000, 10
) AS x USING(<primary key cols>);

5.5 维护索引和表

5.5.1 找到并修复损坏的表

CHECK TABLE 检查是否发生了表损坏
REPAIR TABLE 命令来修复损坏的表
ALTER TABLE innodb_tb1 ENGINE=INNODB; 可以用来重建表

5.5.2 更新索引统计信息

ANALYZE TABLE 重新生成统计信息
使用SHOW TABLE STATUS和SHOW INDEX都会触发索引统计信息的更新

5.5.3 减少索引和数据的碎片

OPTIMIZE TABLE或者导出再导入的方式可以重新整理数据
ALTER TABLE innodb_tb1 ENGINE=INNODB; 可以用来重建表

第6章 查询性能优化

6.2 慢查询基础:优化数据访问

6.2.1 是否向数据库请求了不需要的数据

例如不使用limit,多表关联时返回全部列,总是使用SELECT *,重复查询相同的数据(其实可以取一次后放在缓存中)等

6.2.2 MySQL是否在扫描额外的记录

可以从响应时间、扫描的行数、返回的行数评估一下

6.3 重构查询的方式

6.3.1 一个复杂查询还是多个简单查询

设计查询的时候一个需要考虑的重要问题是,是否需要将一个复杂的查询分成多个简单的查询。MySQL从设计上让连接和断开连接都很轻量级,在返回一个小的查询结果方面很高效。

6.3.2 切分查询

将大查询切分成小查询,每个查询功能一样,只返回一部分查询结果

6.3.3 分解关联查询

很多高性能的应用都会对关联查询进行分解。简单的,可以对每一个表进行一次单表查询,然后将结果在应用程序中进行关联。

6.4 查询执行的基础

MySQl执行一个查询时的过程如下:

image

6.4.1 MySQL客户端/服务器通信协议

实际上是MySQL向客户端推送数据。

6.4.2 查询缓存

查询缓存是通过一个队大小写敏感的哈希查找实现的。如果明知了查询缓存,还会检查用户权限。

6.4.3 查询优化处理

语法解析器和预处理

语法解析器会将sql语句进行解析,生成一颗对应的“解析树”。它会验证是否使用错误的关键字,关键字顺序是否正确等。
预处理器则根据一些MySQL规则进一步检查解析树是否合法。例如,数据表和数据列是否存在,是否有权限等。

查询优化器

MySQL使用基于成本的优化器,它将尝试预测一个查询使用某种执行计划的成本,并选择其中成本最小的一个。它不考虑并发查询的成本,也不考虑用户自定义函数的成本。
优化策略简单的分为两种:静态优化,它直接对解析树进行分析并完成优化,例如通过一些简单的代数变换将WHERE条件转换成另一种等价形式;动态优化则和查询的上下文等因素有关,在每次查询的时候都重新评估。

下面是一些mysql可处理的优化类型:

重新定义关联表的顺序
将外连接转化成内连接
使用等价变换规则
优化COUNT()、MIN()、MAX()等
预估并转化为常数表达式
覆盖索引扫描
子查询优化(转化)
提前终止查询(例如LIMIT)
等值传播
列表IN()的比较:MySQL先将IN()列表中的数据进行排序,然后通过二分查找的方式确定是否满足条件,而不是像其他数据库将其转化成多个OR
数据和索引的统计信息

统计信息由存储引擎实现

MySQL如何执行关联查询

通过嵌套循环的方式实现,即“嵌套循环关联”

执行计划

MySQL并不会生成字节码来执行查询,而是生成一个指令树,然后通过存储引擎执行完成这颗指令树并返回结果,如图:

image

关联查询优化器

MySQL优化器最重要的一部分就是关联查询优化,它决定了多个表关联时的顺序。关联优化器通过评估不同顺序时的成本来选择一个代价最小的关联顺序。

排序优化

不能使用索引排序的时候,MySQL需要自己排序。如果数据量小则在内存中排序,如果数据量大则使用磁盘。这个过程统一称为文件排序(filesort)

6.4.4 查询执行引擎

查询执行引擎根据执行计划来完成整个查询

6.4.5 返回结果给客户端

MySQL将结果集返回客户端是一个增量、逐步返回的过程。只要开始产生第一条结果,就可以向客户端逐步返回结果集了。

6.5 MySQL查询优化器的局限性

6.5.1关联子查询

MySQL的子查询实现得非常糟糕。最糟糕的一类查询是WHERE条件中包含IN()的子查询语句。

一旦使用了DISTINCT和GROUP BY,那么在查询的执行过程中,通常要产生临时中间表。

6.5.2 UNION的限制

如果希望UNION的子句都能够根据LIMIT只取部分结果集,或者希望能够先排好序再合并结果集的话,就需要在UNION的各个子句中分别使用这些句子。

6.5.3 索引合并优化

当WHERE子句中包含多个复杂条件的时候,MySQL能够访问单个表的多个索引以合并和交叉过滤的方式来定位需要查找的行。

6.5.4 等值传递

等值传递有时候会带来意想不到的额外开销。例如,有一个非常大的IN()列表,而MySQL优化器发现存在WHERE、ON或者USING的子句,将这个列表的值和另一个表的某个列相关联,那么优化器会将IN()列表都复制应用到关联的各个表中,如果这个IN()列表非常大,则会导致优化和执行都会变慢。

6.5.5 并行执行

MySQL无法利用多核特性来并行执行查询。

6.5.6 哈希关联

在本书写作的时候,MySQL并不支持哈希关联——MySQL的所有关联都是嵌套循环关联。

6.5.7 松散索引扫描

由于历史原因,MySQL并不支持松散索引扫描,也就无法按照不连续的方式扫描一个索引,在MySQL 5.6之后的版本,关于松散索引扫描的一些限制将会通过“索引条件下推”的方式解决。

MySQL全表扫描(SELECT … FORM table WHERE b BETWEEN 2 AND 3):

image

松散索引扫描(SELECT … FORM table WHERE b BETWEEN 2 AND 3):

image

6.5.8 最大值和最小值优化

对于MIN()和MAX()查询,MySQL的优化做得并不好。

6.5.9 在同一个表上查询和更新

MySQL不允许对同一张表同时进行查询和更新。

6.6 查询优化器的提示(hint)

如果对优化器选择的执行计划不满意,可以使用优化器提供的几个提示(hint)来控制最终的执行计划。建议直接阅读MySQL官方手册,有些提示和版本有直接关系。

HIGH_PRIORITY和LOW_PRIORITY

HIGH_PRIORITY用于SELECT语句,会将此SELECT语句重新调度到所有正在等待表锁以便修改数据的语句之前。还可以用于INSERT语句,其效果只是简单地抵消了全局LOW_PRIORITY设置对该语句的影响。

LOW_PRIORITY则正好相反:它会让该语句一直处于等待状态,只要队列中还有需要访问同一个表的语句——即使是那些比该语句还晚提交到服务器的语句。LOW_PRIORITY在SELECT、INSERT、UPDATE和DELETE中都可以使用。

千万不要在InnoDB或者其他有细粒度锁机制和并发控制的引擎中使用。

DELAYED

这个提示对INSERT和REPLACE有效。MySQL会将使用该提示的语句立即返回给客户端,并将插入的行数据放入到缓冲区,然后在表空闲时批量将数据写入。

STRAIGHT_JOIN

这个提示可以放在SELECT语句的SELECT关键词之后,也可以放置在任何两个关联表的名字之间。第一个用法是让查询中所有的表按照在语句中出现的顺序进行关联。第二个用法则是固定其前后两个表的关联顺序。

SQL_SMALL_RESULT和SQL_BIG_RESULT

这两个提示只对SELECT语句有效。他们告诉优化器对GROUP BY或者DISTINCT查询如何使用临时表及排序。SQL_SMALL_RESULT告诉优化器结果集会很小,可以将结果集放在内存中的索引临时表,以避免排序操作。如果是SQL_BIG_RESULT,则告诉优化器结果集可能会非常大,建议使用磁盘临时表做排序。

SQL_BUFFER_RESULT

这个提示告诉优化器将查询结果放入到一个临时表,然后尽可能快地释放表锁

SQL_CACHE和SQL_NO_CACHE

这个提示告诉MySQL这个结果集是否应该缓存在查询缓存中

SQL_CALC_FOUND_ROWS

严格来说这个不是优化器提示。查询中加上该提示MySQL会计算除去LIMIT子句后这个查询要返回的结果集的总数

FOR UPDATE和LOCK IN SHARE MODE

这也不是真正的优化器提示。这两个提示主要控制SELECT语句的锁机制,但只对实现了行级锁的存储引擎有效。使用该提示会对符合查询条件的数据行加锁。

USE INDEX、IGNORE INDEX和FORCE INDEX

这几个提示会告诉优化器使用或者不使用哪些索引来查询记录

optimizer_search_depth

这个参数控制优化器在穷举执行计划时的限度

optimizer_prune_level

该参数默认打开,这让优化器会根据需要扫描的行数来决定是否跳过某些执行计划

optimizer_switch

这个变量包含了一些开启/关闭优化器特性的标志位

6.7 优化特定类型的查询

6.7.1 优化COUNT()查询

COUNT()是一个特殊的函数,有两种非常不同的作用:它可以统计某个列值的数量,也可以统计行数。在统计列值时要求列值是非空的(不统计NULL)。如果在COUNT()的括号中指定了列或者列的表达式,则统计的就是这个表达式有值的结果数

COUNT()的另一个作用是统计结果集的行数

6.7.2 优化关联查询

确保ON或者USING子句中的列上有索引。确保任何的GROUP BY和ORDER BY中的表达式只涉及到一个表中的列,这样MySQL才有可能使用索引来优化这个过程。当升级MySQL的时候需要注意:关联语法、运算符优先级等其他可能会发生变化的地方。

6.7.3 优化子查询

尽可能使用关联查询代替

6.7.4 优化GROUP BY和DISTINCT

MySQL都使用同样的办法优化这两种查询,事实上,MySQL优化器会在内部处理的时候相互转化这两类查询。它们都可以使用索引来优化,这也是最有效的优化办法。在MySQL中,当无法使用索引的时候,GROUP BY使用两种策略来完成:使用临时表或者文件排序来做分组。

6.7.5 优化LIMIT分页

进行分页操作的时候,我们通常会使用LIMIT加偏移量(OFFSET)的办法实现,同时加上合适的ORDER BY子句。但是在偏移量非常大的时候,例如LIMIT 1000, 20这样的查询,这时MySQL需要查询10020条记录然后只返回最后20条。可以将LIMIT查询转换为已知位置的查询,用BETWEEN 10000 AND 10020。或者可以使用一个书签记录上次取数据的位置,下一次从该位置开始扫描。

6.7.6 优化SQL_CALC_FOUND_ROWS

加上这个提示后,MySQL会扫描所有满足条件的行,代价可能非常高,最好不使用

6.7.7 优化UNION查询

MySQL总是通过创建并填充临时表的方式来执行UNION查询。因此很多优化策略在UNION查询中都没法很好的使用。经常需要手工地将WHERE、LIMIT、ORDER BY等子句“下推”到UNION的各个子查询中,以便优化器可以充分利用这些条件进行优化(例如,直接将这些子句冗余地写一份到各个子查询中)

6.7.8 静态查询分析

可以使用工具解析查询日志、分析查询模式,给出潜在问题

6.7.9 用户自定义变量

用户自定义变量的限制:

使用自定义变量的查询,无法使用查询缓存
不能在使用常量或标识符的地方使用自定义变量,例如表名、列名和LIMIT子句中
用户自定义变量的生命周期是在一个连接中有效,所以不能用它们来做连接间的通信
如果使用连接池或者持久化连接,自定义变量可能让看起来毫无关系的代码发生交互
在5.0之前的版本,是大小写敏感的,所以在不同版本间要注意兼容问题
不能显示地声明自定义变量的类型,所以最好初始化时指定一个初识值以确定类型
MySQL优化器在某些场景下可能会将这些变量优化掉,这可能导致代码不按预想的方式运行
赋值的顺序和赋值的时间点并不总是固定的,这依赖于优化器的决定
赋值符号:=的优先级非常低,所以需要注意,赋值表达式应该使用明确的括号
使用未定义变量不会产生任何语法错误,如果没有意识到这一点,非常容易犯错

6.8 案例学习

6.8.3 使用用户自定义函数

当SQL语句已经无法高效地完成某些任务的时候,可以写一个用户自定义函数(UDFs)

第7章 MySQL高级特性

7.1 分区表

对用户来说,分区表是一个独立的逻辑表,但是底层由多个物理子表组成,索引页是按照分区的子表定义的,而没有全局索引。MySQL在创建表时使用PARTITION BY子句定义每个分区存放的数据。分区的一个主要目的是将数据按照一个较粗的粒度分在不同的表中,这样做可以将相关的数据存放在一起,另外,如果想一次批量删除整个分区的数据也会变得很方便。

分区表的一些限制:

一个表最多只能有1024个分区。
在MySQL 5.1中,分区表达式必须是整数,或者是返回整数的表达式。在MySQL 5.5中,某些场景中可以直接使用列来进行分区。
如果分区字段中有主键或者唯一索引的列,那么所有主键列和唯一索引列都必须包含进来。
分区表中无法使用外键约束

7.1.1 分区表的原理

存储引擎管理分区的各个底层表和管理普通表一样(所有的底层表都必须使用相同的存储引擎),分区表的索引只是在各个底层表上各自加上一个完全相同的索引。从存储引擎的角度来看,底层表和一个普通表没有任何不同。每个操作都会“先打开并锁住所有的底层表”,但这并不是说分区表会在处理过程中是锁住全表的。

7.1.2 分区表的类型

MySQL支持多种分区表。我们看到最多的是根据范围进行分区,其他的分区技术还有根据键值进行分区,使用数学模函数来进行分区(以便将数据轮询放入不同的分区)。

7.1.3 如何使用分区表

全量扫描数据,不要任何索引:只要能够使用WHERE条件将需要的数据限制在少数分区中。

索引数据,并分离热点。可以将热数据放到一个分区中,这样可以有机会将他们都缓存到内存中个,并使用索引。

7.1.4 什么情况下会出问题

一些可能遇到的问题:

NULL值会使分区过滤无效。(可以人为创建一个“无用”的分区)
分区列和索引列不匹配,会导致查询无法进行分区过滤。
选择分区的成本可能很高。
打开并锁住所有底层表的成本可能很高。
维护分区的成本可能很高。

其他的一些限制:

所有分区都必须使用相同的存储引擎
分区函数中可以使用的函数和表达式也有一些限制
某些存储引擎不支持分区
对于MyISAM的分区表,不能再使用LOAD_INDEX_INTO_CACHE操作
对于MyISAM表,使用分区表时需要打开更多的文件描述符

7.1.5 查询优化

分区最大的优点就是优化器可以根据分区函数来过滤一些分区,很重要的一点是要在WHERE条件中带入分区列。即便在创建分区时可以使用表达式,但在查询时却只能根据列来过滤分区。

7.1.5 合并表

合并表是一种早期的、简单的分区实现,和分区表相比有一些不同的限制,并缺乏优化。分区表严格来说是一个逻辑上的概念,用户无法访问底层的各个分区,对用户来说分区是透明的。但是合并表允许用户单独访问各个子表,合并表是一种即将被淘汰的技术。

7.2 视图

视图本身是一个虚拟表,不存放任何数据。访问视图的时候,它返回的数据是MySQL从其他表中生成的。视图和表是在同一个命名空间,MySQL在很多地方对于视图和表是同样对待的。MySQL可以使用两种办法来处理视图:合并算法和临时表算法。视图的实现算法是视图本身的属性,和作用在视图上的查询语句无关。如果可能,会尽可能地使用合并算法,图例如下:

image

7.2.1 可更新视图

可更新视图是指可以通过更新这个视图来更新视图涉及的相关表。如果视图定义中包含了GROUP BY、UNION、聚合函数,以及其他一些特殊情况,就不能被更新了。所有使用临时表算法实现的视图都无法被更新。

7.2.2 视图对性能的影响

多数人认为视图不能提升性能,事实上,MySQL中某些情况下视图也可以帮助提升性能,例如在重构schema的时候可以使用视图,使得在修改视图底层表结构的时候,应用代码还可能继续不报错的运行。外层查询的WHERE条件无法“下推”到构建视图的临时表的查询中,临时表也无法建立索引。

7.2.3 视图的限制

MySQL还不支持物化视图(物化视图是指将视图结果数据存放在一个可以查看的表中,并定期从原始表中刷新数据到这个表中)。

7.3 外键约束

InnoDB是目前MySQL中唯一支持外键的内置存储引擎。有时可以使用触发器来代替外键。

7.4 在MySQL内部存储代码

MySQL允许通过触发器、存储过程、函数的形式来存储代码。有如下优点:

它在服务器内部执行,离数据最近,还能节省带宽和网络延迟。
这是一种代码重用,可以方便地统一业务规则,保证某些行为总是一致,所以也可以为应用提供一定的安全性。
它可以简化代码的维护和版本更新。
它可以帮助提升安全,比如提供更细粒度的权限控制。一个常见的例子是银行用于转移资金的存储过程:这个存储过程可以在一个事务中完成资金转移和记录用于审计的日志。
服务器端可以缓存存储过程的执行计划,这对于需要反复调用的过程,会大大降低消耗。
因为是在服务器端部署的,所以备份、维护都可以在服务器端完成。

存储代码也有如下缺点:

较之引用程序的代码,存储代码效率差些,难以实现太复杂的逻辑。
存储代码可能会给应用程序代码的部署带来额外的复杂性。
因为存储程序都部署在服务器内,所以可能有安全隐患。
存储过程会给数据库服务器增加额外的压力,而数据库服务器的扩展性相比应用服务器要差很多。
存储过程的一个小错误,可以直接拖死服务器。
执行计划缓存是连接级别的,游标的物化和临时表相同。
调试困难。
它和基于语句的二进制日志复制合作的并不好。

7.4.1 存储过程和函数

有如下限制:

优化器无法使用关键字DETERMINISTIC来优化单个查询中多次调用存储函数的情况。
优化器无法评估存储函数的执行成本。
每个连接都有独立的存储过程的执行计划缓存。
存储程序和复制是一组诡异组合。

7.4.2 触发器

可以在MySQL中指定是在SQL语句执行前还是在执行后触发触发器。对于每一个表的每一个事件,最多只能定义一个触发器;MySQL只支持“基于行的触发”。另外,还有一些其他限制:

触发器可以掩盖服务器背后的工作,一个简单的SQL语句背后,因为触发器,可能包含了很多看不见的工作。
触发器的问题很难排查,如果某个性能问题和触发器相关,会很难分析和定位。
触发器可能导致死锁和锁等待。

7.4.3 事件

它类似于linux的定时任务,不过是完全在MySQL内部实现的。如果一个定时事件执行需要很长的时间,那么有可能会出现这样的情况,即前面一个事件还未执行完成,下一个时间点的事件又开始了。MySQL本身不会防止这种并发。

7.4.4 在存储过程中保留注释

MySQL的命令行客户端会自动过滤注释。一个将注释存储到存储程序中的技巧就是使用版本相关的注释,因为这样的注释可能被MySQL服务器执行。服务器和客户端都知道这不是普通的注释,所以也就不会删除。例如:

BEGIN
    SELECT 1234;
    /*!99999 这里是注释1
        注释2    */
    SELECT 1234;
END

7.5 游标

MySQL在服务器端提供只读的、单向的游标,而且只能在存储过程或者更底层的客户端api中使用。因为MySQL游标中指向的对象都是存储在临时表中而不是实际查询到的数据,所以MySQL游标总是只读的。当你打开一个游标的时候需要执行整个查询,使用时要注意效率问题。

7.6 绑定变量

创建一个绑定变量SQL时,客户端向服务器发送了一个SQL语句的原型,服务器端收到这个SQL语句框架后,解析并存储这个SQL语句的部分执行计划,返回给客户端一个SQL语句处理句柄。以后每次执行该类查询,客户端都指定使用这个句柄。绑定变量的SQL,使用问号标记可以接收参数的位置,当真正需要执行具体查询的时候,则使用具体值代替这些问号。

7.7 用户自定义函数

MySQL支持用户自定义函数(UDF),可以使用支持C语言调用约定的任何编程语言来实现。要注意UDF中的一个错误很可能会让服务器直接崩溃。

7.8 插件

除了UDF,MySQL还支持各种各样的插件,细节可以参考MySQL的官方手册。

7.9 字符集和校对

字符集是指一种从二进制编码到某类字符符号的映射,可以参考如何使用一个字节来表示英文字母。校对是指一组用于某个字符集的排序规则。对于校对规则通常需要考虑的一个问题是,是否以大小写敏感的方式比较字符串,或者是以字符串编码的二进制来比较大小,它们对于的校对规则的前缀分别是_cs、_ci和_bin。用户设置对返回的影响如下:

image

7.10 全文索引

这节不是特别感兴趣,笔记记录较少

7.10.1 自然语言的全文索引

关键词在文档中出现次数越少,则匹配时的相关度就越高,而非常常见的单词将不会搜索。

7.11 分布式事务

没记笔记

7.12 查询缓存

查询缓存系统会跟踪查询中涉及的每个表,如果这些表发生变化,那么和这个表相关的所有缓存数据都将失效。随着通用服务器越来越强大,查询缓存被发现是一个影响服务器扩展性能的因素,我们认为应该默认关闭查询缓存。

7.12.1 MySQL如何判断缓存命中

方法很简单:缓存存放在一个引用表中,通过一个哈希值引用,这个哈希值包括了如下因素,即查询本身(两次查询即使只有一个空格的不同,也不会命中)、当前要查询的数据库、客户端协议的版本等信息。当查询中有一些不确定的数据则不会被缓存,例如包含函数NOW()。包含了不确定函数的查询结果不会被放到查询缓存中,所以下次用同样的语句在查询缓存中是无法命中的。

当一个语句在事务中修改了某个表,则这个表对应的查询缓存都会失效。如果查询缓存使用了很大的内存,缓存失效操作就可能成为一个非常严重的问题瓶颈,因为这个操作是靠一个全局锁保护的,所有需要做该操作的查询都要等待这个锁。

7.12.4 如何配置和维护查询缓存

参数query_cache_type可以设置成OFF、ON或DEMAND,前两个是代表是否打开查询缓存,DEMAND表示只有在查询语句中明确写明SQL_CACHE的语句才放入查询缓存。

7.12.6 通用查询缓存优化

将参数query_cache_type设置成ON或DEMAND,然后再希望缓存的查询中加上SQL_CACHE。如果希望缓存多数查询,而少数查询不缓存,则可以使用SQL_NO_CACHE来禁止该sql语句缓存。

7.12.7 查询缓存的替代方案

客户端的缓存可以很大程度上分担MySQL服务器的压力

这次看书重点在索引、查询等,后面的章节将不再做笔记。这本书不能只看笔记,要偶尔重新翻看。


本文由【waitig】发表在等英博客
本文固定链接:高性能mysql第三版读书笔记
欢迎关注本站官方公众号,每日都有干货分享!
等英博客官方公众号
点赞 (0)分享 (0)