数据专栏

智能大数据搬运工,你想要的我们都有

科技资讯:

科技学院:

科技百科:

科技书籍:

网站大全:

软件大全:

「深度学习福利」大神带你进阶工程师,立即查看>>>
一、mysql体系结构
二、mysql索引
1、定义
索引是为了加速对表中的数据行的检索而创造的一种分散存储的数据结构
2、索引的实现
mysql的索引是由存储引擎来实现,不同的存储引擎实现方式不同。
3、存放位置
一般是存放在磁盘中
4、作用 减少扫描的数据行 可以把随机IO变成顺序IO 可以帮助我们在分组、排序等操作时,避免使用临时表
5、索引结构
我们都知道mysql的索引使用B树来实现的,那么为什么会考虑B树,不考虑其他数据结构呢?
5.1 首先我们来看普通的二叉树。
普通的二叉树不是绝对平衡的,会有一个问题,如下图:
它有可能会形成一个链表,这样就失去了二叉树的优势,需要遍历查找,性能查。
5.2 那么如果我们选择平衡二叉树呢?如下图:

平衡二叉树没有普通二叉树可能会形成链表的问题,但是它还有其他的问题。
a、它太深了 这里的太深是指树的高度,大家不要想歪了~ 如果在数据量很大的情况下,这棵树的高度很可能成千上万,因此它的IO次数也会很频繁,会严重影响性能
b、它太小了 太小指的是每一个磁盘块(节点)保存的数据量太小了 没有利用好操作磁盘IO的数据交换特性(4K) 没有利用好磁盘IO的预读能力(空间局部性原理)
这里解释下为什么说没有利用好。
1、操作系统磁盘IO的数据交换一次默认是4KB大小,但是我们的节点里面存储的数据远远小于4KB,即我们进行了一次IO但是没有完全利用这次IO的数据交换大小,造成浪费。
2、操作系统磁盘IO具备预读能力,是什么意思呢?比如我们要读取一张20KB大小的jpg图片,我们第一次读了4KB的头内容,操作系统会认为我们可能需要接下来的16KB的剩余内容,所以会一次性把剩余的内容都传输给我们。
5.3 那么如果我们选择B-Trees即多路平衡查找树呢?如下图:
这里我选择的是一个3路的平衡查找树。(即一个节点最多可以有3-1=2个元素)
可以看出同样的高度,它比平衡二叉树存储的数据多得多,减少了IO次数,同时每次IO获取的数据也更多,提升了IO效率。
5.4 最后来看下B+Trees即加强版多路平衡查找树。如下图:

它有以下几个特点: 采用闭合区间 非叶子节点不保存数据,只保存关键字和子节点的引用 关键字对应的数据保存在叶子节点中 叶子节点是顺序排列的,并且相邻的节点具有顺序引用的关系
5.5 那么我们为什么要采用B+Trees呢? 拥有多路的优势 扫表能力强 磁盘IO能力强 排序能力强 查询能力更稳定
这里我解释下为什么说B+Trees的查询能力更稳定:
B-Trees可能扫秒到第一层就返回,也可能扫秒到最后一层才返回。可能很快也可能很慢。
B+Trees每次都要扫面到最后一层,因此速度更加稳定。
6、B树在存储引擎中的实现方式
6.1、Myisam
非聚簇索引,数据和索引分别存储。 索引文件xx.MYI 数据文件xx.MYD 叶子节点保存的是引用地址而非数据
6.2、InnoDB
聚簇索引,数据和索引保存在一起 文件xx.ibd 在叶子节点保存对应的所有数据 以主键索引来组织数据,没有主键的话,会帮我们隐式创建主键索引 辅助索引不存地址,存主键,这样便于维护
7、列的离散性 列的离散性在索引中是一种很重要的指标。 列的离散性 x = count(distinct col) : count(col) 比例越大,离散性越高,选择性就越好 下面我们看个例子来理解:
name的列的离散性 x1 = 5 : 5 = 1 sex的列的离散性 x2 = 2 : 5 = 0.4 x1>x2,所以sex的列的离散性低,可选择性差。 可选择性差是什么意思呢?比如有如上100W的数据,现在我们要查找sex=男的,那么在索引中我们可选择的范围太大了,因为只有男或者女,查询效率就很低 在mysql查询优化器中,如果列的离散性低的话,可能就不走索引,直接全表扫描
8、联合索引
8.1 建立联合索引的原则: 经常用的列优先 离散性高的列优先 宽度小的列优先
8.2 适用性: 如果不是最左匹配,则无法使用联合索引 where id = ? and age > ? 第二列是范围判断的,走联合索引 where id > ? and age = ? 第一列是范围判断的,不走联合索引
9、覆盖索引 定义:如果查询的列可以通过索引节点的关键字直接返回,则称之为覆盖索引 ex: index_name
覆盖:select name from user where name= ?
非覆盖:select * from user where name= ? 覆盖索引可以减少数据库IO操作,不用再走到B树的叶子节点获取数据,而是在子节点就可以获取关键字进行返回。
10、建立索引的原则 索引不易建多:维护B+Trees成本高,插入、更新、删除等操作要做很多逻辑判断 索引列的长度不易过长:可以有效减少索引的磁盘占用提高查询效率
数据库
2019-05-05 16:20:00
「深度学习福利」大神带你进阶工程师,立即查看>>>
一、提示(Hint)概述
1、为什么引入Hint?
Hint是Oracle数据库中很有特色的一个功能,是很多DBA优化中经常采用的一个手段。那为什么Oracle会考虑引入优化器呢?基于代价的优化器是很聪明的,在绝大多数情况下它会选择正确的优化器,减轻DBA的负担。
但有时它也聪明反被聪明误,选择了很差的执行计划,使某个语句的执行变得奇慢无比。此时就需要DBA进行人为的干预,告诉优化器使用指定的存取路径或连接类型生成执行计划,从而使语句高效地运行。Hint就是Oracle提供的一种机制,用来告诉优化器按照告诉它的方式生成执行计划。
2、不要过分依赖Hint
当遇到SQL执行计划不好的情况,应优先考虑统计信息等问题,而不是直接加Hint了事。如果统计信息无误,应该考虑物理结构是否合理,即没有合适的索引。只有在最后仍然不能SQL按优化的执行计划执行时,才考虑Hint。
毕竟使用Hint,需要应用系统修改代码,Hint只能解决一条SQL的问题,并且由于数据分布的变化或其他原因(如索引更名)等,会导致SQL再次出现性能问题。
3、Hint的弊端 Hint是比较"暴力"的一种解决方式,不是很优雅。需要开发人员手工修改代码。 Hint不会去适应新的变化。比如数据结构、数据规模发生了重大变化,但使用Hint的语句是感知变化并产生更优的执行计划。 Hint随着数据库版本的变化,可能会有一些差异、甚至废弃的情况。此时,语句本身是无感知的,必须人工测试并修正。
4、Hint与注释关系
提示是Oracle为了不破坏和其他数据库引擎之间对SQL语句的兼容性而提供的一种扩展功能。Oracle决定把提示作为一种特殊的注释来添加。它的特殊性表现在提示必须紧跟着DELETE、INSERT、UPDATE或MERGE关键字。
换句话说,提示不能像普通注释那样在SQL语句中随处添加。且在注释分隔符之后的第一个字符必须是加号。在后面的用法部分,会详细说明。
5、Hint功能
Hint提供的功能非常丰富,可以很灵活地调整语句的执行过程。通过Hint,我们可以调整: 优化器类型 优化器优化目标 数据读取方式(访问路径) 查询转换类型 表间关联的顺序 表间关联的类型 并行特性 其他特性
二、Hint用法
1、语法
1)关键字说明 DELETE、INSERT、SELECT和UPDATE是标识一个语句块开始的关键字,包含提示的注释只能出现在这些关键字的后面,否则提示无效。 "+"号表示该注释是一个提示,该加号必须立即跟在"/*"的后面,中间不能有空格。 hint是下面介绍的具体提示之一,如果包含多个提示,则每个提示之间需要用一个或多个空格隔开。 text是其它说明hint的注释性文本
2)提示中的错误
提示中的语法错误不会报错,如果解析器不能解析它,就会把它看做一个普通注释处理。这也是容易造成困惑的一点,使用的Hint到底是否起效?可以采用一些手段,检查提示的有效性。需要注意的是,那些语法正确但引用对象错误的提示是不会被报告的。 explain plan + dbms_xplan
使用dbms_xplan输出中的note选项。 10132事件
在10g中,这个事件产生的输出文档的末尾有一部分内容专门讲提示。通过它可以检查两个方面:一是每个用到的提示都会被列出来。如果漏掉了哪个,就说明这个提示没有被识别;二是检查是否有一些信息指明了出现提示错误(如果出错,err值将大于0)。
3)提示中的对象
SELECT /*+ INDEX(table_name index_name) */ ... table_name是必须要写的,且如果在查询中使用了表的别名,在hint也要用表的别名来代替表名。 index_name可以不必写,Oracle会根据统计值选一个索引。 如果索引名或表名写错了,那这个hint就会被忽略。
如果指定对象是视图,需要按此方法指定。/*+hint view.table ...*/,其中table是view中的表。
一个很常见的错误时,在使用提示的时候最易犯的错误是与表的别名有关。正确的规则是,当在提示中使用表时,只要表有别名就应该使用别名而不是表名。
2、提示的作用域 查询块
初始化参数提示对整个SQL语句起作用,其他的提示仅仅对查询块起作用。仅仅对单个查询块起作用的提示,必须在它控制的查询块内指定。
例外 - 全局提示
可以使用点号引用包含在其他查询块(假设这些块已命名)中的对象。全局提示的语法可以支持两层以上的引用,对象间必须用点号分隔。
命名查询块
既然where子句中的子查询是没有命名的,它们的对象就不能被全局提示引用。为了解决这个问题,10g中使用了另一种方法来解决-命名查询块。查询优化器可以给每个查询生成一个查询块名,而且还可以使用提示qb_name手工为每个查询块命名。大多数提示都可以通过参数来指定在那个查询块中有效。
*在提示中通过@来引用一个查询块。
3、提示数据字典
Oracle在11g的版本中提供了一个数据字典—V$SQL_HINT。通过这个数据字典可以看到提示的出现版本、概要数据版本、SQL特性以及相反提示等。
INVERSE
这个hint相反操作的hint。 VERSION
代表着这个hint正式公布引入的版本。
三、Hint分类
1、和优化器相关的
当对优化器为某个语句所制定的基本执行计划不满意时,最好的办法就是通过提示来转换优化器的模式,并观察其转换后的结果,看是否已经达到期望程度。如果只通过转换优化器的模式就可以获得非常好的执行计划,则就没有必要额外使用更为复杂的提示了。 OPT_PARAM
这个提示的作用就是使我们在某条语句中指定某个系统参数值。 ALL_ROWS
为实现查询语句整体最优化而引导优化器制定最少成本的执行计划。这个提示会使优化器选择一条可最快检索所有查询行的路径,而代价就是在检索一行数据时,速度很慢。 FIRST_ROWS
为获得最佳响应时间而引导优化器制定最少成本的执行计划。这个提示会使优化器选择可最快检索出查询的第一行(或指定行)数据的路径,而代价就是检索很多行时速度就会很慢。利用FIRST_ROWS来优化的行数,默认值为1,这个值介于10到1000之间,这个使用FIRST_ROWS(n)的新方法是完全基于代价的方法。它对n很敏感,如果n值很小,CBO就会生成包含嵌套循环以及索引查找的计划;如果n很大,CBO会生成由哈希连接和全表扫描组成的计划(类似ALL_ROWS)。 CHOOSE
依据SQL中所使用到的表的统计信息存在与否,来决定使用RBO还是CBO。在CHOOSE模式下,如果能够参考表的统计信息,则将按照ALL_ROWS方式执行。除非在查询中的所有表都没有经过分析,否则choose提示会对整个查询使用基于代价的优化。如果在多表连接中有一个表经过分析过,那么就会对整个查询进行基于代价的优化。 RULE
使用基于规则的优化器来实现最优化执行,即引导优化器根据优先顺序规则来决定查询条件中所使用到的索引或运算符的执行顺序来制定执行计划。这个提示强制oracle优先使用预定义的一组规则,而不是对数据进行统计;同时该提示还会使这个语句避免使用其他提示,除了DRIVING_SITE和ORDERED(不管是否进行基于规则的优化,这两个提示都可使用)。
2、和访问路径相关的 FULL
告诉优化器通过全表扫描方式访问数据。这个提示只对所指定的表进行全表扫描,而不是查询中的所有表。FULL提示可以改善性能。这主要是因为它改变了查询中的驱动表,而不是因为全表扫描。在使用其他某些提示时,也必须使用FULL提示。只有访问整个表时,才可利用CACHE提示将表进行缓存。并行组中的某些提示也必须使用全表扫描。 CLUSTER
引导优化器通过扫描聚簇索引来从索引表中读取数据。 HASH
引导优化器按照哈希扫描的方式从表中读取数据。 INDEX
告诉优化器对指定表通过索引的方式访问数据。当访问数据会导致结果集不完整时,优化器将忽略这个Hint。 NO_INDEX
告诉优化器对指定表不允许使用索引。这个提示会禁止优化器使用指定索引。可以在删除不必要的索引之前在许多查询中禁止索引。如果使用了NO_INDEX,但是没有指定任何索引,则会执行全表扫描。如果对某个索引同时使用了NO_INDEX和会之产生冲突的提示(如INDEX),这时两个提示都会被忽略掉。 INDEX_ASC
利用索引从表中读取数据时,引导优化器对提示中所指定索引的索引列值按照升序使用范围扫描。 INDEX_COMBINE
告诉优化器强制选择位图索引。这个提示会使优化器合并表上的多个位图索引,而不是选择其中最好的索引(这是INDEX提示的用途)。还可以使用index_combine指定单个索引(对于指定位图索引,该提示优先于INDEX提示)。对于B树索引,可以使用AND_EQUAL提示而不是这个提示。 INDEX_JOIN
索引关联,当谓词中引用的列上都有索引的时候,可以通过索引关联的方式来访问数据。这个提示可以将同一个表的各个不同索引进行合并,这样就只需要访问这些索引就可以了,节省了回表查询的时间。但只能在基于代价的优化器中使用该提示。这个提示不仅允许只访问表上的索引,这样可以扫描更少的代码块,并且它比使用索引并通过rowid扫描整个表快5倍。 INDEX_DESC
利用索引从表中读取数据时,引导优化器对提示中所指定索引的索引列值按照降序使用范围扫描。 INDEX_FFS
告诉优化器以INDEX FFS(index fast full scan)的方式访问数据。INDEX_FFS提示会执行一次索引的快速全局扫描。这个提示只访问索引,而不是对应的表。只有查询需要检索的信息都在索引上时,才使用这个提示。特别在表有很多列时,使用该提示可以极大地改善性能。 INDEX_SS
强制使用index skip scan的方式访问索引。当在一个联合索引中,某些谓词条件并不在联合索引的第一列时(或者谓词并不在联合索引的第一列时),可以通过index skip scan来访问索引获得数据。当联合索引第一列的唯一值很少时,使用这种方式比全表扫描的方式效率要高。
3、和查询转换相关的 USE_CONCAT
将含有多个OR或者IN运算符所连接起来的查询语句分解为多个单一查询语句,并为每个单一查询语句选择最优化查询路径,然后再将这些最优化查询路径结合在一起,以实现整体查询语句的最优化目的。只有在驱动查询条件中包含OR的时候,才可以使用该提示。 NO_EXPAND
引导优化器不要为使用OR运算符号(或IN运算符)的条件制定相互结合的执行计划。正好和USE_CONCAT相反。 REWRITE
当表连接的对象是数据量比较大的表或者需要获得使用统计函数处理过的结果时,为了提高执行速度可预先创建物化视图。当用户要求查询某个查询语句时,优化器会在从表中和从物化视图中读取数据的两种方法中选择一个更有效的方法来读取数据。该执行方法称之为查询重写。使用REWRITE提示引导优化器按照该方式执行。 MERGE
为了能以最优方式从视图或者嵌套视图中读取数据,通过变换查询语句来直接读取视图使用的基表数据,该过程被称之为视图合并。不同的情况其具体使用类型也有所不同。该提示主要在视图未发生合并时被使用。尤其是对比较复杂的视图或者嵌套视图(比如使用了GROUP BY或DISTINC的视图)使用该提示,有时会取得非常好的效果。 UNNEST
提示优化器将子查询转换为连接的方式。也就是引导优化器合并子查询和主查询并且将其向连接类型转换。 NO_UNNEST
引导优化器让子查询能够独立地执行完毕之后再跟外围的查询做FILTER。 PUSH_PRED
使用该提示可以将视图或嵌套视图以外的查询条件推入到视图之内。 NO_PUSH_PRED
使用该提示确保视图或嵌套视图以外的查询条件不被推入到视图内部。 PUSH_SUBQ
使用该提示引导优化器为不能合并的子查询制定执行计划。不能合并的子查询被优先执行之后,该子查询的执行结果将扮演缩减主查询数据查询范围的提供者角色。通常在无法执行子查询合并的情况下,子查询扮演的都是检验者角色,所以子查询一般被放在最后执行。在无法被合并的子查询拥有较少的结果行,或者该子查询可以缩减主查询查询范围的情况下,可以使用该提示引导优化器最大程度地将该子查询放在前面执行,以提高执行速度。但如果子查询执行的是远程表或者排序合并连接的一部分连接结果,则该提示将不起任何作用。 NO_PUSH_SUBQ
使用该提示将引导优化器将不能实现合并的子查询放在最后执行。在子查询无法缩减主查询的查询范围,或者执行子查询开销较大的情况下,将这样的子查询放在最后执行可以在某种程度上提高整体的执行效率。也就是说,尽可能地使用其他查询条件最大程度地缩减查询范围之后,再执行子查询。
4、和表连接顺序相关的
这些提示可以调整表连接的顺序。调整表连接的顺序并不是只能使用这些提示,在嵌套循环连接方式中也可以让提示来引导优化器使用由驱动查询条件所创建的索引。然而,该方法只有在使用的索引和表连接顺序同时被调整的情况下才比较有效。一般而言,这些提示主要在执行多表连接和表之间的连接顺序比较混乱的情况下才使用,也在排序合并连接或哈希连接方式下,为引导优化器优先执行数据量比较少得表时使用。 LEADING
在一个多表关联的查询中,这个Hint指定由哪个表作为驱动表,即告诉优化器首先要访问那个表上的数据。引导优化器使用LEADING指定的表作为表连接顺序中的第一个表。该提示既与FROM中所描述的表的顺序无关,也与作为调整表连接顺序的ORDERED提示不同,并且在使用该提示时并不需要调整FROM中所描述的表的顺序。当该提示与ORDERED提示同时使用时,该提示被忽略。
这个提示类似ORDERED提示,它允许指定驱动查询的表,然后由优化器来判断下一个要访问的表。如果使用这个提示指定多张表,那么就可以忽略这个提示。 ORDERED
引导优化器按照FROM中所描述的表的顺序执行连接。如果和LEADING提示被一起使用,则LEADING提示将被忽略。由于ORDERED只能调整表连接的顺序并不能改变表连接的方式,所以为了改变表的连接方式,经常将USE_NL、USE_MERGE提示与ORDERED提示放在一起使用。
5、和表连接操作相关的 USE_NL
使用该提示引导优化器按照嵌套循环连接方式执行表连接。它只是指出表连接的方式,对于表连接顺序不会有任何影响。 USE_MERGE
引导优化器按照排序合并连接方式执行连接。在有必要的情况下,推荐将该提示与ORDERED提示一起使用。提示通常用于获得查询的最佳吞吐量。假设将两个表连接在一起,从每个表返回的行集将被排序,然后再被合并(也就是合并排序),从而组成最终的结果集。由于每个行先被排序之后才进行合并,所以在给定查询中检索所有行时,速度将会最快。如果需要以最快速度返回第一行,就应该使用USE_NL提示。 USE_HASH
该提示引导优化器按照哈希连接方式执行连接。在执行哈希连接时,如果由于某一边的表比较小,从而可以在内存中实现哈希连接,那么就能够获得非常好的执行速度。由于在大部分情况下优化器会通过对统计信息的分析来决定Build Input和Prove Input,所以建议不要使用ORDERED提示随意改变表的连接顺序。但是当优化器没能做出正确判断时,或者像从嵌套视图中所获得的结果集合那样不具备统计信息时,可以使用该提示。
6、和并行相关的 PARALLEL
指定SQL执行的并行度,这个值将会覆盖表自身设定的并行度。如果这个值为default,CBO使用系统参数。从表中读取大量数据和执行DML操作时使用该提示来指定SQL的并行操作。
一般情况下需要在该提示中指定将要使用的并行线程个数。如果在该提示中没有指定并行度的个数,则优化器将使用PARALLEL_THREADS_PER_CPU参数所指定的值进行自动计算。如果在定义表时指定了PARALLEL,那么在能够使用并行操作的情况下,即使没有使用该提示,优化器也会按照指定的并行级别选择并行操作。
但是如果想在DELETE、INSERT、UPDATE、MERGE等DML操作中使用并行操作,则必须要在会话中设置ALTER SESSION ENABLE PARALLEL DML。在某个会话中所设置的并行级别也可以被引用在内部的GROUP BY或者排序操作中。在并行操作中如果出现了某个限制要素,则该提示将被忽略。 NOPARALLEL/NO_PARALLEL
在SQL语句禁止使用并行。在有些版本中用NO_PARALLEL提示来代替NOPARALLEL提示。 PQ_DISTRIBUTE
为了提高并行连接的执行速度,使用该提示来定义使用何种方法在主从进程之间(例如生产者进程和消费者进程)分配各连接表的数据行。 PARALLEL_INDEX
为了按照并行操作的方式对分区索引进行索引范围扫描而使用该提示,并且可以指定进程的个数。
7、其他相关的 APPEND
让数据库以直接加载的方式(direct load)将数据加载入库。这个提示不会检查当前是否有插入所需要的块空间,相反它会直接将数据添加到新块中。这样会浪费空间,但可以提高插入的性能。需要注意的是,数据将被存储在HWM之上的位置。 APPEND_VALUES
在11.2中,Oracle新增了APPEND_VALUES提示,使得INSERT INTO VALUES语句也可以使用直接路径插入。 CACHE
在全表扫描之后,数据块将留在LRU列表的最活跃端。如果设置表的CACHE属性,它的作用和HINT一样。这个提示会将全表扫描全部缓存到内存中。如果表很大,会占用大量内存。因此适用于用户经常访问的较小的表。 NOCACHE
引导优化器将通过全表扫描方式获取的数据块缓存在LRU列表的最后位置,这样可以让数据库实例缓存中的这些数据块被优先清除。这是优化器在Buffer Cache中管理数据块的默认方法(仅针对全表扫描)。 QB_NAME
使用该提示为查询语句块命名,在其他查询语句块可以直接使用该查询语句块的名称。 DRIVING_SITE
这个提示在分布式数据库操作中有用。指定表是处理连接所在的位置。可以限制通过网络处理的信息量。此外,还可以建立远程表的本地视图来限制从远程站点检索的行。本地视图应该有where子句,从而视图可以在将行发送回本地数据库之前限制从远程数据库返回的行。 DYNAMIC_SAMPLING
提示SQL执行时动态采样的级别。这个级别为0~10,它将覆盖系统默认的动态采样级别。等级越高,所获得统计信息的准确率越高。该提示的功能就是为了确保将动态采样原理应用在单个SQL中。 AND_EQUAL
这个提示会使优化器合并表上的多个索引,而不是选择其中最好的索引(这是INDEX提示的用途)。这个提示与前面的INDEX_JOIN提示有区别,以此指定的合并索引随后需访问表,而INDEX_JOIN提示则只需访问索引。如果发现需经常用到这个提示,可能需要删除这些单个索引而改用一个组合索引。需要查询条件里面包括所有索引列,然后取得每个索引中得到的rowid列表。然后对这些对象做merge join,过滤出相同的rowid后再去表中获取数据或者直接从索引中获得数据。在10g中,and_equal已经废弃了,只能通过hint才能生效。 CARDINALITY
向优化器提供对某个查询语句的整体或部分的预测基数值,并通过参考该基数值来为查询语句制定执行计划。如果在该提示中没有指定表的名称,则该基数值将被视为从该查询语句所获得的最终结果行数。
四、Hint使用示例
下面通过一个例子说明一下提示的使用及在什么情况下提示会被忽略。
1、构建表
2、使用INDEX提示
*在某些情况下,如果CBO认为Hint会导致错误结果,那么Hint则会忽略。该例子中因为ID字段可能为空,而索引是保存空值的,因此count(*)使用索引将导致错误的结果,故而使用了全表扫描,忽略了Hint。
3、使用INDEX提示(非空字段)
*ID字段不可为空,因此COUNT可用索引扫描的方式处理,Hint生效了。
作者:韩锋
来源: 宜信技术学院 ( http://college.creditease.cn/)
数据库
2019-05-05 14:34:00
「深度学习福利」大神带你进阶工程师,立即查看>>>
oracle system登录密码错误,修改system密码:
cmd >> sqlplus / as sysdba
sql >> alter user system account unlock;
sql >> alter user system identified by 123456;
ORA-01031:insufficient privileges报错
使用本机用户免登录修改密码时,碰到上述错误原因是没有把计算机用户添加进来,解决方案:
https://jingyan.baidu.com/article/e8cdb32b21b3c437042bad7f.html
////////////////////////////////////////////////////////////////////////////////////
数据库
2019-05-05 14:15:00
「深度学习福利」大神带你进阶工程师,立即查看>>>
作者:龙恒
对于一个大型复杂的系统来说,通常包含多个模块或多个组件构成,模拟各个子系统的故障是测试中必不可少的环节,并且这些故障模拟必须做到无侵入地集成到自动化测试系统中,通过在自动化测试中自动激活这些故障点来模拟故障,并观测最终结果是否符合预期结果来判断系统的正确性和稳定性。如果在一个分布式系统中需要专门请一位同事来插拔网线来模拟网络异常,一个存储系统中需要通过破坏硬盘来模拟磁盘损坏,昂贵的测试成本会让测试成为一场灾难,并且难以模拟一些需要精细化控制的的测试。所以我们需要一些自动化的方式来进行确定性的故障测试。
Failpoint 项目 就是为此而生,它是 FreeBSD failpoints 的 Golang 实现,允许在代码中注入错误或异常行为, 并由环境变量或代码动态激活来触发这些异常行为。Failpoint 能用于各种复杂系统中模拟错误处理来提高系统的容错性、正确性和稳定性,比如: 微服务中某个服务出现随机延迟、某个服务不可用。 存储系统磁盘 I/O 延迟增加、I/O 吞吐量过低、落盘时间长。 调度系统中出现热点,某个调度指令失败。 充值系统中模拟第三方重复请求充值成功回调接口。 游戏开发中模拟玩家网络不稳定、掉帧、延迟过大等,以及各种异常输入(外挂请求)情况下系统是否正确工作。 ……
为什么要重复造轮子?
etcd 团队在 2016 年开发了 gofail 极大地简化了错误注入,为 Golang 生态做出了巨大贡献。我们在 2018 年已经引入了 gofail 进行错误注入测试,但是我们在使用中发现了一些功能性以及便利性的问题,所以我们决定造一个更好的「轮子」。
如何使用 gofail 使用注释在程序中注入一个 failpoint: // gofail: var FailIfImportedChunk int // if merger, ok := scp.merger.(*ChunkCheckpointMerger); ok && merger.Checksum.SumKVS() >= uint64(FailIfImportedChunk) { // rc.checkpointsWg.Done() // rc.checkpointsWg.Wait() // panic("forcing failure due to FailIfImportedChunk") // } // goto RETURN1 // gofail: RETURN1: // gofail: var FailIfStatusBecomes int // if merger, ok := scp.merger.(*StatusCheckpointMerger); ok && merger.EngineID >= 0 && int(merger.Status) == FailIfStatusBecomes { // rc.checkpointsWg.Done() // rc.checkpointsWg.Wait() // panic("forcing failure due to FailIfStatusBecomes") // } // goto RETURN2 // gofail: RETURN2: 使用 gofail enable 命令将注释转换为代码: if vFailIfImportedChunk, __fpErr := __fp_FailIfImportedChunk.Acquire(); __fpErr == nil { defer __fp_FailIfImportedChunk.Release(); FailIfImportedChunk, __fpTypeOK := vFailIfImportedChunk.(int); if !__fpTypeOK { goto __badTypeFailIfImportedChunk} if merger, ok := scp.merger.(*ChunkCheckpointMerger); ok && merger.Checksum.SumKVS() >= uint64(FailIfImportedChunk) { rc.checkpointsWg.Done() rc.checkpointsWg.Wait() panic("forcing failure due to FailIfImportedChunk") } goto RETURN1; __badTypeFailIfImportedChunk: __fp_FailIfImportedChunk.BadType(vFailIfImportedChunk, "int"); }; /* gofail-label */ RETURN1: if vFailIfStatusBecomes, __fpErr := __fp_FailIfStatusBecomes.Acquire(); __fpErr == nil { defer __fp_FailIfStatusBecomes.Release(); FailIfStatusBecomes, __fpTypeOK := vFailIfStatusBecomes.(int); if !__fpTypeOK { goto __badTypeFailIfStatusBecomes} if merger, ok := scp.merger.(*StatusCheckpointMerger); ok && merger.EngineID >= 0 && int(merger.Status) == FailIfStatusBecomes { rc.checkpointsWg.Done() rc.checkpointsWg.Wait() panic("forcing failure due to FailIfStatusBecomes") } goto RETURN2; __badTypeFailIfStatusBecomes: __fp_FailIfStatusBecomes.BadType(vFailIfStatusBecomes, "int"); }; /* gofail-label */ RETURN2:
gofail 使用中遇到的问题 使用注释的方式在代码中注入 failpoint,代码容易出错,并且没有编译器检测。 只能全局生效,大型项目为了缩短自动化测试的时间会引入并行测试,不同并行任务之间会存在干扰。 需要写一些 hack 代码来避免一些不必要的错误日志,比如如上代码,必须要写 // goto RETURN2 和 // gofail: RETURN2: ,并且中间必须添加一个空行,至于原因可以看 generated code 逻辑。
我们要设计一个什么样子的 failpoint?
理想的 failpoint 实现应该是什么样子?
理想中的 failpoint 应该是使用代码定义并且对业务逻辑无侵入,如果在一个支持宏的语言中 (比如 Rust),我们可以定义一个 fail_point 宏来定义 failpoint: fail_point!("transport_on_send_store", |sid| if let Some(sid) = sid { let sid: u64 = sid.parse().unwrap(); if sid == store_id { self.raft_client.wl().addrs.remove(&store_id); } })
但是我们遇到了一些问题: Golang 并不支持 macro 语言特性。 Golang 不支持编译器插件。 Golang tags 也不能提供一个比较优雅的实现 ( go build --tag="enable-failpoint-a" )。
Failpoint 设计准则 使用 Golang 代码定义 failpoint,而不是注释或其他形式。 Failpoint 代码不应该有任何额外开销: 不能影响正常功能逻辑,不能对功能代码有任何侵入。 注入 failpoint 代码之后不能导致性能回退。 Failpoint 代码最终不能出现在最终发行的二进制文件中。 Failpoint 代码必须是易读、易写并且能引入编译器检测。 最终生成的代码必须具有可读性。 生成代码中,功能逻辑代码的行号不能发生变化(便于调试)。 支持并行测试,可以通过 context.Context 控制一个某个具体的 failpoint 是否激活。
Golang 如何实现一个类似 failpoint 宏?
宏的本质是什么?如果追本溯源,发现其实可以通过 AST 重写在 Golang 中实现满足以上条件的 failpoint,原理如下图所示:
[图片上传失败...(image-6d3dee-1557978922311)]
对于任何一个 Golang 代码的源文件,可以通过解析出这个文件的语法树,遍历整个语法树,找出所有 failpoint 注入点,然后对语法树重写,转换成想要的逻辑。
相关概念
Failpoint
Failpoint 是一个代码片段,并且仅在对应的 failpoint name 激活的情况下才会执行,如果通过 failpoint.Disable("failpoint-name-for-demo") 禁用后,那么对应的的 failpoint 永远不会触发。所有 failpoint 代码片段不会编译到最终的二进制文件中,比如我们模拟文件系统权限控制: func saveTo(path string) error { failpoint.Inject("mock-permission-deny", func() error { // It's OK to access outer scope variable return fmt.Errorf("mock permission deny: %s", path) }) }
Marker 函数
AST 重写阶段标记需要被重写的部分,主要有以下功能: 提示 Rewriter 重写为一个相等的 IF 语句。 标记函数的参数是重写过程中需要用到的参数。 标记函数是一个空函数,编译过程会被 inline,进一步被消除。 标记函数中注入的 failpoint 是一个闭包,如果闭包访问外部作用域变量,闭包语法允许捕获外部作用域变量,则不会出现编译错误,同时转换后的的代码是一个 IF 语句,IF 语句访问外部作用域变量不会产生任何问题,所以闭包捕获只是为了语法合法,最终不会有任何额外开销。 简单、易读、易写。 引入编译器检测,如果 Marker 函数的参数不正确,程序不能通过编译的,进而保证转换后的代码正确性。
目前支持的 Marker 函数列表: func Inject(fpname string , fpblock func(val Value)) {} func InjectContext(fpname string , ctx context.Context , fpblock func(val Value)) {} func Break(label ...string) {} func Goto(label string) {} func Continue(label ...string) {} func Return(results ...interface{}) {} func Fallthrough() {} func Return(results ...interface{}) {} func Label(label string) {}
如何在你的程序中使用 failpoint 进行注入?
最简单的方式是使用 failpoint.Inject 在调用的地方注入一个 failpoint,最终 failpoint.Inject 调用会重写为一个 IF 语句,其中 mock-io-error 用来判断是否触发, failpoint-closure 中的逻辑会在触发后执行。 比如我们在一个读取文件的函数中注入一个 I/O 错误: failpoint.Inject("mock-io-error", func(val failpoint.Value) error { return fmt.Errorf("mock error: %v", val.(string)) })
最终转换后的代码如下: if ok, val := failpoint.Eval(_curpkg_("mock-io-error")); ok { return fmt.Errorf("mock error: %v", val.(string)) }
通过 failpoint.Enable("mock-io-error", "return("disk error")") 激活程序中的 failpoint,如果需要给 failpoint.Value 赋一个自定义的值,则需要传入一个 failpoint expression,比如这里 return("disk error") ,更多语法可以参考 failpoint 语法 。
闭包可以为 nil ,比如 failpoint.Enable("mock-delay", "sleep(1000)") ,目的是在注入点休眠一秒,不需要执行额外的逻辑。 failpoint.Inject("mock-delay", nil) failpoint.Inject("mock-delay", func(){})
最终会产生以下代码: failpoint.Eval(_curpkg_("mock-delay")) failpoint.Eval(_curpkg_("mock-delay"))
**如果我们只想在 failpoint 中执行一个 panic,不需要接收 failpoint.Value ,则我们可以在闭包的参数中忽略这个值。**例如: failpoint.Inject("mock-panic", func(_ failpoint.Value) error { panic("mock panic") }) // OR failpoint.Inject("mock-panic", func() error { panic("mock panic") })
最佳实践是以下这样: failpoint.Enable("mock-panic", "panic") failpoint.Inject("mock-panic", nil) // GENERATED CODE failpoint.Eval(_curpkg_("mock-panic"))
为了可以在并行测试中防止不同的测试任务之间的干扰,可以在 context.Context 中包含一个回调函数,用于精细化控制 failpoint 的激活与关闭 : failpoint.InjectContext(ctx, "failpoint-name", func(val failpoint.Value) { fmt.Println("unit-test", val) })
转换后的代码: if ok, val := failpoint.EvalContext(ctx, _curpkg_("failpoint-name")); ok { fmt.Println("unit-test", val) }
使用 failpoint.WithHook 的示例 : func (s *dmlSuite) TestCRUDParallel() { sctx := failpoint.WithHook(context.Backgroud(), func(ctx context.Context, fpname string) bool { return ctx.Value(fpname) != nil // Determine by ctx key }) insertFailpoints = map[string]struct{} { "insert-record-fp": {}, "insert-index-fp": {}, "on-duplicate-fp": {}, } ictx := failpoint.WithHook(context.Backgroud(), func(ctx context.Context, fpname string) bool { _, found := insertFailpoints[fpname] // Only enables some failpoints. return found }) deleteFailpoints = map[string]struct{} { "tikv-is-busy-fp": {}, "fetch-tso-timeout": {}, } dctx := failpoint.WithHook(context.Backgroud(), func(ctx context.Context, fpname string) bool { _, found := deleteFailpoints[fpname] // Only disables failpoints. return !found }) // other DML parallel test cases. s.RunParallel(buildSelectTests(sctx)) s.RunParallel(buildInsertTests(ictx)) s.RunParallel(buildDeleteTests(dctx)) }
如果我们在循环中使用 failpoint,可能我们会使用到其他的 Marker 函数 : failpoint.Label("outer") for i := 0; i < 100; i++ { inner: for j := 0; j < 1000; j++ { switch rand.Intn(j) + i { case j / 5: failpoint.Break() case j / 7: failpoint.Continue("outer") case j / 9: failpoint.Fallthrough() case j / 10: failpoint.Goto("outer") default: failpoint.Inject("failpoint-name", func(val failpoint.Value) { fmt.Println("unit-test", val.(int)) if val == j/11 { failpoint.Break("inner") } else { failpoint.Goto("outer") } }) } } }
以上代码最终会重写为如下代码: outer: for i := 0; i < 100; i++ { inner: for j := 0; j < 1000; j++ { switch rand.Intn(j) + i { case j / 5: break case j / 7: continue outer case j / 9: fallthrough case j / 10: goto outer default: if ok, val := failpoint.Eval(_curpkg_("failpoint-name")); ok { fmt.Println("unit-test", val.(int)) if val == j/11 { break inner } else { goto outer } } } } }
为什么会有 label 、 break 、 continue 和 fallthrough 相关 Marker 函数? 为什么不直接使用关键字? Golang 中如果某个变量或则标签未使用,是不能通过编译的。 label1: // compiler error: unused label1 failpoint.Inject("failpoint-name", func(val failpoint.Value) { if val.(int) == 1000 { goto label1 // illegal to use goto here } fmt.Println("unit-test", val) }) break 和 continue 只能在循环上下文中使用,在闭包中使用。
一些复杂的注入示例
示例一:在 IF 语句的 INITIAL 和 CONDITIONAL 中注入 failpoint if a, b := func() { failpoint.Inject("failpoint-name", func(val failpoint.Value) { fmt.Println("unit-test", val) }) }, func() int { return rand.Intn(200) }(); b > func() int { failpoint.Inject("failpoint-name", func(val failpoint.Value) int { return val.(int) }) return rand.Intn(3000) }() && b < func() int { failpoint.Inject("failpoint-name-2", func(val failpoint.Value) { return rand.Intn(val.(int)) }) return rand.Intn(6000) }() { a() failpoint.Inject("failpoint-name-3", func(val failpoint.Value) { fmt.Println("unit-test", val) }) }
上面的代码最终会被重写为: if a, b := func() { if ok, val := failpoint.Eval(_curpkg_("failpoint-name")); ok { fmt.Println("unit-test", val) } }, func() int { return rand.Intn(200) }(); b > func() int { if ok, val := failpoint.Eval(_curpkg_("failpoint-name")); ok { return val.(int) } return rand.Intn(3000) }() && b < func() int { if ok, val := failpoint.Eval(_curpkg_("failpoint-name-2")); ok { return rand.Intn(val.(int)) } return rand.Intn(6000) }() { a() if ok, val := failpoint.Eval(_curpkg_("failpoint-name-3")); ok { fmt.Println("unit-test", val) } }
示例二:在 SELECT 语句的 CASE 中注入 failpoint 来动态控制某个 case 是否被阻塞 func (s *StoreService) ExecuteStoreTask() { select { case <-func() chan *StoreTask { failpoint.Inject("priority-fp", func(_ failpoint.Value) { return make(chan *StoreTask) }) return s.priorityHighCh }(): fmt.Println("execute high priority task") case <- s.priorityNormalCh: fmt.Println("execute normal priority task") case <- s.priorityLowCh: fmt.Println("execute normal low task") } }
上面的代码最终会被重写为: func (s *StoreService) ExecuteStoreTask() { select { case <-func() chan *StoreTask { if ok, _ := failpoint.Eval(_curpkg_("priority-fp")); ok { return make(chan *StoreTask) }) return s.priorityHighCh }(): fmt.Println("execute high priority task") case <- s.priorityNormalCh: fmt.Println("execute normal priority task") case <- s.priorityLowCh: fmt.Println("execute normal low task") } }
示例三:动态注入 SWITCH CASE switch opType := operator.Type(); { case opType == "balance-leader": fmt.Println("create balance leader steps") case opType == "balance-region": fmt.Println("create balance region steps") case opType == "scatter-region": fmt.Println("create scatter region steps") case func() bool { failpoint.Inject("dynamic-op-type", func(val failpoint.Value) bool { return strings.Contains(val.(string), opType) }) return false }(): fmt.Println("do something") default: panic("unsupported operator type") }
以上代码最终会重写为如下代码: switch opType := operator.Type(); { case opType == "balance-leader": fmt.Println("create balance leader steps") case opType == "balance-region": fmt.Println("create balance region steps") case opType == "scatter-region": fmt.Println("create scatter region steps") case func() bool { if ok, val := failpoint.Eval(_curpkg_("dynamic-op-type")); ok { return strings.Contains(val.(string), opType) } return false }(): fmt.Println("do something") default: panic("unsupported operator type") }
除了上面的例子之外,还可以写的更加复杂的情况: 由 INITIAL 语句、 CONDITIONAL 表达式,以及 POST 语句组成的循环 FOR RANGE 语句 SWITCH INITIAL 语句 Slice 的构造和索引 结构体动态初始化 ……
实际上,任何你可以调用函数的地方都可以注入 failpoint,所以请发挥你的想象力。
Failpoint 命名最佳实践
上面生成的代码中会自动添加一个 _curpkg_ 调用在 failpoint-name 上,是因为名字是全局的,为了避免命名冲突,所以会在最终的名字中包含包名, _curpkg_ 相当一个宏,在运行的时候自动使用包名进行展开。你并不需要在自己的应用程序中实现 _curpkg_ ,它在执行 failpoint-ctl enable 命令的时候自动生成以及自动添加,并在执行 failpoint-ctl disable 命令的时候被删除。 package ddl // ddl’s parent package is `github.com/pingcap/tidb` func demo() { // _curpkg_("the-original-failpoint-name") will be expanded as `github.com/pingcap/tidb/ddl/the-original-failpoint-name` if ok, val := failpoint.Eval(_curpkg_("the-original-failpoint-name")); ok {...} }
因为同一个包下面的所有 failpoint 都在同一个命名空间,所以需要小心命名来避免命名冲突,这里有一些推荐的规则来改善这种情况: 保证名字在包内是唯一的。 使用一个自解释的名字。
可以通过环境变量来激活 failpoint: GO_FAILPOINTS="github.com/pingcap/tidb/ddl/renameTableErr=return(100);github.com/pingcap/tidb/planner/core/illegalPushDown=return(true);github.com/pingcap/pd/server/schedulers/balanceLeaderFailed=return(true)"
致谢 感谢 gofail 提供最初实现,给我们提供了灵感,让我们能站在巨人的肩膀上对 failpoint 进行迭代。 感谢 FreeBSD 定义 语法规范 。
最后,欢迎大家和我们交流讨论,一起完善 Failpoint 项目 。
原文链接: https://pingcap.com/blog-cn/golang-failpoint/
数据库
2019-05-05 13:50:00
「深度学习福利」大神带你进阶工程师,立即查看>>>
一 .介绍   EXPLAIN 命令用于SQL语句的 查询执行计划 。这条命令的输出结果能够让我们了解MySQL 优化器是如何执行SQL 语句的。这条命令并没有提供任何调整建议,但它能够提供重要的信息帮助你做出调优决策。
先解析一条sql语句,你可以看出现什么内容
?
1
EXPLAIN SELECT * FROM person,dept WHERE person.dept_id = dept.did and person.salary >20000

下面咱们详细的介绍一下 查询计划的结果列: 二. id : 查询序列号
 查询序号即为sql语句执行顺序 ?
1
EXPLAIN select * from person where dept_id =( select did from dept where dname = 'python' );

从 2 个表中查询,对应输出 2 行,每行对应一个表, id 列表示执行顺序,id 越大,越先执行,id 相同时,由上至下执行。 三.select_type : 查询类型
  select_type 列提供了 对表的查询类型。最常见的值包括 SIMPLE、PRIMARY、DERIVED 和UNION 。其他可能的值还有 UNION RESULT 、 SUBQUERY 等等.
 2.1 simple 简单查询 (没有union和子查询)
  对于不包含子查询和其他复杂语法的简单查询,这是一个常见的类型。 ?
1
EXPLAIN SELECT * FROM person;

 2.2 primary 最外层查询 (在存在子查询的语句中,最外面的select查询就是primary)
  这是为更复杂的查询而创建的首要表(也就是最外层的表)。这个类型通常可以在DERIVED 和 UNION 类型混合使用时见到。
 2.3 derived 子查询( 在FROM列表中包含的子查询 )
  当一个表不是一个物理表时,那么这个就被叫做DERIVED ?
1
EXPLAIN SELECT * FROM ( SELECT * FROM person LIMIT 5) AS s

 2.4 subquery 映射为子查询( 在SELECT或WHERE列表中包含了子查询 )
  这个select-type 的值是为使用子查询而定义的. ?
1
EXPLAIN SELECT person.*,( select 2 from person as p2) FROM person where dept_id = ( select did from dept where dname= 'python' );

2.5 union 联合 ?
1
EXPLAIN SELECT * FROM person union all select * from person ;

2.6 union result 使用联合的结果 ?
1
EXPLAIN SELECT * FROM person union select * from person ;

四. table 输出的行所用的表 ?
1
EXPLAIN SELECT * FROM person;

注意: table 列是EXPLAIN 命令输出结果中的一个单独行的唯一标识符。这个值可能是表名、表的别名或者一个为查询产生临时表的标识符,如派生表、子查询或集合。 五. type 连接类型
  type 列代表表示 查询计划的连接类型, 有多个参数,先从最佳类型到最差类型介绍 重要且困难
   性能: null > system/const > eq_ref > ref > ref_or_null >index_merge > range > index > all
4.1 type=NULL 在优化过程中就已得到结果,不用再访问表或索引。 ?
1
EXPLAIN SELECT max (id) FROM person;

4.2 type=const/system 常量
  在整个查询过程中这个表最多只会有一条匹配的行,比如主键 id=1 就肯定只有一行;
表最多有一个匹配行,const用于比较primary key 或者unique索引。因为只匹配一行数据,所以一定是用到primary key 或者unique 情况下才会是const,看下面这条语句 ?
1
EXPLAIN SELECT * FROM person where id =2;

所以说可以理解为const是最优化的。

4.3 type=eq_ref 使用有唯一性 索引查找(主键或唯一性索引)
 对于eq_ref的解释,mysql手册是这样说的:"对于每个来自于前面的表的行组合,从该表中读取一行。这可能是最好的联接类型,除了const类型。它用在一个索引的所有部分被联接使用并且索引是UNIQUE或PRIMARY KEY"。eq_ref可以用于使用=比较带索引的列。看下面的语句  ?
1
EXPAIN select * from person,dept where person.id = dept.did;
得到的结果是下图所示。很明显,mysql使用eq_ref联接来处理 dept 表。  
4.4 type=ref  非唯一性索引访问
  这是一种索引访问(有时也叫做索引查找),它返回所有匹配某个单个值的行,然而,它可能会找到多个符合条件的行。因此,它是查找和扫描的混合体,此类索引访问只有当使用非唯一性索引或者唯一性索引的非唯一性前缀时才会发生。把它叫做ref是因为索引要跟某个参考值相比较。这个参考值或者是一个常数,或者是来自多表查询前一个表里的结果值。 ?
1
EXPLAIN select * from person where name = 'alex' ;


4.5 ref_or_null 该联接类型如同ref类似,结果包含空行.
上面这五种情况都是很理想的索引使用情况
4.6 type=range
  索引范围扫描,常见于 <,<=,>,>=,between,in等操作符。  ?
1
EXPLAIN select * from person where id BETWEEN 1 and 5;

4.7 type=index
  该联接类型与ALL相同都是扫描表,但index只对索引树进行扫描,而ALL是是对数据表文件的扫描。这通常比ALL快,因为索引文件通常比数据文件小。(也就是说虽然all和Index都是读全表,但index是从索引中读取的,而all是从硬盘中读的)主要优点是避免了排序,因为索引是排好序的。
Extra列中看到“Using index”,说明mysql正在使用覆盖索引,只扫描索引的数据。  ?
1
EXPLAIN select id, name from person;

4.8 type=ALL
  对于每个来自于先前的表的行组合,进行完整的表扫描。如果表是第一个没标记const的表,这通常不好,并且通常在它情况下 很 差。通常可以增加更多的索引而不要使用ALL,使得行能基于前面的表中的常数值或列值被检索出。 ?
1
EXPLAIN select * from person;

六. possible_keys :   该 possible_keys列表示MySQL可以从中选择查找表中的行的索引。如果此列是NULL,则没有相关的索引。在这种情况下,您可以通过检查WHERE 子句来检查是否引用某些适合索引的列,从而提高查询的性能。如果是这样,请创建一个适当的索引并使用 EXPLAIN再次检查查询 。
另外如果这个列出现大量可能被使用的索引(例如多于3 个), 那么这 意味着备选索引数量太多了,同时也可能提示存在无效的索引。
七. key :  该key 列指出mysql优化器决定选择使用哪个索引来优化对该表的访问。一般来说SQL查询中的每个表都只会使用一个索引。但是也存在索引合并的少数例外情况,如给定表上用到了两个或者更多索引。查询过程中由优化器来决定实际使用的索引。如果possible_keys索引列表中没有适合查找行的索引,那么这个key可能会命名一个不存在于该possible_keys值中的索引 。简单且重要
八. key_len :   该key_len 列定义了mysql在索引里使用的字节数。如果mysql正在使用的只是索引里的某些列,那么就可以用这个值来算出具体是哪些列。在mysql5.5及以前的版本里,只能使用索引的最左前缀。例如,sakila.film_actor的主键是两个SMALLINT列,并且每个SMALLINT列是两个字节,那么索引中的每项是4个字节。也即说明key_len通过查找表的定义而被计算出,而不是表中的数据。
在不损失精确性的情况下,长度越短越好.
九. ref :   ref 列显示使用哪个列或常数与key一起从表中选择数据行。指出对 key 列所选择的索引的查找方式,常见的值有 const, func, NULL, 具体字段名。当 key 列为 NULL ,即不使用索引时 。如果值是func,则使用的值是某个函数的结果
create table a11(id int primary key , age int ); insert into a11 value( 1 , 10 ),( 2 , 10 ); mysql > desc select * from a11 where age = 10 ; + -- --+-------------+-------+------+---------------+------+---------+------+------+-------------+ | id | select_type | table | type | possible_keys | key | key_len | ref | rows | Extra | + -- --+-------------+-------+------+---------------+------+---------+------+------+-------------+ | 1 | SIMPLE | a11 | ALL | NULL | NULL | NULL | NULL | 2 | Using where | + -- --+-------------+-------+------+---------------+------+---------+------+------+-------------+ 注意:当 key 列为 NULL , ref 列也相应为 NULL 。 mysql > desc select * from a11 where id = 1 ; + -- --+-------------+-------+-------+---------------+---------+---------+-------+------+-------+ | id | select_type | table | type | possible_keys | key | key_len | ref | rows | Extra | + -- --+-------------+-------+-------+---------------+---------+---------+-------+------+-------+ | 1 | SIMPLE | a11 | const | PRIMARY | PRIMARY | 4 | const | 1 | | + -- --+-------------+-------+-------+---------------+---------+---------+-------+------+-------+ 注意:这次 key 列使用了主键索引, where id = 1 中 1 为常量, ref 列的 const 便是指这种常量。

十.row :   这一列是mysql评估 为了找到所需的行而要读取的行数。这个数字是内嵌循环关联计划里的循环数目,也就是说它不是mysql认为它最终要从表里读取出来的行数,而是mysql为了找到符合查询的每一点上标准的那些行而必须读取的行的平均数。
rows 列提供了试图分析所有存在于累计结果集中的行数目的MySQL 优化器估计值。执行计划很容易描述这个很困难的统计量。
查询中总的读操作数量是基于合并之前行的每一行的rows 值的连续积累而得出的。这是一种嵌套行算法。
简单且重要,数值越大越不好,说明没有用好索引
十一.Extra:  该列包含 MySQL 查询的详细信息。
 10.1 Not exists : 不存在信息
 10.2 range checked for each record :没有找到合适的索引
 10.3 Using index condition :出现这个说明mysql使用了覆盖索引,避免访问了表的数据行,效率不错!
建表及插入数据: create table a13 (id int primary key , age int ); insert into a13 value( 1 , 10 ),( 2 , 10 ); mysql > explain select id from a13; + -- --+-------------+-------+------------+-------+---------------+---------+---------+------+------+----------+-------------+ | id | select_type | table | partitions | type | possible_keys | key | key_len | ref | rows | Extra | + -- --+-------------+-------+------------+-------+---------------+---------+---------+------+------+----------+-------------+ | 1 | SIMPLE | a13 | NULL | index | NULL | PRIMARY | 4 | NULL | 2 | Using index | + -- --+-------------+-------+------------+-------+---------------+---------+---------+------+------+----------+-------------+ 因为 id 为主键索引,索引中直接包含了 id 的值,所以无需访问表,直接查找索引就能返回结果。 mysql > explain select age from a13; + -- --+-------------+-------+------------+------+---------------+------+---------+------+------+----------+-------+ | id | select_type | table | partitions | type | possible_keys | key | key_len | ref | rows | Extra | + -- --+-------------+-------+------------+------+---------------+------+---------+------+------+----------+-------+ | 1 | SIMPLE | a13 | NULL | ALL | NULL | NULL | NULL | NULL | 2 | NULL | + -- --+-------------+-------+------------+------+---------------+------+---------+------+------+----------+-------+ age 列没有索引,因此没有 Using index ,意即需要访问表。 为 age 列添加索引: create table a14 (id int primary key , age int ); insert into a14 value( 1 , 10 ),( 2 , 10 ); create index age on a14(id, age); mysql > explain select age from a14; + -- --+-------------+-------+------------+-------+---------------+------+---------+------+------+----------+-------------+ | id | select_type | table | partitions | type | possible_keys | key | key_len | ref | rows | Extra | + -- --+-------------+-------+------------+-------+---------------+------+---------+------+------+----------+-------------+ | 1 | SIMPLE | a14 | NULL | index | NULL | age | 9 | NULL | 2 | Using index | + -- --+-------------+-------+------------+-------+---------------+------+---------+------+------+----------+-------------+ 现在索引 age 中也包含了 age 列的值,因此不用访问表便能返回结果了。
 10.4 using temporary :mysql对查询结果进行排序的时候使用了一张临时表。
mysql > EXPLAIN SELECT p.id,d.did from person p LEFT JOIN dept d ON p.dept_id = d.did group by p.dept_id ORDER BY p.dept_id; + -- --+-------------+-------+--------+---------------+---------+---------+------------+------+---------------------------------+ | id | select_type | table | type | possible_keys | key | key_len | ref | rows | Extra | + -- --+-------------+-------+--------+---------------+---------+---------+------------+------+---------------------------------+ | 1 | SIMPLE | p | ALL | NULL | NULL | NULL | NULL | 8 | Using temporary ; Using filesort | | 1 | SIMPLE | d | eq_ref | PRIMARY | PRIMARY | 4 | test.p.dept_id | 1 | Using where ; Using index | 我们发现在执行这条SQL语句时出现了 using temporary ,我们再来看看下面这条SQL语句,去掉 条件中 group by 分组 mysql > EXPLAIN SELECT p.id,d.did from person p LEFT JOIN dept d ON p.dept_id = d.did ORDER BY p.dept_id; + -- --+-------------+-------+--------+---------------+---------+---------+------------+------+--------------------------+ | id | select_type | table | type | possible_keys | key | key_len | ref | rows | Extra | + -- --+-------------+-------+--------+---------------+---------+---------+------------+------+--------------------------+ | 1 | SIMPLE | p | ALL | NULL | NULL | NULL | NULL | 8 | Using filesort | | 1 | SIMPLE | d | eq_ref | PRIMARY | PRIMARY | 4 | test.p.dept_id | 1 | Using where ; Using index | + -- --+-------------+-------+--------+---------------+---------+---------+------------+------+--------------------------+ 而为什么第一个用了临时表,而第二个没有用呢? 因为如果有GROUP BY子句,或者如果GROUP BY中的字段都来自其他的表而非连接顺序中的第一个表的话,就会创建一个临时表了。 那么如何解决呢? 咱们为group by 字段添加一个索引 mysql > alter table person add index did_idx(dept_id); Query OK, 0 rows affected mysql > EXPLAIN SELECT p.id,d.did from person p LEFT JOIN dept d ON p.dept_id = d.did group by p.dept_id ORDER BY p.dept_id; + -- --+-------------+-------+--------+---------------+---------+---------+------------+------+--------------------------+ | id | select_type | table | type | possible_keys | key | key_len | ref | rows | Extra | + -- --+-------------+-------+--------+---------------+---------+---------+------------+------+--------------------------+ | 1 | SIMPLE | p | index | NULL | did_idx | 5 | NULL | 8 | Using index | | 1 | SIMPLE | d | eq_ref | PRIMARY | PRIMARY | 4 | test.p.dept_id | 1 | Using where ; Using index | + -- --+-------------+-------+--------+---------------+---------+---------+------------+------+--------------------------+ 为什么添加个索引就不会创建临时表了呢? 原因就在于 SQL查询时优先在索引树中执行,如果索引树满足不了当前SQL,才会进行数据表查询,那么现在加了索引, 已经可以满足查询条件了,就没有必要创建临时表了
 10.5 using filesort: mysql对数据不是按照表内的索引顺序进行读取,而是使用了其他字段重新排序.
mysql > EXPLAIN select * from person ORDER BY id; + -- --+-------------+--------+-------+---------------+---------+---------+------+------+-------+ | id | select_type | table | type | possible_keys | key | key_len | ref | rows | Extra | + -- --+-------------+--------+-------+---------------+---------+---------+------+------+-------+ | 1 | SIMPLE | person | index | NULL | PRIMARY | 4 | NULL | 8 | | + -- --+-------------+--------+-------+---------------+---------+---------+------+------+-------+ 如果我们用聚合主键进行排序,则Extra 为null,我们知道在innodb引擎中,主键为聚合索引,插入数据就会排好顺序.最后说明mysql是按照表内的索引顺序进行读的 再看下面的列子: mysql > EXPLAIN select * from person ORDER BY salary; + -- --+-------------+--------+------+---------------+------+---------+------+------+----------------+ | id | select_type | table | type | possible_keys | key | key_len | ref | rows | Extra | + -- --+-------------+--------+------+---------------+------+---------+------+------+----------------+ | 1 | SIMPLE | person | ALL | NULL | NULL | NULL | NULL | 8 | Using filesort | + -- --+-------------+--------+------+---------------+------+---------+------+------+----------------+ 我们使用非主键字段进行排序,这是mysql就不能按照表内的索引顺序进行读了.需要读取数据行后再进行排序处理
 10.6 using where: 表示 MySQL 服务器从存储引擎收到查询数据,再进行“后过滤”(Post-filter)。所谓“后过滤”,就是先读取整行数据,再检查此行是否符合 where 句的条件,符合就留下,不符合便丢弃。因为检查是在读取行后才进行的,所以称为“后过滤”。
建表及插入数据: create table a16 (num_a int not null , num_b int not null , key (num_a)); insert into a16 value( 1 , 1 ),( 1 , 2 ),( 2 , 1 ),( 2 , 2 ); mysql > explain select * from a16 where num_a = 1 ; + -- --+-------------+-------+------------+------+---------------+-------+---------+-------+------+----------+-------+ | id | select_type | table | partitions | type | possible_keys | key | key_len | ref | rows | Extra | + -- --+-------------+-------+------------+------+---------------+-------+---------+-------+------+----------+-------+ | 1 | SIMPLE | a16 | NULL | ref | num_a | num_a | 4 | const | 2 | NULL | + -- --+-------------+-------+------------+------+---------------+-------+---------+-------+------+----------+-------+ 虽然查询中有 where 子句,但只有 num_a = 1 一个条件,且 num_a 列存在索引,通过索引便能确定返回的行,无需进行“后过滤”。 所以,并非带 WHERE 子句就会显示"Using where "的。 mysql > explain select * from a16 where num_a = 1 and num_b = 1 ; + -- --+-------------+-------+------------+------+---------------+-------+---------+-------+------+----------+-------------+ | id | select_type | table | partitions | type | possible_keys | key | key_len | ref | rows | Extra | + -- --+-------------+-------+------------+------+---------------+-------+---------+-------+------+----------+-------------+ | 1 | SIMPLE | a16 | NULL | ref | num_a | num_a | 4 | const | 2 | Using where | + -- --+-------------+-------+------------+------+---------------+-------+---------+-------+------+----------+-------------+ 此查询增加了条件 num_b = 1 ,此列没有索引,但可以看到查询同样能使用 num_a 索引。 MySQL 先通过索引 num_a 找到 num_a = 1 的行,然后读取整行数据, 再检查 num_b 是否等于 1 ,执行过程看上去象这样: num_a索引 | num_b 没有索引,属于行数据 + -- -----+-------+ | num_a | num_b | where 子句(num_b = 1 ) + -- -----+-------+ | 1 | 1 | 符合 | 1 | 2 | 不符合 | ... | ... | ... + -- -----+-------+

详情参考官方文档: https://dev.mysql.com/doc/refman/5.6/en/using-explain.html
十二. EXPLAIN结果中哪些信息要引起关注 们使用EXPLAIN解析SQL执行计划时,如果有下面几种情况,就需要特别关注下了:
  首先看下 type 这列的结果,如果有类型是 ALL 时,表示预计会进行全表扫描(full table scan)。通常全表扫描的代价是比较大的,建议创建适当的索引,通过索引检索避免全表扫描。
  再来看下 Extra 列的结果,如果有出现 Using temporary 或者 Using filesort 则要多加关注:
  Using temporary ,表示需要创建临时表以满足需求,通常是因为GROUP BY的列没有索引,或者GROUP BY和ORDER BY的列不一样,也需要创建临时表,建议添加适当的索引。
  Using filesort ,表示无法利用索引完成排序,也有可能是因为多表连接时,排序字段不是驱动表中的字段,因此也没办法利用索引完成排序,建议添加适当的索引。
  Using where ,通常是因为全表扫描或全索引扫描时( type 列显示为 ALL 或 index ),又加上了WHERE条件,建议添加适当的索引。
其他状态例如:Using index、Using index condition、Using index for group-by 则都还好,不用紧张。
数据库
2019-05-05 13:46:00
「深度学习福利」大神带你进阶工程师,立即查看>>> 在4月20日的阿里云栖开发者沙龙PHP技术专场上,云智慧Technical VP高驰涛为大家介绍了微服务的前世今生,分享了微服务架构实践中所面对的诸多挑战以及相应的应对策略。
本次直播视频精彩回顾,戳这里!
直播回顾: https://yq.aliyun.com/live/965
PPT分享: https://yq.aliyun.com/download/3527
以下内容根据演讲视频以及PPT整理而成。
专家简介
高驰涛 (Neeke Gao), 云智慧Technical VP,PHP/PECL开发组成员,具有10余年研发管理经验,同时也是PECL/SeasLog、PECL/JsonNet、GoCrab等多项开源软件的作者。2014年加入云智慧,致力于APM与大数据产品的架构研发,崇尚敏捷、高效。
从一个问题谈起
首先,从几年之前某CTO的一个问题谈起,这个问题是“我们的系统将会拥有五千个微服务组件。我们应该怎么做?”大家可以仔细思考这个问题,我们都知道一个接口肯定无法称之为微服务,达到十几个接口或许才能够叫做微服务。那么,对于包含五千个微服务的系统而言,又该怎么实现和管理呢?其实,这样的系统背后会存在很大的问题。
本次分享将会主要围绕以下三个方面内容展开: 微服务的前世今生 微服务的挑战 应该怎么面对
微服务的前世今生
下图所展现的内容其实可以说是供大家在茶余饭后聊天的谈资,如果想要知道微服务是如何诞生的,那么就必须要了解以下四个领域的知识。
TOGAF: 全称为“开放组体系结构框架”,TOGAF在上世纪七、八十年代的时候就已经由专门组织负责开发了,但一直到1995年的时候,美国国防部参与之后,TOGAF才最终成型。举例而言,大家手机里正在使用的产品和应用中,很多都会用到SAP、IBM或者惠普等的软件,而这些软件公司所遵循的就是TOGAF。可以说目前全球超过50%的企业正在使用TOGAF实践软件架构设计和开发。TOGAF是一个架构体系,而并没有提供具体的架构方法。TOGAF包含了业务架构、应用架构、数据架构、技术架构等,其实,阿里云的全局组件也属于TOGAF中的技术架构领域,其能够帮助客户减少各种繁杂的思考,使得客户只需要关注于业务架构即可。
TOGAF有三个最为主要支柱:
1)企业架构域,主要是企业信息与业务流等;
2)ADM一系列的架构方法论;
3)企业连续性,指的是在企业业务高速增长并且也不断变更的过程中,保证架构体系的连续性。
DDD: 全称为“领域驱动设计”,其包含了诸多的概念,但是大家只需要记住主要的三句话即可。
1)DDD是精简的业务,DDD首先关注的就是业务,把各种繁琐的业务流程精简成更细的链条;
2)DDD需要回答业务是干什么的,能够满足什么需求,达成什么目的;
3)不断迭代,DDD的不断迭代与TOGAF的企业连续性类似。
SOA: 全称为“面向服务架构”,其理论也非常多,但是大家也只需要记住三点。
1)SOA解决了信息孤岛的问题,不让信息变成孤岛;
2)业务重用,从业务角度将各个服务组合成一个个中间件或者服务,将其提供给用户或者其他系统;
3)SOA使得系统成为互联互通的信息群。
GRASP原则: 全称为“通用职责分配原则”,其实很多大家耳熟能详的概念如“低耦合”、“高内聚”都来自于GRASP原则。其与设计模式不同,设计模式指导如何实现系统,而GRASP指导如何划分。GRASP原则指导定义业务架构以及API等相关内容和划分服务,其理论内容也非常多,但是只需要记住三个关键:
1)自己干自己的事;
2)自己只干自己能干的事;
3)自己只干自己的事,强调了资源划分。
在软件工程的教科书上给出了微服务架构的定义:微服务架构是一种架构模式,它提介将单一应用程序划分成一组小的服务,服务之间互相协调、互相配合,为⽤户提供最终价值。每个服务运行在其独立的进程中,服务与服务间采用轻量级的通信机制互相沟通(通常是基于HTTP协议的RESTFul API)。每个服务都围绕着具体业务进⾏构建,并且能够被独⽴的部署到⽣产环境、类⽣产环境等。另外,应当尽量避免统一的、集中式的服务管理机制,对具体的一个服务⽽言,应根据业务上下⽂,选择合适的语言、工具对其进行构建。而这些教科书上的内容或许在当下来看已经过时了。
微服务带来的优势
那么,我们使用微服务架构的时候,到底得到了什么东西呢?其实得到了很多,这里为大家总结了四点最为明显的优点。
1)使得开发和迭代变得更加敏捷,使用微服务架构使得敏捷开发成为可能;
2)易于扩展和收缩,一些公司基于Kubernetes、Docker等技术可以在几秒内拉起上万个微服务,当大型流量冲击到达的时候,可以实现无损地承担全部流量,同时实现用户无感知,而当数据访问量降低之后,又可以实现快速缩容;
3)多技术栈可能,目前智慧云的技术栈非常全面,虽然开发人员只有60多人,但是开发语言却多达10多门,而使用微服务可以有效地组织各类开发人员;
4)高可修改性,比如实现数据库的快速迁移,通道的快速切换等。
微服务带来的两点疑问
这里再提出两个问题,虽然微服务能够带来诸多优点,但是也存在两点疑问。第一个就是“微服务架构,你的系统变得更健壮了吗?”;第二个则是“使用微服务让系统变得更快了吗?”对于这两点而言,可能说是见仁见智的。有人说因为组件变得越来越多,可监控性就会变难,因此系统健壮性就会变得越来越差,也有人说因为将系统拆分得越来越细,因此健壮性就会越来越强。如果单体架构是串行的,那么使用微服务可以将其变成并行的和分布式的,而多个组件之间进行通信,也会使得通信成为性能瓶颈,那么使用微服务到底是变快了还是变慢了呢?这两个问题都很难以回答。而作为一个架构师或者开发者需要进行深入的思考。
微服务架构面临的挑战和思考
这里为大家总结了在使用微服务架构的时候所需要面临的8条挑战和相关的思考。
1. 小即是多
当业务从大变小的时候,也意味着业务变多了。由大变小,可以使系统变得更加容易维护和修改,但是由少变多,又会使得问题更加复杂,因此也会有很多的挑战。第一个问题就是多节点、多服务和多状态。系统中的节点、组件服务变得更多了,那么节点和服务之间的状态也会变得更难维护,更加复杂。基于前面提到的四种知识,其实可以将从大变小和从少变多这两个转变进行折中,使得其变得更加可控。而解决这个问题的关键在于对于服务的合理拆分,主要有三点可以考虑,即数据资源、业务功能以及服务对象。
2. 债务管理
比如Bug、代码缺陷、未完成的功能或者版本不兼容等问题都是债务。当服务变得越来越多的时候,债务往往就会变得更多。为了解决这些问题,其实有这样的几种策略,比如单元测试,如果单元测试做的足够好,那么代码缺陷的可能性就会变得更低一些,可以将服务由少变多所造成的债务变多情况进行收敛;集成回归,这部分提供了很多工具去做这件事情,不用开发者自己去做;版本管理,这里指的是静态库的版本管理,动态库指的是正在变更中的库,而静态库指的是不再变更的库和配置项,这一点控制不好,就容易使得系统管理混乱;迭代冲刺,这是一种组织方式,当有很多技术债务需要进行管理时,如何将这些债务一点点处理掉或者把发散的趋势收敛住,迭代冲刺就是一种做法;Bug Crash,这是智慧云团队自己发明的一个名词,相当于是对于Bug的大扫除,无论采用传统的还是敏捷的开发模式,都有一些Bug存在,因此定期会组织全体开发和测试以及产品将自己的产品用一遍,进行Bug大扫除;回归总结,无论采用什么开发模式,在一个迭代周期完成之后,回归总结是少不了的,也需要通过一些方法解决新发生的问题,或者将其封闭住不使债务继续蔓延。
3. 复杂的服务依赖
如果只有一个或者几个组件,那么其实不存在服务依赖问题,而如果有几千个组件,那么服务依赖将会成为巨大的问题。举例而言,如果用户服务需要调用订单服务,那么在启动的时候需要进行一些初始化任务,那么一个服务的版本发布可能导致系统全面瘫痪,这就是复杂服务依赖问题。为了解决这个问题首先就需要服务发现机制,比如使用etcd或者Zookeeper等,首先服务发现中心也需要是分布式高可靠的,那么服务起来之后需要把自己的名字和调用方式告诉服务发现中心,注册上去,对于服务调用者而言只需要从服务发现中心那里通过约定好的名字获取服务调用地址即可。依赖唤醒是有一个相对比较新的东西,比如大流量突然打进来的时候,A服务需要从原来的10个启动到100个,而B从原来的3个肯定也是不够用的,因此需要通过唤醒的机制将服务拉起来,而不是被动的被通知。还有一种情况也需要使用到依赖唤醒机制,比如缓存穿透问题,正常情况下,缓存是生效的,不会存在穿透的情况,但是可能因为某种异常使得缓存不生效了,会将大量的流量打到DB里面去,使得服务变得不可用了,整个服务雪崩掉,针对这些问题一般会开发一些挡板服务,可能会给出一些固定的数据,而这些挡板服务也有可能会面临这种突发的流量也需要通过依赖唤醒的机制实现唤醒。此外,还有灰度发布和AB测试,这两点是相关联的。还有多版本共存问题,对于服务的多版本也是一个技术债务问题,需要考虑如何将其旧版本拿下来。
4. 消息通讯
如果系统中包含多个语言栈,多种实现方式。那么统一标准是必须的,要么统一一种RPC或者就使用RestFul API等。消息中心也是一种处理做法,这一点在Java中应用很多,消息中心并不是消息队列,而是一个事件驱动的消息中心。此外,还有通讯网关,这在使用微服务的时候也是一个必要点,其主要解决了监控问题,而且可以通过网关起到中控的作用,比如安全、性能以及用户校验等任务。
5. 分布式事务
在实现分布式事务的时候可以采用2PC或者3PC原则来实现,2PC原则是通过全部节点投票和执行两个步骤完成的,并且是阻塞的;而3PC则不同,虽然在一个具体的事务里面可以是阻塞的,也可以是非阻塞的。3PC协议则是通过“Can-Pre-Do”三个步骤来实现的,其实PDU就是3PC协议在单体中的实现方式。而在分布式系统中,3PC有三种实现方式,使用分布式的事件驱动、最大通知以及两阶段补偿TCC。
6. 花式故障
很多时候,当系统出现问题可能需要花费数周和很多人力才能找到根源所在,可能因为系统太多,使得系统架构师也无法清楚系统与系统之间的关系。面对诸多的花式故障,也有多种策略可以应对,比如全链路追踪,比如使用Open Tracking;主动拨测,很多用户端的APP里面内置探针,使其可以接收Server端的指令来定期探测接口和服务是否正常。
7. 中心与去中心
中心与去中心可以算是一个永恒的话题,上图中展示的配置、发号、日志、调度、状态以及预警,其实对于比较成熟的大型系统而言,这六点都是需要中心的。
8. 组织危机
最后一个问题,也是最大的问题。其实要实现向微服务架构的变更的时候,最大的问题就是组织危机。这一点与开发者关系不大,但是对于Team Leader以及组织的管理人员而言,关系就非常大了。架构的转变需要考虑到信任危机、过期维护、多语言栈、沟通协作、安全网关以及轮岗结对等问题。
总结
总结而言,最重要的观点有两个:微服务不是银弹。不要让重复的事情做两次。
作者:PHP小能手
原文链接 ​
本文为云栖社区原创内容,未经允许不得转载。
数据库
2019-05-05 13:03:00
「深度学习福利」大神带你进阶工程师,立即查看>>>
摘要: 在阿里云PHP技术沙龙专场中,阿里云邀请到php-nsq作者,pecl、Swoole开发组成员吴振宇分享了Swoole进程模型的原理与Swoole协程实现的原理。并结合具体开发案例讲解了Swoole在网络编程中的应用。
本次直播视频精彩回顾,戳这里!
直播回顾: https://yq.aliyun.com/live/965
PPT分享: https://yq.aliyun.com/download/3528
以下内容根据演讲嘉宾视频分享以及PPT整理而成。
Socket编程
网络编程又可称为Socket编程。编程分为基于Server端开发与基于Client端开发两部分。基于Server端的编程由四大步骤组成,开发者首先创建Socket,利用bind与listen函数绑定监听地址及相应的端口,最后使用accept函数接受来自监听端的请求。Client端的操作较为简便,开发者在创建Socket后使用connect函数对服务器端进行连接即可实现。
下图所示为Client端与Server端的协作示意图。Client端首先向Server端发起带有SYN标识的握手请求,Server端接受到请求后,返回给Client端带有SYN与ACK标识的请求并将Client端中的RCVD文件加载至队列中,在三次握手完成之后,该文件描述符将被添加至accept队列中等待下一步逻辑处理。
下图所示为Socket编程的实现代码
在Socket编程中,Socket的读写状态判断十分重要。Socket可读条件分为以下四条: 该套接字接收缓冲区中的数据字节数大于等于套接字接收缓存区低水位。 该连接的读半部关闭(也就是接收了FIN的TCP连接)。 有新链接到达可读,该套接字是一个listen的监听套接字,并且目前已经完成的连接数不为0。 有一个Socket有异常错误条件待处理.对于这样的Socket读操作将不会阻塞,并且返回一个错误(-1),errno则设置成明确的错误条件。
以上条件中,第一条件与第三条件较为重要。对于TCP和UDP套接字而言,缓冲区低水位的值默认为1,在默认情况下,缓冲区中的数据均为可读。当为Socket收到connect请求,执行了三次握手的第一步接收SYN请求后,Socket便处于可读状态。对这样的套接字进行accept操作通常不会阻塞。
对应于Socket可读条件的判断,Socket可写条件也分为以下四条: 该套接字发送缓冲区中的可用空间字节数大于等于套接字发送缓存区低水位标记时,并且该套接字已经成功连接。 该连接的写半部关闭。 使用非阻塞的connect套接字已建立连接,或者connect已经以失败告终。 有一个错误的套接字待处理。
下图举了生活中与网络阻塞类似的生活事例来展示该过程。在用户到手机店修手机的过程中,用户在手机店不做任何事,等待老板将手机修好类似于网络同步阻塞过程;用户在店中做些其他工作,不时询问老板手机是否修好类似于同步非阻塞过程;用户回到家中,等待手机店老板修好后的电话类似于异步阻塞过程;用户回到家中做其他事情,等待老板修好后的电话类似于多路IO 复用、异步非阻塞过程。
在一款应用开发初期,应用的用户不多,服务器相对的要求同样不高,此时开发者可以使用多进程策略进行应用的开发,以此加快开发效率。下图所示为多进程同步阻塞开发的伪代码。
当业务量扩大,系统需要进行优化时,开发者可以对每个子进程中的套接字进行监听,其伪代码如下图所示。
IO复用与Reactor
当系统的用户及业务量扩大到一定规模时,开发者可以使用多路IO复用、Reactor及异步非阻塞等方法对系统进行改进。如下图所示,在这些系统调用中,Select方法存在内存开销大,支持文件描述符数量少的缺点。目前Epoll系统调用方式占据开发的主流位置,Epoll方式采用了红黑树的数据结构模式,同时拥有就绪列表rdlist,当套接字中存在可读或可写的事件时,该事件将被直接添加到就绪列表当中,从而使系统省去了轮询所有套接字属性的过程,提高了系统的执行效率。
(1)操作系统调度原理
操作系统进程调用时分为正在运行,阻塞运行及等待运行三个状态。在处理进程的过程中,内核会不断发生中断,比如三次握手过程中,当ACK发送时,内核会触发中断,系统此时需要放下正在执行的任务,去处理TCP的任务。处理完成后,系统结束中断处理并恢复运行被打断的进程。下图所示为操作系统进程调度的一些方法。
在三次握手中,系统执行以下三个步骤完成操作系统的调度:
1.网卡收到数据: 网卡收到SYN消息,触发内核中断,系统将直接打断当前执行的进程,同时CPU将会把套接字加入到Socket Queue队列当中进行存储。
2.中断回调: 若当前没有新的连接,accept将阻塞到系统调用上,并将套接字注册到Wait Queue上。
3.系统中断回调: 当新的连接产生时,Wait Queue队列将触发回调函数,将相应数据加载至rdlist列表中。
若网卡收到ACK消息,则继续触发内核中断,内核完成标准的三次握手,将连接从半连接队列移入连接队列,于是 listen Socket有可读事件,内核调用listen Socket的Wait Queue的唤醒回调函数,将之前阻塞的accept进程置为 Ready调度状态。
(2)Epoll的在调度中的作用
Epoll主要用来监听Socket的可读可写过程,在Epoll创建时,开发者需要传对应文件描述符EPOLLIN与EPOLLOUT作为可读与可写的参数标志,epoll_wait函数拥有accept的功能,会在事件发送后提醒开发者。下图罗列了Epoll中的参数与主要方法。
将Socket创建与accept过程转化为Epoll的代码示意图如下所示。首先将fd作为描述符加入创建好的Epoll中,同时把开发者想要监听的可读可写事件也注册入Epoll之中。当listen fd监听到事件时,使用accept方法将该fd描述符设为可读事件,并再次将其加入到Epoll的监听数组中,此时代表真正的客户端连接已接入。

Swoole进程模型与Reactor
Reactor模型的创建与使用较为简单,其中含有以下四个方法: Add方法:添加一个Socket到Reactor之中。 Set方法:修改Socket对应的事件,如可读可写事件等。 Del方法:从Reactor中移除相应的对象。 Callback方法:事件发生后回调指定的函数方法。
Swoole目前使用较多的模式为单线程模式与进程模式。在单线程模式中,系统使用Worker监听accept与链接,当Worker挂掉后会对系统产生一些影响。进程模式的Swoole解决了这些问题。下图为两种模式的详细对比。
在进程模式中,系统采用MainReactor线程监听accept,线程将出现的问题抛给Worker进程进行处理,这样即使单个Worker进程挂掉也不会对系统产生任何的影响。下图所示为进程模式的系统结构示意图。
下图展示了对Swoole模式的调用代码示意。在用户使用客户端去连接服务器的过程中,系统首先注册可读可写与超时三个状态回调函数。客户端与服务器连接成功时,套接字变为可写状态,系统调用可写状态的回调函数,在回调函数中处理相关的数据。

Swoole协程实现原理
Swoole协程是由事件驱动与栈切换两步共同实现完成的。
在C语言环境中,事件的调用往往使用堆栈进行处理。在堆栈中,指针EBP指向堆栈栈底,指针ESP指向堆栈栈顶,在函数调用之后,每个EBP的返回值会返回上一个EBP的地址。以此来进行事件调用的检索。下图所示为C语言中的事件调用示意图。

在PHP中的函数调用步骤如下图所示。PHP首先通过词法分析与语法分析将代码编译成语法树,语法树中的每一个语法会被编译入opcode,语法中的每一个函数会以oparray的形式存入结构体EG中,EG结构体使用函数表对这些函数进行存储。
当函数调用时,结构体中的call对应指针ESP,prev对应于指针EBP。当用户调取函数时,系统会向zend VM中为每一个方法申请一个堆栈的内存。当系统中一个函数调用其他函数时,会调用code下方储存的地址,调用方法的opcode从function存储的成员中找到并进行编译与执行。当触发了opcode后,系统会申请一个新的内存来进行新的内存分配。下图为PHP调用示意图。
下图所示为在PHP函数调用中压栈的过程及函数中存在的opcode。FCALL与DO_FCALL负责函数的调用,当堆栈中第一个opcode执行时,将进行参数压栈的操作。触发函数调用时,将执行DO_FCALL操作,系统将会把下一个函数的调用地址压入堆栈。当调用有结果后系统会将返回值返回入CALL FRAME中。



下图所示为Swoole协程代码。协程代码包括两个执行网络IO操作的go函数,当系统执行connect操作时触发网络IO操作,并将当前的PHP调用栈先保存起来。在当前调用栈保存好后,系统顺次执行下面的函数调用。当connect遇到IO函数时,系统会跳出当前任务去执行堆栈中储存的任务。
在Swoole2.0中使用C函数进行线程任务的协程。当开发者调用setjmp时,函数的返回值为0并调起first函数。当调用longjmp时,setjmp也同样被调起,此时返回值为1。Swoole2.0利用该代码实现了PHP执行的跳转,代码示意图如下。
Swoole2.0协程时序图与代码展示如下图所示。setjump方法设置当前函数堆栈,当有网络事件产生时,系统将首先对产生的事件进行注册,并在有事件通知时跳回执行中的代码,以此完成代码协程过程。


Swoole4.0通过实现C堆栈对Swoole2.0中的问题进行了改进。在Swoole4.0中用户直接调用MySQL中的链接直接就可以形成网络协程。下图所示为Swoole4.0内核系统架构示意图。
Swoole4.0的时序调度与Swoole2.0差别不大,不同的是Swoole4.0使用汇编指令对C栈与堆栈进行了存储。在协程创建时,系统会产生C栈与PHP栈,两个堆栈间会进行通信,通过这种方法解决了C栈销毁后的一些问题。下图展现了Swoole4.0的时序图。
当系统链接数量增多后会出现一些问题,开发者通过设置心跳参数与心跳收回可以保证系统服务器的资源不会被浪费。下图列举了Swoole网络编程对系统进行优化的方式。

总结
下图为吴老师分享的内容的关键词总结。
作者:PHP小能手
原文链接 ​
本文为云栖社区原创内容,未经允许不得转载。
数据库
2019-05-05 12:37:00
「深度学习福利」大神带你进阶工程师,立即查看>>>
摘要:在阿里云栖开发者沙龙PHP技术专场上,掌阅资深后端工程师、掘金小测《Redis深度历险》作者钱文品为大家介绍了RabbitMQ的延时队列和镜像队列的原理与实践,重点比较了RabbitMQ提供的消息可靠与不可靠模式,同时介绍了生产环境下如何使用RabbitMQ实现集群间消息传输。
本次直播视频精彩回顾,戳这里!
直播回顾: https://yq.aliyun.com/live/965
PPT分享: https://yq.aliyun.com/download/3529
本文根据演讲视频以及PPT整理而成。
本文将主要围绕以下四个方面进行分享: RabbitMQ特性 RabbitMQ中的消息不可靠问题及其解决方案 死信队列 生产环境下使用RabbitMQ应注意的事项
RabbitMQ特性
对于左边的Client Publisher而言,RabbitMQ Server是消息的接收者,也就是消费者;对于右边的Client Consumer而言,RabbitMQ Server是消息的发送者,也就是生产者。RabbitMQ Server将消息从Client Publisher传送给Client Consumer,扮演着消息中间商的角色。
RabbitMQ Server负责将Client Publisher传递来的消息持久化,延后地将消息传递给Client Consumer.这样,即使消费者挂掉,RabbitMQ Server也可以存储消息,当消费者重新工作时再将存储的消息传递过去,从而保证消息不丢失。RabbitMQ Server提供了堆积消息的能力。

另外,RabbitMQ Server还具有复制和广播消息的能力。具体来说,RabbitMQ Server可以将Client Publisher发布的消息分发给多个消费者,比如它能够将特定的消息按照特定的队列分发给特定的消费者。“特定”指不同消息具有不同的routing key属性,由上图实例,不同的消息生产者生产了具有不同routing key的消息,通过exchange路由器将不同的routing key消息投递到不同队列,从而分发给不同消费者。
RabbitMQ中的消息不可靠问题及其解决方案
消费端消息不可靠问题及其解决方案
实际上,RabbitMQ Server将消息投递给消费者,具有消息不可靠的特点。具体来说,RabbitMQ Server将消息投递给消费者时会调用套接字的write操作,而write操作的过程是不可靠性的。在write操作的过程中,Server需要将消息发送到套接字的缓存中,通过网卡转发到链路上,最终到达消费者所在的机器内核的套接字缓存中,由消费者使用套接字的read操作将消息读出来。
即使套接字的write操作成功也无法保证消息可靠,潜在的网络故障可能使消费者接收不到消息。机器宕机也可能使消息不可靠,即使消息字节流已经到达消费者所在机器,消费者所在机器的宕机也可能使消息无法被即时读取并处理。另外,即使消费者即时读取消息,内存消息队列中的所有消息也可能因为kill-9操作发生丢失。这些可能性都直接导致了消息不可靠。
因此,需要额外的措施为消息提供可靠保障。一种消息可靠性保障方式是,Server投递消息后并不立即将消息从Server删除,而是等到消费者接收、处理消息并返回Ack包给Server后,Server才删除该消息。如果消费者没有发送Ack包,那么Server将重新投递该消息。这个过程确保消息被消费者处理,保证了消息可靠。另外,假如消费者已处理消息并发送Ack包给Server,但由于网络故障等问题导致Ack包丢失时,那么Server同样会重新投递该消息,导致消息被重复处理。消息的重复处理通常由业务层面的技术手段来避免,比如在数据库层面添加主键约束等。另一种重复消息处理的避免方式是客户端对每条消息维护ID, 将被处理消息的ID记录在列表中,同时检查新到消息是否在该列表中。
RabbitMQ中的Auto Ack和Manual Ack对应着消息不可靠模式和消息可靠模式. Auto Ack即no ack,指消息投后即删除,对应消息不可靠传输。Manual Ack即手动Ack,消费者处理完消息后使用Ack包通知Server删除消息,对应消息可靠传输。
Auto Ack是RabbitMQ中最常用的模式,性能较好,但具有以下问题。当消息通过套接字write操作投递后,RabbitMQ Server立即删除该消息,该模式在遇到网络故障时容易发生消息丢失。另外,假如消费者处理消息的速率过低,可能导致消息在消费者recv buffer中大量堆积,从而导致Server端send buffer也堆积大量消息, Server端无法继续调用套接字write操作。这样,一段时间之后,Server可能强制关闭消息传输链接,导致消息不可传输。
尽管Auto Ack存在一定风险,目前许多公司仍在应用Auto Ack模式。使用Auto Ack模式时,开发者需要注意消费者和生产者的实例数量比例,使消息生产者产生消息的速率与消费者消费消息的速率大致持平。
Manual Ack是RabbitMQ 中更加智能的一种模式。Manual Ack在工作时会考虑消息消费者的消息接收能力,根据消费者的消息接受能力和当前接收到的Ack包自动调节分发消息的速率,保证消息分发可靠、不阻塞。具体来说,客户端通过PrefetchCount告知Server自身堆积消息的能力。
生产端消息不可靠问题及其解决方案
消息生产端同样存在消息的可靠性问题。从Client Publisher将消息传递给Server和从Server将消息传递给Client Consumer的过程是完全对等的,Server和Client Consumer间传递消息的可靠性问题在Client Publisher和Server间同样存在。
Client Publisher首先将消息写到套接字,再通过网络传递给Server的套接字buffer,最终由Server读取该消息。这一过程的潜在网络问题也可能使Server端接收不到消息。
另外,Server端本身也可能导致消息不可靠。Server端需要持久化消息,但出于性能开销的考虑,Server端并不在每次持久化消息时都刷盘。具体来说,Server端会对文件执行write操作,将脏数据写入操作系统的缓存中,而不是立即将数据写入磁盘。一般情况下,Server可能每几百毫秒执行一次fsync操作,通过fsync操作将文件的脏数据写入磁盘。由于Server具有宕机风险,那么每次Server宕机时,还未被fsync操作处理的数据就可能丢失,此过程类似于Redis AOF。
RabbitMQ通过生产者事务和生产者确认两个方法解决Server产生的数据不可靠问题。
生产者事务的基本原理是采用select和commit指令包裹publish,在消息生产者publish数据之前执行select操作,相当于begin transaction事务开始,在执行若干个publish操作后,再执行commit操作,相当于提交事务。根据tcp包的有序性,commit包成功接收意味着commit包之前的包也成功接收。因此,收到从Client Publisher传递过来的commit包意味着该commit包之前的所有publish包都已成功接收,即所有消息都成功接收。然而,commit包只有等到Server端的fsync操作执行完毕时才返回,因此生产者事务的效率较低,通常只在有批量publish操作时才使用生产者事务模式。也就是说,客户端将消息累计起来批量发送,以降低fsync操作带来的性能损失。此外,在进程中累计消息也存在风险,累计的消息可能由于进程挂掉而丢失。总的来说,生产者事务由于性能缺点不被RabbitMQ官方推荐。
另一种Server带来的数据不可靠问题的解决方案是生产者确认。生产者确认类似于消费端的Ack机制,生产者可能连续发送多条消息,Server将这些消息异步地通过fsync操作写入磁盘再异步地给生产者发送Ack包,告知生产者消息的接收成功。由于Ack包异步传输,不影响生产者端消息的正常发送。生产者确认模式下,Ack包批量发送,并且都携带有序号,以告知生产者该序号以前的所有消息都已正常落盘。尽管RabbitMQ推荐用户使用生产者确认模式,目前的RabbitMQ版本还未实现消息的重发机制,只实现了Ack包的批量发送,以通知Client Publisher哪些消息接收成功。当消息丢失时,Client Publisher端已publish的消息在进程挂掉时也可能丢失,而不是重新发送,因此生产者确认的作用也不明显。当然,生产者确认起到了降低消息发布速度的作用,减小了消息丢失的数量。
生产者确认中的消息重发可以通过以下几种方法实现。第一种方式在内存中累积还未收到Ack包的消息,收到Ack包后删除该消息,对于一段时间内还停留在内存中的消息,重发该消息。这种方式将未Ack消息存入内存,一旦消息生产者宕机,这些消息也会丢失。另一种方式将未收到Ack包消息存入磁盘,当收到Ack包后删除该消息,然而,磁盘存储依赖于fsync操作,降低了系统处理消息的性能。同时,这还会提高编程的复杂度,因为这要求发布消息时维护文件队列,还要求一个异步线程将文件队列中的消息发布到Server,带来了多线程和锁问题。还有一种方式将未Ack消息存入Redis,但当出现网络故障时,Redis也是不可靠的。目前提供的生产者确认中的消息重发方案都还存在问题,具体的方案选择依赖于实际场景和个人取舍。
死信队列
生产者确认中的消息重发可以通过以下几种方法实现。第一种方式在内存中累积还未收到Ack包的消息,收到Ack包后删除该消息,对于一段时间内还停留在内存中的消息,重发该消息。这种方式将未Ack消息存入内存,一旦消息生产者宕机,这些消息也会丢失。另一种方式将未收到Ack包消息存入磁盘,当收到Ack包后删除该消息,然而,磁盘存储依赖于fsync操作,降低了系统处理消息的性能。同时,这还会提高编程的复杂度,因为这要求发布消息时维护文件队列,还要求一个异步线程将文件队列中的消息发布到Server,带来了多线程和锁问题。还有一种方式将未Ack消息存入Redis,但当出现网络故障时,Redis也是不可靠的。目前提供的生产者确认中的消息重发方案都还存在问题,具体的方案选择依赖于实际场景和个人取舍。
三、死信队列
死信队列使用了RabbitMQ中的一种特殊队列属性,即x-message-ttl属性,表示队列中消息的构建时间。假如用户在声明队列时定义队列的x-message-ttl属性,此后所有进入该队列的消息都将持有构建时间,到达构建时间的消息将被删除。如果还为队列配置了回收站属性,那么即使构建时间到达,RabbitMQ也不会立即删除这些消息,而是将这些过期消息丢入回收站,即死信队列。
死信队列的工作方式如上图。Client Publisher将消息投递给路由器,也就是exchange,再由exchange将消息投递给队列,由队列生成该消息的构建时间,到达构建时间的消息将过期,同时进入死信队列。过期消息进入死信队列的方式和进入普通队列的方式基本一致,即先投递给exchange路由器,再由exchange投递消息。消费者消费死信队列,得到的消息是延后的消息,延迟的时间长度即构建时间。目前,死信队列存在的问题是,一个队列只能设置一个构建时间,消息的过期时间不够灵活,不能满足一些特殊场景的需求,比如动态的重试时间。
死信队列的另一个使用场景是Retry Later,即在一段时间后才重新处理此前处理失败的消息,这时可能用到双重死信。具体来说,死信队列不仅可以接收过期消息,还可以接收被reject的消息,即消费端拒绝处理或处理过程发生异常的消息,Reject操作具有requeue参数,当requeue设为true时被reject消息会重新进入消息队列并被重新投递,当requeue设为false时被reject消息将进入死信队列。假如死信队列持有构建时间,那么到达构建消息的消息将重新投递给原有队列,实现Retry Later。双重死信在使用过程中需注意消息处理的死循环问题,因为消息可能无限循环地进入死信队列。
生产环境下使用RabbitMQ应注意的事项
生产环境下,RabbitMQ通过使用集群模式。集群模式下,只有元信息分布在所有节点中。元信息指队列信息,路由器信息等,队列中的信息只存储在一个节点中,因此,单个节点宕机会导致所有节点都不可用。另外,RabbitMQ的所有节点间存在转发机制,即允许节点转发其他目标节点的消息处理请求,这样客户端只需连接到任意一个节点就可以实现其消息转发需求。
队列的高可用依赖于RabbitMQ的镜像队列,即在其他节点上备份某节点的消息内容。这样,当消息所在主节点宕机时,其他镜像节点可以替代主节点完成消息传递任务。
通常情况下,镜像节点是默默无闻的,客户端无需感知镜像节点的存在。只有当主节点宕机时,镜像节点才发挥作用。镜像队列的配置如下: Ha-mode具有三个选项,all指将所有队列的信息存入所有节点,这种模式最安全,但也最浪费存储空间;exactly指由用户精确指定每个队列的复制数,当ha-mode设置为exactly,ha-params设置为2时表示“一主一从”,这种模式是官方推荐的;nodes指由用户指定副本所在的节点,这种模式极少被使用。 x-queue-master-locator用于设置存储队列主节点的RabbitMQ节点。min-master指将队列主节点设置在队列数量最少的RabbitMQ节点,client-local指将队列主节点设置在当前客户端所在的RabbitMQ节点,random即随机选择节点。 Ha-sync-mode用于镜像节点代替宕机主节点并创建新节点以弥补缺失节点时,设置新节点上数据的同步策略。automatic指自动地将新主节点上数据全部同步给新节点,manual指不同步新主节点上的老数据,只同步新产生的数据。由于节点间数据同步需要耗费时间,长时间的数据同步可能会影响服务的稳定性,但通常情况下RabbitMQ的节点堆积的数据量并不大,因此RabbitMQ官方推荐使用Automatic进行数据同步。 Ha-sync-batch-size指节点间批量同步的数据量。 Ha-promote-on-shutdown表示主动停止主节点的服务时,其他节点如何替代主节点。Always指其他节点总是能顺利地替代主节点,when-synced要求与原主节点数据完全一致的节点才能替代主节点。 Ha-promote-on-failure表示异常情况下其他节点如何替代主节点,always和when-synced的含义与Ha-promote-on-shutdown中一致。
许多公司为RabbitMQ集群设置了内存模式,认为内存模式无需落盘,能够提升系统性能。但实际上,RabbitMQ官方文档指出,内存模式无法提升系统性能,它只提升了产生元信息数据的速度,即Ram Node指将元信息存入内存,可以提升元信息的创建速度,而不是消息数据的性能。这是使用RabbitMQ时的一个常见误区。
作者:PHP小能手
原文链接 ​
本文为云栖社区原创内容,未经允许不得转载。
数据库
2019-05-05 12:01:00
「深度学习福利」大神带你进阶工程师,立即查看>>>
场景:Redis面试
(图片来源于网络) 面试官 : 我看到你的简历上说你熟练使用Redis,那么你讲一下Redis是干嘛用的?
小明 : (心中窃喜,Redis不就是缓存吗?)Redis主要用作缓存,通过内存高效地存储非持久化数据。
面试官 : Redis可以用作持久化的存储吗?
小明 :嗯...应该可以吧...
面试官 : 那Redis怎么进行持久化操作呢?
小明 :嗯...不是太清楚。
面试官 : Redis的内存淘汰机制有哪些?
小明 :嗯...没了解过
面试官 :我们还可以用Redis做哪些事情?分别利用了Redis的哪个指令?
小明 :我只知道Redis还可以做分布式锁、消息队列...
面试官 :好了,我们进入下一个话题...
思考 :很明显,小明同学在面试过程中关于Redis的表现和回答肯定是比较失败的。Redis是我们工作中每天都会使用到的东西,为什么一到面试却变成了丢分项呢?
作为开发者,我们习惯了使用大神们已经封装好的东西,以此保障我们能够更专注于业务开发,却不知道这些常用工具的底层实现是什么,因此尽管平时应用起来得心应手,但一到面试还是无法让面试官眼前一亮。
本文总结了一些Redis的知识点,有原理有应用,希望可以帮助到大家。
一、Redis是什么 REmote DIctionary Server(Redis) 是一个由Salvatore Sanfilippo写的key-value存储系统。
Redis是一个开源的使用ANSI 、C语言编写、遵守BSD协议、支持网络、可基于内存亦可持久化的日志型、Key-Value数据库,并提供多种语言的API。
这里我引用了Redis教程里对Redis的描述,很官方,但是很标准。 可基于内存亦可持久化的日志型、Key-Value数据库。 我认为这个描述很贴切很全面。
1.1 Redis的行业地位
Redis是互联网技术领域使用最为广泛的存储中间件,因超高的性能、完美的文档、多方面的应用能力以及丰富完善的客户端支持在存储方面独当一面,广受好评,尤其以其性能和读取速度而成为了领域中最受青睐的中间件。基本上每一个软件公司都会使用Redis,其中包括很多大型互联网公司,比如京东、阿里、腾讯、github等。因此,Redis也成为了后端开发人员必不可少的技能。
1.2 知识图谱
在我看来,学习每一项技术,都需要有一个清晰的脉络和结构,不然你也不知道自己会了哪些、还有多少没学会。就像一本书,如果没有目录章节,也就失去了灵魂。
因此我试图总结出Redis的知识图谱,也称为脑图,如下图所示,可能知识点不是很全,后续会不断更新补充。
本系列文章的知识点也会和这个脑图基本一致,本文先介绍Redis的基本知识,后续文章会详细介绍Redis的数据结构、应用、持久化等多个方面。
二、Redis优点
2.1 速度快
作为缓存工具,Redis最广为人知的特点就是快,到底有多快呢?Redis单机qps(每秒的并发)可以达到110000次/s,写的速度是81000次/s。 那么,Redis为什么这么快呢? 绝大部分请求是纯粹的内存操作,非常快速; 使用了很多查找操作都特别快的数据结构进行数据存储,Redis中的数据结构是专门设计的。如HashMap,查找、插入的时间复杂度都是O(1); 采用单线程,避免了不必要的上下文切换和竞争条件,也不存在多进程或者多线程导致的切换而消耗CPU,不用去考虑各种锁的问题,不存在加锁、释放锁操作,没有因为可能出现死锁而导致的性能消耗; 用到了非阻塞I/O多路复用机制。
2.2 丰富的数据类型
Redis有5种常用的数据类型:String、List、Hash、set、zset,每种数据类型都有自己的用处。
2.3 原子性,支持事务
Redis支持事务,并且它的所有操作都是原子性的,同时Redis还支持对几个操作合并后的原子性执行。
2.4 丰富的特性
Redis具有丰富的特性,比如可以用作分布式锁;可以持久化数据;可以用作消息队列、排行榜、计数器;还支持publish/subscribe、通知、key过期等等。当我们要用中间件来解决实际问题的时候,Redis总能发挥出自己的用处。
三、Redis和Memcache对比
Memcache和Redis都是优秀的、高性能的内存数据库,一般我们说到Redis的时候,都会拿Memcache来和Redis做对比。(为什么要做对比呢?当然是要衬托出Redis有多好,没有对比,就没有伤害~)对比的方面包括: 存储方式 Memcache把数据全部存在内存之中,断电后会挂掉,无法做到数据的持久化,且数据不能超过内存大小。 Redis有一部分数据存在硬盘上,可以做到数据的持久性。 数据支持类型 Memcache对数据类型支持相对简单,只支持String类型的数据结构。 Redis有丰富的数据类型,包括:String、List、Hash、Set、Zset。 使用的底层模型 它们之间底层实现方式以及与客户端之间通信的应用协议不一样。 Redis直接自己构建了VM机制 ,因为一般的系统调用系统函数,会浪费一定的时间去移动和请求。
4) 存储值大小 Redis最大可以存储1GB,而memcache只有1MB。
看到这里,会不会觉得Redis特别好,全是优点,完美无缺?其实Redis还是有很多缺点的,这些缺点平常我们该如何克服呢?
四、Redis存在的问题及解决方案
4.1 缓存数据库的双写一致性的问题
问题 :一致性的问题是分布式系统中很常见的问题。一致性一般分为两种:强一致性和最终一致性,当我们要满足强一致性的时候,Redis也无法做到完美无瑕,因为数据库和缓存双写,肯定会出现不一致的情况,Redis只能保证最终一致性。
解决 :我们如何保证最终一致性呢? 第一种方式是给缓存设置一定的过期时间,在缓存过期之后会自动查询数据库,保证数据库和缓存的一致性。 如果不设置过期时间的话,我们首先要选取正确的更新策略:先更新数据库再删除缓存。但我们删除缓存的时候也可能出现某些问题,所以需要将要删除的缓存的key放到消息队列中去,不断重试,直到删除成功为止。
4.2 缓存雪崩问题
问题: 我们应该都在电影里看到过雪崩,开始很平静,然后一瞬间就开始崩塌,具有很强的毁灭性。这里也是一样的,我们执行代码的时候将很多缓存的实效时间设定成一样,接着这些缓存在同一时间都会实效,然后都会重新访问数据库更新数据,这样会导致数据库连接数过多、压力过大而崩溃。
解决: 设置缓存过期时间的时候加一个随机值。 设置双缓存,缓存1设置缓存时间,缓存2不设置,1过期后直接返回缓存2,并且启动一个进程去更新缓存1和2。
4.3 缓存穿透问题
问题: 缓存穿透是指一些非正常用户(黑客)故意去请求缓存中不存在的数据,导致所有的请求都集中到到数据库上,从而导致数据库连接异常。
解决: 利用互斥锁。缓存失效的时候,不能直接访问数据库,而是要先获取到锁,才能去请求数据库。没得到锁,则休眠一段时间后重试。 采用异步更新策略。无论key是否取到值,都直接返回。value值中维护一个缓存失效时间,缓存如果过期,异步起一个线程去读数据库,更新缓存。需要做缓存预热(项目启动前,先加载缓存)操作。 提供一个能迅速判断请求是否有效的拦截机制。比如利用布隆过滤器,内部维护一系列合法有效的key,迅速判断出请求所携带的Key是否合法有效。如果不合法,则直接返回。
4.4 缓存的并发竞争问题
问题:
缓存并发竞争的问题,主要发生在多线程对某个key进行set的时候,这时会出现数据不一致的情况。
比如Redis中我们存着一个key为amount的值,它的value是100,两个线程同时都对value加100然后更新,正确的结果应该是变为300。但是两个线程拿到这个值的时候都是100,最后结果也就是200,这就导致了缓存的并发竞争问题。
解决 如果多线程操作没有顺序要求的话,我们可以设置一个分布式锁,然后多个线程去争夺锁,谁先抢到锁谁就可以先执行。这个分布式锁可以用zookeeper或者Redis本身去实现。 可以利用Redis的incr命令。 当我们的多线程操作需要顺序的时候,我们可以设置一个消息队列,把需要的操作加到消息队列中去,严格按照队列的先后执行命令。
五、Redis的过期策略
Redis随着数据的增多,内存占用率会持续变高,我们以为一些键到达设置的删除时间就会被删除,但是时间到了,内存的占用率还是很高,这是为什么呢?
Redis采用的是 定期删除 和 惰性删除 的内存淘汰机制。
5.1 定期删除
定期删除和定时删除是有区别的: 定时删除是必须严格按照设定的时间去删除缓存,这就需要我们设置一个定时器去不断地轮询所有的key,判断是否需要进行删除。但是这样的话cpu的资源会被大幅度地占据,资源的利用率变低。所以我们选择采用定期删除,。 定期删除是时间由我们定,我们可以每隔100ms进行检查,但还是不能检查所有的缓存,Redis还是会卡死,只能随机地去检查一部分缓存,但是这样会有一些缓存无法在规定时间内删除。这时惰性删除就派上用场了。
5.2 惰性删除
举个简单的例子:中学的时候,平时作业太多,根本做不完,老师说下节课要讲这个卷子,你们都做完了吧?其实有很多人没做完,所以需要在下节课之前赶紧补上。
惰性删除也是这个道理,我们的这个值按理说应该没了,但是它还在,当你要获取这个key的时候,发现这个key应该过期了,赶紧删了,然后返回一个'没有这个值,已经过期了!'。
现在我们有了定期删除 + 惰性删除的过期策略,就可以高枕无忧了吗?并不是这样的,如果这个key一直不访问,那么它会一直滞留,也是不合理的,这就需要我们的内存淘汰机制了。
5.3 Redis的内存淘汰机制
Redis的内存淘汰机制一般有6种,如下图所示:
那么我们如何去配置Redis的内存淘汰机制呢?
在Redis.conf中我们可以进行配置 # maxmemory-policy allkeys-lru
六、小结
本文初探Redis,大概整理出了Redis的知识图谱,对照之下可以发现Redis居然有这么多的知识点需要学习;接着我们分析了Redis的优缺点,知道了其基于内存的高效的读写速度和丰富的数据类型,也分析了Redis面对数据一致性、缓存穿透、缓存雪崩等问题时该如何处理;最后我们了解了Redis的过期策略和缓存淘汰机制。
相信大家已经对Redis有了一些了解,下篇文章我们将分析Redis的数据结构、每一种数据类型是如何实现的、对应的命令有哪些。
作者:杨亨
来源: 宜信技术学院
数据库
2019-05-05 11:07:00
「深度学习福利」大神带你进阶工程师,立即查看>>>

时区转换 转换数据库中已存时间的时区 CONVERT_TZ(dt ,from_tz ,to_tz ) eg SELECT now(), CONVERT_TZ (now(), '+8:00', '-8:00') as los; //时区转换也可以通过 date_add, date_sub, timestampadd 来实现
获得国家地区时间格式 get_format(date|time|datetime, 'eur'|'usa'|'jis'|'iso'|'internal' eg select get_format(date,'usa'); -- %m.%d.%Y select get_format(time,'usa'); -- %h:%i:%s %p select get_format(datetime,'usa'); -- %Y-%m-%d %H.%i.%s //函数使用说明: CONVERT_TZ() 将时间日期值 dt 从 from_tz 给出的时区转到 to_tz 给出的时区,然后返回结果值。 关于可能指定的时区的详细论述,若自变量无效,则这个函数会返回 NULL

查询当前日期 时间 select now() -- 日期+时间 select sysdate() -- 日期+时间 select current_timestamp() -- 日期+时间 select localtime() -- 日期+时间 select localtimestamp() -- 日期+时间 select curdate() -- 日期 select curtime() -- 时间 select current_time() -- 时间

sysdate() 日期时间函数跟 now() 类似,不同之处在于:now() 在执行开始时值就得到了, sysdate() 在函数执行时动态得到值
eg:select now(), sleep(3), now(); select sysdate(), sleep(3), sysdate();
格式转换 select UNIX_TIMESTAMP('2016-12-04 16:30:28') -- 把时间转换成时间戳 select FROM_UNIXTIME('1480581161',"%Y/%m/%d") -- 时间戳转换成时间 select time_to_sec('01:00:05'); -- 时间转换成秒 3605 select sec_to_time(3605); -- 秒转换成时间 '01:00:05' select to_days('2008-08-08'); -- 日期转换成天数 733627 select from_days(733627); -- 天数转换成日期 '2008-08-08' select date_format(now(),'%Y/%m/%d') -- 日期转化为字符串 select time_format('22:23:01', '%H.%i.%s'); -- 字符串转化为时间 select str_to_date('12.12.2016 14:09:30', '%m.%d.%Y %H:%i:%s'); -- 字符串转换为日期 2016-12-12 14:09:30

时间日期选取 set @dt = '2016-12-12 11:00:19.123456'; select date(@dt); -- 2016-12-12(日期类型) select time(@dt); -- 11:00:19.123456(日期类型) select year(@dt); -- 2016 select quarter(@dt); -- 4 (返回的一年日期,取值范围为1至4季度) select month(@dt); -- 12 select week(@dt); -- 50 select day(@dt); -- 12 select hour(@dt); -- 11 select minute(@dt); -- 0 select second(@dt); -- 19 select microsecond(@dt); -- 123456(返回微秒,在mysql中我没有找到显示当前微妙的函数,now()只精确到秒,也没有找到可以储存微妙的日期类型,,好玩的是却可以抽取微秒) select dayofweek(@dt); -- 2 (从周日算起) select dayofmonth(@dt); -- 12 select dayofyear(@dt); -- 347 select week(@dt); -- 50(有mode参数) select weekofyear(@dt); -- 50 select dayofweek(@dt); -- 2 select weekday(@dt); -- 0(返回日期的星期索引0=周一,1=周二,6=星期日) select yearweek(@dt); -- 201650(获取年份和周数)(有mode参数) select dayname(@dt); -- Monday select monthname(@dt); -- December select last_day(@dt); -- 2016-12-31(返回该月份的最后一天)


mode 参数相关 (用法 eg: select week( @dt ,3)😉 模式 星期的第一天 范围 星期 1 是第一天 0 Sunday 0-53 一年中多一个星期天
1 Monday 0-53 一年多3天
2 Sunday 1-53 一年中多一个星期天
3 Monday 1-53 一年多3天
4 Sunday 0-53 一年多3天
5
6 7
Monday
Sunday Monday
0-53
1-53 1-53
一年中多一个星期一
一年多3天 一年中多一个星期一
extract()选取函数 set @dt = '2016-12-12 11:21:19.123456'; select extract(year from @dt); -- 2016 select extract(quarter from @dt); -- 4 select extract(month from @dt); -- 12 select extract(week from @dt); -- 50 select extract(day from @dt); -- 12 select extract(hour from @dt); -- 11 select extract(minute from @dt); -- 21 select extract(second from @dt); -- 19 select extract(microsecond from @dt); -- 123456 select extract(year_month from @dt); -- 201712 select extract(day_hour from @dt); -- 1211 select extract(day_minute from @dt); -- 121121 select extract(day_second from @dt); -- 12112119 select extract(day_microsecond from @dt); -- 12112119123456 select extract(hour_minute from @dt); -- 1121 select extract(hour_second from @dt); -- 112119 select extract(hour_microsecond from @dt); -- 112119123456 select extract(minute_second from @dt); -- 2119 select extract(minute_microsecond from @dt); -- 2119123456 select extract(second_microsecond from @dt); -- 19123456

MySQL Extract() 函数除了没有date(),time() 的功能外,其他功能一应具全。并且还具有选取‘day_microsecond’ 等功能。注意这里不是只选取 day 和 microsecond,而是从日期的 day 部分一直选取到 microsecond 部分。
日期加减时间间隔 set @dt = '2016-12-12 11:21:19.123456'; select date_add(@dt, interval 1 day); -- 2016-12-13 11:21:19.123456 select date_add(@dt, interval 1 hour); -- 2016-12-12 12:21:19.123456 select date_add(@dt, interval 1 minute); -- 2016-12-12 11:22:19.123456 select date_add(@dt, interval 1 second); -- 2016-12-12 11:21:20.123456 select date_add(@dt, interval 1 microsecond); -- 2016-12-12 11:21:19.123457 select date_add(@dt, interval 1 week); -- 2016-12-19 11:21:19.123456 select date_add(@dt, interval 1 month); -- 2017-01-12 11:21:19.123456 select date_add(@dt, interval 1 quarter); -- 2017-03-12 11:21:19.123456 select date_add(@dt, interval 1 year); -- 2017-12-12 11:21:19.123456 select date_add(@dt, interval -1 day); -- 2016-12-11 11:21:19.123456 select date_add(@dt, interval '01:15:30' hour_second); -- 2016-12-12 12:36:49.123456 select date_add(@dt, interval '1 01:15:30' day_second); -- 2016-12-13 12:36:49.123456 select date_sub(@dt, interval '1 1:1:1' day_second); -- 2016-12-11 10:20:18.123456 -- 用法同 date_add select period_add(@dt,2); -- 202106 (日期加/减去N月,返回到月) select timestampadd(day, 1, '2016-12-12 12:00:00'); -- 2016-12-13 12:00:00 select timestampadd(day, 3, now()); -- 查询三天后的日期

日期差值计算 set @dt2 = '2010-10-10 10:10:10.123456'; set @dt1 = '2011-12-13 14:15:16.223456'; select datediff(@dt1,@dt2); -- 429(返回天数) select timediff(@dt1,@dt2); -- 838:59:59.000000(返回日期类型,返回time差值) select period_diff(@dt1,@dt2); -- 1 (返回月数) select timestampdiff(unit,datetime_expr1,datetime_expr2) eg: select timestampdiff(year,@dt1,@dt2); -- -1 select timestampdiff(day ,@dt1,@dt2); -- -429 select timestampdiff(hour,@dt1,@dt2); -- -10300

日期 时间 拼接 makdedate(year,dayofyear) eg select makedate(2001,32); -- '2001-02-01' maketime(hour,minute,second) eg select maketime(12,15,30); -- '12:15:30'

format 格式化标识 %W 星期名字(Sunday……Saturday) %D 有英语前缀的月份的日期(1st, 2nd, 3rd, 等等。) %Y 年,数字, 4 位 %y 年, 数字, 2 位 %a 缩写的星期名字(Sun……Sat) %d 月份中的天数, 数字(00……31) %e 月份中的天数, 数字(0……31) %m 月, 数字(01……12) %c 月, 数字(1……12) %b 缩写的月份名字(Jan……Dec) %j 一年中的天数(001……366) %H 小时(00……23) %k 小时(0……23) %h 小时(01……12) %I 小时(01……12) %l 小时(1……12) %i 分钟, 数字(00……59) %r 时间,12 小时(hh:mm:ss [AP]M) %T 时间,24 小时(hh:mm:ss) %S 秒(00……59) %s 秒(00……59) %p AM或PM %w 一个星期中的天数(0=Sunday ……6=Saturday ) %U 星期(0……52), 这里星期天是星期的第一天 %u 星期(0……52), 这里星期一是星期的第一天

时间段查询实例 表tablename,添加时间是insert_time字段,该字段为int(5)类型的,现需要查询今天添加的文章总数并且按照时间从大到小排序,则查询语句如下: select * from tablename where date_format(from_UNIXTIME(insert_time),'%Y-%m-%d') = date_format(now(),'%Y-%m-%d'); 或者: select * from tablename where to_days(date_format(from_UNIXTIME(insert_time),'%Y-%m-%d')) = to_days(now()); 表的insert_time字段的存储类型是DATETIME类型或者TIMESTAMP类型,则查询语句也可按如下写法: 查询今天的信息记录: select * from tablename where to_days(insert_time) = to_days(now()); 查询昨天的信息记录: select * from tablename where to_days(now()) - to_days(insert_time) <= 1; 查询近7天的信息记录: select * from tablename where date_sub(curdate(), INTERVAL 7 DAY) <= date(insert_time); 查询近30天的信息记录: select * from tablename where date_sub(curdate(), INTERVAL 30 DAY) <= date(insert_time); 查询本月的信息记录: select * from tablename where date_format(insert_time, '%Y%m') = date_format(curdate() , '%Y%m'); 查询上一月的信息记录: select * from tablename where period_diff(date_format(now() , '%Y%m') , date_format(insert_time, '%Y%m')) =1;
转载请注明出处!
数据库
2019-05-05 09:33:00
「深度学习福利」大神带你进阶工程师,立即查看>>>
物化 视图 的修改语句
ALTER MATERIALIZED VIEW
包含了
CONSIDER FRESH
语句。通过这个语句可以使得数据库认为物化视图已经是刷新后的状态。同时可以使得这个物化视图对于查询重新生效。
CONSIDER FRESH语句的另一个主要功能就是使得物化视图可以继续为查询重写所使用。
查询重新包括三个级别的参数:ENFORCED、TRUSTED和STALE_TOLERATED。对于一个无法刷新到FRESH状态的物化视图,只有STALE_TOLERATED级别的查询重新可以考虑读取这个物化视图,而使用了CONSIDER FRESH语句后,在TRUSTED级别 Oracle 在查询重写的时候也可以使用这个物化视图:
SQL > DROP TABLE T PURGE;
表已删除。
SQL> DROP MATERIALIZED VIEW MV_T;
实体化视图 已删除。
SQL> CREATE TABLE T
2 (ID NUMBER,
3 NAME VARCHAR2(30),
4 TYPE VARCHAR2(18),
5 CREATE_DATE DATE);
表已创建。
SQL> INSERT INTO T
2 SELECT ROWNUM,
3 OBJECT_NAME,
4 OBJECT_TYPE,
5 CREATED
6 FROM ALL_OBJECTS;
已创建55680行。
SQL> COMMIT;
提交完成。
SQL> CREATE MATERIALIZED VIEW LOG ON T
2 WITH ROWID, SEQUENCE (TYPE)
3 INCLUDING NEW VALUES;
实体化视图日志已创建。
SQL> CREATE MATERIALIZED VIEW MV_T
2 REFRESH FAST
3 ENABLE QUERY REWRITE
4 AS SELECT TYPE, COUNT(*)
5 FROM T
6 GROUP BY TYPE;
实体化视图已创建。
首先建立了测试的物化视图,然后检查一下查询重写的配置:
SQL> SHOW PARAMETER QUERY_REWRITE
NAME TYPE VALUE

query_rewrite_enabled string TRUE
query_rewrite_integrity string enforced
SQL> @?/ rdbms /admin/utlxrw
表已创建。
SQL> EXEC DBMS_MVIEW.EXPLAIN_REWRITE('SELECT TYPE, COUNT(*) FROM T GROUP BY TYPE')
PL/SQL 过程已成功完成。
SQL> SELECT MESSAGE
2 FROM REWRITE_TABLE;
MESSAGE

QSM-01151: 已重写查询
QSM-01209: 已通过实体化视图 MV_T, 采用文本匹配算法进行了查询重写
SQL> TRUNCATE TABLE REWRITE_TABLE;
表被截断。
SQL> INSERT INTO T
2 VALUES (1, ' TEST ', 'T', SYSDATE);
已创建 1 行。
SQL> COMMIT;
提交完成。
SQL> EXEC DBMS_MVIEW.EXPLAIN_REWRITE('SELECT TYPE, COUNT(*) FROM T GROUP BY TYPE')
PL/SQL 过程已成功完成。
SQL> SELECT MESSAGE
2 FROM REWRITE_TABLE;
MESSAGE

QSM-01150: 未重写查询
QSM-01029: 实体化视图 MV_T 在 ENFORCED 完整性模式中已过时
SQL> ALTER SESSION SET QUERY_REWRITE_INTEGRITY = STALE_TOLERATED;
会话已更改。
SQL> TRUNCATE TABLE REWRITE_TABLE;
表被截断。
SQL> EXEC DBMS_MVIEW.EXPLAIN_REWRITE('SELECT TYPE, COUNT(*) FROM T GROUP BY TYPE')
PL/SQL 过程已成功完成。
SQL> SELECT MESSAGE
2 FROM REWRITE_TABLE;
MESSAGE

QSM-01151: 已重写查询
QSM-01209: 已通过实体化视图 MV_T, 采用文本匹配算法进行了查询重写
SQL> TRUNCATE TABLE REWRITE_TABLE;
表被截断。
SQL> ALTER SESSION SET QUERY_REWRITE_INTEGRITY = TRUSTED;
会话已更改。
SQL> EXEC DBMS_MVIEW.EXPLAIN_REWRITE('SELECT TYPE, COUNT(*) FROM T GROUP BY TYPE')
PL/SQL 过程已成功完成。
SQL> SELECT MESSAGE
2 FROM REWRITE_TABLE;
MESSAGE

QSM-01150: 未重写查询
QSM-01031: 实体化视图 MV_T 在 TRUSTED 完整性模式中已过时
可以看到,和前面分析的一样,一旦物化视图的数据和基表中的最新的数据存在差异,这个物化视图在TRUSTED模式下无法被查询重写所使用。
下面使用CONSIDER FRESH语句:
SQL> TRUNCATE TABLE REWRITE_TABLE;
表被截断。
SQL> ALTER MATERIALIZED VIEW MV_T
2 CONSIDER FRESH;
实体化视图已更改。
SQL> EXEC DBMS_MVIEW.EXPLAIN_REWRITE('SELECT TYPE, COUNT(*) FROM T GROUP BY TYPE')
PL/SQL 过程已成功完成。
SQL > SELECT MESSAGE
2 FROM REWRITE_TABLE;
MESSAGE

QSM-01151: 已重写查询
QSM-01209: 已通过 实体化视图 MV_T, 采用文本匹配算法进行了查询重写
通过使用 CONSIDER FRESH ,数据库重新认为物化视图已经和基表的数据保持一致,因此TRUSTED模式下,物化 视图 重新可以被查询重写机制所使用。
数据库
2019-05-04 17:30:00
「深度学习福利」大神带你进阶工程师,立即查看>>>
1.如果条件中有or,即使其中有条件带索引也不会使用(这也是为什么尽量少用or的原因)
注意:要想使用or,又想让索引生效,只能将or条件中的每个列都加上索引
2.对于多列索引,不是使用的第一部分,则不会使用索引
3.like查询是以%开头
4.如果列类型是字符串,那一定要在条件中将数据使用引号引用起来,否则不使用索引
数据库
2019-05-04 17:01:00
「深度学习福利」大神带你进阶工程师,立即查看>>>
安装过程:
1.去 官网 下载mysql 8.0 解压版mysql
2.直接解压,解压路径为(F:\develop\mysql-8.0.16-winx64,可以自己决定,建议不要选择C盘),配置path路径(F:\develop\mysql-8.0.16-winx64\bin追加到path下)
3.cmd(超级管理员下)进入到mysql的bin目录下。
4. mysqld --initialize --console 生成data目录,console会显示root用户密码,记住这个初始密码,后面需要用到
5. mysqld --install ,检查服务中是否出现了mysql,出现了表示安装完成,出现安装失败的,可能是因为不是超级管理员打开的cmd。
6. net start mysql ,启动成功则成功,启动失败的话,看看my.ini文件,删除skip-grant-tables(8.0不允许直接跳过密码)
7. mysql -uroot -p ,输入初始密码,登录
8.修改初始密码 ALTER USER 'root'@'localhost' IDENTIFIED BY '新密码' PASSWORD EXPIRE NEVER;
退出重新登录。
附:
1.登陆的时候,容易出现
Access denied for user 'root'@'localhost' (using password: NO)
Access denied for user 'root'@'localhost' (using password: YES)
解决办法:
1.多尝试输入几次密码,密码错误的概率比较大,因为密码是初始化随机生成的,建议先在文本文件中打出来,直接复制到cmd中。
2.参考其他大部分解决方案都是在my.ini文件的[mysqld]下添加skip-grant-tables,添加这个将导致net start mysql无法启动mysql服务,使用命令行的方式 # 这里路径改为自己路径 mysqld --defaults-file="F:\develop\mysql-8.0.16-winx64\my.ini" --console --skip-grant-tables
使用这个命令的时候,步骤4初始化生成的data文件夹不要删除,然后重新输入 mysql -uroot -p ,输入密码登录(初始密码)进行登录,本人使用这个方式成功登录,但是也怀疑是密码或者命令输入错误, mysql -uroot -p 多次输入成了mysql -u root -p
2.my.ini文件 [mysqld] # 设置3306端口 port=3306 # 设置mysql的安装目录 basedir=F:\develop\mysql-8.0.16-winx64 # 设置mysql数据库的数据的存放目录 datadir=F:\develop\mysql-8.0.16-winx64\data # 允许最大连接数 max_connections=200 # 允许连接失败的次数。这是为了防止有人从该主机试图攻击数据库系统 max_connect_errors=10 # 服务端使用的字符集默认为UTF8 character-set-server=utf8 # 创建新表时将使用的默认存储引擎 default-storage-engine=INNODB # 默认使用“mysql_native_password”插件认证 default_authentication_plugin=mysql_native_password [mysql] # 设置mysql客户端默认字符集 default-character-set=utf8 [client] # 设置mysql客户端连接服务端时默认使用的端口 port=3306 default-character-set=utf8
数据库
2019-05-04 15:26:00
「深度学习福利」大神带你进阶工程师,立即查看>>>
redis的缓存击穿?
缓存穿透是指查询一个根本不存在的数据,缓存层和存储层都不会命中,但是出于容错的考虑,如果从存储层查不到数据则不写入缓存层,如图 11-3 所示整个过程分为如下 3 步: 缓存层不命中 存储层不命中,所以不将空结果写回缓存 返回空结果
缓存穿透将导致不存在的数据每次请求都要到存储层去查询,失去了缓存保护后端存储的意义。
缓存穿透模型
缓存穿透问题可能会使后端存储负载加大,由于很多后端存储不具备高并发性,甚至可能造成后端存储宕掉。通常可以在程序中分别统计总调用数、缓存层命中数、存储层命中数,如果发现大量存储层空命中,可能就是出现了缓存穿透问题。
造成缓存穿透的基本有两个。第一,业务自身代码或者数据出现问题,第二,一些恶意攻击、爬虫等造成大量空命中,下面我们来看一下如何解决缓存穿透问题。
缓存穿透的解决方法
1)缓存空对象
如下图所示,当第 2 步存储层不命中后,仍然将空对象保留到缓存层中,之后再访问这个数据将会从缓存中获取,保护了后端数据源。
缓存空值应对穿透问题
缓存空对象会有两个问题:
第一,空值做了缓存,意味着缓存层中存了更多的键,需要更多的内存空间 ( 如果是攻击,问题更严重 ),比较有效的方法是针对这类数据设置一个较短的过期时间,让其自动剔除。
第二,缓存层和存储层的数据会有一段时间窗口的不一致,可能会对业务有一定影响。例如过期时间设置为 5 分钟,如果此时存储层添加了这个数据,那此段时间就会出现缓存层和存储层数据的不一致,此时可以利用消息系统或者其他方式清除掉缓存层中的空对象。
下面给出了缓存空对象的实现伪代码:
2)布隆过滤器拦截
如下图所示,在访问缓存层和存储层之前,将存在的 key 用布隆过滤器提前保存起来,做第一层拦截。例如: 一个个性化推荐系统有 4 亿个用户 ID,每个小时算法工程师会根据每个用户之前历史行为做出来的个性化放到存储层中,但是最新的用户由于没有历史行为,就会发生缓存穿透的行为,为此可以将所有有个性化推荐数据的用户做成布隆过滤器。如果布隆过滤器认为该用户 ID 不存在,那么就不会访问存储层,在一定程度保护了存储层。
开发提示:
有关布隆过滤器的相关知识,可以参考: https://en.wikipedia.org/wiki/Bloom_filter
可以利用 Redis 的 Bitmaps 实现布隆过滤器,GitHub 上已经开源了类似的方案,读者可以进行参考:
https://github.com/erikdubbelboer/Redis-Lua-scaling-bloom-filter
使用布隆过滤器应对穿透问题
这种方法适用于数据命中不高,数据相对固定实时性低(通常是数据集较大)的应用场景 ,代码维护较为复杂,但是缓存空间占用少。
两种方案对比
前面介绍了缓存穿透问题的两种解决方法 ( 实际上这个问题是一个开放问题,有很多解决方法 ),下面通过下表从适用场景和维护成本两个方面对两种方案进行分析。
缓存空对象和布隆过滤器方案对比
###redis的缓存雪崩?
从下图可以很清晰出什么是缓存雪崩: 由于缓存层承载着大量请求,有效的保护了存储层,但是如果缓存层由于某些原因整体不能提供服务,于是所有的请求都会达到存储层,存储层的调用量会暴增,造成存储层也会挂掉的情况。  缓存雪崩的英文原意是 stampeding herd(奔逃的野牛),指的是缓存层宕掉后,流量会像奔逃的野牛一样,打向后端存储。
缓存层不可用引起的雪崩
预防和解决缓存雪崩问题,可以从以下三个方面进行着手。
1)保证缓存层服务高可用性。
和飞机都有多个引擎一样,如果缓存层设计成高可用的,即使个别节点、个别机器、甚至是机房宕掉,依然可以提供服务,例如前面介绍过的 Redis Sentinel 和 Redis Cluster 都实现了高可用。
2)依赖隔离组件为后端限流并降级。
无论是缓存层还是存储层都会有出错的概率,可以将它们视同为资源。作为并发量较大的系统,假如有一个资源不可用,可能会造成线程全部 hang 在这个资源上,造成整个系统不可用。降级在高并发系统中是非常正常的:比如推荐服务中,如果个性化推荐服务不可用,可以降级补充热点数据,不至于造成前端页面是开天窗。
在实际项目中,我们需要对重要的资源 ( 例如 Redis、 MySQL、 Hbase、外部接口 ) 都进行隔离 ,让每种资源都单独运行在自己的线程池中,即使个别资源出现了问题,对其他服务没有影响。但是线程池如何管理,比如如何关闭资源池,开启资源池,资源池阀值管理,这些做起来还是相当复杂的,这里推荐一个 Java 依赖隔离工具 Hystrix( https://github.com/Netflix/Hystrix ),如下图所示。
数据库
2019-05-02 14:33:00
「深度学习福利」大神带你进阶工程师,立即查看>>>
我们在高并发使用redis时,我们不止需要采用分布式事务的模式,还需要采用乐观锁保证数据安全。
redis乐观锁的实现一定要伴随着redis事务。
话不多说直接上代码 redisTemplate.watch("key"); // 1 redisTemplate.multi(); redisTemplate.boundValueOps("key").set(""+id); List list= redisTemplate.exec(); System.out.println(list); if(list != null ){ //操作成功 System.out.println(id+"操作成功"); }else{ //操作失败 System.out.println(id+"操作失败"); }
注意: 使用spring整合redis需要开始事务配置 ....
数据库
2019-05-02 13:49:00
「深度学习福利」大神带你进阶工程师,立即查看>>>
redis是基于内存的,内存都是有限的,如果我们往redis中添加的数据超出了redis的内存范围会怎么样呢?
答案:某些数据将会被删除
那么如何删除呢? 手动设置过期时间:比如使用set key (expire time)设置为1个小时过期,那么1个小时之后get key还能不能得到数据呢? 答案肯定是得不到的了,那就证明数据被删除了吗?显然不是,设置过期时间, redis也只是 ---- 定期删除+惰性删除 简单说:设置了过期的key,redis会定期去检查当前过期的,然后删除,但是不是遍历所有key找出过期的,而是随机抽取,那么这样定期删除肯定就不能保证所有过期的key被删除掉,那内存中就会堆积大量过期的key,当然当我们手动 get key时如果过期了,则redis会删除, 那么如果没有被随机抽取我们也没有手动去操作的过期key,redis如何处理呢—— “淘汰”策略
Redis 有哪几种数据“淘汰”策略? 内存淘汰策略(LRU算法) noeviction:当内存不足以容纳新写入数据时,新写入操作会报错。一般不使用 allkeys-lru:当内存不足以容纳新写入数据时,在键空间中,移除最近最少使用的key(这个是最常用的) allkeys-random:当内存不足以容纳新写入数据时,在键空间中,随机移除某个key volatile-lru:当内存不足以容纳新写入数据时,在设置了过期时间的键空间中,移除最近最少使用的key volatile-random:当内存不足以容纳新写入数据时,在设置了过期时间的键空间中,随机移除某个key volatile-ttl:当内存不足以容纳新写入数据时,在设置了过期时间的键空间中,有更早过期时间的key优先移除
数据库
2019-05-02 12:23:00
「深度学习福利」大神带你进阶工程师,立即查看>>> ● mariadb.service - MariaDB 10.3.14 database server Loaded: loaded (/usr/lib/systemd/system/mariadb.service; enabled; vendor preset: disabled) Active: failed (Result: exit-code) since Wed 2019-05-01 10:43:21 CST; 17s ago Docs: man:mysqld(8) https://mariadb.com/kb/en/library/systemd/ Process: 961 ExecStartPre=/bin/sh -c systemctl unset-environment _WSREP_START_POSITION (code=exited, status=0/SUCCESS) Process: 962 ExecStartPre=/bin/sh -c [ ! -e /usr/bin/galera_recovery ] && VAR= || VAR=`/usr/bin/galera_recovery`; [ $? -eq 0 ] && systemctl set-environment _WSREP_START_POSITION=$VAR > Process: 970 ExecStart=/usr/bin/mysqld $MYSQLD_OPTS $_WSREP_NEW_CLUSTER $_WSREP_START_POSITION (code=exited, status=1/FAILURE) Main PID: 970 (code=exited, status=1/FAILURE) Status: "MariaDB server is down" 5月 01 10:43:19 pengArch mysqld[970]: 2019-05-01 10:43:19 0 [Note] InnoDB: File './ibtmp1' size is now 12 MB. 5月 01 10:43:19 pengArch mysqld[970]: 2019-05-01 10:43:19 0 [Note] InnoDB: Waiting for purge to start 5月 01 10:43:19 pengArch mysqld[970]: 2019-05-01 10:43:19 0 [Note] InnoDB: 10.3.14 started; log sequence number 3330072; transaction id 1614 5月 01 10:43:19 pengArch mysqld[970]: 2019-05-01 10:43:19 0 [Note] InnoDB: Loading buffer pool(s) from /var/lib/mysql/ib_buffer_pool 5月 01 10:43:19 pengArch mysqld[970]: 2019-05-01 10:43:19 0 [Note] Recovering after a crash using tc.log 5月 01 10:43:19 pengArch mysqld[970]: 2019-05-01 10:43:19 0 [ERROR] Can't init tc log 5月 01 10:43:19 pengArch mysqld[970]: 2019-05-01 10:43:19 0 [ERROR] Aborting 5月 01 10:43:21 pengArch systemd[1]: mariadb.service: Main process exited, code=exited, status=1/FAILURE 5月 01 10:43:21 pengArch systemd[1]: mariadb.service: Failed with result 'exit-code'. 5月 01 10:43:21 pengArch systemd[1]: Failed to start MariaDB 10.3.14 database server.
删除/var/lib/mysql/ib_logfile*和/var/lib/mysql/tc.log,重新启动,正常。
数据库
2019-05-01 11:05:03
「深度学习福利」大神带你进阶工程师,立即查看>>>
一、数据库设计范式
1. 什么是范式
简言之就是,数据库设计对数据的存储性能,还有开发人员对数据的操作都有莫大的关系。所以建立科学的,规范的的数据库是需要满足一些规范的来优化数据存储方式。在关系型数据库中这些规范就可以称为范式。
2. 什么是三大范式: 第一范式:当关系模式R的所有属性都不能在分解为更基本的数据单位时,称R是满足第一范式的,简记为1NF。满足第一范式是关系模式规范化的最低要求,否则,将有很多基本操作在这样的关系模式中实现不了。 第二范式:如果关系模式R满足第一范式,并且R得所有非主属性都完全依赖于R的每一个候选关键属性,称R满足第二范式,简记为2NF。 第三范式:设R是一个满足第一范式条件的关系模式,X是R的任意属性集,如果X非传递依赖于R的任意一个候选关键字,称R满足第三范式,简记为3NF.
注:关系实质上是一张二维表,其中每一行是一个元组,每一列是一个属性
二、理解三大范式
第一范式 每一列属性都是不可再分的属性值,确保每一列的原子性 两列的属性相近或相似或一样,尽量合并属性一样的列,确保不产生冗余数据。
第二范式 每一行的数据只能与其中一列相关,即一行数据只做一件事。只要数据列中出现数据重复,就要把表拆分开来。
例如: 一个人同时订几个房间,就会出来一个订单号多条数据,这样子联系人都是重复的,就会造成数据冗余。我们应该把他拆开来。
这样便实现啦一条数据做一件事,不掺杂复杂的关系逻辑。同时对表数据的更新维护也更易操作。
第三范式 数据不能存在传递关系,即没个属性都跟主键有直接关系而不是间接关系。像:a-->b-->c 属性之间含有这样的关系,是不符合第三范式的。
例如:Student表(学号,姓名,年龄,性别,所在院校,院校地址,院校电话)
这样一个表结构,就存在上述关系。 学号--> 所在院校 --> (院校地址,院校电话)
这样的表结构,我们应该拆开来,如下。
(学号,姓名,年龄,性别,所在院校)--(所在院校,院校地址,院校电话)
个人意见
三大范式只是一般设计数据库的基本理念,可以建立冗余较小、结构合理的数据库。如果有特殊情况,当然要特殊对待,数据库设计最重要的是看需求跟性能,需求>性能>表结构。所以不能一味的去追求范式建立数据库。
数据库
2019-04-30 22:30:07
「深度学习福利」大神带你进阶工程师,立即查看>>>
1. redis的优点 速度快 (1) 因为数据存在内存中,类似于 HashMap ,HashMap 的优势就是查找和操作的时间复杂度都是O (1) 。
(2) Redis 本质上是一个 Key-Value 类型的内存数据库,很像Memcached ,整个数据库统统加载在内存当中进行操作,定期通过 异步操作 把数据库数据 flush 到硬盘上进行保存。fork子进程持久化。
(3) 因为是纯内存操作,Redis 的性能非常出色,每秒可以处理超过 10 万次读写操作,是已知性能最快的 Key-Value 数据库。 支持丰富数据类型: String ,List,Set,Sorted Set,Hash 。 Redis 的出色之处不仅仅是性能,Redis 最大的魅力是支持保存多种数据结构,此外单个 Value 的最大限制是1GB,不像 Memcached只能保存1MB的数据,因此Redis可以用来实现很多有用的功能 丰富的特性 订阅发布 Pub / Sub 功能
Key 过期策略
事务
支持多个 DB
计数 持久化存储 Redis 提供 RDB 和 AOF 两种数据的持久化存储方案,解决内存数据库最担心的万一 Redis 挂掉,数据会消失掉。
2.redis的缺点 1、由于 Redis 是内存数据库,所以,单台机器,存储的数据量,跟机器本身的内存大小。虽然 Redis 本身有 Key 过期策略,但是还是需要提前预估和节约内存。如果内存增长过快,需要定期删除数据。 2、redis是单线程的,单台服务器无法充分利用多核服务器的CPU
数据库
2019-04-30 15:55:00
「深度学习福利」大神带你进阶工程师,立即查看>>>
Linux centos7 以tar包方式安装elasticsearch 7.0

1. 官网下载 elasticsearch
https://www.elastic.co/cn/downloads/elasticsearch

我下载的是最新稳定版本7.0.0
wget https://artifacts.elastic.co/downloads/elasticsearch/elasticsearch-7.0.0-linux-x86_64.tar.gz

2. 解压文件, 拷贝到安装目录
tar -xzvf elasticsearch 解压缩,并将解压缩的文件剪切到/usr/local/目录下
3. 进入config文件夹下编辑elasticsearch.yml
集群模式下,放开cluster.name注释,单机模式下,放开node.name
数据存储和日志存储路径放开注释
网络设置 设置ip限制,端口设置,跨越设置i
4. 配置完成后进入bin目录执行启动脚本elasticsearch
前台启动 ./elasticsearch
后台启动 ./elasticsearch -d

5. 启动过程中遇到的坑
1) root启动时,elasticsearch不能以root用户启动
org.elasticsearch.bootstrap.StartupException: java.lang.RuntimeException: can not run elasticsearch as root
处理方法,创建非root用户,使用非root用户启动
a) adduser es 创建用户es
b) passwd es 设置es密码,
c) 设置文件权限 chown -R es /opt/supp_app/elasticsearch-6.4.0
d) visudo.设置用户对命令的执行权限 配置同root

2) JDK版本要求最低版本1.8,在elasticsearch中设置JAVA_HOME CATALINA_HOME和PATH等信息

3) elasticsearch.yml配置有问题,保证配置的文件的key后面直接跟着冒号,冒号后空格一位,再输入对应的value
4) elasticsearch用户拥有的可创建文件描述的权限太低,至少需要65536,
处理办法: #切换到root用户修改
vim /etc/security/limits.conf # 在最后面追加下面内容
*** hard nofile 65536
*** soft nofile 65536 #*** 是启动ES的用户
5) max_map_count文件包含限制一个进程可以拥有的VMA(虚拟内存区域)的数量
处理办法: #切换到root用户修改
vim /etc/sysctl.conf # 在最后面追加下面内容
vm.max_map_count=655360
执行 sysctl -p
最终正常启动显示页面


数据库
2019-04-30 14:46:00
「深度学习福利」大神带你进阶工程师,立即查看>>>
假设我们有这样的场景:我们想在 Cassandra 中使用一张表记录用户基本信息(比如 email、密码等)以及用户状态更新。我们知道,用户的基本信息一般很少会变动,但是状态会经常变化,如果每次状态更新都把用户基本信息都加进去,势必会让费大量的存储空间。为了解决这种问题,Cassandra 引入了 static column。同一个 partition key 中被声明为 static 的列只有一个值的,也就是只存储一份。
定义 static column
在表中将某个列定义为 STATIC 很简单,只需要在列的最后面加上 STATIC 关键字,具体如下: CREATE TABLE "iteblog_users_with_status_updates" ( "username" text, "id" timeuuid, "email" text STATIC, "encrypted_password" blob STATIC, "body" text, PRIMARY KEY ("username", "id") );
iteblog_users_with_status_updates 表中我们将 email 和 encrypted_password 两个字段设置为 STATIC 了,这意味着同一个 username 只会有一个 email 和 encrypted_password 。
注意,不是任何表都支持给列加上 STATIC 关键字的,静态列有以下限制。
1、如果表没有定义 Clustering columns(又称 Clustering key),这种情况是不能添加静态列的。如下: cqlsh:iteblog_keyspace> CREATE TABLE "iteblog_users_with_status_updates_invalid" ( ... "username" text, ... "id" timeuuid, ... "email" text STATIC, ... "encrypted_password" blob STATIC, ... "body" text, ... PRIMARY KEY ("username") ... ); InvalidRequest: Error from server: code=2200 [Invalid query] message="Static columns are only useful (and thus allowed) if the table has at least one clustering column"
iteblog_users_with_status_updates_invalid 表只有 PRIMARY KEY,没有定义 clustering column,不支持创建 Static columns。这是因为静态列在同一个 partition key 存在多行的情况下才能达到最优情况,而且行数越多效果也好。但是如果没有定义 clustering column,相同 PRIMARY KEY 的数据在同一个分区里面只存在一行数据,本质上就是静态的,所以没必要支持静态列。
2、如果建表的时候指定了 COMPACT STORAGE,这时候也不允许存在静态列: cqlsh:iteblog_keyspace> CREATE TABLE "iteblog_users_with_status_updates_invalid" ( ... "username" text, ... "id" timeuuid, ... "email" text STATIC, ... "encrypted_password" blob STATIC, ... "body" text, ... PRIMARY KEY ("username", "id") ... )WITH COMPACT STORAGE; InvalidRequest: Error from server: code=2200 [Invalid query] message="Static columns are not supported in COMPACT STORAGE tables"
3、如果列是 partition key/Clustering columns 的一部分,那么这个列不能说明为静态列: cqlsh:iteblog_keyspace> CREATE TABLE "iteblog_users_with_status_updates_invalid" ( ... "username" text, ... "id" timeuuid STATIC, ... "email" text STATIC, ... "encrypted_password" blob STATIC, ... "body" text, ... PRIMARY KEY ("username", "id") ... ); InvalidRequest: Error from server: code=2200 [Invalid query] message="Static column id cannot be part of the PRIMARY KEY" cqlsh:iteblog_keyspace> CREATE TABLE "iteblog_users_with_status_updates_invalid" ( ... "username" text, ... "id" timeuuid, ... "email" text STATIC, ... "encrypted_password" blob STATIC, ... "body" text, ... PRIMARY KEY (("username", "id"), email) ... ); InvalidRequest: Error from server: code=2200 [Invalid query] message="Static column email cannot be part of the PRIMARY KEY"
给静态列的表插入数据
含有静态列的表插入数据和正常表类似,比如我们现在往 iteblog_users_with_status_updates 导入数据: cqlsh:iteblog_keyspace> INSERT INTO "iteblog_users_with_status_updates" ... ("username", "id", "email", "encrypted_password", "body") ... VALUES ( ... 'iteblog', ... NOW(), ... 'iteblog_hadoop@iteblog.com', ... 0x877E8C36EFA827DBD4CAFBC92DD90D76, ... 'Learning Cassandra!' ... ); cqlsh:iteblog_keyspace> select username, email, encrypted_password, body from iteblog_users_with_status_updates; username | email | encrypted_password | body ----------+----------------------------+------------------------------------+--------------------- iteblog | iteblog_hadoop@iteblog.com | 0x877e8c36efa827dbd4cafbc92dd90d76 | Learning Cassandra! (1 rows)
我们成功的插入一条数据了。但是上面的插入语句做了两件事: 所有 username 为 iteblog 数据中的 email 和 encrypted_password 都被设置为 iteblog_hadoop@iteblog.com 和 0x877e8c36efa827dbd4cafbc92dd90d76 了。 在 iteblog 所在的分区中新增了 body 内容为 Learning Cassandra! 的记录。
现在我们再往表中插入一条数据,如下: cqlsh:iteblog_keyspace> INSERT INTO "iteblog_users_with_status_updates" ... ("username", "id", "body") ... VALUES ('iteblog', NOW(), 'I love Cassandra!'); cqlsh:iteblog_keyspace> select username, email, encrypted_password, body from iteblog_users_with_status_updates; username | email | encrypted_password | body ----------+----------------------------+------------------------------------+--------------------- iteblog | iteblog_hadoop@iteblog.com | 0x877e8c36efa827dbd4cafbc92dd90d76 | Learning Cassandra! iteblog | iteblog_hadoop@iteblog.com | 0x877e8c36efa827dbd4cafbc92dd90d76 | I love Cassandra! (2 rows) cqlsh:iteblog_keyspace>
可以看到,这次插入数据的时候,我们并没有指定 email 和 encrypted_password,但是从查询结果可以看出,新增加的行 email 和 encrypted_password 的值和之前是一样的!
现在由于某些原因,用户修改了自己的 email,我们来看看会发生什么事: cqlsh:iteblog_keyspace> UPDATE iteblog_users_with_status_updates SET email = 'iteblog@iteblog.com' ... WHERE username = 'iteblog'; cqlsh:iteblog_keyspace> select username, email, encrypted_password, body from iteblog_users_with_status_updates; username | email | encrypted_password | body ----------+---------------------+------------------------------------+--------------------- iteblog | iteblog@iteblog.com | 0x877e8c36efa827dbd4cafbc92dd90d76 | Learning Cassandra! iteblog | iteblog@iteblog.com | 0x877e8c36efa827dbd4cafbc92dd90d76 | I love Cassandra! (2 rows)
从上面查询这输出的结果可以看出, username 为 iteblog 的 email 全部修改成一样的了!这就是静态列的强大之处。
现在表中存在了用户的邮箱和密码等信息,如果我们前端做了个页面支持用户修改自己的邮箱和密码,这时候我们的后台系统需要获取到现有的邮箱和密码,具体如下: cqlsh:iteblog_keyspace> SELECT "username", "email", "encrypted_password" ... FROM "iteblog_users_with_status_updates" ... WHERE "username" = 'iteblog'; username | email | encrypted_password ----------+---------------------+------------------------------------ iteblog | iteblog@iteblog.com | 0x877e8c36efa827dbd4cafbc92dd90d76 iteblog | iteblog@iteblog.com | 0x877e8c36efa827dbd4cafbc92dd90d76 (2 rows)
可以看出,表中有多少行 username 为 iteblog 的数据将会输出多少行邮箱和密码,这肯定不是我们想要的。这时候我们可以在查询的时候加上 DISTINCT 关键字,如下: cqlsh:iteblog_keyspace> SELECT DISTINCT "username", "email", "encrypted_password" ... FROM "iteblog_users_with_status_updates" ... WHERE "username" = 'iteblog'; username | email | encrypted_password ----------+---------------------+------------------------------------ iteblog | iteblog@iteblog.com | 0x877e8c36efa827dbd4cafbc92dd90d76 (1 rows)
这样不管表中有多少行 username 为 iteblog 的数据,最终都会显示一行数据。注意,虽然我们加了 DISTINCT 关键字,但是 Cassandra 并不是将 username 为 iteblog 的数据全部拿出来,然后再去重的,因为静态列本来在底层就存储了一份,所以没必要去重。
静态列的意义
到这里,我们已经了解了 Cassandra 中静态列的创建、使用等。那静态列有什么意义呢?因为 Cassandra 中是不支持 join 的,静态列相当于把两张表进行了 join 操作。
那什么时候建议使用静态列呢?如果两张表关联度很大,而且我们经常需要同时查询这两张表,那这时候就可以考虑使用静态列了。
原文链接
本文为云栖社区原创内容,未经允许不得转载。
数据库
2019-04-30 13:32:00
「深度学习福利」大神带你进阶工程师,立即查看>>>
Redis实现了不定长压缩前缀的radix tree,用在集群模式下存储slot对应的的所有key信息。本文将详述在Redis中如何实现radix tree。
核心数据结构
raxNode是radix tree的核心数据结构,其结构体如下代码所示: typedef struct raxNode { uint32_t iskey:1; uint32_t isnull:1; uint32_t iscompr:1; uint32_t size:29; unsigned char data[]; } raxNode; iskey:表示这个节点是否包含key 0:没有key 1:表示从头部到其父节点的路径完整的存储了key,查找的时候按子节点iskey=1来判断key是否存在 isnull:是否有存储value值,比如存储元数据就只有key,没有value值。value值也是存储在data中 iscompr:是否有前缀压缩,决定了data存储的数据结构 size:该节点存储的字符个数 data:存储子节点的信息 iscompr=0:非压缩模式下,数据格式是: [header strlen=0][abc][a-ptr][b-ptr][c-ptr](value-ptr?) ,有size个字符,紧跟着是size个指针,指向每个字符对应的下一个节点。size个字符之间互相没有路径联系。 iscompr=1:压缩模式下,数据格式是: [header strlen=3][xyz][z-ptr](value-ptr?) ,只有一个指针,指向下一个节点。size个字符是压缩字符片段
Rax Insert
以下用几个示例来详解rax tree插入的流程。假设j是遍历已有节点的游标,i是遍历新增节点的游标。
场景一:只插入abcd
z-ptr指向的叶子节点iskey=1,使用了压缩前缀。
场景二:在abcd之后插入abcdef
从abcd父节点的每个压缩前缀字符比较,遍历完所有abcd节点后指向了其空子节点,j = 0, i < len(abcded)。
查找到abcd的空子节点,直接将ef赋值到子节点上,成为abcd的子节点。ef节点被标记为iskey=1,用来标识abcd这个key。ef节点下再创建一个空子节点,iskey=1来表示abcdef这个key。
场景三:在abcd之后插入ab
ab在abcd能找到前两位的前缀,也就是i=len(ab),j < len(abcd)。
将abcd分割成ab和cd两个子节点,cd也是一个压缩前缀节点,cd同时被标记为iskey=1,来表示ab这个key。
cd下挂着一个空子节点,来标记abcd这个key。
场景四:在abcd之后插入abABC
abcABC在abcd中只找到了ab这个前缀,即i < len(abcABC),j < len(abcd)。这个步骤有点复杂,分解一下: step 1:将abcd从ab之后拆分,拆分成ab、c、d 三个节点。 step 2:c节点是一个非压缩的节点,c挂在ab子节点上。 step 3:d节点只有一个字符,所以也是一个非压缩节点,挂在c子节点上。 step 4:将ABC 拆分成了A和BC, A挂在ab子节点上,和c节点属于同一个节点,这样A就和c同属于父节点ab。 step 5:将BC作为一个压缩前缀的节点,挂在A子节点下。 step 6:d节点和BC节点都挂一个空子节点分别标识abcd和abcABC这两个key。
场景五:在abcd之后插入Aabc
abcd和Aabc没有前缀匹配,i = 0,j = 0。
将abcd拆分成a、bcd两个节点,a节点是一个非压缩前缀节点。
将Aabc拆分成A、abc两个节点,A节点也是一个非压缩前缀节点。
将A节点挂在和a相同的父节点上。
同上,在bcd和abc这两个节点下挂空子节点来分别表示两个key。
Rax Remove
删除
删除一个key的流程比较简单,找到iskey的节点后,向上遍历父节点删除非iskey的节点。如果是非压缩的父节点并且size > 1,表示还有其他非相关的路径存在,则需要按删除子节点的模式去处理这个父节点,主要是做memove和realloc。
合并
删除一个key之后需要尝试做一些合并,以收敛树的高度。
合并的条件是: iskey=1的节点不能合并 子节点只有一个字符 父节点只有一个子节点(如果父节点是压缩前缀的节点,那么只有一个子节点,满足条件。如果父节点是非压缩前缀的节点,那么只能有一个字符路径才能满足条件)
结束语
云数据库Redis版(ApsaraDB for Redis)是一种稳定可靠、性能卓越、可弹性伸缩的数据库服务。基于飞天分布式系统和全SSD盘高性能存储,支持主备版和集群版两套高可用架构。提供了全套的容灾切换、故障迁移、在线扩容、性能优化的数据库解决方案。欢迎各位购买使用:云数据库 Redis 版
作者:羽洵
原文链接 ​
本文为云栖社区原创内容,未经允许不得转载。
数据库
2019-04-30 12:23:07
「深度学习福利」大神带你进阶工程师,立即查看>>>
什么影响了数据库查询速度
影响数据库查询速度的四个因素
风险分析
QPS: QueriesPerSecond 意思是 “每秒查询率” ,是一台服务器每秒能够相应的查询次数,是对一个特定的查询服务器在规定时间内所处理流量多少的衡量标准。
TPS: 是 TransactionsPerSecond 的缩写,也就是 事务数/秒 。它是软件测试结果的测量单位。客户机在发送请求时开始计时,收到服务器响应后结束计时,以此来计算使用的时间和完成的事务个数。 网站性能测试指标详解 ,更多看这篇文章。
Tips: 最好不要在主库上数据库备份 ,大型活动前取消这样的计划。
1、效率低下的 sql :超高的 QPS 与 TPS 。
2、大量的并发:数据连接数被占满( max_connection 默认 100 ,一般把连接数设置得大一些)。

并发量 : 同一时刻数据库服务器处理的请求数量
3、超高的 CPU 使用率: CPU 资源耗尽出现宕机。
4、磁盘 IO :磁盘 IO 性能突然下降、大量消耗磁盘性能的计划任务。解决:更快磁盘设备、调整计划任务、做好磁盘维护。

网卡流量:如何 避免无法连接数据库的情况
1、减少从服务器的数量(从服务器会从主服务器复制日志)
2、进行分级缓存(避免前端大量缓存失效)
3、避免使用 select 进行查询
4、分离业务网络和服务器网络

大表带来的问题(重要)
1.4.1 大表的特点
1、记录行数巨大,单表超千万
2、表数据文件巨大,超过 10 个 G

大表的危害
1、慢查询: 很难在短时间内过滤出需要的数据
查询字区分度低 -> 要在大数据量的表中筛选出来其中一部分数据会产生大量的磁盘 io -> 降低磁盘效率

2.对 DDL 影响:
建立索引需要很长时间:
MySQL-v<5.5 建立索引会锁表 MySQL-v>=5.5 建立索引会造成主从延迟( mysql 建立索引,先在组上执行,再在库上执行)
修改表结构需要长时间的锁表: 会造成长时间的主从延迟('480秒延迟')

如何处理数据库上的大表
分库分表把一张大表分成多个小表
难点:
1、分表主键的选择
2、分表后跨分区数据的查询和统计

大事务带来的问题(重要)
1、 什么是事务
事务是数据库系统区别于其他一切文件系统的重要特性之一
事务是一组具有原子性的SQL语句,或是一个独立的工作单元
事务要求符合:原子性、一致性、隔离性、持久性
2、事务的 ACID 属性
1、原子性( atomicity ):全部成功,全部回滚失败。银行存取款。
2、一致性(consistent):银行转账的总金额不变。3、隔离性(isolation):

隔离性等级:
未提交读( READ UNCOMMITED ) 脏读 ,两个事务之间互相可见;已提交读( READ COMMITED )符合隔离性的基本概念,一个事务进行时,其它已提交的事物对于该事务是可见的,即可以获取其它事务提交的数据。可重复读( REPEATABLE READ ) InnoDB的默认隔离等级 。事务进行时,其它所有事务对其不可见,即多次执行读,得到的结果是一样的!可串行化( SERIALIZABLE ) 在读取的每一行数据上都加锁,会造成大量的锁超时和锁征用,严格数据一致性且没有并发是可使用。
查看系统的事务隔离级别: show variables like'%iso%' ; 开启一个新事务: begin ; 提交一个事务: commit ; 修改事物的隔离级别: setsession tx_isolation='read-committed' ;推荐:面试问烂的 MySQL 四种隔离级别,看完吊打面试官!关注Java技术栈微信公众号,在后台回复关键字: mysql ,可以获取更多栈长整理的MySQL技术干货。
4、持久性( DURABILITY ):从数据库的角度的持久性,磁盘损坏就不行了
redolog 机制保证事务更新的 一致性 和 持久性

大事务
运行时间长,操作数据比较多的事务;
风险:锁定数据太多,回滚时间长,执行时间长。
1、锁定太多数据,造成大量阻塞和锁超时;
2、回滚时所需时间比较长,且数据仍然会处于锁定;
3、如果执行时间长,将造成主从延迟,因为只有当主服务器全部执行完写入日志时,从服务器才会开始进行同步,造成延迟。关注Java技术栈微信公众号,在后台回复关键字: mysql ,可以获取更多栈长整理的MySQL技术干货。
解决思路:
1、避免一次处理太多数据,可以分批次处理;
2、移出不必要的 SELECT 操作,保证事务中只有必要的写操作。

什么影响了MySQL性能(非常重要)
2.1 影响性能的几个方面
1、服务器硬件。
2、服务器系统(系统参数优化)。
3、 存储引擎 。 MyISAM :不支持事务,表级锁。 InnoDB : 支持事务,支持行级锁,事务 ACID 。
4、 数据库参数配置。
5、 数据库结构设计和SQL语句。(重点优化)
2.2 MySQL体系结构
分三层:客户端->服务层->存储引擎

1、 MySQL 是 插件式的存储引擎 ,其中存储引擎分很多种。只要实现符合mysql存储引擎的接口,可以开发自己的存储引擎! 2、所有跨存储引擎的功能都是在服务层实现的。
3、MySQL的 存储引擎是针对表的,不是针对库的 。也就是说在一个数据库中可以使用不同的存储引擎。但是不建议这样做。
2.3 InnoDB存储引擎
MySQL5.5 及之后版本 默认的存储引擎 : InnoDB 。
2.3.1 InnoDB使用表空间进行数据存储。
show variables like'innodb_file_per_table
如果innodbfileper_table 为 ON 将建立独立的表空间,文件为tablename.ibd;
如果innodbfileper_table 为 OFF 将数据存储到系统的共享表空间,文件为ibdataX(X为从1开始的整数);
.frm :是服务器层面产生的文件,类似服务器层的数据字典, 记录表结构 。
2.3.2 (MySQL5.5默认)系统表空间与( MySQL5.6 及以后默认)独立表空间
1、系统表空间无法简单的收缩文件大小,造成空间浪费,并会产生大量的磁盘碎片。
2、独立表空间可以通过 optimeze table 收缩系统文件,不需要重启服务器也不会影响对表的正常访问。
3、如果对多个表进行刷新时,实际上是顺序进行的,会产生IO瓶颈。
4、独立表空间可以同时向多个文件刷新数据。
强烈建立对Innodb 使用独立表空间,优化什么的更方便,可控。
2.3.3 系统表空间的表转移到独立表空间中的方法
1、使用mysqldump 导出所有数据库数据(存储过程、触发器、计划任务一起都要导出 )可以在从服务器上操作。
2、停止MYsql 服务器,修改参数(my.cnf加入innodbfileper_table),并删除Inoodb相关文件(可以重建Data目录)。
3、重启MYSQL,并重建Innodb系统表空间。
4、 重新导入数据。
或者 Altertable 同样可以的转移,但是无法回收系统表空间中占用的空间。
2.4 InnoDB存储引擎的特性
2.4.1 特性一:事务性存储引擎及两个特殊日志类型:Redo Log 和 Undo Log
1、 Innodb 是一种 事务性存储引擎 。
2、完全支持事务的 ACID 特性。3、支持事务所需要的两个特殊日志类型: RedoLog 和 UndoLog
Redo Log: 实现事务的持久性(已提交的事务)。 Undo Log: 未提交的事务,独立于表空间,需要随机访问,可以存储在高性能io设备上。
Undo 日志记录某数据被修改前的值,可以用来在事务失败时进行 rollback ; Redo 日志记录某数据块被修改后的值,可以用来恢复未写入 data file 的已成功事务更新的数据。
2.4.2 特性二:支持行级锁
1、InnoDB支持行级锁。
2、行级锁可以最大程度地支持并发。
3、行级锁是由存储引擎层实现的。
2.5 什么是锁
2.5.1 锁
锁的主要作用是管理共享资源的并发访问
锁用于实现事务的隔离性
2.5.2 锁类型
2.5.3 锁的粒度
MySQL的事务支持 不是绑定在MySQL服务器本身 , 而是与存储引擎相关 表级锁 行级锁
将 table_name 加表级锁命令: locktable table_name write ; 写锁会阻塞其它用户对该表的‘读写’操作,直到 写锁被释放: unlock tables ;
1、 锁的开销越大,粒度越小,并发度越高。 2、表级锁通常是在服务器层实现的。3、行级锁是存储引擎层实现的。innodb的锁机制,服务器层是不知道的
2.5.4 阻塞和死锁
1、阻塞是由于资源不足引起的排队等待现象。
2、死锁是由于两个对象在拥有一份资源的情况下申请另一份资源,而另一份资源恰好又是这两对象正持有的,导致两对象无法完成操作,且所持资源无法释放。
2.6 如何选择正确的存储引擎
参考条件:
1、事务 2、备份( Innobd 免费在线备份) 3、崩溃恢复 4、存储引擎的特有特性
总结: nnodb大法好。 注意: 别使用混合存储引擎,比如回滚会出问题在线热备问题。
2.7 配置参数
2.7.1 内存配置相关参数
确定可以使用的内存上限。
内存的使用上限不能超过物理内存,否则容易造成内存溢出;(对于32位操作系统,MySQL只能试用3G以下的内存。 37 个 MySQL 数据库小技巧, 推荐看一下。关注Java技术栈微信公众号,在后台回复关键字:mysql,可以获取更多栈长整理的MySQL技术干货。
确定MySQL的 每个连接 单独 使用的内存。
sort_buffer_size
定义了每个线程排序缓存区的大小,MySQL在有查询、需要做排序操作时才会为每个缓冲区分配内存(直接分配该参数的全部内存);
join_buffer_size
定义了每个线程所使用的连接缓冲区的大小,如果一个查询关联了多张表,MySQL会为每张表分配一个连接缓冲,导致一个查询产生了多个连接缓冲;
read_buffer_size
定义了当对一张MyISAM进行全表扫描时所分配读缓冲池大小,MySQL有查询需要时会为其分配内存,其必须是4k的倍数;
read_rnd_buffer_size
索引缓冲区大小,MySQL有查询需要时会为其分配内存,只会分配需要的大小。
注意: 以上四个参数是为一个线程分配的,如果有100个连接,那么需要×100。
MySQL数据库实例:
MySQL是 单进程多线程 (而oracle是多进程),也就是说 MySQL 实例在系统上表现就是一个服务进程,即进程;
MySQL实例是线程和内存组成,实例才是真正用于操作数据库文件的;
一般情况下 一个实例操作一个或多个数据库; 集群情况下 多个实例操作一个或多个数据库。
如何为缓存池分配内存:
Innodb_buffer_pool_size
定义了Innodb所使用缓存池的大小,对其性能十分重要,必须足够大,但是过大时,使得Innodb 关闭时候需要更多时间把脏页从缓冲池中刷新到磁盘中;
总内存-(每个线程所需要的内存*连接数)- 统保留内存
key_buffer_size
定义了MyISAM所使用的缓存池的大小,由于数据是依赖存储操作系统缓存的,所以要为操作系统预留更大的内存空间; select sum(index_length) from information_schema.talbes where engine='myisam'
注意: 即使开发使用的表全部是Innodb表,也要为MyISAM预留内存,因为MySQL系统使用的表仍然是MyISAM表。
max_connections
控制允许的最大连接数, 一般2000更大。不要使用外键约束保证数据的完整性
2.8 性能优化顺序 库结构设计和SQL语句 数据库存储引擎的选择和参数配置 系统选择及优化 硬件升级
两种最长使用的 Innodb 和 Myisam 区别
1. 事务的支持不同(innodb支持事务,myisam不支持事务)
2. 锁粒度(innodb行锁应用,myisam表锁)
3. 存储空间(innodb既缓存索引文件又缓存数据文件,myisam只能缓存索引文件)
4. 存储结构
(myisam:数据文件的扩展名为.MYD myData ,索引文件的扩展名是.MYI myIndex)
(innodb:所有的表都保存在同一个数据文件里面 即为.Ibd)
5. 统计记录行数
(myisam:保存有表的总行数,select count(*) from table;会直接取出出该值)
(innodb:没有保存表的总行数,select count(*) from table;就会遍历整个表,消耗相当大)


数据库
2019-04-29 23:15:00
「深度学习福利」大神带你进阶工程师,立即查看>>>
1、字符转换
1.1 将字符的数字转成数字,比如'0'转成0可以直接用加法来实现
例如:将test表中的 t 进行排序,可t的定义为varchar,可以这样解决
select * from test order by (t+0)
1.2在进行ifnull处理时,比如 ifnull(a/b,'0') 这样就会导致 a/b成了字符串,因此需要把'0'改成0,即可解决此困扰
1.3 比较数字和varchar时,比如a=11,b="11ddddd";
则 select 11="11ddddd"相等
若绝对比较可以这样:
select binary 11 =binary "11ddddd"
1.4 字符集转换 : CONVERT(xxx USING gb2312)
类型转换和SQL Server一样,就是类型参数有点点不同 : CAST(xxx AS 类型) , CONVERT(xxx,类型),类型必须用下列的类型:
可用的类型: 
二进制,同带binary前缀的效果 : BINARY
字符型,可带参数 : CHAR()
日期 : DATE
时间: TIME
日期时间型 : DATETIME
浮点数 : DECIMAL
整数 : SIGNED
无符号整数 : UNSIGNED
2、 group_concat()函数的排序方法
SELECT username,GROUP_CONCAT(score ORDER BY score DESC) AS myScore FROM tt GROUP BY username;
数据库
2019-04-29 18:28:02
「深度学习福利」大神带你进阶工程师,立即查看>>>
在11g中,全表扫描可能使用direct path read方式,绕过buffer cache,这样的全表扫描就是物理读了。 在10g中,都是通过gc buffer来读的,所以不存在direct path read的问题。
direct path read较高的可能原因有:
1. 大量的磁盘排序操作,order by, group by, union, distinct , rollup, 无法在PGA中完成排序,需要利用temp表空间进行排序。 当从临时表空间中读取排序结果时,会产生direct path read.
2. 大量的 Hash Join操作,利用temp表空间保存 hash 区。
3. SQL语句的并行处理
4. 大表的全表扫描,在中,全表扫描的算法有新的变化,根据表的大小、高速缓存的大小等信息,决定是否绕过SGA直接从磁盘读 Oracle 11g取数据。而10g则是全部通过高速缓存读取数据,称为table scan(large)。11g认为大表全表时使用直接路径读,可能比10g中的数据文件散列读(db file scattered reads)速度更快,使用的latch也更少。
大量的direct path read等待时间最可能是一个应用程序问题。 direct path read事件由SQL语句驱动,这些SQL语句执行来自临时的或常规的表空间的直接读取操作。 当输入的内容大于PGA中的工作区域时,带有需要排序的函数的SQL语句将排序结果写入到临时表空间中,临时表空间中的排序顺序串随后被合并,用于提供最终的结果。读取排序结果时, Oracle 会话在direct path read等待事件上等待。DB_FILE_DIRECT_IO_COUNT初始化参数可能影响direct path read的性能。

数据库
2019-04-29 17:11:00
「深度学习福利」大神带你进阶工程师,立即查看>>>
Oracle中的AWR,全称为Automatic Workload Repository,自动负载信息库。它收集关于特定数据库的操作统计信息和其他统计信息,Oracle以固定的时间间隔(默认为1个小时)为其所有重要的统计信息和负载信息执行一次快照,并将快照存放入AWR中。这些信息在AWR中保留指定的时间(默认为1周),然后执行删除。执行快照的频率和保持时间都是可以自定义的。
AWR的引入,为我们分析数据库提供了非常好的便利条件(这方面MySQL就相差了太多)。曾经有这样的一个比喻——“一个系统,就像是一个黑暗的大房间,系统收集的统计信息,就如同放置在房间不同位置的蜡烛,用于照亮这个黑暗大房间。Oracle,恰到好处地放置了足够的蜡烛(AWR),房间中只有极少的烛光未覆盖之处,性能瓶颈就容易定位。而对于蜡烛较少或是没有蜡烛的系统,性能优化就如同黑暗中的舞者。”
那如何解读AWR的数据呢?Oracle本身提供了一些报告,方便进行查看、分析。下面就针对最为常见的一种报告——《AWR数据库报告》进行说明。希望通过这篇文章,能方便大家更好地利用AWR,方便进行分析工作。
一、MAIN
1、Database Information
2、Snapshot Information
(1)Sessions
表示采集实例连接的会话数。这个数可以帮助我们了解数据库的并发用户数大概的情况。这个数值对于我们判断数据库的类型有帮助。
(2)Cursors/session
每个会话平均打开的游标数。
(3)Elapsed
通过Elapsed/DB Time比较,反映出数据库的繁忙程度。如果DB Time>>Elapsed,则说明数据库很忙。
(4)DB Time
表示用户操作花费的时间,包括CPU时间和等待事件。通常同时这个数值判读数据库的负载情况。
具体含义
db time = cpu time + wait time(不包含空闲等待)(非后台进程)
*db time就是记录的服务器花在数据库运算(非后台进程)和等待(非空闲等待)上的时间。对应于V$SESSION的elapsed_time字段累积。
"合集数据"
需要注意的是AWR是一个数据合集。比如在1分钟之内,1个用户等待了30秒钟,那么10个用户等待事件就是300秒。CPU时间也是一样,在1分钟之内,1个CPU处理30秒钟,那么4个CPU就是120秒。这些时间都是以累积的方式记录在AWR当中的。
示例
DB CPU——这是一个用于衡量CPU的使用率的重要指标。假设系统有N个CPU,那么如果CPU全忙的话,一秒钟内的DB CPU就是N秒。除了利用CPU进行计算外,数据库还会利用其它计算资源,如网络、硬盘、内存等等,这些对资源的利用同样可以利用时间进行度量。假设系统有M个session在运行,同一时刻有的session可能在利用CPU,有的session可能在访问硬盘,那么在一秒钟内,所有session的时间加起来就可以表征系统在这一秒内的繁忙程度。一般的,这个和的最大值应该为M。这其实就是Oracle提供的另一个重要指标:DB time,它用以衡量前端进程所消耗的总时间。
对除CPU以后的计算资源的访问,Oracle用等待事件进行描述。同样地,和CPU可分为前台消耗CPU和后台消耗CPU一样,等待事件也可以分为前台等待事件和后台等待事件。DB Time一般的应该等于"DB CPU + 前台等待事件所消耗时间"的总和。等待时间通过v$system_event视图进行统计,DB Time和DB CPU则是通过同一个视图,即v$sys_time_model进行统计。 --"Load Profile"中关于DB Time的描述
*这个系统的CPU个数是8,因此我们可以知道前台进程用了系统CPU的7.1/8=88.75%。DB Time/s为11.7,可以看出这个系统是CPU非常繁忙的。里面CPU占了7.1,则其它前台等待事件占了11.7 – 7.1 = 4.6 Wait Time/s。DB Time 占 DB CPU的比重: 7.1/11.7= 60.68% --"Top 5 Timed Events"中关于DB CPU的描述
按照CPU/等待事件占DB Time的比例大小,这里列出了Top 5。如果一个工作负载是CPU繁忙型的话,那么在这里应该可以看到 DB CPU的影子。
*注意到,我们刚刚已经算出了DB CPU 的%DB time,60%。其它的external table read、direct path write、PX Deq: read credit、PX Deq: Slave Session Stats这些就是占比重40的等待事件里的Top 4了。 --"Top 5 Timed Foreground Events"的局限性
再研究下这个Top 5 Timed Foreground Events,如果先不看Load Profile,是不能计算出一个CPU-Bound的工作负载。要知道系统CPU的繁忙程序,还要知道这个AWR所基于两个snapshot的时间间隔,还要知道系统CPU的个数。要不系统可以是一个很IDLE的系统呢。记住CPU利用率 = DB CPU/(CPU_COUNT*Elapsed TIME)。这个Top 5 给我们的信息只是这个工作负载应该是并行查询,从外部表读取数据,并用insert append的方式写入磁盘,同时,主要时间耗费在CPU的运算上。 --解读"DB Time" > "DB CPU" + "前台等待事件所消耗时间" ——进程排队时间
上面提到,DB Time一般的应该等于DB CPU + 前台等待事件所消耗时间的总和。在下面有对这三个值的统计:
DB CPU = 6474.65
DB TIME = 10711.2
FG Wait Time = 1182.63
明显的,DB CPU + FG Wait Time < DB Time,只占了71.5%
*其它的28.5%被消耗到哪里去了呢?这里其实又隐含着一个Oracle如何计算DB CPU和DB Time的问题。当CPU很忙时,如果系统里存在着很多进程,就会发生进程排队等待CPU的现象。在这样,DB TIME是把进程排队等待CPU的时间算在内的,而DB CPU是不包括这一部分时间。这是造成 DB CPU + FG Wait Time < DB Time的一个重要原因。如果一个系统CPU不忙,这这两者应该就比较接近了。不要忘了在这个例子中,这是一个CPU非常繁忙的系统,而71.5%就是一个信号,它提示着这个系统可能是一个CPU-Bound的系统。
二、Report Summary
1、Cache Sizes
这部分列出AWR在性能采集开始和结束的时候,数据缓冲池(buffer cache)和共享池(shared pool)的大小。通过对比前后的变化,可以了解系统内存消耗的变化。
2、Load Profile
这两部分是数据库资源负载的一个明细列表,分隔成每秒钟的资源负载和每个事务的资源负载。 Redo size
每秒(每个事务)产生的日志大小(单位字节) Logical reads
每秒(每个事务)产生的逻辑读(单位是block)。在很多系统里select执行次数要远远大于transaction次数。这种情况下,可以参考Logical reads/Executes。在良好的oltp环境下,这个应该不会超过50,一般只有10左右。如果这个值很大,说明有些语句需要优化。 Block Changes
每秒(每个事务)改变的数据块数。 Physical reads
每秒(每个事务)产生的物理读(单位是block)。一般物理读都会伴随逻辑读,除非直接读取这种方式,不经过cache。 Physical writes
每秒(每个事务)产生的物理写(单位是block)。 User calls
每秒(每个事务)用户调用次数。User calls/Executes基本上代表了每个语句的请求次数,Executes越接近User calls越好。 Parses
每秒(每个事务)产生的解析(或分析)的次数,包括软解析和硬解析,但是不包括快速软解析。软解析每秒超过300次意味着你的"应用程序"效率不高,没有使用soft soft parse,调整session_cursor_cache。 Hard parses
每秒(每个事务)产生的硬解析次数。每秒超过100次,就可能说明你绑定使用的不好。 Sorts
每秒(每个事务)排序次数。 Logons
每秒(每个事务)登录数据库次数。 Executes
每秒(每个事务)SQL语句执行次数。包括了用户执行的SQL语句与系统执行的SQL语句,表示一个系统SQL语句的繁忙程度。 Transactions
每秒的事务数。表示一个系统的事务繁忙程度。目前已知的最繁忙的系统为淘宝的在线交易系统,这个值达到了1000。 % Blocks changed per Read
表示逻辑读用于只读而不是修改的块的比例。如果有很多PLSQL,那么就会比较高。 Rollback per transaction %
看回滚率是不是很高,因为回滚很耗资源。 Recursive Call %
递归调用SQL的比例,在PL/SQL上执行的SQL称为递归的SQL。
3、Instance Efficiency Percentages (Target 100%)
这个部分是内存效率的统计信息。对于OLTP系统而言,这些值都应该尽可能地接近100%。对于OLAP系统而言,意义不太大。因为在OLAP系统中,大查询的速度才是对性能影响的最大因素。 Buffer Nowait %
非等待方式获取数据块的百分比。
这个值偏小,说明发生SQL访问数据块时数据块正在被别的会话读入内存,需要等待这个操作完成。发生这样的事情通常就是某些数据块变成了热块。
Buffer Nowait<99%说明,有可能是有热块(查找x$bh的 tch和v$latch_children的cache buffers chains)。 Redo NoWait %
非等待方式获取redo数据百分比。 Buffer Hit %
数据缓冲命中率,表示了数据块在数据缓冲区中的命中率。
Buffer Hit<95%,可能是要加db_cache_size,但是大量的非选择的索引也会造成该值很高(大量的db file sequential read)。 In-memory Sort %
数据块在内存中排序的百分比。总排序中包括内存排序和磁盘排序。当内存中排序空间不足时,使用临时表空间进行排序,这个是内存排序对总排序的百分比。
过低说明有大量排序在临时表空间进行。在oltp环境下,最好是100%。如果太小,可以调整PGA参数。 Library Hit %
共享池中SQL解析的命中率。
Library Hit<95%,要考虑加大共享池,绑定变量,修改cursor_sharing等。 Soft Parse %
软解析占总解析数的百分比。可以近似当作sql在共享区的命中率。
这个数值偏低,说明系统中有些SQL没有重用,最优可能的原因就是没有使用绑定变量。
<95%:需要考虑到绑定
<80%:那么就可能sql基本没有被重用 Execute to Parse %
执行次数对分析次数的百分比。
如果该值偏小,说明解析(硬解析和软解析)的比例过大,快速软解析比例小。根据实际情况,可以适当调整参数session_cursor_cache,以提高会话中sql执行的命中率。 round(100*(1-:prse/:exe),2) 即(Execute次数 - Parse次数)/Execute次数 x 100% prse = select value from v$sysstat where name = 'parse count (total)'; exe = select value from v$sysstat where name = 'execute count';
没绑定的话导致不能重用也是一个原因,当然sharedpool太小也有可能,单纯的加session_cached_cursors也不是根治的办法,不同的sql还是不能重用,还要解析。即使是soft parse 也会被统计入 parse count,所以这个指标并不能反应出fast soft(pga 中)/soft (shared pool中)/hard (shared pool 中新解析) 几种解析的比例。只有在pl/sql的类似循环这种程序中使用使用变量才能避免大量parse,所以这个指标跟是否使用bind并没有必然联系增加session_cached_cursors 是为了在大量parse的情况下把soft转化为fast soft而节约资源。 Latch Hit %
latch的命中率。
其值低是因为shared_pool_size过大或没有使用绑定变量导致硬解析过多。要确保>99%,否则存在严重的性能问题,比如绑定等会影响该参数。 Parse CPU to Parse Elapsd %
解析总时间中消耗CPU的时间百分比。即:100*(parse time cpu / parse time elapsed)
解析实际运行事件/(解析实际运行时间+解析中等待资源时间),越高越好。 % Non-Parse CPU
CPU非分析时间在整个CPU时间的百分比。
100*(parse time cpu / parse time elapsed)= Parse CPU to Parse Elapsd %
查询实际运行时间/(查询实际运行时间+sql解析时间),太低表示解析消耗时间过多。
4、Shared Pool Statistics
Memory Usage %
共享池内存使用率。
应该稳定在70%-90%间,太小浪费内存,太大则内存不足。 % SQL with executions>1
执行次数大于1的SQL比率。
若太小可能是没有使用绑定变量。 % Memory for SQL w/exec>1
执行次数大于1的SQL消耗内存/所有SQL消耗的内存(即memory for sql with execution > 1)。
5、Top 5 Timed Events
三、RAC Statistics
这一部分只在有RAC环境下才会出现,是一些全局内存中数据发送、接收方面的性能指标,还有一些全局锁的信息。除非这个数据库在运行正常是设定了一个基线作为参照,否则很难从这部分数据中直接看出性能问题。
经验
Oracle公司经验,下面GCS和GES各项指标中,凡是与时间相关的指标,只要GCS指标低于10ms,GES指标低于15ms,则一般表示节点间通讯效率正常。但是,即便时间指标正常,也不表示应用本身或应用在RAC部署中没有问题。
1、Global Cache Load Profile
2、Global Cache Efficiency Percentages (Target local+remote 100%)
3、Global Cache and Enqueue Services - Workload Characteristics
4、Global Cache and Enqueue Services - Messaging Statistics
5、Global Cache Transfer Stats
*如果CR的%Busy很大,说明节点间存在大量的块争用。
四、Wait Events Statistics
1、Time Model Statistics
这部分信息列出了各种操作占用的数据库时间比例。 parse time elapsed/hard parse elapsed time
通过这两个指标的对比,可以看出硬解析占整个的比例。如果很高,就说明存在大量硬解析。 % Not-Parse CPU
花费在非解析上CPU消耗占整个CPU消耗的比例。反之,则可以看出解析占用情况。如果很高,也可以反映出解析过多(进一步可以看看是否是硬解析过多)。
示例 - 计算CPU消耗
Total DB CPU = DB CPU + background cpu time = 1305.89 + 35.91 = 1341.8 seconds
再除以总的 BUSY_TIME + IDLE_TIME
% Total CPU = 1341.8/1941.76 = 69.1%,这刚好与上面Report的值相吻合。
其实,在Load Profile部分,我们也可以看出DB对系统CPU的资源利用情况。
用DB CPU per Second除以CPU Count就可以得到DB在前台所消耗的CPU%了。
这里 5.3/8 = 66.25 %
比69.1%稍小,说明DB在后台也消耗了大约3%的CPU。
2、Wait Class
这一部分是等待的类型。可以看出那类等待占用的时间最长。
3、Wait Events
这一部分是整个实例等待事件的明细,它包含了TOP 5等待事件的信息。
%Time-outs: 超时百分比(超时依据不太清楚?)
4、Background Wait Events
这一部分是实例后台进程的等待事件。如果我们怀疑那个后台进程(比如DBWR)无法及时响应,可以在这里确认一下是否有后台进程等待时间过长的事件存在。
5、Operating System Statistics
(1)背景知识
如果关注数据库的性能,那么当拿到一份AWR报告的时候,最想知道的第一件事情可能就是系统资源的利用情况了,而首当其冲的,就是CPU。而细分起来,CPU可能指的是: OS级的User%, Sys%, Idle% DB所占OS CPU资源的Busy% DB CPU又可以分为前台所消耗的CPU和后台所消耗的CPU
(2)11g
如果数据库的版本是11g,那么很幸运的,这些信息在AWR报告中一目了然:
OS级的%User为75.4,%Sys为2.8,%Idle为21.2,所以%Busy应该是78.8。
DB占了OS CPU资源的69.1,%Busy CPU则可以通过上面的数据得到:%Busy CPU = %Total CPU/(%Busy) * 100 = 69.1/78.8 * 100 = 87.69,和报告的87.7相吻合。
(3)10g
如果是10g,则需要手工对Report里的一些数据进行计算了。Host CPU的结果来源于DBA_HIST_OSSTAT,AWR报告里已经帮忙整出了这段时间内的绝对数据(这里的时间单位是厘秒-也就是1/100秒)。
解读输出 %User = USER_TIME/(BUSY_TIME+IDLE_TIME)*100 = 146355/(152946+41230)*100 = 75.37 %Sys = SYS_TIME/(BUSY_TIME+IDLE_TIME)*100 %Idle = IDLE_TIME/(BUSY_TIME+IDLE_TIME)*100
ELAPSED_TIME
这里已经隐含着这个AWR报告所捕捉的两个snapshot之间的时间长短了。有下面的公式。正确的理解这个公式可以对系统CPU资源的使用及其度量的方式有更深一步的理解。 BUSY_TIME + IDLE_TIME = ELAPSED_TIME * CPU_COUNT
推算出:ELAPSED_TIME = (152946+41230)/8/100 = 242.72 seconds //这是正确的。
时间统计视图v$sys_time_model
至于DB对CPU的利用情况,这就涉及到10g新引入的一个关于时间统计的视图 - v$sys_time_model。简单而言,Oracle采用了一个统一的时间模型对一些重要的时间指标进行了记录,具体而言,这些指标包括: 1) background elapsed time 2) background cpu time 3) RMAN cpu time (backup/restore) 1) DB time 2) DB CPU 2) connection management call elapsed time 2) sequence load elapsed time 2) sql execute elapsed time 2) parse time elapsed 3) hard parse elapsed time 4) hard parse (sharing criteria) elapsed time 5) hard parse (bind mismatch) elapsed time 3) failed parse elapsed time 4) failed parse (out of shared memory) elapsed time 2) PL/SQL execution elapsed time 2) inbound PL/SQL rpc elapsed time 2) PL/SQL compilation elapsed time 2) Java execution elapsed time 2) repeated bind elapsed time
我们这里关注的只有和CPU相关的两个: background cpu time 和 DB CPU。这两个值在AWR里面也有记录。
五、SQL Statistics
1、SQL ordered by Elapsed Time
这一部分是按照SQL执行时间从长到短的排序。 Elapsed Time(S)
SQL语句执行用总时长,此排序就是按照这个字段进行的。注意该时间不是单个SQL跑的时间,而是监控范围内SQL执行次数的总和时间。单位时间为秒。Elapsed Time = CPU Time + Wait Time CPU Time(s)
为SQL语句执行时CPU占用时间总时长,此时间会小于等于Elapsed Time时间。单位时间为秒。 Executions
SQL语句在监控范围内的执行次数总计。如果Executions=0,则说明语句没有正常完成,被中间停止,需要关注。 Elap per Exec(s)
执行一次SQL的平均时间。单位时间为秒。 % Total DB Time
为SQL的Elapsed Time时间占数据库总时间的百分比。 SQL ID
SQL语句的ID编号,点击之后就能导航到下边的SQL详细列表中,点击IE的返回可以回到当前SQL ID的地方。 SQL Module
显示该SQL是用什么方式连接到数据库执行的,如果是用SQL*Plus或者PL/SQL链接上来的那基本上都是有人在调试程序。一般用前台应用链接过来执行的sql该位置为空。 SQL Text
简单的SQL提示,详细的需要点击SQL ID。
分析说明
如果看到SQL语句执行时间很长,而CPU时间很少,则说明SQL在I/O操作时(包括逻辑I/O和物理I/O)消耗较多。可以结合前面I/O方面的报告以及相关等待事件,进一步分析是否是I/O存在问题。当然SQL的等待时间主要发生在I/O操作方面,不能说明系统就存在I/O瓶颈,只能说SQL有大量的I/O操作。
如果SQL语句执行次数很多,需要关注一些对应表的记录变化。如果变化不大,需要从前面考虑是否大多数操作都进行了Rollback,导致大量的无用功。
2、SQL ordered by CPU Time
记录了执行占CPU时间总和时间最长的TOP SQL(请注意是监控范围内该SQL的执行占CPU时间总和,而不是单次SQL执行时间)。这部分是SQL消耗的CPU时间从高到底的排序。 CPU Time (s)
SQL消耗的CPU时间。 Elapsed Time (s)
SQL执行时间。 Executions
SQL执行次数。 CPU per Exec (s)
每次执行消耗CPU时间。 % Total DB Time
SQL执行时间占总共DB time的百分比。
3、SQL ordered by Gets
这部分列出SQL获取的内存数据块的数量,按照由大到小的顺序排序。buffer get其实就是逻辑读或一致性读。在sql 10046里面,也叫query read。表示一个语句在执行期间的逻辑IO,单位是块。在报告中,该数值是一个累计值。Buffer Get=执行次数 * 每次的buffer get。记录了执行占总buffer gets(逻辑IO)的TOP SQL(请注意是监控范围内该SQL的执行占Gets总和,而不是单次SQL执行所占的Gets)。 Buffer Gets
SQL执行获得的内存数据块数量。 Executions
SQL执行次数。 Gets per Exec
每次执行获得的内存数据块数量。 %Total
占总数的百分比。 CPU Time (s)
消耗的CPU时间。 Elapsed Time (s)
SQL执行时间。
筛选SQL的标准
因为statspack/awr列出的是总体的top buffer,它们关心的是总体的性能指标,而不是把重心放在只执行一次的语句上。为了防止过大,采用了以下原则。如果有sql没有使用绑定变量,执行非常差但是由于没有绑定,因此系统人为是不同的sql。有可能不会被列入到这个列表中。
大于阀值buffer_gets_th的数值,这是sql执行缓冲区获取的数量(默认10000)。
小于define top_n_sql=65的数值。
4、SQL ordered by Reads
这部分列出了SQL执行物理读的信息,按照从高到低的顺序排序。记录了执行占总磁盘物理读(物理IO)的TOP SQL(请注意是监控范围内该SQL的执行占磁盘物理读总和,而不是单次SQL执行所占的磁盘物理读)。 Physical Reads
SQL物理读的次数。 Executions
SQL执行次数。 Reads per Exec
SQL每次执行产生的物理读。 %Total
占整个物理读的百分比。 CPU Time (s)
SQL执行消耗的CPU时间。 Elapsed Time (s)
SQL的执行时间。
5、SQL ordered by Executions
这部分列出了SQL执行次数的信息,按照从大到小的顺序排列。如果是OLTP系统的话,这部分比较有用。因此SQL执行频率非常大,SQL的执行次数会对性能有比较大的影响。OLAP系统因为SQL重复执行的频率很低,因此意义不大。 Executions
SQL的执行次数。 Rows Processed
SQL处理的记录数。 Rows per Exec
SQL每次执行处理的记录数。 CPU per Exec (s)
每次执行消耗的CPU时间。 Elap per Exec (s)
每次执行的时长。
6、SQL ordered by Parse Calls
这部分列出了SQL按分析次(软解析)数的信息,按照从高到底的顺序排列。这部分对OLTP系统比较重要,这里列出的总分析次数并没有区分是硬分析还是软分析。但是即使是软分析,次数多了,也是需要关注的。这样会消耗很多内存资源,引起latch的等待,降低系统的性能。软分析过多需要检查应用上是否有频繁的游标打开、关闭操作。 Parse Calls
SQL分析的次数。 Executions
SQL执行的次数。 % Total Parses
占整个分析次数的百分比。
7、SQL ordered by Sharable Memory
记录了SQL占用library cache的大小的TOP SQL。 Sharable Mem (b)
占用library cache的大小。单位是byte。
8、SQL ordered by Version Count
这部分列出了SQL多版本的信息。记录了SQL的打开子游标的TOP SQL。一个SQL产生多版本的原因有很多,可以查询视图v$sql_sahred_cursor视图了解具体原因。对于OLTP系统,这部分值得关注,了解SQL被重用的情况。 Version Count
SQL的版本数。 Executions
SQL的执行次数。
9、SQL ordered by Cluster Wait Time
记录了集群的等待时间的TOP SQL。这部分只在RAC环境中存在,列出了实例之间共享内存数据时发生的等待。在RAC环境下,几个实例之间需要有一种锁的机制来保证数据块版本的一致性,这就出现了一类新的等待事件,发生在RAC实例之间的数据访问等待。对于RAC结构,还是采用业务分隔方式较好。这样某个业务固定使用某个实例,它访问的内存块就会固定地存在某个实例的内存中,这样降低了实例之间的GC等待事件。此外,如果RAC结构采用负载均衡模式,这样每个实例都会被各种应用的会话连接,大量的数据块需要在各个实例的内存中被拷贝和锁定,会加剧GC等待事件。 Cluster Wait Time (s)
集群等待时长。 CWT % of Elapsd Time
集群操作等待时长占总时长的百分比。 Elapsed Time(s)
SQL执行总时长。
10、Complete List of SQL Text
这部分是上面各部分涉及的SQL的完整文本。
六、Instance Activity Statistics
1、Instance Activity Stats
这部分是实例的信息统计,项目非常多。对于RAC架构的数据库,需要分析每个实例的AWR报告,才能对整体性能做出客观的评价。 CPU used by this session
这个指标用来上面在当前的性能采集区间里面,Oracle消耗的CPU单位。一个CPU单位是1/100秒。从这个指标可以看出CPU的负载情况。
案例 - 分析系统CPU繁忙程度
在TOP5等待事件里,找到"CPU time",可以看到系统消耗CPU的时间为26469秒。
在实例统计部分,可以看到整个过程消耗了1813626个CPU单位。每秒钟消耗21个CPU单位,对应实际的时间就是0.21秒。也就是每秒钟CPU的处理时间为0.21秒。
系统内CPU个数为8。每秒钟每个CPU消耗是21/8=2.6(个CPU单位)。在一秒钟内,每个CPU处理时间就是2.6/100=0.026秒。
*总体来看,当前数据库每秒钟每个CPU处理时间才0.026秒,远远算不上高负荷。数据库CPU资源很丰富,远没有出现瓶颈。
七、IO Stats
1、Tablespace IO Stats
表空间的I/O性能统计。 Reads
发生了多少次物理读。 Av Reads/s
每秒钟物理读的次数。 Av Rd(ms)
平均一次物理读的时间(毫秒)。一个高相应的磁盘的响应时间应当在10ms以内,最好不要超过20ms;如果达到了100ms,应用基本就开始出现严重问题甚至不能正常运行。 Av Blks/Rd
每次读多少个数据块。 Writes
发生了多少次写。 Av Writes/s
每秒钟写的次数。 Buffer Waits
获取内存数据块等待的次数。 Av Buf Wt(ms)
获取内存数据块平均等待时间。
2、File IO Stats
文件级别的I/O统计。
八、Advisory Statistics
顾问信息。这块提供了多种顾问程序,提出在不同情况下的模拟情况。包括databuffer、pga、shared pool、sga、stream pool、java pool等的情况。
1、Buffer Pool Advisory
Buffer pool的大小建议。 Size for Est (M)
Oracle估算Buffer pool的大小。 Size Factor
估算值与实际值的比例。如果0.9就表示估算值是实际值的0.9倍。1.0表示buffer pool的实际大小。 Buffers for Estimate
估算的Buffer的大小(数量)。 Est Phys Read Factor
估算的物理读的影响因子,是估算物理读和实际物理读的一个比例。1.0表示实际的物理读。 Estimated Physical Reads
估计的物理读次数。
2、PGA Memory Advisory
PGA的大小建议。 PGA Target Est (MB)
PGA的估算大小。 Size Factr
影响因子,作用和buffer pool advisory中相同。 W/A MB Processed
Oracle为了产生影响估算处理的数据量。 Estd Extra W/A MB Read/ Written to Disk
处理数据中需要物理读写的数据量。 Estd PGA Cache Hit %
估算的PGA命中率。 Estd PGA Overalloc Count
需要在估算的PGA大小下额外分配内存的个数。
3、Shared Pool Advisory
建议器通过设置不同的共享池大小,来获取相应的性能指标值。 Shared Pool Size(M)
估算的共享池大小。 SP Size Factr
共享池大小的影响因子。 Est LC Size (M)
估算的库高速缓存占用的大小。 Est LC Mem Obj
高速缓存中的对象数。 Est LC Time Saved (s)
需要额外将对象读入共享池的时间。 Est LC Time Saved Factr
对象读入共享池时间的影响因子。
表示每一个模拟的shared pool大小对重新将对象读入共享池的影响情况。当这个值的变化很小或不变的时候,增加shared pool的大小就意义不大。 Est LC Load Time (s)
分析所花费的时间。 Est LC Load Time Factr
分析花费时间的影响因子。 Est LC Mem Obj Hits
内存中对象被发现的次数。
4、SGA Target Advisory
建议器对SGA整体的性能的一个建议。 SGA Target Size (M)
估算的SGA大小。 SGA Size Factor
SGA大小的影响因子。 Est DB Time (s)
估算的SGA大小计算出的DB Time。 Est Physical Reads
物理读的次数。
九、Latch Statistics
1、Latch Activity
Get Requests/Pct Get Miss/Avg Slps /Miss
表示愿意等待类型的latch的统计信息。 NoWait Requests/Pct NoWait Miss
表示不愿意等待类型的latch的统计信息。 Pct Misses
比例最好接近0。
十、Segment Statistics
1、Segments by Logical Reads
段的逻辑读情况。
2、 Segments by Physical Reads
段的物理读情况。
3、Segments by Buffer Busy Waits
从这部分可以发现那些对象访问频繁。Buffer Busy Waits事件通常由于某些数据块太过频繁的访问,导致热点块的产生。
4、Segments by Row Lock Waits
AWR报告Segment Statistics部分的Segments by Row Lock Waits,非常容易引起误解,它包含的不仅仅是事务的行级锁等待,还包括了索引分裂的等待。之前我一直抱怨为什么v$segment_statistics中没有统计段级别的索引分裂计数,原来ORACLE已经实现了。但是统计进这个指标中,你觉得合适吗?
十一、其他问题
SQL运行周期对报告的影响
对SQL语句来讲,只有当它执行完毕之后,它的相关信息才会被Oracle所记录(比如:CPU时间、SQL执行时长等)。当时当某个SQL终止于做AWR报告选取的2个快照间隔时间之后,那么它的信息就不能被这个AWR报告反映出来。尽管它在采样周期里面的运行,也消耗了很多资源。
也就是说某个区间的性能报告并不能精确地反映出在这个采样周期中资源的消耗情况。因为有些在这个区间运行的SQL可能结束于这个时间周期之后,也可能有一些SQL在这个周期开始之前就已经运行了很久,恰好结束于这个采样周期。这些因素都导致了采样周期里面的数据并不绝对是这个时间段发生的所有数据库操作的资源使用的数据。
作者:韩锋 来源: 宜信技术学院
数据库
2019-04-29 14:59:00
「深度学习福利」大神带你进阶工程师,立即查看>>> STR_TIME_FORMAT = "2006-01-02 15:04:05" cstZone := time.FixedZone("CST", 8*3600) sTime, _ := time.ParseInLocation(dao.STR_TIME_FORMAT, startTime, cstZone) eTime, _ := time.ParseInLocation(dao.STR_TIME_FORMAT, endTime, cstZone)
数据库
2019-04-29 14:47:00
「深度学习福利」大神带你进阶工程师,立即查看>>>
最近要对数据库的数据进行一个定时迁移,为了防止在执行过程sql语句因为某些原因报错而导致数据转移混乱,因此要对我们的脚本加以事务进行控制。
首先我们建一张tran_test表 CREATE TABLE tran_test( f1 VARCHAR(10) NOT NULL, f2 INT(1) DEFAULT NULL, PRIMARY KEY (f1) )ENGINE=INNODB CHARSET=utf8
我想对tran_test插入两条数据,但是为了防止插入中报错,因此我要把插入语句控制在一个事务内。
这时候,如果你查一下有些人的文章,许多时候会给出你这么一条答案。 START TRANSACTION; INSERT INTO tran_test VALUES('A',1); INSERT INTO tran_test VALUES('B',2); ROLLBACK;
或 START TRANSACTION; INSERT INTO tran_test VALUES('A',1); INSERT INTO tran_test VALUES('B',2); COMMIT;
看上去很简单的sql语句,并且这两句也确实能实现提交或回滚。

然而这真的能达到我们的目的吗?答案是否定的。
比如第一段,它是将你在事务中的sql语句无论对错全部进行ROLLBACK。这样绝对的回滚使得你的sql没有任何意义了。

因此我们想要真正的控制好事务,我的思路是对要执行的sql进行异常检测。如果sql没有出现异常,COMMIT,如果捕获到了异常,则ROLLBACK。
这时候,我们就需要建一个存储过程来捕获异常。执行成功时进行COMMIT,sql执行失败时则进行ROLLBACK。
两种思路可以达到我想要的效果。

第一种是对我们要执行的sql进行异常捕获,我们再定义一个变量t_error,当捕获到异常的时候,让t_error=1。再对t_error进行条件判断,如果t_error=1则进行ROLLBACK,否则进行COMMIT。 DROP PROCEDURE IF EXISTS t_test; DELIMITER // CREATE PROCEDURE t_test() BEGIN DECLARE t_error INTEGER; DECLARE CONTINUE HANDLER FOR SQLEXCEPTION SET t_error = 1; START TRANSACTION; INSERT INTO tran_test VALUES('A',1); INSERT INTO tran_test VALUES('B',2); IF t_error = 1 THEN ROLLBACK; ELSE COMMIT; END IF; END// CALL t_test();
另一只则是第一种的简化,即捕获到异常直接进行ROLLBACK,如果没捕获到异常,直接COMMIT DROP PROCEDURE IF EXISTS t_test; DELIMITER // CREATE PROCEDURE t_test() BEGIN DECLARE EXIT HANDLER FOR SQLEXCEPTION ROLLBACK; START TRANSACTION; INSERT INTO tran_test VALUES('A',1); INSERT INTO tran_test VALUES('B',2); COMMIT; END// CALL t_test()
这样,这两个insert语句便真正的被控制在了一个事务内了。
数据库
2019-04-29 14:21:00
「深度学习福利」大神带你进阶工程师,立即查看>>>
MSSQL导入数据迁移时遇到的几个问题 Posted on 2017-11-15 10:35 hddk 阅读(775) 评论(0) 编辑 收藏
1.当sql server出现当 MUST_CHANGE 为 ON (开)时
取消账号强制策略时出现错误
当 MUST_CHANGE 为 ON (开)时,不能将 CHECK_POLICY 和 CHECK_EXPIRATION 选项设为 OFF (关)。 (Microsoft SQL Server,错误: 15128)
这错误引起的原因是因为勾选了那个"强制实施密码策略",于是..我们必须执行如下语句.先更改一次密码.然后再做其他的操作:
1
ALTER LOGIN 登录名 WITH PASSWORD= '新密码' ;
执行完这个以后..赶紧把强制密码去掉..万事OK
2.服务器级的urn筛选器无效:筛选器必须为空,或服务器属性必须等于实际的服务器名称
这个问题是出在本地连接还是远程连接上,这个问题可能是由于修改过服务器名称导致的。你可以尝试在本地的服务器和SQL Server上运行以下指令,看看服务器名称是否一致 ->打开CMD,运行:hostname;打开SQL Server,运行SELECT @ @SERVERNAME AS 'Server Name' 。
如果不一致的话,需要重命名一下计算机或者SQL Server,看一下这篇文档: https://docs.microsoft.com/en-us/sql/database-engine/install-windows/rename-a-computer-that-hosts-a-stand-alone-instance-of-sql-server
如果只是远程连接有问题的话就可能是DNS的问题,可以参考下这篇博客: http://blog.patricknielsen.net/2011/01/sql-server-invalid-urn-filter-on-server.html
1
2
3
4
sp_dropserver ; GO sp_addserver , local; GO
  
1
2
3
4
sp_dropserver ; GO sp_addserver , local; GO
3.批量导入数据时保留标识值 (SQL Server)
在sql server 导入数据的时候,会有一个“启用标示插入”的选项,我不知道有多数据库从业者注意过这个功能,也不知道大家是否都清楚,就我个人而言,很少在意这个按钮。今天在导测试数据的时候,选了这个选项突然间蒙了,经过几番测试。一点小体会。和大家分享。
首先:“启用标示插入”只对标示列(identity定义的列)起作用。所以如果你要使用这个功能,肯定得有标示列了。我当时狂晕的地方在于,我没在数据库创建表,而是直接在导入数据的过程中创建表,默认的创建表的sql语句是不会定义标示列的,所以,你如果偷懒,请在“编辑sql语句”的按钮中把标示列增加上。如果为非标识列,你导入数据的时候,启用和不启用标识列插入。没有任何意义。
其次:如果是标示列,你选择了“启用标示插入” 则标识列不自动生成,而是使用外部文件(如excel,txt等)中的导入列,如果你没有选择“启用标示插入”,则标识列自动生成。
数据库
2019-04-29 11:29:00
「深度学习福利」大神带你进阶工程师,立即查看>>>
1. 问题描述
项目代码中使用typeorm进行mysql的数据操作, 昨天突然部署测试环境发现测试环境数据库的外键都为空了, 导致涉及的整个系统不可用.
2. 问题探究 尝试复现 mysqldump 线上数据, 然后导入测试数据库 运行程序代码, 外键稳定被删除, 然后外键列被删除, 最后外键列被重新创建. 寻找切入口 github issue Foreign key dropped on every sync and Unique Index created : 从该issue得知该问题曾经是去年的一个bug, 已经被修复了, 但是这个issue提供了一个重要线索: 有问题的键都是uuid debug: 尝试在typeorm中的包中打印日志, 日志位置: 期初位置: 所有dropForeignKeys的地方 判定是dropColumnReferencedForeignKeys位置有问题, 依据是复现过程中日志显示调用了dropForeignKeys 判定是updateExistColumns方法问题, 依据是复现过程中日志显示其调用了dropColumnReferencedForeignKeys 判定是MysqlDriver的findChangedColumns方法问题, 即程序在获取数据库表定义和当前程序表定义做对比, 如果有变更就修改 findChangedColumns 详细日志得知: uuid的列在数据库定义的长度是225,而程序解析的是36, 所以认为列定义发生了改变, 进而进行删除重建 查看对应源代码逻辑发现了一个comment, uuid长度225变为36是为了解决: Synchronize issue with UUID (MySQL) 查看上面的issue得知代码变更: commit 理解问题 最初uuid类型的键定义的是225的varcahr长度, 因为有同步bug进行了一次修正,修改器长度为36 在测试环境复现的原因即生成环境数据使用的还是uuid 225长度的键定义, 而最新的包已经升级uuid的键定义为36长度, 所以启动程序时程序判断这个键发生了定义变化, 进而执行了删除重建等操作
3. 解决方案
重新导入数据, 修改导入数据外键列, 使其长度是varchar(36), 操作如下: set foreign_key_checks=0; alter table `order` modify column instanceId varchar(36) null; alter table `order` modify column organizationId varchar(36) null; alter table `order` modify column userId varchar(36) null; set foreign_key_checks=1;
4. 启示 线上环境没有受破坏是因为没有开始同步, 所以表结构修改等危险操作对生产环境屏蔽 数据备份策略: 测试数据库应该有简单的数据库备份策略, 以便及时恢复, 否则也影响开发和测试使用, 生产环境更因该备份
数据库
2019-04-23 15:58:00
「深度学习福利」大神带你进阶工程师,立即查看>>>
本文来自我的知识星球的球友投稿,他在最近的校招中拿到了蚂蚁金服的实习生Offer,整体思路和面试题目由作者——泽林提供,部分答案由Hollis整理自知识星球《Hollis和他的朋友们》中「直面Java」板块。
经历了漫长一个月的等待,终于在前几天通过面试官获悉已被蚂蚁金服录取,这期间的焦虑、痛苦自不必说,知道被录取的那一刻,一整年的阴霾都一扫而空了。
笔者面的是阿里的Java研发工程师岗,面试流程是3轮技术面+1轮hr面。
意外的一面
一面的时候大概是3月12号,面完等了差不多半个月才突然接到二面面试官的电话。一面可能是简历面,所以问题比较简单。
ArrayList和LinkedList区别
ArrayList 是一个可改变大小的数组.当更多的元素加入到ArrayList中时,其大小将会动态地增长.内部的元素可以直接通过get与set方法进行访问,因为ArrayList本质上就是一个数组.
LinkedList 是一个双链表,在添加和删除元素时具有比ArrayList更好的性能.但在get与set方面弱于ArrayList.
当然,这些对比都是指数据量很大或者操作很频繁的情况下的对比,如果数据和运算量很小,那么对比将失去意义.
什么情况会造成内存泄漏
在Java中,内存泄漏就是存在一些被分配的对象,这些对象有下面两个特点:
首先,这些对象是可达的,即在有向图中,存在通路可以与其相连;
其次,这些对象是无用的,即程序以后不会再使用这些对象。
如果对象满足这两个条件,这些对象就可以判定为Java中的内存泄漏,这些对象不会被GC所回收,然而它却占用内存。
什么是线程死锁,如何解决 产生死锁的条件有四个:
1.互斥条件:所谓互斥就是进程在某一时间内独占资源。 2.请求与保持条件:一个进程因请求资源而阻塞时,对已获得的资源保持不放。 3.不剥夺条件:进程已获得资源,在末使用完之前,不能强行剥夺。 4.循环等待条件:若干进程之间形成一种头尾相接的循环等待资源关系。
线程死锁是因为多线程访问共享资源,由于访问的顺序不当所造成的,通常是一个线程锁定了一个资源A,而又想去锁定资源B;在另一个线程中,锁定了资源B,而又想去锁定资源A以完成自身的操作,两个线程都想得到对方的资源,而不愿释放自己的资源,造成两个线程都在等待,而无法执行的情况。
要解决死锁,可以从死锁的四个条件出发,只要破坏了一个必要条件,那么我们的死锁就解决了。在java中使用多线程的时候一定要考虑是否有死锁的问题哦。
红黑树是什么?怎么实现?时间复杂度 红黑树(Red-Black Tree,简称R-B Tree),它一种特殊的二叉查找树。 红黑树是特殊的二叉查找树,意味着它满足二叉查找树的特征:任意一个节点所包含的键值,大于等于左孩子的键值,小于等于右孩子的键值。 除了具备该特性之外,红黑树还包括许多额外的信息。
红黑树的每个节点上都有存储位表示节点的颜色,颜色是红(Red)或黑(Black)。 红黑树的特性: (1) 每个节点或者是黑色,或者是红色。 (2) 根节点是黑色。 (3) 每个叶子节点是黑色。 (4) 如果一个节点是红色的,则它的子节点必须是黑色的。 (5) 从一个节点到该节点的子孙节点的所有路径上包含相同数目的黑节点。
关于它的特性,需要注意的是: 第一,特性(3)中的叶子节点,是只为空(NIL或null)的节点。 第二,特性(5),确保没有一条路径会比其他路径长出俩倍。因而,红黑树是相对是接近平衡的二叉树。

具体实现代码这里不贴了,要实现起来,需要包含的基本操作是添加、删除和旋转。在对红黑树进行添加或删除后,会用到旋转方法。旋转的目的是让树保持红黑树的特性。旋转包括两种:左旋 和 右旋。
红黑树的应用比较广泛,主要是用它来存储有序的数据,它的查找、插入和删除操作的时间复杂度是O(lgn)。
TCP三次握手 三次握手(three times handshake;three-way handshake)所谓的“三次握手”即对每次发送的数据量是怎样跟踪进行协商使数据段的发送和接收同步,根据所接收到的数据量而确定的数据确认数及数据发送、接收完毕后何时撤消联系,并建立虚连接。
为了提供可靠的传送,TCP在发送新的数据之前,以特定的顺序将数据包的序号,并需要这些包传送给目标机之后的确认消息。TCP总是用来发送大批量的数据。当应用程序在收到数据后要做出确认时也要用到TCP。

第一次握手:建立连接时,客户端发送syn包(syn=j)到服务器,并进入SYN_SENT状态,等待服务器确认;SYN:同步序列编号(Synchronize Sequence Numbers)。
第二次握手:服务器收到syn包,必须确认客户的SYN(ack=j+1),同时自己也发送一个SYN包(syn=k),即SYN+ACK包,此时服务器进入SYN_RECV状态;
第三次握手:客户端收到服务器的SYN+ACK包,向服务器发送确认包ACK(ack=k+1),此包发送完毕,客户端和服务器进入ESTABLISHED(TCP连接成功)状态,完成三次握手。
突如其来的二面
一面的时候大概是3月12号,面完等了差不多半个月才突然接到二面面试官的电话。
介绍项目
Storm怎么保证一致性 Storm是一个分布式的流处理系统,利用anchor和ack机制保证所有tuple都被成功处理。如果tuple出错,则可以被重传,但是如何保证出错的tuple只被处理一次呢?Storm提供了一套事务性组件Transaction Topology,用来解决这个问题。
Transactional Topology目前已经不再维护,由Trident来实现事务性topology,但是原理相同。
参考: https://cloud.tencent.com/info/5721fb4532f6a72ed2e563f9449fd025.html
说一下hashmap以及它是否线程安全
HashMap基于哈希表的 Map 接口的实现。HashMap中,null可以作为键,这样的键只有一个;可以有一个或多个键所对应的值为null。HashMap中hash数组的默认大小是16,而且一定是2的指数。Hashtable、HashMap都使用了 Iterator。而由于历史原因,Hashtable还使用了Enumeration的方式 。 HashMap 实现 Iterator,支持fast-fail。
哈希表是由数组+链表组成的,它是通过把key值进行hash来定位对象的,这样可以提供比线性存储更好的性能。

HashMap不是线程安全的。
十亿条淘宝购买记录,怎么获取出现最多的前十个 这是一道典型的有限内存的海量数据处理的题目。一般这类题目的解答无非是以下几种:
分治,hash映射,堆排序,双层桶划分,Bloom Filter,bitmap,数据库索引,mapreduce等。
具体情形都有很多不同的方案。这类题目可以到网上搜索一下,了解下套路,后面就基本都会了。
平时有没有用linux系统,怎么查看某个进程 ps aux|grep java 查看java进程
ps aux 查看所有进程
ps –ef|grep tomcat 查看所有有关tomcat的进程
ps -ef|grep --color java 高亮要查询的关键字
kill -9 19979 终止线程号位19979的进程
说一下Innodb和MySIAM的区别
MyISAM类型不支持事务处理等高级处理,而InnoDB类型支持。MyISAM类型的表强调的是性能,其执行数度比InnoDB类型更快,但是不提供事务支持,而InnoDB提供事务支持以及外部键等高级数据库功能。
InnoDB不支持FULLTEXT类型的索引。
InnoDB 中不保存表的具体行数,也就是说,执行 select count(*) from table 时,InnoDB要扫描一遍整个表来计算有多少行,但是MyISAM只要简单的读出保存好的行数即可。注意的是,当count(*)语句包含 where条件时,两种表的操作是一样的。
对于AUTO_INCREMENT类型的字段,InnoDB中必须包含只有该字段的索引,但是在MyISAM表中,可以和其他字段一起建立联合索引。
DELETE FROM table时,InnoDB不会重新建立表,而是一行一行的删除。
LOAD TABLE FROM MASTER操作对InnoDB是不起作用的,解决方法是首先把InnoDB表改成MyISAM表,导入数据后再改成InnoDB表,但是对于使用的额外的InnoDB特性(例如外键)的表不适用。
说一下jvm内存模型,介绍一下你了解的垃圾收集器 其实并没有jvm内存模型的概念。应该是Java内存模型或者jvm内存结构,这里面试者一定要听清楚问的是哪个,再回答。
可以参考: JVM内存结构 VS Java内存模型 VS Java对象模型
你说你是大数据方向的,了解哪些大数据框架
作者回答了一些zookeeper、storm、HDFS、Hbase等
其他问题
100个有序的整型,如何打乱顺序?
如何设计一个可靠的UDP协议?
二面大概就是这些,其中storm一致性这个问题被面试官怀疑了一下,就有点紧张,其实没答错,所以还是要对知识掌握得更明确才行。
准备充足的三面
清明节的时候例外地没有回家扫墓,因为知道自己的弱项是操作系统和海量数据题这块,所以想着恶补这方面的知识,不过之后的面试意外的并没有问到这方面的内容。
介绍项目
项目介绍完之后没问太多
介绍一下hashmap
HashMap真的是面试高频题,多次面试都问到了,一定要掌握。
介绍一下并发
这里可以把整个并发的体系都说下,包括volatile、synchronized、lock、乐观悲观锁、锁膨胀、锁降级、线程池等
银行账户读写怎么做
我说了读写锁以及可能出现死锁问题
说一下关系型数据库和非关系型数据库的区别 非关系型数据库的优势: 1、性能 NOSQL是基于键值对的,可以想象成表中的主键和值的对应关系,而且不需要经过SQL层的解析,所以性能非常高 2、可扩展性 同样也是因为基于键值对,数据之间没有耦合性,所以非常容易水平扩展。 3、使用场景:日志、埋点、论坛、博客等
关系型数据库的优势: 1、 复杂查询 可以用SQL语句方便的在一个表以及多个表之间做非常复杂的数据查询 2、事务支持 使得对于安全性能很高的数据访问要求得以实现。 3、使用场景:所有有逻辑关系的数据存储
如何访问链表中间节点
对于这个问题,我们首先能够想到的就是先遍历一遍整个的链表,然后计算出链表的长度,进而遍历第二遍找出中间位置的数据。这种方式非常简单。
若题目要求只能遍历一次链表,那又当如何解决问题?
可以采取建立两个指针,一个指针一次遍历两个节点,另一个节点一次遍历一个节点,当快指针遍历到空节点时,慢指针指向的位置为链表的中间位置,这种解决问题的方法称为快慢指针方法。
说下进程间通信,以及各自的区别
进程间通信是指在不同进程之间传播或交换信息。方式通常有管道(包括无名管道和命名管道)、消息队列、信号量、共享存储、Socket、Streams等。
访问淘宝网页的一个具体流程,从获取ip地址,到怎么返回相关内容
先通过DNS解析到服务器地址,然后反向代理、负载均衡服务器等,寻找集群中的一台机器来真正执行你的请求。还可以介绍CDN、页面缓存、Cookie以及session等。
这个过程还包括三次握手、HTTP request中包含哪些内容,状态码等,还有OSI七层分层可以介绍。
服务器接到请求后,会执行业务逻辑,执行过程中可以按照MVC来分别介绍。
服务处理过程中是否调用其他RPC服务或者异步消息,这个过程包含服务发现与注册,消息路由。
最后查询数据库,会不会经过缓存?是不是关系型数据库?是会分库分表还是做哪些操作?
对于数据库,分库分表如果数据量大的话是有必要的,一般业务根据一个分表字段进行取模进行分表,而在做数据库操作的时候,也根据同样的规则,决定数据的读写操作对应哪张表。这种也有开源的实现的,如阿里的TDDL就有这种功能。分库分表还涉及到很多技术,比如sequence如何设置 ,如何解决热点问题等。
最后再把处理结果封装成response,返回给客户端。浏览器再进行页面渲染。
焦虑的hr面
之所以说hr面焦虑,是因为面试前我还在看IG的半决赛(实在复习不下),接到电话的时候分外紧张,在一些点上答得很差。
遇到什么挫折
这种问题主要考察面试者遇见困难是否能坚持下去,并且可以看出他的解决问题的能力。
可以简单描述挫折,并说明自己如何克服,最终有哪些收获。
职业规划 表名自己决心,首先自己不准备继续求学了,必须招工作了。然后说下自己不会短期内换行业,或者换工作,自己比较喜欢,希望可以坚持几年看自己的兴趣再规划之类的。
对阿里的认识 这个比较简答,夸就行了。
有什么崇拜的人吗
我说了詹姆斯哈登,hr小姐姐居然笑了。
这个可以说一些IT大牛。
希望去哪里就业
这个问题果断回答该公司所在的城市啊。
其他问题 有什么兴趣爱好,能拿得上台表演的有吗
记忆深刻的事情
总结
提前批更多的是考察 基础知识 ,大公司都有自己在用的框架,你进去后基本上得重新学这些框架,所以对他们来说,基础是否扎实才是考察的关键。
基础包括:操作系统、linxu、数据库、数据结构、算法、java(基础、容器、高并发、jvm)、计算机网络等
建议要投资知识,从寒假到现在,先后买了9个极客时间的课程、订阅了H神的知识星球、当当买了四五本相关技术书籍...
虽然购买的课很多还来不及读(惭愧)
当时我问一个java群的师兄,学不下了怎么办,他说,换种姿势继续学,还别说,有时候失眠的时候,我都在看极客时间或知识星球催眠自己...
要对知识做好总结,虽然以前也有记录简书的习惯,但是大多数时候都是写了不发表,自己做一个记忆的作用,3月份我给自己的要求就是,对每个知识点要做到能够有自己的理解,然后写一篇质量较好的博客总结。
面试建议是, 一定要自信,敢于表达 ,面试的时候我们对知识的掌握有时候很难面面俱到,把自己的思路说出来,而不是直接告诉面试官自己不懂,这也是可以加分的。
最后
总之,可以拿到蚂蚁金服的offer真的很意外,也很幸运,蚂蚁金服从来是我觉得很难达到的目标,但它确实发生了,也许这就是幸福来敲门吧,我可以给到自己或其他人的建议就是,一定要把握好时机。
Don't ever let somebody tell you you can't do something, not even me. You got a dream, you gotta protect it. People can't do something by themselves,they wanna tell you you can not do it. If You want something. Go get it!
作者:Hollis
原文链接 ​
本文为云栖社区原创内容,未经允许不得转载。
数据库
2019-04-26 12:51:00
「深度学习福利」大神带你进阶工程师,立即查看>>>
记录一下最近用到的es查询,感觉常见的应该都遇上了,下午抽空更新。
又拖,一直拖,终于闲下来了,现在开始写吧。。。
es作为一款基于文档的非关系型数据库,elasticsearch,既然带有search,所以,可以猜到他的检索能力是非常出色的,今天,我想记录一下我最近用的es的查询分析功能, 先说基本查询,然后模糊查询,最后说聚合查询吧。
一,简单查询
1,先看一下java api SearchRequestBuilder searchRequestBuilder = client.prepareSearch(index); RangeQueryBuilder rangequerybuilder = QueryBuilders .rangeQuery("date") .from(startTime).to(endTime); TermQueryBuilder termQueryBuilder = QueryBuilders.termQuery(key, value); TermQueryBuilder termQueryBuilderNot = QueryBuilders.termQuery("status", "0"); searchRequestBuilder = searchRequestBuilder.setQuery(QueryBuilders.boolQuery().must(rangequerybuilder).must(termQueryBuilder).mustNot(termQueryBuilderNot)) .setFrom(0).setSize(10); SearchResponse searchResponse = searchRequestBuilder.execute().actionGet();
上面是java查询es最基本的api。
client.prepareSearch(index);指定index,提示:es6中每个index只能有一个type,所以查询时type存在的意义不大,且es支持查询时不带type
RangeQueryBuilder rangequerybuilder 可以限定范围
TermQueryBuilder termQueryBuilder 可以查询等于某值的结果,拼装searchRequestBuilder时,配合.mustNot(),must()使用
searchRequestBuilder用于最后拼装上面的查询限定条件
.setFrom(0).setSize(0)代表从查询结果的第0个开始显示,共显示10个
.execute().actionGet();这个是真正的向es集群发送请求的语句
了解了基本的查询之后,我们在说一下模糊查询
二,模糊查询
我用到的是正则匹配,比如实际值是 https://my.oschina.net/u/3796880/blog/write/3042734 ,但你想通过 https://my.oschina.net/u/3796880/blog/write 就可以查出来,后面的值模糊匹配,ok,开始 WildcardQueryBuilder wildcardQueryBuilder = QueryBuilders.wildcardQuery(key, value);
key是es中的字段名,value是“ https://my.oschina.net/u/3796880/blog/write *”,道理懂吧。值“* oschina.net/u/3796880/blog *”也可以查询来,总之就是正则嘛
同理前缀查询道理也一样 PrefixQueryBuilder prefixQueryBuilder=QueryBuilders.prefixQuery(key,value)
我暂时没用,想用的同学可自行了解一下
说道这里,我想到自己踩过的一个坑
TermsQueryBuilder termsQueryBuilder = QueryBuilders.termsQuery(key, values);
TermsQueryBuilder 支持查询某字段的多个值,比如查询语言是java,c,c++的数据,可以这么写 String values[]={"java","c","python"}; TermsQueryBuilder termsQueryBuilder = QueryBuilders.termsQuery("language", values);
是不是很方便?但是,有一天,需求变了,language字段不是精准查询,要改为模糊匹配。第一反应是so eazy,把wildcardQuery改为wildcardsQuery不得了,但。。。最后发现es没提供这个方法。。。,最后只能一个一个加了
但怎么加呢,他们之间是或的关系,理所当然这样写 WildcardQueryBuilder wildcardQueryBuilder1 = QueryBuilders.wildcardQuery(key, value1); WildcardQueryBuilder wildcardQueryBuilder2 = QueryBuilders.wildcardQuery(key, value2); WildcardQueryBuilder wildcardQueryBuilder3 = QueryBuilders.wildcardQuery(key, value3);
最后 QueryBuilders.boolQuery().must(其他QueryBuilders).should(wildcardQueryBuilder1).should(wildcardQueryBuilder2).should(wildcardQueryBuilder3)
但,最后发现查不到数据,最后解决办法:需要外面包一层must BoolQueryBuilder queryBuilder= QueryBuilders.boolQuery().should(wildcardQueryBuilder1).should(wildcardQueryBuilder2).should(wildcardQueryBuilder3); QueryBuilders.boolQuery().must(其他QueryBuilders).must(queryBuilder);
完美解决!
三、聚合查询
1、某字段聚合
最常见的需求,查询某字段分组后的个数,比如男生几个,女生几个,类似于sql中的group by AggregationBuilder aggregationBuilder = AggregationBuilders.terms("sex").field("sex).size(1000);
第一个“sex”代表你给聚合完的数据起的名字
第二个“sex”,即.field("sex)代表真实的es中存在的,你想要分组的字段名
1000,代表本次查询支持最大的buckets数量,此例中,不出意外,最大是3,非男即女或空。所以此处的1000改为3也无所谓。即有几个分组就会有几个buckets。
2、简单运算 计算平均值 AggregationBuilders.avg(自己取的名字).field(实际字段名) 计算某字段的50线,60线...95线,99线 AggregationBuilders.percentiles(自己取的名字).field(实际字段名).percentiles(50.0, 60.0, 70.0, 80.0, 90.0, 95.0, 99.0); 将某字段的值按大小分组 如,统计工资在0-100,100-150,150-200,大于200的个数 RangeAggregationBuilder rangeQueryBuilder2 = AggregationBuilders.range("tolerating").addRange(0, 10000).field("load.duration"); RangeAggregationBuilder rangeQueryBuilder1 = AggregationBuilders.range("least").addRange(0, 100).field("wages"); RangeAggregationBuilder rangeQueryBuilder2 = AggregationBuilders.range("less").addRange(100, 150).field("wages"); RangeAggregationBuilder rangeQueryBuilder3 = AggregationBuilders.range("many").addRange(150, 200).field("wages"); RangeAggregationBuilder rangeQueryBuilder4 = AggregationBuilders.range("mush").addRange(200, 10000000000).field("wages"); 然后再searchRequestBuilder中加入聚合 SearchResponse searchResponse = searchRequestBuilder.addAggregation(rangeQueryBuilder1) .addAggregation(rangeQueryBuilder2) .addAggregation(rangeQueryBuilder3) .addAggregation(rangeQueryBuilder4) 这里有个不方便的地方,比如大于200的,这个不好限制,写成【200,1000000000】确实结果大概率是对的,但加入真的有一个人,工资超级高,那你就把它,它,哈哈,落下了。怎么办呢? 大于200:RangeAggregationBuilder rangeQueryBuilder1 = AggregationBuilders.range("must").addUnboundedFrom("200").field("wages"); 小于100:RangeAggregationBuilder rangeQueryBuilder1 = AggregationBuilders.range("must").addUnboundedTo("100").field("wages"); 时间分组 方法1: AggregationBuilders.dateHistogram(自己取的名字).field(实际字段名).dateHistogramInterval(DateHistogramInterval.SECOND);表示按秒分组 还有DateHistogramInterval.MINUTE,DateHistogramInterval.HOURDate,HistogramInterval.DAY 方法2: AggregationBuilders.dateHistogram(自己取的名字).field(实际字段名).interval(毫秒值);
3、多重聚合 AggregationBuilder firstAggregationBuilder = AggregationBuilders.terms(agg1).field(agg1).size(1000); AvgAggregationBuilder secondAggregationBuilder = AggregationBuilders.avg(agg2).field(agg2); firstAggregationBuilder.subAggregation(secondAggregationBuilder) 最后searchRequestBuilder.addAggregation(firstAggregationBuilder)即可
四、哈哈哈
1,有不对或不懂的地方,私信我,或者评论区评论都可以
2,有实现不了的查询需求,发出来,大家一起想想,哦了
数据库
2019-04-26 11:54:00
「深度学习福利」大神带你进阶工程师,立即查看>>>
SELECT TOP 1000
ST.text AS '执行的SQL语句' ,
QS.execution_count AS '执行次数' ,
QS.total_elapsed_time / 10000 AS '耗时' ,
QS.total_logical_reads AS '逻辑读取次数' ,
QS.total_logical_writes AS '逻辑写入次数' ,
QS.total_physical_reads AS '物理读取次数' ,
QS.creation_time AS '执行时间' ,
QS.*
FROM sys.dm_exec_query_stats QS
CROSS APPLY sys.dm_exec_sql_text(QS.sql_handle) ST
WHERE QS.creation_time BETWEEN '2006-11-17'
AND GETDATE()
ORDER BY creation_time DESC
数据库
2019-04-26 11:35:00
「深度学习福利」大神带你进阶工程师,立即查看>>>
这里记录一下mac中搭建mysql中环境的过程,主要记录一下操作,以便日后再次安装。 进入mysql官方网站下载dmg包,解压,安装,生成数据库登录密码。 在bash_profile中加入PATH=$PATH:/usr/local/mysql/bin 路径。 source bash_profile,然后在终端中输入mysql -uroot -p进行登录,输入密码,操作完成。
最终登录图如下:
数据库
2019-04-25 20:21:00
「深度学习福利」大神带你进阶工程师,立即查看>>>
       前几天往 MongoDB 中写入了几个 G 的数据,发现 MongoDB 内存占用太 TMD 高了。不使用的时候也不释放,搞得交换空间都整了好几 G,其他进程的服务访问起来也慢得要死。没办法啊,人穷,能省就省。今天本来只是准备想办法限制一下 MongoDB 的内存占用的,可惜很多事情不折腾不行啊。
       系统中安装的是 apt 源提供的 MongoDB 包,3.2 的。在配置文件中设置 wiredTigeredCacheSizeGB = 0.5 后,终端输入 service mongodb restart service mongodb status
       服务无法启动,显示状态是 dead,启动日志提示 wiredTigeredCacheSizeGB 是整数。
       查官方和乡下资料,折腾了半天没有解决,想着升级一下得了。apt 源中提供的最新版本只到 3.2.10,没法再升了,只能找找官方的看看。
       卸载原有版本 apt purge mongodb apt autoremove
       在官方网站查到的最新版本安装方法如下 curl https://www.mongodb.org/static/pgp/server-4.0.asc | apt-key add - echo "deb http://repo.mongodb.org/apt/debian stretch/mongodb-org/4.0 main" | tee /etc/apt/sources.list.d/mongodb-org-4.0.list apt update apt install mongodb-org
       可惜,直接安装 4.0 不兼容啊。指定数据目录为原来的数据目录后,启动不起来,试了 3.6 也不行。
       看网上有人说需要先升 3.4,再升 3.6,最后才能升到 4.0。到官网找到 3.4 的安装说明,只有针对 Debian 8 的包,试试 curl https://www.mongodb.org/static/pgp/server-3.4.asc | apt-key add - echo "deb http://repo.mongodb.org/apt/debian jessie/mongodb-org/3.4 main" | tee /etc/apt/sources.list.d/mongodb-org-3.4.list apt update apt install mongodb-org=3.4.20 mongodb-org-server=3.4.20 mongodb-org-shell=3.4.20 mongodb-org-mongos=3.4.20 mongodb-org-tools=3.4.20
       安装不上,错误信息显示找不到 libssl1.0.1,系统安装的 libssl 版本太高了,1.1 的。这东西也不好卸载重装啊,依赖它的软件太多了。想想算了,还是按最终方案,先把数据导出,升级后再导入吧。
       装回原来版本 apt install mongodb service mongodb start service mongodb status
       网上找到的 MongoDB 导入导出以及数据库备份的文章 -------------------MongoDB数据导入与导出------------------- 1、导出工具:mongoexport 1、概念: mongoDB中的mongoexport工具可以把一个collection导出成JSON格式或CSV格式的文件。可以通过参数指定导出的数据项,也可以根据指定的条件导出数据。 2、语法: mongoexport -d dbname -c collectionname -o file --type json/csv -f field 参数说明: -d :数据库名 -c :collection名 -o :输出的文件名 --type : 输出的格式,默认为json -f :输出的字段,如果-type为csv,则需要加上-f "字段名" 3、示例: sudo mongoexport -d mongotest -c users -o /home/python/Desktop/mongoDB/users.json --type json -f "_id,user_id,user_name,age,status" 2、数据导入:mongoimport 1、语法: mongoimport -d dbname -c collectionname --file filename --headerline --type json/csv -f field 参数说明: -d :数据库名 -c :collection名 --type :导入的格式默认json -f :导入的字段名 --headerline :如果导入的格式是csv,则可以使用第一行的标题作为导入的字段 --file :要导入的文件 2、示例: sudo mongoimport -d mongotest -c users --file /home/mongodump/articles.json --type json -------------------MongoDB备份与恢复------------------- 1、MongoDB数据库备份 1、语法: mongodump -h dbhost -d dbname -o dbdirectory 参数说明: -h: MongDB所在服务器地址,例如:127.0.0.1,当然也可以指定端口号:127.0.0.1:27017 -d: 需要备份的数据库实例,例如:test -o: 备份的数据存放位置,例如:/home/mongodump/,当然该目录需要提前建立,这个目录里面存放该数据库实例的备份数据。 2、实例: sudo rm -rf /home/momgodump/ sudo mkdir -p /home/momgodump sudo mongodump -h 192.168.17.129:27017 -d itcast -o /home/mongodump/ 2、MongoDB数据库恢复 1、语法: mongorestore -h dbhost -d dbname --dir dbdirectory 参数说明: -h: MongoDB所在服务器地址 -d: 需要恢复的数据库实例,例如:test,当然这个名称也可以和备份时候的不一样,比如test2 --dir: 备份数据所在位置,例如:/home/mongodump/itcast/ --drop: 恢复的时候,先删除当前数据,然后恢复备份的数据。就是说,恢复后,备份后添加修改的数据都会被删除,慎用! 2、实例: mongorestore -h 192.168.17.129:27017 -d itcast_restore --dir /home/mongodump/itcast/
       在配置文件中禁用基于角色的访问控制并重启后,在终端执行 mongodump -d dbname -o bkpdirectory
       很好,成功备份到其他目录了。
       忽然想起,既然是导出数据再升级,何必非要先安装 3.6,直接装 4.0 试试再说不是更好?咱的数据结构不复杂,说不定会兼容。
       安装完成后,重新在配置文件中指定数据目录。为防意外,先移动原来的数据文件,创建一个新文件夹,然后再启动 mv /opt/data/mongodb /opt/data/mongodb1 mkdir /opt/data/mongodb service mongod start service mongod status
       启动不了,看来文件夹不是 MongoDB 用户组还不行。换个方式 rm -R /opt/data/mongodb mv /opt/data/mongodb1 /opt/data/mongodb mkdir /opt/data/bkp mv /opt/data/mongodb/* /opt/data/bkp service mongod start service mongod status
       OK,启动起来了。
       然后就是还原备份了。 mongorestore -d dbname --dir bkpdirectory
       数据量比较大,备份的时候还算快,可还原起来就慢了。特别是大的集合,1 M 要六七秒钟,有的数据项数量多但单个比较小,更慢。总共花了大概两个半小时。还好,没出什么问题。
       删除原来的数据文件 rm -R /opt/data/bkp
       然后回到正题,在配置文件中添加内存限制。在 storage 下面 wiredTiger: engineConfig: cacheSizeGB: 0.5
       最后,重新添加用户。在 admin 集合创建超级用户 db.createUser({user:'name',pwd:'pwd',roles:['userAdminAnyDatabase']})
       在数据集合创建一般用户 db.createUser({user:'name',pwd:'pwd',roles:['dbAdmin', 'readWrite']})
       在配置文件中启用基于角色的访问控制 security: authorization: enabled
       重启,完成。
       其他在网络上查到的:
       MongoDB 3.6 的安装 curl https://www.mongodb.org/static/pgp/server-3.6.asc | apt-key add - echo "deb http://repo.mongodb.org/apt/debian stretch/mongodb-org/3.6 main" | tee /etc/apt/sources.list.d/mongodb-org-3.6.list apt install mongodb-org=3.6.12 mongodb-org-server=3.6.12 mongodb-org-shell=3.6.12 mongodb-org-mongos=3.6.12 mongodb-org-tools=3.6.12
       导入数据的时候如果不兼容,可以指定 --noIndexRestore 参数不导入索引 mongorestore -h host --port port -u username -p password --noIndexRestore
参考: https://www.cnblogs.com/yiduobaozhiblog1/p/9934933.html
数据库
2019-04-25 17:49:00
「深度学习福利」大神带你进阶工程师,立即查看>>>
首先出现的情况是mysql -uroot -p123456可以登录,
但是网站配置了账号却显示Mysql:ERROR 1698 (28000): Access denied for user 'root'@'localhost'。
那就直接登录进去执行下面一条语句即可。
mysql.user表里authentication_string这个字段是密码,以后会逐渐废弃password字段 UPDATE mysql.user SET authentication_string=PASSWORD('root的密码'), PLUGIN='mysql_native_password' WHERE USER='root';
如果命令行都登不上去的话,
使用cat命令查看默认用户名密码。 sudo cat /etc/mysql/debian.cnf root @ubuntu :~# sudo cat /etc/mysql/debian.cnf
# Automatically generated for Debian scripts. DO NOT TOUCH!
[client]
host = localhost
user = debian-sys-maint
password = 0XRu7iGdhb6UT8Af
socket = /var/run/mysqld/mysqld.sock
[mysql_upgrade]
host = localhost
user = debian-sys-maint
password = 0XRu7iGdhb6UT8Af
socket = /var/run/mysqld/mysqld.sock
使用默认用户名密码登录 root@ubuntu:~# mysql -udebian-sys-maint -p0XRu7iGdhb6UT8Af mysql: [Warning] Using a password on the command line interface can be insecure. Welcome to the MySQL monitor. Commands end with ; or \g. Your MySQL connection id is 63 Server version: 5.7.25-1 (Ubuntu) Copyright (c) 2000, 2019, Oracle and/or its affiliates. All rights reserved. Oracle is a registered trademark of Oracle Corporation and/or its affiliates. Other names may be trademarks of their respective owners. Type 'help;' or '\h' for help. Type '\c' to clear the current input statement. mysql>
执行刚才那个更新语句,
退出来
重启mysql。
生效。
嗯。
实在解决不了,就卸载重装吧。 root@ubuntu:~#rm /var/lib/mysql/ -R root@ubuntu:~#rm /etc/mysql/ -R root@ubuntu:~#apt-get autoremove mysql* --purge root@ubuntu:~#apt-get remove apparmor root@ubuntu:~#apt-get update root@ubuntu:~#apt-get install mysql-server
数据库
2019-04-25 16:30:00
「深度学习福利」大神带你进阶工程师,立即查看>>>
CentOS 6.5 下安装配置 mysql
使用yum安装,具体过程参见最下边的参考文章。
安装之后启动失败: [root@localhost ~]# service mysqld start Starting mysqld: [ FAILD ]
启动失败,查看出错原因(记录在/var/log/mysqld.log 文件里): [root@localhost ~]# cat /var/log/mysqld.log 190425 13:56:00 mysqld_safe Starting mysqld daemon with databases from /var/lib/mysql /usr/libexec/mysqld: Table 'mysql.plugin' doesn't exist 190425 13:56:00 [ERROR] Can't open the mysql.plugin table. Please run mysql_upgrade to create it. 190425 13:56:00 InnoDB: Initializing buffer pool, size = 8.0M 190425 13:56:00 InnoDB: Completed initialization of buffer pool InnoDB: Error: log file ./ib_logfile0 is of different size 0 50331648 bytes InnoDB: than specified in the .cnf file 0 5242880 bytes! 190425 13:56:00 [ERROR] Plugin 'InnoDB' init function returned error. 190425 13:56:00 [ERROR] Plugin 'InnoDB' registration as a STORAGE ENGINE failed. 190425 13:56:00 [ERROR] Fatal error: Can't open and lock privilege tables: Table 'mysql.host' doesn't exist 190425 13:56:00 mysqld_safe mysqld from pid file /var/run/mysqld/mysqld.pid ended 190425 13:58:07 mysqld_safe Starting mysqld daemon with databases from /var/lib/mysql 190425 13:58:07 InnoDB: Initializing buffer pool, size = 8.0M 190425 13:58:07 InnoDB: Completed initialization of buffer pool InnoDB: Error: log file ./ib_logfile0 is of different size 0 50331648 bytes InnoDB: than specified in the .cnf file 0 5242880 bytes! 190425 13:58:07 [ERROR] Plugin 'InnoDB' init function returned error. 190425 13:58:07 [ERROR] Plugin 'InnoDB' registration as a STORAGE ENGINE failed. 190425 13:58:07 [Note] Event Scheduler: Loaded 0 events 190425 13:58:07 [Note] /usr/libexec/mysqld: ready for connections. Version: '5.1.73' socket: '/var/lib/mysql/mysql.sock' port: 3306 Source distribution # 注意!(这里的提示是解决之后的,刚安装完mysql时启动是报错的,OK那里是FAILD) # 这里的提示最后几行是解决之后启动了软件然后端口开放了。与前边报错显示在同一文件里了。)
mysql报错 [ERROR] Fatal error: Can't open and lock privilege tables: Table 'mysql.host' doesn't exist
解决方法:
mysql_install_db (参考自文章2。) # mysql默认不推荐用root用户来启动软件。正常情况下用root启动会有提示或报错,这里没有报错就启动了。需要注意一下。 [root@localhost ~]# service mysqld start Starting mysqld: [ OK ]
mysqladmin 设置用户名初始密码报错you need the SUPER privilege for this operation CREATE USER 'root'@'localhost' IDENTIFIED BY '[这里替换成想要设置的密码]'; GRANT SELECT,INSERT,UPDATE,DELETE ON *.* TO 'root'@'localhost'; GRANT ALL ON *.* TO 'root'@'localhost';
配置root远程访问 # 说明:mysql对用户访问的控制比较完善,同一个用户名从不同的机器访问同一数据库权限也是不同的。 # 以root为例。同一个用户root对应mysql数据库中user表里的多行数据。从本地访问时user表里对应host为localhost的一行。 # 即使user表里有一行的host叫做localhost.localdomain也不能从本地访问。(经验如此,具体原因不详) # 从远程访问时需要配置user表里对应host为%的一行。 Enter password: ******* select user,host from user; update user set host='%' where user='root'; flush privileges;
参考资料:
1、 Linux学习之CentOS(十三)--CentOS6.4下Mysql数据库的安装与配置
2、 mysql报错Fatal error: Can't open and lock privilege tables: Table 'mysql.host' doesn't exist
数据库
2019-04-25 14:17:00
「深度学习福利」大神带你进阶工程师,立即查看>>>
author: 风移
摘要
在SQL Server安全系列专题月报分享中,往期我们已经陆续分享了: 如何使用对称密钥实现SQL Server列加密技术 、 使用非对称密钥实现SQL Server列加密 、 使用混合密钥实现SQL Server列加密技术 、 列加密技术带来的查询性能问题以及相应解决方案 、 行级别安全解决方案 、 SQL Server 2016 dynamic data masking实现隐私数据列打码技术 和 使用证书做数据库备份加密 这七篇文章,直接点击以上文章前往查看详情。本期月报我们分享SQL Server 2016新特性Always Encrypted技术。
问题引入
在云计算大行其道的如今,有没有一种方法保证存储在云端的数据库中数据永远保持加密状态,即便是云服务提供商也看不到数据库中的明文数据,以此来保证客户云数据库中数据的绝对安全呢?答案是肯定的,就是我们今天将要谈到的SQL Server 2016引入的始终加密技术(Always Encrypted)。
使用SQL Server Always Encrypted,始终保持数据处于加密状态,只有调用SQL Server的应用才能读写和操作加密数据,如此您可以避免数据库或者操作系统管理员接触到客户应用程序敏感数据。SQL Server 2016 Always Encrypted通过验证加密密钥来实现了对客户端应用的控制,该加密密钥永远不会通过网络传递给远程的SQL Server服务端。因此,最大限度保证了云数据库客户数据安全,即使是云服务提供商也无法准确获知用户数据明文。
具体实现
SQL Server 2016引入的新特性Always Encrypted让用户数据在应用端加密、解密,因此在云端始终处于加密状态存储和读写,最大限制保证用户数据安全,彻底解决客户对云服务提供商的信任问题。以下是SQL Server 2016 Always Encrypted技术的详细实现步骤。
创建测试数据库
为了测试方便,我们首先创建了测试数据库AlwaysEncrypted。 --Step 1 - Create MSSQL sample database USE master GO IF DB_ID('AlwaysEncrypted') IS NULL CREATE DATABASE [AlwaysEncrypted]; GO -- Not 100% require, but option adviced. ALTER DATABASE [AlwaysEncrypted] COLLATE Latin1_General_BIN2;
创建列主密钥
其次,在AlwaysEncrypted数据库中,我们创建列主密钥(Column Master Key,简写为CMK)。 -- Step 2 - Create a column master key USE [AlwaysEncrypted] GO CREATE COLUMN MASTER KEY [AE_ColumnMasterKey] WITH ( KEY_STORE_PROVIDER_NAME = N'MSSQL_CERTIFICATE_STORE', KEY_PATH = N'CurrentUser/My/C3C1AFCDA7F2486A9BBB16232A052A6A1431ACB0' ) GO
创建列加密密钥
然后,我们创建列加密密钥(Column Encryption Key,简写为CEK)。
检查CMK和CEK
接下来,我们检查下刚才创建的列主密钥和列加密密钥,方法如下: -- Step 4 - CMK & CEK Checking select * from sys.column_master_keys select * from sys.column_encryption_keys select * from sys.column_encryption_key_values
一切正常,如下截图所示:
当然,您也可以使用SSMS的IDE来查看Column Master Key和Column Encryption Key,方法是:
展开需要检查的数据库 -> Security -> Always Encrypted Keys -> 展开Column Master Keys和 Column Encryption Keys。如下图所示:
创建Always Encryped测试表
下一步,我们创建Always Encrypted测试表,代码如下: -- Step 5 - Create a table with an encrypted column USE [AlwaysEncrypted] GO IF OBJECT_ID('dbo.CustomerInfo', 'U') IS NOT NULL DROP TABLE dbo.CustomerInfo GO CREATE TABLE dbo.CustomerInfo ( CustomerId INT IDENTITY(10000,1) NOT NULL PRIMARY KEY, CustomerName NVARCHAR(100) COLLATE Latin1_General_BIN2 ENCRYPTED WITH ( ENCRYPTION_TYPE = DETERMINISTIC, ALGORITHM = 'AEAD_AES_256_CBC_HMAC_SHA_256', COLUMN_ENCRYPTION_KEY = AE_ColumnEncryptionKey ) NOT NULL, CustomerPhone NVARCHAR(11) COLLATE Latin1_General_BIN2 ENCRYPTED WITH ( ENCRYPTION_TYPE = RANDOMIZED, ALGORITHM = 'AEAD_AES_256_CBC_HMAC_SHA_256', COLUMN_ENCRYPTION_KEY = AE_ColumnEncryptionKey ) NOT NULL ) ; GO
在创建Always Encrypted测试表过程中,对于加密字段,我们指定了:
 加密类型:DETERMINISTIC和RANDOMIZED。
 算法:AEAD_AES_256_CBC_HMAC_SHA_256是Always Encrypted专有算法。
 加密密钥:创建的加密密钥名字。
导出服务器端证书
最后,我们将服务端的证书导出成文件,方法如下:
Control Panel –> Internet Options -> Content -> Certificates -> Export。如下图所示:
导出向导中输入私钥保护密码。
选择存放路径。
最后导出成功。
应用程序端测试
SQL Server服务端配置完毕后,我们需要在测试应用程序端导入证书,然后测试应用程序。
客户端导入证书
客户端导入证书方法与服务端证书导出方法入口是一致的,方法是:Control Panel –> Internet Options -> Content -> Certificates -> Import。如下截图所示:
然后输入私钥文件加密密码,导入成功。
测试应用程序
我们使用VS创建一个C#的Console Application做为测试应用程序,使用NuGet Package功能安装Dapper,做为我们SQL Server数据库操作的工具。
注意:仅.NET 4.6及以上版本支持Always Encrypted特性的SQL Server driver,因此,请确保您的项目Target framework至少是.NET 4.6版本,方法如下:右键点击您的项目 -> Properties -> 在Application中,切换你的Target framework为.NET Framework 4.6。
为了简单方便,我们直接在SQL Server服务端测试应用程序,因此您看到的连接字符串是连接本地SQL Server服务。如果您需要测试远程SQL Server,修改连接字符串即可。整个测试应用程序代码如下: using System; using System.Collections.Generic; using System.Linq; using System.Text; using System.Threading.Tasks; using Dapper; using System.Data; using System.Data.SqlClient; namespace AlwaysEncryptedExample { public class AlwaysEncrypted { public static readonly string CONN_STRING = "Column Encryption Setting = Enabled;Server=.,1433;Initial Catalog=AlwaysEncrypted;Trusted_Connection=Yes;MultipleActiveResultSets=True;"; public static void Main(string[] args) { List Customers = QueryCustomerList(@"SELECT TOP 3 * FROM dbo.CustomerInfo WITH(NOLOCK)"); // there is no record if(Customers.Count == 0) { Console.WriteLine("************There is no record.************"); string execSql = @"INSERT INTO dbo.CustomerInfo VALUES (@customerName, @cellPhone);"; Console.WriteLine("************Insert some records.************"); DynamicParameters dp = new DynamicParameters(); dp.Add("@customerName", "CustomerA", dbType: DbType.String, direction: ParameterDirection.Input, size: 100); dp.Add("@cellPhone", "13402871524", dbType: DbType.String, direction: ParameterDirection.Input, size: 11); DoExecuteSql(execSql, dp); Console.WriteLine("************re-generate records.************"); Customers = QueryCustomerList(@"SELECT TOP 3 * FROM dbo.CustomerInfo WITH(NOLOCK)"); } else { Console.WriteLine("************There are a couple of records.************"); } foreach(Customer cus in Customers) { Console.WriteLine(string.Format("Customer name is {0} and cell phone is {1}.", cus.CustomerName, cus.CustomerPhone)); } Console.ReadKey(); } public static List QueryCustomerList(string queryText) { // input variable checking if (queryText == null || queryText == "") { return new List(); } try { using (IDbConnection dbConn = new SqlConnection(CONN_STRING)) { // if connection is closed, open it if (dbConn.State == ConnectionState.Closed) { dbConn.Open(); } // return the query result data set to list. return dbConn.Query(queryText, commandTimeout: 120).ToList(); } } catch (Exception ex) { Console.WriteLine("Failed to execute {0} with error message : {1}, StackTrace: {2}.", queryText, ex.Message, ex.StackTrace); // return empty list return new List(); } } public static bool DoExecuteSql(String execSql, object parms) { bool rt = false; // input parameters checking if (string.IsNullOrEmpty(execSql)) { return rt; } if (!string.IsNullOrEmpty(CONN_STRING)) { // try to add event file target try { using (IDbConnection dbConn = new SqlConnection(CONN_STRING)) { // if connection is closed, open it if (dbConn.State == ConnectionState.Closed) { dbConn.Open(); } var affectedRows = dbConn.Execute(execSql, parms); rt = (affectedRows > 0); } } catch (Exception ex) { Console.WriteLine("Failed to execute {0} with error message : {1}, StackTrace: {2}.", execSql, ex.Message, ex.StackTrace); } } return rt; } public class Customer { private int customerId; private string customerName; private string customerPhone; public Customer(int customerId, string customerName, string customerPhone) { this.customerId = customerId; this.customerName = customerName; this.customerPhone = customerPhone; } public int CustomerId { get { return customerId; } set { customerId = value; } } public string CustomerName { get { return customerName; } set { customerName = value; } } public string CustomerPhone { get { return customerPhone; } set { customerPhone = value; } } } } }
我们在应用程序代码中,仅需要在连接字符串中添加Column Encryption Setting = Enabled;属性配置,即可支持SQL Server 2016新特性Always Encrypted,非常简单。为了方便大家观察,我把这个属性配置放到了连接字符串的第一个位置,如下图所示:
运行我们的测试应用程序,展示结果如下图所示:
从应用程序的测试结果来看,我们可以正常读、写Always Encrypted测试表,应用程序工作良好。那么,假如我们抛开应用程序使用其它方式能否读写该测试表,看到又是什么样的数据结果呢?
测试SSMS
假设,我们使用SSMS做为测试工具。首先读取Always Encrypted测试表中的数据: -- try to read Always Encrypted table and it'll show us encrypted data instead of the plaintext. USE [AlwaysEncrypted] GO SELECT * FROM dbo.CustomerInfo WITH(NOLOCK)
展示结果如下截图:
然后,使用SSMS直接往测试表中插入数据: -- try to insert records to encrypted table, will be fail. USE [AlwaysEncrypted] GO INSERT INTO dbo.CustomerInfo VALUES ('CustomerA','13402872514'),('CustomerB','13880674722') GO
会报告如下错误: Msg 206, Level 16, State 2, Line 74 Operand type clash: varchar is incompatible with varchar(8000) encrypted with (encryption_type = 'DETERMINISTIC', encryption_algorithm_name = 'AEAD_AES_256_CBC_HMAC_SHA_256', column_encryption_key_name = 'AE_ColumnEncryptionKey', column_encryption_key_database_name = 'AlwaysEncrypted') collation_name = 'Chinese_PRC_CI_AS'
如下截图:
由此可见,我们无法使用测试应用程序以外的方法读取和操作Always Encrypted表的明文数据。
测试结果分析
从应用程序读写测试和使用SSMS直接读写Always Encrypted表的测试结果来看,用户可以使用前者正常读写测试表,工作良好;而后者无法读取测试表明文,仅可查看测试表的加密后的密文数据,加之写入操作直接报错。
测试应用源代码
如果您需要本文的测试应用程序源代码,请 点击下载 。
最后总结
本期月报,我们分享了SQL Server 2016新特性Always Encrypted的原理及实现方法,以此来保证存储在云端的数据库中数据永远保持加密状态,即便是云服务提供商也看不到数据库中的明文数据,以此来保证客户云数据库的数据绝对安全,解决了云数据库场景中最重要的用户对云服务提供商信任问题。
作者:风移
原文链接 ​
本文为云栖社区原创内容,未经允许不得转载。
数据库
2019-04-25 12:08:00
「深度学习福利」大神带你进阶工程师,立即查看>>>
作者:屈鹏
在 《TiKV 源码解析(二)raft-rs proposal 示例情景分析》 中,我们主要介绍了 raft-rs 的基本 API 使用,其中,与应用程序进行交互的主要 API 是: RawNode::propose 发起一次新的提交,尝试在 Raft 日志中追加一个新项; RawNode::ready_since 从 Raft 节点中获取最近的更新,包括新近追加的日志、新近确认的日志,以及需要给其他节点发送的消息等; 在将一个 Ready 中的所有更新处理完毕之后,使用 RawNode::advance 在这个 Raft 节点中将这个 Ready 标记为完成状态。
熟悉了以上 3 个 API,用户就可以写出基本的基于 Raft 的分布式应用的框架了,而 Raft 协议中将写入同步到多个副本中的任务,则由 raft-rs 库本身的内部实现来完成,无须应用程序进行额外干预。本文将对数据冗余复制的过程进行详细展开,特别是关于 snapshot 及流量控制的机制,帮助读者更深刻地理解 Raft 的原理。
一般 MsgAppend 及 MsgAppendResponse 的处理
在 Raft leader 上,应用程序通过 RawNode::propose 发起的写入会被处理成一条 MsgPropose 类型的消息,然后调用 Raft::append_entry 和 Raft::bcast_append 将消息中的数据追加到 Raft 日志中并广播到其他副本上。整体流程如伪代码所示: fn Raft::step_leader(&mut self, mut m: Message) -> Result<()> { if m.get_msg_type() == MessageType::MsgPropose { // Propose with an empty entry list is not allowed. assert!(!m.get_entries().is_empty()); self.append_entry(&mut m.mut_entries()); self.bcast_append(); } }
这段代码中 append_entry 的参数是一个可变引用,这是因为在 append_entry 函数中会为每一个 Entry 赋予正确的 term 和 index。term 由选举产生,在一个 Raft 系统中,每选举出一个新的 Leader,便会产生一个更高的 term。而 index 则是 Entry 在 Raft 日志中的下标。Entry 需要带上 term 和 index 的原因是,在其他副本上的 Raft 日志是可能跟 Leader 不同的,例如一个旧 Leader 在相同的位置(即 Raft 日志中具有相同 index 的地方)广播了一条过期的 Entry,那么当其他副本收到了重叠的、但是具有更高 term 的消息时,便可以用它们替换旧的消息,以便达成与最新的 Leader 一致的状态。
在 Leader 将新的写入追加到自己的 Raft log 中之后,便可以调用 bcast_append 将它们广播到其他副本了。注意这个函数并没有任何参数,那么 Leader 如何知道应该给每一个副本从哪一个位置开始广播呢?原来在 Leader 上对每一个副本,都关联维护了一个 Progress,该结构体定义如下: pub struct Progress { pub matched: u64, // 该副本期望接收的下一个 Entry 的 index pub next_idx: u64, // 未 commit 的消息的滑动窗口 pub ins: Inflights, // ProgressState::Probe:Leader 每个心跳间隔中最多发送一条 MsgAppend // ProgressState::Replicate:Leader 在每个心跳间隔中可以发送多个 MsgAppend // ProgressState::Snapshot:Leader 无法再继续发送 MsgAppend 给这个副本 pub state: ProgressState, // 是否暂停给这个副本发送 MsgAppend 了 pub paused: bool, // 一些其他字段…… }
如代码注释中所说的那样,Leader 在给副本广播新的日志时,会从对应的副本的 next_idx 开始。这就蕴含了两个问题: 在刚开始启动的时候,所有副本的 next_idx 应该如何设置? 在接收并处理完成 Leader 广播的新写入后,其他副本应该如何向 Leader 更新 next_idx ?
第一个问题的答案在 Raft::reset 函数中。这个函数会在 Raft 完成选举之后选出的 Leader 上调用,会将 Leader 的所有其他副本的 next_idx 设置为跟 Leader 相同的值。之后,Leader 就可以会按照 Raft 论文里的规定,广播一条包含了自己的 term 的空 Entry 了。
第二个问题的答案在 Raft::handle_append_response 函数中。我们继续考察上面的情景,Leader 的其他副本在收到 Leader 广播的最新的日志之后,可能会采取两种动作: fn Raft::handle_append_entries(&mut self, m: &Message) { let mut to_send = Message::new_message_append_response(); match self.raft_log.maybe_append(...) { // 追加日志成功,将最新的 last index 上报给 Leader Some(last_index) => to_send.set_index(last_index), // 追加日志失败,设置 reject 标志,并告诉 Leader 自己的 last index None => { to_send.set_reject(true); to_send.set_reject_hint(self.raft_log.last_index()); } } } self.send(to_send);
其他副本调用 maybe_append 失败的原因可能是比 Leader 的日志更少,但是 Leader 在刚选举出来的时候将所有副本的 next_idx 设置为与自己相同的值了。这个时候这些副本就会在 MsgAppendResponse 中设置拒绝的标志。在 Leader 接收到这样的反馈之后,就可以将对应副本的 next_idx 设置为正确的值了。这个逻辑在 Raft::handle_append_response 中: fn Raft::handle_append_response(&mut self, m: &Message, …) { if m.get_reject() { let pr: &mut Progress = self.get_progress(m.get_from()); // 将副本对应的 `next_idx` 回退到一个合适的值 pr.maybe_decr_to(m.get_index(), m.get_reject_hint()); } else { // 将副本对应的 `next_idx` 设置为 `m.get_index() + 1` pr.maybe_update(m.get_index()); } }
以上伪代码中我们省略了一些丢弃乱序消息的代码,避免过多的细节造成干扰。
pipeline 优化和流量控制机制
上一节我们重点观察了 MsgAppend 及 MsgAppendResponse 消息的处理流程,原理是非常简单、清晰的。然而,这个未经任何优化的实现能够工作的前提是在 Leader 收到某个副本的 MsgAppendResponse 之前,不再给它发送任何 MsgAppend。由于等待响应的时间取决于网络的 TTL,这在实际应用中是非常低效的,因此我们需要引入 pipeline 优化,以及配套的流量控制机制来避免“优化”带来的网络壅塞。
Pipeline 在 Raft::prepare_send_entries 函数中被引入。这个函数在 Raft::send_append 中被调用,内部会直接修改对目标副本的 next_idex 值,这样,后续的 MsgAppend 便可以在此基础上继续发送了。而一旦之前的 MsgAppend 被该目标副本拒绝掉了,也可以通过上一节中介绍的 maybe_decr_to 机制将 next_idx 重置为正确的值。我们来看一下这段代码: // 这个函数在 `Raft::prepare_send_entries` 中被调用 fn Progress::update_state(&mut self, last: u64) { match self.state { ProgressState::Replicate => { self.next_idx = last + 1; self.ins.add(last); }, ProgressState::Probe => self.pause(), _ => unreachable!(), } }
Progress 有 3 种不同的状态,如这个结构体的定义的代码片段所示。其中 Probe 状态和 Snapshot 状态会在下一节详细介绍,现在只需要关注 Replicate 状态。我们已经知道 Pipeline 机制是由更新 next_idx 的那一行引入的了,那么下面更新 ins 的一行的作用是什么呢?
从 Progress 的定义的代码片段中我们知道, ins 字段的类型是 Inflights,可以想象成一个类似 TCP 的滑动窗口:所有 Leader 发出了,但是尚未被目标副本响应的消息,都被框在该副本在 Leader 上对应的 Progress 的 ins 中。这样,由于滑动窗口的大小是有限的,Raft 系统中任意时刻的消息数量也会是有限的,这就实现了流量控制的机制。更具体地,Leader 在给某一副本发送 MsgAppend 时,会检查其对应的滑动窗口,这个逻辑在 Raft::send_append 函数中;在收到该副本的 MsgAppendResponse 之后,会适时调用 Inflights 的 free_to 函数,使窗口向前滑动,这个逻辑在 Raft::handle_append_response 中。
ProgressState 相关优化
我们已经在 Progress 结构体的定义以及上面一些代码片段中见过了 ProgressState 这个枚举类型。在 3 种可能的状态中,Replicate 状态是最容易理解的,Leader 可以给对应的副本发送多个 MsgAppend 消息(不超过滑动窗口的限制),并适时地将窗口向前滑动。然而,我们注意到,在 Leader 刚选举出来时,Leader 上面的所有其他副本的状态却被设置成了 Probe。这是为什么呢?
从 Progress 结构体的字段注释中,我们知道当某个副本处于 Probe 状态时,Leader 只能给它发送 1 条 MsgAppend 消息。这是因为,在这个状态下的 Progress 的 next_idx 是 Leader 猜出来的,而不是由这个副本明确的上报信息推算出来的。它有很大的概率是错误的,亦即 Leader 很可能会回退到某个地方重新发送;甚至有可能这个副本是不活跃的,那么 Leader 发送的整个滑动窗口的消息都可能浪费掉。因此,我们引入 Probe 状态,当 Leader 给处于这一状态的副本发送了 MsgAppend 时,这个 Progress 会被暂停掉(源码片段见上一节),这样在下一次尝试给这个副本发送 MsgAppend 时,会在 Raft::send_append 中跳过。而当 Leader 收到了这个副本上报的正确的 last index 之后,Leader 便知道下一次应该从什么位置给这个副本发送日志了,这一过程在 Progress::maybe_update 函数中: fn Progress::maybe_update(&mut self, n: u64) { if self.matched < n { self.matched = n; self.resume(); // 取消暂停的状态 } if self.next_idx < n + 1 { self.next = n + 1; } }
ProgressState::Snapshot 状态与 Progress 中的 pause 标志十分相似,一个副本对应的 Progress 一旦处于这个状态,Leader 便不会再给这个副本发送任何 MsgAppend 了。但是仍有细微的差别:事实上在 Leader 收到 MsgHeartbeatResponse 时,也会调用 Progress::resume 来将取消对该副本的暂停,然而对于 ProgressState::Snapshot 状态的 Progress 则没有这个逻辑。这个状态会在 Leader 成功发送完成 Snapshot,或者收到了对应的副本的最新的 MsgAppendResponse 之后被改变,详细的逻辑请参考源代码,这里就不作赘述了。
我们把篇幅留给在 Follower 上收到 Snapshot 之后的处理逻辑,主要是 Raft::restore_raft 和 RaftLog::restore 两个函数。前者中主要包含了对 Progress 的处理,因为 Snapshot 包含了 Leader 上最新的信息,而 Leader 上的 Configuration 是可能跟 Follower 不同的。后者的主要逻辑伪代码如下所示: fn RaftLog::restore(&mut self, snapshot: Snapshot) { self.committed = snapshot.get_metadata().get_index(); self.unstable.restore(snapshot); }
可以看到,内部仅更新了 committed,并没有更新 applied。这是因为 raft-rs 仅关心 Raft 日志的部分,至于如何把日志中的内容更新到真正的状态机中,是应用程序的任务。应用程序需要从上一篇文章中介绍的 Ready 接口中把 Snapshot 拿到,然后自行将其应用到状态机中,最后再通过 RawNode::advance 接口将 applied 更新到正确的值。
总结
Raft 日志复制及相关的流量控制、Snapshot 流程就介绍到这里,代码仓库仍然在 https://github.com/pingcap/raft-rs ,source-code 分支。下一期 raft-rs 源码解析我们会继续为大家带来 configuration change 相关的内容,敬请期待!
数据库
2019-04-25 10:40:00
「深度学习福利」大神带你进阶工程师,立即查看>>>
MySQL中的point用于表示GIS中的地理坐标,在GIS中广泛使用
如何写入mysql,如下图: CREATE TABLE `test-point` ( `id` int(11) NOT NULL AUTO_INCREMENT COMMENT '序号', `point` point NOT NULL COMMENT '经纬度', `text` varchar(50) DEFAULT NULL COMMENT '描述', PRIMARY KEY (`id`) ) ENGINE=MyISAM AUTO_INCREMENT=10 DEFAULT CHARSET=utf8mb4; INSERT INTO `test-point` ( point,text ) VALUES ( GeomFromText ( 'POINT(1 1)' ),'第一个点');
mybatis中配置如下图:
数据格式为: 'POINT(1 1)'
mapping文件为: GeomFromText(#{point,jdbcType=OTHER}),
数据库
2019-04-24 21:05:00