《高性能mysql》读书笔记

序言

MyISAM引擎->InnoDB引擎

QPS

主备复制

InnoDB引擎性能优化 和诊断方法
在这里插入图片描述

在这里插入图片描述
在这里插入图片描述
在这里插入图片描述

序:

4.0:MyISAM引擎

5.0:InnoDB引擎 6.5万QPS

第1章 MySQL架构与历史

1.1MySQL逻辑架构

MySQL逻辑架构图:
在这里插入图片描述

中间部分比较重要

最下边的存储引擎负责mysql中数据的存储和提取。

大多数mysql的核心在第二层,包括查询解析、分析、优化、缓存以及所有的内置函数。所有存储引擎的功能都在这一层实现:存储过程、触发器、视图等。

服务器会负责缓存线程,因此不需要为每一个新建的连接创建或者销毁线程。

1.1.1 安全和连接:MySQL服务器验证连接信息,成功之后,服务器会继续验证该客户端的权限。

1.1.2 优化和执行:MySQL创建内部数据结构(解析树),然后对其进行各种优化。决策过程。

对于select语句,在解析查询之前,服务器会先检查缓存,服务器就不必再执行查询解析、优化和执行这个过程。

1.2并发控制

多个查询需要在同一时刻修改数据,都会产生并发控制问题。

1.2.1 读写锁: 共享锁和排他锁,也叫读锁和写锁。

读锁是共享的,或者说是相互不阻塞的。写锁是排他的,也就是说一个写锁会阻塞其他的写锁和读锁。

1.2.2 锁粒度:尽量锁定需要修改的部分数据,而不是所有的资源。花费时间。

MySQL存储引擎都可以实现自己的锁策略和锁粒度。写锁有优先级,但alter table之类的语句使用表锁,忽略存储引擎的锁机制。

表锁:MySQL最基本的锁策略,并且是开销最小的策略。

行级锁:最大程度地支持并发处理。

1.3事务:ACID

数据库事务是指作为单个逻辑工作单元执行的一系列操作,要么完全地执行,要么完全地不执行。

ACID是指数据库正确执行的四个基本要素缩写。

ACID解释比较好:https://www.cnblogs.com/stone94/p/10409669.html

一致性:100-100,0+60,应该为100,但是是60,所以一致性不符合,原子性就符合了。

1.3.1 存储引擎的隔离级别:

没有事务隔离,会出现:

丢失更新

脏读:(A和B两个事务同时修改同一个数据,A修改的提交在B提交之后,导致B好像没有修改,丢失修改)

不可重复读:(事务A查询数据之后,数据被事务B修改,导致事务A两次读取数据不一致)

幻读:一个人将范围内数据改为50,而另外一个人插入范围内100数据。(另一个事务只能读不能修改 但是能插入)。幻读是由插入或者删除引起的

  • read uncommitted(未提交读):事务中的修改,即使没有提交,对其他事务也都是可见的,会产生脏读,不可重复读,幻读。

  • read committed(提交读):大多数数据库系统默认的隔离级别(但MySQL不是)。满足隔离性的建档定义:一个事务从开始直到提交之前,所做修改对其他事务都是不可见的,叫不可重复读,因为两次执行同样的查询,可能得到不一样的结果。不可重复读:事务A查询数据之后,数据被事务B修改,导致事务A两次读取数据不一致。

  • REPEATTABLE READ(可重复读):MYSQL的事务隔离级别。该sql第一次读取到数据后,就将这些数据加锁(悲观锁),其它事务无法读取和修改这些数据,就可以实现可重复读了。解决了脏读的问题,但会有幻读问题。

    可重复读是mysql的默认事务隔离级别

  • SERIAZABLE(可串行化):隔离最高级别,每一行加锁在这里插入图片描述
    锁分类:锁的粒度:行,表,页;锁级别:共享锁(读锁,加了读锁,其他事物只能加读锁),排他锁(写锁,加了写锁,其他事物不能读不能写,相当于悲观锁);按加锁方式:显示锁,隐式锁;按锁使用方式:乐观锁(加版本)和悲观锁;

    InnoDB的锁定模式实际上可以分为四种:共享锁(S),排他锁(X),意向共享锁(IS)和意向排他锁(IX);意向锁的作用:不需要逐条判断是否加锁,用意向锁就可以,如果冲突,则等待。

    只有LOCK TABLE … READLOCK TABLE … WRITE才能申请表级别的锁。
    意向共享锁(IS锁):一个事务在获取(任何一行/或者全表)S锁之前,一定会先在所在的表上加IS锁。
    意向排他锁(IX锁):一个事务在获取(任何一行/或者全表)X锁之前,一定会先在所在的表上加IX锁。

    ![]](https://img-blog.csdnimg.cn/20191124151821518.png?x-oss-process=image/watermark,type_ZmFuZ3poZW5naGVpdGk,shadow_10,text_aHR0cHM6Ly9ibG9nLmNzZG4ubmV0L3hpMTUyMzIxMzExMzU=,size_16,color_FFFFFF,t_70)

例子:

select … for update;//当前读 意向排它锁 要放在一个事务中,mysql 默认是一个sql一个事务

select … lock in share mode;//当前读 意向共享锁

lock in share mode适用于两张表存在业务关系时的一致性要求,for update适用于操作同一张表时的一致性要求

1.3.2 死锁:指两个或多个事务对同一资源抢占,并请求锁定对方占用的资源,从而导致恶性循环的现象。

数据库系统实现了各种死锁检测和死锁超时机制。InnoDB目前处理死锁的方法是,将持有最少行级排它锁的事务进行回滚。

1.3.3 事务日志:存储引擎在修改表的数据时只需要修改其内存拷贝,再把该修改行为记录到持久化在硬盘的事务日志中,而不用每次都将修改的数据本身持久化到磁盘。预写式日志。修改数据需要写两次磁盘。

事务日志持久化以后,内存中被修改的数据在后台可以慢慢刷回到磁盘。

1.3.4 MySQL中的事务:提供了两种事务型的存储引擎:InnoDB和NDB Cluster。另外还有一些第三方存储引擎也支持事务,包括XtraDB和PBXT。

  • 自动提交:mysql默认采用自动提交模式。也就是说,如果不是显示地开始一个事务,则每个查询都当做一个事务执行提交操作。
  • 事务下层存储引擎实现。
  • MySQL也支持LOCK TABLES和UNLOCK TABLES语句,这是在服务器层实现的。

经常可以发现,应用已经将表从MyISAM转换到InnoDB,是没有必要的,是会严重影响性能。

MyISAM是mysql5.1之前。


MyISAM,内存表

mysql能够识别所有的4个ANSI隔离级别,InnoDB引擎也支持多有的隔离级别。

  • 隐式和显示锁定: InnoDB采用两阶段锁定协议。

1.4多版本并发控制

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

不同存储引擎的MVCC实现是不同的,典型的有乐观并发控制和悲观并发控制。

InnoDB的MVCC(多版本的并发控制),在REPETABLE READ隔离级别下,MVCC具体是如何操作:

通过在每行记录后面保存两个隐藏的列来实现的。这两个列,一个保存行的创建时间,一个行的过期时间(删除时间)。当然存储的并不是实际的时间,而是系统版本号。每开始一个新的事务,系统版本号会自动递增。事务开始时刻的系统版本号会作为事务的版本号,用来和查询到的每行记录的版本号进行比价。

  • select (快照读) 读情况下不会产生幻读,对于修改数据还是有问题,而且读的是历史数据。开启事务后第一个select语句才是快照读的地方,而不是一开启事务就快照读。

InnoDB会根据以下两个条件检查每行记录:

a. 查找早于当前事务版本的数据行,这样可以确保事务读取的行,要么是在事务开始前存在的,要么是事务自身插入或者修改过的。

b.行的删除版本要么未定义,要么大于当前事务版本号的,这可以确保事务读取到的行,在事务开始之前未被删除。

只有符合上述两个条件的记录,才能返回作为查询结果。

  • insert 当前读(会加锁)

InnoDB为新插入的每一行保存当前系统版本号作为行版本号

  • delete 当前读

InnoDB为删除的每一行保存当前系统版本号作为行删除标识。、

  • update 当前读

InnoDB为插入一行新记录,保存当前系统版本号作为行版本号,同时保存当前系统版本号到原来的行作为行删除标识。

总结:MVCC只在REPEATABLE READ和READ COMMITTED两个隔离级别下工作。其他的与MVCC不兼容。因为READ UNCOMMITTED总是读取最新的数据行,而不是符合当前事务版本的数据行。而SERIALIZABLE则会对所有读取的行都加锁。

很明显可重复读的隔离级别没有办法彻底的解决幻读的问题,如果我们的项目中需要解决幻读的话也有两个办法:

  • 使用串行化读的隔离级别
  • MVCC+next-key locks:next-key locks由record locks(索引加锁) 和 gap locks(间隙锁,每次锁住的不光是需要使用的数据,还会锁住这些数据附近的数据)

1.5 MySQL的存储引擎

mysql将每个数据库(也称之为schema)。

创建表时,MySQL会在数据库子目录下创建一个和表相同的.frm文件保存表的定义。

命令 show table status like ‘contract_config’

含义具体可看书p52

存储引擎

  • InnoDB引擎

InooDB是MySQL的默认事务型引擎。

  • MyISAM引擎在MySQL5.1之前的版本

不支持事务和行级锁。

  1. 加锁与并发:MyISAM对整张表加锁,而不是针对行。读取时会对需要读到的所有表加共享锁,写入时对表加排他锁。但是在表有读取查询的同时,也可以往表中插入新的记录。
  2. 修复:命令有check table mytable,repair table mytable
  3. 索引的特性:对于MyISAM表,即使BOLB和TEXT等长字段,也可以基于前500个字符创建索引。MyISAM也支持全文索引,这是一种基于分词创建的索引,可以支持复杂的查询。
  4. 延迟更新索引键:DELAY_KEY_WRITE选项,不会立刻清理缓存区,只有在清理缓存区或者关闭表的时候才会将对应的索引块写入到磁盘。
  • 其他引擎:

大数据量:在3~5TB之间,这是单台机器上的量,不是一个分片的量。如果数据量继续增长到10TB以上的级别,需要建立数据仓库。

转换搜索引擎:

  • alter table mytable ENGINE=InnoDB;但需要很长时间。
  • MySQLdump工具:是mysql用于转存储数据库的使用程序。主要包含一个sql脚本。
  • 创建与查询:数据量不大的时候使用,数据量大可以分批导入,使用一个工具锁表,使两个表数据一致。

create table a like select * from b where 1=2;

alter table innodb_table ENGINE = InnoDB;

insert into innodb_table select * from myisam_table;

create table a like select * from b;

分段处理:

start transaction;

insert into innodb_table Select * from myisam_table where id between x and y;

commit;

第2章 MySQL基准测试

压力测试

基准测试的策略:一是针对整个系统的整体测试,另外是单独测试mysql。

测试指标:

  • 吞吐量:指单位时间内事务处理数。常用的测试单位是每秒事务数(TPS),有些也采用每分钟事务(TPM)。
  • 响应时间或延迟:用于测试任务所需的整体时间。
  • 并发性:web服务器的并发性也不等于数据库的并发性。web服务器的并发性更准确的度量指标,应该是在任意时间有多少同时发生的并发请求。

一个设计良好的应用,同时可以打开成百上千个MySQL数据库服务器连接,但可能同时只要少数连接在查询。所以说,一个web站点“同时有50000个用户访问,却可能10~15个并发请求到mysql数据库”。

并发性基准测试需要关注的是正在工作中的并发操作,或者是同时工作中的线程数或者连接数。

测试尽可能的真实。实际应用服务器和数据库服务器分别部署在不同的机器。

集成测试工具:ab,Jmeter,http_load

第三章 服务器性能剖析

最常碰到三个性能相关的服务请求:如何确认服务器是否达到了性能最佳的状态;找出某条语句为什么执行不够快;诊断被用户描述成“停顿”,“堆积”,“卡死”的某些间歇性疑难故障。

3.1 性能优化简介

给性能一个定义:为完成某件任务所需要的时间度量,换句话说,性能即响应时间。服务器的目的是执行SQL语句。缺少精确的测量。数据库的就是查询时间。

性能剖析是测量和分析时间花费在哪里的主要方法。对结果进行统计和排序,将重要的任务排到前面。

性能剖析工具:在任务开始启动计时器,在任务结束时停止计时器,然后用结束时间减去启动时间得到响应时间。也有些工具会记录任务的父任务。这写结果数据可以用来绘制调用关系图,但对于我们的目标来说更重要的是,可以将相似的任务分组并进行汇总。

性能剖析类型:基于时间的分析和基于等待的分析。基于执行时间的分析研究的是什么任务的执行时间最长,而基于等待的分析是判断任务在什么地方被阻塞的时间最长。

3.2 对应用程序进行性能剖析

虽然性能问题大多数情况下都和数据库有关,但应用导致的性能问题也不少。

性能瓶颈可能有很多影响因素:

  • 外部资源,比如调用了外部的web服务或者搜索引擎
  • 应用需要处理大量的数据,比如分析一个超大的XML文件
  • 在循环中执行昂贵的操作,比如滥用正则表达式
  • 使用了低效的算法,比如暴力算法

3.3 剖析MySQL查询

“慢查询”,“慢查询工具”

对一个占响应时间不超过5%的查询进行优化,无论如何努力,收益也不会超过5%

mysql慢查询日志:mysql5.1新版本中,可以通过long_query_time=0来捕获所有的查询。

”通用日志“:在查询请求到服务器时进行记录。所以不包含响应时间和执行计划等,

pt-query-digest

show table status like ‘contract_config’;
show STATUS;
show PROFILES;


分析慢查询日志


定位到需要优化的单条查询后:

show profile:5.1之后的版本中引入的。默认是禁用的。但可以通过服务器变量在会话(连接)级别动态地修改。

show status:

检查慢查询日志的条目:

  • 5.1以后的版本

3.4 诊断间歇性问题

如果服务器上所有的程序都突然变慢,又突然都变好,每一条查询也都变慢了,那么慢查询可能就不一定是原因,而是由于其他问题导致的结果。反过来说,如果服务器整体运行没有问题,只有某条查询偶尔变慢,就需要将注意力放到这条特定的查询上面。

show global status

show processlist

3.6 总结

定义性能最有效的方法是响应时间。

测量的最佳开始点是应用程序,而不是数据库。即使问题出在底层的数据库,借助良好的测量也可以很容易地发现问题。

完整的测量会产生大量需要分析的数据,所以需要用到剖析器。这是最佳的工具,可以帮助将重要的问题冒泡到前面,这样就可以决定从哪里开始分析会比较好。

优化和提升是两回事。当继续提升的成本超过收益的时候,应当停止优化。

第四章 Schema与数据类型优化

既要关注全局,也需要专注细节

4.1 选择优化的数据类型

几个简单的原则:

  1. 更小的通常更好

简单就好:简单数据类型的操作通常需要更少的CPU周期。例如:整型比字符操作代价更低,因为字符集和校对规则(排序规则)使字符比较比整型更复杂。有个例子:一个是应该使用MySQL内建的类型而不是字符串来存储日期和时间,另外一个是应该用整型存储IP地址

  1. 尽量避免NULL

如果查询中包含可为NULL的列,对mysql来说更难优化,因为可为NULL的列使得索引、索引统计和值比较都更复杂。可为NULL的列会使用更多的存储空间,在mysql里也需要特殊处理。当可为NULL的列别索引时,每个索引记录需要一个额外的字节,在MyISAM里甚至还可能导致固定大小的索引(例如只有一个整数列的索引)变为可变大小的索引。

通常把可为NULL的列改为NOT NULL带来的性能提升比价小,所以(调优时)没有必要首先在现有schema中查找并修改掉这种情况,除非确定这会导致问题。但是,如果计划在列上建索引,就应该尽量避免设计成可列NULL的列。

当然也有例外,例如值得一提的是,InnoDB使用单位的位(bit)存储NULL值,所以对于稀疏数据有很好的空间效率,但这一点不适用于MyISAM。

DateTime和TimeStamp列都可以存储相同类型的数据:时间和日期,精确到秒。然而TimeStamp只使用DateTime一半的存储空间,并且会根据时区变化,具有特殊的自动更新能力。另一方面,TimeStamp允许的时间范围要小的得多,有时候它的特殊能力会成为障碍。

mysql为了兼容性支持很多别名,例如Integer、Bool以及Numeric。它们都只是别名。这些别名可能令人不解,但不会影响性能。如果建表时采用数据类型的别名,然后用Show Create Table检查,会发现mysql报告的是基本类型,而不是别名。

4.1.1 整数类型

有两种类型的数字:整数和实数。整数可以使用的整数类型:tinyint,smallint,mediumint,int,bigint。分别使用8,16,24,32,64位存储空间,可以存储的值的范围-2(N-1)到2(N-1)-1。整数类型有可选的UNSigned,表示不允许负值,这大致可以使正数的上限提高一倍。例如Tinyint unsigned可以存储的值的范围是0255,而tinyint的存储范围是-128127

整数计算一般使用64位的bigint,即使在32位环境也是如此。

int(11)和int(20)对于存储和计算来说是相同的,只是规定了mysql的一些交互工具用来显示字符的个数。

4.1.2 实数类型

实数是带有小数部分的数字,然而,它们不只是为了存储小数部分;也可以使用DECIMAL存储比BigInt还大的整数。

因为CPU不支持对DECIMAL的直接计算,所以在MySQL5.0以及更高版本中,mysql服务器自身实现了DECIMAL的高精度计算。相对而言,CPU直接支持原生浮点计算,所以浮点运算明显更快。

浮点和DECIMAL类型都可以指定精度。对于DECIMAL列,可以指定小数点前后所允许的最大位数。这会影响列的空间消耗。MySQL5.0和更高版本将数字打包保存到一个二进制字符串中(每4个字节存9个数字)。例如,DECIMAL(18,9)小数点两边将各存储9个数字,一共使用9个字节;小数点前的数字用4个字节,小数点后的数字用4个字节,小数点本身占1个字节。允许最多65个数字。

因为需要额外的空间和计算开销,所以应该尽量只在对小数进行精确计算时才使用DECIMAL,例如存储财务数据。但也可以用BIGINT。

4.1.3 字符串类型

varchar

varchar可用于存储可变长字符串,是最常见的字符串数据类型。比定长类型更节省空间,因为它仅使用必要的空间。有一种情况例外:如果mysql表使用ROW_FORMAT=FIXED创建的话,每一行都会使用定长存储,这会浪费空间。

varchar需要使用1或2个额外字节记录字符串的长度:如果列的最大长度小于或等于255字节,则只使用1个字节标识,否则使用2个字节。

varchar节省了存储空间,所以对性能也有帮助。但是,由于行是变长的,在update时可能使行变得比原来更长,这就导致需要额外的工作。如果一个行占用的空间增长,并且在页内没有更多的空间可以存储,在这种情况下,不同的存储引擎的处理方式是不一样的。例如,MyISAM会将行拆成不同的片段存储,InnoDB则需要分裂页来使行可以放进页内。

varchar合适的情况:字符串的最大长度比平均长度大很多;列的更新很少,所以碎片不是问题;使用了像UTF-8这样复杂的字符集,每个字符都使用不用的字节数进行存储。

在5.0或者更高版本,mysql在存储和检索时会保留末尾空格。

InnoDB则更灵活,它可以把过长的varchar存储为BLOB。

char

char类型是定长的:mysql总是根据定义的字符串长度分配足够的空间。当存储char值时,mysql会删除所有的末尾空格(在mysql4.1和更老版本中的varchar也是这样实现的-也就是说这些版本char和varchar在逻辑上是一样的,区别只是在存储格式上)。char值会根据需要采用空格进行填充以方便比较。

char适合存储很短的字符串,或者所有值都接近同一个长度。例如,char非常适合存储密码的MD5值,因为这是一个定长的值。对于经常变更的数据,char也比varchar更好,因为定长的char类型不容产生碎片。对于非常短的列,char比varchar在存储空间上也更有效率。例如用char(1)来存储只有Y和N的值,如果采用单字节字符集只需要一个字节,但是varchar(1)却需要两个字节,因为还有一个记录长度的额外字节。

BOLB和TEXT类型

blob和text都是为存储很大的数据而设计的字符串数据类型,分别采用二进制和字符方式存储。

实际上,它们分别属于两组不同的数据类型家族:字符类型是TINYTEXT,SMALLTEXT,TEXT,MEDIUMTEXT,LONGTEXT;对应的二进制类型是TINYBOLB,SMALLBLOB,BLOB,MEDIUMBLOB,LONGBLOB。BOLB是SMALLBLOB的同义词,TEXT是SMALLTEXT的同义词。

与其他类型不同,mysql把每个blob和text当作一个独立的对象处理。存储引擎在存储时通常会做特殊处理。当blob和text值太大时,InnoDB会使用专门的"外部"存储区域来进行存储,此时每个值在行内需要1~4个字节存储指针,然后在外部存储区域存储实际的值。

BLOB和TEXT家族之间仅有的不同是BLOB类型存储的是二进制数据,没有排序规则或字符集,而TEXT类型有字符集合排序规则。

mysql对blob和text列进行排序与其他类型是不同的:它值对每个列的最前max_sort_length(默认是1024,该值可以在启动服务器时使用)字节而不是整个字符串做排序。如果只需要排序前面一小部分字符,则可以减少max_sort_length的配置,或者使用order by sustring(column,length)。

mysql不能将blob和text列全部长度的字符串进行索引,也不能使用这些索引消除排序。

如果explain执行计划extra列包含”using temporary“,则说明这个查询使用了隐式临时表。

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

有时候可以使用枚举列代替常用的字符串类型。枚举列可以把一些不重复的字符串存储成一个预定的集合。mysql在存储枚举时非常紧凑,会根据列表值的数量压缩到一个或者两个字节中。,mysql在内部会将每个值在列表中的位置保存为整数,并且在表的.frm文件中保存"数字-字符串"映射关系的"查找表"。

select e+0 from enum_test

如果使用数字作为ENUM枚举常量,这种双重性很容易导致混乱,例如ENUM(‘1’,‘2’,‘3’),建议尽量避免这么做。要使用 ENUM(“fish”,“dog”,“apple”)

枚举字段是按照存储的整数而不是定义的字符串进行排序的;

select e from enum_test order by e;

一种绕过这种限制的方式是按照需要的顺序来定义枚举列。另外也可以在查询中使用FIELD()函数显示地指定排序顺序,但这会导致mysql无法利用索引消除排序。

select e from enum_test order by field(e,‘apple’,‘dog’,‘fish’)如果在定义时就是按照字母的顺序,就没必要这么做了。field是自定义排序,按照apple,dog,fish 排序。

枚举最不好的地方是,字符串列表是固定的,添加或删除字符串必须使用alter table。因此,对于一系列未来可能会改变的字符串,使用枚举不是一个好主意,除非能接受只在列表末尾添加元素,这样在mysql5.1中就可以不用重建整个表来完成修改。

由于mysql把每个枚举值保存为整数,并且必须进行查找才能转换为字符串,所以枚举列有一些开销。通常枚举列表都比较小,所以开销还可以控制,但不能保证一直如此。在特定情况下,把char/varchar列与枚举进行关联可能会比直接关联char/varchar列更慢。

select no_cache count(*) from a join b using(id);//相当于on

转换列为枚举型还有另一个好处。根据show table status命令输出结果中Data_length列的值,把这两列转换为Enum可以让表的大小缩小1/3。在某些情况下,即使可能出现Enum和varchar进行关联情况,这也是值得的。同样,转换后主键也只有原来的一半大小了。因为这是InnoDB表,如果表上有其他索引,减少主键大小会使非主键索引也会变得更小。

4.1.4 日期和时间类型

mysql可以使用许多类型来保存日期和时间值,例如YEAR和DATE。mysql能存储的最小时间粒度为秒(MariaDB支持微妙级别的时间类型)。但是mysql也可以使用微秒级的粒度进行临时计算,我们会展示怎么绕开这种存储限制。

mysql提供两种相似的日期类型:DATETIME和TIMESTAMP。对于很多应用程序,它们都能工作,但是在某些场景下,一个比另一个工作得好。

DATETIME

这个类型能保存大范围的值,从1001年到9999年,精度为秒,它把日期和时间封装到格式为YYYYMMDDHHMMSS的整数中,与时区无关。使用8个字节的存储空间。

TIMESTAMP

就像它的名字一样,TIMESTAMP类型保存了1970年1月1日午夜(格林尼治标准时间)以来的秒数,它和UNIX时间戳相同。TIMESTAMP只使用4个字节的存储空间,因此它的范围比DATETIME小得多;只能表示从1970年到2038年。mysql提供了from_unixtime()函数把unix时间戳转换为日期,并提供了unix_timestamp()函数把日期转换为unix时间戳。

TIMESTAMP显示的值也依赖于时区。mysql服务器、操作系统,以及客户端连接都有时区设置。因此,存储值为0的timestamp在美国东部时区显示为“1969-12-31 19:00:00”,与格林尼治时间差5个小时。有必要强调一下这个区别:如果在多个时区存储或访问数据,TIMESTAMP和DATETIME的行为将很不一样。前者提供的值与时区有关系,后者则保留文本表示的日期和时间。

TIMESTAMP也有DATETIME没有的特殊属性。默认情况下,如果插入时没有指定第一个TIMESTAMP列的值,mysql则设置这个列的值为当前时间。在更新一行记录时,mysql默认也会更新第一个TIMESTAMP列的值(除非在update语句中明确指定了值)。你可以配置任何TIMESTAMP列的插入和更新行为。最后,TIMESTAMP列默认为not null,这也和其他的数据类型不一样。

注:TIMESTAMP的行为比价复杂,并且在不同的mysql版本里会变动,所以你应该验证数据库的行为是你需要的。一个好的方式是修改完TIMESTAMP列后用show create table命令检查输出。

除了特殊行为之外,通常也应该尽量使用TIMESTAMP,因为它比DATETIME空间效率更高。有时候人们会将unix时间存储为整数值,但这不会带来任何收益。用整数保存时间戳的格式通常不方便处理,所以我们不推荐这样做。

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

4.1.7 特殊类型数据

某些类型的数据并不直接与内置类型一致。低于秒级精度的时间戳就是一个例子;

另一个例子是IPv4地址。人们经常使用VARCHAR(15)列来存储IP地址。然而,它们实际上是32位无符号整数,不是字符串。用小数点将地址分为四段的表示方法只是为了让人们阅读容易。所以应该用无符号整数存储IP地址。mysql提供了INET_ATON()和INET_NTOA()函数在表示方法之间转换。

如果选择标识符

如果使用的是InnoDB存储引擎,将不能在数据类型不是完全匹配的情况下创建外键,否则会有报错信息:"error 1005(HY000):Can’t create table"这个信息可能让人迷惑不解。但是:在不同长度varchar列上创建外键又是可以的。

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

ENUM和SET类型:对于标识来说,ENUM和SET类型通常是一个糟糕的选择,尽管对某些只包含固定状态或者类型的静态“定义表”来说可能是没有问题的。ENUM和SET列适合存储固定信息,例如有序的状态、产品类型、人的性别。

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

对于完全“随机”的字符串也需要多加注意,例如MD5(),SHA1()或者UUID()产生的字符串。这些函数生成新值会任意分布在很大的空间内,这会导致insert以及一些select语句变得很慢;

  1. 因为插入值会随机谢傲索引不用位置,所以使得insert语句更慢。这会导致页分裂、磁盘随机访问,以及对于聚簇存储引擎产生聚簇索引碎片。
  2. select语句变得更慢,因为逻辑上相邻的行会分布在磁盘和内存的不同地方。
  3. 随机值导致缓存对所有类型的查询语句效果都很差,因为使得缓存赖以工作的访问局部性原理失效。如果整个数据集都一样的“热”,那么缓存任何一部分特定数据到内存都没有好处;如果工作集比内存大,缓存将会有很多刷新和不命中。

如果存储UUID值,则应该移除’-'符号;或者更好的做法是,UNHEX()函数转换UUID值为16字节的数字,并且存储在一个binary(16)列中。检索时可以通过HEX()函数来格式化为十六进制格式。

UUID()生成的值与加密散列函数例如SHA1()生成的值有不同的特征:UUID值虽然分布也不均匀,但还是有一定顺序。尽管如此,但还是不如递增的整数好用。

4.2 mysql schema设计中的陷阱

  1. 范式的优点和缺点:当为性能问题而寻求帮助时,经常会被建议对schema进行范式化设计,尤其是写密集的场景。因为下面这些原因,范式化通常能够带来好处:
  • 范式化的更新操作通常比反范式化要快。

  • 当数据较好地范式化时,就只有很少或者没有重复数据,所以只需要修改更少的数据。

  • 范式化的表通常更小,可以更好地放在内存里,所以执行操作会更快。

  • 很少有多余的数据意味着检索列数据时更少需要distinct或者group by才能获得一份唯一的部门列表,但是如果部门是一张单独的表,则只需要简单的查询这张表就行。

    关联导致索引失效。

  1. 反范式的优点和缺点

    如果不需要关联表,则对大部分查询最差的情况-即使表没有使用索引-全表扫描。当数据比内存大时这可能比关联要快得多,这样避免了I/O.

  2. 混用范式化和反范式化

    在实际应用中经常需要混用,可能使用部分范式化的schema、缓存表,以及其他技巧。

    最常见的反范式化数据的方法是复制或者缓存,在不同的表中存储相同的特定列。在mysql5.0和更新版本中,可以使用触发器更新缓存值,这使得实现这样的方案变得更简单。

    在范式化的schema里通过作者的名字对消息做排序的代价将会非常高。

4.4 缓存表和汇总表

有时提升性能最好的方法是在同一张表中保存衍生的冗余数据。然而,有时也需要创建一张完全独立的汇总表或缓存表。如果能容许少量的脏数据,这是非常好的方法。

术语“缓存表”和“汇总表”没有标准的含义。我们用术语“缓存表”来表示存储那些可以比较简单地从schema其他表获取(但是每次获取的速度比较慢)数据的表(例如,逻辑上冗余的数据)。而术语“汇总表”时,则保存的是使用group by语句聚合数据的表。

为了提升查询的速度,经常会需要建一些额外的索引,增加冗余列,甚至是创建缓存表和汇总表。这些会增加些查询的负担,也需要额外的维护任务,但在设计高性能数据库时,这些都是常见的技巧:虽然写操作变得更慢了,但更显著地提高了读操作的性能。

alter table 允许使用alter column、modify column和change column语句修改列。这三种操作都是不一样的。

alter table sakila.film modify column rental_duration tinyint(3) not null default s;show status 显示这个语句做了1000次读和1000次插入操作。它拷贝了整张表到一张新表,甚至列的类型、大小和可否为NULL属性都没改变。

理论上,mysql可以跳过创建新表的步骤。列的默认值实际上存在表的.frm文件中,所以可以直接修改这个文件而不是需要改动表本身。然而mysql还没有采用这种优化的方法,所有的modify column将导致表重建。

alter table sakila.film alter column rental_duration set default 5;这个语句会直接修改.frm文件而不涉及表数据,这个操作非常快。

4.5.1 只修改.frm文件

从上面的例子我们看到修改表的.frm文件是很快的,但mysql有时候会在没有必要的时候也重建表。

执行下面技巧,需要先备份数据。

最后,alter table是让人痛苦的操作,因为在大部分情况下,它都会锁表并且重建整张表。

第五章 创建高性能索引

索引是存储引擎用于快速找到记录的一种数据结构。在数据量较小且负载较低时,不恰当的索引对性能的影响可能还不明显,但当数据量逐渐增大时,性能则会极具下降。“最优”
如果数据量很小,没有必要加索引,将数据加载到内存查找就可以。

5.1 索引的基础

创建一个包含两个列的索引,和创建两个只包含一列的索引是大不相同的。

5.1.1 mysql支持的索引类型:

5.1.1.1B-Tree索引

mysql使用的是B+树

B-Tree索引能够加快访问数据的速度,因为存储引擎不再需要进行全表扫描来获取需要的数据,取而代之的是从索引的根节点开始进行搜索。
B+更适合用来存储索引:

  1. B+树的读写代价更低(每个节点存储的关键字比B树多)
  2. B+树的查询效率更加稳定
  3. B+树更有利于数据库的全表扫描

B-Tree索引适用于

key(last_name,first_name,dob)

全健值、键值范围或键前缀查找。其中键前缀查找只适用于根据最左前缀的查找。

  • 全值匹配:全值匹配指的是和索引中的所有列进行匹配,例如和前面提到的索引可用于查找姓名为 A B、出生于1960-01-01的人。
  • 匹配最左前缀:前面提到的索引可用于查找所有姓为Allen的人,即只使用索引的第一列。
  • 匹配列前缀:也可以只匹配某一列的值的开头部分。例如用于查找以J开头的姓的人,治理也只使用了索引的第一列。
  • 匹配范围值:例如前面提到的索引可用于查找姓在Allen和Barraymore之间的人,这里也只使用了索引的第一列。
  • 精确匹配某一列并范围匹配另外一列:前面提到的索引也可用于查找所有姓为Allen,并且名字是字母K开头的人。即第一列last_name全匹配,第二列firt_name范围匹配。

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

关于B-Tree索引的限制:

  • 如果不是按照索引的最左列开始查找,联合索引则无法使用索引。例如上面例子中的索引无法用于查找名字为Bill的人,也无法查找某个特定生日的人,因为这两列都不是最左数据列。类似地,也无法查找以某个字母结尾的人。

  • 不能跳过索引中的列。

  • 如果查询中有某个列的范围查询,则其右边所有列都无法使用索引优化查找。例如有查询where last_name = ‘Smith’ and first_name like ‘J%’ and dob = ‘1976-12-23’,这个查询只能使用索引的前两列,因为这里like是一个范围条件(但是服务器可以把其余列用于其他目的)。如果范围查询列值的数量有限,那么可以通过使用多个等于条件来替代范围条件。

    也有些限制并不是B-Tree本身导致的,而是MySQL优化器和存储引擎使用索引的方式导致的,这部分限制在未来的版本可能就不再是限制了。

5.1.1.2 哈希索引

哈希索引基于哈希表实现,只有精确匹配索引所有列的查询才有效。对于每一行数据,存储引擎都会对所有的索引计算一个哈希码。

在mysql中,只有memory(默认的就是哈希索引)引擎显式支持哈希索引类型,memory引擎同时也支持B-Tree索引。memory引擎是支持非唯一哈希索引的,这在数据库里面是比较与众不同的。如果多个列的哈希值相同,索引会以链表的方式存放多个记录到同一个哈希条目中。

优势:因为索引自身只需要存储对应的哈希值,所以索引的结构十分紧凑,这也让哈希索引查找的速度非常快。

限制:

  • 哈希索引数据并不是按照索引值顺序存储的,所以也就无法用于排序。

  • 哈希索引也不支持部分索引列匹配查找,因为哈希索引始终是使用索引列的全部内容来计算哈希值。例如,在数据列(A、B)上建立哈希索引,如果查询只有数据列A,则无法使用该索引。

  • 哈希索引只支持等值比较查询,包括=,in,<=>。也不支持任何范围查询

  • 访问哈希索引的数据非常快,除非有很多哈希冲突。

    因为这些限制,哈希索引只适用于某些特定的场合。

    InnoDB引擎有一个特殊的功能叫“自适应哈希索引”。当InnoDB注意到某些索引值被适用得非常频繁时,它会在内存中基于B-Tree索引之上再创建一个哈希索引,这样就让B-Tree索引也具有哈希索引的一些优点,比如快速的哈希查找。这是一个完全自动的、内部的行为,用户无法控制或者配置,不过如果有必要,完全可以关闭该功能。

    创建自定义哈希索引。如果存储引擎不支持哈希索引,则可以模拟像InnoDB一样创建哈希索引,这可以享受一些哈希索引的便利,例如只需要很小的索引就可以为超长的键创建索引。

    例如需要存储大量的URL,并需要根据URL进行搜索查找。如果使用B-Tree进行查找,但是它使用哈希值而不是键本身进行索引查找。

    select id from url where url = ‘dddd’ and url_cc = CRC32(“dddd”);

    这样做的性能会非常高,因为mysql优化器会使用这个选择性很高而体积很小的基于url_crc列的索引来完成查找。

5.1.1.3 空间数据索引:可以用作地理数据存储。MyISAM存储引擎
5.1.1.4 全文索引:是一种特殊类型索引,它查找的是文本中的关键字词,而不是直接比较索引中的值。全文索引和其他几类索引的匹配方式完全不一样。它有很多需要注意的细节,如停用词、词干和复数、布尔搜索等。全文索引更类似于搜索引擎做的事情,而不是简单的where条件匹配。

在相同的列上同时创建全文索引和基于值的B-Tree索引不会有冲突,全文索引适用于MATCH AGAINST操作,而不是普通的where条件操作。

5.2 索引的优点

最常见的B-Tree索引,按照顺序存储数据,所以mysql可以用来做order by和group by操作。因为数据是有序的,所以B-Tree也就会将相关的列值都存储在一起。因为索引中存储量实际的列值,如下优点:

1.索引大大减少了服务器需要扫描的数据量。

2.索引可以帮助服务器避免排序和临时表

3.索引可以将随机IO变为顺序I/O

一星 : 索引将相关的记录放到一起则获得一星。

二星 : 如果索引中的数据顺序和查找中的排列顺序一致则获得二星。

三星 : 如果索引中的列包含了查询中需要的全部列则获得三星。

5.3 高性能的索引策略

5.3.1独立的列

独立的列是指索引列不能是表达式的一部分,也不能是函数的参数。例如,where a_id+1 = 7 就不能使用索引了。

5.3.2 前缀索引和索引选择性

有时候需要索引很长的字符列,这会让索引变得大且慢。一个策略是前面提到过的模拟哈希索引。另一种通常可以索引开始的部分字符,这样可以大大节约索引空间,从而提高索引效率。

索引的选择性是指,不重复的索引值(也称为基数)和数据表的记录总数的比值,索引的选择性越高则查询效率越高。

对于BLOB,TEXT或者很长的VARCHAR类型的列,必须使用前缀索引,因为mysql不允许索引这些列的完整长度。

计算合适的前缀长度的另外一个方法就是计算完整列的选择性,并使前缀的选择性接近于完整列的选择性。

select count(DISTINCT city)/count(*) from sakila.city_demo;

select count(DISTINCT LEFT(city,4))/count(*) from sakila.city_demo;

平均选择性会有极端情况。select count(*) as cnt,left(city,4)as pref from sakila.city_demo group by pref order by cnt desc limit 5;

前缀索引是一种能使索引更小、更快的有效办法,但另一个方面也有其缺点:MySQL无法使用前缀索引做order by和group by,也无法使用前缀索引做覆盖扫描。

5.3.3 多列索引

type:index_merge

索引合并策略有时候是一种优化的结果,但实际上更多说明了表上索引建得很糟糕:

  • 当出现服务器对多个索引做交互操作时(通常有多个AND条件),通常意味着需要一个包含所有相关列的多列索引,而不是多个独立的单列索引
  • 当服务器需要对多个索引做联合操作时(通常有多个or条件),通常需要耗费大量CPU和内存资源在算法的缓存、排序和合并操作上。特别是当其中有些索引的选择性不高,需要合并扫描返回的大量数据的时候。
  • 更重要的是,优化器不会把这些计算到"查询成本"中,优化器只关心随机页面读取。这会使得查询的成本被“低估”,导致该执行计划还不如直接全表扫描。这样做不但会消耗更多的CPU和内存资源,还可能影响查询的并发性,但如果是单独运行这样的查询则会往往忽略对并发性的影响。通常来说,还不如像在MySQL4.1或者更早时代一样,将查询改写成UNION的方式往往更好。
  • 如果在Explain中看到索引合并,应该好好检查一下查询和表的结构,看是不是已经是最优的。也可以通过optimizer_switch来关闭索引合并功能。也可以使用IGNORE INDEX提示让优化器忽略掉某些索引。

5.3.4 选择合适的索引列顺序

对于如何选择索引的列顺序有一个经验法则:将选择性最高的列放到索引最前列。

当不需要考虑排序和分组时,将选择性最高的列放在前面通常是很好的。这时候索引的作用只是用于where条件的查找。

后缀索引通过触发器维护,反转使用。

当出现服务器对多个索引做相交操作时,通常意味着需要一个包含所有相关列的多列索引,而不是多个单独的单列索引。

当服务器需要对多个索引做联合操作时(通常有多个OR条件)

在explain中看到有索引合并

聚簇索引并不是一种单独的索引类型,而是一种数据存储方式。但InnoDB的聚簇索引实际上在同一个结构中保存了B-Tree索引和数据行。

第六章 查询性能优化

查询优化,索引优化,库表结构

确认应用程序是否在检索大量超过需要的数据,这通常意味着访问了太多行,但有时候也可能是访问了太多的列。

确认mysql服务器是否在分析大量超过需要的数据行。

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

典型案例:

总是取出全部列:每次看到select *的时候都需要用怀疑的眼光审视,是不是真的需要返回全部的列?取出全部列,会让优化器无法完成索引覆盖扫描这类优化,还会为服务器带来额外的I/O、内存和CPU的消耗。

重复查询相同的数据:例如,在用户评论的地方需要查询用户头像的URL,那么用户多次评论的时候,就可能反复查询这个数据,比较好的方案是,当初次查询将这个数据缓存起来,需要的时候从缓存中取出,这样性能显然会更好。

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

衡量指标:

  1. 响应时间

    响应时间是两个部分之和,服务时间和排队时间。

  2. 扫描行数 理想情况下扫描的行数和返回的行数应该是相同的。

  3. 返回的行数

    一般mysql能够使用如下三种方式应用where条件,从好到坏依次为:

    • 在索引中使用where条件来过滤不匹配的记录,这是存储引擎层完成的。
    • 使用索引覆盖扫描(在Extra列中出现了Using index)来返回记录,直接从索引中过滤不需要的记录并返回命中的结果。这是在MySQL服务器层完成的,但无须再回表查询记录。
    • 从数据表中返回数据,然后过滤不满足条件的记录(在Extra列中出现using where)。这在mysql服务器层完成,mysql需要先从数据库表读出记录然后过滤。需要回表

    使用索引覆盖扫描,把所有需要的列都放到索引中,这样存储引擎无须回表获取对应行就可以返回结果了。

    6.3 重构查询的方式

    mysql内部每秒能够扫描内存中上百万行数据,相比之下,mysql响应客户端数据给客户端就慢得多了。

    分而治之:例子,删除旧的数据就是一个很好的例子。定期清除大量数据时,如果用一个大的语句一次性完成的话,则可能需要一次锁住很多数据、占满整个事务日志、耗尽系统资源、阻塞很多小的但重要的查询。

    一次删除一万行数据一般来说是一个比较高效而且对服务器影响也最小的做法。

    分解关联查询的优势:让缓存更高效

    6.4 查询执行的基础

    mysql执行一个查询的过程 在这里插入图片描述

    1.客户端发送一个查询给服务器。

    2.服务器先检查查询缓存,如果命中了缓存,则立刻返回存储到缓存中的结果。否则进入下一阶段。

    3.服务器端进行SQL解析、预处理,再由优化器生成对应的执行计划。

    4.MySQL服务器根据优化器生成的执行计划,调用存储引擎的API来执行查询。

    5.将结果返回给客户端。

    半双工:在任何一个时刻,要么是服务器向客户端发送数据,要么是客户端向服务器发送数据,这两个动作不能同时发生。

    6.5 MySQL查询优化器的局限性

    6.5.1 关联子查询

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

在这里插入图片描述

改写方法:select film.* from sakila.film inner join sakila.film_actor using(film_id) where actor_id = 1;

或者 用 exists 代替

select * from sakila.film where exists(

​ select * from sakila.film_actor where actor_id = 1 and film_actor.film_id = film.film_id

);

6.5.2 UNION的限制

在这里插入图片描述

会建立一个中间临时表,上边这个临时表只会包含40条记录了,需要注意一点:从临时表中取出数据的顺序并不是一定的,所以如果想获得正确的顺序,还需要加一个全局的order by和limit操作。

6.5.9 同一个表查询和更新

mysql不允许对同一张表进行查询和更新,这其实并不是优化器的限制,如果清楚mysql是如何执行查询的,就可以避免这种情况。

可以通过使用生成表的形式来绕过上面的限制,因为mysql只会把这个表当作一个临时表来处理。

update a inner join(

​ select type ,count(*) as num from a group by type

)as temp using(type)

set a.cnt = temp.cnt;

或者

delete from assess_car_relation

where customer_id in (

select n.customer_id from (select customer_id

from assess_car_relation t

group by customer_id

having count(customer_id)>1

)as n)

优化特定类型的查询

6.7.1 优化count()查询

count(*)

6.7.2 优化关联查询
  • 确保on或者using子句中的列上有索引。在创建索引的时候就要考虑到关联的顺序。当表A和表B用列c关联的时候,如果优化器的关联顺序是B,A,那么就不需要在B表的对应列上建上索引。没有用到索引只会带来额外的负担。一般来说,除非有其他理由,否则只需要在关联顺序中的第二个表的相应列上创建索引。
  • 确保任何的group by和order by中的表达式只涉及到一个表中的列,这样mysql有可能使用索引来优化这个过程。
6.7.3 优化子查询

尽可能使用关联查询代替。如果使用的是mysql 5.6或更新的版本或者MariaDB,则可以忽略这些建议。

6.7.4 优化group by和distinct

6.8 案例学习

  • 3
    点赞
  • 7
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值