Mysql的架构可以在多种场景中应用,并发挥好的作用。
Mysql最与众不同的特性是:将数据的存储和提取相分离。
逻辑架构图:
第二层架构是mysql的核心服务功能,包括查询解析、分析、优化、缓存以及所有的内置函数都在这一层实现。
第三层包含了存储引擎,存储引擎负责Mysql中数据的存储和提取。服务器通过API与存储引擎进行通信,这些接口屏了不同存储引擎的差异,使得这些差异对查询过程透明。
客户端连接都会在服务器进程中拥有一个线程,连接的查询只会在这个单独的线程中执行,该线程只能轮流在某个CPU核心或者CPU中运行。
Mysql会解析查询,并创建内部数据结构(解析树),然后对其进行各种优化。(重新查询、决定表的读取顺序、选择合适的索引)。
当多个查询需要在同一时刻修改数据,都会出现并发控制问题。
1.2.1、读写锁
实现两种类型的锁组成的锁系统来解决有读也有写(改)的情况。
读锁(read lock) = 共享锁(shared lock)
写锁(write lock) = 排他锁(exclusive lock)
读锁是共享的,互不阻塞的。
写锁是排他的,会阻塞其他的写锁和读锁。
1.2.2、锁粒度
提高共享资源并发性的方式就是让锁定对象更有选择性,最好:只锁定需要修改的部分数据,而不是所用的资源。
锁定的数据量越少,系统的并发程度越高。
注意:加锁,也是消耗资源的。
锁策略:锁在开销和数据的安全性之间寻求平衡。
大部分数据库系统,都是在表上,施加行级锁(row level lock)。
而mysql提供多种选择:
表锁:(table lock)
是Mysql最基本的锁策略,并且是开销最小的策略,
用户对表进行写操作(cud),需要获得写锁,这会阻塞其他用户对表进行读、写。
并且写锁比读锁有更高的优先级。
行级锁:(row lock)
是最大程度支持并发处理(同时也是最大的锁开销)。
InnoDB、XtraDB都实现了行级锁。行级锁只在存储引擎层实现,而服务层没有实现。
事务是一组原子性的SQL查询,或者说一个独立的工作单元。
系统要经过严格的ACID测试,否则空谈事务的概念是不够的。
原子性:
一致性:
隔离性
持久性:
对事务处理过程中额外的安全性,需要数据库做更多的额外工作。(和锁粒度升级会增加系统开销一样)
因此Mysql的存储引擎架构就可以发挥优势了,可以根据当前业务,来选择合适的存储引擎。
1.3.1、隔离级别
SQL标准定义了四种隔离级别:
每一种级别规定了一个事物所做的修改,哪些在事务内和事务间是可见的,哪些是不可见的。
较低级别的隔离,通常可以执行更高的并发,系统开销也更低。
事务可以读取未提交的数据,这也称为脏读(Dirty Read)。
事务开始时,只能“看见”已经提交的事务所做的修改。
换句话说:一个事务从开始到提交之前,所做的任何修改对其他的事务都是不可见的。该级别叫做:不可重复读(nonrepeatable read)。因为执行两次同样的查询,会得到不一样的结果。
解决了脏读的问题。保证了同一个事务多次读取同样记录的结果是一致的。
理论上,该级别会产生幻读(Phantom Read)问题:某个事务在读取某个范围内的记录时,另外一二事务又在该范围内插入了新的记录,当之前的事务再次读取该范围的记录时,会产生幻行(Phantom Row)
最高的隔离级别,可以解决幻读问题。它会给读取的每一行数据加锁,因此可能会导致大量的超时和锁争用的问题。
1.3.2、死锁
指两个或多个事务在同一资源上相互占用,并请求锁定对方占用的资源,从而导致恶性循环的现象。
解决这个问题,数据库系统实现了各种死锁检测和死锁超时机制。
InnoDB目前处理死锁的方法是:将持有最少行级排他锁的事务进行回滚。
1.3.3、事务日志
目的:提高事务的效率。使用事务日志,修改数据时的步骤:
因此,修改数据需要写两次磁盘。
1.3.4、Mysql中的事务
自动提交(AUTOCOMMIT)
Mysql默认采用自动提交模式。若不是显式开始一个事务,则每个查询都被当作一个事务执行提交操作。
隐式锁定:InnoDB采用的是两阶段锁定协议(tow-phase locking protocol)。在事务执行过程中,随时都可以执行锁定,锁只有在COMMIT或者ROLLBACK的时候才会释放,并且所有的锁是在同一时刻被释放。
显示锁定:InnoDB也支持特定的语句进行显式锁定:LOCK TABLES 和UNLOCK TABLES,这是在服务器层实现的,和存储引擎无关。
InnoDB的MVCC:通过每行记录后面保存两个隐藏的列来实现的。
一个保存了行的创建时间,一个是保存了行的过期时间(或删除时间)。(注:存储的不是具体的时间值,而是系统版本号)。
每开始一个新的事务,系统版本号会自动递增。
例子:在REPEATABLE READ隔离级别下,MVCC具体是如何操作的。
SELECT:
INSERT:
InnoDB为新插入的每一行保存当前系统版本号作为行版本号。
DELETE:
InnoDB为删除的每一行保存当前版本号作为行删除标记。
UPDATE:
InnoDB为插入一行新纪录,保存当前系统版本号作为行版本号,同时保存当前系统版本号到原来的行作为行删除标识。
保存这两个额外的系统版本号,是大多数读操作都可以不用加锁。使得读数据操作很简单,性能很好,并且也能保证只会读取到符合标准的行。
这是典型的:用空间换时间
MVCC只在REPEATABLE READ和READ COMMITTED两个隔离级别下工作。
READ UNCOMMITTED 总是读取最新的数据行,而不是符合当前事务版本的数据行。
SERIALIZABLE对所有读取的数据行加锁。
文件系统将Mysql的每个数据库(也称为schema)保存在数据目录下的一个子目录下,创建表时,Mysql会在数据库的子目录下创建一个和表同名的.frm文件保存表的定义。
1.5.1、InnoDB存储引擎
Mysql默认的事务型引擎。使用最多、使用最广。
InnoDB概览:InnoDB的数据存储在表空间(tablespace)中,表空间是由InnoDB管理的一个黑盒子,由一系列数据文件组成。
Mysql4.1后,InnoDB可以将每个表的数据和索引存放在单独的文件中。
InnoDB表是基于聚簇索引建立的。它对主键查询有很高的性能。但:它的二级索引(secondary index,非主键索引)包含主键列,所以如果主键列很大的话,其他索引都会很大。,因此若表中索引很多的话,主键应该尽可能的小。
1.5.2、MyISAM存储引擎
使用场景:只读数据、表较小、可以忍受修复(repair)操作。
缺点:不支持事务和行级锁,崩溃后无法完全恢复。
存储:
MyISAM:会将表存储在两个文件中:数据文件和索引文件,分别以:.MYD和.MYI为扩展名。
1.5.5、选择合适的引擎
日志型应用:对插入速度有很高的要求,数据库不能成为瓶颈。MyISAM或者Archive存储引擎对这类应用比较合适,因为他们开销低,而且插入速度很快。
如果对记录日志做分析报表,有两种高效的方案:
只读或者大部分情况只读的表:用InnoDB。因为MyISAM不会保证将数据安全地写入磁盘中。
基准测试是针对系统设计的一种压力设计。
集成式(full-stack):针对整个系统的整体测试。
单组件式(single-component):单独测试Mysql
2.2.1、测试何种指标
吞吐量:单位时间内事务处理数。测试单位:TPS
响应时间或者延迟:测试任务所需的整体时间。通常可以用百分比响应时间来替代最大响应时间。
并发性:任意时间有多少同时发生的并发请求。
可扩展性:
注意:测试应该测对用户来说最重要的指标,应该多了解一下需求,比如什么样的响应时间是可以接受的,期待多少并发性。
性能:完成某件任务所需要的时间度量。性能即响应时间。
无法测量就无法有效地优化。
优化的方法:用90%的时间测量响应时间花在哪里。而不是花时间去调参!
实际上,剖析程序程序比数据库服务器容易,并且回报更多。
3.3.1、剖析服务器负载
捕获Mysql的查询到日志文件中。
3.3.2、剖析单条查询
使用 show profile
尽量不要用试错的方式解决问题,这种方式有很大风险,并且是一种令人沮丧且低效的方式。
如果一时无法定位问题,可能是测量方式不对,测量点有问题,或者使用的工具不合适。
3.4.1、单条查询问题还是服务器问题
有三个方法可以排查:
其中观察:Threads_running,Threads_connected、Queries的值,来查看
观察线程是否有大量处于不正常状态或者其他不正常的特征。
通过以下语句,查看某个列值出现的次数:
Mysql -e ‘show processlist\G’ | grep state: |sort|uniq -c |sort -rn
将long_query_time设为0,这样使所有的查询都记录到日志中。
日志分析的魔法工具:sort|uniq|sort
推荐一个监控服务器的工具:Percona Toolkit。有了它就不用自己写脚本监控了。
1、定义性能最有效的方法是响应时间。
2、无法测量就无法有效优化,性能优化工作,需要基于高质量、全方位及完整的响应时间测量。
3、有两种消耗时间的操作:工作或等待。大多数剖析器只能测量因为工作而消耗的时间。
4、优化和提升是两回事,当继续提升的成本超过收益时,应当停止优化。
5、注意你的直觉,应该根据你的直觉来指导解决问题的思路,而不是用于确定系统问题。
决策应当尽量基于数据而不是直觉。
不管选择哪种类型的数据,以下原则有助于做出更好的选择。
1)更小的通常更好
一般情况下,尽量使用可以正确存储数据的最小数据类型。选择一个认为不会超过最小类型的数据类型。
2)简单就好
整形比字符操作代价更低。
应该使用Mysql的内建的类型而不是字符串来存储日期和时间。
用整形存储IP地址。
3)尽量避免NULL
通常情况下,最好指定列为NOT NULL。若计划在列上建索引,就应该尽量避免设计成为NULL的列。
选择具体的类型:
DATETIME和TIMESTAMP都可以存储相同类型的数据,时间和日期,精确到秒。然而:TIMESTAMP只使用DATETIME一半的存储空间。
4.1.1、整数类型
整数:TINYINT、SMALLINT、MEDIUMINT、INT、BIGINT,分别使用8,16,24,32,64位存储空间。可以存储的值的范围为:,N为存储空间位数。
整数类型还有UNSIGNED属性,表示不允许负值,这可使整数的上限提高一倍。
Mysql可以为整数类型指定宽度,例如:INT(11),对大多数应用这是没有意义的,它不会限制值的合法范围,只是规定了Mysql的一些交互工具(如:Mysql命令行客户端,用来显示字符的个数)。对存储和计算来说,INT(11)和INT(20)是相同的。
4.1.2、实数类型
实数是带有小数部分的数字。
FLOAT和DOUBLE类型支持使用标准的浮点运算进行近似计算。
DECIMAL类型用户存储精确的小数。
浮点类型在存储同样的范围的值时,通常比DECIMAI使用更小的空间。
FLOAT使用4个字节存储,DOUBLE使用8个字节。
Mysql使用DOUBLE作为内部浮点计算的类型。
浮点存储计算会带来不精确。DECIMAL精确计算的代价高。
4.1.3、字符串类型
1)varchar
用户存储可变长字符串。
Varchar使用1或2个额外的字节记录字符串长度,如果类最大长度<=255字节则用1个字节表示,否则用两个。
使用场景:
2)char
Char类型是定长的:Mysql总是根据定义的字符串长度,分配足够的空间。
使用场景:存储短的,或者所有值都接近同一个长度的。
慷慨是不明智的
使用varchar(5)和varchar(200)存储的空间是一样的,那使用短的列,有什么优势?
事实证明,更长的列消耗更多的内存,因为Mysql通常会分配固定大小的内存块来保存内部值。尤其是使用内部临时表进行排序或操作时会特别糟糕。
所以:最好是只分配真正需要的空间。
3)BLOB和TEXT类型
它们是为了存储很大的数据而设计的字符串数据类型,分别采用二进制和字符串方式存储。
4)使用枚举(ENUM)代替字符串类型
可以用枚举类型代替常用的字符串类型。
Mysql能存储的最小时间粒度为秒(MariaDB支持微秒级别)。
日期类型有如下两种:
能保存大范围的值,从1001到9999,精度为秒。使用8字节存储。它把时间封装到格式为YYYYMMDDHHMMSS整数中,与时区无关。
保存了从1970年1月1日午夜以来的秒数,和UNIX时间戳相同。使用4字节存储空间。只能表示从1970到2038年。
Mysql提供FROM_UNIXTIME()函数把时间戳转换为日期。并提供UNIX_TIMESTAMP()函数把日期转换为UNIX时间戳。
除特殊行为外,通常应该尽量使用TIMESTAMP,因为它比DATETIME空间效率更高。
有时人们会将UNIX时间戳存储为整数值,但这并不会带来任何收益。用整数保存时间戳的格式通常不方便处理,所以不推荐这样做。
4.1.6、选择标识符
使用自增整数最好。
4.1.7、特殊类型数据
应该用无符号整数存储IP地址。用小数点将地址分成四段的表示方法,只是让人们容易阅读。用整形存储能减少空间。但是可读性低。Mysql提供了两个函数进行装换:
Ip->整形:inet_aton()
整形->IP:inet_ntoa()
算法是:国际上对各国IP地址的区分中使用的ip number
a.b.c.d 的ip number是:
a * 256的3次方 + b * 256的2次方 + c * 256的1次方 + d * 256的0次方。
枚举列允许列中存储一组定义值中的单个列,集合(SET)列则允许在列中存储一组定义值中的一个或多个值。
如下反例:
这种情况该使用枚举列代替集合列。
列中避免使用NULL,可以用0,某个特殊值或空字符串作为代替。
但是当确实需要表示未知值时也不要害怕使用NULL。
注:Mysql的索引可以存储NULL值,而Oracle则不会。
范式数据中,每个事实数据出现并且只出现一次。
反范式化的数据库中,信息是冗余的,可能会存储在多个地方。
4.3.1、范式的优点和缺点
优点:
缺点:
4.3.2、反范式的优点和缺点
优点:
4.3.3、混用范式化和反范式化
完全的范式化和完全的反范式化是实验室里才有的东西。
缓存表:表示存储那些可以比较简单地从schema其他表获取(但是每次获取的速度比较慢)数据的表。
汇总表:保存的是使用GROUP BY语句聚合的表。
当重建汇总表和缓存表时,通常保证数据在操作时依然可用。这就需要通过使用“影子表”来实现。它是指一张在真实表“背后”创建的表,当完成建表操作后,可以通过一个原子的重命名操作切换影子表和原表。如下:
将my_summary这个名字分配给新建的表之前将原始的my_summary表重命名为my_summary_old,就可以在下一次重建之前一直保留旧版本的数据,如果新表有问题,可以很容易地进行快速回滚。
4.4.1、物化视图
Mysql并不原生支持物化视图。
可以使用开源工具Flexviews实现物化视图。
4.4.2、计数器表
更新计数器表会存在并发问题。
如旧表:
更新操作:
对更新一行的事务来说,这条记录上都有一个全局的互斥锁(mutex),导致这些事务只能串行执行。
要获得更高的并发更新性能,可以将计数器保存在多行中,每次选择一行进行更新。
新表:
预先在新表增加100行数据,然后随机选择一个槽(slot)进行更新
还有一个需求,每天开始一个新的计数器。表设计如下:
这个场景就不能用前面的例子那样预生成行,而要用on duplicate key update
技巧:
有一些工具可以完成影子拷贝工作:“online schema change”、Percona Toolkit、Flexviews
不是所有的alter table 操作都会引起表重建。
如修改字段默认值:
慢方法:
理论上,Mysql可以跳过创建新表的步骤。列的默认值实际上存在表的.frm文件中,所以可以直接修改这个文件而不需要改动表本身。
然而,所有MODIFY COLUMN 操作都将导致表重建。(没验证过)
快方法:
这个语句直接修改.frm文件而不涉及表数据,所以这个操作非常快。
索引(在Mysql也叫做“键(key)”)。
索引可以包含一个或多个列的值。列的顺序很重要,因为Mysql只能高效地使用索引的最左前缀列。
主键是非空的唯一索引
5.1.1、索引类型
索引是存储引擎层实现的。所以没有统一的索引标准。
Mysql支持的索引类型如下:
1)B-Tree索引
存储引擎以不同的方式使用B-Tree索引,性能各有不同。(InnoDB叫B+Tree)
MyISAM使用前缀压缩技术使得索引更小,但InnoDB则按照原数据格式进行存储。
MyISAM索引通过数据的物理位置引用被索引的行,而InnoDB则根据主键引用被索引的行。
B-Tree通常意味这所有的值都是按顺序存储的,并且每个叶子页到根的距离相同。
InnoDB的索引结构如下:
流程:
B-Tree对索引列式顺序组织存储的,很适合查找范围数据。
假设有如下数据表:
下图显示了索引是如何组织数据的存储的;
因为索引树的节点是有序的,所以索引还可以用于查询中的ORDER BY 操作。
B-Tree索引的限制:
如:where last_name = ‘Smith’ and first_name like ‘J%’ and job = ‘1976-12-23’。只能用到索引的前两列,因为like是个范围条件。
最常见的B-Tree索引,按照顺序存储数据,所以Mysql可以用来做ORDER BY 和GROUP BY操作。
三星系统(three-star system):
一星:索引将相关的记录放到一起。
二星:索引中的数据顺序和查找中的排列顺序一致。
三星:索引中的列包含了查询中需要的全部列。
索引是最好的解决方案吗?
对于非常小的表,简单的全表扫描更高效。
对于中到大型表:索引非常有效。
对于特大型表;需要一种技术可以区分出查询需要的一组数据。(如分区、使用元数据表)。
5.3.1、独立的列
如果查询中的列不是独立的,则Mysql不会使用索引。
我们应该养成简化Where条件的习惯,始终将索引列,放在单独放在比较符号的一侧。
如:这个查询无法使用actor_id列的索引
这也是一个:
5.3.2、前缀索引和索引选择性
当需要索引很长的字符列时,就要考虑前缀索引,否则索引变得大且慢,特别是对BLOB、TEXT或者很长的VARCHAR类型的类,必须要用前缀索引,因为Mysql不允许索引这些列的完整长度。
索引的选择性:不重复的索引值(也称基数,cardinality)和数据表的记录总数(#T)的比值,范围从1/#T到1之间。索引选择性越高,查询效率越高,因为选择性高的索引能在查找时过滤更多的行。
5.3.3、多列索引
当查询的where条件中有or 时或多个and时,会执行索引合并策略(or条件的联合(union)、and条件的相交(intersection),组合前两种情况的联合及相交)。
当出现索引的合并策略时,说明索引建得糟糕:
解决方案:
5.3.4、选择合适的索引列顺序
在一个多列索引中,索引列的顺序意味着索引首先按照最左列进行排序,其次是第二列,等等。所以,索引可以按照升序或者降序进行扫描,以满足精确符合列顺序的ORDER BY、GROUP BY和DISTINCT等子句的查询需求。
尽管选择性和基数的经验法则(选择性高的列放在最左边)值得去研究和分析,但一定要记住别忘了where子句的排序、分组和范围条件等其他因素。
5.3.5、聚簇索引
不是一种单独的索引类型,而是一种数据存储方式。
InnoDB的聚簇索引实际上在同一个结构中保存了B-Tree索引和数据行。
聚簇:表示数据行和相邻的键值紧凑地存储在一起。
聚簇索引中的叶子页(left page)包含了行的全部数据,但是节点页只包含了索引列。如图所示:
InnoDB通过主键聚集数据,图中被索引的列就是主键列。
如果没有定义主键,InnoDB会选择一个唯一的非空索引代替。
聚集数据的优点:
缺点:
InnoDB和MyISAM的数据分布对比
InnoDB和MyISAM对该表的存储
MyISAM的数据分布:按数据插入顺序存储在磁盘上。
MyISAM在主键索引和其他索引在结构上是一样的:
MyISAM需要独立地对行进行存储。
所以说:主键索引是一个名为PRIMAAY的唯一非空索引。
InnoDB的数据分布,因为InnoDB支持聚簇索引,所以使用非常不同的方式存储同样的数据。如下图:
在InnoDB中,聚簇索引就是表,它不仅存索引,还存了整个表的信息(col2)。
InnoDB的二级索引和聚簇索引很不同,InnoDB二级索引的叶子节点存储的不是“行指针”,而是主键值,并以此作为指向行的“指针”。该策略会减少当出现行移动或者数据页分裂时二级索引的维护工作,但会让二级索引占用更多的空间。
下图显示二级索引的分布结构图:
用一张图进行对比如下:
主键顺序最好使用:自增主键,避免用UUID。
顺序的主键什么时候造成更坏的结果?
高并发工作负载时,间隙锁的竞争、AUTO_INCREAMENT锁机制、主键的上界会成为“热点”。
5.3.6、覆盖索引
如果一个索引包含(或者说覆盖)所需要查询的字段的值,我们就称为“覆盖索引”。
优点:
覆盖索引必须存储索引列的值,因此Mysql只能使用B-Tree索引做覆盖索引。
当发起以被索引覆盖的查询时,在Explain的Extra列可以看到“Using index”的信息。
5.3.7、使用索引扫描来做排序
有两种方式生成有序的结果:1、通过排序操作。2、按索引顺序扫描(如果Explain 出来的type列的值为“index”,说明使用了索引扫描来做排序)。
只有ORDER BY子句满足索引的最左前缀要求,Mysql才会利用索引排序。(例外,order by子句不满足索引的最左前缀的要求,但是多列索引的最左列为常量时,会利用索引排序)。
5.3.8、压缩(前缀压缩)索引
MyISAM压缩索引块的方法是:
如:索引块第一个值是“perform”,第二个值是:“performance”,那么第二个值的前缀压缩后存储的是类似“7,ance”这样的形式。
5.3.9、冗余和重复索引
1、创建了索引(A,B),再创建索引(A)就是冗余索引,因为这只是前一个索引的前缀索引。(这种冗余是对B-Tree索引来说)
2、索引扩展为(A,ID),其中ID是主键,对于InnoDB来说,主键列包含在二级索引中了,所以这也是冗余的。
大多数情况都不需要冗余索引,应该尽量扩展已有的索引而不是创建新索引。
但也有出于性能方便的考虑需要冗余索引,因为扩展已有的索引会导致其变得太大,从而影响其他使用该索引的查询性能。
一般来说:增加新索引将会导致Insert、update、delete等操作的速度变慢。
5.3.10、未使用的索引
有些服务器永远不用的索引,这些事累赘,建议删除。
5.3.11、索引和锁
索引可以让查询锁定更少的行。InnoDB只有在访问行的时候才会对其加锁,而索引能够减少InnoDB的访问行数,从而减少锁的数量。
高性能:需要库表结构优化、查询优化、索引优化齐头并进。
查询的生命周期:
客户端——服务器——服务器解析——生成执行计划——执行——返回结果给客户端
执行是最重要的阶段:包括大量为了检索数据到存储引擎的调用以及调用后的数据处理(排序、分组)
对于慢查询,可以通过下面两个步骤来分析:
6.2.1、是否向数据库请求了不需要的数据
常见错误:以为Mysql只会返回需要的数据,实际上Mysql却是先返回全部结果集再进行计算。
最简单的解决方法:在查询后面加limit。
如:你想查询所有在电影academy Dinosaur中出现的演员,千万不要这样写,它会返回三个表的全部数据。
正确的方式,只取需要的列:
每次看到select * 的时候都需要用怀疑的眼光审视,是不是需要返回全部列。取全部列会让优化器无法完成索引覆盖扫描这类优化。
但是返回超过需要的列,会简化开发。
所以自己要权衡。
6.2.2、Mysql是否在扫描额外的记录
查询查询开销的三个指标
Mysql使用如下三种方式应用where条件,从好到坏依次是:
上面的思路是:分而治之
查询的执行路径如下:
6.4.1、Mysql客户端/服务器通信协议
C/S的通信协议是:半双工的。
需要了解Mysql的查询状态。
6.4.2、查询缓存
查询缓存时一个对大小写敏感的哈希查找实现,查询和缓存中的查询及时只有一个字节不同,也不会匹配缓存结果。
6.4.3、查询优化处理
通过下图,展示Mysql如何进行关联:
当不能用索引进行排序时,Mysql需要自己进行排序,如果数据量小,则在内存中进行,如果数据量大则需要你使用磁盘,不过mysql统一称为:文件排序(fiiesort)
两种排序算法:(旧)
两次传输排序:读取行指针和需要排序的字段,排序后,再根据排序结果读取所需要的数据行。
单词传输排序:(新)
先读取所需要的所有列,然后再根据给定列进行排序,然后直接返回排序结果。
在关联查询时,如果需要排序,Msyql会分两种情况处理:
6.4.4、查询执行引擎
6.4.5、返回结果给客户端
6.5.1、关联子查询
In 加子查询,性能通常非常糟糕。建议使用join或exists()等效的改写查询来获取更好的效率。
注:需要通过测试来验证对子查询的执行计划和响应时间的假设,而不要听信什么“绝对真理”
6.5.2、UNION的限制
通过显示配置sql语句,实现对优化器的提示。
常用:use index、ignore index 、force index
6.7.1、优化count()查询
Count(*)就是统计行数的标准写法。
MyISAM引擎使用count(*),且没有任何where条件时,速度会很快。
例子1:
例子2:
熟悉的困境:“快速、精确和实现简单”永远只能满足三选二。
6.7.2、优化关联查询
6.7.3、优化子查询
6.7.4、优化GROUP BY 和DISTINCT
6.7.5、优化LIMIT分页
若表很大,需要分页时,优化方法是:尽可能使用索引覆盖扫描,而不是查询所有列,然后根据需要做一次关联操作再返回所需的列。
一定要加Order by
如:select * from payment_logs order by id limit 1000000,20
可改写为:
select * from payment_logs a inner JOIN
(
select id from payment_logs order by id limit 1000000,20
) b
on a.id = b.id
6.7.7、优化union查询
优化需要三管齐下:不做、少做、快速地做。
对用户来说,分区表是一个独立的逻辑表,底层由多个物理子表组成。
分区实际上是对一组底层表的句柄对象(Handler Object)的封装。对分区表的请求,都会通过句柄对象转化成对存储引擎的接口调用。
目的:将数据按照一个较粗的粒度分在不同的表上。
使用场景:
分区的限制:
7.1.1、分区表的原理
分区表由多个相关底层表实现,这些底层表也是由句柄对象(Handler Object)表示。存储引擎管理分区表和其他表都一样,从存储引擎来看,分区底层表和普通表没区别。
分区表的索引只是在各个底层表上各自加上一个完全相同的索引。
先锁住所有底层表,再判断是否可以过滤部分分区,在访问各区数据。
先锁住——再确定——最后写入
先锁住——再确定——最后删除
先锁住——确定更新记录放哪个分区——取数更新——判断更新后的数据在哪个分区——对底层表进行写入——对原数据底层表进行删除
虽说每个操作都会“先打开并锁住所有的底层表”,但并不一定是锁住全表,如果存储引擎能够自己实现行级锁,如InnoDB,就不会锁全表,这个加锁和解锁的过程与查询类似。
7.1.2、分区表的类型
一般用按范围分区。
7.1.3、如何使用分区表
数据量很大时,B-Tree索引就不起作用了,除非是索引覆盖查询,否则数据库服务器需要根据索引扫描的结果回表。
分区可理解为索引的最初形态。
7.1.4、什么情况下会出问题
7.1.5、查询优化
使用explain partition可以观察到优化器使用了哪些分区。
视图本身是一个虚拟表、不存放任何数据。
方法一:实现视图有一个简单方法,将select结果放在一个临时表中。
Temporary table:只在当前连接可见,当关闭连接,Mysql会删除表并释放空间。
方法二:讲视图定义的Sql合并进查询SQL。
该两种算法的实现如下,建议使用合并算法。
InnoDB是目前Mysql中唯一支持外键的内置索引。
使用外键是有成本的,外键通常要求在修改数据时都要在另外一张表中多执行一次查找操作。
Mysql在服务端提供只读的、单向的游标,而且只能在存储过程或者更底层的客户端API使用。
使用场景:通过关键字的匹配来进行查询过滤,需要基于相似度的查询,而不是精确查询。
支持各种字符内容搜索:char、varchar、text
MATCH AGAINST
7.10.1、自然语言的全文索引
使用语法:match(列,【列】) against(需要查找的内容)
如下:
7.10.2、布尔全文索引
Mysql查询缓存保存查询返回的完整结果。当查询命中该缓存,Mysql会立刻返回结果,跳过解析、优化和执行阶段。
查询缓存对应用程序是完全透明的。
7.12.1、Mysql如何判断缓存命中
Mysql判断缓存命中的方法很简单:缓存存放在一个引用表中,通过一个哈希值引用。
当查询语句中有一些不确定的数据时,则不会被缓存。如函数now()、currrent_date()的查询不会被缓存。
注意:查询缓存对读写带来额外消耗:
7.12.3、什么情况下查询缓存能发挥作用
查询缓存可以由如下公式计算:Qcache_hits/(Qcache_hits+Com_select)
7.12.4、如何配置和维护查询缓存
7.12.7、查询缓存的替代方案
查询缓存工作的原则是:执行查询最快的方式是不去执行。
Mysql有大量的参数可以修改——但不应该随便去修改。通常只需要把基本配置项配置正确(大部分情况下只有很少一些参数是真正正确的),应该花更多时间在schema的优化、索引、以及查询设计上。
在类unix系统上:配置文件一般在/etc/my.cnf或者/etc/msyql/my.cnf中。
找到配置文件的路径;
Which mysqld
**/**/mysqld –verbose –help|grep -A 1 ‘Default options’
InnoDB在大多数情况下运行得很好,配置大小合适的缓冲池(Buffer Pool)和日志文件(Log File)是必须的。
8.4、配置内存使用
8.4.5、InnoDB缓冲池(Buffer Pool)
如果大部分都是InnoDB表,InnoDB缓冲池或许比其他任何东西更需要内存。
InnoDB缓冲池并不仅仅缓存索引,还缓存行的数据、自适应哈希索引、插入缓冲、锁,以及其他内部数据结构。
8.5.1、InnoDB I/O配置
InnoDB事务日志
InnoDB使用日志来减少提交事务时的开销。因为日志中已经记录了事务,就无需在每个事务提交时吧缓冲池的脏块刷新到磁盘中。
InnoDB用日志把随机I/O变成顺序I/O。一旦日志安全写到磁盘,事务就持久化了,即使变更还没写到数据文件,如果发生了糟糕的情况,InnoDB可以重放日志并且恢复已经提交的事务。
8.7.2、优化排序
最常见的两个瓶颈:CPU 和 I/O。
当找到一个限制系统的因素时,应该问问自己:“是这个部分本身的问题,还是系统中其他不合理的压力转移到这里所导致的?”
如:内存不足时,MySQL可能必须刷出缓存来腾出空间给需要的数据,然后过一会,再读回刚刚刷新的数据,本来是内存不足,去导致出现了I/O容量不足。
复制解决的基本问题是:让一台服务器的数据与其他服务器保持同步。一台主库的数据可以同步到多台备库上,备库本身也可以被配置成另外一台服务器的主库。
MySQL支持两种复制方式:基于行的复制、基于语句的复制。
实现方式:通过在主库上记录二进制日志,在备库重放日志的方式来实现异步的数据复制。
Mysql复制大部分是向后兼容的。
10.1.1、复制解决的问题
10.1.1、复制如何工作
三个步骤:
逻辑图如下:
复制用到三个线程来处理:
三个步骤:
10.3.1、基于语句的复制
主库记录那些造成数据更改的查询,当备库服务并重放这些事件时,实际上只是把主库上执行过的SQL再执行一遍。
好处:实现简单。简单地记录和执行这些语句,能够 让主备保持同步。
不足:存在一些无法被真确复制的SQL,如使用CURRENT_USER()函数的语句。
10.3.2、基于行的复制
将实际数据记录在二进制日志中。
好处:正确复制每一行。
由于无需重放更新主库数据的查询,使用基于行的复制模式能够更高效地复制数据。
如:
这个语句最终只产生3条语句,使用基于行的复制方式,在备库上开销会小很多。
但是,
这个SQL做了全表更新,使用基于行复制开销会大很多,因为每一行数据都会被记录到二进制日志中。而使用基于语句的复制方式代价会小很多。
可扩展性,表明当需要增加资源以执行更多工作时,系统能够获得划算的等同提升(equal bnad for the buck)的能力。
容量:系统在一定时间内能够完成的工作量。
EXPLAIN 获取关于查询执行计划的信息,以及如何解释输出。
在查询语句前加一个EXPLAIN这个词。
可以通过Explain partiions 显示查询将访问的分区。
Explain是一个近似的结果。
一个编号,标识Select 所属的行。
这一列显示了对应行是简单还是复杂select。
简单:SIMPLE意味着查询不包括子查询和union。
复杂:最外层标记为PRIMARY,其他部分标记为:
SUBQUERY:包含select 列表中的子查询中的select(子查询不在from 子句中)
如下sql:explain select (select 1 from login_logs limit 1) from login_logs limit 10
DERIVED:表示包含在FROM子句的子查询中的SELECT,MySQL会递归执行并将结果放到一个临时表中。服务器内部称为:派生表,因为该表是从子查询中派生来的。
如下sql:explain select id from (select * from login_logs limit 10) a
UNION:在UNION中的第二个和随后的SELECT被标记为UNION。
UNION RESULT:用来从UNION的匿名临时表搜索结果的SELECT
如下SQL:explain select id from games union all select id from games
显示对应行正在访问哪个表。
显示访问类型——Mysql决定如何查找表中的行,下面是最重要的访问方法,从最差到最优:all < index < range < ref < eq_ref < const,system < NULL
All:全表扫描。
Index:跟全表扫描一样,Mysql扫描表时按索引次序进行而不是行。主要的优点是:避免排序,缺点是:承担按索引次序读取整个表的开销。
若Extra列中看到“using index”,说明Mysql正在使用覆盖索引,它指扫描索引的数据,而不是按索引次序扫描每一行。
它比按索引次序全表扫描开销要少很多。
Range:有限制的索引扫描,它开始索引里的某一点,返回匹配这个值域的行。
Ref:索引访问,也叫索引查找,它返回所有匹配某个单个值的行。它会找到多个符合条件的行,因此,它是查找和扫描的混合体。把它叫做:ref,是因为索引要跟某个参考值相比较。这种索引访问只有使用非唯一索引或唯一索引的非唯一性前缀时才会发生。
Eq_ref:使用这种索引查找,Mysql知道最多只返回一条符合条件的记录。这种访问方法以在MySQL使用主键或唯一性索引查找时看到。
Const,system:当mysql能对查询的某部分进行优化并将其转换为一个常量时,就会使用这些访问类型。
NULL:这种访问方式意味着Mysql能在优化阶段分解查询语句,在执行阶段甚至用不着再访问表或者索引。
该列包含的是不适合在其他列显示的额外信息:
“Using index”:使用覆盖索引,避免访问表。注意:不要和访问类型index弄混。
“Using where”:Mysql服务器将在存储引擎检索行后再进行过滤。
“Using temporary”:对查询结果使用一个临时表。
“Using filesort”:对查询结果使用一个外部索引排序,而不是按照索引次序从表里读取行。
Mysql5.6 支持对类似:Update、Insert等的查询进行解释。