• [技术干货] 写着简单跑得又快的数据库语言 SPL[转载]
    文章目录数据库语言的目标SQL为什么不行SPL为什么能行数据库语言的目标要说清这个目标,先要理解数据库是做什么的。数据库这个软件,名字中有个“库”字,会让人觉得它主要是为了存储的。其实不然,数据库实现的重要功能有两条:计算、事务!也就是我们常说的OLAP和OLTP,数据库的存储都是为这两件事服务的,单纯的存储并不是数据库的目标。我们知道,SQL是目前数据库的主流语言。那么,用SQL做这两件事是不是很方便呢?事务类功能主要解决数据在写入和读出时要保持的一致性,实现这件事的难度并不小,但对于应用程序的接口却非常简单,用于操纵数据库读写的代码也很简单。如果假定目前关系数据库的逻辑存储模式是合理的(也就是用数据表和记录来存储数据,其合理性与否是另一个复杂问题,不在这里展开了),那么SQL在描述事务类功能时没什么大问题,因为并不需要描述多复杂的动作,复杂性都在数据库内部解决了。但计算类功能却不一样了。这里说的计算是个更广泛的概念,并不只是简单的加加减减,查找、关联都可以看成是某种计算。什么样的计算体系才算好呢?还是两条:写着简单、跑得快。写着简单,很好理解,就是让程序员很快能写出来代码来,这样单位时间内可以完成更多的工作;跑得快就更容易理解,我们当然希望更短时间内获得计算结果。其实SQL中的Q就是查询的意思,发明它的初衷主要是为了做查询(也就是计算),这才是SQL的主要目标。然而,SQL在描述计算任务时,却很难说是很胜任的。SQL为什么不行先看写着简单的问题。SQL写出来很象英语,有些查询可以当英语来读和写(网上多得很,就不举例了),这应当算是满足写着简单这一条了吧。且慢!我们在教科书上看到的SQL经常只有两三行,这些SQL确实算是写着简单的,但如果我们尝试一些稍复杂化的问题呢?这是一个其实还不算很复杂的例子:计算一支股票最长连续上涨了多少天?用SQL写出来是这样的:select max (consecutive_day)from (select count(*) (consecutive_day      from (select sum(rise_mark) over(order by trade_date) days_no_gain            from (select trade_date,                         case when closing_price>lag(closing_price) over(order by trade_date)                              then 0 else 1 END rise_mark                  from stock_price ) )      group by days_no_gain)这个语句的工作原理就不解释了,反正有点绕,同学们可以自己尝试一下。这是润乾公司的招聘考题,通过率不足20%;因为太难,后来被改成另一种方式:把SQL语句写出来让应聘者解释它在算什么,通过率依然不高。这说明什么?说明情况稍有复杂,SQL就变得即难懂又难写!再看跑得快的问题,还是一个经常拿出来的简单例子:1亿条数据中取前10名。这个任务用SQL写出来并不复杂:SELECT TOP 10 x FROM T ORDER BY x DESC1但是,这个语句对应的执行逻辑是先对所有数据进行大排序,然后再取出前10个,后面的不要了。大家知道,排序是一个很慢的动作,会多次遍历数据,如果数据量大到内存装不下,那还需要外存做缓存,性能还会进一步急剧下降。如果严格按这句SQL体现的逻辑去执行,这个运算无论如何是跑不快的。然而,很多程序员都知道这个运算并不需要大排序,也用不着外存缓存,一次遍历用一点点内存就可以完成,也就是存在更高性能的算法。可惜的是,用SQL却写不出这样的算法,只能寄希望于数据库的优化器足够聪明,能把这句SQL转换成高性能算法执行,但情况复杂时数据库的优化器也未必靠谱。看样子,SQL在这两方面做得都不够好。这两个并不复杂的问题都是这样,现实中数千行的SQL代码中,这种难写且跑不快的情况比比皆是。为什么SQL不行呢?要回答这个问题,我们要分析一下用程序代码实现计算到底是在干什么。本质上讲,编写程序的过程,就是把解决问题的思路翻译成计算机可执行的精确化形式语言的过程。举例来说,就象小学生解应用题,分析问题想出解法之后,还要列出四则运算表达式。用程序计算也是一样,不仅要想出解决问题的方法,还要把解法翻译成计算机能理解执行的动作才算完成。用于描述计算方法的形式语言,其核心在于所采用的代数体系。所谓代数体系,简单说就是一些数据类型和其上的运算规则,比如小学学到的算术,就是整数和加减乘除运算。有了这套东西,我们就能把想做的运算用这个代数体系约定的符号写出来,也就是代码,然后计算机就可以执行了。如果这个代数体系设计时考虑不周到,提供的数据类型和运算不方便,那就会导致描述算法非常困难。这时候会发生一个怪现象:翻译解法到代码的难度远远超过解决问题本身。举个例子,我们从小学习用阿拉伯数字做日常计算,做加减乘除都很方便,所有人都天经地义认为数值运算就该是这样的。其实未必!估计很多人都知道还有一种叫做罗马数字的东西,你知道用罗马数字该怎么做加减乘除吗?古罗马人又是如何上街买菜的?代码难写很大程度是代数的问题。再看跑不快的原因。软件没办法改变硬件的性能,CPU和硬盘该多快就是多快。不过,我们可以设计出低复杂度的算法,也就是计算量更小的算法,这样计算机执行的动作变少,自然也就会快了。但是,光想出算法还不够,还要把这个算法用某种形式语言写得出来才行,否则计算机不会执行。而且,写起来还要比较简单,都要写很长很麻烦,也没有人会去用。所以呢,对于程序来讲,跑得快和写着简单其实是同一个问题,背后还是这个形式语言采用的代数的问题。如果这个代数不好,就会导致高性能算法很难实现甚至实现不了,也就没办法跑得快了。就象上面说的,用SQL写不出我们期望的小内存单次遍历算法,能不能跑得快就只能寄希望于优化器。我们再做个类比:上过小学的同学大概都知道高斯计算1+2+3+…+100的小故事。普通人就是一步步地硬加100次,高斯小朋友很聪明,发现1+100=101、2+99=101、…、50+51=101,结果是50乘101,很快算完回家午饭了。听过这个故事,我们都会感慨高斯很聪明,能想到这么巧妙的办法,即简单又迅速。这没有错,但是,大家容易忽略一点:在高斯的时代,人类的算术体系(也是一个代数)中已经有了乘法!象前面所说,我们从小学习四则运算,会觉得乘法是理所当然的,然而并不是!乘法是后于加法被发明出来的。如果高斯的年代还没有乘法,即使有聪明的高斯,也没办法快速解决这个问题。目前主流数据库是关系数据库,之所以这么叫,是因为它的数学基础被称为关系代数,SQL也就是关系代数理论上发展出来的形式语言。现在我们能回答,为什么SQL在期望的两个方面做得不够好?问题出在关系代数上,关系代数就像一个只有加法还没发明乘法的算术体系,很多事做不好是必然的。关系代数已经发明五十年了,五十年前的应用需求以及硬件环境,和今天比的差异是很巨大了,继续延用五十年前的理论来解决今天的问题,听着就感觉太陈旧了?然而现实就是这样,由于存量用户太多,而且也还没有成熟的新技术出现,基于关系代数的SQL,今天仍然是最重要的数据库语言。虽然这几十年来也有一些改进完善,但根子并没有变,面对当代的复杂需求和硬件环境,SQL不胜任也是情理之中的事。而且,不幸的是,这个问题是理论上的,在工程上无论如何优化也无济于事,只能有限改善,不能根除。不过,绝大部分的数据库开发者并不会想到这一层,或者说为了照顾存量用户的兼容性,也没打算想到这一层。于是,主流数据库界一直在这个圈圈里打转转。SPL为什么能行那么该怎样让计算写着更简单、跑得更快呢?发明新的代数!有“乘法”的代数。在其基础上再设计新的语言。这就是SPL的由来。它的理论基础不再是关系代数,称为离散数据集。基于这个新代数设计的形式语言,起名为SPL(Structured Process Language)。SPL针对SQL的不足(更确切地说法是,离散数据集针对关系代数的各种缺陷)进行了革新。SPL重新定义了并扩展许多结构化数据中的运算,增加了离散性、强化了有序计算、实现了彻底的集合化、支持对象引用、提倡分步运算。把前面的问题用SPL重写一遍有个直接感受。一支股票最长连续上涨多少天:stock_price.sort(trade_date).group@i(closing_price<closing_price[-1]).max(~.len())1计算思路和前面的SQL相同,但因为引入了有序性后,表达起来容易多了,不再绕了。1亿条数据中取前10名:T.groups(;top(-10,x))1SPL有更丰富的集合数据类型,容易描述单次遍历上实施简单聚合的高效算法,不涉及大排序动作。限于篇幅,这里不能介绍SPL(离散数据集)的全貌。我们在这里列举SPL(离散数据集)针对SQL(关系代数)的部分差异化改进:游离记录离散数据集中的记录是一种基本数据类型,它可以不依赖于数据表而独立存在。数据表是记录构成的集合,而构成某个数据表的记录还可以用于构成其它数据表。比如过滤运算就是用原数据表中满足条件的记录构成新数据表,这样,无论空间占用还是运算性能都更有优势。关系代数没有可运算的数据类型来表示记录,单记录实际上是只有一行的数据表,不同数据表中的记录也不能共享。比如,过滤运算时会复制出新记录来构成新数据表,空间和时间成本都变大。特别地,因为有游离记录,离散数据集允许记录的字段取值是某个记录,这样可以更方便地实现外键连接。有序性关系代数是基于无序集合设计的,集合成员没有序号的概念,也没有提供定位计算以及相邻引用的机制。SQL实践时在工程上做了一些局部完善,使得现代SQL能方便地进行一部分有序运算。离散数据集中的集合是有序的,集合成员都有序号的概念,可以用序号访问成员,并定义了定位运算以返回成员在集合中的序号。离散数据集提供了符号以在集合运算中实现相邻引用,并支持针对集合中某个序号位置进行计算。有序运算很常见,却一直是SQL的困难问题,即使在有了窗口函数后仍然很繁琐。SPL则大大改善了这个局面,前面那个股票上涨的例子就能说明问题。离散性与集合化关系代数中定义了丰富的集合运算,即能将集合作为整体参加运算,比如聚合、分组等。这是SQL比Java等高级语言更为方便的地方。但关系代数的离散性非常差,没有游离记录。而Java等高级语言在这方面则没有问题。离散数据集则相当于将离散性和集合化结合起来了,既有集合数据类型及相关的运算,也有集合成员游离在集合之外单独运算或再组成其它集合。可以说SPL集中了SQL和Java两者的优势。有序运算是典型的离散性与集合化的结合场景。次序的概念只有在集合中才有意义,单个成员无所谓次序,这里体现了集合化;而有序计算又需要针对某个成员及其相邻成员进行计算,需要离散性。在离散性的支持下才能获得更彻底的集合化,才能解决诸如有序计算类型的问题。离散数据集是即有离散性又有集合化的代数体系,关系代数只有集合化。分组理解分组运算的本意是将一个大集合按某种规则拆成若干个子集合,关系代数中没有数据类型能够表示集合的集合,于是强迫在分组后做聚合运算。离散数据集中允许集合的集合,可以表示合理的分组运算结果,分组和分组后的聚合被拆分成相互独立的两步运算,这样可以针对分组子集再进行更复杂的运算。关系代数中只有一种等值分组,即按分组键值划分集合,等值分组是个完全划分。离散数据集认为任何拆分大集合的方法都是分组运算,除了常规的等值分组外,还提供了与有序性结合的有序分组,以及可能得到不完全划分结果的对位分组。聚合理解关系代数中没有显式的集合数据类型,聚合计算的结果都是单值,分组后的聚合运算也是这样,只有SUM、COUNT、MAX、MIN等几种。特别地,关系代数无法把TOPN运算看成是聚合,针对全集的TOPN只能在输出结果集时排序后取前N条,而针对分组子集则很难做到TOPN,需要转变思路拼出序号才能完成。离散数据集提倡普遍集合,聚合运算的结果不一定是单值,仍然可能是个集合。在离散数据集中,TOPN运算和SUM、COUNT这些是地位等同的,即可以针对全集也可以针对分组子集。SPL把TOPN理解成聚合运算后,在工程实现时还可以避免全量数据的排序,从而获得高性能。而SQL的TOPN总是伴随ORDER BY动作,理论上需要大排序才能实现,需要寄希望于数据库在工程实现时做优化。有序支持的高性能离散数据集特别强调有序集合,利用有序的特征可以实施很多高性能算法。这是基于无序集合的关系代数无能为力的,只能寄希望于工程上的优化。下面是部分利用有序特征后可以实施的低复杂度运算:1)数据表对主键有序,相当于天然有一个索引。对键字段的过滤经常可以快速定位,以减少外存遍历量。随机按键值取数时也可以用二分法定位,在同时针对多个键值取数时还能重复利用索引信息。2)通常的分组运算是用HASH算法实现的,如果我们确定地知道数据对分组键值有序,则可以只做相邻对比,避免计算HASH值,也不会有HASH冲突的问题,而且非常容易并行。3)数据表对键有序,两个大表之间对位连接可以执行更高性能的归并算法,只要对数据遍历一次,不必缓存,对内存占用很小;而传统的HASH值分堆方法不仅比较复杂度高,需要较大内存并做外部缓存,还可能因HASH函数不当而造成二次HASH再缓存。4)大表作为外键表的连接。事实表小时,可以利用外键表有序,快速从中取出关联键值对应的数据实现连接,不需要做HASH分堆动作。事实表也很大时,可以将外键表用分位点分成多个逻辑段,再将事实表按逻辑段进行分堆,这样只需要对一个表做分堆,而且分堆过程中不会出现HASH分堆时的可能出现的二次分堆,计算复杂度能大幅下降。其中3和4利用了离散数据集对连接运算的改造,如果仍然延用关系代数的定义(可能产生多对多),则很难实现这种低复杂的算法。除了理论上的差异, SPL还有许多工程层面的优势,比如更易于编写并行代码、大内存预关联提高外键连接性能等、特有的列存机制以支持随意分段并行等。这里还有更多SPL代码以体现其思路及大数据算法:性能优化技巧:遍历复用提速多次分组性能优化技巧:TopN性能优化技巧:预关联性能优化技巧:部分预关联性能优化技巧:外键序号化性能优化技巧:维表过滤或计算时的关联性能优化技巧:有序归并性能优化技巧:有序定位关联提速主子关联后的过滤性能优化技巧:附表性能优化技巧:小事实表与大维表关联性能优化技巧:大事实表与大维表关联性能优化技巧:有序分组性能优化技巧:后半有序分组性能优化技巧:前半有序时的排序原文链接:https://blog.csdn.net/wangyuxiang946/article/details/124921223
  • [实践系列] DWS SQL调优的一些总结
    0. 统计信息-- 统计信息是动态调优的核心信息输入,统计信息准确与否,至关重要0.1 低效算子-- NEST LOOP0.2 不下推分析优化器在分布式框架下有三种执行计划优化策略* 下推语句执行计划:CN发送查询语句到DN直接执行,执行结果返回给CN。(DN间无需数据交换场景) 特征:Data Node Scan on *_REMOTE_FQS_QUERY_*例如:create table t1(a int,b int) distribute by (a);create table t2(a int,b int) distribute by (a);explain verbose select t1.* from t1  join t2 on t1.a = t2.a;-- t1和t2 都是分布表,t1.a 和 t2.a都是其分布列,join能匹配到的数据都在同一个DN上,因此DN间不需要数据交换,原语句直接下发到DN上执行即可。-- 此种语句在CH上打印的执行计划信息较少,可直接在DN上打印详细执行信息* 分布式计划:CN生成计划树,发送计划树给DN执行;DN执行完成后,将结果返回给CN。 特征:Streaming (type:GATHER) broadcast : 全表广播。全表数据量的数据向所有DN传输 redistribute : 重分布。不多于全表数据量的数据向所有DN传输。性能优于 broadcast gather :聚合流。聚合流将数据从多个查询片段聚合到一个。* 不下推执行计划:CN承担大量计算任务,导致性能劣化。优化器将部分查询(多为基表扫描语句,DN只扫描、不计算,不过滤)下推到DN执行,将获取到的中间结果返回给CN,CN再执行计划剩余的部分。 特征:Data Node Scan + _REMOTE_XXX 0.3 --优化思路和手段1.扫描慢1.1 建立单字段分区或多字段分区-- 逻辑上的一张表根据某种方案分成几张物理块进行存储,这张逻辑上的表称之为分区表,物理块称之为分区。分区表是一张逻辑表,不存储数据,数据实际是存储在分区上的。-- 目前行存表、列存表仅支持范围分区和列表分区。(8.1.3)-- 有限地支持唯一约束和主键约束,即唯一约束和主键约束的约束键必须包含所有分区键。-- VACUUM和ANALYZE只会对主表起作用,要想分析分区表,需要分别分析每个分区表。-- 数据迁移到分区表后建议禁用主表,如果主表未执行vacuum操作,那么执行计划会全表扫描主表,非常耗时。•查看分区表信息,可使用系统表dba_tab_partitions。select * from dba_tab_partitions where table_name='tpcds.customer_address' --单子段分区partition by range(followup_create_time)(    partition p1 START('2022-01-01') END ('2022-06-30') EVERY (INTERVAL '1 day')) partition by range(order_date)(    partition p1 START('2022-01-01'::TIMESATMP(0)) END ('2022-06-30'::TIMESTAMP(0)) EVERY (INTERVAL '3 months')))  -- 多字段分区(多字段分区不能使用START END 来指定分区) WITH (orientation=column, compression=low, colversion=2.0, enable_delta=false) DISTRIBUTE BY HASH(imsi) PARTITION BY RANGE (idperiodo, idcalendario) (          PARTITION p_20201231 VALUES LESS THAN (202101, 20210101) TABLESPACE pg_default,          PARTITION p_20210101 VALUES LESS THAN (202101, 20210102) TABLESPACE pg_default)-- 查询分区select * from table_name partition('partition_name');1.1 行存表:建立btree保序索引 -- 涉及排序场景建立btree索引,目前行存btree索引保持排序,列存不保存排序结果。但是建立索引后,因为要排序,所以表在插入、更新时会有一定的性能影响。CREATE INDEX dws_tt_flink_eos_wide_01 ON dws_tt_flink_eos_wide USING btree(followup_create_time) local;1.2 列存表建立PCK(Partial Cluster Key(局部聚簇));局部聚簇存储,列存表导入数据时按照指定的列(单列或多列),进行局部排序。-- 一个表只能建立一个PCK-- 一个PCK可以包含多列,但是不建议超过两列-- 建议在查询中的简单表达式的过滤条件上建立PCK;如column >,=,< 常量。-- 在满足上面条件的情况下,选择distinct值比较少的列建立PCKCREATE TABLE tpcds.warehouse_t21(    W_WAREHOUSE_SK            INTEGER         NOT NULL,    PRIMARY KEY (column_name1,column_name2),    PARTIAL CLUSTER KEY(column_name1,column_name2))WITH (orientation=column, compression=low, colversion=2.0, enable_delta=false) DISTRIBUTE BY REPLICATION; 2.表结构orientation不支持修改。行列存储方式一旦建立,不能修改行存表:-- 点查询场景(大基表单表过滤查询,返回结果少,基于索引的简单查询,比如btree排序索引)-- 增删改较多的场景,并发增删改;实时数据接入等-- 行存表压缩功能暂未商用,如需使用请联系技术支持工程师。列存表:-- 多表关联的统计分析类场景-- 即席查询(查询条件列不确定,行存无法确定索引)-- 多表关联查询、聚合、分组查询等,访问大量行,少数列的场景。数据类型:-- 数据类型合理 变长可以变为定长的一律改为定长;变长在方便的同时,肯定会影响性能。-- 多个表间存在逻辑关系时,表示同一含义字段使用相同类型。字符串字段尽量使用变长数据类型。不建议使用定长数据类型。text,carchar  --> char(8)numeric(12,0) --> bigint2.1分布键选择合理 (不同DN间相差5%以上即可认为倾斜,10%以上必须调整分布列)-- ◾当指定DISTRIBUTE BY HASH (column_name)参数时,创建主键和唯一索引必须包含分布键。-- 查询数据分布:select table_skewness('table_name');-- 不指定分布方式时,默认第一个字段为分布键,进行hash分布-- 通常选取表的主键作为分布列-- 选择查询中关联条件作为分布列,以便Join任务可以下推到DN执行,且减少DN间的通信数据量。-- 复杂查询场景下,尽量不要选取存在常量等值过滤的列,避免剪枝后扫描集中在部分DN上。-- 点查询场景下,则应该尽量选取WHERE条件中的等值过滤条件列作为分布列。2.2 维度小表建立复制表  ,列存(数据量10万以下)CREATE TABLE tpcds.warehouse_t21(    W_WAREHOUSE_SK            INTEGER               NOT NULL)WITH (orientation=column, compression=low, colversion=2.0, enable_delta=false) DISTRIBUTE BY REPLICATION;2.3 列存表涉及更新,开启delta,不过在实时更新频繁时,还是搞不定,比如RDS服务实时导入数据的表,只能为行存表。CREATE TABLE tpcds.warehouse_t21(    W_WAREHOUSE_SK            INTEGER               NOT NULL)WITH (orientation=column, compression=low, colversion=2.0, enable_delta=on) DISTRIBUTE BY REPLICATION;2.4 行存表强制打开向量化(GUC控制台参数),执行计划走列存向量set enable_force_vector_engine=on-- 干预执行计划:best_agg_plan-- Stream执行框架分为如下三种计划形态:-- hashagg+gather(redistribute)+hashagg-- redistribute+hashagg(+gather)-- hashagg+redistribute+hashagg(+gather)-- GaussDB(DWS)提供了guc参数best_agg_plan来干预执行计划,强制其生成上述对应的执行计划,此参数取值范围为0,1,2,3-- •取值为1时,强制生成第一种计划。-- •取值为2时,如果group by列可以重分布,强制生成第二种计划,否则生成第一种计划。-- •取值为3时,如果group by列可以重分布,强制生成第三种计划,否则生成第一种计划。-- •取值为0时,优化器会根据以上三种计划的估算代价选择最优的一种计划生成。3.模糊匹配-- 建立全文索引create index index_name on table_name using gin(to_tsvector(col_name));select * from table_name where to_tsvector(col_name) @@ plainto_tsquery('某公司')----where            to_tsvector('zhparser',ef.remark ) @@ to_tsquery('%hiphi%')           --  ef.remark  like '%hiphi%'4.查询数据库大小(已用空间)select datname,pg_size_pretty(pg_database_size(datname)) from pg_database; 5.改写SQL5.1 not in -——> not exists6.query dop的输出信息含义Initial DOP: 7   -- 初始DOP  Avail(CPU/IO)/Max core: (7.92/8.00)/8.00        -- 语句可用CPU/IO/最大核数CPU/IO/Task util: 1.00/0.00/0                   -- 当前最大CPU/IO/DN作业个数Running/Active/Max statement: 160/0/21474836     --当前DN正在运行作业数/进入CN作业数/允许最大作业数(ma)Final Max DOP: 6   -- 最终DOP7.数据库float数据类型,小数点前面的0不显示oracle兼容的不显示小数点前的0,mysql兼容的显示;-- 创建兼容ORA格式的数据库:CREATE DATABASE ora_compatible_db DBCOMPATIBILITY 'ORA';-- DBCOMPATIBILITY [ = ] compatibilty_type;指定兼容的数据库的类型。取值范围:ORA、TD、MySQL。分别表示兼容Oracle、Teradata和MySQL数据库。若不指定该参数,默认为ORA。8.清空表:推荐使用truncate  操作,因为truncate操作会物理的清空数据表,并将其占用的空间归还给操作系统。
  • [技术干货] 这是啥SQL,室友看了人傻了[转载]
    文章目录SQLite适应常规基本应用场景SQLite面对复杂场景尚有不足SPL全面支持各种数据源SPL的计算能力更强大优化体系结构SPL资料可以在Java应用中嵌入的数据引擎看起来比较丰富,但其实并不容易选择。Redis计算能力很差,只适合简单查询的场景。Spark架构复杂沉重,部署维护很是麻烦。H2\HSQLDB\Derby等内嵌数据库倒是架构简单,但计算能力又不足,连基本的窗口函数都不支持。相比之下,SQLite在架构性和计算能力上取得了较好的平衡,是应用较广的Java嵌入数据引擎。SQLite适应常规基本应用场景SQLite架构简单,其核心虽然是C语言开发的,但封装得比较好,对外呈现为一个小巧的Jar包,能方便地集成在Java应用中。SQLite提供了JDBC接口,可以被Java调用:Connection connection = DriverManager.getConnection("jdbc:sqlite::memory:");Statement st = connection.createStatement();st.execute("restore from d:/ex1");ResultSet rs = st.executeQuery("SELECT * FROM orders");1234SQLite提供了标准的SQL语法,常规的数据处理和计算都没有问题。特别地,SQLite已经能支持窗口函数,可以方便地实现很多组内运算,计算能力比其他内嵌数据库更强。SELECT x, y, row_number() OVER (ORDER BY y) AS row_number FROM t0 ORDER BY x;SELECT a, b, group_concat(b, '.') OVER ( ORDER BY a ROWS BETWEEN 1 PRECEDING AND 1 FOLLOWING) AS group_concat FROM t1;12SQLite面对复杂场景尚有不足SQLite的优点亮眼,但对于复杂应用场景时还是有些缺点。Java应用可能处理的数据源多种多样,比如csv文件、RDB、Excel、Restful,但SQLite只处理了简单情况,即对csv等文本文件提供了直接可用的命令行加载程序:.import --csv --skip 1 --schema temp /Users/scudata/somedata.csv tab1对于其他大部分数据源,SQLite都没有提供方便的接口,只能硬写代码加载数据,需要多次调用命令行,整个过程很繁琐,时效性也差。以加载RDB数据源为例,一般的做法是先用Java执行命令行,把RDB库表转为csv;再用JDBC访问SQLite,创建表结构;之后用Java执行命令行,将csv文件导入SQLite;最后为新表建索引,以提高性能。这个方法比较死板,如果想灵活定义表结构和表名,或通过计算确定加载的数据,代码就更难写了。类似地,对于其他数据源,SQLite也不能直接加载,同样要通过繁琐地转换过程才可以。SQL接近自然语言,学习门槛低,容易实现简单的计算,但不擅长复杂的计算,比如复杂的集合计算、有序计算、关联计算、多步骤计算。SQLite采用SQL语句做计算,SQL优点和缺点都会继承下来,勉强实现这些复杂计算的话,代码会显得繁琐难懂。比如,某只股票最长的上涨天数,SQL要这样写:select max(continuousDays)-1from (select count(*) continuousDaysfrom (select sum(changeSign) over(order by tradeDate) unRiseDaysfrom (select tradeDate,case when price>lag(price) over(order by tradeDate) then 0 else 1 end changeSign from AAPL) )group by unRiseDays)123456这也不单是SQLite的难题,事实上,由于集合化不彻底、缺乏序号、缺乏对象引用等原因,其他SQL数据库也不擅长这些运算。业务逻辑由结构化数据计算和流程控制组成,SQLite支持SQL,具有结构化数据计算能力,但SQLite没有提供存储过程,不具备独立的流程控制能力,也就不能实现一般的业务逻辑,通常要利用Java主程序的判断和循环语句。由于Java没有专业的结构化数据对象来承载SQLite数据表和记录,转换过程麻烦,处理过程不畅,开发效率不高。前面提过,SQLite内核是C程序,虽然可以被集成到Java应用中,但并不能和Java无缝集成,和Java主程序交换数据时要经过耗时的转换才能完成,在涉及数据量较大或交互频繁时性能就会明显不足。同样因为内核是C程序,SQLite会在一定程度上破坏Java架构的一致性和健壮性。对于Java应用来讲,原生在JVM上的esProc SPL是更好的选择。SPL全面支持各种数据源esProc SPL是JVM下开源的嵌入数据引擎,架构简单,可直接加载数据源,可以通过JDBC接口被Java集成调用,并方便地进行后续计算。SPL架构简单,无须独立服务,只要引入SPL的Jar包,就可以部署在Java环境中。直接加载数据源,代码简短,过程简单,时效性强。比如加载Oracle:A1    =connect("orcl")2    =A1.query@x("select OrderID,Client,SellerID,OrderDate,Amount from orders order by OrderID")3    >env(orders,A2)对于SQLite擅长加载的csv文件,SPL也可以直接加载,使用内置函数而不是外部命令行,稳定且效率高,代码更简短:=T(“/Users/scudata/somedata.csv”)多种外部数据源。除了RDB和csv,SPL还直接支持txt\xls等文件,MongoDB、Hadoop、redis、ElasticSearch、Kafka、Cassandra等NoSQL,以及WebService XML、Restful Json等多层数据。比如,将HDSF里的文件加载到内存:A1    =hdfs_open(;"hdfs://192.168.0.8:9000")2    =hdfs_file(A1,"/user/Orders.csv":"GBK")3    =A2.cursor@t()4    =hdfs_close(A1)5    >env(orders,A4)    JDBC接口可以方便地集成。加载的数据量一般比较大,通常在应用的初始阶段运行一次,只须将上面的加载过程存为SPL脚本文件,在Java中以存储过程的形式引用脚本文件名:Class.forName("com.esproc.jdbc.InternalDriver");Connection conn =DriverManager.getConnection("jdbc:esproc:local://");CallableStatement statement = conn.prepareCall("{call init()}");statement.execute();1234SPL的计算能力更强大SPL提供了丰富的计算函数,可以轻松实现日常计算。SPL支持多种高级语法,大量的日期函数和字符串函数,很多用SQL难以表达的计算,用SPL都可以轻松实现,包括复杂的有序计算、集合计算、分步计算、关联计算,以及带流程控制的业务逻辑。丰富的计算函数。SPL可以轻松实现各类日常计算:A    B1    =Orders.find(arg_OrderIDList)    //多键值查找2    =Orders.select(Amount>1000 && like(Client,\"*S*\"))    //模糊查询3    = Orders.sort(Client,-Amount)    //排序4    = Orders.id(Client)    //去重5    =join(Orders:O,SellerId; Employees:E,EId).new(O.OrderID, O.Client,O.Amount,E.Name,E.Gender,E.Dept)    //关联标准SQL语法。SPL也提供了SQL-92标准的语法,比如分组汇总:$select year(OrderDate) y,month(OrderDate) m, sum(Amount) s,count(1) cfrom {Orders}Where Amount&gt;=? and Amount&lt;? ;arg1,arg2123函数选项、层次参数等方便的语法。功能相似的函数可以共用一个函数名,只用函数选项区分差别,比SQL更加灵活方便。比如select函数的基本功能是过滤,如果只过滤出符合条件的第1条记录,可使用选项@1:T.select@1(Amount>1000)二分法排序,即对有序数据用二分法进行快速过滤,使用@b:T.select@b(Amount>1000)有序分组,即对分组字段有序的数据,将相邻且字段值相同的记录分为一组,使用@b:T.groups@b(Client;sum(Amount))函数选项还可以组合搭配,比如:Orders.select@1b(Amount>1000)结构化运算函数的参数有些很复杂,比如SQL就需要用各种关键字把一条语句的参数分隔成多个组,但这会动用很多关键字,也使语句结构不统一。SPL使用层次参数简化了复杂参数的表达,即通过分号、逗号、冒号自高而低将参数分为三层:join(Orders:o,SellerId ; Employees:e,EId)更丰富的日期和字符串函数。除了常见函数,比如日期增减、截取字符串,SPL还提供了更丰富的日期和字符串函数,在数量和功能上远远超过了SQL,同样运算时代码更短。比如:季度增减:elapse@q(“2020-02-27”,-3) //返回2019-05-27N个工作日之后的日期:workday(date(“2022-01-01”),25) //返回2022-02-04字符串类函数,判断是否全为数字:isdigit(“12345”) //返回true取子串前面的字符串:substr@l(“abCDcdef”,“cd”) //返回abCD按竖线拆成字符串数组:“aa|bb|cc”.split(“|”) //返回[“aa”,“bb”,“cc”]SPL还支持年份增减、求季度、按正则表达式拆分字符串、拆出SQL的where或select部分、拆出单词、按标记拆HTML等大量函数。简化有序运算。涉及跨行的有序运算,通常都有一定的难度,比如比上期和同期比。SPL使用"字段[相对位置]"引用跨行的数据,可显著简化代码,还可以自动处理数组越界等特殊情况,比SQL窗口函数更加方便。比如,追加一个计算列rate,计算每条订单的金额增长率:=T.derive(AMOUNT/AMOUNT[-1]-1: rate)综合运用位置表达式和有序函数,很多SQL难以实现的有序运算,都可以用SPL轻松解决。比如,根据考勤表,找出连续 4 周每天均出勤达 7 小时的学生:A1    =Student.select(DURATION>=7).derive(pdate@w(ATTDATE):w)2    =A1.group@o(SID;~.groups@o(W;count(~):CNT).select(CNT==7).group@i(W-W[-1]!=7).max(~.len()):weeks)3    =A2.select(weeks>=4).(SID)简化集合运算,SPL的集合化更加彻底,配合灵活的语法和强大的集合函数,可大幅简化复杂的集合计算。比如,在各部门找出比本部门平均年龄小的员工:A1    =Employees.group(DEPT; (a=~.avg(age(BIRTHDAY)),~.select(age(BIRTHDAY)<a)):YOUNG)2    =A1.conj(YOUNG)计算某支股票最长的连续上涨天数:A1    =a=0,AAPL.max(a=if(price>price[-1],a+1,0))简化关联计算。SPL支持对象引用的形式表达关联,可以通过点号直观地访问关联表,避免使用JOIN导致的混乱繁琐,尤其适合复杂的多层关联和自关联。比如,根据员工表计算女经理的男员工:=employees.select(gender:“male”,dept.manager.gender:“female”)方便的分步计算,SPL集合化更加彻底,可以用变量方便地表达集合,适合多步骤计算,SQL要用嵌套表达的运算,用SPL可以更轻松实现。比如,找出销售额累计占到一半的前n个大客户,并按销售额从大到小排序:A    B2    =sales.sort(amount:-1)    /销售额逆序排序,可在SQL中完成3    =A2.cumulate(amount)    /计算累计序列4    =A3.m(-1)/2    /最后的累计即总额5    =A3.pselect(~>=A4)    /超过一半的位置6    =A2(to(A5))    /按位置取值流程控制语法。SPL提供了流程控制语句,配合内置的结构化数据对象,可以方便地实现各类业务逻辑。分支判断语句:A    B2    …    3    if T.AMOUNT>10000    =T.BONUS=T.AMOUNT*0.054    else if T.AMOUNT>=5000 && T.AMOUNT<10000    =T.BONUS=T.AMOUNT*0.035    else if T.AMOUNT>=2000 && T.AMOUNT<5000    =T.BONUS=T.AMOUNT*0.02循环语句:A    B1    =db=connect("db")    2    =T=db.query@x("select * from sales where SellerID=? order by OrderDate",9)3    for T    =A3.BONUS=A3.BONUS+A3.AMOUNT*0.014        =A3.CLIENT=CONCAT(LEFT(A3.CLIENT,4), " co.,ltd.")5         …与Java的循环类似,SPL还可用break关键字跳出(中断)当前循环体,或用next关键字跳过(忽略)本轮循环,不展开说了。计算性能更好。在内存计算方面,除了常规的主键和索引外,SPL还提供了很多高性能的数据结构和算法支持,比大多数使用SQL的内存数据库性能好得多,且占用内存更少,比如预关联技术、并行计算、指针式复用。优化体系结构SPL支持JDBC接口,代码可外置于Java,耦合性更低,也可内置于Java,调用更简单。SPL支持解释执行和热切换,代码方便移植和管理运营,支持内外存混合计算。外置代码耦合性低。SPL代码可外置于Java,通过文件名被调用,既不依赖数据库,也不依赖Java,业务逻辑和前端代码天然解耦。对于较短的计算,也可以像SQLite那样合并成一句,写在Java代码中:Class.forName("com.esproc.jdbc.InternalDriver");Connection conn =DriverManager.getConnection("jdbc:esproc:local://");Statement statement = conn.createStatement();String arg1="1000";String arg2="2000"ResultSet result = statement.executeQuery(=Orders.select(Amount>="+arg1+" && Amount<"+arg2+"). groups(year(OrderDate):y,month(OrderDate):m; sum(Amount):s,count(1):c)");123456解释执行和热切换。业务逻辑数量多,复杂度高,变化是常态。良好的系统构架,应该有能力应对变化的业务逻辑。SPL是基于Java的解释型语言,无须编译就能执行,脚本修改后立即生效,支持不停机的热切换,适合应对变化的业务逻辑。方便代码移植。SPL通过数据源名从数据库取数,如果需要移植,只要改动配置文件中的数据源配置信息,而不必修改SPL代码。SPL支持动态数据源,可通过参数或宏切换不同的数据库,从而进行更方便的移植。为了进一步增强可移植性,SPL还提供了与具体数据库无关的标准SQL语法,使用sqltranslate函数可将标准SQL转为主流方言SQL,仍然通过query函数执行。方便管理运营。由于支持库外计算,代码可被第三方工具管理,方便团队协作;SPL脚本可以按文件目录进行存放,方便灵活,管理成本低;SPL对数据库的权限要求类似Java,不影响数据安全。内外存混合计算。有些数据太大,无法放入内存,但又要与内存表共同计算,这种情况可利用SPL实现内外存混合计算。比如,主表orders已加载到内存,大明细表orderdetail是文本文件,下面进行主表和明细表的关联计算:A1    =file("orderdetail.txt").cursor@t()2    =orders.cursor()3    =join(A1:detail,orderid ; A2:main,orderid)4    =A3.groups(year(main.orderdate):y; sum(detail.amount):s)SQLite使用简单方便,但数据源加载繁琐,计算能力不足。SPL架构也非常简单,并直接支持更多数据源。SPL计算能力强大,提供了丰富的计算函数,可以轻松实现SQL不擅长的复杂计算。SPL还提供多种优化体系结构的手段,代码既可外置也可内置于Java,支持解释执行和热切换,方便移植和管理运营,并支持内外存混合计算。原文链接:https://blog.csdn.net/m0_60264772/article/details/125273709
  • [问题求助] 【ABC产品】【SQL功能】执行SQL超时
    【功能模块】请问一下  SQL超时是SQL查询有什么限制吗按页数条数返回的呀【操作步骤&问题现象】1、2、【截图信息】【日志信息】(可选,上传日志内容或者附件)
  • [技术干货] openGauss内核分析(四):查询重写
    查询重写SQL语言是丰富多样的,非常的灵活,不同的开发人员依据经验的不同,手写的SQL语句也是各式各样,另外还可以通过工具自动生成。SQL语言是一种描述性语言,数据库的使用者只是描述了想要的结果,而不关心数据的具体获取方式,输入数据库的SQL语言很难做到是以最优形式表示的,往往隐含了一些冗余信息,这些信息可以被挖掘用来生成更加高效的SQL语句。查询重写就是把用户输入的SQL语句转换为更高效的等价SQL,查询重写遵循两个基本原则。• 等价性:原语句和重写后的语句,输出结果相同。• 高效性:重写后的语句,比原语句在执行时间和资源使用上更高效。查询重写优化既可以基于关系代数的理论进行优化,例如谓词下推、子查询优化等,也可以基于启发式规则进行优化,例如Outer Join消除、表连接消除等。查询重写是基于规则的逻辑优化。在代码层面,查询重写的架构如下:下面以外连接消除Outer2Inner—外连接转内连接为例分析查询重写过程:在left outer join或者right outer join中,如果查询条件中存在逻辑上能够包含IS NOT NULL,例如c1 > 0,可以将查询转换成INNER JOIN,从而减少关联处理产生的中间结果集外连接消除Outer2Inner下面首先以一个例子来说明各种多表连接方式的区别create table t1(c1 int, c2 int); create table t2(c1 int, c2 int); insert into t1 values(1, 10); insert into t1 values(2, 20); insert into t1 values(3, 30); insert into t2 values(1, 100); insert into t2 values(3, 300); insert into t2 values(5, 500);内连接inner join:返回两个表都满足的组合,相当于取两个表的交集SELECT * FROM t1 inner JOIN t2 ON t1.c1 = t2.c1;左连接 left outer join:返回左表中的所有行,如果左表中行在右表中没有匹配行,则结果中右表中的列返回空值SELECT * FROM t1 Left OUTER JOIN t2 ON t1.c1 = t2.c1;右连接 right outer join:返回右表中的所有行,如果右表中行在左表中没有匹配行,则结果中左表中的列返回空值SELECT * FROM t1 right OUTER JOIN t2 ON t1.c1 = t2.c1;全连接 full join:返回左表和右表中的所有行。当某行在另一表中没有匹配行,则另一表中的列返回空值,相当于取两个表并集SELECT * FROM t1 full JOIN t2 ON t1.c1 = t2.c1;在以上实验的基础上增加t2表的where条件left join和inner join的结果是一样的,这是因为查询条件中包含WHERE t2.c2 >100这个条件,t2表所有不匹配元组均被过滤掉(包括空值),因此可以进行查询转换left-outer join -> inner join,能够有效减小t1和t2关联产生的结果集,达到性能提升的目的。在openGauss数据库系统中,subquery_planner会遍历查询树中的rtable,看看是否有RTE_JOIN类型的节点存在,设置hasOuterJoins标志量,从而进入到reduce_outer_joins接口,满足外连接消除条件时再执行外连接的消除。 reduce_outer_Joins函数内部做两个动作,(1)reduce_outer_joins_pass1预检查,就是检查jointree中是否含有外链接,以及一些引用表的信息,为动作2做好信息采集准备,重点参考数据结构reduce_outer_joins_state;(2)reduce_outer_joins_pass2真正完成消除外链接。void reduce_outer_joins(PlannerInfo* root) { reduce_outer_joins_state* state = NULL; state = reduce_outer_joins_pass1((Node*)root->parse->jointree); /* planner.c shouldn't have called me if no outer joins */ if (state == NULL || !state->contains_outer) ereport(ERROR, (errmodule(MOD_OPT), errcode(ERRCODE_OPTIMIZER_INCONSISTENT_STATE), (errmsg("so where are the outer joins?")))); reduce_outer_joins_pass2((Node*)root->parse->jointree, state, root, NULL, NIL, NIL); }利用上一期的分析方法,可以得到查询树内存结构(查询树Query结构体中targetList存储目标属性语义分析结果,rtable存储FROM子句生成的范围表,jointree的quals字段存储WHERE子句语义分析的表达式树)对比reduce_outer_joins运行前后查询树,jointree和rtable中的jointype都由join_left转换为join_inner,即外连接已转为内连接(gdb) p *((JoinExpr*)(parse->jointree->fromlist->head.data->ptr_value)) $1 = {type = T_JoinExpr, jointype = JOIN_INNER, isNatural = false, larg = 0x7fdfb345cd08, rarg = 0x7fdfb345e2e8, usingClause = 0x0, quals = 0x7fdfb2f0b8a8, alias = 0x0, rtindex = 3} (gdb) p *(RangeTblEntry*)(parse->rtable->tail.data->ptr_value) $2 = {type = T_RangeTblEntry, rtekind = RTE_JOIN, relname = 0x0, partAttrNum = 0x0, relid = 0, partitionOid = 0, isContainPartition = false, subpartitionOid = 0, isContainSubPartition = false, refSynOid = 0, partid_list = 0x0, relkind = 0 '\000', isResultRel = false, tablesample = 0x0, timecapsule = 0x0, ispartrel = false, ignoreResetRelid = false, subquery = 0x0, security_barrier = false, jointype = JOIN_INNER, …}作者:酷哥
  • [技术干货] 智慧园区数据平台DO GaussDB 100和GaussDB 200的差异点(上)
    Cube环境使用的是GaussDB 100数据库,公有云环境使用的是GaussDB 200数据库。两者脚本开发时差异点如下:大小写区分GaussDB 100数据库:严格区分大小写,不加引号默认是大写。加引号后,引号里面的内容格式不变。GaussDB 200数据库:表名、字段无论加不加引号,都是创建的小写的表名和字段名。所以GaussDB 100数据库脚本开发规范:在GaussDB 100数据库中,建表语句ddl的表名、schema名和字段名不要加引号。表1 数据库ddl示例GaussDB 100数据库ddl示例GaussDB 200数据库ddl示例CREATE TABLE IF NOT EXISTS dm_asset.dm_asset_dept_distribution_f ( id serial, dw_creation_by character varying(100), dw_creation_date timestamp without time zone, dw_last_update_by character varying(100), dw_last_update_date timestamp without time zone, dw_batch_number bigint ) WITH (orientation=row, compression=no) DISTRIBUTE BY HASH (id); COMMENT ON TABLE dm_asset.dm_asset_dept_distribution_f IS '部门资产归总表(Table of department assets summarized)'; COMMENT ON COLUMN dm_asset.dm_asset_dept_distribution_f.id IS 'id'; COMMENT ON COLUMN dm_asset.dm_asset_dept_distribution_f.dw_creation_by IS '数据创建者(The creator)'; COMMENT ON COLUMN dm_asset.dm_asset_dept_distribution_f.dw_creation_date IS '数据创建时间(Creation date)'; COMMENT ON COLUMN dm_asset.dm_asset_dept_distribution_f.dw_last_update_by IS '数据最后更新者(Last updater)'; COMMENT ON COLUMN dm_asset.dm_asset_dept_distribution_f.dw_last_update_date IS '最后更新时间(Last update date)'; COMMENT ON COLUMN dm_asset.dm_asset_dept_distribution_f.dw_batch_number IS '批次号(Data batch number)';CREATE TABLE IF NOT EXISTS "dm_asset"."dm_asset_dept_distribution_f" ( id serial, dw_creation_by character varying(100), dw_creation_date timestamp without time zone, dw_last_update_by character varying(100), dw_last_update_date timestamp without time zone, dw_batch_number bigint ) WITH (orientation=row, compression=no) DISTRIBUTE BY HASH (id); COMMENT ON TABLE "dm_asset"."dm_asset_dept_distribution_f" IS '部门资产归总表(Table of department assets summarized)'; COMMENT ON COLUMN "dm_asset"."dm_asset_dept_distribution_f".id IS 'id'; COMMENT ON COLUMN "dm_asset"."dm_asset_dept_distribution_f".dw_creation_by IS '数据创建者(The creator)'; COMMENT ON COLUMN "dm_asset"."dm_asset_dept_distribution_f".dw_creation_date IS '数据创建时间(Creation date)'; COMMENT ON COLUMN "dm_asset"."dm_asset_dept_distribution_f".dw_last_update_by IS '数据最后更新者(Last updater)'; COMMENT ON COLUMN "dm_asset"."dm_asset_dept_distribution_f".dw_last_update_date IS '最后更新时间(Last update date)'; COMMENT ON COLUMN "dm_asset"."dm_asset_dept_distribution_f".dw_batch_number IS '批次号(Data batch number)';在GaussDB 100数据库中,DGC的脚本SQL涉及到的表名、字段名不需要加引号。表2 DGC的SQL示例GaussDB 100数据库DGC的SQL示例GaussDB 200数据库DGC的SQL示例--查询表 select * from dm_asset.dm_asset_dept_distribution_f; --资产基本信息视图 create or replace view dm_asset.logic_depts_view as( ... ); --删除视图 drop view dm_asset.logic_depts_view;--查询表 select * from dm_asset."dm_asset_dept_distribution_f"; --资产基本信息视图 create or replace view dm_asset."logic_depts_view" as( ... ); --删除视图 drop view dm_asset."logic_depts_view";在GaussDB 100数据库中,自定义函数或者方法里函数名称、方法名称和涉及到的表名等(例如:end;$$language plpgsql;),需要写成没有引号的。表3 自定义函数或者方法示例GaussDB 100数据库自定义函数或者方法示例GaussDB 200数据库自定义函数或者方法示例--创建自定义函数 create or replace function dm_asset.func_find_dept(current_id text) returns text[][] as $$ declare cursor_id text; ... begin select org_id,org_name,parent_code from dwr_dim.dim_org_base_d where org_id = current_id into cursor_id; ... end; $$ language plpgsql--创建自定义函数 create or replace function dm_asset."func_find_dept"(current_id text) returns text[][] as $$ declare cursor_id text; ... begin select org_id,org_name,parent_code from dwr_dim."dim_org_base_d" where org_id = current_id into cursor_id; ... end; $$ language 'plpgsql'在GaussDB 100数据库中,由于DWS默认是小写,所以在ROMA里面的API接口返回必须进行重命名,使得从GaussDB 100和DWS取出的数据字段是一致的。表4 ROMA接口的SQL示例GaussDB 100数据库ROMA接口的SQL示例GaussDB 200数据库ROMA接口的SQL示例SELECT TOP_TYPE_ID as "top_type_id", TOP_TYPE_CN as "top_type_name", ASSET_COUNT as "asset_count", ASSET_COST as "asset_cost", ... FROM dm_asset.dm_asset_distribution_f WHERE summary_code = '2';SELECT TOP_TYPE_ID, TOP_TYPE_CN, ASSET_COUNT, ASSET_COST, ... FROM dm_asset."dm_asset_distribution_f" WHERE summary_code = '2';
  • [交流吐槽] 彻底根除MySQL慢查询,这12个问题都不能落下
    前言日常开发中,我们经常会遇到数据库慢查询。那么导致数据慢查询都有哪些常见的原因呢?今天田螺哥就跟大家聊聊导致MySQL慢查询的12个常见原因,以及对应的解决方法。一、SQL没加索引1、反例select * from user_info where name ='dbaplus社群' ;2、正例//添加索引 alter table user_info add index idx_name (name);二、SQL 索引不生效有时候我们明明加了索引了,但是索引却不生效。在哪些场景,索引会不生效呢?主要有以下十大经典场景:1、隐式的类型转换,索引失效我们创建一个用户user表。CREATE TABLE user ( id int(11) NOT NULL AUTO_INCREMENT, userId varchar(32) NOT NULL, age varchar(16) NOT NULL, name varchar(255) NOT NULL, PRIMARY KEY (id), KEY idx_userid (userId) USING BTREE ) ENGINE=InnoDB DEFAULT CHARSET=utf8;userId字段为字串类型,是B+树的普通索引,如果查询条件传了一个数字过去,会导致索引失效。如下:如果给数字加上'',也就是说,传的是一个字符串呢,当然是走索引,如下图:为什么第一条语句未加单引号就不走索引了呢?这是因为不加单引号时,是字符串跟数字的比较,它们类型不匹配,MySQL会做隐式的类型转换,把它们转换为浮点数再做比较。隐式的类型转换,索引会失效。2、查询条件包含or,可能导致索引失效我们还是用这个表结构:CREATE TABLE user ( id int(11) NOT NULL AUTO_INCREMENT, userId varchar(32) NOT NULL, age varchar(16) NOT NULL, name varchar(255) NOT NULL, PRIMARY KEY (id), KEY idx_userid (userId) USING BTREE ) ENGINE=InnoDB DEFAULT CHARSET=utf8;其中userId加了索引,但是age没有加索引的。我们使用了or,以下SQL是不走索引的,如下:对于or+没有索引的age这种情况,假设它走了userId的索引,但是走到age查询条件时,它还得全表扫描,也就是需要三步过程:全表扫描+索引扫描+合并。如果它一开始就走全表扫描,直接一遍扫描就完事。Mysql优化器出于效率与成本考虑,遇到or条件,让索引失效,看起来也合情合理嘛。注意:如果or条件的列都加了索引,索引可能会走也可能不走,大家可以自己试一试哈。但是平时大家使用的时候,还是要注意一下这个or,学会用explain分析。遇到不走索引的时候,考虑拆开两条SQL。3、like通配符可能导致索引失效并不是用了like通配符,索引一定会失效,而是like查询是以%开头,才会导致索引失效。like查询以%开头,索引失效。explain select * from user where userId like '%123';把%放后面,发现索引还是正常走的,如下:既然like查询以%开头,会导致索引失效。我们如何优化呢?使用覆盖索把%放后面4、查询条件不满足联合索引的最左匹配原则MySQl建立联合索引时,会遵循最左前缀匹配的原则,即最左优先。如果你建立一个(a,b,c)的联合索引,相当于建立了(a)、(a,b)、(a,b,c)三个索引。假设有以下表结构:CREATE TABLE user ( id int(11) NOT NULL AUTO_INCREMENT, user_id varchar(32) NOT NULL, age varchar(16) NOT NULL, name varchar(255) NOT NULL, PRIMARY KEY (id), KEY idx_userid_name (user_id,name) USING BTREE ) ENGINE=InnoDB DEFAULT CHARSET=utf8;有一个联合索引idx_userid_name,我们执行这个SQL,查询条件是name,索引是无效:explain select * from user where name ='dbaplus社群';因为查询条件列name不是联合索引idx_userid_name中的第一个列,索引不生效在联合索引中,查询条件满足最左匹配原则时,索引才正常生效。5、在索引列上使用mysql的内置函数表结构:CREATE TABLE `user` ( `id` int(11) NOT NULL AUTO_INCREMENT, `userId` varchar(32) NOT NULL, `login_time` datetime NOT NULL, PRIMARY KEY (`id`), KEY `idx_userId` (`userId`) USING BTREE, KEY `idx_login_time` (`login_Time`) USING BTREE ) ENGINE=InnoDB AUTO_INCREMENT=2 DEFAULT CHARSET=utf8;虽然login_time加了索引,但是因为使用了mysql的内置函数Date_ADD(),索引直接GG,如图:一般这种情况怎么优化呢?可以把内置函数的逻辑转移到右边,如下:6、对索引进行列运算(如,+、-、*、/),索引不生效表结构:CREATE TABLE `user` ( `id` int(11) NOT NULL AUTO_INCREMENT, `userId` varchar(32) NOT NULL, `age` int(11) DEFAULT NULL, PRIMARY KEY (`id`), KEY `idx_age` (`age`) USING BTREE ) ENGINE=InnoDB AUTO_INCREMENT=2 DEFAULT CHARSET=utf8;虽然age加了索引,但是因为它进行运算,索引直接迷路了。如图:所以不可以对索引列进行运算,可以在代码处理好,再传参进去。
  • [交流吐槽] 几种常用关系型数据库介绍
    数据库管理系统是用于创建,维护与管理数据库的系统软件,是搭建其他应用环境所必备的软件之一,是软件系统架构的重要组成部分。对于IT人员,不论是开发还是测试人员都是其必须掌握的软件。对于开发可以说是他们吃饭的家伙,对于测试人员可以说是测试利器。目前,商品化的数据库管理系统以关系型数据库为主导产品,技术比较成熟。面向对象的数据库管理系统虽然技术先进,数据库易于开发、维护,但尚未有成熟的产品。今天我们就专门来聊一聊常见的关系型数据库管理系统都有哪些,各自有什么特点。一、MySQLMySQL是最受欢迎的开源SQL数据库管理系统,它由 MySQL AB开发、发布和支持。MySQL AB是一家基于MySQL开发人员的商业公司,它是一家使用了一种成功的商业模式来结合开源价值和方法论的第二代开源公司。MySQL是MySQL AB的注册商标。MySQL是一个快速的、多线程、多用户和健壮的SQL数据库服务器。MySQL服务器支持关键任务、重负载生产系统的使用,也可以将它嵌入到一个大配置(mass- deployed)的软件中去。与其他数据库管理系统相比,MySQL具有以下优势:(1)MySQL是一个关系数据库管理系统。(2)MySQL是开源的。(3)MySQL服务器是一个快速的、可靠的和易于使用的数据库服务器。(4)MySQL服务器工作在客户/服务器或嵌入系统中。(5)有大量的MySQL软件可以使用。二、SQL ServerSQL Server是由微软开发的数据库管理系统,是Web上最流行的用于存储数据的数据库,它已广泛用于电子商务、银行、保险、电力等与数据库有关的行业。目前最新版本是SQL Server 2005,它只能在Windows上运行,操作系统的系统稳定性对数据库十分重要。并行实施和共存模型并不成熟,很难处理日益增多的用户数和数据卷,伸缩性有限。SQL Server 提供了众多的Web和电子商务功能,如对XML和Internet标准的丰富支持,通过Web对数据进行轻松安全的访问,具有强大的、灵活的、基于Web的和安全的应用程序管理等。而且,由于其易操作性及其友好的操作界面,深受广大用户的喜爱。三、Oracle提起数据库,第一个想到的公司,一般都会是Oracle(甲骨文)。该公司成立于1977年,最初是一家专门开发数据库的公司。Oracle在数据库领域一直处于领先地位。 1984年,首先将关系数据库转到了桌面计算机上。然后,Oracle5率先推出了分布式数据库、客户/服务器结构等崭新的概念。Oracle 6首创行锁定模式以及对称多处理计算机的支持……最新的Oracle 8主要增加了对象技术,成为关系—对象数据库系统。目前,Oracle产品覆盖了大、中、小型机等几十种机型,Oracle数据库成为世界上使用最广泛的关系数据系统之一。Oracle数据库产品具有以下优良特性:(1)兼容性:Oracle产品采用标准SQL,并经过美国国家标准技术所(NIST)测试。与IBM SQL/DS、DB2、INGRES、IDMS/R等兼容。(2)可移植性:Oracle的产品可运行于很宽范围的硬件与操作系统平台上。可以安装在70种以上不同的大、中、小型机上;可在VMS、DOS、UNIX、Windows等多种操作系统下工作。(3)可联结性:Oracle能与多种通讯网络相连,支持各种协议(TCP/IP、DECnet、LU6.2等)。(4)高生产率:Oracle产品提供了多种开发工具,能极大地方便用户进行进一步的开发。(5)开放性;Oracle良好的兼容性、可移植性、可连接性和高生产率使Oracle RDBMS具有良好的开放性。四、Sybase1984年,Mark B. Hiffman和Robert Epstern创建了Sybase公司,并在1987年推出了Sybase数据库产品。Sybase主要有三种版本:一是UNIX操作系统下运行的版本; 二是Novell Netware环境下运行的版本;三是Windows NT环境下运行的版本。对UNIX操作系统,目前应用最广泛的是SYBASE 10及SYABSE 11 for SCO UNIX。Sybase数据库的特点:(1)它是基于客户/服务器体系结构的数据库。(2)它是真正开放的数据库。(3)它是一种高性能的数据库。五、DB2DB2是内嵌于IBM的AS/400系统上的数据库管理系统,直接由硬件支持。它支持标准的SQL语言,具有与异种数据库相连的GATEWAY。因此它具有速度快、可靠性好的优点。但是,只有硬件平台选择了IBM的AS/400,才能选择使用DB2数据库管理系统。DB2能在所有主流平台上运行(包括Windows),最适于海量数据。DB2在企业级的应用最为广泛,在全球的500家最大的企业中,几乎85%以上都用DB2数据库服务器,而国内到1997年约占5%。除此之外,还有微软的 Access数据库、FoxPro数据库等。既然现在有这么多的数据库系统,那么在游戏编程时应该选择什么样的数据库呢?首要的原则就是根据实际需要,另一方面还要考虑游戏开发预算。现在常用的数据库有:SQL Server、My SQL、Oracle、FoxPro。其中MySQL是一个完全免费的数据库系统,其功能也具备了标准数据库的功能,因此,在独立制作时,建议使用。 Oracle虽然功能强劲,但它毕竟是为商业用途而存在的,目前很少在游戏中使用到。
  • [知识分享] 10个常见触发IO瓶颈的高频业务场景
    >摘要:本文从应用业务优化角度,以常见触发IO慢的业务SQL场景为例,指导如何通过优化业务去提升IO效率和降低IO。 本文分享自华为云社区《[GaussDB(DWS)性能优化之业务降IO优化](https://bbs.huaweicloud.com/blogs/351796?utm_source=csdn&utm_medium=bbs-ex&utm_campaign=other&utm_content=content)》,作者:along_2020。 IO高?业务慢?在DWS实际业务场景中因IO高、IO瓶颈导致的性能问题非常多,其中应用业务设计不合理导致的问题占大多数。本文从应用业务优化角度,以常见触发IO慢的业务SQL场景为例,指导如何通过优化业务去提升IO效率和降低IO。 说明 :因磁盘故障(如慢盘)、raid卡读写策略(如Write Through)、集群主备不均等非应用业务原因导致的IO高不在本次讨论。 # 一、确定IO瓶颈&识别高IO的语句 ## 1、查等待视图确定IO瓶颈 ``` SELECT wait_status,wait_event,count(*) AS cnt FROM pgxc_thread_wait_status WHERE wait_status 'wait cmd' AND wait_status 'synchronize quit' AND wait_status 'none' GROUP BY 1,2 ORDER BY 3 DESC limit 50; ``` IO瓶颈时常见等待状态如下: ![image.png](https://bbs-img.huaweicloud.com/data/forums/attachment/forum/20226/10/1654835298790314932.png) ## 2、抓取高IO消耗的SQL 主要思路为先通过OS命令识别消耗高的线程,然后结合DWS的线程号信息找到消耗高的业务SQL,具体方法参见附件中iowatcher.py脚本和README使用介绍 ## 3、SQL级IO问题分析基础 在抓取到消耗IO高的业务SQL后怎么分析?主要掌握以下两点基础知识: 1)PGXC_THREAD_WAIT_STATUS视图功能,详细介绍参见: [PGXC_THREAD_WAIT_STATUS_数据仓库服务 GaussDB(DWS)_开发指南(8.1.0)_系统表和系统视图_系统视图_华为云](https://support.huaweicloud.com/devg2-dws/dws_0402_0892.html) 2)EXPLAIN功能,至少需掌握的知识点有Scan算子、A-time、A-rows、E- rows,详细介绍参见: [GaussDB(DWS)性能调优系列基础篇二:大道至简explain分布式计划-云社区-华为云](https://bbs.huaweicloud.com/blogs/197945) # 二、常见触发IO瓶颈的高频业务场景 ## 场景1:列存小CU膨胀 某业务SQL查询出390871条数据需43248ms,分析计划主要耗时在Cstore Scan ![image.png](https://bbs-img.huaweicloud.com/data/forums/attachment/forum/20226/10/1654835367872429035.png) Cstore Scan的详细信息中,每个DN扫描出2w左右的数据,但是扫描了有数据的CU(CUSome) 155079个,没有数据的CU(CUNone) 156375个,说明当前小CU、未命中数据的CU极多,也即CU膨胀严重。 ![image.png](https://bbs-img.huaweicloud.com/data/forums/attachment/forum/20226/10/1654835376385705944.png) 触发因素:对列存表(分区表尤甚)进行高频小批量导入会造成CU膨胀 处理方法: 1、列存表的数据入库方式修改为攒批入库,单分区单批次入库数据量大于DN个数*6W为宜 2、如果确因业务原因无法攒批,则考虑次选方案,定期VACUUM FULL此类高频小批量导入的列存表。 3、当小CU膨胀很快时,频繁VACUUM FULL也会消耗大量IO,甚至加剧整个系统的IO瓶颈,这时需考虑整改为行存表(CU长期膨胀严重的情况下,列存的存储空间优势和顺序扫描性能优势将不复存在)。 ## 场景2:脏数据&数据清理 某SQL总执行时间2.519s,其中Scan占了2.516s,同时该表的扫描最终只扫描到0条符合条件数据,过滤了20480条数据,也即总共扫描了20480+0条数据却消耗了2s+,这种扫描时间与扫描数据量严重不符的情况,基本就是脏数据多影响扫描和IO效率。 ![image.png](https://bbs-img.huaweicloud.com/data/forums/attachment/forum/20226/10/1654835394320723349.png) 查看表脏页率为99%,Vacuum Full后性能优化到100ms左右 ![image.png](https://bbs-img.huaweicloud.com/data/forums/attachment/forum/20226/10/1654835401320889032.png) 触发因素:表频繁执行update/delete导致脏数据过多,且长时间未VACUUM FULL清理 处理方法: - 对频繁update/delete产生脏数据的表,定期VACUUM FULL,因大表的VACUUM FULL也会消耗大量IO,因此需要在业务低峰时执行,避免加剧业务高峰期IO压力。 - 当脏数据产生很快,频繁VACUUM FULL也会消耗大量IO,甚至加剧整个系统的IO瓶颈,这时需要考虑脏数据的产生是否合理。针对频繁delete的场景,可以考虑如下方案:1)全量delete修改为truncate或者使用临时表替代 2)定期delete某时间段数据,设计成分区表并使用truncate&drop分区替代 ## 场景3:表存储倾斜 例如表Scan的A-time中,max time dn执行耗时6554ms,min time dn耗时0s,dn之间扫描差异超过10倍以上,这种集合Scan的详细信息,基本可以确定为表存储倾斜导致 ![image.png](https://bbs-img.huaweicloud.com/data/forums/attachment/forum/20226/10/1654835422845773048.png) 通过table_distribution发现所有数据倾斜到了dn_6009单个dn,修改分布列使的表存储分布均匀后,max dn time和min dn time基本维持在相同水平400ms左右,Scan时间从6554ms优化到431ms。 ![image.png](https://bbs-img.huaweicloud.com/data/forums/attachment/forum/20226/10/1654835431228454951.png) 触发因素:分布式场景,表分布列选择不合理会导致存储倾斜,同时导致DN间压力失衡,单DN IO压力大,整体IO效率下降。 解决办法:修改表的分布列使表的存储分布均匀,分布列选择原则参《GaussDB 8.x.x 产品文档》中“表设计最佳实践”之“选择分布列章节”。 ## 场景4:无索引、有索引不走 例如某点查询,Seq Scan扫描需要3767ms,因涉及从4096000条数据中获取8240条数据,符合索引扫描的场景(海量数据中寻找少量数据),在对过滤条件列增加索引后,计划依然是Seq Scan而没有走Index Scan。 ![image.png](https://bbs-img.huaweicloud.com/data/forums/attachment/forum/20226/10/1654835445289839263.png) 对目标表analyze后,计划能够自动选择索引,性能从3s+优化到2ms+,极大降低IO消耗 ![image.png](https://bbs-img.huaweicloud.com/data/forums/attachment/forum/20226/10/1654835451747933783.png) 常见场景:行存大表的查询场景,从大量数据中访问极少数据,没走索引扫描而是走顺序扫描,导致IO效率低,不走索引常见有两种情况: - 过滤条件列上没建索引 - 有索引但是计划没选索引扫描 触发因素: - 常用过滤条件列没有建索引 - 表中数据因DML产生数据特征变化后未及时ANALYZE导致优化器无法选择索引扫描计划,ANALYZE介绍[参见GaussDB(DWS)性能调优系列基础篇一:万物之始analyze统计信息-云社区-华为云](https://bbs.huaweicloud.com/blogs/192029) 处理方式: 1、对行存表常用过滤列增加索引,索引基本设计原则: - 索引列选择distinct值多,且常用于过滤条件,过滤条件多时可以考虑建组合索引,组合索引中distinct值多的列排在前面,索引个数不宜超过3个 - 大量数据带索引导入会产生大量IO,如果该表涉及大量数据导入,需严格控制索引个数,建议导入前先将索引删除,导数完毕后再重新建索引; 2、对频繁做DML操作的表,业务中加入及时ANALYZE,主要场景: - 表数据从无到有 - 表频繁进行INSERT/UPDATE/DELETE - 表数据即插即用,需要立即访问且只访问刚插入的数据 ## 场景5:无分区、有分区不剪枝 例如某业务表进场使用createtime时间列作为过滤条件获取特定时间数据,对该表设计为分区表后没有走分区剪枝(Selected Partitions数量多),Scan花了701785ms,IO效率极低。 ![image.png](https://bbs-img.huaweicloud.com/data/forums/attachment/forum/20226/10/1654835521707639495.png) 在增加分区键creattime作为过滤条件后,Partitioned scan走分区剪枝(Selected Partitions数量极少),性能从700s优化到10s,IO效率极大提升。 ![image.png](https://bbs-img.huaweicloud.com/data/forums/attachment/forum/20226/10/1654835529685175555.png) 常见场景:按照时间存储数据的大表,查询特征大多为访问当天或者某几天的数据,这种情况应该通过分区键进行分区剪枝(只扫描对应少量分区)来极大提升IO效率,不走分区剪枝常见的情况有: - 未设计成分区表 - 设计了分区没使用分区键做过滤条件 - 分区键做过滤条件时,对列值有函数转换 触发因素:未合理使用分区表和分区剪枝功能,导致扫描效率低 处理方式: - 对按照时间特征存储和访问的大表设计成分区表 - 分区键一般选离散度高、常用于查询filter条件中的时间类型的字段 - 分区间隔一般参考高频的查询所使用的间隔,需要注意的是针对列存表,分区间隔过小(例如按小时)可能会导致小文件过多的问题,一般建议最小间隔为按天。 ## 场景6:行存表求count值 例如某行存大表频繁全表count(指不带filter条件或者filter条件过滤很少数据的count),其中Scan花费43s,持续占用大量IO,此类作业并发起来后,整体系统IO持续100%,触发IO瓶颈,导致整体性能慢。 ![image.png](https://bbs-img.huaweicloud.com/data/forums/attachment/forum/20226/10/1654835559536792396.png) 对比相同数据量的列存表(A-rows均为40960000),列存的Scan只花费14ms,IO占用极低 ![image.png](https://bbs-img.huaweicloud.com/data/forums/attachment/forum/20226/10/1654835566265451401.png) 触发因素:行存表因其存储方式的原因,全表scan的效率较低,频繁的大表全表扫描,导致IO持续占用。 解决办法: - 业务侧审视频繁全表count的必要性,降低全表count的频率和并发度 - 如果业务类型符合列存表,则将行存表修改为列存表,提高IO效率 ## 场景7:行存表求max值 例如求某行存表某列的max值,花费了26772ms,此类作业并发起来后,整体系统IO持续100%,触发IO瓶颈,导致整体性能慢。 ![image.png](https://bbs-img.huaweicloud.com/data/forums/attachment/forum/20226/10/1654835584403273527.png) 针对max列增加索引后,语句耗时从26s优化到32ms,极大减少IO消耗 ![image.png](https://bbs-img.huaweicloud.com/data/forums/attachment/forum/20226/10/1654835590660680610.png) 触发因素:行存表max值逐个scan符合条件的值来计算max,当scan的数据量很大时,会持续消耗IO 解决办法:给max列增加索引,依靠btree索引天然有序的特征,加速扫描过程,降低IO消耗。 ## 场景8:大量数据带索引导入 某客户场景数据往DWS同步时,延迟严重,集群整体IO压力大。 ![image.png](https://bbs-img.huaweicloud.com/data/forums/attachment/forum/20226/10/1654835610449230701.png) 后台查看等待视图有大量wait wal sync和WALWriteLock状态,均为xlog同步状态 ![image.png](https://bbs-img.huaweicloud.com/data/forums/attachment/forum/20226/10/1654835618475829770.png) 触发因素:大量数据带索引(一般超过3个)导入(insert/copy/merge into)会产生大量xlog,导致主备同步慢,备机长期Catchup,整体IO利用率飙高。历史案例参考:[GaussDB(DWS)实例长期处于catchup问题分析-云社区-华为云](https://bbs.huaweicloud.com/blogs/242269) 解决方案: - 严格控制每张表的索引个数,建议3个以内 - 大量数据导入前先将索引删除,导数完毕后再重新建索引; ## 场景9:行存大表首次查询 某客户场景出现备DN持续Catcup,IO压力大,观察某个sql等待视图在wait wal sync ![image.png](https://bbs-img.huaweicloud.com/data/forums/attachment/forum/20226/10/1654835654821710446.png) 排查业务发现某查询语句执行时间较长,kill后恢复 触发因素:行存表大量数据入库后,首次查询触发page hint产生大量XLOG,触发主备同步慢及大量IO消耗。 解决措施: - 对该类一次性访问大量新数据的场景,修改为列存表 - 关闭wal_log_hints和enable_crc_check参数(故障期间有丢数风险,不推荐) ## 场景10:小文件多IOPS高 某业务现场一批业务起来后,整个集群IOPS飙高,另外当出现集群故障后,长期building不完,IOPS飙高,相关表信息如下: ``` SELECT relname,reloptions,partcount FROM pg_class c INNER JOIN ( SELECT parented,count(*) AS partcount FROM pg_partition GROUP BY parentid ) s ON c.oid = s.parentid ORDER BY partcount DESC; ``` ![image.png](https://bbs-img.huaweicloud.com/data/forums/attachment/forum/20226/10/1654835684251113526.png) 触发因素:某业务库大量列存多分区(3000+)的表,导致小文件巨多(单DN文件2000w+),访问效率低,故障恢复Building极慢,同时building也消耗大量IOPS,发向影响业务性能。 解决办法: - 整改列存分区间隔,减少分区个数来降低文件个数 - 列存表修改为行存表,行存的存储特征决定其文件个数不会像列存那么膨胀严重 # 三、小结 经过前面案例,稍微总结下不难发现,提升IO使用效率概括起来可分为两个维度,即提升IO的存储效率和计算效率(又称访问效率),提升存储效率包括整合小CU、减少脏数据、消除存储倾斜等,提升计算效率包括分区剪枝、索引扫描等,大家根据实际场景灵活处理即可。
  • [技术干货] 几款分布式数据库的对比
    过去十年见证了分布式数据库的崛起不仅通过本地集群来实现负载均衡,并提供高可用性,还具有数据中心内的机架感知等属性。专为云而设计的分布式数据库,可以跨越可用性区域,通过编排技术,支持公有云、私有云、混合云部署。近年来,市面上出现了大量专为分布式数据库部署而设计的新数据库系统,以及在初始设计中添加了分布式架构组件的其他数据库系统。DB-Engines排名前100的数据库DB-Engines是数据库领域的权威排行榜,它保留了所有数据库的流行指数,使用一种算法进行加权,监测诸如网站上的提及次数和谷歌的搜索趋势,Stack Overflow上的讨论或推特中的评论,工作职位要求的技术技能,以及在LinkedIn个人资料中提到这些技术的数量。虽然DB-Engines收集了数百个不同的数据库(截至2022年5月共有394个)。但是本文我们缩小范围,只观察前100名数据库。在很大程度上,反映了市场现状。关系型数据库管理系统(RDBMS),传统的SQL系统,仍然是最大的类别,占列表的47%。另外,列表中有25%是NoSQL系统,涵盖了许多不同类型的数据库,像MongoDB文档数据库、Redis键值系统、ScyllaDB宽列数据库,以及Neo4j图数据库。还有11%的数据库被列为多模型数据库,包括在同一系统中支持SQL和NoSQL的混合数据库,如微软的Cosmos DB或ArangoDB,或者支持多种NoSQL数据模型的数据库,如DynamoDB,它将自己列为NoSQL键值系统和文档存储。最后,还有一些是由各种特殊用途的数据库组成,从搜索引擎到时间序列数据库,以及其他不容易归入简单的“SQL与NoSQL”区域的数据库。但是所有这些数据库都是分布式数据库吗?这个词到底是什么意思?分布式数据库的定义2016年12月14日,ISO/IEC发布了最新版本的数据库语言SQL标准(ISO/IEC9075:2016)。随着时间的推移,如何构建与SQL兼容的分布式RDBMS系统一直在发展。分布式SQL,如PostgreSQL或CockroachDB NewSQL系统。相反,没有ANSI或ISO或IETF或W3C定义什么是NoSQL数据库。每种数据库都使用自己的专有查询语言,比如用于宽列NoSQL数据库的Cassandra查询语言(CQL),用于图形数据库的Gremlin/Tinkerpop查询方法。然而,它们并没有定义数据如何在这些数据库中分布,查询语言也不能解决架构问题。因此,无论是SQL还是NoSQL,对于什么是分布式数据库,并没有标准、协议或共识。因此,我花了一些时间来写下我自己的定义。坦率地说,这更像是一个门外汉的实用主义观点,而不是计算机科学教授的见解。简而言之,你必须决定你如何定义集群,以及如何跨集群分配数据。接下来,你必须确定集群中每个节点的角色。每个节点都是对等的,还是有些节点处于更优越的领导地位,而其他节点则是跟随者。然后,基于这些角色,你如何处理故障转移?最后,你必须在此基础上,弄清楚你如何尽可能均匀和容易地复制和分片数据。而这并不试图做到详尽无遗,你可以添加自己的特定条件。简短的清单:感兴趣的系统考虑到这些,我在前100名数据库中,找到五个示例,看看它们在测量属性时是如何比较的。其中有两个SQL系统和三个NoSQL系统。Postgres和CockroachDB代表最好的分布式SQL。CockroachDB被称为 NewSQL,专为分布式数据库而设计。MongoDB、Redis和ScyllaDB是分布式NoSQL,分别是文档数据库,键值存储,宽列数据库(也被称为键值数据库)。在大多数情况下,适用于ScyllaDB的也同样适用于Apache Cassandra和其他与Cassandra兼容的系统。假定你拥有专业的经验,而且对SQL与NoSQL的区别相对了解。基本上,如果需要一个表JOIN,坚持使用SQL和RDBMS。如果你可以将数据反规范化,那么NoSQL可能是一个很好的选择。我们不打算讨论作为数据结构或查询语言,两者哪个“更好”。而是讨论作为一个分布式数据库,哪个更好。多数据中心集群我们的选项在集群方面是如何比较的?现在,它们都能够进行集群,甚至是多数据中心操作。但是在PostgreSQL、MongoDB和Redis中,它们最初设计于单数据中心本地集群,在多数据中心设计之前就已经成为一种架构要求。Postgres首次发布于1986年,完全早于云计算的概念。后来,它允许在其设计上,纳入这些技术和能力。作为NewSQL革命的一部分,CockroachDB从一开始就考虑到了全球分布。MongoDB是在公有云诞生之初发布的,最开始设计时考虑到了单数据中心集群,但现在已经增加了对许多不同拓扑结构的支持。通过MongoDB Atlas,可以轻松部署到多个地区。Redis,由于其低延迟的设计,通常被部署在单个数据中心,但它具有允许多数据中心部署的企业特性。ScyllaDB,像Cassandra一样,从一开始就考虑到了多数据中心的部署。集群管理如何进行复制和分片,取决于数据库架构的分层或同质化程度。例如,在MongoDB中,有一个主服务器,其余的是主服务器的副本。副本是只读的,你只能对这个数据库的主副本进行写操作,不能直接更新。相反,你写到主数据库,它就会更新副本。所以,节点是异质的,而不是同质的。这有助于在读取繁重的工作负载中分配流量,但在混合或写入工作负载中,对你没有一点好处,主服务器可能会成为一个瓶颈。同样,如果主服务器发生故障会怎样?你将不得不完全停止写操作,直到集群选出一个新的主服务器,并将写操作分流到它上面。相反,如果ScyllaDB或Cassandra,或任何其他无active-active的系统,客户可以从任何节点读取或写入。没有单一的故障点,节点的同质化程度要高得多。而且每个节点都可以更新集群中的任何数据副本。因此,如果你有三个节点,每个节点都会根据其他两个节点的任何写入进行更新。active-active在计算方面本身就比较困难,但是一旦解决了服务器保持彼此同步的问题,就会得到一个可以更好地平衡混合或写入大量工作负载的系统,因为每个节点都可以提供读取或写入服务。那么,我们的各种例子在主复本或active-active对等方面是如何叠加的?CockroachDB和ScyllaDB,以及Cassandra一开始就考虑了active-active的主动式设计。在Postgres中,有一些可选的方法可以做到这一点,但它不是内置的。此外,MongoDB没有正式支持active-active,但是已经有一些人在尝试如何做到这一点了。对于Redis来说,active-active模型在Redis企业中可以通过无冲突复制数据类型(CRDTs)实现。Postgres、MongoDB和Redis都默认使用主副本数据分布模型。复制分布式系统设计也会影响如何跨部署到不同机架或数据中心之间分配数据。例如,给定一个主副本系统,只具有主的数据中心可以为任何写入工作负载服务,其他数据中心只能作为只读副本。在一个支持多数据中心集群的点对点系统中,整个集群中的每个节点都可以接受读或写操作。通过ScyllaDB,你可以决定每个站点有相同或甚至不同的复制因素。这里我展示了在一个数据中心的三个副本,在另一个数据中心有两个副本的可能性。操作可以有不同级别的一致性。你可能在三个节点的数据中心进行本地数据的读或写,需要更新任一数据中心的节点才能成功执行操作。可调整的一致性,结合多数据中心的拓扑感知,为工作负载提供更多的灵活性。拓扑感知本地集群是分布式数据库开始的方式,允许多个系统共享负载。如果想让数据库在多个节点上进行分片,或者通过确保相同的数据在多个节点上可用来实现高可用性,那么这一点非常重要。如果所有节点都安装在同一个机架上,一旦这个机架发生故障,就会很棘手。因此,添加拓扑感知,以便你可以感知同一数据中心内的机架。确保将数据分散在数据中心的多个机架上,从而最大限度地减少电源或连接丢失到一个或另一个机架的中断。有些数据库做的很好,允许在不同的数据中心运行数据库的多个副本,并使用某种跨集群更新机制。每个数据库都是自主运行的,它们的同步机制可以是单向的,一个数据中心更新一个下游的副本,也可以是双向的或多向的。这种地理分布可以通过允许更靠近用户的连接,来减少延迟。跨可用性区域或地区的数据库,还可以确保单个数据中心灾难不会导致数据库的部分或全部丢失。去年我们的一个客户就发生了这种情况,但由于他们部署在三个不同的数据中心,所以数据损失为零。跨集群更新最初是在批量级别上实现的。确保你的数据中心每天至少有一次同步。这并没有持续多久,后面人们开始确保更活跃的事务级更新。如果你在运行强一致性数据库,就会受到基于光速的实时传播延迟的限制。因此,实现最终一致性是为了允许每个操作更新使用多数据中心,同时考虑到在短期内,要使所有数据中心的数据保持一致可能需要时间。那么,在拓扑感知方面,它是如何叠加的?所以,CockroachDB和ScyllaDB也是内置的。从2015年开始,拓扑感知也成为MongoDB的一部分,他们在这方面有着多年的经验。Postgres和Redis最初被设计为单数据中心解决方案,因此处理多数据中心的延迟对两者来说并非易事。现在,你可以添加拓扑感知,就像添加active-active系统功能一样,但它并不是开箱即用的。让我们回顾一下所讨论的内容,分别查看这些数据库的属性。▶︎ PostgreSQLPostgreSQL是世界上最流行的的开源数据库之一,它以可靠性和稳定性而著称,在处理复杂SQL方面也表现出了绝对的优势。然而,Postgres仍在研究其跨集群和多数据中心的集群。由于SQL基于强一致性事务模式,所以它不能很好地跨地域跨集群。在所有相关的数据中心之间,每个查询都将由于长时间的延迟而暂停。此外,Postgres依靠的是主副本模型。集群中的一个节点是领导者,而其他节点是副本。虽然有负载平衡器或active-active插件,但这些也超出了基本的服务范围。最后,Postgres的分片在大多数情况下仍然是手动的,尽管他们在开发自动分片方面取得了进展,但这也超出了基本产品的范围。▶︎ CockroachDBCockroachDB声称自己是“NewSQL”,一个专为分发而设计的SQL数据库。它可以水平扩展,在磁盘、机器、机架,甚至数据中心故障时都能生存下来,做到延迟最小,无需手动干预。值得一提的是,CockroachDB使用Postgres线协议,并大量借鉴了Postgres开创的许多概念,而且并不局限于Postgres的架构。多数据中心集群和点对点的拓扑结构从一开始就被内置。自动分片和数据复制也是如此。它还内置了数据中心感知功能,而且还可以添加机架感知功能。对CockroachDB来说,它要求所有的事务都有很强的一致性,你可以把它看作是一个优点或缺点。既没有最终一致性的灵活性,也没有可调的一致性。这将降低吞吐量,并在任何跨数据中心部署中要求较高的基线延迟。▶︎ MongoDBMongoDB是NoSQL领域的领导者。随着它的发展,大量的分布式数据库功能被添加。现如今,MongoDB能够支持多数据中心集群。在大多数情况下,它仍然遵循主副本模式,也有办法使其成为对等的active-active。▶︎ Redis接下来是Redis,一个旨在作为内存缓存或数据存储的键值存储。Redis的数据全部在内存里,如果突然宕机,数据就会全部丢失,因此必须有一种机制来保证Redis的数据不会因为故障而丢失,这种机制就是Redis的持久化机制。虽然持久化保存数据,但如果数据集不适合放在RAM中,它就会遭受巨大的性能损失。正因为如此,它在设计时考虑到了本地集群。如果你无法承受5毫秒的等待时间来从SSD上获取数据,您可能更无法等待145毫秒来完成从旧金山到伦敦的网络往返时间。然而,有一些企业特性允许多数据中心的Redis集群。Redis在大多数情况下是以主副本模式运行的。这适用于大量读取的缓存服务器。但这意味着,主节点是数据需要首先写入的地方,然后将这些数据分散到副本,以帮助平衡其缓存负载。有一个企业功能,允许对等的active-active集群。Redis可以自动分片和复制数据,但它的拓扑感知仅限于作为企业功能的机架感知。▶︎ ScyllaDBScyllaDB是按照Apache Cassandra中的分布式数据库模型设计的。因此,它默认是多数据中心集群。它可以自动分片,并且每个操作都有可调整的一致性,如果你想要更强的一致性,它甚至还支持轻量级事务来提供写入的线性化。就拓扑感知而言,ScyllaDB支持机架感知和数据中心意识,甚至支持标记感知和分片感知,不仅知道数据存储在哪个节点上,甚至可以知道与该数据关联的CPU。结论虽然对于什么是分布式数据库,还没有一个行业标准,但是我们可以看到,许多领先的SQL和NoSQL数据库,都在某种程度上支持一组核心功能或属性。其中有些功能是内置的,有些被认为是增值包或第三方选项。在本文分析的五个典型分布式数据库系统中,CockroachDB为SQL数据库提供了最全面的功能和特性,ScyllaDB为NoSQL系统提供了最全面的功能。该分析应被视为某个时间段的调查。鉴于下一个技术周期的需求,每一个数据库系统都在不断发展,这个行业并没有停滞不前。对用户来说,分布式数据库每年都在进步,变得更加灵活、性能更强、更具弹性和可扩展性。来源:今日头条
  • [最佳实践] 基于华为隐私计算产品TICS实现端到端的企业积分查询作业【玩转华为云】
    本次TICS端到端体验,将以一个“小微企业信用评分”的场景为例。 社保、水电气和资助金等数据统一存储在某某政务云,由不同的局进行管理,机构想单独申请进行企业相关评分的计算会非常困难。 因此可以由某市政数局出面,统一制定隐私规则,审批数据提供方的数据使用申请, 并通过**华为Tics可信智能计算平台**进行安全计算。 [Tics服务官网链接](https://www.huaweicloud.com/product/tics.html) ![image.png](https://bbs-img.huaweicloud.com/blogs/img/20220601/1654067909797358323.png) # 数据准备 企业税收和资助金情况表tax(partner_gov,属于政府信息提供方,部署在用户计算节点agent_gov上) | 列名 | 含义 | 字段分类 | |----|----|----| | Id | 企业id | 唯一标识 | | tax_bal | 税收 | 敏感 | | Industry | 行业类型 | 不敏感 | 企业政府资助金数据表support(partner_gov, 属于政府信息提供方,部署在用户计算节点agent_gov上) | 列名 | 含义 | 字段分类 | |----|----|----| | Id | 企业id | 唯一标识 | | supp_bal | 资助金金额 | 敏感 | | Industry | 行业类型 | 不敏感 | 企业水电情况表power(partner_power,能源信息提供方,部署在用户计算节点agent_pow上) | 列名 | 含义 | 字段分类 | |----|----|----| | Id | 企业id | 唯一标识 | | electric_bal | 电费 | 敏感 | | water_bal | 水费 | 敏感 | 注意以上数据和表结构是根据场景进行模拟的数据,并非真实数据,样例数据和表结构文件都已在附件中给出。 >> 上述数据需要提前存导入到Mysql\Hive\Oracle等用户所属数据源中,Tics本身不会持有这些数据,这些数据会通过用户购买的计算节点进行加密计算,保障数据安全 从业务角度考虑,我安排了五个阶段,来对TICS系统进行验证和测试。 这里不讲述联盟创建、代理创建等内容,只重点讲述如何端到端实现一个该场景下的隐私计算作业完整执行流程。 ## 阶段一:数据发布 首先第一步,肯定是要做好数据准备工作。 我们首先进入Tics服务控制台([Tics服务控制台链接](https://console.huaweicloud.com/tics/?region=cn-north-4)) 在计算节点管理中,找到我们购买的计算节点,通过登录地址,进入计算节点控制台 ![image.png](https://bbs-img.huaweicloud.com/blogs/img/20220607/1654584251569715166.png) 登录计算节点后,在下图所述位置进行**连接器的新建** ![image.png](https://bbs-img.huaweicloud.com/blogs/img/20220606/1654521610969285860.png) 输入正确的连接信息,以建立数据源和计算节点之间的安全连接: ![image.png](https://bbs-img.huaweicloud.com/blogs/img/20220606/1654521855679957445.png) 建立完成后,看到连接器显示正常说明连接正常。 ![image.png](https://bbs-img.huaweicloud.com/blogs/img/20220606/1654507396127213040.png) 接着进入数据管理,进行数据集发布: ![image.png](https://bbs-img.huaweicloud.com/blogs/img/20220606/1654517045149107781.png) 数据集发布动作参考如下: ![image.png](https://bbs-img.huaweicloud.com/blogs/img/20220606/1654507407826387787.png) 然后我们以同样的方式,发布了 support资助金数据 表和power_data能源表。 这个过程并不会直接从数据源中导出用户数据,仅仅是从数据源处获取了数据集相关的元数据信息,用于任务的解析、验证等。 ## 阶段二:隐私规则防护 数据集发布后, 作为数据提供方,肯定会担心数据是否可能被随意使用。 因此第一步应该先确认tics的隐私规则能力是如何保护大家的数据安全的。 我们首先进入联邦分析的作业执行界面,点击作业创建 ![image.png](https://bbs-img.huaweicloud.com/blogs/img/20220606/1654517127219450257.png) 可看到如下的作业框,我们可按照下文提供的案例和sql语句进行作业测试。 ![image.png](https://bbs-img.huaweicloud.com/blogs/img/20220606/1654517199325764171.png) 假设有人试图直接查询敏感数据: ```sql select tax_bal, id from league_creator.tax ``` 则可以看到被提示不支持进行敏感数据的SELECT操作。 ![image.png](https://bbs-img.huaweicloud.com/blogs/img/20220606/1654507440936299192.png) 如果有人试图拿敏感数据加上自己的数据, 从结果倒推敏感数据,如下所示: ```sql Select tax_bal + electric_bal from LEAGUE_CREATOR.tax a join ZZZZZZ.power_data b on a.id = b.id ``` 这个操作等同于求原数据, 这个操作也会被tics识别并提示出来。 ![image.png](https://bbs-img.huaweicloud.com/blogs/img/20220606/1654507498058174652.png) ## 阶段三:审批防护 上述隐私规则,都是tics系统提供的默认规则。 但规则的完善总是有一个过程的,在规则未完全完善之前, 作为用户,可能更愿意支持开启审批功能, 来进行更“灵活”的作业合法性确认。 审批功能可以由联盟管理员在联盟界面进行开启: ![image.png](https://bbs-img.huaweicloud.com/blogs/img/20220606/1654517551131754964.png) 开启后,如下图所示,当有人直接查询我的敏感数据时,我可以在审批详情中,看到对方试图让敏感字段在结果可见,那就可以由该提供方进行识别,并进行拒绝操作。 ![image.png](https://bbs-img.huaweicloud.com/blogs/img/20220606/1654507576304259871.png) 对于两个字段相加的情况,也可以在审批中看到相加的情况, 也能看到id是用来做join碰撞的用途。 ![image.png](https://bbs-img.huaweicloud.com/blogs/img/20220606/1654507585386941112.png) 通过查看字段是否可见, 以及字段用途,能够确认该字段的应用是否符合自己的安全预期。 ## 阶段四:基本计算能力验证 下面场景是计算各企业在2021年的价值评分, 以用于评估信贷能力,其中的公式并非真实公式,仅仅是一个简单的参考计算式。 其目的是为了确认Tics的基础计算能力。 我们执行如下的sql作业: ```sql select c.id as `企业id`, 0.5 * a.tax_bal + 0.8 * b.supp_bal + (0.05 * c.electric_bal + 0.05 * c.water_bal) * 0.1 as `企业评分` from Partner1.TAX a, Partner1SUPPORT b, Partner2.POWER_DATA c where b.id = c.id and a.id = b.id ``` 审批时可以看到如下的情况,涉及关联字段较多,其使用方式都能够在审批界面中展示出来。 ![image.png](https://bbs-img.huaweicloud.com/blogs/img/20220606/1654507621540525742.png) 执行结果如下: ![image.png](https://bbs-img.huaweicloud.com/blogs/img/20220606/1654507631172306832.png) 可以看到基础的sql语法都能够支持。 并且从作业执行页面的提示上来看,已经支持了相当多的常用语法和sql函数。 ![image.png](https://bbs-img.huaweicloud.com/blogs/img/20220606/1654507734832255891.png) ## 阶段五:基于MPC算法的高安全级别计算 如果我度过了前期的demo验证阶段, 准备接入更高安全级别的数据, 就可能会希望提升数据保护级别, 以**纯密文的状态做计算**, 则我可以通过让开启高隐私级别开关,将联盟安全级别默认提升一个等级。 ![image.png](https://bbs-img.huaweicloud.com/blogs/img/20220606/1654517528916725432.png) 再次点击刚才的作业,审批时可以看到敏感数据被进行了同态加密。 从DAG图上可以看到 psi + 同态的全过程流向, 基本符合业界已公开的PSI算法流程和同态加密流程。 ![image.png](https://bbs-img.huaweicloud.com/blogs/img/20220606/1654507825264106824.png) ![image.png](https://bbs-img.huaweicloud.com/blogs/img/20220606/1654507830713689090.png) ## 阶段六:统计型作业的差分隐私保护 假设有以下作业,试图统计各行业的企业税收总和 和用电量总和, 进行统计分析 ```sql Select industry, sum(tax_bal), sum(electric_bal) from LEAGUE_CREATOR.tax a join dayu002.power_data b on a.id = b.id group by industry ``` 但是这种统计分析型的作业, 有可能被作业执行方通过增删某个碰撞的id, 得到两次作业之间的差值,从而推算出实际taxpay和water_fee。 此时我可以通过开启联盟中的差分隐私开关来保护自己的敏感数据。 开启后,符合差分隐私条件的这类统计作业,都会自动应用差分隐私算法进行加噪保护计算结果, 在一定误差范围内保证数据无法被恶意偷取。开启方式如下: ![image.png](https://bbs-img.huaweicloud.com/blogs/img/20220606/1654517608554979928.png) 以下是第一次执行作业时得到的结果: ![image.png](https://bbs-img.huaweicloud.com/blogs/img/20220606/1654507889869268045.png) 可以从DAG图看到,我们在返回最终统计结果前,增加了一个差分隐私计算的任务节点。 ![image.png](https://bbs-img.huaweicloud.com/blogs/img/20220606/1654507943373480619.png) 接着再执行一个sql,这个sql中过滤掉了某个企业,试图用差值去计算这个企业的税收值。 ```sql Select industry, sum(tax_bal), sum(electric_bal) from LEAGUE_CREATOR.tax a join dayu002.power_data b on a.id = b.id where a.id '123400558' group by industry ``` 这个企业的实际tax为274: ![image.png](https://bbs-img.huaweicloud.com/blogs/img/20220606/1654507982840189556.png) 得到新的结果如下: ![image.png](https://bbs-img.huaweicloud.com/blogs/img/20220606/1654507993599253617.png) 经过计算,66539.583321490225131 - 66078.857559963717677 = -461 可以看到并不会像使用者预期的那样直接得到实际的274差值,因此通过差分隐私算法保护了聚合操作的安全性。
  • [酷哥说库] openGauss内核分析(三):SQL解析
    在传统数据库中SQL引擎一般指对用户输入的SQL语句进行解析、优化的软件模块SQL的解析过程主要分为:• 词法分析:将用户输入的SQL语句拆解成单词(Token)序列,并识别出关键字、标识、常量等。• 语法分析:分析器对词法分析器解析出来的单词(Token)序列在语法上是否满足SQL语法规则。• 语义分析:语义分析是SQL解析过程的一个逻辑阶段,主要任务是在语法正确的基础上进行上下文有关性质的审查,在SQL解析过程中该阶段完成表名、操作符、类型等元素的合法性判断,同时检测语义上的二义性。openGauss在pg_parse_query中调用raw_parser函数对用户输入的SQL命令进行词法分析和语法分析,生成语法树添加到链表parsetree_list中。完成语法分析后,对于parsetree_list中的每一颗语法树parsetree,会调用parse_**yze函数进行语义分析,根据SQL命令的不同,执行对应的入口函数,最终生成查询树词法分析openGauss使用flex工具进行词法分析。flex工具通过对已经定义好的词法文件进行编译,生成词法分析的代码。词法文件是scan.l,它根据SQL语言标准对SQL语言中的关键字、标识符、操作符、常量、终结符进行了定义和识别。在kwlist.h中定义了大量的关键字,按照字母的顺序排列,方便在查找关键字时通过二分法进行查找。 在scan.l中处理“标识符”时,会到关键字列表中进行匹配,如果一个标识符匹配到关键字,则认为是关键字,否则才是标识符,即关键字优先. 以“select a, b from item”为例说明词法分析结果名称词性内容说明关键字keywordSELECT,FROM如SELECT/FROM/WHERE等,对大小写不敏感标识符IDENTa,b,item用户自己定义的名字、常量名、变量名和过程名,若无括号修饰则对大小写不敏感语法分析openGauss中定义了bison工具能够识别的语法文件gram.y,根据SQL语言的不同定义了一系列表达Statement的结构体(这些结构体通常以Stmt作为命名后缀),用来保存语法分析结果。以SELECT查询为例,它对应的Statement结构体如下。typedef struct SelectStmt { NodeTag type; List *distinctClause; /* NULL, list of DISTINCT ON exprs, or * lcons(NIL,NIL) for all (SELECT DISTINCT) */ IntoClause *intoClause; /* target for SELECT INTO */ List *targetList; /* the target list (of ResTarget) */ List *fromClause; /* the FROM clause */ Node *whereClause; /* WHERE qualification */ List *groupClause; /* GROUP BY clauses */ Node *havingClause; /* HAVING conditional-expression */ List *windowClause; /* WINDOW window_name AS (...), ... */ WithClause *withClause; /* WITH clause */ List *valuesLists; /* untransformed list of expression lists */ List *sortClause; /* sort clause (a list of SortBy's) */ Node *limitOffset; /* # of result tuples to skip */ Node *limitCount; /* # of result tuples to return */ …… } SelectStmt;这个结构体可以看作一个多叉树,每个叶子节点都表达了SELECT查询语句中的一个语法结构,对应到gram.y中,它会有一个SelectStmt。代码如下:从simple_select语法分析结构可以看出,一条简单的查询语句由以下子句组成:去除行重复的distinctClause、目标属性targetList、SELECT INTO子句intoClause、FROM子句fromClause、WHERE子句whereClause、GROUP BY子句groupClause、HAVING子句havingClause、窗口子句windowClause和plan_hint子句。在成功匹配simple_select语法结构后,将会创建一个Statement结构体,将各个子句进行相应的赋值。对simple_select而言,目标属性、FROM子句、WHERE子句是最重要的组成部分。SelectStmt与其他结构体的关系如下下面以“select a, b from item”为例说明简单select语句的解析过程,函数exec_simple_query调用pg_parse_query执行解析,解析树中只有一个元素(gdb) p *parsetree_list $47 = {type = T_List, length = 1, head = 0x7f5ff986c8f0, tail = 0x7f5ff986c8f0}List中的节点类型为T_SelectStmt(gdb) p *(Node *)(parsetree_list->head.data->ptr_value) $45 = {type = T_SelectStmt}查看SelectStmt结构体,targetList 和fromClause非空(gdb) set $stmt = (SelectStmt *)(parsetree_list->head.data->ptr_value) (gdb) p *$stmt $50 = {type = T_SelectStmt, distinctClause = 0x0, intoClause = 0x0, targetList = 0x7f5ffa43d588, fromClause = 0x7f5ff986c888, startWithClause = 0x0, whereClause = 0x0, groupClause = 0x0, havingClause = 0x0, windowClause = 0x0, withClause = 0x0, valuesLists = 0x0, sortClause = 0x0, limitOffset = 0x0, limitCount = 0x0, lockingClause = 0x0, hintState = 0x0, op = SETOP_NONE, all = false, larg = 0x0, rarg = 0x0, hasPlus = false}查看SelectStmt的targetlist,有两个ResTarget(gdb) p *($stmt->targetList) $55 = {type = T_List, length = 2, head = 0x7f5ffa43d540, tail = 0x7f5ffa43d800} (gdb) p *(Node *)($stmt->targetList->head.data->ptr_value) $57 = {type = T_ResTarget} (gdb) set $restarget1=(ResTarget *)($stmt->targetList->head.data->ptr_value) (gdb) p *$restarget1 $60 = {type = T_ResTarget, name = 0x0, indirection = 0x0, val = 0x7f5ffa43d378, location = 7} (gdb) p *$restarget1->val $63 = {type = T_ColumnRef} (gdb) p *(ColumnRef *)$restarget1->val $64 = {type = T_ColumnRef, fields = 0x7f5ffa43d470, prior = false, indnum = 0, location = 7} (gdb) p *((ColumnRef *)$restarget1->val)->fields $66 = {type = T_List, length = 1, head = 0x7f5ffa43d428, tail = 0x7f5ffa43d428} (gdb) p *(Node *)(((ColumnRef *)$restarget1->val)->fields)->head.data->ptr_value $67 = {type = T_String} (gdb) p *(Value *)(((ColumnRef *)$restarget1->val)->fields)->head.data->ptr_value $77 = {type = T_String, val = {ival = 140050197369648, str = 0x7f5ffa43d330 "a"}}(gdb) set $restarget2=(ResTarget *)($stmt->targetList->tail.data->ptr_value) (gdb) p *$restarget2 $89 = {type = T_ResTarget, name = 0x0, indirection = 0x0, val = 0x7f5ffa43d638, location = 10} (gdb) p *$restarget2->val $90 = {type = T_ColumnRef} (gdb) p *(ColumnRef *)$restarget2->val $91 = {type = T_ColumnRef, fields = 0x7f5ffa43d730, prior = false, indnum = 0, location = 10} (gdb) p *((ColumnRef *)$restarget2->val)->fields $92 = {type = T_List, length = 1, head = 0x7f5ffa43d6e8, tail = 0x7f5ffa43d6e8} (gdb) p *(Node *)(((ColumnRef *)$restarget2->val)->fields)->head.data->ptr_value $93 = {type = T_String} (gdb) p *(Value *)(((ColumnRef *)$restarget2->val)->fields)->head.data->ptr_value $94 = {type = T_String, val = {ival = 140050197370352, str = 0x7f5ffa43d5f0 "b"}}查看SelectStmt的fromClause,有一个RangeVar(gdb) p *$stmt->fromClause $102 = {type = T_List, length = 1, head = 0x7f5ffa43dfe0, tail = 0x7f5ffa43dfe0} (gdb) set $fromclause=(RangeVar*)($stmt->fromClause->head.data->ptr_value) (gdb) p *$fromclause $103 = {type = T_RangeVar, catalogname = 0x0, schemaname = 0x0, relname = 0x7f5ffa43d848 "item", partitionname = 0x0, subpartitionname = 0x0, inhOpt = INH_DEFAULT, relpersistence = 112 'p', alias = 0x0, location = 17, ispartition = false, issubpartition = false, partitionKeyValuesList = 0x0, isbucket = false, buckets = 0x0, length = 0, foreignOid = 0, withVerExpr = false}综合以上分析可以得到语法树结构语义分析在完成词法分析和语法分析后,parse_Ana lyze函数会根据语法树的类型,调用transformSelectStmt将parseTree改写为查询树(gdb) p *result $3 = {type = T_Query, commandType = CMD_SELECT, querySource = QSRC_ORIGINAL, queryId = 0, canSetTag = false, utilityStmt = 0x0, resultRelation = 0, hasAggs = false, hasWindowFuncs = false, hasSubLinks = false, hasDistinctOn = false, hasRecursive = false, hasModifyingCTE = false, hasForUpdate = false, hasRowSecurity = false, hasSynonyms = false, cteList = 0x0, rtable = 0x7f5ff5eb8c88, jointree = 0x7f5ff5eb9310, targetList = 0x7f5ff5eb9110,…} (gdb) p *result->targetList $13 = {type = T_List, length = 2, head = 0x7f5ff5eb90c8, tail = 0x7f5ff5eb92c8} (gdb) p *(Node *)(result->targetList->head.data->ptr_value) $8 = {type = T_TargetEntry} (gdb) p *(TargetEntry*)(result->targetList->head.data->ptr_value) $9 = {xpr = {type = T_TargetEntry, selec = 0}, expr = 0x7f5ff636ff48, resno = 1, resname = 0x7f5ff5caf330 "a", ressortgroupref = 0, resorigtbl = 24576, resorigcol = 1, resjunk = false} (gdb) p *(TargetEntry*)(result->targetList->tail.data->ptr_value) $10 = {xpr = {type = T_TargetEntry, selec = 0}, expr = 0x7f5ff5eb9178, resno = 2, resname = 0x7f5ff5caf5f0 "b", ressortgroupref = 0, resorigtbl = 24576, resorigcol = 2, resjunk = false} (gdb)(gdb) p *result->rtable $14 = {type = T_List, length = 1, head = 0x7f5ff5eb8c40, tail = 0x7f5ff5eb8c40} (gdb) p *(Node *)(result->rtable->head.data->ptr_value) $15 = {type = T_RangeTblEntry} (gdb) p *(RangeTblEntry*)(result->rtable->head.data->ptr_value) $16 = {type = T_RangeTblEntry, rtekind = RTE_RELATION, relname = 0x7f5ff636efb0 "item", partAttrNum = 0x0, relid = 24576, partitionOid = 0, isContainPartition = false, subpartitionOid = 0……}得到的查询树结构如下:完成词法、语法和语义分析后,SQL解析过程完成,SQL引擎开始执行查询优化,在下一期中再具体分析。
  • [交流吐槽] Delete、Drop、Truncate有什么区别?你知道吗?
    在 MySQL 中,删除的方法总共有 3 种:delete、truncate、drop,而三者的用法和使用场景又完全不同,接下来我们具体来看。1.deletedetele 可用于删除表的部分或所有数据,它的使用语法如下:PS:[] 中的命令为可选命令,可以被省略。如果我们要删除学生表中数学成绩排名最高的前 3 位学生,可以使用以下 SQL:delete from table_name [where...] [order by...] [limit...]1.1 delete 实现原理在 InnoDB 引擎中,delete 操作并不是真的把数据删除掉了,而是给数据打上删除标记,标记为删除状态,这一点我们可以通过将 MySQL 设置为非自动提交模式,来测试验证一下。非自动提交模式的设置 SQL 如下:delete from student order by math desc limit 3;之后先将一个数据 delete 删除掉,然后再使用 rollback 回滚操作,最后验证一下我们之前删除的数据是否还存在,如果数据还存在就说明 delete 并不是真的将数据删除掉了,只是标识数据为删除状态而已,验证 SQL 和执行结果如下图所示:1.2 关于自增列在 InnoDB 引擎中,使用了 delete 删除所有的数据之后,并不会重置自增列为初始值,我们可以通过以下命令来验证一下:2.truncatetruncate 执行效果和 delete 类似,也是用来删除表中的所有行数据的,它的使用语法如下:truncate [table] table_nametruncate 在使用上和 delete 最大的区别是,delete 可以使用条件表达式删除部分数据,而 truncate 不能加条件表达式,所以它只能删除所有的行数据,比如以下 truncate 添加了 where 命令之后就会报错:2.1 truncate 实现原理truncate 看似只删除了行数据,但它却是 DDL 语句,也就是 Data Definition Language 数据定义语言,它是用来维护存储数据的结构指令,所以这点也是和 delete 命令是不同的,delete 语句属于 DML,Data Manipulation Language 数据操纵语言,用来对数据进行操作的。为什么 truncate 只是删除了行数据,没有删除列数据(字段和索引等数据)却是 DDL 语言呢?这是因为 truncate 本质上是新建了一个表结构,再把原先的表删除掉,所以它属于 DDL 语言,而非 DML 语言。2.2 重置自增列truncate 在 InnoDB 引擎中会重置自增列,如下命令所示:3.dropdrop 和前两个命令只删除表的行数据不同,drop 会把整张表的行数据和表结构一起删除掉,它的语法如下:DROP [TEMPORARY] TABLE [IF EXISTS] tbl_name [,tbl_name]其中 TEMPORARY 是临时表的意思,一般情况下此命令都会被忽略。drop 使用示例如下:三者的区别数据恢复方面:delete 可以恢复删除的数据,而 truncate 和 drop 不能恢复删除的数据。执行速度方面:drop > truncate > delete。删除数据方面:drop 是删除整张表,包含行数据和字段、索引等数据,而 truncate 和 drop 只删除了行数据。添加条件方面:delete 可以使用 where 表达式添加查询条件,而 truncate 和 drop 不能添加 where 查询条件。重置自增列方面:在 InnoDB 引擎中,truncate 可以重置自增列,而 delete 不能重置自增列。总结delete、truncate 可用于删除表中的行数据,而 drop 是把整张表全部删除了,删除的数据包含所有行数据和字段、索引等数据,其中 delete 删除的数据可以被恢复,而 truncate 和 drop 是不可恢复的,但在执行效率上,后两种删除方式又有很大的优势,所以要根据实际场景来选择相应的删除命令,当然 truncate 和 drop 这些不可恢复数据的删除方式使用的时候也要小心。
  • [交流吐槽] SQL 中为什么经常要加 Nolock ?
    刚开始工作的时候,经常听同事说在SQL代码的表后面加上WITH(NOLOCK)会好一些,后来仔细研究测试了一下,终于知道为什么了。那么加与不加到底有什么区别呢?SQL在每次新建一个查询,就相当于创建了一个会话。在不同的查询窗口操作,会影响到其他会话的查询。当某张表正在写数据时,这时候去查询很可能就会一直处于阻塞状态,哪怕你只是一个很简单的SELECT也会一直等待。我们这里使用事务来往某张表里写数据,我们知道事务在写完表必须提交(COMMIT)或回滚(ROLLBACK)才能释放表,否则会一直处于阻塞状态。在插入过程中,我们写一个简单的查询语句,在不添加WITH(NOLOCK)和添加WITH(NOLOCK)的情况下,看会发生什么。示例数据如下表A,是我们新建的一个非常简单的表。下面我们创建一个往里面写数据的事务(使用BEGIN TRAN就可以开始一个事务了)我们发现有1行受影响了,注意这里的会话ID是59(左上角黄色标签上的数字)不添加NOLOCK我们新建一个查询窗口,然后查询A表从上面的查询可以看到,表A被锁住了,我们的查询一直处于阻塞状态。这里的会话ID是60。这个时候如果你在会话59的窗口执行COMMIT或ROLLBACK,会话60的查询结果会立刻显示出来,这里为了下面的演示我们暂时不提交或回滚。添加NOLOCK我们再新建一个查询窗口,还是查询A表,这次我们加上NOLOCK。注意上图标红色的地方,当前会话ID是55,旁边的60还在执行状态,而我们加了NOLOCK后,瞬间就查询出结果了,而且还把事务里即将要插入的数据给查询到了。这是为什么呢?事务里的数据虽然还没有提交,但是它实际上已经存在内存里面了,这个时候我们使用NOLOCK查询到的结果,实际上还没存储到硬盘。从上面的两个测试可以看出,NOLOCK的作用其实就是为了防止查询时被阻塞,只是这样会产生脏读(未提交的数据)。那么一般什么情况下使用NOLOCK呢?通常是一些被频繁写的表,不管是插入,更新还是删除。这样的表在查询时,使用NOLOCK是非常有效的。WITH(NOLOCK)和NOLOCK的区别不知道小伙伴注意没,我前面介绍时是写的WITH(NOLOCK),但是测试时,使用的是(NOLOCK),它们有什么区别呢?为了搞清楚WITH(NOLOCK)与NOLOCK的区别,我们先看看下面三个SQL语句有啥区别SELECT * FROM A NOLOCK SELECT * FROM A (NOLOCK); SELECT * FROM A WITH(NOLOCK);(NOLOCK)这样的写法,NOLOCK其实只是别名的作用,而没有任何实质作用。所以不要粗心将(NOLOCK)写成NOLOCK。(NOLOCK)与WITH(NOLOCK)其实功能上是一样的。(NOLOCK)只是WITH(NOLOCK)的别名,但是在SQL Server 2008及以后版本中,(NOLOCK)不推荐使用了,"不借助 WITH 关键字指定表提示”的写法已经过时了。在使用链接服务器的SQL当中,(NOLOCK)不会生效,WITH(NOLOCK)才会生效。--这样会提示用错误 select * from [IP].[dbname].dbo.tableName with (nolock) --这样就可以 select * from [dbname].dbo.tableName with(nolock)
  • [互动交流] fusioninsight opensource flink sql 作业
    fusioninsight opensource flink 1.12 sql 作业中,怎么把kafka的数据接进来写入postgres中,尝试好多,一直sql校验失败。查资料没有示例
总条数:865 到第 页
上滑加载中