• [性能调优] dws中sql在执行Streaming(type: REDISTRIBUTE)之前先进行HashAggregate是否能提升性能
    问题有两个一、sql在执行Streaming(type: REDISTRIBUTE)(数据重分布)之前先进行HashAggregate是否能提升性能?根据网上的说法是重分布之前自己会先HashAggregate一次减少需要重分布的数据量,这样可以提升性能,但这边很多sql在执行Streaming(type: REDISTRIBUTE)之前都没有先HashAggregate导致重分布的数据量大导致性能查。二、如果sql在执行Streaming(type: REDISTRIBUTE)前自己不先HashAggregate,那如何手动调优使sql能在在执行Streaming(type: REDISTRIBUTE)之前先进行HashAggregate一次?执行计划如下
  • [性能调优] dws中sql在执行Streaming(type: REDISTRIBUTE)之前先进行HashAggregate是否能提升性能
    问题有两个一、sql在执行Streaming(type: REDISTRIBUTE)(数据重分布)之前先进行HashAggregate是否能提升性能?根据网上的说法是重分布之前自己会先HashAggregate一次减少需要重分布的数据量,这样可以提升性能,但这边很多sql在执行Streaming(type: REDISTRIBUTE)之前都没有先HashAggregate导致重分布的数据量大导致性能查。二、如果sql在执行Streaming(type: REDISTRIBUTE)前自己不先HashAggregate,那如何手动调优使sql能在在执行Streaming(type: REDISTRIBUTE)之前先进行HashAggregate一次?执行计划如下
  • [知识分享] 一文读懂数仓中的pg_stat
    【摘要】 GaussDB(DWS)在SQL执行过程中,会记录表增删改查相关的运行时统计信息,并在事务提交或回滚后记录到共享的内存中。这些信息可以通过 “pg_stat_all_tables视图” 查询,也可以通过下面函数进行查询。pg_stat_get_tuples_inserted --表累积insert条数pg_stat_get_tuples_updated --表累积update条数p...本文分享自华为云社区《一文读懂pgstat》,作者: leapdb 。GaussDB(DWS)在SQL执行过程中,会记录表增删改查相关的运行时统计信息,并在事务提交或回滚后记录到共享的内存中。这些信息可以通过 “pg_stat_all_tables视图” 查询,也可以通过下面函数进行查询。pg_stat_get_tuples_inserted --表累积insert条数 pg_stat_get_tuples_updated --表累积update条数 pg_stat_get_tuples_deleted --表累积delete条数 pg_stat_get_tuples_changed --表自上次analyze以来,修改的条数 pg_stat_get_last_analyze_time --查询最近一次analyze时间因此,根据共享内存中 "表自上次analyze以来修改过的条数" 是否超过一定阈值,就可以判定是否需要做analyze了。1. 事务中如何记录统计信息每个session有一个PgStat_TableStatus内存结构,当前事务访问过的每个表占用一个。其中包含每一层子事务的统计信息,用结构体PgStat_TableXactStatus描述。这里记录了当前子事务增删改查的各种信息。pg_stat_get_xact_numscans --当前事务在当前表上,启动顺序扫描的次数 pg_stat_get_xact_tuples_returned --当前事务在当前表上,顺序扫描抓取的可见元组条数。 pg_stat_get_xact_tuples_fetched --当前事务在当前表上,抓取的可见元组条数。 pg_stat_get_xact_tuples_inserted --当前事务在当前表上,插入条数 pg_stat_get_xact_tuples_deleted --当前事务在当前表上,删除条数 pg_stat_get_xact_tuples_updated --当前事务在当前表上,更新条数 pg_stat_get_xact_tuples_hot_updated --当前事务在当前表上,HOT更新条数 pg_stat_get_xact_blocks_fetched --当前事务在当前表上,选中的页面数 pg_stat_get_xact_blocks_hit --当前事务在当前表上,扫描过的页面数 pg_stat_get_xact_function_calls --当前事务在当前函数上,调用次数 pg_stat_get_xact_function_total_time --当前事务在当前函数上,所有调用的总执行时间 pg_stat_get_xact_function_self_time --当前事务在当前函数上,上次调用一次的执行时间 pg_stat_get_xact_partition_tuples_inserted --当前事务在当前表分区上,插入条数 pg_stat_get_xact_partition_tuples_deleted --当前事务在当前表分区上,删除条数 pg_stat_get_xact_partition_tuples_updated --当前事务在当前表分区上,更新条数 pg_stat_get_xact_partition_tuples_hot_updated --当前事务在当前表分区上,HOT更新条数事务提交时会把当前表,所有子事务中的信息汇总,并转储到pgstat中。2. 事务外如何记录统计信息事务提交时将事务中的执行信息通过UDP报文发送给PgCollector后台线程,由其记录到统一的内存中,并在集群正常停止时转储到物理文件pg_stat_tmp/pgstat.stat中。实例级信息,PgStat_GlobalStats库级信息,PgStat_StatDBEntry表级信息,其内存结构PgStat_StatTabEntry--实例级信息 pg_stat_get_bgwriter_timed_checkpoints --后台写进程开启定时检查点的次数 pg_stat_get_bgwriter_requested_checkpoints --后台写进程开启基于后端请求的检查点的次数 pg_stat_get_checkpoint_write_time --最近一次checkpoint写页面的时间 pg_stat_get_checkpoint_sync_time --最近一次checkpoint同步页面的时间 pg_stat_get_bgwriter_buf_written_checkpoints --在检查点期间后台写进程写入的缓冲区数目。 pg_stat_get_bgwriter_buf_written_clean --为日常清理脏块,后台写进程写入的缓冲区数目。 pg_stat_get_bgwriter_maxwritten_clean --后台写进程停止清理扫描的时间,因为已经写入了更多的缓冲区(相比bgwriter_lru_maxpages参数声明的缓冲区数)。 pg_stat_get_buf_written_backend --后端进程写入的缓冲区数,因为它们需要分配一个新的缓冲区。 pg_stat_get_buf_fsync_backend --后台进程执行fsync的次数 pg_stat_get_buf_alloc --分配的总缓冲区数。 pg_stat_get_bgwriter_stat_reset_time --后台bgwriter线程的重置时间 --当前CN记录的库级信息 pg_stat_get_db_xact_commit --当前实例上指定数据库中已提交事务的数量。 pg_stat_get_db_xact_rollback --当前实例上指定数据库中回滚事务的数量。 pg_stat_get_db_blocks_fetched --当前实例上指定数据库中磁盘块抓取请求的数量。 pg_stat_get_db_blocks_hit --当前实例上指定数据库在缓冲区中找到的请求磁盘块的数量。 pg_stat_get_db_tuples_returned --当前实例上指定数据库返回的元祖数量。 pg_stat_get_db_tuples_fetched --当前实例上指定数据库中读取的元组数量。 pg_stat_get_db_tuples_inserted --当前实例上指定数据库中插入的元组数量。 pg_stat_get_db_tuples_updated --当前实例上指定数据库中更新的元组数量。 pg_stat_get_db_tuples_deleted --当前实例上指定数据库中删除的元组数量。 pg_stat_get_db_conflict_all --当前实例上指定数据库中发生冲突恢复的次数。 pg_stat_get_db_conflict_lock --集群中所有CN和DN上指定数据库锁冲突的总数。在DN上执行该函数,返回当前实例上指定数据库中锁冲突数量。 pg_stat_get_db_deadlocks --当前实例上指定数据库中死锁的数量。 pg_stat_get_db_temp_files --当前实例上指定数据库中创建临时文件的个数。 pg_stat_get_db_temp_bytes --当前实例上指定数据库中创建临时文件的字节数。 pg_stat_get_db_blk_read_time --当前实例上指定数据库中读数据块所用的时间。 pg_stat_get_db_blk_write_time --当前实例上指定数据库中写数据块所用的时间。 --整个集群记录的库级信息 pg_stat_get_db_total_xact_commit --在CN上执行该函数,返回集群中所有CN上指定数据库中已提交事务的总数。在DN上执行该函数,返回当前实例上指定数据库中已提交事务的数量。 pg_stat_get_db_total_xact_rollback --在CN上执行该函数,返回集群中所有CN上指定数据库中回滚事务的总数。在DN上执行该函数,返回当前实例上指定数据库中回滚事务的数量。 pg_stat_get_db_total_blocks_fetched --在CN上执行该函数,返回集群中所有DN上指定数据库中磁盘块抓取请求的总数。在DN上执行该函数,返回当前实例上指定数据库中磁盘块抓取请求的数量。 pg_stat_get_db_total_blocks_hit --在CN上执行该函数,返回集群中所有DN上指定数据库在缓冲区中找到的请求磁盘块的总数。在DN上执行该函数,返回当前实例上指定数据库在缓冲区中找到的请求磁盘块的数量。 pg_stat_get_db_total_tuples_returned --在CN上执行该函数,返回集群中所有DN上指定数据库返回的元组总数。在DN上执行该函数,返回当前实例上指定数据库返回的元组数量。 pg_stat_get_db_total_tuples_fetched --在CN上执行该函数,返回集群中所有DN上指定数据库读取的元组总数。在DN上执行该函数,返回当前实例上指定数据库读取的元组数量。 pg_stat_get_db_total_tuples_inserted --在CN上执行该函数,返回集群中所有DN上指定数据库插入的元组总数。在DN上执行该函数,返回当前实例上指定数据库插入的元组数量。 pg_stat_get_db_total_tuples_updated --在CN上执行该函数,返回集群中所有DN上指定数据库更新的元组总数。在DN上执行该函数,返回当前实例上指定数据库更新的元组数量。 pg_stat_get_db_total_tuples_deleted --在CN上执行该函数,返回集群中所有DN上指定数据库删除的元组总数。在DN上执行该函数,返回当前实例上指定数据库删除的元组数量。 pg_stat_get_db_total_conflict_all --在CN上执行该函数,返回集群中所有CN和DN上指定数据库发生冲突恢复的总次数。在DN上执行该函数,返回当前实例上指定数据库中发生冲突恢复的次数。 pg_stat_get_db_total_temp_files --在CN上执行该函数,返回集群中所有DN上指定数据库中创建临时文件的总个数。在DN上执行该函数,返回当前实例上指定数据库中创建临时文件的个数。 pg_stat_get_db_total_temp_bytes --在CN上执行该函数,返回集群中所有DN上指定数据库中创建临时文件的总字节数。在DN上执行该函数,返回当前实例上指定数据库中创建临时文件的字节数。 pg_stat_get_db_total_deadlocks --集群中所有CN和DN上指定数据库死锁的总数。在DN上执行该函数,返回当前实例上指定数据库中死锁的数量。 pg_stat_get_db_total_blk_read_time --在CN上执行该函数,返回集群中所有DN上指定数据库中读数据块所用的总时间。在DN上执行该函数,返回当前实例上指定数据库中读数据块所用的时间。 pg_stat_get_db_total_blk_write_time --在CN上执行该函数,返回集群中所有DN上指定数据库中写数据块所用的总时间。在DN上执行该函数,返回当前实例上指定数据库中写数据块所用的时间。 --表级信息 pg_stat_get_numscans --当前表上,启动顺序扫描的次数 pg_stat_get_tuples_returned --当前表上,顺序扫描抓取的可见元组条数。 pg_stat_get_tuples_fetched --当前表上,抓取的可见元组条数。 pg_stat_get_tuples_inserted --当前表上,插入条数 pg_stat_get_tuples_deleted --当前表上,删除条数 pg_stat_get_tuples_updated --当前表上,更新条数 pg_stat_get_tuples_hot_updated --当前表上,HOT更新条数 pg_stat_get_blocks_fetched --当前表上,选中的页面数 pg_stat_get_blocks_hit --当前表上,扫描过的页面数 pg_stat_get_function_calls --当前函数上,调用次数 pg_stat_get_function_total_time --当前函数上,所有调用的总执行时间 pg_stat_get_function_self_time --当前函数上,上次调用一次的执行时间 pg_stat_get_partition_tuples_inserted --当前表分区上,插入条数 pg_stat_get_partition_tuples_deleted --当前表分区上,删除条数 pg_stat_get_partition_tuples_updated --当前表分区上,更新条数 pg_stat_get_partition_tuples_hot_updated --当前表分区上,HOT更新条数 pg_stat_get_tuples_changed --当前表上自上一次analyze的历史累积变化量 pg_stat_get_partition_tuples_changed --当前表分区上自上一次analyze的历史累积变化量 pg_stat_get_partition_live_tuples --当前表分区上可见元组数 pg_stat_get_partition_dead_tuples --当前表分区上删除元组数 pg_stat_get_live_tuples --当前表上可见元组数 pg_stat_get_last_vacuum_time --当前表上最近一次vacuum的时间 pg_stat_get_last_data_changed_time --当前表上最近一次数据修改时间 pg_stat_get_last_autovacuum_time --当前表上最近一次autovacuum时间 pg_stat_get_last_autoanalyze_time --当前表上最近一次autoanalyze时间 pg_stat_get_last_analyze_time --当前表上最近一次手动analyze时间 --local表示当前节点的信息 pg_stat_get_local_tuples_updated pg_stat_get_local_tuples_inserted pg_stat_get_local_tuples_hot_updated pg_stat_get_local_tuples_deleted pg_stat_get_local_tuples_changed pg_stat_get_local_live_tuples pg_stat_get_local_last_autovacuum_time pg_stat_get_local_last_autoanalyze_time pg_stat_get_local_dead_tuples pg_stat_get_local_autovacuum_count pg_stat_get_local_autoanalyze_count pg_stat_get_local_analyze_status函数信息,PgStat_StatFuncEntrypg_stat_get_function_calls --函数已被调用次数。 pg_stat_get_function_total_time --该函数花费的总挂钟时间,以微秒为单位。包括花费在此函数调用上的时间。 pg_stat_get_function_self_time --在当前事务中仅花费在此函数上的时间。不包括花费在调用函数上的时间。3. 单节点的统计信息每个CN和DN节点,各自记录自己的统计信息。可以通过上面的函数分别查看。另外,CN在增删改的SQL执行结束时,将各个DN的返回条数信息记录到CN自己的pgstat结构中,构成了全局的统计信息。4. 集群全局的统计信息各个CN各自维护自己的pgstat信息,因此若想知道集群全局的,还需要向所有CN查询并汇总。5. 统计信息的生命周期pgstat中的统计信息属于运行时信息,这些信息处于事务外,不严格保证数据一致性。在数据库异常停止时会清空所有数据,正常停止会保留数据。6. 统计信息的维护这些信息在执行SQL时被自动记录,不需要人工进行维护。
  • [技术干货] 为什么大数据平台要回归SQL[转载]
    先说观点:因为还没找到更好的。接下来说原因,首先来看看大数据平台都在干什么。原因结构化数据计算仍是重中之重大数据平台主要是为了应对海量数据存储和分析的需求,海量数据存储的确不假,除了生产经营产生的结构化数据,还有大量音视频等非结构化数据,这部分数据很大,占用的空间也很多,有时大数据平台80%以上都存储着非结构化数据。不过,数据光存储还不行,只有利用起来才能产生价值,这就要进行分析了。大数据分析要分结构化和非机构化数据两部分讨论。结构化数据主要是企业生产经营过程中产生的业务数据,可以说是企业的核心,以往在没有大数据平台的时候企业主要或全部在使用的就是这部分数据。随着业务的不断积累,这部分数据也越来越大,传统数据库方案面临很大挑战,建设大数据平台自然要解决这部分核心数据分析问题。有了大数据平台,给大家的想象空间也大了起来,以往无法利用的日志、图片、音视频等非结构化数据也要产生价值,这就涉及到非结构化数据分析了。相对核心业务数据分析,非结构化数据分析看起来更像是锦上添花。即使如此,非结构化数据分析并不是孤立存在,也还会伴随大量结构化数据处理。采集非结构化数据的同时,常常会伴随着采集许多相关的结构化数据,比如音视频的制作人、制作时间、所属类别、时长、…;有些非结构化数据经过处理后也会转变成结构化数据,比如网页日志中拆解出访问人 IP、访问时刻、关键搜索词等。所谓的非结构化数据分析,经常实际上是针对这些伴生而出的结构化数据。结构化数据分析仍然是大数据平台的重中之重。而结构化数据处理技术就比较成熟了,比如我们常用的基于关系数据模型的关系数据库(SQL)。SQL仍是目前最广泛的结构化数据计算技术回归 SQL 却是当前大数据计算语法的一个发展倾向。在 Hadoop 体系中,早期的 PIG Latin 已经被淘汰,而 Hive 却一直坚挺;Spark 上也在更多地使用 Spark SQL,而 Scala 反而少很多(Scala易学难精,作为编译型语言不支持热部署也有很多不方便之处)。其它一些新的大数据计算体系一般也将 SQL 作为首选的计算语法,经过几年时间的混战,现在 SQL 又逐步拿回了主动权。这个现象,大概有这么两个原因:1. 实在没什么别的好用关系数据库过于普及,程序员对 SQL 相当熟悉,甚至思维习惯都是 SQL 式的。SQL 用来做一些常规查询也比较简单,虽然用于处理复杂的过程计算或有序运算并不方便,但其它那些替代技术也好不到哪里去,碰到 SQL 难写的运算一样要写和 UDF 相当的复杂代码,反正都是麻烦,还不如继续用 SQL。2. 大数据厂商的鼎力支持大数据的技术本质是高性能,而 SQL 是性能比拼的关键阵地。比性能要面对同样的运算才有意义,过于专门和复杂的运算涉及的影响因素太多,不容易评估出大数据平台本身的能力。而 SQL 有国际标准的 TPC 系列,所有用户都看得懂,这样就有明确的可比性,厂商也会把性能优化的重点放在 SQL 上。兼容SQL更利于移植大数据平台兼容 SQL 的好处是很明显的,SQL 的应用非常广泛,会 SQL 的程序员很多,如果继续采用 SQL 则可以避免许多学习成本。支持 SQL 的前端软件也很多,使用 SQL 的大数据平台很容易融入这个现成的生态圈中。大数据平台打算替代的传统数据库也是 SQL 语法的,这样兼容性会很好,移植成本相对较低。好了,我们说完大数据平台为什么会回归关系数据模型了。那么继续使用关系数据模型(SQL)会存在哪些问题呢?问题性能低继续使用 SQL的最大问题就是难以获得大数据计算最需要的高性能。SQL 中缺乏一些必要的数据类型和运算定义,这使得某些高性能算法无法描述,只能寄希望于计算引擎在工程上的优化。传统商业数据库经过几十年的发展,优化经验已经相当丰富,但即使这样仍有许多场景难以被优化,理论层面的问题确实很难在工程层面解决。而新兴的大数据平台在优化方面的经验还远远不如传统数据库,算法上不占优,就只能靠集群更多的机器获得性能提升。另外,SQL 描述过程的能力不太好,不擅长指定执行路径,而想获得高性能常常需要专门优化的执行路径,这又需要增加许多特殊的修饰符来人为干预,那还不如直接用过程性语法更为直接,这也会妨碍用 SQL 写出高性能的代码。SQL 发明之初的计算机硬件能力还比较差,要保证实用性,SQL 的设计必须适应当时的硬件条件,这就导致了 SQL 很难充分利用当代计算机的硬件能力,具体来说就是大内存、并行和集群。SQL 中的 JOIN 是按键值对应的,而大内存情况下其实可以直接用地址对应,不需要计算 HASH 值和比对,性能可以提高很多;SQL 的数据表无序,单表计算时还容易做到分段并行,多表关联运算时一般就只能事先做好固定分段,很难做到同步动态分段,这就难以根据机器的负载临时决定并行数量;对于集群运算也是这样,SQL 在理论上不区分维表和事实表,JOIN 运算简单地定义为笛卡尔积后过滤,要实现大表 JOIN 就会不可避免地产生占用大量网络资源的 HASH Shuffle 动作,在集群节点数太多时,网络传输造成的延迟会超过节点多带来的好处。举个具体的例子,我们想在 1 亿条数据中取出前 10 名,用 SQL 写出来是这样的:select top 10 x,y from T order by x desc这个语句中有个 order by,严格按它执行就会涉及大排序,而排序非常慢。其实我们可以想出一个不用大排序的算法,但用 SQL 却无法描述,只能指望数据库优化器了。对于这句 SQL 描述的简单情况,很多商用数据库确实都能优化,使用不必大排序的算法,性能通常很好。但情况复杂一些,比如在每个分组中取前 10 名,要用窗口函数和子查询把 SQL 写成这样:select * from     (select y,*,row_number() over (partition by y order by x desc) rn from T)where rn<=10这时候,数据库优化器就会犯晕了,猜不出这句 SQL 的目的,只能老老实实地执行排序的逻辑(这个语句中还是有 order by 的字样),结果性能陡降。开发效率低不仅跑的慢,开发效率也不高,尤其在复杂计算方面,SQL实现很繁琐。比如根据股票记录查询某只股票最长连续上涨天数,SQL(oracle)的写法如下:select code, max(ContinuousDays) - 1from (    select code, NoRisingDays, count(*) ContinuousDays    from (        select code,            sum(RisingFlag) over (partition by code order by day) NoRisingDays        from (            select code, day,                case when price>lag(price) over (partittion by code order by day)                    then 0 else 1 end RisingFlag            from stock  ) )     group by NoRisingDays )group by code用了很绕的方式实现,别说写出来,看懂都要半天。此外,SQL也很难实现过程计算。什么是过程性计算呢?就是一步写不出来,需要多次分步运算,特别是与数据次序相关的运算。我们举几个例子来看:一周内累计登录时长超过一小时的用户占比,但要除去登录时长小于 10 秒的误操作情况信用卡在最近三个月内最长连续消费的天数分布情况,考虑实施连续消费 10 天后积分三倍的促销活动一个月中有多少用户在 24 小时连续操作了查看商品后加入购物车并购买的的动作,有多少用户在中间步骤中放弃?……(为了便于理解,这些例子已经做了简化,实际情况的运算还要复杂很多)这类过程性运算,用 SQL 写出来的难度就很大,经常还要写 UDF 才能完成。如果SQL写都写不出来,那么SQL的使用效果将大打折扣。开发效率低导致性能低复杂SQL的执行效率往往也很低,这就又回到性能的问题了,实际上开发效率和计算性能是密切相关的,很多性能问题本质上是开发效率造成。复杂 SQL 的优化效果很差,在嵌套几层之后,数据库引擎也会晕掉,不知道如何优化。提高这类复杂运算的性能,指望计算平台的自动优化就靠不住了,根本手段还要靠写出高性能的算法。象过程式运算中还常常需要保存中间结果以复用,SQL 需要用临时表,多了 IO 操作就会影响性能,这都不是引擎优化能解决的事情,必须要去改写计算过程。所以,本质上,提高性能还是降低开发难度。软件无法提高硬件的性能,只能想办法设计复杂度更低的算法,而如果能够快速低成本地实现这些算法,那就可以达到提高性能的目标。如果语法体系难以甚至没办法描述高性能算法,必须迫使程序员采用复杂度较高的算法,那也就很难再提高性能了。优化 SQL 运算无助于降低它的开发难度,SQL 语法体系就是那样,无论怎样优化它的性能,开发难度并不会改变,很多高性能算法仍然实现不了,也就难以实质性地提高运算性能。编写 UDF 在许多场景时确实能提高性能,但一方面开发难度很大,另一方面这是程序员硬写的,也不能利用到 SQL 引擎的优化能力。而且经常并不能将完整运算都写成 UDF,只能使用计算平台提供的接口,仍然要在 SQL 框架使用它的数据类型,这样还是会限制高性能算法的实现。根本的解决方法,还是要让大数据平台真地有一些更好用的语法。解法使用开源集算器SPL就可以作为SQL很好的替代和延伸,作为大数据平台专用的计算语言,延续SQL优点的同时改善其缺点。SPL是一款专业的开源数据计算引擎,提供了独立的计算语法,整个体系不依赖关系数据模型,因此在很多方面都有长足突破,尤其在开发效率和计算性能方面。下面来盘点一下SPL都有哪些特性适用于当代大数据平台。强集成性首先是集成性,不管SPL多优秀,如果与大数据平台无法结合使用也是白费。要在大数据平台中使用SPL其实很方便,引入jar包就可以使用(本身也是开源的,想怎么用就怎么用)。SPL提供了标准JDBC驱动,可以直接执行SPL脚本,也可以调用SPL脚本文件。…Class.forName("com.esproc.jdbc.InternalDriver");Connection conn =DriverManager.getConnection("jdbc:esproc:local://");//PreparedStatement st = (PreparedStatement)conn.createStatement();;//直接执行SPL脚本//ResultSet rs = st.executeQuery("=100.new(~:baseNum,~*~:square2)");//调用SPL脚本文件CallableStatement st = conn.prepareCall("{call SplScript(?, ?)}");st.setObject(1, 3000);st.setObject(2, 5000);ResultSet result=st.execute();...高效开发敏捷语法在结构化数据计算方面,SPL提供了独立的计算语法和丰富的计算类库,同时支持过程计算使得复杂计算实现也很简单。前面举的计算股票最长连涨天数的例子,用SPL实现是这样的:A1    =db.query(“select * from stock order by day”)2    =A1.group@i(price<price[-1]).max(~.len())-1按交易日排好序,将连涨的记录分到一组,然后求最大值-1就是最长连续上涨天数了,完全按照自然思维实现,不用绕来绕去,比SQL简单不少。再比如根据用户登录记录列出每个用户最近一次登录间隔:A    1    =ulogin.groups(uid;top(2,-logtime))    最后2个登录记录2    =A1.new(uid,#2(1).logtime-#2(2).logtime:interval)    计算间隔支持分步的SPL语法完成过程计算很方便。SPL提供了丰富的计算类库,可以更进一步简化运算。直观易用开发环境同时,SPL还提供了简洁易用的开发环境,单步执行、设置断点,所见即所得的结果预览窗口…,开发效率也更高。多数据源支持SPL还提供了多样性数据源支持,多种数据源可以直接使用,相比大数据平台需要数据先“入库”才能计算,SPL的体系更加开放。SPL支持的部分数据源(仍在扩展中…)不仅如此,SPL还支持多种数据源混合计算,充分发挥各类数据源自身的优势,扩展大数据平台的开放性。同时,直接使用多种数据源开发实现上也更简单,进一步提升开发效率。热切换SPL是解释执行的,天然支持热切换,这对Java体系下的大数据平台是重大利好。基于SPL的大数据计算逻辑编写、修改和运维都不需要重启,实时生效,开发运维更加便捷。高计算性能前面我们说过,高性能与高开发效率本质上是一回事,基于SPL的简洁语法更容易写出高性能算法。同时,SPL还提供了众多高性能数据存储和高性能算法机制,SQL中很难实现的高性能算法及存储方案用SPL却可以轻松实现,而软件提高性能关键就在于算法和存储。例如前面说过的TopN运算,在SPL中TopN被理解为聚合运算,这样可以将高复杂度的排序转换成低复杂度的聚合运算,而且很还能扩展应用范围。A    1    =file(“data.ctx”).create().cursor()    2    =A1.groups(;top(10,amount))    金额在前 10 名的订单3    =A1.groups(area;top(10,amount))    每个地区金额在前 10 名的订单这里的语句中没有排序字样,也不会产生大排序的动作,在全集还是分组中计算TopN的语法基本一致,而且都会有较高的性能。以下是一些用SPL实现的高性能计算案例:开源 SPL 提速保险公司团保明细单查询 2000+ 倍开源 SPL 提升银行自助分析从 5 并发到 100 并发开源 SPL 提速银行用户画像客群交集计算 200+ 倍开源 SPL 优化银行预计算固定查询成实时灵活查询开源 SPL 将银行手机账户查询的预先关联变成实时关联开源 SPL 提速银行资金头寸报表 20+ 倍开源 SPL 提速银行贷款协议跑批 10+ 倍开源 SPL 优化保险公司跑批优从 2 小时到 17 分钟开源 SPL 提速银行 POS 机交易报表 30+ 倍开源 SPL 提速银行贷款跑批任务 150+ 倍开源 SPL 提速资产负债表 60 倍再多说两句,SPL没有基于关系数据模型,而是采用了一种创新的理论体系,在理论层面就进行了创新,篇幅原因这里不再过多提及,写着简单跑得又快的数据库语言 SPL 这里有更细致一些的介绍,感兴趣的小伙伴也可以自行搜索,下载。SPL资料SPL官网SPL下载SPL源代码————————————————版权声明:本文为CSDN博主「3分钟秒懂大数据」的原创文章,遵循CC 4.0 BY-SA版权协议,转载请附上原文出处链接及本声明。原文链接:https://blog.csdn.net/weixin_38201936/article/details/125159358
  • [性能调优] 【DWS】【sql】pg_stat_user_functions 为什么没有数据
    【操作步骤&问题现象】产品文档中说这个视图是用来:显示命名空间中用户自定义函数(函数语言为非内部语言)的状态信息。为什么直接查这个视图里,没有数据呢【截图信息】
  • [其他] GaussDB(DWS) 语句处于排队状态执行慢
    问题现象:语句执行慢,处于排队状态,而之后提交的任务先执行了。下图显示14:34提交的SQL等待了2个小时,而15:49提交的SQL已经在执行中。问题影响:语句的等待时间超过了2个小时,因超时被查杀掉,始终无法执行排查流程:1、执行 select * from pg_stat_get_cgroup_info(0); 来查看cgroup的信息2、对于不同的cgroup,配置的percent值不同,表示优先级不同,percent值小的优先级低。3、执行SQL的用户属于不同的cgroup,因优先级不同造成执行慢。问题原因:由于优先级设置造成的用户作业排队。
  • [技术干货] 【Mybatis】单表多表查询,动态SQL使用[转载]
    单表查询操作参数占位符#{}和${}#{}:相当于JDBC里面替换占位符的操作方式(#{}->“”).相当于预编译处理(预编译处理可以防止SQL注入问题)${}:相当于直接替换(desc这种关键字),但这种不能预防SQL注入select * from userinfo where username='${name}'1${} VS #{}${}是直接替换,#{}是预执行;${} 会存在SQL 注入问题,#{}不存在SQL注入问题SQL 注入UserInfo userInfo = userMapper.login("admin","' or 1='1");1mysql> select * from userinfo where username = 'admin' and password ='' or 1='1';+----+----------+----------+-------+---------------------+---------------------+-------+| id | username | password | photo | createtime          | updatetime          | state |+----+----------+----------+-------+---------------------+---------------------+-------+|  1 | admin    | admin    |       | 2021-12-06 17:10:48 | 2021-12-06 17:10:48 |     1 |+----+----------+----------+-------+---------------------+---------------------+-------+1 row in set (0.00 sec)like模糊查询用concat进行字符串拼接   <select id="findListByName" resultMap="BaseMap">        select * from userinfo where username like concat('%',#{name},'%')    </select>多表查询操作一对一多表查询一对一的多表查询:需要设置resultMap中有个association标签,property对应实体类的属性名,resultMap是关联属性的字典映射(必须要设置),columnPrefix是设置前缀,当多表查询中有相同的字段的话,就会报错<?xml version="1.0" encoding="UTF-8"?><!DOCTYPE mapper PUBLIC "-//mybatis.org//DTD Mapper 3.0//EN" "http://mybatis.org/dtd/mybatis-3-mapper.dtd"><mapper namespace="com.example.demo.mapper.ArticleInfoMapper">    <resultMap id="BaseMap" type="com.example.demo.model.ArticleInfo">        <!--主键-->        <id property="id" column="id"></id>        <!--普通属性-->        <result property="updatetime" column="updatetime"></result>        <result property="title" column="title"></result>        <result property="content" column="content"></result>        <result property="createtime" column="createtime"></result>        <result property="rcount" column="rcount"></result>        <!--自定义对象属性-->        <association property="user"                     resultMap="com.example.demo.mapper.UserMapper.BaseMap"                     columnPrefix="u_">        </association>    </resultMap>    <select id="getAll" resultType="com.example.demo.model.ArticleInfo">        select a.*,u.id from articleinfo as a left join userinfo as u on a.uid = u.id;    </select>    <select id="getAll2" resultMap="BaseMap">        select a.*,u.id as u_id ,u.username as u_username,u.password as u_password from articleinfo as a left join userinfo as u on a.uid = u.id;    </select></mapper>一对多多表查询collection标签,用法同association <resultMap id="BaseMapper2" type="com.example.demo.model.UserInfo">        <!--映射主键的)(表中主键和程序实体类中的主键)-->        <id column="id" property="id"></id>        <!--普通列的映射-->        <result column="username" property="name"></result>        <result column="password" property="password"></result>        <result column="photo" property="photo"></result>        <result column="createtime" property="createtime"></result>        <result column="updatetime" property="updatetime"></result>        <!--外部关联-->        <collection property="artlist" resultMap="com.example.demo.mapper.ArticleInfoMapper.BaseMap"                    columnPrefix="a_"></collection>    </resultMap> <select id="getAll3" resultMap="BaseMapper2">        select u.*,a.id a_id,a.title a_title from userinfo u left join articleinfo a on u.id=a.uid </select>动态SQL使用if标签注册分为必填和选填,如果在添加用户的时候有不确定的字段传入,就需要使用动态标签if来判断//p是传递过来的参数名,并不是表的字段名 <insert id="add3">        insert into userinfo(username,password,        <if test="p!=null">         photo,        </if>         state)        values(#{username},#{password},        <if test="p!=null">            #{p},        </if>       #{state}) </insert>trim标签trim标签的属性:prefix:表示整个语句块,以prefix的值作为前缀suffix:表示整个语句块,以suffix的值作为后缀prefixOverrides:去掉最前面的符合条件的字符suffixOverrides:去掉最后面的符合条件的字符 <insert id="add4">        insert into userinfo        <trim prefix="(" suffix=")" suffixOverrides=",">            <if test="username!=null">                username,            </if>            <if test="password!=null">                password,            </if>            <if test="p!=null">                photo,            </if>            <if test="state!=null">                state,            </if>        </trim>        values        <trim prefix="(" suffix=")" suffixOverrides=",">            <if test="username!=null">                #{username},            </if>            <if test="password!=null">                #{password},            </if>            <if test="p!=null">                #{p},            </if>            <if test="state!=null">                #{state},            </if>        </trim>    </insert>where标签where标签首先可以帮助我们生成where,如果有查询条件,那么就生成where,如果没有查询条件,就会忽略where其次where标签可以判断第一个查询条件前面有没有and,如果有则会删除  <select id="login2" resultType="com.example.demo.model.UserInfo">        select * from userinfo        <where>        <if test="username!=null">            username=#{username}        </if>        <if test="password!=null">            and password=#{password}        </if>        </where>    </select>set标签和where的使用基本一样可以自动帮助你处理最后一个逗号,并且自动写set    <update id="update" parameterType="map">        update blog        <set>            <if test="newTitle != null">                title=#{newTitle},            </if>            <if test="newAuthor != null">                author=#{newAuthor},            </if>            <if test="newViews != null">                views = #{newViews}            </if>        </set>        <where>            <if test="id != null">                id=#{id}            </if>            <if test="title != null">                and title=#{title}            </if>            <if test="author != null">                and author=#{author}            </if>            <if test="views != null">                and views = #{views}            </if>        </where>    </update>foreach标签foreach属性:collection:参数集合的名字item:给接下来要遍历的集合起的名字open:加的前缀是什么close:加的后缀是什么separator:每次遍历之间间隔的字符串 <delete id="dels">        delete from userinfo where id in        <foreach collection="list" item="item" open="(" close=")" separator="," >            #{item}        </foreach> </delete>————————————————版权声明:本文为CSDN博主「亚太地区百大最帅面孔第101名」的原创文章,遵循CC 4.0 BY-SA版权协议,转载请附上原文出处链接及本声明。原文链接:https://blog.csdn.net/partworld/article/details/125232457
  • [知识分享] openGauss内核:SQL解析过程分析
    摘要:在传统数据库中SQL引擎一般指对用户输入的SQL语句进行解析、优化的软件模块。SQL的解析过程主要分为:词法、语法和语义分析。本文分享自华为云社区《 openGauss内核分析(三):SQL解析》,作者:Gauss松鼠会。在传统数据库中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引擎开始执行查询优化,在下一期中再具体分析。
  • [知识分享] jfinal中如何使用过滤器监控Druid监听SQL执行?
    【摘要】 问题起源最开始我想做的是通过拦截器拦截SQL执行,比如类似与PageHelper这种插件,通过拦截器或过滤器,手动修改SQL语句,以实现某些业务需求,比如执行分页,或者限制访问的数据权限等等。但是查到资料说过滤器不是干这个的,干这个的是数据库中间件干的事情,比如MyCat等。但是经过测试发现,过滤器至少可以监听每一个SQL的执行与返回结果。因此,将这一次探索过程记录下来。 配置过滤器在jf...本文分享自华为云社区《jfinal中使用过滤器监控Druid的SQL执行【五月07】》,作者:KevinQ 。问题起源最开始我想做的是通过拦截器拦截SQL执行,比如类似与PageHelper这种插件,通过拦截器或过滤器,手动修改SQL语句,以实现某些业务需求,比如执行分页,或者限制访问的数据权限等等。但是查到资料说过滤器不是干这个的,干这个的是数据库中间件干的事情,比如MyCat等。但是经过测试发现,过滤器至少可以监听每一个SQL的执行与返回结果。因此,将这一次探索过程记录下来。配置过滤器在jfinal的启动配置类中,有一个函数configPlugin(Plugins me)函数来配置插件,这个函数会在jfinal启动时调用,这个函数的参数是Plugins me,这个参数是一个插件管理器,可以通过这个插件管理器来添加插件。数据库插件Druid就是在该函数内添加的。public void configPlugin(Plugins me) { DruidPlugin druidPlugin = createDruidPlugin_holdoa(); druidPlugin.setPublicKey(p.get("publicKeydebug").trim()); wallFilter = new WallFilter(); wallFilter.setDbType("mysql"); druidPlugin_oa.addFilter(wallFilter); druidPlugin_oa.addFilter(new StatFilter()); me.add(druidPlugin); } 我们参考WallFilter以及StatFilter也创建一个过滤器类:import com.alibaba.druid.filter.FilterEventAdapter; public class DataScopeFilter extends FilterEventAdapter { } 我们发现FilterEventAdapter中的方法大概有这几个:public boolean statement_execute(FilterChain chain, StatementProxy statement, String sql) throws SQLException {...} protected void statementExecuteUpdateBefore(StatementProxy statement, String sql) {...} protected void statementExecuteUpdateAfter(StatementProxy statement, String sql, int updateCount) {...} protected void statementExecuteQueryBefore(StatementProxy statement, String sql) {...} protected void statementExecuteQueryAfter(StatementProxy statement, String sql, ResultSetProxy resultSet) {...} protected void statementExecuteBefore(StatementProxy statement, String sql) {...} protected void statementExecuteAfter(StatementProxy statement, String sql, boolean result) {...}我们复写这几个方法来看一下(排除Update方法,因为我们更关心查询语句)package xxxx.xxxx; import com.alibaba.druid.filter.FilterChain; import com.alibaba.druid.filter.FilterEventAdapter; import com.alibaba.druid.proxy.jdbc.ResultSetProxy; import com.alibaba.druid.proxy.jdbc.StatementProxy; import com.jfinal.kit.LogKit; import java.sql.SQLException; public class DataScopeFilter extends FilterEventAdapter { @Override public boolean statement_execute(FilterChain chain, StatementProxy statement, String sql) throws SQLException { LogKit.info("statement_execute"); return super.statement_execute(chain, statement, sql); } @Override protected void statementExecuteQueryBefore(StatementProxy statement, String sql) { LogKit.info("statementExecuteQueryBefore"); super.statementExecuteQueryBefore(statement, sql); } @Override protected void statementExecuteQueryAfter(StatementProxy statement, String sql, ResultSetProxy resultSet) { LogKit.info("statementExecuteQueryAfter"); super.statementExecuteQueryAfter(statement, sql, resultSet); } @Override protected void statementExecuteBefore(StatementProxy statement, String sql) { LogKit.info("statementExecuteBefore"); super.statementExecuteBefore(statement, sql); } @Override protected void statementExecuteAfter(StatementProxy statement, String sql, boolean result) { LogKit.info("statementExecuteAfter"); super.statementExecuteAfter(statement, sql, result); } @Override public ResultSetProxy statement_executeQuery(FilterChain chain, StatementProxy statement, String sql) throws SQLException { LogKit.info("statement_executeQuery"); return super.statement_executeQuery(chain, statement, sql); } } 然后再config配置类中添加过滤器:druidPlugin.addFilter(new DataScopeFilter());发起其执行顺序为:statement_executeQuery statementExecuteQueryBefore statementExecuteQueryAfter查看父级代码,发现其执行逻辑是,首先执行statement_executeQuery,然后因为调用父级的方法,而父级方法体为:@Override public ResultSetProxy statement_executeQuery(FilterChain chain, StatementProxy statement, String sql) throws SQLException { statementExecuteQueryBefore(statement, sql); try { ResultSetProxy resultSet = super.statement_executeQuery(chain, statement, sql); if (resultSet != null) { statementExecuteQueryAfter(statement, sql, resultSet); resultSetOpenAfter(resultSet); } return resultSet; } catch (SQLException error) { statement_executeErrorAfter(statement, sql, error); throw error; } catch (RuntimeException error) { statement_executeErrorAfter(statement, sql, error); throw error; } catch (Error error) { statement_executeErrorAfter(statement, sql, error); throw error; } }从而进一步触发statementExecuteQueryBefore方法与statementExecuteQueryAfter方法。因此我们,修改statement_executeQuery方法: @Override public ResultSetProxy statement_executeQuery(FilterChain chain, StatementProxy statement, String sql) throws SQLException { statementExecuteQueryBefore(statement, sql); ResultSetProxy result = chain.statement_executeQuery(statement, sql); statementExecuteQueryAfter(statement, sql, result); return result; }如此,便让输出结果为:statementExecuteQueryBefore statement_executeQuery statementExecuteQueryAfter我们可以在Before或者After方法中添加一些逻辑,比如:记录SQL的实际执行人,操作时间,请求执行SQL的接口。sql被声明为final类型发现执行的SQL在Druid中对应的类是:DruidPooledPreparedStatement,其类结构为:public class DruidPooledPreparedStatement extends DruidPooledStatement implements PreparedStatement { private final static Log LOG = LogFactory.getLog(DruidPooledPreparedStatement.class); private final PreparedStatementHolder holder; private final PreparedStatement stmt; private final String sql; .... }这也就以为着,该类一旦创建,SQL设置后就不允许再修改了,因此,我们需要修改SQL的话,就需要在prepared对象生成之前就修改到对应的执行SQL。在调试过程中,发现需要覆盖下面这个方法:@Override public PreparedStatementProxy connection_prepareStatement(FilterChain chain, ConnectionProxy connection, String sql) throws SQLException { // 可以达到修改SQL的目的 sql += " LIMIT 1"; PreparedStatementProxy statement = super.connection_prepareStatement(chain, connection, sql); statementPrepareAfter(statement); return statement; }我们可以在这里添加自定义的SQL修改逻辑,比如添加数据权限等等。
  • [技术干货] 受到华尔街追捧,MongoDB热度直线上升
    6月7日(美国时间),MongoDB年度最大的活动MongoDB World 2022在纽约举行。会上,MongoDB发布了公司新愿景——成为开发者数据平台提供商,同时还发布了一系列新产品和功能,包括其旗舰数据库 MongoDB 6.0、增强的分析功能、Atlas数据湖服务以及可查询加密数据技术等。这些新功能将进一步优化开发人员的体验、简化发布流程、加速创新步伐。MongoDB World是MongoDB每年一度的市场活动,会议当天MongoDB股价飙升7.65%,领涨美股云计算板块,引来了更多人关注。实际上,近几个月来,MongoDB一直很受华尔街关注,热度直逼网红股Snowflake,包括摩根斯坦利、花旗银行等多家分析机构分析师给予MongoDB“增持”的评级。MongoDB公司于2007年在纽约成立,2017年10月在纳斯达克上市,是纳斯达克第一家上市的数据库公司。MongoDB从成立以来一直处于高速增长之中,市值从最初的12亿美元上涨到目前的200亿美元左右,一度突破300亿美元。与此同时,MongoDB已经成为文档数据库的事实标准,AWS、Azure等云平台商推出的文档数据库纷纷选择与其兼容,并专门为其提供API接口。“今天的MongoDB是世界上用于构建现代应用程序最受欢迎的数据库。”MongoDB首席执行官兼总裁 Dev Ittycheria 在大会主题演讲中表示。下面这些数字提供了很好的证明:截至目前MongoDB社区版下载量超过了2.65亿次,仅仅过去12个月的下载量就比MongoDB之前下载量的总和还要多(中国是下载量最大的地区),在全球100多个国家拥有3.5万家客户。此外,MongoDB云服务版Atlas进展顺利,已经在AWS、Google Cloud和 Azure的全球95个区域运行,MongoDB 还与世界各地的其他云提供商合作,其中包括中国的阿里云和腾讯云。 伴随着MongoDB产品功能的不断丰富,MongoDB不再把自己仅仅定位为文档型数据库,而是希望成为一个通用数据库,同时不断向更多应用场景扩张。尤为值得关注的是,MongoDB正在向传统数据库领域渗透,与Oracle等传统数据库巨头的竞争正在悄悄展开,这一场竞争结果或许将对数据库市场的未来演变带来重要影响。 01  提供现代而便捷的开发体验MongoDB是一个专门为“开发者(Developer)”打造的数据库,也正是这一出发点让MongoDB成为最受开发者认可的数据库之一。在DB-Engine排行榜上,MongoDB多年来一直和Oracle、MySQL、SQL Server、PostgreSQL一起成为最受关注的5个数据库产品。  “MongoDB致力于帮助开发人员提升效率,减少他们非必要的工作。这是公司从15年前成立就一直秉承的核心理念,也正是这一理念让MongoDB拥有世界上最忠实的开发者社区。” MongoDB首席技术官Mark Porter在接受记者采访时表示。 Dev Ittycheria在大会的主题演讲中也强调了类似观点,他说:“我们构建的每一个产品,我们开发的每一个功能都是为了提高开发人员的生产力。”如何让开发人员变得异常快速和高效?MongoDB选择以文档模型为突破口。该模型改变了传统数据库以表格来组织数据和与数据交互的方式,灵活而且可以很容易扩展。基于此模型,MongoDB构建了整个产品系列。 “未来几年,不计其数的全新应用程序将不断面世,这些应用程序可以提供令人惊艳的客户体验,满足企业转型的功能需求,通过更尖端的自动化水平提高运营效率等,这些都离不开一个高度可扩展、云原生、全球分布式的数据平台。”Dev Ittycheria表示,MongoDB的愿景是打造一个开发者数据平台,为开发者提供现代、便捷的使用体验,广泛支持各种用例,并满足最为严苛的性能和规模要求。 Dev Ittycheria援引 IDC的数据说,到2025年将有7.5亿个新的数字应用程序,而且未来4年构建的应用程序将比前40年构建的应用程序还多。 创新的步伐正在加快,意味着开发人员的生产力越来越重要。为了支持开发人员创新的需求,MongoDB致力于打造出一个高效的开发者数据平台,以帮助开发者更快速地开发应用,让企业迅速把握机遇,以更好地应对挑战。02 不仅仅是文档型数据库MongoDB属于文档型数据库,这也是人们对MongoDB的普遍认知。不过,MongoDB显然无意强化人们的这一认知,而是正好相反,MongoDB希望突出其通用性,以将应用场景拓展到更广泛的领域。为此,MongoDB不断增加支持的数据类型。 Mark Porter说,今天的MongoDB几乎支持所有的数据类型,包括文档、图形、数组、文本、对象、地理空间、时间序列和关系型数据等,同时一直在强化对这些数据类型的支持能力。其目的就希望通过一个统一的数据库来帮助开发者处理所有需求,从而简化开发者的工作。 因为在MongoDB看来,“通用”让开发人员的生活更轻松,而 MongoDB 一直专注于为开发人员提供便利。比如,MongoDB 6.0新增的时间序列集合就可以简化应用程序构建,加快构建速度并降低成本。 在即将发布的MongoDB 6.0版本中,时间序列集合功能将支持测量数据库的二级索引,并针对读取性能进行改进和优化,以便更快地对时序数据进行排序。尽管市场上有许多专门针对时间序列数据的数据库,但许多人可能不想为此专门建立一个新的数据库。 一般而言,NoSQL与关系型数据库是两个不同的市场,两者基本不会存在直接竞争关系。不过,正如MongoDB并不把自己限定在NoSQL一样,它对关系数据库市场同样有兴趣。实际上,MongoDB正在将覆盖领域扩展到关系型数据市场,大会上推出的Rational Migrator工具就是举措之一,它可帮助用户把数据从关系型数据库(包括Oracle、SQL Server、MySQL和 PostgreSQL)轻松地迁移到MongoDB。 MongoDB未来与关系型数据库会是竞争关系吗?对此,Mark Porter表示,关系数据库太不灵活,也不便于扩展。多年来,MongoDB一直在与关系数据库竞争并赢得胜利。因为MongoDB将通用的现代分布式架构与强大的基础数据库功能(如ACID事务和企业级安全性)相结合,让用户能比利用其他平台更快地构建创新性、高度可扩展的应用程序。 “Relational Migrator工具的推出是回应众多客户的需求,他们希望更容易地将旧的遗留应用程序转移到 MongoDB。”Mark Porter说。03  满足更多应用场景的需求今年MongoDB World大会上发布的新产品和功能超过了150项,达到了近几年的一个高峰。其中一些让人尤为印象深刻,分析能力就是其中之一。 随着大数据时代的兴起,数据分析的需求越来越普遍,对分析的支持成为数据库能力的重要组成部分。显然,MongoDB也注意到用户不断增长的分析需求。不过,MongoDB最先响应的是更靠近应用程序的分析需求,而不是离线分析。这一点也不难理解,在 MongoDB 的世界里,前者能让开发人员能够更方便地构建应用程序,而离线分析可能更多地面向数据分析人员。 为了方便开发人员的工作,MongoDB推出了几种新手段来简化针对运营数据的分析,支持应用内分析的列存储索引技术就是其中之一。由于列格式非常适合分析类工作,因此该索引选项使开发人员可以轻松地将文档保存在适合其应用程序的模型中,而无需迁移数据,从而大幅提升许多常见分析查询的速度。此外,分析节点目前已支持单独扩展,便于团队独立调整其操作和分析查询性能,避免出现过度配置或配置不足情况。 “我们会优先关注应用内分析能力,如产品推荐、防欺诈等,而不是OLAP。前者要在很短的时间内完成,一般低于1秒,有的甚至在100毫秒内,完全不同于数据仓库。”Mark Porter表示。 其他支持分析的技术还包括新的Atlas SQL Interface、Atlas Data Federation 、Atlas Data Lake、Cluster-to-Cluster Synchronization等。 另外,MongoDB的云服务版Atlas尤其值得一提。MongoDB今天的成功很大程度上就是因为Atlas。Atlas于2016年正式推出以来,增长迅速,最近4个季度都保持80%的增长,贡献了MongoDB 60%的营收,对MongoDB的重要性由此可见一斑。 高速增长的背后是MongoDB一直在不断丰富Atlas的功能,除了前面提到的分析能力方面增强外,今年Atlas还新增了不少其他功能,包括Atlas Search、Atlas Device Sync、Data API等。其中,Atlas Search是在应用程序中构建基于相关性搜索功能的最迅速且最简单的方法;Atlas Device Sync可以实现Atlas与移动端数据库Realm的同步;Data API无需额外的开发就可以通过HTTPS协议为Atlas中的数据提供安全的API访问服务。 近年来,无服务器计算是云计算市场的热点,此前MongoDB已经推出了Atlas Serverless预览版。在大会上,MongoDB宣布Atlas Serverless已全面上市,用户已经可以在AWS、Azure、GCP的平台中使用这一功能。04  突破加密技术,保护使用中的数据如果要问今年MongoDB World大会的参会者,哪项技术发布印象最深?可查询加密(Queryable Encryption)很可能会高票当选。 这些年,MongoDB一直在持续提升安全性,但可查询加密将其安全性提高了新的层次。虽然大多数数据库已经对如何保护静态或动态数据的安全有了很好的办法,但一直无法对使用中的数据进行很好地保护。目前,针对使用中数据进行加密技术普遍很复杂,需要使用来自应用程序端的自定义代码,还需要加密密钥管理,以便对数据进行加密和解密;同时,开发者还需要掌握一定的密码学经验。另外,查询功能会受限,甚至有些不具备查询功能,这导致应用层数据加密变得格外困难,性能上也很难得到保证。 可查询加密技术支持客户从客户端加密敏感数据,将完全随机加密的数据存储在数据库服务器端,并对加密数据执行表达式查询。随着可查询加密技术的推出,MongoDB成为唯一一家支持客户对完全随机加密数据执行表达式查询的数据库提供商,包括等值查询(预览版中可用)、范围查询、前缀查询、后缀查询、子串查询等(即将推出)。对于那些既需要执行表达式查询,又需要确保数据安全的企业而言,这无疑是一个巨大优势。05  写在最后当前,数据库市场仍处于高速增长态势。根据Gartner发布的《2021年数据库管理系统(DBMS)市场研究报告》,非关系型数据库市场2021年总收入为 148亿美元,年增长22%,占整个数据库市场的19%。其中,MongoDB表现亮眼,年增长达到47%,远高于市场平均水平。与AWS、Google、阿里云、Cloudera一起位列市场前5位,已经把其他NoSQL厂商甩在了身后。 随着MongoDB的成长,MongoDB面对的竞争对手会越来愈多,其中有像Snowflake这样的高增长热门公司,更有像甲骨文和 IBM 这样的老牌公司,竞争可能会非常激烈。特别是与传统数据库之间,这是一场事关数据库市场演变的竞争,我们期待竞争格局的演变。
  • [问题求助] hive元数据内置到DBService,如何通过sql获取对应的元数据信息
    hive元数据内置到DBService,如何通过sql获取对应的元数据信息
  • [问题求助] 华为沃土开发平台没有发布包没有SQL执行权限
    【功能模块】华为u沃土【操作步骤&问题现象】1、2、【截图信息】【日志信息】(可选,上传日志内容或者附件)
  • [干货汇总] openGauss内核:简单查询的执行
    摘要:本文主要分析简单查询语句在业务处理线程Postgres上的执行流程,并介绍如何利用gdb梳理代码逻辑。本文分享自华为云社区《openGauss内核分析(二):简单查询的执行》,作者:Gauss松鼠会。简单查询的执行SQL引擎是数据库系统的入口,执行用户简单查询的入口函数是exec_simple_query。运行在业务处理线程Postgres。通常可以把SQL引擎分成SQL解析和查询优化两个主要的模块,SQL引擎对输入的SQL语言进行词法分析、语法分析、语义分析,从而生成逻辑执行计划,逻辑执行计划经过代数优化和代价优化之后,产生物理执行计划。在SQL引擎将用户的查询解析优化成可执行的计划之后,数据库进入查询执行阶段。执行器基于执行计划对相关数据进行提取、运算、更新、删除等操作,以达到用户查询想要实现的目的。exec_simple_query1.start_xact_command():开始一个事务2.pg_parse_query():对查询语句进行词法和语法分析,生成一个或者多个初始的语法分析树3. 进入foreach (parsetree_item, parsetree_list)循环,对每个语法分析树执行查询4. pg_**yze_and_rewrite():根据语法分析树生成基于Query数据结构的逻辑查询树,并进行重写等操作5. pg_plan_queries():对逻辑查询树进行优化,生成查询计划6. CreatePortal():创建Portal, Portal是执行SQL语句的载体,每一条SQL对应唯一的Portal7. PortalStart():负责进行Portal结构体初始化工作,包括执行算子初始化、内存上下文分配等8. PortalRun():负责真正的执行和运算,它是执行器的核心9. PortalDrop():负责最后的清理工作,主要是数据结构、缓存的清理10. finish_xact_command():完成事务提交11. EndCommand():通知客户端查询执行完成gdb调试调试需要用到符号信息,configure使用如下命令./configure --gcc-version=7.3.0 CC=g++ CFLAGS='-O0' --prefix=$GAUSSHOME --3rd=$BINARYLIBS --enable-debug --enable-cassert --enable-thread-safety --with-readline --without-zlibgdb attach 进程号,这里进程号为17012gdb attach 17012info threads查看所有线程,t 线程号切换线程,bt可以查看线程调用栈也可以使用linux工具gstack 打印函数调用栈以调试select语句为例,gdb attach 进程号,在exec_simple_query打上断点,执行select语句即可开始调试
  • [技术干货] 写着简单跑得又快的数据库语言 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操作会物理的清空数据表,并将其占用的空间归还给操作系统。