Lumen-实现-SQL-监听

首发于:我的博客之前 Lumen 框架从 5.6 升级到 5.7。发现 laravel-sql-logger 包不能正常纪录日志了。进行排查,发现是 Lumen 框架没有对 DB 类型注入 event 对象,导致不能正常对其进行SQL监听。 那么解决方案也非常简单。 // file: bootstrap/app.php$app["db"]->connection()->setEventDispatcher($app["events"]); // 在下面的注册前加入这一行即可$app->register(Mnabialek\LaravelSqlLogger\Providers\ServiceProvider::class);但是这也让我对如何实现SQL纪录产生了兴趣。接下来,我们就具体了解一下如何实现SQL监听。 我们知道在Larvel上非常简单。只需要如下方法即可对其进行SQL监听: namespace App\Providers;use Illuminate\Support\Facades\DB;use Illuminate\Support\ServiceProvider;class AppServiceProvider extends ServiceProvider{ /** * 启动应用服务 * * @return void */ public function boot() { DB::listen(function ($query) { // $query->sql // $query->bindings // $query->time }); } //...}但是在 Lumen 上这种办法是没有办法使用的。Lumen有一些自己的调试SQL的方法,但是这些并不是我们想要的。所以我们只能自己写监听事件。 具体的解决方案是,我们首先创建一个Listener文件。 // file: app\Listeners\QueryListener.phpnamespace App\Listeners;use Illuminate\Database\Events\QueryExecuted;class QueryListener{ /** * Create the event listener. * * @return void */ public function __construct() { // } /** * Handle the event. * * @param ExampleEvent $event * @return void */ public function handle(QueryExecuted $event) { dd($event); // 此处直接对其进行打印 }}接下来我们直接对其进行注册 ...

July 12, 2019 · 3 min · jiezi

你真的了解SQL吗SQL优化最佳实践作者带你重新了解SQL

一、SQL :一种熟悉又陌生的编程语言这里有几个关键词;“熟悉”、“陌生”、“编程语言”。 说它“熟悉”,是因为它是DBA和广大开发人员,操作数据库的主要手段,几乎每天都在使用。说它“陌生”,是很多人只是简单的使用它,至于它是怎么工作的?如何才能让它更高效的工作?却从来没有考虑过。 这里把SQL归结为一种“编程语言”,可能跟很多人对它的认知不同。让我们看看它的简单定义(以下内容摘自百度百科) 结构化查询语言(Structured Query Language),简称SQL,是一种特殊目的的编程语言,是一种数据库查询和程序设计语言,用于存取数据以及查询、更新和管理关系数据库系统。结构化查询语言是高级的非过程化编程语言,允许用户在高层数据结构上工作。它不要求用户指定对数据的存放方法,也不需要用户了解具体的数据存放方式,所以具有完全不同底层结构的不同数据库系统, 可以使用相同的结构化查询语言作为数据输入与管理的接口。结构化查询语言语句可以嵌套,这使它具有极大的灵活性和强大的功能。总结一句话,SQL是一种非过程化的的编程语言,可通过它去访问关系型数据库系统。 二、你真的了解“SQL”吗?下面我会通过一个小例子,看看大家是否真正了解SQL。 这是一个很简单的示例,是关于SQL语句执行顺序的。这里将一个普通的SELECT语句,拆分为三个子句。那么在实际的执行过程中,是按照什么顺序处理的呢?这里有A-F六个选项,大家可以思考选择一下… 最终的答案是D,即按照先执行FROM子句,然后WHERE子句,最后是SELECT部分。 针对上面的示例,让我们真实构造一个场景,通过查看执行计划看看是否按照我们选择的顺序执行的。关于执行计划的判读,我后面会专门谈到。这里我先解释一下整个执行过程。 第一步,是按照全表扫描的方式访问了对象表(EMP)。对应于语句中的FROM部分。第二步,是对提取出的结果集进行了过滤(filter部分),即将满足条件的记录筛选出来。对应于语句中的WHERE部分。第三步,是对满足条件的记录进行字段投射,即将需要显示的字段提取出来。对应于语句中的SELECT部分。 这是一个详细的SQL各部分执行顺序的说明。 通过对执行顺序的理解,可以为我们未来的优化工作带来很大帮助。一个很浅显的认识就是,优化动作越靠前越好。 三、SQL现在是否仍然重要?这里引入了一个新的问题,在现有阶段SQL语言是否还重要? 之所以引入这一话题,是因为随着NOSQL、NEWSQL、BIGDATA等技术逐步成熟推广,“SQL语言在现阶段已经变得不那么重要”成为一些人的观点。那实际情况又是如何呢? 让我们先来看一张经典的图。图中描述了传统SMP架构的关系型数据库、MPP架构的NEWSQL、MPP架构的NoSQL不同方案的适用场景对比。 从上面的“数据价值密度、实时性”来看,传统关系型数据库适合于价值密度更高、实时性要求更高的场景(这也就不难理解类似账户、金额类信息都是保存在传统关系型数据库中);MPP架构的NewSQL次之,MPP架构的NoSQL更适合于低价值、实时性要求不高的场景。 从下面的“数据规模”来看,传统关系型数据库适合保存的大小限制在TB级别,而后两者可在更大尺度上(PB、EB)级保存数据。 从下面的“典型场景”来看,传统关系型数据库适合于OLTP在线交易系统;MPP架构的NewSQL适合于OLAP在线分析系统;而NoSQL的使用场景较多(利于KV型需求、数据挖掘等均可以考虑)。 最后从“数据特征”来看,前两者适合于保存结构化数据,后者更适合于半结构化、乃至非结构化数据的保存。 归纳一下,不同技术有其各自特点,不存在谁代替谁的问题。传统关系型数据库有其自身鲜明特点,在某些场合依然是不二选择。而作为其主要交互语言,SQL必然长期存在发展下去。 我们再来对比一下传统数据库与大数据技术。从数据量、增长型、多样化、价值等维度对比两种技术,各自有其适用场景。 对于大数据领域而言,各种技术层出不穷。但对于广大使用者来说,往往会存在一定的使用门槛,因此现在的一种趋势就是在大数据领域也引入“类SQL”,以类似SQL的方式访问数据。这对于广大使用者来说,无疑大大降低了使用门槛。 解答一些疑问: NoSQL、NewSQL已经超越了传统数据库,SQL没有了用武之地! 各种技术有着各自适合的不同场景,不能一概而论。SQL语言作为关系型数据库的主要访问方式,依然有其用武之地。 以后都是云时代了,谁还用关系型数据库! 对于价值密度高,严格一致性的场景,仍然适合采用关系型数据库作为解决方案。 我编程都是用OR Mapping工具,从不需要写SQL! 的确,引入OR Mapping工具大大提高了生产效率,但是它的副作用也很明显,那就是对语句的运行效率失去了控制。很多低效的语句,往往是通过工具直接生成的。这也是为什么有的Mapping工具还提供了原始的SQL接口,用来保证关键语句的执行效率。 大数据时代,我们都用Hadoop、Spark了,不用写SQL啦! 无论是使用Hadoop、Spark都是可以通过编写程序完成数据分析的,但其生产效率往往很低。这也是为什么产生了Hive 、Spark SQL等“类SQL”的解决方案来提高生产效率。 数据库处理能力很强,不用太在意SQL性能! 的确,随着多核CPU、大内存、闪存等硬件技术的发展,数据库的处理能力较以前有了很大的增强。但是SQL的性能依然很重要。后面我们可以看到,一个简单SQL语句就可以轻易地搞垮一个数据库。 SQL优化,找DBA就行了,我就不用学了! SQL优化是DBA的职责范畴,但对于开发人员来讲,更应该对自己的代码负责。如果能在开发阶段就注重SQL质量,会避免很多低级问题。 我只是个运维DBA,SQL优化我不行! DBA的发展可分为“运维DBA->开发DBA->数据架构师…”。如果只能完成数据库的运维类工作,无疑是技能的欠缺,也是对各人未来发展不利。况且,随着Paas云的逐步推广,对于数据库的运维需求越来越少,对于优化、设计、架构的要求越来越多。因此,SQL优化是每个DBA必须掌握的技能。 现在优化有工具了,很简单的! 的确现在有些工具可以为我们减少些优化分析工作,会自动给出一些优化建议。但是,作为DBA来讲,不仅要知其然,还要知其所以然。况且,数据库优化器本身就是一个非常复杂的组件,很难做到完全无误的优化,这就需要人工的介入,分析。 优化不就是加索引嘛,这有啥! 的确,加索引是一个非常常用的优化手段,但其不是唯一的。且很多情况下,加了索引可能导致性能更差。后面,会有一个案例说明。 四、SQL仍然很重要! 我们通过一个示例,说明一下理解SQL运行原理仍然很重要。 这是我在生产环境碰到的一个真实案例。Oracle数据库环境,两个表做关联。执行计划触目惊心,优化器评估返回的数据量为3505T条记录,计划返回量127P字节,总成本9890G,返回时间999:59:59。 从执行计划中可见,两表关联使用了笛卡尔积的关联方式。我们知道笛卡尔连接是指在两表连接没有任何连接条件的情况。一般情况下应尽量避免笛卡尔积,除非某些特殊场合。否则再强大的数据库,也无法处理。这是一个典型的多表关联缺乏连接条件,导致笛卡尔积,引发性能问题的案例。 从案例本身来讲,并没有什么特别之处,不过是开发人员疏忽,导致了一条质量很差的SQL。但从更深层次来讲,这个案例可以给我们带来如下启示: 开发人员的一个疏忽,造成了严重的后果,原来数据库竟是如此的脆弱。需要对数据库保持一种"敬畏"之心。电脑不是人脑,它不知道你的需求是什么,只能用写好的逻辑进行处理。不要去责怪开发人员,谁都会犯错误,关键是如何从制度上保证不再发生类似的问题。五、SQL优化法则下面我们来看看常见的优化法则。这里所说的优化法则,其实是指可以从那些角度去考虑SQL优化的问题。可以有很多种方式去看待它。下面列举一二。 这里来自阿里-叶正盛的一篇博客里的一张图,相信很多人都看过。这里提出了经典的漏斗优化法则,高度是指我们投入的资源,宽度是指可能实现的收益。从图中可见,“减少数据访问”是投入资源最少,而收益较多的方式;“增加硬件资源”是相对投入资源最多,而收益较少的一种方式。受时间所限,这里不展开说明了。 这是我总结的一个优化法则,简称为“DoDo”法则。 第一条,“Do Less or not do!”翻译过来,就是尽量让数据库少做工作、甚至不做工作。 ...

July 11, 2019 · 1 min · jiezi

如何连接NAS里的MySQL数据库

MySQL作为开放源代码的数据库管理系统,任何人都可以在GeneralPublicLicense的许可下下载并根据个性化的需要对其进行修改。您可以在TOS 中安装MySQL 服务器,以便您通过MySQL 高效的管理您的数据。TOS 应用中心提供的MySQL 服务器版本是MariaDB,这是一个被广泛使用的数据库。1.TOS系统-桌面-应用中心;2.下载安装 MariaDB;3.前往TOS 桌面,双击MariaDB 图标来运行应用程序;4.启用MySQL服务器,设置端口,点击应用;5.点击进入MySQL;6.输入用户名密码(初始用户名:root, 密码:admin);7.登录完成;8.如需修改密码,请点击 “修改密码”;如需删除数据库,请点击“删除数据库”,数据库的所有数据将被清除。

July 10, 2019 · 1 min · jiezi

开发进阶篇书写高效的SQL语句

调查显示,SQL是目前第二大编程语言,自诞生以来40多年一直发挥着重要的作用,有50%的开发者都在使用SQL。虽然使用非常广泛,但是SQL的质量水平却并不令人满意。 根据经验,80%的数据库问题是由于SQL引起的,而80%的SQL问题来自于20%的SQL语句,在一些高并发高负载的系统中,由于一条SQL的性能问题导致数据库整体出现异常的情况屡见不鲜。 那么,面对SQL引起的数据库问题,我们又该如何解决呢? 虽然我们都知道问题,但是该如何解决?如果你对SQL有一些了解,想必也就知道,大体有四种能力是必备的: 1.充分理解新特性的功能以及其适用场景 2.将数据整理处理作为思路 3.从一开始就考虑好这个SQL的执行计划,驱动表是谁,采用何种JOIN方式连接到被驱动表 4.严格过滤不必要的数据 可见,如果你能够拥有以上四种能力,必然可以解决SQL引起的数据库问题,也必然会成为SOL语句编写市场上极具价值、抢手的存在。 但是此刻,也许你会有个疑惑:我要如何才能拥有这些能力呢? 这也正是我们今天想要给你的答案:其实墨天轮里面一门课程可以帮你完美锻炼这四项能力,成为真正能通过SQL语句编写解决实际业务问题的稀缺人才。 这门课程就是——ACOUG 核心专家、Oracle ACE 总监,号称"Oracle 的百科全书"——杨廷琨老师主讲的《高效SQL语句编写》 至今,这门课程已经全部完结,饱受好评,想要得到解决问题的你,快戳下方链接领取99元优惠券,免费的观看学习吧(注:本课程原价99,优惠券99,课程完全免费哦~) 优惠券:https://cs.enmotech.com/invit... 领完优惠券快戳课程链接学习吧~ 课程链接:https://cs.enmotech.com/course/6接下来,我们再详细介绍下这门《高效SQL语句编写》课程凭什么可以帮你解决SQL引起的数据库问题 第一,这门课程足够系统和深入,能够让你全面的了解到SQL语句编写引起的问题,从而重视SQL的执行效率。 第二,这门课程中有众多真实需求和案例,结合理论逐一找到完美解决方案,项目组可以直接使用。 一名合格的开发人员,要成为更有价值的存在,仅仅只有单点技能,是远远不够的,你需要了解的足够全面,小到数据处理,大到执行计划的制定,你需要了解的足够全面。 基于此,帮助更多的开发人员解决SQL引起的数据库问题是《高效SQL语句编写》课程的初衷。 所以,这门课程几乎涵盖了SQL引起的数据库问题的全部知识点。(下面放上课程大纲供你参考) 第三,专业的主讲人老师,在《高效SQL语句编写》课程中,主讲老师是ACOUG 核心专家、Oracle ACE 总监,号称"Oracle 的百科全书"——杨廷琨老师 杨廷琨先生是中国地区的第一批Oracle ACE总监,也是 ITPUB 论坛上最活跃的分享者之一,他日均一篇的博客更新坚持了10年之久,影响了很多Oracle DBA和开发者的学习和成长,他在SQL开发方向的积累丰富,对于性能和效率具有深刻理解。 适用人群:需要解决SQL引起的数据库问题,从而完成从低阶到高阶的跃迁的开发人员。 还有重要的一点,就是使用优惠券观看学习可立减99元!此课程原价99,使用优惠券就是免费!数量有限(仅100张),先到先得!(优惠券使用方式:扫描下方二维码——进入优惠券页面——输入手机号,获取优惠券——注册登录——点击购买——购买成功——学习观看)以上,介绍得差不多了,抢完优惠券的话,赶快戳下方链接观看学习吧! 学习地址:https://cs.enmotech.com/course/6

July 9, 2019 · 1 min · jiezi

使用Spark-Streaming-SQL基于时间窗口进行数据统计

1.背景介绍流式计算一个很常见的场景是基于事件时间进行处理,常用于检测、监控、根据时间进行统计等系统中。比如埋点日志中每条日志记录了埋点处操作的时间,或者业务系统中记录了用户操作时间,用于统计各种操作处理的频率等,或者根据规则匹配,进行异常行为检测或监控系统告警。这样的时间数据都会包含在事件数据中,需要提取时间字段并根据一定的时间范围进行统计或者规则匹配等。使用Spark Streaming SQL可以很方便的对事件数据中的时间字段进行处理,同时Spark Streaming SQL提供的时间窗口函数可以将事件时间按照一定的时间区间对数据进行统计操作。本文通过讲解一个统计用户在过去5秒钟内点击网页次数的案例,介绍如何使用Spark Streaming SQL对事件时间进行操作。 2.时间窗语法说明Spark Streaming SQL支持两类窗口操作:滚动窗口(TUMBLING)和滑动窗口(HOPPING)。 2.1滚动窗口 滚动窗口(TUMBLING)根据每条数据的时间字段将数据分配到一个指定大小的窗口中进行操作,窗口以窗口大小为步长进行滑动,窗口之间不会出现重叠。例如:如果指定了一个5分钟大小的滚动窗口,数据会根据时间划分到 [0:00 - 0:05)、 [0:05, 0:10)、[0:10, 0:15)等窗口。 语法GROUP BY TUMBLING ( colName, windowDuration ) 示例对inventory表的inv_data_time时间列进行窗口操作,统计inv_quantity_on_hand的均值;窗口大小为1分钟。 SELECT avg(inv_quantity_on_hand) qohFROM inventoryGROUP BY TUMBLING (inv_data_time, interval 1 minute)2.2滑动窗口 滑动窗口(HOPPING),也被称作Sliding Window。不同于滚动窗口,滑动窗口可以设置窗口滑动的步长,所以窗口可以重叠。滑动窗口有两个参数:windowDuration和slideDuration。slideDuration为每次滑动的步长,windowDuration为窗口的大小。当slideDuration < windowDuration时窗口会重叠,每个元素会被分配到多个窗口中。所以,滚动窗口其实是滑动窗口的一种特殊情况,即slideDuration = windowDuration则等同于滚动窗口。 语法GROUP BY HOPPING ( colName, windowDuration, slideDuration ) 示例对inventory表的inv_data_time时间列进行窗口操作,统计inv_quantity_on_hand的均值;窗口为1分钟,滑动步长为30秒。 SELECT avg(inv_quantity_on_hand) qohFROM inventoryGROUP BY HOPPING (inv_data_time, interval 1 minute, interval 30 second)3.系统架构 业务日志收集到Aliyun SLS后,Spark对接SLS,通过Streaming SQL对数据进行处理并将统计后的结果写入HDFS中。后续的操作流程主要集中在Spark Streaming SQL接收SLS数据并写入HDFS的部分,有关日志的采集请参考日志服务。 4.操作流程4.1环境准备 ...

July 8, 2019 · 1 min · jiezi

DBASK问答集萃第三期

引言近期我们在DBASK小程序增加了众多专栏,其中包括盖国强、杨廷琨、罗海雄、张乐奕、黄廷忠、崔华、熊军、李真旭、何剑敏等专家栏目,还有2019年6月SCN兼容性问题、XTTS、备份恢复等技术专栏,另外蚂蚁金服OceanBase入驻小程序。欢迎大家阅读分享小程序中的专题栏目。 问答集萃接下来,我们分享本期整理出的问题和诊断总结,供大家参考学习,详细的诊断分析过程可以通过标题链接跳转到小程序中查看。 问题一、RMAN-20039: format requires %c when duplexing 备份时报错: RMAN-00571: ===========================================================RMAN-00569: =============== ERROR MESSAGE STACK FOLLOWS ===============RMAN-00571: ===========================================================RMAN-03009: failure of backup commandRMAN-20039: format requires %c when duplexing备份数据文件不加%C就会报错,加%C有两份一样的? ----备份脚本run{ allocate channel c1 device type disk;allocate channel c2 device type disk;allocate channel c3 device type disk;allocate channel c4 device type disk;crosscheck backup;sql 'alter system archive log current';backup spfile format '/bak/backup/spfile_%T_%s_%p_%c';#backup database format '/bak/backup/dbbk_0_%d_%t_%u_%s_%p';backup as compressed backupset incremental level 0 database format '/bak/backup/dbbk_0_%d_%t_%u_%s_%p';sql 'alter system archive log current';backup archivelog all format '/bak/backup/arc_%T_%s_%p_%c' delete all input;backup current controlfile format '/bak/backup/cntrl_%T_%s_%p_%c';crosscheck archivelog all;delete noprompt expired backup;delete noprompt obsolete;release channel c1;release channel c2;release channel c3;release channel c4;}诊断结论:如果设置不冗余就不需要加c%,否则就会出现你的报错。如果设置了冗余必须加%c,那么也就会产生相应的备份片。 ...

July 3, 2019 · 3 min · jiezi

SLS机器学习最佳实战批量时序异常检测

1. 高频检测场景1.1 场景一集群中有N台机器,每台机器中有M个时序指标(CPU、内存、IO、流量等),若单独的针对每条时序曲线做建模,要手写太多重复的SQL,且对平台的计算消耗特别大。该如何更好的应用SQL实现上述的场景需求? 1.2 场景二针对系统中的N条时序曲线进行异常检测后,有要如何快速知道:这其中有哪些时序曲线是有异常的呢? 2. 平台实验2.1 解决一针对场景一中描述的问题,我们给出如下的数据约束。其中数据在日志服务的LogStore中按照如下结构存储: timestamp : unix_time_stampmachine: name1metricName: cpu0metricValue: 50---timestamp : unix_time_stampmachine: name1metricName: cpu1metricValue: 50---timestamp : unix_time_stampmachine: name1metricName: memmetricValue: 50---timestamp : unix_time_stampmachine: name2metricName: memmetricValue: 60在上述的LogStore中我们先获取N个指标的时序信息: * | select timestamp - timestamp % 60 as time, machine, metricName, avg(metricValue) from log group by time, machine, metricName现在我们针对上述结果做批量的时序异常检测算法,并得到N个指标的检测结果: * | select machine, metricName, ts_predicate_aram(time, value, 5, 1, 1) as res from ( select timestamp - timestamp % 60 as time, machine, metricName, avg(metricValue) as value from log group by time, machine, metricName )group by machine, metricName通过上述SQL,我们得到的结果的结构如下 ...

July 1, 2019 · 2 min · jiezi

MaxCompute-费用暴涨之新增SQL分区裁剪失败

现象:因业务需求新增了SQL任务,这SQL扫描的表为分区表,且SQL条件里表只指定了一个分区,按指定的分区来看数据量并不大,但是SQL的费用非常高。费用比预想的结果相差几倍甚至10倍以上。 若只知道总体费用暴涨,但是没明确是什么任务暴涨,可以可以参考查看账单详情-使用记录文档,找出费用异常的记录。分析:我们先明确MaxCompute SQL后付费的计费公式:一条SQL执行的费用=扫描输入量 ️ SQL复杂度 ️ 0.3(¥/GB)。 变量主要是输入量和复杂度,但实际上复杂度最高也就为4,由复杂度引起的费用暴涨是比较罕见,我们不妨先把排查重点放在输入量上。 排查:查看Logview的inputs信息 如上图会发现input的分区量是14个,这个与预想的(SQL条件中只指定一个分区)不一致。问题就出在这里,此时基本可以判断这个SQL的分区并没有裁剪好,也就是说最终输入量不是一个分区而是多个或者全表。 输入的分区量和预计的不一致,排除SQL中确实没有对分区设置条件这因素,那么就是分区裁剪失效了。已知的分区裁剪失效场景主要有:分区条件用了自定义函数进行裁剪;在 Join 关联时的 Where 条件中也有可能会失效。 执行explain sql语句;看执行结果,读取的分区都有哪些,如执行explain select seller_id from xxxxx_trd_slr_ord_1d where ds=rand(); 结果如下: 看上图中红框的内容,表示读取了表 xxxxx_trd_slr_ord_1d 的 1344 个分区,即该表的所有分区,如果直接执行这个sql,最终会因为全表扫描导致输入量增加从而费用增加。 关于分区裁剪失败场景(使用函数或者跟join关联有关的场景)分析可以参考文档《分区剪裁合理性评估》。大家在执行sql前如果对分区的裁剪有疑虑,不放执行一次explain sql语句;再执行SQL语句。 关于分区条件用自定义函数或者内置函数导致分区裁剪失效的解决方案: 内置函数目前已经都支持进行分区裁剪。自定义函数需要支持分区裁剪有两种方式: 在编写UDF的时候,UDF类上加入Annotation。@com.aliyun.odps.udf.annotation.UdfProperty(isDeterministic=true)> 注意: com.aliyun.odps.udf.annotation.UdfProperty定义在odps-sdk-udf.jar文件中。您需要把引用的odps-sdk-udf版本提高到0.30.x或以上。* 在SQL语句前设置Flag:`set odps.sql.udf.ppr.deterministic = true;`,此时SQL中所有的UDF均被视为deterministic。该操作执行的原理是做执行结果回填,但是结果回填存在限制,即最多回填1000个Partition。 因此,如果UDF类加入Annotation,则可能会导致出现超过1000个回填结果的报错。此时您如果需要忽视此错误,可以通过设置Flag:`set odps.sql.udf.ppr.to.subquery = false;`全局关闭此功能。关闭后,UDF分区裁剪也会失效。 本文作者:海清阅读原文 本文为云栖社区原创内容,未经允许不得转载。

June 27, 2019 · 1 min · jiezi

Tableau-BI工具对接-AnalyticDB-for-PostgreSQL数据源

AnalyticDB for PostgreSQL(原HybridDB for PostgreSQL)作为高性能分析型数据库,可以支持用户对其业务数据进行实时分析,能够让企业敏锐感知市场动态,做出必要决策。Tableau是一款数据分析与可视化工具,它支持连接本地或云端数据,不管是电子表格,还是数据库数据,都能进行无缝连接。本文介绍Tableau以AnalyticDB for PostgreSQL作为数据源,如何进行有效的数据分析。 使用AnalyticDB for PostgreSQLAnalyticDB for PostgreSQL基于Greenplum,所以在选择连接器的时候选择Greenplum连接器: 点开出现登录页面,填上DB的连接信息完成登录。 登录后页面: 根据指导操作,可以将任意表进行统计分析,并进行报表展示。 例如使用TPCH数据中的lineitem,点开一张工作表可以进行任意维度的数据展示了: 每从度量或者维度中选择一个字段,放到工作表区时,Tableau都会发送一个query到AnalyticDB for PostgreSQL进行数据查询,例如上述图表发送的query: BEGIN;declare "SQL_CUR0x7fdabf04ca00" cursor with hold for SELECT "lineitem"."l_linestatus" AS "l_linestatus", "lineitem"."l_shipmode" AS "l_shipmode", SUM("lineitem"."l_orderkey") AS "sum_l_orderkey_ok", ((CAST("lineitem"."l_shipdate" AS DATE) + CAST(TRUNC((-1 * (EXTRACT(DAY FROM "lineitem"."l_shipdate") - 1))) AS INTEGER) * INTERVAL '1 DAY') + CAST(TRUNC((-1 * (EXTRACT(MONTH FROM "lineitem"."l_shipdate") - 1))) AS INTEGER) * INTERVAL '1 MONTH') AS "tyr_l_shipdate_ok" FROM "public"."lineitem" "lineitem" GROUP BY 1, 2, 4;fetch 10000 in "SQL_CUR0x7fdabf04ca00一些注意事项关掉cursor默认情况下Tableau使用cursor模式从AnalyticDB for PostgreSQL拉取数据: ...

June 26, 2019 · 1 min · jiezi

更新墨天轮v196发布

发布DBDOC方案文档在线免费浏览,拷贝导航栏、控制台新增方案文档入口技术专家可上传方案文档注册用户可通过墨值下载文档 活动:首页活动发布活动日历,可方便查看当月活动和往期活动活动样式调整,更改了些页面适配问题将线上活动和线下活动图标进行了区分个人信息增加职位,方便活动报名预填活动议程显示嘉宾名称直播页面增加回放入口直播聊天室支持多通道直播支持导播台,可加入主持人环节或者多人同时直播 DBASK:开放技术专家申请,以及自主订阅功能开放ORA错误快速查询在问题详情增加知识库检索功能小程序精选中将公众号文章栏目并移到首屏调整小程序公众号显示图标小程序可后台新增管理公众号修复小程序生成图片分享公众号名称被截断问题 墨值:控制台增加墨值标识,用户可查看个人墨值开放墨值详情页面,查看墨值变化情况用户可通过分享微博、微信、带来新用户获得墨值 其他调整:课程增加级别和常用筛选项,砍价页面加入讲师职位解决部分火狐浏览器不能下载的问题elementUI版本升级工具、文档和书籍新增tag标签,可进行通过tag筛选用户头像模块优化解决微信分享可能为空的情况智能巡检名称上增加跳转链接发布MyData 1.5输入不存在ID访问时,给出相关报错提示修改订单显示交付状态问题,并增加过期状态 如果您对墨天轮有任何建议和疑问,欢迎大家反馈给我们,平台管理员微信:emcs007。

June 24, 2019 · 1 min · jiezi

DBASK问答集萃

引言 近期我们对DBASK小程序进行了升级,UI交互做了重大优化调整,对注册用户开放知识库全文检索功能,引入数据和云公众号文章,提问时自动关联知识库已知问题,专栏可生成图片分享给好友,欢迎大家通过微信搜索DBASK体验。 问答集萃 接下来,我们分享本期整理出的问题和诊断总结,供大家参考学习,详细的诊断分析过程可以通过标题链接跳转到小程序中查看。 问题一、数据库夯ORA-00494: enqueue [CF] held for too longlistener不能访问,重启lsrnctl restart 无效,最后操作系统重启后正常,请帮忙分析下原因。2019.01.30 02:41接到电话,反映不能使用,erp有画面报警;我发现db不能连接,lsnr 不能服务了。查询日志发现: Wed Jan 30 01:02:02 China Standard Time 2019 ORA-00494: enqueue [CF] held for too long (more than 900 seconds) by 'inst 1, osid 4688'waited for 'direct path read', seq_num: 10340for 'rdbms ipc message' count=1 wait_time=3.009785 secDB: direct path read 这个值超时。2019-01-30 00:50:24时,有锁出现 :sql::DELETE FROM XXX WHERE XXX<=TO_CHAR(SYSDATE-30,'YYYYMMDD')||' 0000000' AND ROWNUM<1001有大量锁表:XXX,接着有XXXX表,用户FTRPT/sqlplus.exe_程序,XXXXX,XXXXX,一些job等进程锁,越来越多!造成连锁反映!详细日志如下: Wed Jan 30 01:02:02 China Standard Time 2019Errors in file d:\oracle\product\10.2.0\admin\\bdump\_mmon_4704.trc:ORA-00494: enqueue [CF] held for too long (more than 900 seconds) by 'inst 1, osid 4688'Wed Jan 30 01:02:02 China Standard Time 2019System State dumped to trace file d:\oracle\product\10.2.0\admin\\bdump\_mmon_4704.trcKilling enqueue blocker (pid=4688) on resource CF-00000000-00000000by killing session 162.1Killing enqueue blocker (pid=4688) on resource CF-00000000-00000000by terminating the processMMON: terminating instance due to error 2103Wed Jan 30 01:12:05 China Standard Time 2019USER: terminating instance due to error 1092Wed Jan 30 01:12:05 China Standard Time 2019...省略诊断结论:表象是控制文件的enq,最终锁定到根源是闪回区清理进程RVWR,清空闪回区问题解决。 ...

June 24, 2019 · 2 min · jiezi

从-Spark-Streaming-到-Apache-Flink-实时数据流在爱奇艺的演进

本文将为大家介绍Apache Flink在爱奇艺的生产与实践过程。你可以借此了解到爱奇艺引入Apache Flink的背景与挑战,以及平台构建化流程。主要内容如下: 爱奇艺在实时计算方面的的演化和遇到的一些挑战爱奇艺使用Flink的User Case爱奇艺Flink平台化构建流程爱奇艺在Flink上的改进未来工作爱奇艺简介 爱奇艺在2010年正式上线,于2018年3月份在纳斯达克上市。我们拥有规模庞大且高度活跃的用户基础,月活跃用户数5.65亿人,在在线视频领域名列第一。在移动端,爱奇艺月度总有效时长59.08亿小时,稳居中国APP榜第三名。 一、爱奇艺在实时计算方面的演化和遇到的一些挑战1. 实时计算在爱奇艺的演化过程 实时计算是基于一些实时到达、速率不可控、到达次序独立不保证顺序、一经处理无法重放除非特意保存的无序时间序列的数据的在线计算。 因此,在实时计算中,会遇到数据乱序、数据延时、事件时间与处理时间不一致等问题。爱奇艺的峰值事件数达到1100万/秒,在正确性、容错、性能、延迟、吞吐量、扩展性等方面均遇到不小的挑战。 爱奇艺从2013年开始小规模使用storm,部署了3个独立集群。在2015年,开始引入Spark Streaming,部署在YARN上。在2016年,将Spark Streaming平台化,构建流计算平台,降低用户使用成本,之后流计算开始在爱奇艺大规模使用。在2017年,因为Spark Streaming的先天缺陷,引入Flink,部署在独立集群和YARN上。在2018年,构建Streaming SQL与实时分析平台,进一步降低用户使用门槛。 2. 从Spark Streaming到Apache Flink 爱奇艺主要使用的是Spark Streaming和Flink来进行流式计算。Spark Streaming的实现非常简单,通过微批次将实时数据拆成一个个批处理任务,通过批处理的方式完成各个子Batch。Spark Streaming的API也非常简单灵活,既可以用DStream的java/scala API,也可以使用SQL定义处理逻辑。但Spark Streaming受限于微批次处理模型,业务方需要完成一个真正意义上的实时计算会非常困难,比如基于数据事件时间、数据晚到后的处理,都得用户进行大量编程实现。爱奇艺这边大量使用Spark Streaming的场景往往都在于实时数据的采集落盘。 Apache Flink框架的实时计算模型是基于Dataflow Model实现的,完全支持Dataflow Model的四个问题:What,支持定义DAG图;Where:定义各类窗口(固定窗口、滑动窗口和Session窗口);When:支持灵活定义计算触发时间;How:支持丰富的Function定义数据更新模式。和Spark Streaming一样,Flink支持分层API,支持DataStream API,Process Function,SQL。Flink最大特点在于其实时计算的正确性保证:Exactly once,原生支持事件时间,支持延时数据处理。由于Flink本身基于原生数据流计算,可以达到毫秒级低延时。 在爱奇艺实测下来,相比Spark Streaming,Apache Flink在相近的吞吐量上,有更低的延时,更好的实时计算表述能力,原生实时事件时间、延时数据处理等。 二、在爱奇艺使用Flink的一些案例下面通过三个Use Case来介绍一下,爱奇艺具体是怎么使用Flink的,包括海量数据实时ETL,实时风控,分布式调用链分析。 1. 海量数据实时ETL 在爱奇艺这边所有用户在端上的任何行为都会发一条日志到nginx服务器上,总量超过千万QPS。对于具体某个业务来说,他们后续做实时分析,只希望访问到业务自身的数据,于是这中间就涉及一个数据拆分的工作。 在引入Flink之前,最早的数据拆分逻辑是这样子的,在Ngnix机器上通过“tail -f /xxx/ngnix.log | grep "xxx"”的方式,配置了无数条这样的规则,将这些不同的数据按照不同的规则,打到不同的业务kafka中。但这样的规则随着业务线的规模的扩大,这个tail进程越来越多,逐渐遇到了服务器性能瓶颈。 于是,我们就有了这样一个设想,希望通过实时流计算将数据拆分到各个业务kafka。具体来说,就是Nginx上的全量数据,全量采集到一级Kafka,通过实时ETL程序,按需将数据采集到各个业务Kafka中。当时,爱奇艺主的实时流计算基本均是基于Spark Streaming的,但考虑到Spark Streaming延迟相对来说比较高,爱奇艺从这个case展开开始推进Apache Flink的应用。 海量数据实时ETL的具体实现,主要有以下几个步骤: 解码:各个端的投递日志格式不统一,需要首先将各个端的日志按照各种解码方式解析成规范化的格式,这边选用的是JSON风控:实时拆分这边的数据都会过一下风控的规则,过滤掉很大一部分刷量日志。由于量级太高,如果将每条日志都过一下风控规则,延时会非常大。这边做了几个优化,首先,将用户数据通过DeviceID拆分,不同的DeviceID拆分到不同的task manager上,每个task manager用本地内存做一级缓存,将redis和flink部署在一起,用本地redis做二级缓存。最终的效果是,每秒redis访问降到了平均4k,实时拆分的P99延时小于500ms。拆分:按照各个业务进行拆分采样、再过滤:根据每个业务的拆分过程中根据用户的需求不同,有采样、再过滤等过程 2. 实时风控 防机器撞库盗号攻击是安全风控的一个常见需求,主要需求集中于事中和事后。在事中,进行超高频异常检测分析,过滤用户异常行为;在事后,生成IP和设备ID的黑名单,供各业务实时分析时进行防刷使用。 ...

June 21, 2019 · 1 min · jiezi

DLA-SQL技巧行列转换和JSON数据列展开

1. 简介在数据库SQL处理中,常常有行转列(Pivot)和列转行(Unpivot)的数据处理需求。本文以示例说明在Data Lake Analytics(https://www.aliyun.com/product/datalakeanalytics)中,如何使用SQL的一些技巧,达到行转列(Pivot)和列转行(Unpivot)的目的。另外,DLA支持函数式表达式的处理逻辑、丰富的JSON数据处理函数和UNNEST的SQL语法,结合这些功能,能够实现非常丰富、强大的SQL数据处理语义和能力,本文也以JSON数据列展开为示例,说明在DLA中使用这种SQL的技巧。 2. 行转列(Pivot)2.1 样例数据 test_pivot表内容: +------+----------+---------+--------+| id | username | subject | source |+------+----------+---------+--------+| 1 | 张三 | 语文 | 60 || 2 | 李四 | 数学 | 70 || 3 | 王五 | 英语 | 80 || 4 | 王五 | 数学 | 75 || 5 | 王五 | 语文 | 57 || 6 | 李四 | 语文 | 80 || 7 | 张三 | 英语 | 100 |+------+----------+---------+--------+2.2 方法一:通过CASE WHEN语句 ...

June 20, 2019 · 4 min · jiezi

SQL-笔记

彻底卸载SQL1.程序和功能,红框卸载。 2.路径删除文件夹。 C:\Program Files (x86)C:\Program Files3.下载“搜索Everything”,安装后,搜索有没有Microsoft SQL Server ,然后删除。Microsoft SQL Server#也删除,不知道是什么,是为了防止,觉得没什么影响。 4.检查服务有没有SQL。 5.重启计算机。 6.注册表编辑器,不敢动,就像人的神经学一样,数据库连接很多文件。动了一毛,就惨了。

June 19, 2019 · 1 min · jiezi

数据库根据指定字段去重

需求:对一张用户表根据name/email/card_num字段去除重复数据; 思路:用group by方法可以查询出'去重'后的数据,将这些数据存储到一张临时表中,然后将临时表的数据存储到指定的表中; 误区及解决方案:group by方法只能获取部分字段(去重指定字段),不能一次获取到完整的数据,但是可以通过max函数获取group by结果集中的id,再根据id集合查询出全部的记录。 测试思路查询去重后的数据SELECT max(id) as id,name,email,card_num FROM users GROUP BY name,email,card_num; 从去重后的数据中获取id集合SELECT ID from (SELECT max(id) as id,name,email,card_num FROM users GROUP BY name,email,card_num) as T; 根据去重后的数据中获取id集合,从源数据中获得记录列表SELECT * from users where id in (SELECT ID from (SELECT max(id) as id,name,email,card_num FROM users GROUP BY name,email,card_num) as T); 实际方法根据去重后的数据中获取id集合,从源数据中获得记录列表,将这些列表数据存入一个临时表中create TEMP TABLE tmp_data as SELECT * from users where id in (SELECT ID from (SELECT max(id) as id,name,email,card_num FROM users GROUP BY name,email,card_num) as T); ...

June 19, 2019 · 1 min · jiezi

墨天轮DBASK技术专家邀请函

各位数据库管理员、工程师们: 大家好,感谢大家一直以来对墨天轮DBASK的支持。DBASK 是一个开放、互助、便捷的数据库问答社区。在遇到任何数据库疑难杂症都可在DBASK上提问,平台认证的技术专家免费在线解答,最后归档沉淀为一个开放的知识库。 最近注意到Oracle官方文档中部分视图已经没有了说明,MOS上很多文档也隐藏了起来,开源公司不断被资本收购,但是我们始终向往构建一个开放开源、互帮互助的社区环境。 现在诚挚邀请您成为DBASK的技术专家,与我们一起讨论交流回复数据库相关问题,创建一个开放互助的数据库技术社区。 另外,近几年来,TiDB、达梦、巨杉等国产数据库也在不断成长,砥砺前行,还有阿里、华为等云上数据库作为新兴力量的加入,相信未来国产数据库一定会大有作为。另外,我们也会陆续在DBASK中开放国产数据库的专区,特别欢迎国产数据库相关的专家入驻DBASK,为国产数据库添砖加瓦! 专家权利:a.可以看到平台上所有的问题,参与讨论或者学习b.身份象征:平台标注技术专家的标识c.定期邀请您参加我们的线下专家交流会d.赠送技术嘉年华等大会门票、文化衫、书籍e.在 DBASK上回答问题、上传技术文档等可以获得墨值,墨值可用于兑换话费、京东卡等等 (PS:成为专家后自动赠送50墨值)。 DBASK小程序: 随着用户的不断增多,为了用户使用更方便,DBASK发布了微信小程序。小程序的发布可以让大家随时随地提问,专家也可在小程序内即时回复,减少了提问的门槛,加快问题交互的流程。另外可以在微信小程序中浏览知识库,方便查找学习相关问题。 DBASK常驻专家团下面是墨天轮DBASK常驻专家团队(其中囊括了国内IT服务商中绝大部分的Oracle ACE和Oracle ACE总监级别的技术专家)。我们特别欢迎数据库管理员、工程师们入驻DBASK,为墨天轮DBASK专家团队注入新鲜的血液。 如何成为DBASK技术专家?网页登录DBASK:https://cs.enmotech.com/issue,点击右上角【申请成为专家】。即可加入墨天轮DBASK专家团队!(PS:审核通过后须重新登录)

June 18, 2019 · 1 min · jiezi

DVWA从入门到放弃之SQL-InjectionSQL-InjectionBlind

SQL InjectionSQL语句基本知识由于常见的注入类型为数字型和字符型(根据查询的字段值有无引号决定的)可通过a' or 1 = 1#或者a or 1 = 1#(a表示正确输入的值,#为注释)来判断注入类型。 若为数字型sql注入,前者报错或查询不到数据、后者可能查询到所有结果若为数字型sql注入,前者可能查询到所有结果、后者报错或查询不到数据将两句payload带入构造的sql查询语句(以本实验为例,a用1代替)数字型: select first_name,last_name from users where user_id = 1' or 1 = 1##由于是数字型。'user_id'的值没有用''包围,所以"1'"不能识别为int型select first_name,last_name from users where user_id = 1 or 1 = 1##where语句恒为真(or后面的子句恒为真),所以查询的结果为所有数据字符型: select first_name,last_name from users where user_id = '1' or 1 = 1#'#由于是字符型。'user_id'的值用''包围,且在sql语句中'#'为注释功能。故where子句中条件恒为真select first_name,last_name from users where user_id = '1 or 1 = 1#'#由于字符型查询右引号缺失,导致报错或查询不到数据Low代码分析<?phpif( isset( $_REQUEST[ 'Submit' ] ) ) { // Get input $id = $_REQUEST[ 'id' ]; // Check database $query = "SELECT first_name, last_name FROM users WHERE user_id = '$id';"; $result = mysqli_query($GLOBALS["___mysqli_ston"], $query ) or die( '<pre>' . ((is_object($GLOBALS["___mysqli_ston"])) ? mysqli_error($GLOBALS["___mysqli_ston"]) : (($___mysqli_res = mysqli_connect_error()) ? $___mysqli_res : false)) . '</pre>' ); // Get results while( $row = mysqli_fetch_assoc( $result ) ) { // Get values $first = $row["first_name"]; $last = $row["last_name"]; // Feedback for end user echo "<pre>ID: {$id}<br />First name: {$first}<br />Surname: {$last}</pre>"; } mysqli_close($GLOBALS["___mysqli_ston"]);}?> 代码解析&自我理解:isset()函数在php中用来检测变量是否设置,该函数返回的是布尔类型的值,即true/falsequery变量为直接构造的sql查询语句,没有对用户的输入进行任何的过滤,导致sql注入的存在。result通过mysqli_query()函数获取数据库查询的结果集。die()函数表示连接数据库失败退出当前脚本。$GLOBALS["___mysqli_ston"]表示数据库的连接语句mysqli_fetch_assoc($result)从结果集中取出一行作为关联数组,即列名和值对应的键值对。 ...

June 13, 2019 · 8 min · jiezi

数据库的完整性约束

完整性约束条件的作用对象:列级约束(针对字段,key)主要针对列的类型,取值范围,精度等约束 - 对空值的约束。规定某个字段是否为空- 对取值范围的约束。例如,学生成绩的字段规定为 0 - 100- 数据类型的约束。包括数据类型,长度,精度等。例如常用的定长 varchar- 数据格式的约束。例如,学生表中的学号 stu_no 字段,认为规定前四位为入学年份,后面是院系的编号等元组(或称作 row,一条数据)约束 元组中字段之间的约束。例如,一个活动的开始时间必须早于它的结束时间表级约束(外键) 指多个元组之间,关系之间的联系的约束。例如,学生成绩表中的 stu_no 字段,实际取值源于 学生表中的 stu_no 字段以上是一些约束的概念,理论上的,如何实现约束,请往下看。 实体完整性实体的完整性是通过主键(primary key)约束和候选键(candidate key)约束来实现的。所以前提条件是要了解键的一些概念和分类: key:用于保证元组的唯一性 super-key:能够区分唯一的元组的集合candidate key:super-key 中最小集primary key:candidate key 中人工选择一个(一张表只能有一个或多个组成的联合主键)举个例子:例如有 students 表,含有字段 stu_number(学号)id(身份证号)name(姓名) 那么找出所有能够保证元组唯一性的super-key={{stu_number}, {id}, {stu_number, name}, {id, name}, {stu_number, id}, {stu_number, id, name}}然后可得 candidate key=stu_number 或 id 主键约束 每张表只能定义一个主键或多个主键组合的联合主键(复合主键)确保能够根据主键查询到唯一的元组,且不能为 NULL,这是唯一性原则联合主键不能包含不必要的字段。也就是说,从联合主键中删除其中一列后,还能保证唯一性,那么是不正确的。因为要满足最小集原则一个字段只能在联合主键中出现一次。因为集合的元素是唯一的创建主键约束 可以在 CREATE TABLE 或 ALTER TABLE 语句中使用 PRIMARY KEY 来实现唯一主键:直接在某个字段后加上关键字联合主键:PRIMARY KEY(column_0, column_1, ...)创建主键后,数据库会自动创建唯一索引,用于对主键的快速查询,索引名默认为PRIMARY,也可以重新自定义命名 创建候选键索引 可以在 CREATE TABLE 或 ALTER TABLE 语句中使用 UNIQUE 来实现主键和候选键一样,只不过主键是唯一的,候选键可以是多个,所以同样具有唯一性,且不能为 NULL创建候选键后,数据库也会自动创建 UNIQUE 索引 ...

June 12, 2019 · 1 min · jiezi

数据分析中常用的SQL语句

分组选择数据问题:如何获得每个分组前n项结果? 场景:假设有一个表,记录了学生所有科目的成绩,那么现在要取出每个科目分数最高的3位同学的考试成绩。表名为student_grade表中字段为:course_id,course_name, student_id, student_name, grade 方法一:利用row_number()函数select course_id,course_name, student_id, student_name, grade, row_number() over(partition by course_id order by grade desc) as rankfrom student_gradewhere rank <= 3方法二:利用嵌套函数select course_id,course_name, student_id, student_name, grade, (select count(*)from student_grade as t2 where t1.grade<=t2.grade) as rankfrom student_grade as t1where rank <=3

June 8, 2019 · 1 min · jiezi

MySQL存储引擎详解

一、MySQL常用存储引擎及特点1、InnoDB存储引擎 从MySQL5.5版本之后,MySQL的默认内置存储引擎已经是InnoDB了,他的主要特点有: (1)灾难恢复性比较好;(2)支持事务。默认的事务隔离级别为可重复度,通过MVCC(并发版本控制)来实现的。(3)使用的锁粒度为行级锁,可以支持更高的并发;(4)支持外键;(5)配合一些热备工具可以支持在线热备份;(6)在InnoDB中存在着缓冲管理,通过缓冲池,将索引和数据全部缓存起来,加快查询的速度;(7)对于InnoDB类型的表,其数据的物理组织形式是聚簇表。所有的数据按照主键来组织。数据和索引放在一块,都位于B+数的叶子节点上; 2、MyISAM存储引擎在5.5版本之前,MyISAM是MySQL的默认存储引擎,该存储引擎并发性差,不支持事务,所以使用场景比较少,主要特点为: (1)不支持事务;(2)不支持外键,如果强行增加外键,不会提示错误,只是外键不其作用;(3)对数据的查询缓存只会缓存索引,不会像InnoDB一样缓存数据,而且是利用操作系统本身的缓存;(4)默认的锁粒度为表级锁,所以并发度很差,加锁快,锁冲突较少,所以不太容易发生死锁;(5)支持全文索引(MySQL5.6之后,InnoDB存储引擎也对全文索引做了支持),但是MySQL的全文索引基本不会使用,对于全文索引,现在有其他成熟的解决方案,比如:ElasticSearch,Solr,Sphinx等。(6)数据库所在主机如果宕机,MyISAM的数据文件容易损坏,而且难恢复; 3、MEMORY存储引擎将数据存在内存中,和市场上的Redis,memcached等思想类似,为了提高数据的访问速度,主要特点: (1)支持的数据类型有限制,比如:不支持TEXT和BLOB类型,对于字符串类型的数据,只支持固定长度的行,VARCHAR会被自动存储为CHAR类型;(2)支持的锁粒度为表级锁。所以,在访问量比较大时,表级锁会成为MEMORY存储引擎的瓶颈;(3)由于数据是存放在内存中,所以在服务器重启之后,所有数据都会丢失;(4)查询的时候,如果有用到临时表,而且临时表中有BLOB,TEXT类型的字段,那么这个临时表就会转化为MyISAM类型的表,性能会急剧降低; 4、ARCHIVE存储引擎ARCHIVE存储引擎适合的场景有限,由于其支持压缩,故主要是用来做日志,流水等数据的归档,主要特点: (1)支持Zlib压缩,数据在插入表之前,会先被压缩;(2)仅支持SELECT和INSERT操作,存入的数据就只能查询,不能做修改和删除;(3)只支持自增键上的索引,不支持其他索引; 5、CSV存储引擎数据中转试用,主要特点: (1)其数据格式为.csv格式的文本,可以直接编辑保存;(2)导入导出比较方便,可以将某个表中的数据直接导出为csv,试用Excel办公软件打开; 二、InnoDB和MyISAM的对比1、由于锁粒度的不同,InnoDB比MyISAM支持更高的并发;2、InnoDB为行级锁,MyISAM为表级锁,所以InnoDB相对于MyISAM来说,更容易发生死锁,锁冲突的概率更大,而且上锁的开销也更大,因为需要为每一行加锁;3、在备份容灾上,InnoDB支持在线热备,有很成熟的在线热备解决方案;4、查询性能上,MyISAM的查询效率高于InnoDB,因为InnoDB在查询过程中,是需要维护数据缓存,而且查询过程是先定位到行所在的数据块,然后在从数据块中定位到要查找的行;而MyISAM可以直接定位到数据所在的内存地址,可以直接找到数据;5、SELECT COUNT(*)语句,如果行数在千万级别以上,MyISAM可以快速查出,而InnoDB查询的特别慢,因为MyISAM将行数单独存储了,而InnoDB需要朱行去统计行数;所以如果使用InnoDB,而且需要查询行数,则需要对行数进行特殊处理,如:离线查询并缓存;6、MyISAM的表结构文件包括:.frm(表结构定义),.MYI(索引),.MYD(数据);而InnoDB的表数据文件为:.ibd和.frm(表结构定义); 三、如何选择合适的存储引擎1、使用场景是否需要事务支持;2、是否需要支持高并发,InnoDB的并发度远高于MyISAM;3、是否需要支持外键;4、是否需要支持在线热备;5、高效缓冲数据,InnoDB对数据和索引都做了缓冲,而MyISAM只缓冲了索引;6、索引,不同存储引擎的索引并不太一样; 注:文章属原创,如果转发,请标注出处。 后续更多文章将更新在个人博客上https://www.jinnianshizhunian... 上,欢迎查看。

June 5, 2019 · 1 min · jiezi

sqlalchemy-配置多连接读写库后的关系设置

前言一般来说,解决sqlalchemy 连接多个库的最简单的方式是新建两个或多个db.session 相互没有关联,modle配置不同的db.session来连接,这样的话,relationship正常配置就行,不用特殊配置.如果这样解决的话,也就不用看下面的配置了 # -*- coding:utf-8 -*-import flaskfrom flask_sqlalchemy import SQLAlchemy # Flask-SQLAlchemy 2.3.2from datetime import datetimefrom sqlalchemy.orm import backref, foreign # SQLAlchemy 1.3.1app = flask.Flask(__name__)app.config['DEBUG'] = Trueapp.config['SQLALCHEMY_BINDS'] = { 'read_db': 'mysql://reader:test@127.0.0.1:3306/test?charset=utf8', 'write_db': 'mysql://writer:test@127.0.0.2:3306/test?charset=utf8'}app.config['SQLALCHEMY_TRACK_MODIFICATIONS'] = Falseapp.config['SQLALCHEMY_ECHO'] = Falsedb = SQLAlchemy(app)class RDriver(db.Model): __bind_key__ = 'read_db' __tablename__ = 'driver' # __table_args__ = {'schema': 'test'} # 不可以加上 id = db.Column(db.Integer, primary_key=True, autoincrement=True) fk_user_id = db.Column(db.Integer, db.ForeignKey("user.id")) driver_name = db.Column(db.String(7)) create_time = db.Column(db.TIMESTAMP, default=datetime.now)class RUser(db.Model): __bind_key__ = 'read_db' __tablename__ = 'user' # __table_args__ = {'schema': 'test'} id = db.Column(db.Integer, primary_key=True, autoincrement=True) user_name = db.Column(db.String(32), index=True, unique=True) user_password = db.Column(db.String(32)) create_time = db.Column(db.TIMESTAMP, default=datetime.now) update_time = db.Column(db.TIMESTAMP, default=datetime.now) # 如下的五种方式都是可以的 # driver_fk = db.relationship("RDriver", foreign_keys='RDriver.fk_user_id') # driver_fk = db.relationship("RDriver", primaryjoin=lambda: RDriver.fk_user_id == RUser.id, viewonly=True) # driver_fk = db.relationship("RDriver", primaryjoin=RDriver.fk_user_id == id) fk_driver = db.relationship("RDriver", primaryjoin='RDriver.fk_user_id == RUser.id') # driver_fk = db.relationship("RDriver", backref=db.backref('user', lazy=True), # primaryjoin=lambda: RDriver.fk_user_id == RUser.id, viewonly=True)class WDriver(db.Model): __bind_key__ = 'write_db' __tablename__ = 'driver' __table_args__ = {'schema': 'test', 'extend_existing': True} # 这个配置很关键 id = db.Column(db.Integer, primary_key=True, autoincrement=True) fk_user_id = db.Column(db.Integer, db.ForeignKey("test.user.id")) # test.user.id很关键 plate = db.Column(db.String(7)) create_at = db.Column(db.TIMESTAMP, default=datetime.now)class WUser(db.Model): __bind_key__ = 'write_db' __tablename__ = 'user' __table_args__ = {'schema': 'test', 'extend_existing': True} # 这个配置很关键 id = db.Column(db.Integer, primary_key=True, autoincrement=True) hash = db.Column(db.String(256), nullable=False) user_no = db.Column(db.String(32), index=True, unique=True) # 用户工号 create_time = db.Column(db.TIMESTAMP, default=datetime.now) update_time = db.Column(db.TIMESTAMP, default=datetime.now) # 以下五种方式都是可以的 # fk_driver = db.relationship("WDriver", foreign_keys='WDriver.fk_user_id', uselist=False) # fk_driver = db.relationship("WDriver", primaryjoin=lambda: WDriver.fk_user_id == WUser.id) fk_driver = db.relationship("WDriver", primaryjoin=WDriver.fk_user_id == id) # fk_driver = db.relationship("WDriver", primaryjoin='WDriver.fk_user_id == WUser.id') # fk_driver = db.relationship("WDriver", backref=db.backref('test.user', lazy=True), # primaryjoin=lambda: WDriver.fk_user_id == WUser.id)r_user_obj = RUser.query.filter_by().first()print("r_user_obj:", r_user_obj)print("r_user_obj.driver_fk:", r_user_obj.fk_driver)w_user_obj = WUser.query.filter_by(id=2188).first()print("w_user_obj:", w_user_obj)print("w_user_obj.driver_fk:", w_user_obj.fk_driver)参考文档:* https://docs.sqlalchemy.org/en/13/orm/relationship_api.html # 值得细看* https://www.osgeo.cn/sqlalchemy/orm/relationship_api.html # 同上,中文* https://www.cnblogs.com/srd945/p/9851227.html* extend_existing: (False)当表已经存在于元数据中时,如果元数据中存在与column_list中的列同名的列,column_list中同名的列会替换掉元数据中已经有的列* useexisting已被废弃, 新版本使用extend_existing总结关系配置参数真的很多,如下,很容易就会出错,需要多读读官方文档,还有就是建立modle时候尽量简洁,风格统一,不要在数据库层建立外键. ...

June 4, 2019 · 2 min · jiezi

N分钟看懂SQL基础语法

网站Y分钟学会X语言上展示了多种语言的基础语法,其他很多语言都有中文版本了,但SQL没有。于是我简单翻译一下。 -- 注释以 -- 开始-- 每条命令以;结束-- SQL并不区分关键字的大小写.为了更容易把关键字与数据库、表、字段名区分,-- 示例里采用了关键字大写的惯例-- 创建与删除数据库. 数据库名与表名区分大小写CREATE DATABASE someDatabase;DROP DATABASE someDatabase;-- 列出所有可用的数据库SHOW DATABASES;-- 使用某个存在的数据库 USE employees;-- 从当前数据库的departments表查出所有的记录,每条记录包含了所有的字段,-- 输出默认会在屏幕上滚动展示.SELECT * FROM departments;-- 从departments表查出所有的记录,但只查询dept_no、dept_name字段.-- 可以跨行拆分命令.SELECT dept_no, dept_name FROM departments;-- 从departments表查出包含所有的字段的记录,但只取5行SELECT * FROM departments LIMIT 5;-- 从departments表查出值里包含了'en'的dept_name字段SELECT dept_name FROM departments WHERE dept_name LIKE '%en%';-- 从departments表查出以S开头、后续长度为4作为dept_name字段值的记录SELECT * FROM departments WHERE dept_name LIKE 'S____';-- 从titles表查出title字段去重后都有哪些值SELECT DISTINCT title FROM titles;-- 同上,但是对title字段的值进行排序(区分大小写).SELECT DISTINCT title FROM titles ORDER BY title;-- 统计departments表里有多少条记录.SELECT COUNT(*) FROM departments;-- 统计departments表里有多少条记录的ept_name字段值里包含了enSELECT COUNT(*) FROM departments WHERE dept_name LIKE '%en%';-- 从tiltes表里查出某个job的title、开始日期与结束日期(只查询10条记录).-- 根据job所属的employee编号从employee表里关联出所属employee的first_name、last_name.SELECT employees.first_name, employees.last_name, titles.title, titles.from_date, titles.to_dateFROM titles INNER JOIN employees ON employees.emp_no = titles.emp_no LIMIT 10; -- 列出所有数据库里的所有表.-- 各数据库通常会对此提供特有的快捷命令.SELECT * FROM INFORMATION_SCHEMA.TABLESWHERE TABLE_TYPE='BASE TABLE';-- 在当前使用的数据库里,创建一张名为tablename1的表.-- 除了语句里的两个字段使用到的数据类型,还有很多其他的可选项可以用来定义字段.CREATE TABLE tablename1 (fname VARCHAR(20), lname VARCHAR(20));-- 向tablename1中插入一行数据,-- 这条语句假设插入的值顺序与列字段顺序一致.INSERT INTO tablename1 VALUES('Richard','Mutt');-- 找出tablename1表里lname字段的值为Mutt的记录,将fname字段的值更新为John.UPDATE tablename1 SET fname='John' WHERE lname='Mutt';-- 找出tablename1里lname字段值以M开头的记录删除DELETE FROM tablename1 WHERE lname like 'M%';-- 从tablename1表里删除所有数据DELETE FROM tablename1;-- 删除tablename1整表DROP TABLE tablename1;

June 3, 2019 · 1 min · jiezi

一次SQL优化

问题有A、B、C、D、E...多张表,分别存放的是不同的数据,因业务需要,需要将各表数据统计后合并到一起原sql是这样写的 select T.DATE , sum(A_NUM_1) A_NUM_1, sum(A_AMT_1) A_AMT_1 , sum(A_NUM_2) A_NUM_2, sum(A_AMT_2) A_AMT_2 , sum(A_NUM_3) A_NUM_3, sum(A_AMT_3) A_AMT_3 , sum(A_NUM_4) A_NUM_4, sum(A_AMT_4) A_AMT_4 , sum(B_NUM_1) B_NUM_1, sum(B_AMT_1) B_AMT_1 , sum(B_NUM_2) B_NUM_2, sum(B_AMT_2) B_AMT_2 , sum(C_NUM_1) C_NUM_1, sum(C_AMT_1) C_AMT_1 , sum(D_NUM_1) D_NUM_1, sum(D_AMT_1) D_AMT_1 , sum(E_NUM_1) E_NUM_1, sum(E_AMT_1) E_AMT_1 , sum(E_NUM_2) E_NUM_2, sum(E_AMT_2) E_AMT_2 , sum(E_NUM_3) E_NUM_3, sum(E_AMT_3) E_AMT_3 , sum(E_NUM_4) E_NUM_4, sum(E_AMT_4) E_AMT_4 from ( select A.DATE , sum(case when OPTION_1 = '0' and OPTION_2 = '1' and OPTION_3 = '1' and OPTION_4 = '0' then 1 else 0 end) A_NUM_1 , sum(case when OPTION_1 = '0' and OPTION_2 = '1' and OPTION_3 = '1' and OPTION_4 = '0' then AMT else 0 end) A_AMT_1 , sum(case when OPTION_1 = '0' and OPTION_2 = '2' then 1 else 0 end) A_NUM_2 , sum(case when OPTION_1 = '0' and OPTION_2 = '2' then AMT else 0 end) A_AMT_2 , sum(case when OPTION_1 = '0' and OPTION_2 = '3' and OPTION_3 = '1' and OPTION_4 = '4' then 1 else 0 end) A_NUM_3 , sum(case when OPTION_1 = '0' and OPTION_2 = '3' and OPTION_3 = '1' and OPTION_4 = '4' then AMT else 0 end) A_AMT_3 , sum(case when OPTION_1 = '1' then 1 else 0 end) A_NUM_4 , sum(case when OPTION_1 = '1' then AMT else 0 end) A_AMT_4 , 0 B_NUM_1, 0 B_AMT_1 , 0 B_NUM_2, 0 B_AMT_2 , 0 C_NUM_1, 0 C_AMT_1 , 0 D_NUM_1, 0 D_AMT_1 , 0 E_NUM_1, 0 E_AMT_1 , 0 E_NUM_2, 0 E_AMT_2 , 0 E_NUM_3, 0 E_AMT_3 , 0 E_NUM_4, 0 E_AMT_4 from A group by A.DATE union all select B.DATE , 0 A_NUM_1, 0 A_AMT_1 , 0 A_NUM_2, 0 A_AMT_2 , 0 A_NUM_3, 0 A_AMT_3 , 0 A_NUM_4, 0 A_AMT_4 , sum(case when OPTION_1 = '0' and OPTION_2 = '1' and OPTION_3 = '2' and OPTION_4 = '3' then 1 else 0 end) B_NUM_1 , sum(case when OPTION_1 = '0' and OPTION_2 = '1' and OPTION_3 = '2' and OPTION_4 = '3' then AMT else 0 end) B_AMT_1 , sum(case when OPTION_1 = '1' then 1 else 0 end) B_NUM_2 , sum(case when OPTION_1 = '1' then AMT else 0 end) B_AMT_2 , 0 C_NUM_1, 0 C_AMT_1 , 0 D_NUM_1, 0 D_AMT_1 , 0 E_NUM_1, 0 E_AMT_1 , 0 E_NUM_2, 0 E_AMT_2 , 0 E_NUM_3, 0 E_AMT_3 , 0 E_NUM_4, 0 E_AMT_4 from B group by B.DATE union all ... )T group by T.DATE存在的问题有 ...

June 3, 2019 · 4 min · jiezi

SQL-on-Hadoop在快手大数据平台的实践与优化-分享实录

快手大数据架构工程师钟靓 本文是根据快手大数据架构工程师钟靓于 5月18-19日在A2M人工智能与机器学习创新峰会《SQL on Hadoop在快手大数据平台的实践与优化》演讲中的分享内容整理而成。 内容简介:本文主要从SQL on Hadoop介绍、快手SQL on Hadoop平台概述、SQL on Hadoop在快手的使用经验和改进分析、快手SQL on Hadoop的未来计划四方面介绍了SQL on Hadoop架构。 01SQL on Hadoop介绍 SQL on Hadoop,顾名思义它是基于Hadoop生态的一个SQL引擎架构,我们其实常常听到Hive、SparkSQL、Presto、Impala架构,接下来,我会简单的描述一下常用的架构情况。 SQL on Hadoop-HIVE HIVE,一个数据仓库系统。它将数据结构映射到存储的数据中,通过SQL对大规模的分布式存储数据进行读、写、管理。 根据定义的数据模式,以及输出Storage,它会对输入的SQL经过编译、优化,生成对应引擎的任务,然后调度执行生成的任务。 HIVE当前支持的引擎类型有:MR、SPARK、TEZ。 基于HIVE本身的架构,还有一些额外的服务提供方式,比如HiveServer2与MetaStoreServer都是Thrift架构。 此外,HiveServer2提供远程客户端提交SQL任务的功能,MetaStoreServer则提供远程客户端操作元数据的功能。 SQL on Hadoop介绍-SPARK Spark,一个快速、易用,以DAG作为执行模式的大规模数据处理的统一分析引擎,主要模块分为SQL引擎、流式处理 、机器学习、图处理。 SQL on Hadoop介绍-SPARKSQL SPARKSQL基于SPARK的计算引擎,做到了统一数据访问,集成Hive,支持标准JDBC连接。SPARKSQL常用于数据交互分析的场景。 SPARKSQL的主要执行逻辑,首先是将SQL解析为语法树,然后语义分析生成逻辑执行计划,接着与元数据交互,进行逻辑执行计划的优化,最后,将逻辑执行翻译为物理执行计划,即RDD lineage,并执行任务。 SQL on Hadoop介绍-PRESTO PRESTO,一个交互式分析查询的开源分布式SQL查询引擎。 因为基于内存计算,PRESTO的计算性能大于有大量IO操作的MR和SPARK引擎。它有易于弹性扩展,支持可插拔连接的特点。 业内的使用案例很多,包括FaceBook、AirBnb、美团等都有大规模的使用。 SQL on Hadoop介绍-其它业内方案 我们看到这么多的SQL on Hadoop架构,它侧面地说明了这种架构比较实用且成熟。利用SQL on Hadoop架构,我们可以实现支持海量数据处理的需求。 02快手SQL on Hadoop平台概述 ...

June 3, 2019 · 2 min · jiezi

工作中用到的SQL语法

不等于SQL中表示不等于有两种方式: <>!=CASE WHENcase when类似于编程语言中的switch语法,通常用在select语句中确定最终的值,如下: SELECT userid, name, school, (case sex when male then '男' when female then '女' else "保密" end) as sex FROM user;

May 31, 2019 · 1 min · jiezi

PostgreSQL-JSONB-使用入门

json 类型说明根据RFC 7159中的说明,JSON 数据类型是用来存储 JSON(JavaScript Object Notation)数据的。这种数据也可以被存储为text,但是 JSON 数据类型的优势在于能强制要求每个被存储的值符合 JSON 规则。也有很多 JSON 相关的函数和操作符可以用于存储在这些数据类型中的数据 PostgreSQL支持两种 JSON 数据类型:json 和 jsonb。它们几乎接受完全相同的值集合作为输入。两者最大的区别是效率。json数据类型存储输入文本的精准拷贝,处理函数必须在每 次执行时必须重新解析该数据。而jsonb数据被存储在一种分解好的二进制格式中,因为需要做附加的转换,它在输入时要稍慢一些。但是 jsonb在处理时要快很多,因为不需要重新解析。 重点:jsonb支持索引由于json类型存储的是输入文本的准确拷贝,存储时会空格和JSON 对象内部的键的顺序。如果一个值中的 JSON 对象包含同一个键超过一次,所有的键/值对都会被保留( 处理函数会把最后的值当作有效值)。 jsonb不保留空格、不保留对象键的顺序并且不保留重复的对象键。如果在输入中指定了重复的键,只有最后一个值会被保留。 推荐把JSON 数据存储为jsonb在把文本 JSON 输入转换成jsonb时,JSON的基本类型(RFC 7159 )会被映射到原生的 PostgreSQL类型。因此,jsonb数据有一些次要额外约束。比如:jsonb将拒绝除 PostgreSQL numeric数据类型范围之外的数字,而json则不会。 JSON 基本类型和相应的PostgreSQL类型 JSON 基本类型PostgreSQL类型注释stringtext不允许\u0000,如果数据库编码不是 UTF8,非 ASCII Unicode 转义也是这样numbernumeric不允许NaN 和 infinity值booleanboolean只接受小写true和false拼写null(无)SQL NULL是一个不同的概念json 输入输出语法-- 简单标量/基本值-- 基本值可以是数字、带引号的字符串、true、false或者nullSELECT '5'::json;-- 有零个或者更多元素的数组(元素不需要为同一类型)SELECT '[1, 2, "foo", null]'::json;-- 包含键值对的对象-- 注意对象键必须总是带引号的字符串SELECT '{"bar": "baz", "balance": 7.77, "active": false}'::json;-- 数组和对象可以被任意嵌套SELECT '{"foo": [true, "bar"], "tags": {"a": 1, "b": null}}'::json;-- "->" 通过键获得 JSON 对象域 结果为json对象select '{"nickname": "goodspeed", "avatar": "avatar_url", "tags": ["python", "golang", "db"]}'::json->'nickname' as nickname; nickname------------- "goodspeed"-- "->>" 通过键获得 JSON 对象域 结果为text select '{"nickname": "goodspeed", "avatar": "avatar_url", "tags": ["python", "golang", "db"]}'::json->>'nickname' as nickname; nickname----------- goodspeed -- "->" 通过键获得 JSON 对象域 结果为json对象select '{"nickname": "goodspeed", "avatar": "avatar_url", "tags": ["python", "golang", "db"]}'::jsonb->'nickname' as nickname; nickname------------- "goodspeed"-- "->>" 通过键获得 JSON 对象域 结果为text select '{"nickname": "goodspeed", "avatar": "avatar_url", "tags": ["python", "golang", "db"]}'::jsonb->>'nickname' as nickname; nickname----------- goodspeed当一个 JSON 值被输入并且接着不做任何附加处理就输出时, json会输出和输入完全相同的文本,而jsonb 则不会保留语义上没有意义的细节 ...

May 30, 2019 · 9 min · jiezi

3分钟干货之如何提高数据库SQL语句执行速度下

技巧1 WHERE 子句里面的列尽量被索引只是“尽量”哦,并不是说所有的列。因地制宜,根据实际情况进行调整,因为有时索引太多也会降低性能。 技巧2 JOIN 子句里面的列尽量被索引同样只是“尽量”哦,并不是说所有的列。 技巧3 ORDER BY 的列尽量被索引ORDER BY的列如果被索引,性能也会更好。 技巧4 使用 LIMIT 实现分页逻辑不仅提高了性能,同时减少了不必要的数据库和应用间的网络传输。 技巧5 使用 EXPLAIN 关键字去查看执行计划EXPLAIN 可以检查索引使用情况以及扫描的行。

May 30, 2019 · 1 min · jiezi

3分钟干货之如何提高数据库SQL语句执行速度上

技巧1 比较运算符能用 “=”就不用“”“=”增加了索引的使用几率。 技巧2 明知只有一条查询结果,那请使用 “LIMIT 1”“LIMIT 1”可以避免全表扫描,找到对应结果就不会再继续扫描了。 技巧3 为列选择合适的数据类型能用TINYINT就不用SMALLINT,能用SMALLINT就不用INT,道理你懂的,磁盘和内存消耗越小越好嘛。 技巧4 将大的DELETE,UPDATE or INSERT 查询变成多个小查询能写一个几十行、几百行的SQL语句是不是显得逼格很高?然而,为了达到更好的性能以及更好的数据控制,你可以将他们变成多个小查询。 技巧5 使用UNION ALL 代替 UNION,如果结果集允许重复的话因为 UNION ALL 不去重,效率高于 UNION。

May 28, 2019 · 1 min · jiezi

SQLRESTful开源GO脚手架工具ginbrogin-and-gorms-brother-详解

安装felixgit clone https://github.com/dejavuzhou/felixcd felixgo mod downloadgo installecho "添加 GOBIN 到 PATH环境变量"echo "或者"go get github.com/dejavuzhou/felixecho "go build && ./felix -h"What is GinbroGin脚手架工具:因为工作中非常多次的使用mysql数据库 + gin + GORM 来开发RESTful API程序,所以开发一个Go语言的RESTful APIs的脚手架工具Ginbro代码来源:Ginrbo的代码迭代自github.com/dejavuzhou/ginbroSPA二进制化工具:vuejs全家桶代码二进制化成go代码,编译的时候变成二进制,运行的时候直接加载到内存中,同时和gin API在一个域名下不需要再nginx中配置rewrite或者跨域,加快API访问速度功能一:Gin+GORM_SQL RESTful 脚手架工具工作原理通过cobra 获取命令行参数使用sql参数连接数据库后去数据库表的名称和字段类型等数据库数据库边的表名和字段信息,转换成 Swagger doc 规范字段 和 GORM 模型字段使用标准库 text/template 生成swagger.yaml, GORM 模型文件, GIN handler 文件 ...使用 go fmt ./... 格式化代码使用标准库archive/zip打包*.go config.toml ...代码,提供zip文件下载(命令行模式没有)支持数据库大多数SQL数据库mysqlSQLitepostgreSQLmssql(TODO:: sqlserver)ginbro 生成app代码包含功能简介每一张数据库表生成一个RESTful规范的资源(GET<pagination>/POST/GET<one>/PATCH/DELETE)支持API-json数据分页-和总数分页缓存,减少全表扫描支持golang-内存单机缓存缓存前端代码和API公用一个服务,减少跨域OPTION的请求时间和配置时间,同时完美支持前后端分离开箱支持jwt-token认证和Bearer Token 路由中间件开箱即用的logrus数据库开箱即用的viper配置文件开箱即用的swagger API 文档开箱即用的定时任务系统项目演示地址felix sshw 网页UI演示地址 用户名和密码都是admin生成swagger API交互文档地址 http://ginbro.mojotv.cn/swagger/msql生成go代码地址bili命令行演示视频地址命令行参数详解[root@ericzhou felix]# felix ginbro -hgenerate a RESTful APIs app with gin and gorm for gophersUsage: felix ginbro [flags]示例:felix ginbro -a dev.wordpress.com:3306 -P go_package_name -n db_name -u db_username -p 'my_db_password' -d '~/thisDir'Flags: --authColumn string 使用bcrypt方式加密的用户表密码字段名称 (default "password") --authTable string 认知登陆用户表名称 (default "users") -a, --dbAddr string 数据库连接的地址 (default "127.0.0.1:3306") -c, --dbChar string 数据库字符集 (default "utf8") -n, --dbName string 数据库名称 -p, --dbPassword string 数据库密码 (default "password") -t, --dbType string 数据库类型: mysql/postgres/mssql/sqlite (default "mysql") -u, --dbUser string 数据库用户名 (default "root") -d, --dir string golang代码输出的目录,默认是当前目录 (default ".") -h, --help 帮助 -l, --listen string 生成go app 接口监听的地址 (default "127.0.0.1:5555") --pkg string 生成go app 包名称(go version > 1.12) 生成go.mod文件, eg: ginbroSon[root@ericzhou felix]# web界面对于那些喜欢使用命令行的同学,你们可以选择使用web界面来操作 ...

May 22, 2019 · 2 min · jiezi

入门教程-5分钟从零构建第一个-Flink-应用

本文转载自 Jark’s Blog ,作者伍翀(云邪),Apache Flink Committer,阿里巴巴高级开发工程师。本文将从开发环境准备、创建 Maven 项目,编写 Flink 程序、运行程序等方面讲述如何迅速搭建第一个 Flink 应用。在本文中,我们将从零开始,教您如何构建第一个 Flink 应用程序。开发环境准备Flink 可以运行在 Linux, Max OS X, 或者是 Windows 上。为了开发 Flink 应用程序,在本地机器上需要有 Java 8.x 和 maven 环境。 如果有 Java 8 环境,运行下面的命令会输出如下版本信息: $ java -versionjava version "1.8.0_65"Java(TM) SE Runtime Environment (build 1.8.0_65-b17)Java HotSpot(TM) 64-Bit Server VM (build 25.65-b01, mixed mode)如果有 maven 环境,运行下面的命令会输出如下版本信息:$ mvn -versionApache Maven 3.5.4 (1edded0938998edf8bf061f1ceb3cfdeccf443fe; 2018-06-18T02:33:14+08:00)Maven home: /Users/wuchong/dev/mavenJava version: 1.8.0_65, vendor: Oracle Corporation, runtime: /Library/Java/JavaVirtualMachines/jdk1.8.0_65.jdk/Contents/Home/jreDefault locale: zh_CN, platform encoding: UTF-8OS name: "mac os x", version: "10.13.6", arch: "x86_64", family: "mac"另外我们推荐使用 ItelliJ IDEA (社区免费版已够用)作为 Flink 应用程序的开发 IDE。Eclipse 虽然也可以,但是 Eclipse 在 Scala 和 Java 混合型项目下会有些已知问题,所以不太推荐 Eclipse。下一章节,我们会介绍如何创建一个 Flink 工程并将其导入 ItelliJ IDEA。创建 Maven 项目我们将使用 Flink Maven Archetype 来创建我们的项目结构和一些初始的默认依赖。在你的工作目录下,运行如下命令来创建项目: ...

May 22, 2019 · 2 min · jiezi

从-Spark-Streaming-到-Apache-Flink-实时数据流在爱奇艺的演进

作者:陈越晨 整理:刘河 本文将为大家介绍Apache Flink在爱奇艺的生产与实践过程。你可以借此了解到爱奇艺引入Apache Flink的背景与挑战,以及平台构建化流程。主要内容如下: 爱奇艺在实时计算方面的的演化和遇到的一些挑战爱奇艺使用Flink的User Case爱奇艺Flink平台化构建流程爱奇艺在Flink上的改进未来工作爱奇艺简介 爱奇艺在2010年正式上线,于2018年3月份在纳斯达克上市。我们拥有规模庞大且高度活跃的用户基础,月活跃用户数5.65亿人,在在线视频领域名列第一。在移动端,爱奇艺月度总有效时长59.08亿小时,稳居中国APP榜第三名。 一、爱奇艺在实时计算方面的演化和遇到的一些挑战1. 实时计算在爱奇艺的演化过程 实时计算是基于一些实时到达、速率不可控、到达次序独立不保证顺序、一经处理无法重放除非特意保存的无序时间序列的数据的在线计算。 因此,在实时计算中,会遇到数据乱序、数据延时、事件时间与处理时间不一致等问题。爱奇艺的峰值事件数达到1100万/秒,在正确性、容错、性能、延迟、吞吐量、扩展性等方面均遇到不小的挑战。 爱奇艺从2013年开始小规模使用storm,部署了3个独立集群。在2015年,开始引入Spark Streaming,部署在YARN上。在2016年,将Spark Streaming平台化,构建流计算平台,降低用户使用成本,之后流计算开始在爱奇艺大规模使用。在2017年,因为Spark Streaming的先天缺陷,引入Flink,部署在独立集群和YARN上。在2018年,构建Streaming SQL与实时分析平台,进一步降低用户使用门槛。 2. 从Spark Streaming到Apache Flink 爱奇艺主要使用的是Spark Streaming和Flink来进行流式计算。Spark Streaming的实现非常简单,通过微批次将实时数据拆成一个个批处理任务,通过批处理的方式完成各个子Batch。Spark Streaming的API也非常简单灵活,既可以用DStream的java/scala API,也可以使用SQL定义处理逻辑。但Spark Streaming受限于微批次处理模型,业务方需要完成一个真正意义上的实时计算会非常困难,比如基于数据事件时间、数据晚到后的处理,都得用户进行大量编程实现。爱奇艺这边大量使用Spark Streaming的场景往往都在于实时数据的采集落盘。 Apache Flink框架的实时计算模型是基于Dataflow Model实现的,完全支持Dataflow Model的四个问题:What,支持定义DAG图;Where:定义各类窗口(固定窗口、滑动窗口和Session窗口);When:支持灵活定义计算触发时间;How:支持丰富的Function定义数据更新模式。和Spark Streaming一样,Flink支持分层API,支持DataStream API,Process Function,SQL。Flink最大特点在于其实时计算的正确性保证:Exactly once,原生支持事件时间,支持延时数据处理。由于Flink本身基于原生数据流计算,可以达到毫秒级低延时。 在爱奇艺实测下来,相比Spark Streaming,Apache Flink在相近的吞吐量上,有更低的延时,更好的实时计算表述能力,原生实时事件时间、延时数据处理等。 二、在爱奇艺使用Flink的一些案例下面通过三个Use Case来介绍一下,爱奇艺具体是怎么使用Flink的,包括海量数据实时ETL,实时风控,分布式调用链分析。 1. 海量数据实时ETL 在爱奇艺这边所有用户在端上的任何行为都会发一条日志到nginx服务器上,总量超过千万QPS。对于具体某个业务来说,他们后续做实时分析,只希望访问到业务自身的数据,于是这中间就涉及一个数据拆分的工作。 在引入Flink之前,最早的数据拆分逻辑是这样子的,在Ngnix机器上通过“tail -f /xxx/ngnix.log | grep "xxx"”的方式,配置了无数条这样的规则,将这些不同的数据按照不同的规则,打到不同的业务kafka中。但这样的规则随着业务线的规模的扩大,这个tail进程越来越多,逐渐遇到了服务器性能瓶颈。 于是,我们就有了这样一个设想,希望通过实时流计算将数据拆分到各个业务kafka。具体来说,就是Nginx上的全量数据,全量采集到一级Kafka,通过实时ETL程序,按需将数据采集到各个业务Kafka中。当时,爱奇艺主的实时流计算基本均是基于Spark Streaming的,但考虑到Spark Streaming延迟相对来说比较高,爱奇艺从这个case展开开始推进Apache Flink的应用。 海量数据实时ETL的具体实现,主要有以下几个步骤: 解码:各个端的投递日志格式不统一,需要首先将各个端的日志按照各种解码方式解析成规范化的格式,这边选用的是JSON风控:实时拆分这边的数据都会过一下风控的规则,过滤掉很大一部分刷量日志。由于量级太高,如果将每条日志都过一下风控规则,延时会非常大。这边做了几个优化,首先,将用户数据通过DeviceID拆分,不同的DeviceID拆分到不同的task manager上,每个task manager用本地内存做一级缓存,将redis和flink部署在一起,用本地redis做二级缓存。最终的效果是,每秒redis访问降到了平均4k,实时拆分的P99延时小于500ms。拆分:按照各个业务进行拆分采样、再过滤:根据每个业务的拆分过程中根据用户的需求不同,有采样、再过滤等过程 2. 实时风控 防机器撞库盗号攻击是安全风控的一个常见需求,主要需求集中于事中和事后。在事中,进行超高频异常检测分析,过滤用户异常行为;在事后,生成IP和设备ID的黑名单,供各业务实时分析时进行防刷使用。 以下是两个使用Flink特性的案例: CEP:因为很多黑产用户是有固定的一些套路,比如刚注册的用户可能在短时间内会进行一两项操作,我们通过CEP模式匹配,过滤掉那些有固定套路的黑产行为多窗口聚合:风控这边会有一些需求,它需要在不同的一些时间窗口,有些时间窗口要求比较苛刻,可能是需要在一秒内或亚秒内去看一下某个用户有多少次访问,然后对他进行计数,计数的结果超过某些阈值就判断他是异常用户。通过Flink低延时且支持多窗口的特点,进行超高频的异常检测,比如对同一个用户在1秒内的请求进行计数,超过某个阈值的话就会被识别成黑产。3. 分布式追踪系统 ...

May 22, 2019 · 1 min · jiezi

SQL-行转列列转行

SQL 行转列,列转行行列转换在做报表分析时还是经常会遇到的,今天就说一下如何实现行列转换吧。 行列转换就是如下图所示两种展示形式的互相转换 行转列假如我们有下表: SELECT *FROM studentPIVOT ( SUM(score) FOR subject IN (语文, 数学, 英语))通过上面 SQL 语句即可得到下面的结果 PIVOT 后跟一个聚合函数来拿到结果,FOR 后面跟的科目是我们要转换的列,这样的话科目中的语文、数学、英语就就被转换为列。IN 后面跟的就是具体的科目值。 当然我们也可以用 CASE WHEN 得到同样的结果,就是写起来麻烦一点。 SELECT name, MAX( CASE WHEN subject='语文' THEN score ELSE 0 END) AS "语文", MAX( CASE WHEN subject='数学' THEN score ELSE 0 END) AS "数学", MAX( CASE WHEN subject='英语' THEN score ELSE 0 END) AS "英语"FROM studentGROUP BY name使用 CASE WHEN 可以得到和 PIVOT 同样的结果,没有 PIVOT 简单直观。 ...

May 18, 2019 · 1 min · jiezi

OPPO数据中台之基石基于Flink-SQL构建实数据仓库

作者 | 张俊本文整理自 2019 年 4 月 13 日在深圳举行的 Flink Meetup 会议,分享嘉宾张俊,目前担任 OPPO 大数据平台研发负责人,也是 Apache Flink contributor。本文主要内容如下: OPPO 实时数仓的演进思路;基于 Flink SQL 的扩展工作;构建实时数仓的应用案例;未来工作的思考和展望。一.OPPO 实时数仓的演进思路1.1.OPPO 业务与数据规模 大家都知道 OPPO 是做智能手机的,但并不知道 OPPO 与互联网以及大数据有什么关系,下图概要介绍了 OPPO 的业务与数据情况: OPPO 作为手机厂商,基于 Android 定制了自己的 ColorOS 系统,当前日活跃用户超过 2 亿。围绕 ColorOS,OPPO 构建了很多互联网应用,比如应用商店、浏览器、信息流等。在运营这些互联网应用的过程中,OPPO 积累了大量的数据,上图右边是整体数据规模的演进:从 2012 年开始每年都是 2~3 倍的增长速度,截至目前总数据量已经超过 100PB,日增数据量超过 200TB。要支撑这么大的一个数据量,OPPO 研发出一整套的数据系统与服务,并逐渐形成了自己的数据中台体系。 1.2.OPPO 数据中台 今年大家都在谈数据中台,OPPO 是如何理解数据中台的呢?我们把它分成了 4 个层次: 最下层是统一工具体系,涵盖了"接入 - 治理 - 开发 - 消费"全数据链路;基于工具体系之上构建了数据仓库,划分成"原始层 - 明细层 - 汇总层 - 应用层",这也是经典的数仓架构;再往上是全域的数据体系,什么是全域呢?就是把公司所有的业务数据都打通,形成统一的数据资产,比如 ID-Mapping、用户标签等;最终,数据要能被业务用起来,需要场景驱动的数据产品与服务。以上就是 OPPO 数据中台的整个体系,而数据仓库在其中处于非常基础与核心的位置。 ...

May 15, 2019 · 4 min · jiezi

Mysqlvarchar类型

1.varchar类型(1)varchar (N):中的N指的是字符的长度,即:该字段最多能存储多少个字符(characters),不是字节数。不管是一个中英文字符或者数字、或者一个汉字,都当做一个字符。【 a,我,1 都是一个字符,但是a和1是一个字节,‘我’(utf8下)是3个字节。 utf8mb4下:汉字也是3个字节,表情符号是4个字节 】(2)varchar 最多能存储 65535 个字节的数据。65535 = 所有字段的长度 + 变长字符的长度标识 + NULL标识位变长字符的长度标识:用1到2个字节表示实际长度(长度 >255 时,需要2个字节; <255 时,需要1个字节)NULL标识位:varchar字段定义中带有 default null 允许列空,则需要 1 bit 来标识,每 8 个bits的标识组成一个字段。一张表中存在N个varchar字段,那么需要(N+7)/8 (取整)bytes存储所有的NULL标识位。 (3)虽然InnoDB内部支持 varchar 65535 字节的行大小,但是MySQL本身对所有列的合并大小施加了 65535 字节的行大小限制。详情见例子 2.varchar 长度的编编限制:字符类型若为gbk,每个字符最多占2个字节,最大长度不能超过32766;字符类型若为utf8,每个字符最多占3个字节,最大长度不能超过21845。字符类型若为utf8mb4,每个字符最多占4个字节,最大长度不能超过16283。若定义的时候超过上述限制,则varchar字段会被强行转为text类型,并产生warning。 3.例子若一个表定义为create table t4(c int, c2 char(30), c3 varchar(N)) charset=utf8;则此处N的最大值为 (65535-1-2-4-30*3)/3=21812减 1:实际行存储从第二个字节开始;减 2:varchar 头部的2个字节表示长度减 4:原因是int类型的c占4个字节;减 30*3:原因是char(30)占用90个字节,编码是utf8。如果被varchar超过上述的b规则,被强转成text类型,则每个字段占用定义长度为11字节,当然这已经不是“varchar”了。 mysql> alter table t4 modify column c3 varchar(21813);ERROR 1118 (42000): Row size too large. The maximum row size for the used table type, not counting BLOBs, is 65535. This includes storage overhead, check the manual. You have to change some columns to TEXT or BLOBs

May 14, 2019 · 1 min · jiezi

mysql-sql语句大全MySQL语句-整理

mysql sql语句大全(MySQL语句 整理) 整理加入DESC 降序ASC 升序 复制代码 1、说明:创建数据库CREATE DATABASE database-name2、说明:删除数据库drop database dbname3、说明:备份sql server--- 创建 备份数据的 deviceUSE masterEXEC sp_addumpdevice 'disk', 'testBack', 'c:mssql7backupMyNwind_1.dat'--- 开始 备份BACKUP DATABASE pubs TO testBack4、说明:创建新表create table tabname(col1 type1 [not null] [primary key],col2 type2 [not null],..)根据已有的表创建新表:A:create table tab_new like tab_old (使用旧表创建新表)B:create table tab_new as select col1,col2… from tab_old definition only5、说明:删除新表drop table tabname6、说明:增加一个列Alter table tabname add column col type注:列增加后将不能删除。DB2中列加上后数据类型也不能改变,唯一能改变的是增加varchar类型的长度。7、说明:添加主键: Alter table tabname add primary key(col)说明:删除主键: Alter table tabname drop primary key(col)8、说明:创建索引:create [unique] index idxname on tabname(col….)删除索引:drop index idxname注:索引是不可更改的,想更改必须删除重新建。9、说明:创建视图:create view viewname as select statement删除视图:drop view viewname10、说明:几个简单的基本的sql语句选择:select * from table1 where 范围插入:insert into table1(field1,field2) values(value1,value2)删除:delete from table1 where 范围更新:update table1 set field1=value1 where 范围查找:select * from table1 where field1 like ’%value1%’ ---like的语法很精妙,查资料!排序:select * from table1 order by field1,field2 [desc]总数:select count as totalcount from table1求和:select sum(field1) as sumvalue from table1平均:select avg(field1) as avgvalue from table1最大:select max(field1) as maxvalue from table1最小:select min(field1) as minvalue from table111、说明:几个高级查询运算词A: UNION 运算符UNION 运算符通过组合其他两个结果表(例如 TABLE1 和 TABLE2)并消去表中任何重复行而派生出一个结果表。当 ALL 随 UNION 一起使用时(即 UNION ALL),不消除重复行。两种情况下,派生表的每一行不是来自 TABLE1 就是来自 TABLE2。B: EXCEPT 运算符EXCEPT 运算符通过包括所有在 TABLE1 中但不在 TABLE2 中的行并消除所有重复行而派生出一个结果表。当 ALL 随 EXCEPT 一起使用时 (EXCEPT ALL),不消除重复行。C: INTERSECT 运算符INTERSECT 运算符通过只包括 TABLE1 和 TABLE2 中都有的行并消除所有重复行而派生出一个结果表。当 ALL 随 INTERSECT 一起使用时 (INTERSECT ALL),不消除重复行。注:使用运算词的几个查询结果行必须是一致的。12、说明:使用外连接A、left (outer) join:左外连接(左连接):结果集几包括连接表的匹配行,也包括左连接表的所有行。SQL: select a.a, a.b, a.c, b.c, b.d, b.f from a LEFT OUT JOIN b ON a.a = b.cB:right (outer) join:右外连接(右连接):结果集既包括连接表的匹配连接行,也包括右连接表的所有行。C:full/cross (outer) join:全外连接:不仅包括符号连接表的匹配行,还包括两个连接表中的所有记录。12、分组:Group by: 一张表,一旦分组完成后,查询后只能得到组相关的信息。 组相关的信息:(统计信息) count,sum,max,min,avg 分组的标准) ...

May 10, 2019 · 8 min · jiezi

蚂蚁金服开源的机器学习工具-SQLFlow有何特别之处

阿里妹导读:近日,蚂蚁金服副 CTO 胡喜正式宣布开源机器学习工具 SQLFlow,他在大会演讲中表示:“未来三年,AI 能力会成为每一位技术人员的基本能力。我们希望通过开源 SQLFlow,降低人工智能应用的技术门槛,让技术人员调用 AI 像 SQL 一样简单。” SQLFlow 能够抽象出端到端从数据到模型的研发过程,配合底层的引擎及自动优化,具备基础 SQL 知识的技术人员即可完成大部分的机器学习模型训练及预测任务。SQLFlow 由何而来?蚂蚁金服对于 SQLFlow 未来还有哪些规划?一起来深入了解。 SQLFlow 的目标是将 SQL 引擎和 AI 引擎连接起来,让用户仅需几行 SQL 代码就能描述整个应用或者产品背后的数据流和 AI 构造。其中所涉及的 SQL 引擎包括 MySQL、Oracle、Hive、SparkSQL、Flink 等支持用 SQL 或其某个变种语言描述数据,以及描述对数据的操作的系统。而这里所指的 AI 引擎包括 TensorFlow、PyTorch 等深度学习系统,也包括 XGBoost、LibLinear、LibSVM 等传统机器学习系统。 SQLFlow 研发团队认为,在 SQLFlow 和 AI 引擎之间存在一个很大的空隙——如何把数据变成 AI 模型需要的输入。谷歌开源的 TensorFlow 项目开了一个好头,TFX Data Transform 和 feature column API 都是意图填补这个空缺的项目。但是这个空缺很大,是各种 SQL 引擎和各种 AI 引擎的笛卡尔积,远不是 TensorFlow 的这两个子项目就足以填补的,需要一个开源社区才行。要填补好这个空缺,需要先让用户意识到其重要性,这也是蚂蚁金服开源 SQLFlow 的意图之一。 SQLFlow 位于 AI 软件系统生态的最顶端,最接近用户,它也位于数据和数据流软件生态之上。 ...

May 9, 2019 · 3 min · jiezi

案例分享巧用工具提升无源码系统的性能和稳定性

导读:在没有核心系统源码的情况下,修改源码打印耗时的方法无法使用,通过tcpdump、wireshark、gdb、010 editor、火焰图、ida、数据库抓sql耗时语句、oracle ash报告、loadrunner等工具找到了服务器tps上不去、C程序进程随机挂掉的问题,并顺利解决,收获颇多。 背景公司最近新上线一个系统,主要架构如下: 测试环境系统部署后,出现了两个问题: 1.loadrunner压测tps上不去,压测java接口tps 单机只能到100多tps就上不去了,耗时从单次访问的100ms上升到110并发时的1s左右。2.压测期间C服务器1 经常不定时挂掉。 因为某些原因,该项目C相关程序没有源码,只有安装部署文件,为了解决上述两个问题,我们几个同事和重庆同事一块参与问题排查和解决。因为没有源码,中间经历了层层波折,经过一个月努力,终于解决了上述两个问题,整个排查过程学到了很多知识。 用到的分析工具1.tcpdump, 2.wireshark, 3.gdb, 4.010 editor, 5.火焰图, 6.ida, 7.数据库抓sql耗时语句, 8.oracle ash报告, 9.loadrunner 几句话总结1.C程序客户端socket长连接调用C服务端存在性能瓶颈,通过tcpdump,wireshark 二进制分析出传输协议后改用java调用C服务端,单机tps提升1倍,性能提升3倍 2.数据库语句存在for update 语句导致并发上不去,经过分析从业务上采用sequence 替换for update语句,并通过010 editor直接修改二进制 修改for update 语句相关逻辑为sequence ,系统具备了扩容服务器tps也能同步提升的能力 3.数据库insert语句并发情况下存在瓶颈,扩大oracle redo log日志大小解决,继续提升tps40%。 4.程序进程随机挂掉,通过gdb分析core dump文件,定位到在并发情况下程序中使用的gethostbyname有问题,采用临时方法解决。 分析过程1.第一次瓶颈定位 刚开始排查问题时,loadrunner压测java接口,并发用户从0逐渐增加到110个的情况下,tps到100左右就不再提升,响应耗时从100ms增大到1s。此时我们的分析重点是谁是当前的主要瓶颈 再看一遍架构图, 图中5个节点都有可能是瓶颈点,数据库此时我们通过数据库dba管理权限抓取耗时sql,没抓取到,先排除数据库问题,java的我们打印分步耗时日志,定位到jni调用 c客户端耗时占比最大。这时瓶颈点初步定位到C客户端,C服务端1,C服务端2 这三个节点。 因为没有源码,我们采用tcpdump抓包分析,在C服务器1上 tcpdump -i eth0 -s 0 -w aa.txt host java客户端ip抓出的包用wireshark分析 通过追踪流-TCP流 分析服务端耗时并没有变的太大,因为C客户端和C服务端是长连接,多个请求可能会共用一个连接,所以此时分析出的数据可能会不太准,因此我们采用loadrunner压测,其它条件不变,一台C服务器1和两台C服务器1分别查看耗时变化, 其它条件不变,一台java服务器和两台java服务器分别查看耗时变化. 最终定位到是C客户端的问题。(ps:在wireshark的分析数据时还跟秦迪大师弄明白了tcp延迟确认) 2.改造C客户端 C客户端和C服务端是通过长连接通信的,直接改造C代码难度较大,所有我们准备把C替换成java,这就需要分析C之间通信传参时候用的什么协议,然后根据这个协议用java重写。我们根据之前的经验推测出了编码协议,用wireshark分析二进制确认确实是这种编码。 我们根据这种协议编码采用java重写后,同样在110并发用户情况下,tps提升到了210(提升两倍),耗时降到了330ms(是原来的三分之一) 3.第二次瓶颈定位。 经过第二步优化后tps提升了两倍,但是此时扩容tomcat,扩容C服务器,tps就维持在210左右,不会变高了。因此我们继续进行定位新的瓶颈点。此时找dba要到一个实时查看oracle 耗时sql的语句 ...

May 9, 2019 · 1 min · jiezi

一次非常有意思的SQL优化经历从30248271s到0001s

本文来源 | toutiao.com/i6668275333034148356 作者 | Java技术架构 背景介绍 我用的数据库是mysql5.6,下面简单的介绍下场景: 课程表: 数据100条 学生表: 数据70000条 学生成绩表SC: 数据70w条 查询目的: 查找语文考100分的考生 查询语句: 执行时间:30248.271s 为什么这么慢?先来查看下查询计划: 发现没有用到索引,type全是ALL,那么首先想到的就是建立一个索引,建立索引的字段当然是在where条件的字段。 先给sc表的c_id和score建个索引 再次执行上述查询语句,时间为: 1.054s 快了3w多倍,大大缩短了查询时间,看来索引能极大程度的提高查询效率,看来建索引很有必要,很多时候都忘记建索引了,数据量小的的时候压根没感觉,这优化感觉挺爽。 但是1s的时间还是太长了,还能进行优化吗,仔细看执行计划: 查看优化后的sql: 补充:这里有网友问怎么查看优化后的语句 方法如下: 在命令窗口执行 有type=all 按照我之前的想法,该sql的执行的顺序应该是先执行子查询 耗时:0.001s 得到如下结果: 然后再执行 耗时:0.001s 这样就是相当快了啊,Mysql竟然不是先执行里层的查询,而是将sql优化成了exists子句,并出现了EPENDENT SUBQUERY, mysql是先执行外层查询,再执行里层的查询,这样就要循环70007*11=770077次。 那么改用连接查询呢? 这里为了重新分析连接查询的情况,先暂时删除索引sc_c_id_index,sc_score_index 执行时间是:0.057s 效率有所提高,看看执行计划: 这里有连表的情况出现,我猜想是不是要给sc表的s_id建立个索引 CREATE index sc_s_id_index on SC(s_id); show index from SC ...

May 9, 2019 · 1 min · jiezi

PL/SQL(Procedure Language & Structured Query Language )

1.基本语法declare v_id employees.employee_id % type ; --动态获取表中字段的类型 v_email employees.email % type ; v_salary employees.salary % type ;begin v_id := 105; select e.email, e.salary into v_email , v_salaryfrom employees ewhere e.employee_id = v_id;dbms_output.put_line( 'id:' || v_id || ', email:' || v_email ||', salary:' || v_salary);end; 2.记录类型declare type emp_rec is record( v_name employees.last_name %type,v_salary employees.salary %type,v_hire_date date );v_emp_rec emp_rec; v_emp_id employees.employee_id%type ;begin v_emp_id := 105; select e.last_name, e.salary , e.hire_date ...

April 22, 2019 · 5 min · jiezi

sql学习笔记

查询最近一小时的数据:select (current_date - to_date( '2015-1-1 15:14:44', 'yyyy-mm-dd hh24:mi:ss')) * 24 from dual ;--0.256666666666667假定还书逾期要罚款0.2。要使用一条Insert语句记录这条罚款:INSERT INTO libraryReturn(member, book, returnDate, fine)VALUES('jerry', 'book01', CURRENT_DATE, IFNULL( SELECT 0.2 fine FROM libraryLoan WHERE membername='jim' AND book='book01' GROUP BY member, book HAVING MAX(due_Date) < CURRENT_DATE ))mysql和oracle分别用ifNull(arg, n),nvl(arg, n)来替换null。oracle中:select e.empno , (select nvl(comm , 0) from emp where empno = e.empno ) from emp e;LOLI订房时要插入一条sql到roomBooking:INSERT INTO roomBooking(whn, wht, who) SELECT (DATE '2014-12-31', 'motel', 'LOLI') FROM DUAL WHERE NOT EXISTS( SELECT who FROM roomBooking WHERE whn = DATA '2014-12-31' AND wht = 'motel');避免冗余计算: ...

April 22, 2019 · 4 min · jiezi

【数据库】MySQL查询优化

欢迎关注公众号:【爱编程】如果有需要后台回复2019赠送1T的学习资料哦!! 背景在这个快速发展的时代,时间变得越来越重要,也流逝得非常得快,有些人长大了,有些人却变老了。稍不留神,2019已经过完了三分之一。回首这四个月收获什么,懂得了什么?欢迎留言分享给我哟。 **言归正传:MySQL的查询怎么才能更快,更合理?除了加索引还有什么可以学习的呢?** 原理要想更好地学习某样东西,从其原理和运作方式入手更容易掌握。道理你们都懂,我就不废话了。 MySQL发送查询请求,到底做了什么工作?下图是MySQL查询执行流程图: 客户端发送一条查询给服务器。服务器先检查查询缓存,如果命中了缓存,则立刻返回查询在缓存中的结果。否则会进入下一个阶段。3.服务端进行SQL解析、预处理、再由优化器生成对应的执行计划。4.MySQL根据优化器生成的执行计划,调用存储引擎的API来执行查询。5.将结果返回给客户端。 是什么导致MySQL查询变慢了?对于MySQL,最简单的衡量查询开销的三个指标如下: 响应时间扫描的行数返回的行数没有哪个指标能够完美地衡量查询的开销,但它们大致反映了MySQL在内部执行查询时需要访问多少数据,并可以大概推算出查询运行的时间。 查询慢的原因基本都是:我们的不合理操作导致查询的多余数据太多了。常见原因有以下: 1.查询不需要的记录。2.多表关联时返回全部列3.总是取出全部列常用优化技巧1.用索引最简单且见效最快的方式就是给你的条件加索引(主键索引,普通索引,唯一索引等)。注:索引是要另开辟一块空间存储的,所以不能不要钱滴都加索引。 2.关联子查询MySQL的子查询实现是非常糟糕的。比如下面的 SELECT * FROM book WHERE book_id IN (SELECT book_id FROM author WHERE author_id = 1)MySQL对IN()列表中的选项有专门的优化策略,一般会认为MySQL会先执行子查询返回所有包含author_id 为1的book_id。 或许你想MySQL的运行时这样子的: SELECT GROUP_CONCAT(book_id) FROM author WHERE author_id = 1SELECT * FROM book WHERE book_id IN (1,21,3,45,656,766,213,123)但是,MySQL会将相关的外层表压到子查询中的,就是下面的样子: SELECT * FROM book WHERE EXISTS (SELECT * FROM author WHERE author_id = 1 AND book.book_id = author.book_id)原因:因为子查询需要book_id ,所以MySQL认为无法先执行这个子查询,而是先对book 进行全表扫描,然后再根据book_id进行子查询。具体可以EXPLAIN该SQL进行分析。 建议:1.使用左外连接(LEFT OUTER JOIN)代替子查询。 ...

April 21, 2019 · 2 min · jiezi

select * 和 select 所有字段的区别

阅读本文大概需要 1 分钟。之前发过的文章中,关于 select * 和 select 所有字段的知识,有描述不恰当,这次重新纠正下,加深下理解。MySQL 5.1.37表记录数 41,547,002,即 4000+w 行。使用远程客户端取 1000 条数据,统计时间:SELECT * FROM dmsp.dmsp_dimension_content LIMIT 0, 1000;时间 2.218s,网络消耗 0.547s 。SELECT id, appid, aop, t, uid, sid, pid, pname, bid, bname, ptype, sm, sv, bt, national, area, ov FROM dmsp.dmsp_dimension_content LIMIT 0, 1000;取出所有字段,时间 2.250s,网络消耗 0.578s 。多次查询(改变 limit 条件避免缓存),时间变化不大。结论:两者差别几乎可忽略。所以查询所有字段(或者大多数字段)的时候,大可 select * 来操作。如果某些不需要的字段数据量特别大,还是写清楚字段比较好,因为这样可以减少网络传输。(1)减少数据的负担。SELECT *,需要数据库先 Query Table Metadata For Columns,一定程度上为数据库增加了负担(影响网络传输的性能),但是实际上,两者效率差别不大。(2)考虑到今后的扩展性。因为程序里面你需要使用到的列毕竟是确定的, SELECT * 只是减少了一句 SQL String 的长度,并不能减少其他地方的代码。(3)索引问题select abc from table; 和 select * from table;在 abc 字段有索引的情况下,mysql 是可以不用读 data,直接使用 index 里面的值就返回结果的。但是一旦用了 select ,就会有其他列需要读取,这时在读完 index 以后还需要去读 data 才会返回结果,这样就造成了额外的性能开销。综上:除平时练习使用,其他情况都不推荐使用 SELECT * FROM XXX 。·END·程序员的成长之路路虽远,行则必至本文原发于 同名微信公众号「程序员的成长之路」,回复「1024」你懂得,给个赞呗。往期精彩回顾程序员接私活的7大平台利器select count() 底层究竟做了什么? 删库后,除了跑路还能怎么办? MySQL索引优化看这篇文章就够了! 选择 25k 的 996 还是 18k 的 965一个完整的 Web 请求到底发生了什么 会写代码是你创业路上的包袱吗? 支付宝架构师眼中的高并发架构 最近话题火爆的四件事你知道不? ...

April 20, 2019 · 1 min · jiezi

3分钟干货之正排索引与倒排索引

△什么是正排索引(forward index)?简言之,由key查询实体的过程,使用正排索引。例如,用户表:t_user(uid, name, passwd, age, sex)由uid查询整行的过程,就时正排索引查询。又例如,网页库:t_web_page(url, page_content)由url查询整个网页的过程,也是正排索引查询。网页内容分词后,page_content会对应一个分词后的集合list。简易的,正排索引可以理解为:Map>能够由网页url快速找到内容的一个数据结构。画外音:时间复杂度可以认为是O(1)。△什么是倒排索引(inverted index)?与正排索引相反,由item查询key的过程,使用倒排索引。对于网页搜索,倒排索引可以理解为:Map>能够由查询词快速找到包含这个查询词的网页的数据结构。画外音:时间复杂度也是O(1)。举个例子,假设有3个网页:url1 -> “我爱北京”url2 -> “我爱到家”url3 -> “到家美好”这是一个正排索引:Map。分词之后:url1 -> {我,爱,北京}url2 -> {我,爱,到家}url3 -> {到家,美好}这是一个分词后的正排索引:Map>。分词后倒排索引:我 -> {url1, url2}爱 -> {url1, url2}北京 -> {url1}到家 -> {url2, url3}美好 -> {url3}由检索词item快速找到包含这个查询词的网页Map>就是倒排索引。画外音:明白了吧,词到url的过程,是倒排索引。正排索引和倒排索引是spider和build_index系统提前建立好的数据结构,为什么要使用这两种数据结构,是因为它能够快速的实现“用户网页检索”需求。画外音,业务需求决定架构实现,查询起来都很快。

April 19, 2019 · 1 min · jiezi

袋鼠云研发手记 | 开源·数栈-扩展FlinkSQL实现流与维表的join

作为一家创新驱动的科技公司,袋鼠云每年研发投入达数千万,公司80%员工都是技术人员,袋鼠云产品家族包括企业级一站式数据中台PaaS数栈、交互式数据可视化大屏开发平台Easy[V]等产品也在迅速迭代。在进行产品研发的过程中,技术小哥哥们能文能武,不断提升产品性能和体验的同时,也把这些提升和优化过程记录下来,现录入“袋鼠云研发手记”专栏中,以和业内童鞋们分享交流。下为“袋鼠云研发手记”专栏第三期,本期作者为袋鼠云数栈引擎团队。袋鼠云数栈引擎团队袋鼠云数栈引擎团队拥有多名专家级别,经验丰富的后端开发工程师,分别支撑公司大数栈产品线的不同子项目的开发需求,从项目中提取并开源了FlinkX(基于Flink的数据同步),Jlogstash(logstash 的java 版本实现),FlinkStreamSQL(扩展原生FlinkSQL,实现流与维表的join)多个项目。在长期的项目实践与产品迭代过程中,团队成员在 Hadoop技术栈上不断深耕探索,积累了丰富的经验与最佳实践。第三期数栈·开源 拓展FlinkSQL实现流与维表的joinFlinkStreamSQL 已经开源在Github上 目前已获380+Star1、为什么要扩展FlinkSQL?实时计算需要完全SQL化SQL是数据处理中使用最广泛的语言。它允许用户简明扼要地声明他们的业务逻辑。大数据批计算使用SQL很常见,但是支持SQL的实时计算并不多。其实,用SQL开发实时任务可以极大降低数据开发的门槛,在袋鼠云数栈-实时计算模块,我们决定实现完全SQL化。数据计算采用SQL的优势☑ 声明式。用户只需要表达我想要什么,至于怎么计算那是系统的事情,用户不用关心。☑ 自动调优。查询优化器可以为用户的 SQL 生成最有的执行计划。用户不需要了解它,就能自动享受优化器带来的性能提升。☑ 易于理解。很多不同行业不同领域的人都懂 SQL,SQL 的学习门槛很低,用 SQL 作为跨团队的开发语言可以很大地提高效率。☑ 稳定。SQL 是一个拥有几十年历史的语言,是一个非常稳定的语言,很少有变动。所以当我们升级引擎的版本时,甚至替换成另一个引擎,都可以做到兼容地、平滑地升级。实时计算还需要流与维表的JOIN在实时计算的世界里不只是流与流的JOIN,还需要流与维表的JOIN在实时计算的世界里不只是流与流的JOIN,还需要流与维表的JOIN。在去年,袋鼠云数栈V3.0版本研发期间,当时最新版本——flink1.6中FlinkSQL,已经将SQL的优势应用到Flink引擎中,但还未支持流与维表的JOIN。关于FlinkSQLFlinkSQL于2017年7月开始面向阿里巴巴集团开放流计算服务的,虽然是一个非常年轻的产品,但是到双11期间已经支撑了数千个作业,在双11期间,Blink 作业的处理峰值达到了5+亿每秒,而其中仅 Flink SQL 作业的处理总峰值就达到了3亿/秒。参考链接:https://yq.aliyun.com/article…这里先解释下什么是维表;维表是动态表,表里所存储的数据有可能不变,也有可能定时更新,但是更新频率不是很频繁。在业务开发中一般的维表数据存储在关系型数据库如mysql,oracle等,也可能存储在hbase,redis等nosql数据库。2、所以要用FlinkSQL实现流与维表的join 分两步:一、用Flink api实现维表的功能要实现维表功能就要用到 Flink Aysnc I/O 这个功能,是由阿里巴巴贡献给Apache Flink的。Async I/O 是由阿里巴巴贡献给社区的,于1.2版本引入,主要目的是为了解决与外部系统交互时网络延迟成为了系统瓶颈的问题。具体介绍可以看这篇文章:http://wuchong.me/blog/2017/0…对应到Flink 的api就是RichAsyncFunction 这个抽象类,继层这个抽象类实现里面的open(初始化),asyncInvoke(数据异步调用),close(停止的一些操作)方法,最主要的是实现asyncInvoke 里面的方法。流与维表的join会碰到两个问题:第一个是性能问题。因为流速要是很快,每一条数据都需要到维表做下join,但是维表的数据是存在第三方存储系统,如果实时访问第三方存储系统,不仅join的性能会差,每次都要走网络io;还会给第三方存储系统带来很大的压力,有可能会把第三方存储系统搞挂掉。所以解决的方法就是维表里的数据要缓存,可以全量缓存,这个主要是维表数据不大的情况,还有一个是LRU缓存,维表数据量比较大的情况。LRU维表的实现第二个问题是流延迟过来的数据这么跟之前的维表数据做关联。这个就涉及到维表数据需要存储快照数据,所以这样的场景用HBase 做维表是比较适合的,因为HBase 是天生支持数据多版本的。ALL维表的实现二、解析流与维表join的SQL语法转化成底层的FlinkAPI因为FlinkSQL已经做了大部分SQL场景,我们不可能在去解析SQL的所有语法,在把他转化成底层FlinkAPI。所以我们做的就是解析SQL语法,来找到join表里有没有维表,如果有维表,那我们会把这个join的维表的语句单独拆来,用Flink的TableAPI和StreamAPi 生成新DataStream,在把这个DataStream与其他的表在做join这样就能用SQL来实现流与维表的join语法了。SQL解析的工具就是用Apache calcite,Flink也是用这个框架做SQL解析的。所以所有语法都是可以解析的。1. DEMO SQLCalcite解析Insert into语句,拆分出子语句3. Calcite继续解析select语句Calcite继续解析select语句Calcite继续解析select语句

April 18, 2019 · 1 min · jiezi

InfluxDB 简介

InfluxDB 是一个时间序列数据库(TSDB), 被设计用来处理高写入、高查询负载,是 TICK 的一部分。TSDB 是针对时间戳或时间序列数据进行优化的数据库,专门为处理带有时间戳的度量和事件或度量而构建的。而时间序列数据可以是随时间跟踪、监视、下采样和聚合的度量或事件,如服务器指标、应用程序性能、网络数据、传感器数据以及许多其他类型的分析数据。关键特性能够高速读取和压缩时间序列数据使用 Go 编写,能够但文件运行,没有依赖提供了简单、高效的 HTTP 读写接口能够使用插件支持其他的数据协议,如: Graphite=, =collectd 和 OpenTSDB可轻松使用 SQL 语言查询聚合数据能够使用 Tag 进行快速高效的查询支持保留策略(Retention Policy), 能够自动清理旧数据支持持续查询,能够自动定期计算聚合数据,提高了查询的效率注意: 开源版本的 InfluxDB 只运行在单个节点上,如需更好的性能或避免单点故障,请使用企业版。安装deepin/Ubuntu/Debiansudo apt install influxdb influxdb-cliArchlinuxyaourt -S influxdb 或 sudo pacman -S influxdb其他请参见:Installing InfluxDB配置InfluxDB 的配置文件为: /etc/influxdb/influxdb.conf ,选项详情请参见:Configuration Settings,这里就不在赘述。基本操作服务相关启用/停止服务systemctl start/stop influxdb.service数据库连接数据库使用 influx 命令连接数据库,参看其帮组手册了解使用方法创建数据库CREATE DATABASE <name>删除数据库DROP DATABASE <name>列出数据库SHOW DATABASES选择数据库USE <name>写入查询InfluxDB 中使用 measurement 表示表, tags 表示表的元数据, fields 表示数据。表的 scheme 不用定义, null 值也不会被存储。tag 可理解为表中需要索引的列, field 是不需要索引的列, point 表示一条记录。tags 之间或 fields 之间使用 ‘,’ 分割, 而 tags 与 fields 之间使用空格分割。删除表DROP MEASUREMENTS <name>列出表SHOW MEASUREMENTS写入数据point 写入的语法如下:<measurement>[,<tag-key>=<tag-value>…] <field-key>=<field-value>[,<field2-key>=<field2-value>…] [unix-nano-timestamp]插入一条 cpu load 的数据: INSERT cpu_load,machine=001,region=ch value=0.56 ,这就向名为 cpu_load 的 measurement 中添加了 tags 为 machine 和 region , fields 为 value 的 point 。不指定 timestamp 时,默认会使用 本地的当前时间 作为 timestamp 。查询数据查询语法:SELECT <field_key>[,<field_key>,<tag_key>] FROM <measurement_name>[,<measurement_name>]查询语句中必须要有 field 存在 ,查询语句还支持 Go 风格的正则,下面给出一些例子。SELECT * FROM cpu_load查询 cpu_load 中的所有 fields 和 tagsSELECT ::field FROM cpu_load查询 cpu_load 中的所有 fieldsSELECT value,machine FROM cpu_load只查询 value 与 machineSELECT value::field,machine::tag FROM cpu_load只查询 value 与 machine ,并限定了类型,如果类型错误将返回 null ,如果所有查询字段的类型都错误将没有 point 返回SELECT * FROM /./查询所有表中的所有字段注意: WHERE 语句后的值不为数字的,必须引起来。更多用法参见: Data exploration using InfluxQL 。删除 PointInfluxDB 不支持 Point 的删除操作,但可以通过 Retention Policy 清理 Point 。SERIESSERIES 是 measurement,<tag1>,<tag2>… 的集合,如之前的写入的 SERIES 就是 cpu_load,machine,region查看语法:SHOW SERIES FROM [measurement],[tag1],[tag2]…FROM 可以不加,如:SHOW SERIES 显示数据库中所有的 seriesSHOW SERIES FROM cpu_load 显示表 cpu_load 中的所有 series删除DROPDROP 将删除所有的记录,并删除所有的索引,语法:DROP SERIES FROM <measurement> WHERE [condition]DELETEDELETE 将删除所有的记录,但不会删除索引,并支持在 WHERE 语句中使用 =timestamp=,语法:DELETE FROM <measurement_name> WHERE [<tag_key>=’<tag_value>’] | [<time interval>]~持续查询连续查询(Continuous Queries 简称 CQ)是 InfluxQL 对实时数据自动周期运行的查询,然后把查询结果写入到指定的 measurement 中。语法如下:CREATE CONTINUOUS QUERY <cq_name> ON <database_name>BEGIN <cq_query>END删除语法: DROP CONTINUOUS QUERY <cq_name> ON <database_name>cq_query 需要一个函数,一个 INTO 子句和一个 GROUP BY time() 子句:SELECT <function[s]> INTO <destination_measurement> FROM <measurement> [WHERE <stuff>] GROUP BY time(<interval>)[,<tag_key[s]>]注意: 在 WHERE 子句中, cq_query 不需要时间范围。 InfluxDB 在执行 CQ 时自动生成 cq_query 的时间范围。cq_query 的 WHERE 子句中的任何用户指定的时间范围将被系统忽略。如创建一个一分钟采样一次 cpu_load 并写入 cpu_load_1min 表的连续查询:CREATE CONTINUOUS QUERY “cpu_load_1min” ON “learn_test"BEGIN SELECT mean(“value”) INTO “cpu_load_1min” FROM “cpu_load” GROUP BY time(1m)ENDvalue 将以 mean 为名保存在 cpu_load_1min 中。更多高级用法参加: InfluxQL Continuous Queries保留策略InfluxDB 是没有提供直接删除数据记录的方法,但是提供数据保存策略,主要用于指定数据保留时间,超过指定时间,就删除这部分数据。可以有多个 RP 并存,但 default 表明默认策略。更多用法参见: Database management using InfluxQL 。列出SHOW RETENTION POLICY ON <database name>创建创建语法:CREATE RETENTION POLICY <retention_policy_name> ON <database_name> DURATION <duration> REPLICATION <n> [SHARD DURATION <duration>] [DEFAULT]REPLICATION 子句确定每个点在集群中存储多少个独立副本,其中 n 是数据节点的数量,对单节点实例无效。碎片持续时间子句确定碎片组覆盖的时间范围,是一个 duration 文字,不支持 INF (infinite) duration 。这个设置是可选的。默认情况下,碎片组的持续时间由保留策略的持续时间决定:RP DurationShard Duration< 2 days1 hour>= 2 days and <= 6 months1 day> 6 months7 days如果 RP Duration 大于 0s 小于 1 hour , Shard Duration 仍将设置为 1 hour 。删除DROP RETENTION POLICY <rp_name>修改ALTER RETENTION POLICY <rp_name> ON <database name> DURATION <duration> REPLICATION <n> [SHARD DURATION <duration>] DEFAULTHTTP 接口/query数据主要使用 query 接口查询,下面给出一些常见用法,而更多用法参见: Querying data with the HTTP API 。创建数据库POST 请求可用于创建数据库,如:curl -X POST http://localhost:8086/query –data-urlencode “q=CREATE DATABASE <database name>“查询curl -X GET http://localhost:8086/query?pretty=true –data-urlencode ‘db=<database name>’ –data-urlencode ‘q=SELECT “field1”,“tag1”… FROM <measurement> WHERE <condition>‘多个查询多个查询语句间用 ; 分割,如:curl -X GET http://localhost:8086/query?pretty=true –data-urlencode ‘db=<database name>’ –data-urlencode ‘q=SELECT “field1”,“tag1”… FROM <measurement> WHERE <condition>;SELECT fields FROM <measurement>‘最大行限制(max-row-limit) 允许使用者限制返回结果的数目,以保护InfluxDB不会在聚合结果的时候导致的内存耗尽。分块(chunking) 可以设置参数 chunked=true 开启分块,使返回的数据是流式的 batch ,而不是单个的返回。返回结果可以按 100 数据点被分块,为了改变这个返回最大的分块的大小,可以在查询的时候加上 chunk_size 参数,例如返回数据点是每 20000 为一个批次。curl -X GET ‘http://localhost:8086/query’ –data-urlencode “db=<name>” –data-urlencode “chunked=true” –data-urlencode “chunk_size=100” –data-urlencode “q=SELECT * FROM cpu_load”/write发送 POST 请求是写入数据的主要方式,,下面给出一些常见用法,而更多用法参见: Writing data with the HTTP API 。插入一条 Pointcurl -X POST http://localhost:8086/write?db=<database name> –data-binary “cpu_load,machine=001,region=cn value=0.56 1555164637838240795"必须指定 database name插入多条 Point多条 Point 之间用行分割,如:curl -X POST http://localhost:8086/write?db=<database name> –data-binary “cpu_load,machine=001,region=cn value=0.56 1555164637838240795cpu_load,machine=001,region=cn value=0.65 1555164637838340795cpu_load,machine=003,region-cn value=0.6 1555164637839240795"如果需要写入 Point 过多,可以将 Point 放入文件中,然后通过 POST 请求上传。文件(cpu_data.txt)内容如:cpu_load,machine=001,region=cn value=0.56 1555164637838240795cpu_load,machine=001,region=cn value=0.65 1555164637838340795cpu_load,machine=003,region-cn value=0.6 1555164637839240795然后上传:curl -X POST http://localhost:8086/write?db=<database name> –data-binary @cpu_data.txt ...

April 14, 2019 · 3 min · jiezi

clickhouse两个表关联后出现几十位的小数

问题在一次clickhouse的数据查询的时候,QA反馈说列表中某些指标数据出现了几十位的小数。开始以为是DataFormat时候bug导致的。于是从接口入手开始跟数据,一直跟到数据源,发现几十位的小数点一直都在。最后打印了执行sql在DataGrip中执行。发现sql产生的数据就带有几十位的小数点,详见图例。之前在clickhouse聚合查询的时候会出现小数后面数字浮动的情况,但这种明显又是另一类的问题。解决于是开始分析这个几千行的sql,精简后其实主要是两个表的单独分组聚合然后再进行关联。单独执行子查询数据是正常的。经过反复尝试后,发现是 子查询 子查询 子查询 中使用了 select * 导致的,将这里换成具体查询的字段数据就正常了。这里使用*是因为报表是需要根据用户所选择的字段动态展示。SQLSELECT *FROM ( SELECT fieldsa, fieldsb, fields1c FROM table1 WHERE condition1 ORDER BY fields1c DESC )ANY LEFT JOIN ( SELECT * //将这里换成具体要查询的字段就可以了 FROM ( SELECT fieldsa, fieldsb, round(sum(fields2c) / 1, 2) AS fields2c, round(sum(fields2d) / 1, 2) AS fields2d FROM table2 WHERE condition2 GROUP BY fieldsa,fieldsb ) ALL FULL JOIN ( SELECT fieldsa, fieldsb, round(sum(fields3c) / 1, 2) AS fields3c, round(sum(fields3d) / 1, 2) AS fields3d FROM table3 WHERE condition3 GROUP BY fieldsa,fieldsb ) USING fieldsa,fieldsb WHERE condition4 )USING fieldsa,fieldsb图例 ...

April 10, 2019 · 1 min · jiezi

MySQL 常用命令

大学学的数据库系统概论工作后几年没有使用都已忘了, 现在项目需要用到数据库, 但来不及细看相关书籍了, 遂将一些常用的记录下来.常用类型MySQL 支持多种类型, 大致可以分为三类: 数值, 日期/时间和字符串(字符)类型, 大致如下:数值类型整数: tinyint, smallint, mediumint, int, bigint浮点数: float, double, decimal日期和时间date, time, datetime, timestamp, year字符串类型字符串: char, varchar文本: tinytext, text, mediumtext, longtext二进制字符串: tinyblob, blob, mediumblob, longblob下面将详细给出每种类型的大小和描述.数值类型类型字节描述tinyint1小整数值smallint2大整数值mediumint3大整数值int4大整数值bigint8极大整数值float4单精度浮点数double8双精度浮点数decimaldecimal(M,D)定点数DECIMAL(M,D) 高精度的数据类型, 常用来存储交易相关的数据M 代表总精度, N 代表小数点右侧的位数. 1 < M < 254, 0 < N < 60, 存储空间变长日期和时间类型字节描述date3精确到年月日, 如: 2016-09-01time3精确到时分秒, 如: 09:10:11datetime8精确到年月日时分秒, 如: 2016-09-01 09:10:11timedtamp8精确到年月日时分秒, 如: 2016-09-01 09:10:11MySQL 5.6.4 之后, datetime 和 timestamp 支持到微秒timestamp 会根据时区进行转换, datetime 则不会timestamp 存储范围: 1970-01-01 00::00:01 ~ 2038-01-19 03:14:07datetime 的存储范围: 1000-01-01 00:00:00 to 9999-12-31 23:59:59一般使用 timestamp 国际化如存时间戳使用数字类型 BIGINT字符串类型类型大小描述char最大为255字符存储定长, 容易造成空间的浪费varchar可以超过255个字符存储变长,节省存储空间text总大小为65535字节,约为64KB长文本数据根据 MySQL 版本的不同, 类型的大小范围可能会有改变.常用命令数据库创建CREATE DATABASE <name>;删除DROP DATABASE <name>;列出数据库show databases;使用数据库use <name>;修改 CHARSETALTER DATABASE <name> DEFAULT CHARACTER SET <char name>;显示创建类型show create database <name>表创建CREATE TABLE <name>(id INT(11) AUTO_INCREMENT),name VARCHAR(64) NOT NULL,…)CHARACTER SET=utf8mb4;删除DROP TABLE <name>;插入INSERT INTO <name>(filed1, filed2,…) VALUES (value1, value2,…),(value3,value4,…),…;可同时插入多条记录查询SELECT filed1,filed2,… FROM <name> WHERE <condition>多表查询: SELECT t1.id,t1.name,t2.name AS desc FROM t1,t2 WHERE t1.t2_id=t2.id, 这将会返回两个表的交集多表查询也可使用联合查询, 联合查询详情见后文.使用 LIKE 可以模糊查询, % 来表示任意字符, 如: SELECT * FROM <name> WHERE name LIKE ‘%oy%’;显示表显示表字段desc <name>显示表创建字段show create table <name>列出表show tables;修改值UPDATE <name> SET <filed1>=value1 WHERE <condition>添加列ALTER TABLE <name> ADD filed1 <type>删除列ALTER TABLE <name> DROP filed1修改列类型ALTER TABLE <name> MODIFY filed1 <type>ALTER TABLE <name> CHANGE filed1 filed1 <type> 可以修改列名修改 CHARSET修改表的默认 CHARSETALTER TABLE <name> DEFAULT CHARACTER SET utf8mb4修改表及所以列的字符ALTER TABLE <name> CONVERT TO CHARACTER SET utf8mb4修改列的字符集ALTER TABLE <name> CHANGE filed1 CHARACTER SET utf8mb4备份/恢复备份mysqldump -h<address> –port <port> -u<username> -p<password> <datebase name> <table name> > bak.sql其中 table name 可选恢复mysql -h<address> –port <port> -u<username> -p<password> <datebase name> < bak.sql导入数据到指定的数据库中联合查询假设存在表: user(id, name, email) 和 user_profile(id,uid,avatar), 几种联合方式如下:左联LEFT JOIN 或 LEFT OUTER JOIN 返回的结果包含左表中的所有行, 若左行在右行中匹配, 则在对应的右表中显示为 NULLSELECT * FROM user LEFT JOIN user_profile ON user.id=user_profile.uid右联RIGHT JOIN 或 RIGHT OUTER JOIN 返回的结果包含右表中的所有行, 若左行在右行中匹配, 则在对应的左表中显示为 NULL全联FULL JOIN 或 FULL OUTER JOIN 返回左右两表中的所有行, 如果右表中某行在左表中没有匹配, 则结果中对应行右表的部分全部为 NULL;如果左表中某行在右表中没有匹配, 则结果中对应行左表的部分全部为空 NULL.内联inner join 是比较运算符, 只返回符合条件的行, 如:SELECT * FROM user INNER JOIN user_profile ON user.id=user_profile.uid等同于 SELECT * FROM user,user_profile WHERE user.id=user_profile.uid ...

April 7, 2019 · 2 min · jiezi

TiDB 3.0.0 Beta.1 Release Notes

2019 年 03 月 26 日,TiDB 发布 3.0.0 Beta.1 版,对应的 TiDB-Ansible 版本为 3.0.0 Beta。相比 3.0.0 Beta 版本,该版本对系统稳定性、易用性、功能、优化器、统计信息以及执行引擎做了很多改进。TiDBSQL 优化器支持使用 Sort Merge Join 计算笛卡尔积支持 Skyline Pruning,用一些规则来防止执行计划过于依赖统计信息支持 Window FunctionsNTILELEAD 和 LAGPERCENT_RANKNTH_VALUECUME_DISTFIRST_VALUE 和 LAST_VALUERANK 和 DENSE_RANKRANGE FRAMEDROW FRAMEDROW NUMBER增加了一类统计信息,表示列和 handle 列之间顺序的相关性SQL 执行引擎增加内建函数JSON_QUOTEJSON_ARRAY_APPENDJSON_MERGE_PRESERVEBENCHMARKCOALESCENAME_CONST根据查询上下文优化 Chunk 大小,降低 SQL 执行时间和集群的资源消耗权限管理支持 SET ROLE 和 CURRENT_ROLE支持 DROP ROLE支持 CREATE ROLEServer新增 /debug/zip HTTP 接口,获取当前 TiDB 实例的信息支持使用 show pump status/show drainer status 语句查看 Pump/Drainer 状态支持使用 SQL 语句在线修改 Pump/Drainer 状态支持给 SQL 文本加上 HASH 指纹,方便追查慢 SQL新增 log_bin 系统变量,默认:0,管理 binlog 开启状态,当前仅支持查看状态支持通过配置文件管理发送 binlog 策略支持通过内存表 INFORMATION_SCHEMA.SLOW_QUERY 查询慢日志将 TiDB 显示的 MySQL Version 从 5.7.10 变更为 5.7.25统一日志格式规范,利于工具收集分析增加监控项 high_error_rate_feedback_total,记录实际数据量与统计信息估算数据量差距情况新增 Database 维度的 QPS 监控项 , 可以通过配置项开启DDL增加ddl_error_count_limit全局变量,默认值:512,限制 DDL 任务重试次数,超过限制次数会取消出错的 DDL支持 ALTER ALGORITHM INPLACE/INSTANT支持 SHOW CREATE VIEW 语句支持 SHOW CREATE USER 语句PD统一日志格式规范,利于工具收集分析模拟器支持不同 store 可采用不同的心跳间隔时间添加导入数据的场景热点调度可配置化增加 store 地址为维度的监控项,代替原有的 Store ID优化 GetStores 开销,加快 Region 巡检周期新增删除 Tombstone Store 的接口TiKV优化 Coprocessor 计算执行框架,完成 TableScan 算子,单 TableScan 即扫表操作性能提升 5% ~ 30%实现行 BatchRows 和列 BatchColumn 的定义- 实现 VectorLike 使得编码和解码的数据能够用统一的方式访问- 定义 BatchExecutor 接口,实现将请求转化为 BatchExecutor 的方法 - 实现将表达式树转化成 RPN 格式 - TableScan 算子实现为 Batch 方式,通过向量化计算加速计算统一日志格式规范,利于工具收集分析支持 Raw Read 接口使用 Local Reader 进行读新增配置信息的 Metrics新增 Key 越界的 Metrics新增碰到扫越界错误时 Panic 或者报错选项增加 Insert 语义,只有在 Key 不存在的时候 Prewrite 才成功,消除 Batch GetBatch System 使用更加公平的 batch 策略tikv-ctl 支持 Raw scanToolsTiDB-Binlog新增 Arbiter 工具支持从 Kafka 读取 binlog 同步到 MySQLReparo 支持过滤不需要同步的文件支持同步 generated columnLightning支持禁用 TiKV periodic Level-1 compaction,当 TiKV 集群为 2.1.4 或更高时,在导入模式下会自动执行 Level-1 compaction根据 table_concurrency 配置项限制 import engines 数量,默认值:16,防止过多占用 importer 磁盘空间支持保存中间状态的 SST 到磁盘,减少内存使用优化 TiKV-Importer 导入性能,支持将大表的数据和索引分离导入支持 CSV 文件导入数据同步对比工具 (sync-diff-inspector)支持使用 TiDB 统计信息来划分对比的 chunk支持使用多个 column 来划分对比的 chunkAnsibleN/A ...

March 27, 2019 · 2 min · jiezi

一次非常有趣的 SQL 优化经历

阅读本文大概需要 6 分钟。前言在网上刷到一篇数据库优化的文章,自己也来研究一波。场景数据库版本:5.7.25 ,运行在虚拟机中。课程表#课程表create table Course(c_id int PRIMARY KEY,name varchar(10))增加 100 条数据#增加课程表100条数据DROP PROCEDURE IF EXISTS insert_Course;DELIMITER $CREATE PROCEDURE insert_Course()BEGIN DECLARE i INT DEFAULT 1; WHILE i<=100 DO INSERT INTO Course(`c_id`,`name`) VALUES(i, CONCAT(‘语文’,i+’’)); SET i = i+1; END WHILE;END $CALL insert_Course();运行耗时CALL insert_Course();> OK> 时间: 0.152s课程数据学生表#学生表create table Student(s_id int PRIMARY KEY,name varchar(10))增加 7W 条数据#学生表增加70000条数据DROP PROCEDURE IF EXISTS insert_Student;DELIMITER $CREATE PROCEDURE insert_Student()BEGIN DECLARE i INT DEFAULT 1; WHILE i<=70000 DO INSERT INTO Student(`s_id`,`name`) VALUES(i, CONCAT(‘张三’,i+’’)); SET i = i+1; END WHILE;END $CALL insert_Student();运行结果CALL insert_Student();> OK> 时间: 175.838s学生数据成绩表#成绩表CREATE table Result(r_id int PRIMARY KEY,s_id int,c_id int,score int)增加 70W 条数据#成绩表增加70W条数据DROP PROCEDURE IF EXISTS insert_Result;DELIMITER $CREATE PROCEDURE insert_Result()BEGIN DECLARE i INT DEFAULT 1; DECLARE sNum INT DEFAULT 1; DECLARE cNum INT DEFAULT 1; WHILE i<=700000 DO if (sNum%70000 = 0) THEN set sNum = 1; elseif (cNum%100 = 0) THEN set cNum = 1; end if; INSERT INTO Result(`r_id`,`s_id`,`c_id`,`score`) VALUES(i,sNum ,cNum , (RAND()99)+1); SET i = i+1; SET sNum = sNum+1; SET cNum = cNum+1; END WHILE;END $CALL insert_Result();运行结果CALL insert_Result();> OK> 时间: 2029.5s成绩数据测试业务需求查找 语文1 成绩为 100 分的考生查询语句#查询语文1考100分的考生select s. from Student s where s.s_id in (select s_id from Result r where r.c_id = 1 and r.score = 100)执行时间:0.937s查询结果:32 位满足条件的学生用了 0.9s ,来查看下查询计划:EXPLAINselect s. from Student s where s.s_id in (select s_id from Result r where r.c_id = 1 and r.score = 100)发现没有用到索引,type 全是 ALL ,那么首先想到的就是建立一个索引,建立索引的字段当然是在 where 条件的字段了。查询结果中 type 列:all 是全表扫描,index 是通过索引扫描。先给 Result 表的 c_id 和 score 建立个索引CREATE index result_c_id_index on Result(c_id);CREATE index result_score_index on Result(score);再次执行上述查询语句,时间为:0.027s快了 34.7 倍(四舍五入),大大缩短了查询的时间,看来索引能极大程度的提高查询效率,在合适的列上面建立索引很有必要,很多时候都忘记建立索引,数据量小的时候没什么感觉,这优化的感觉很 nice 。相同的 SQL 语句多次执行,你会发现第一次是最久的,后面执行所需的时间会比第一次执行短些许,原因是,相同语句第二次查询会直接从缓存中读取。0.027s 很短了,但是还能再进行优化吗,仔细看下执行计划:查看优化后的 SQL :SELECT `example`.`s`.`s_id` AS `s_id`, `example`.`s`.`name` AS `name` FROM `example`.`Student` `s` semi JOIN ( `example`.`Result` `r` ) WHERE ( ( `example`.`s`.`s_id` = &lt;subquery2&gt;.`s_id` ) AND ( `example`.`r`.`score` = 100 ) AND ( `example`.`r`.`c_id` = 1 ) )怎么查看优化后的语句呢?方法如下(在命令窗口执行):#先执行EXPLAINselect s. from Student s where s.s_id in (select s_id from Result r where r.c_id = 1 and r.score = 100);#在执行show warnings;结果如下有 type = all按照之前的想法,该 SQL 执行的顺序是执行子查询select s_id from Result r where r.c_id = 1 and r.score = 100耗时:1.402s得到如下结果(部分)然后在执行select s. from Student s where s.s_id in (12871,40987,46729,61381,3955,10687,14047,26917,28897,31174,38896,56518,10774,25030,9778,12544,24721,27295,60361,38479,46990,66988,6790,35995,46192,47578,58171,63220,6685,67372,46279,64693)耗时:0.222s比一起执行快多了,查看优化后的 SQL 语句,发现MySQL 竟然不是先执行里层的查询,而是将 SQL 优化成了 exists 字句,执行计划中的 select_type 为 MATERIALIZED(物化子查询)。MySQL 先执行外层查询,在执行里层的查询,这样就要循环学生数量满足条件的学生 ID 次,也就是 7W 32 次。物化子查询: 优化器使用物化能够更有效的来处理子查询。物化通过将子查询结果作为一个临时表来加快查询执行速度,正常来说是在内存中的。mysql 第一次需要子查询结果是,它物化结果到一张临时表中。在之后的任何地方需要该结果集,mysql 会再次引用临时表。优化器也许会使用一个哈希索引来使得查询更快速代价更小。索引是唯一的,排除重复并使得表数据更少。那么改用连接查询呢?这里为了重新分析连接查询的情况,先暂时删除索引 result_c_id_index ,result_score_index 。DROP index result_c_id_index on Result;DROP index result_score_index on Result;连接查询select s. from Student s INNER JOIN Result r on r.s_id = s.s_id where r.c_id = 1 and r.score = 100;执行耗时:1.293s查询结果用了 1.2s ,来看看执行计划( EXPLAIN + 查询 SQL 即可查看该 SQL 的执行计划):这里有连表的情况出现,我猜想是不是要给 result 表的 s_id 建立个索引CREATE index result_s_id_index on Result(s_id);show index from Result;在执行连接查询耗时:1.17s (有点奇怪,按照所看文章的时间应该会变长的)看下执行计划:优化后的查询语句为:SELECT `example`.`s`.`s_id` AS `s_id`, `example`.`s`.`name` AS `name` FROM `example`.`Student` `s` JOIN `example`.`Result` `r` WHERE ( ( `example`.`s`.`s_id` = `example`.`r`.`s_id` ) AND ( `example`.`r`.`score` = 100 ) AND ( `example`.`r`.`c_id` = 1 ) )貌似是先做的连接查询,在进行的 where 条件过滤。回到前面的执行计划:这里是先做的 where 条件过滤,再做连表,执行计划还不是固定的,那么我们先看下标准的 sql 执行顺序:正常情况下是先 join 再进行 where 过滤,但是我们这里的情况,如果先 join ,将会有 70W 条数据发送 join ,因此先执行 where 过滤式明智方案,现在为了排除 mysql 的查询优化,我自己写一条优化后的 sql 。先删除索引DROP index result_s_id_index on Result;执行自己写的优化 sqlSELECT s. FROM ( SELECT FROM Result r WHERE r.c_id = 1 AND r.score = 100 ) tINNER JOIN Student s ON t.s_id = s.s_id耗时为:0.413s比之前 sql 的时间都要短。查看执行计划先提取 result 再连表,这样效率就高多了,现在的问题是提取 result 的时候出现了扫描表,那么现在可以明确需要建立相关索引。CREATE index result_c_id_index on Result(c_id);CREATE index result_score_index on Result(score);再次执行查询SELECT s. FROM ( SELECT FROM Result r WHERE r.c_id = 1 AND r.score = 100 ) tINNER JOIN Student s ON t.s_id = s.s_id耗时为:0.044s这个时间相当靠谱,快了 10 倍。执行计划:我们会看到,先提取 result ,再连表,都用到了索引。那么再来执行下 sql :EXPLAINselect s. from Student s INNER JOIN Result r on r.s_id = s.s_id where r.c_id = 1 and r.score = 100;执行耗时:0.050s执行计划:这里是 mysql 进行了查询语句优化,先执行了 where 过滤,再执行连接操作,且都用到了索引。扩大测试数据,调整内容为 result 表的数据增长到 300W ,学生数据更为分散。DROP PROCEDURE IF EXISTS insert_Result_TO300W;DELIMITER $CREATE PROCEDURE insert_Result_TO300W()BEGIN DECLARE i INT DEFAULT 700001; DECLARE sNum INT DEFAULT 1; DECLARE cNum INT DEFAULT 1; WHILE i<=3000000 DO INSERT INTO Result(`r_id`,`s_id`,`c_id`,`score`) VALUES(i,(RAND()*69999)+1 ,(RAND()*99)+1 , (RAND()99)+1); SET i = i+1; END WHILE;END $CALL insert_Result_TO300W();更换了一下数据生成的方式,全部采用随机数格式。先回顾下:show index from Result;执行 sqlselect s. from Student sINNER JOIN Result ron r.s_id = s.s_idwhere r.c_id = 81 and r.score = 84;执行耗时:1.278s执行计划:这里用到了 intersect 并集操作,即两个索引同时检索的结果再求并集,再看字段 score 和 c_id 的区分度,但从一个字段看,区分度都不是很大,从 Result 表检索,c_id = 81 检索的结果是 81 ,score = 84 的结果是 84 。而 c_id = 81 and score = 84 的结果是 19881,即这两个字段联合起来的区分度还是比较高的,因此建立联合索引查询效率将会更高,从另外一个角度看,该表的数据是 300W ,以后会更多,就索引存储而言,都是不小的数目,随着数据量的增加,索引就不能全部加载到内存,而是要从磁盘读取,这样索引的个数越多,读磁盘的开销就越大,因此根据具体业务情况建立多列的联合索引是必要的,我们来试试。DROP index result_c_id_index on Result;DROP index result_score_index on Result;CREATE index result_c_id_score_index on Result(c_id,score);指向上述查询语句消耗时间:0.025s这个速度就就很快了,可以接受。该语句的优化暂时告一段落。总结MySQL 嵌套子查询效率确实比较低可以将其优化成连接查询连接表时,可以先用 where 条件对表进行过滤,然后做表连接(虽然 MySQL 会对连表语句做优化)建立合适的索引,必要时建立多列联合索引学会分析 sql 执行计划,mysql 会对 sql 进行优化,所有分析计划很重要知识扩展索引优化上面讲到子查询的优化,以及如何建立索引,而且在多个字段索引时,分别对字段建立了单个索引。后面发现其实建立联合索引效率会更高,尤其是在数据量较大,单个列区分度不高的情况下。单列索引查询语句如下:select from user_test_copy where sex = 2 and type = 2 and age = 10索引:CREATE index user_test_index_sex on user_test_copy(sex);CREATE index user_test_index_type on user_test_copy(type);CREATE index user_test_index_age on user_test_copy(age);分别对 sex ,type ,age 字段做了索引,数据量为300w查询时间:0.415s执行计划:发现 type = index_merge这是mysql对多个单列索引的优化,对结果集采用intersect并集操作多列索引多列索引我们可以在这3个列上建立多列索引,将表copy一份以便做测试。create index user_test_index_sex_type_age on user_test(sex,type,age);查询语句:select from user_test where sex = 2 and type = 2 and age = 10执行时间:0.032s快了10多倍,且多列索引的区分度越高,提高的速度也越多。执行计划:最左前缀多列索引还有最左前缀的特性:都会使用到索引,即索引的第一个字段sex要出现在where条件中。执行一下语句:select from user_test where sex = 2select from user_test where sex = 2 and type = 2select from user_test where sex = 2 and age = 10索引覆盖就是查询的列都建立了索引,这样在获取结果集的时候不用再去磁盘获取其它列的数据,直接返回索引数据即可如:select sex,type,age from user_test where sex = 2 and type = 2 and age = 10执行时间:0.003s要比取所有字段快的多排序select from user_test where sex = 2 and type = 2 ORDER BY user_name时间:0.139s在排序字段上建立索引会提高排序的效率select from user_test where sex = 2 and type = 2 ORDER BY user_name最后附上一些sql调优的总结,以后有时间再深入研究列类型尽量定义成数值类型,且长度尽可能短,如主键和外键,类型字段等等建立单列索引根据需要建立多列联合索引当单个列过滤之后还有很多数据,那么索引的效率将会比较低,即列的区分度较低,那么如果在多个列上建立索引,那么多个列的区分度就大多了,将会有显著的效率提高。根据业务场景建立覆盖索引只查询业务需要的字段,如果这些字段被索引覆盖,将极大的提高查询效率多表连接的字段上需要建立索引这样可以极大的提高表连接的效率where条件字段上需要建立索引排序字段上需要建立索引分组字段上需要建立索引Where条件上不要使用运算函数,以免索引失效·END·程序员的成长之路路虽远,行则必至本文原发于 同名微信公众号「程序员的成长之路」,回复「1024」你懂得,给个赞呗。微信ID:cxydczzl往期精彩回顾程序员接私活的7大平台利器Java程序员的成长之路白话TCP为什么需要进行三次握手Java性能优化的50个细节(珍藏版)设计电商平台优惠券系统一个对话让你明白架构师是做什么的?教你一招用 IDE 编程提升效率的骚操作!送给程序员们的经典电子书大礼包 ...

March 26, 2019 · 4 min · jiezi

卤蛋跌跌撞撞的入门之路之SQL按条件计数&按条件加和

背景本卤蛋小白一枚,刷题时被大家都说简单的SQL难住了,题目涉及同时按条件计数和按条件加和最后靠着PureWeber大大的解答解了出来。本解法还综合了其他帖子。所以本卤蛋打算来分享一下这道题,和一种思路。题目上图是了题干。简单概括如下:你有一个DVD租赁店的订单信息数据表。关键字段有:staff_id:员工工号;1是个叫Mike的家伙,2是一个叫Jon的家伙rental_id: 此处可以理解为订单编号amount: 支付金额?反正是要用于加和东西但算出来又不太像????的东西payment_date: 成交日期,只有07年一年的数据我们需要按月汇总这家店逐月的单量和总amount情况,同时我们也需要搞清楚Mike和Jon分别经手了多少订单,分别有多少amount。因此,这就是一个既要分条件计数又要分条件加和的问题。输出结果需要的字段如下:PS. 这个数据库运行在PostgreSQL 9.6下一种思路以下提供一种思路/https://www.pureweber.com/article/mysql-conditional-count//SELECT EXTRACT(month FROM payment_date) AS month, COUNT(rental_id) AS total_count, SUM(amount) AS total_amount, COUNT(CASE WHEN staff_id=1 THEN 1 ELSE NULL END) AS mike_count, SUM(CASE WHEN staff_id<>1 THEN NULL ELSE (amount) END) AS mike_amount, COUNT(CASE WHEN staff_id=2 THEN 1 ELSE NULL END) AS jon_count, SUM(CASE WHEN staff_id<>2 THEN NULL ELSE (amount) END) AS jon_amountFROM paymentGROUP BY monthORDER BY month这里的思路是:整体先按月汇总,然后具体列根据需要使用CASE…WHEN灵活处理;按照PureWeber大大的思路解决按条件计数不是难事,问题在于如何在不影响原值的情况下按条件加和;这里的方法参考了这个问答,使用括号带入应有的变量名;这个问答同时提醒我们为什么PureWeber在COUNT语句中使用了NULL,因为如果令为0确实是会计数的;SELECT后跟多个子查询应该也是可行的,就是麻烦,而且似乎显得略不优雅,因为涉及到需要多次提取月份信息重命名本卤蛋在抓狂的时候还查询了以下帖子:https://q.cnblogs.com/q/74846/ 感谢

March 25, 2019 · 1 min · jiezi

使用split_size优化的ODPS SQL的场景

使用split_size优化的ODPS SQL的场景首先有两个大背景需要说明如下:说明1:split_size,设定一个map的最大数据输入量,单位M,默认256M。用户可以通过控制这个变量,从而达到对map端输入的控制。设置语句:set odps.sql.mapper.split.size=256。一般在调整这个设置时,往往是发现一个map instance处理的数据行数太多。说明2:小文件越多,需要instance资源也越多,MaxCompute对单个Instance可以处理的小文件数限制为120个,如此造成浪费资源,影响整体的执行性能(文件的大小小于块Block 64M的文件)。场景一:单记录数据存储太少原始Logview Detail:可以发现Job只调起一个Map Instance,供处理了156M的数据,但这些数据共有5千多万的记录(单记录平均3个byte),花费了25分钟。此外,从TimeLine看可以发现,整个Job耗费43分钟,map占用了超过60%的时间。故可对map进行优化。优化手段:调小split_size为16M优化之后的logview:优化后,可以发现,Job调起了7个Map Instance,耗时4分钟;某一个Map处理了27M的数据,6百万记录。(这里可以看出set split_size只是向Job提出申请,单不会严格生效,Job还是会根据现有的资源情况等来调度Instance)因为Map的变多,Join和Reduce的instance也有增加。整个Job的执行时间也下降到7分钟。场景二:用MapJoin实现笛卡尔积原始logview:可以发现,Job调起了4个Map,花费了3个小时没有跑完;查看详细Log,某一个Map因为笛卡尔的缘故,生成的数据量暴涨。综合考虑,因为该语句使用Mapjoin生成笛卡尔积,再筛选符合条件的记录,两件事情都由map一次性完成,故对map进行优化。策略调低split_size优化后的logview:优化后,可以看到,Job调度了38个map,单一map的生成数据量下降了,整体map阶段耗时也下降到37分钟。回头追朔这个问题的根源,主要是因为使用mapjoin笛卡尔积的方式来实现udf条件关联的join,导致数据量暴涨。故使用这种方式来优化,看起来并不能从根本解决问题,故我们需要考虑更好的方式来实现类似逻辑。本文作者:祎休阅读原文本文为云栖社区原创内容,未经允许不得转载。

March 20, 2019 · 1 min · jiezi

leetcode SQL题目解析

前言这一篇文章, 是对leetcode上部分SQL题目(14/19)的解析, 所有的题目均使用MySQL的语法。这些题目的SQL的写法可能不是最优的, 但是它们都通过了leetcode上的所有的测试用例, 如果你有更好的SQL请务必联系我, ????: 1025873823。leetcode上的SQL类的题目不是很多, 只有19题。很遗憾, 我没有将它们全部攻克。未来我会尝试将它们全部解答出来。题目1: 组合两张表组合两张表, 题目很简单, 主要考察JOIN语法的使用。唯一需要注意的一点, 是题目中的这句话, “无论 person 是否有地址信息”。说明即使Person表, 没有信息我们也需要将Person表的内容进行返回。所以我选择使用左外查询, 当然你也可以选择RIGHT OUTER JOIN, 这取决于你查询语句的写法。解答SELECT Person.FirstName, Person.LastName, Address.City, Address.StateFROM Person LEFT OUTER JOIN Address ON Person.PersonId = Address.PersonId题目2: 第二高的薪水第二高的薪水, 题目本身并不难, 但是请注意, 题目中的描述"如果不存在第二高的薪水,那么查询应返回 null", 这意味着, 如果SQL没有查询到结果, SQL本身需要一个默认的返回值。如何才能做到, 即使没有结果也返回一个值。通过谷歌, 我查找到了解决方案[Returning a value even if no result](https://stackoverflow.com/que…。使用IFNULL函数, 并且将整个SQL语句作为IFNULL函数的参数。如果IFNULL函数第一个的参数为NULL, 则返回IFNULL函数的第二个参数, 否则返回第一个参数。解答SELECT IFNULL( ( SELECT Employee.Salary FROM Employee GROUP BY Employee.Salary ORDER BY Employee.Salary DESC LIMIT 1 OFFSET 1 ), NULL) AS SecondHighestSalary;题目3: 分数排名本题主要考察了, 如何在SQL查询中生成序号, 因为在表中本身是不含有RANK字段的。我通过谷歌, 在stackoverflow上找到了答案, Generate serial number in mysql query。为查询结果添加序号解答# 3. 通过INNER JOIN为没有去重的分数表添加名次的字段SELECT Scores.Score, RANKINDEX.rank AS RANKFROM Scores INNER JOIN (# 2. 为排序去重后分数表, 添加名次字段(序号) SELECT RANK.Score AS Score, @a:=@a+1 rank FROM (# 1. 首先排序并去重分数表 SELECT DISTINCT Scores.Score FROM Scores ORDER BY Scores.Score DESC ) RANK, (SELECT @a:=0) AS a) AS RANKINDEXON RANKINDEX.Score = Scores.ScoreORDER BY Scores.Score DESC题目4: 超过经理收入的员工非常简单的一道题目, 这里不在多做解释解答SELECT emp1.Name AS EmployeeFROM Employee AS emp1, Employee AS emp2WHERE emp1.ManagerId = emp2.Id AND emp1.Salary > emp2.Salary题目5: 查找重复的电子邮箱同样是非常简单的一道题目, 唯一可能需要了解的就是, GROUP BY Person.Email的字句, 可以对Person.Email字段起到去重的作用解答SELECT Person.Email AS EmailFROM PersonGROUP BY Person.EmailHAVING COUNT(Person.Email) > 1题目6: 从不订购的客户依然是非常简单的一道题目, 主要考察对子查询的使用解答SELECT Customers.Name AS CustomersFROM CustomersWHERE Customers.Id NOT IN ( SELECT Orders.CustomerId FROM Orders)题目7: 部门工资最高的员工部门工资最高的员工, 在对这一题目进行解答之前。我们需要明确知道一点。“除聚合, 计算语句外,SELECT语句中的每个列都必须在GROUP BY子句中给出”。也就是说, 我们并不能在求, 每一个部门工资的Max最大值的时候, 把员工的id也计算出来。对于这道题目,我们解答的步骤分为两步, 1. 求出每一个部门对应的最高工资, 并且将结果存储为派生表 2. 根据员工的部门id, 以及员工的工资, 与派生表联结, 比较对应员工的工资是否等于派生表的部门的最高工资。如果等于, 此人的工资就是部门的最高工资解答SELECT Department.Name AS Department, Employee.Name AS Employee, Employee.Salary AS SalaryFROM Employee INNER JOIN Department INNER JOIN (# 第一步求出每一个部门的最高工资, 并作为派生表使用 SELECT Max(Employee.Salary) AS Salary, Department.Id AS DepartmentId FROM Employee INNER JOIN Department ON Employee.DepartmentId = Department.Id GROUP BY Employee.DepartmentId) AS DepartmentBigSalary# 三张表进行联结ON Employee.DepartmentId = Department.Id AND Department.Id = DepartmentBigSalary.DepartmentId# 比较对应员工的工资是否等于派生表的部门的最高工资WHERE Employee.Salary = DepartmentBigSalary.Salary题目8: 删除重复的电子邮箱DELETE语句在不指定WHERE子句的时候, 默认是删除表中全部的行。题目指定了两个条件, “删除 Person 表中所有重复的电子邮箱,重复的邮箱里只保留 Id 最小 的那个”, WHERE同时也需要指定两个条件。两个条件, 请参考下面的代码。唯一值的注意的一点是, DELETE本身是更新操作, 所以在FROM需要新建一个派生表, 否则会产生错误(You can’t specify target table ‘Person’ for update in FROM clause)解答DELETEFROM PersonWHERE Person.Email IN ( # 条件1: 删除长度大于2的行 SELECT table1.Email FROM ( SELECT Person.Email AS Email FROM Person GROUP BY Person.Email HAVING COUNT(Person.Email) > 1 ) AS table1) AND Person.Id NOT IN ( # 条件1: 删除长度大于2的行, 但是不包含id最小的行 SELECT table2.id FROM ( SELECT MIN(Person.Id) AS id FROM Person GROUP BY Person.Email HAVING COUNT(Person.Email) > 1 ) AS table2)题目9: 上升的温度本题主要考察了对自联结的使用。如何判断两个相邻的RecordDate的Temperature的大小?通过对同一张表进行JOIN联结, JOIN的ON的条件修改为w1.RecordDate = DATE_SUB(w2.RecordDate,INTERVAL -1 DAY), w1表的RecordDate是w2表RecordDate前一天, w1的每一行关联的w2的每一行其实w1的后一天。解答SELECT w1.Id AS IdFROM Weather AS w1 INNER JOIN Weather AS w2ON w1.RecordDate = DATE_SUB(w2.RecordDate,INTERVAL -1 DAY)WHERE w1.Temperature > w2.Temperature题目10: 大的国家非常简单的一道题, 这里不在赘述解答SELECT World.Name AS Name, World.population AS population, World.area AS areaFROM WorldWHERE World.population > 25000000 OR World.area > 3000000题目11: 超过5名学生的课超过5名学生的课, 本道题目注意考察点在于对GROUP BY去重效果的认知上。首先子查询的采用嵌套分组。首先使用课程分组然后根据学生进行分组。可以有效去除课程, 学生重复的行。为什么不直接使用学生分组呢?因为这样做会丢失学生的课程信息。在外层的查询中只需要查找中COUNT大于5的课程即可。解答SELECT ClassLength.class FROM (# 排除了学生和课程重复的行 SELECT courses.class AS class FROM courses GROUP BY courses.class, courses.student) AS ClassLengthGROUP BY ClassLength.classHAVING COUNT(ClassLength.class) >= 5题目12: 有趣的电影本道题目也较为简单, 考察点在于对于奇偶数的判断上, 我们可以使用MySQL的MOD函数。MOD(N, M), MOD函数将返回N/M的余数解答SELECT cinema.id AS id, cinema.movie AS movie, cinema.description AS description, cinema.rating AS ratingFROM cinemaWHERE cinema.description <> ‘boring’ AND MOD(cinema.id, 2) = 1ORDER BY rating DESC题目13: 交换工资题目本身要求使用一个更新查询,并且没有中间临时表。所以SQL中避免不了需要使用逻辑判断, 这里使用MySQl的CASE WHEN语句解答UPDATE salarySET salary.sex = ( CASE WHEN salary.sex = ’m’ THEN ‘f’ WHEN salary.sex = ‘f’ THEN ’m’ ELSE ‘sex’ END)题目14: 连续出现的数字与"上升的温度"的题目类似, 合理的使用自联结, 就可以解答出本题解答SELECT Consecutive.ConsecutiveNumsFROM ( SELECT l1.Num AS ConsecutiveNums FROM Logs AS l1 INNER JOIN Logs AS l2 INNER JOIN Logs AS l3 ON l1.id = l2.id - 1 AND l2.id = l3.id - 1 AND l1.id = l3.id - 2 WHERE l1.Num = l2.Num AND l2.Num = l3.Num AND l1.Num = l3.Num) AS ConsecutiveGROUP BY Consecutive.ConsecutiveNums ...

March 18, 2019 · 3 min · jiezi

MySQL 查询in操作,查询结果按in集合顺序显示

MySQL 查询in操作,查询结果按in集合顺序显示 复制代码 代码如下:select * from test where id in(3,1,5) order by find_in_set(id,‘3,1,5’); select * from test where id in(3,1,5) order by substring_index(‘3,1,2’,id,1);

March 16, 2019 · 1 min · jiezi

Mysql中的常用sql语句汇总

如果你对NodeJs系列感兴趣,欢迎关注微信公众号:前端神盾局或 github NodeJs系列文章本文整理自MySQL Tutorial和SQL必知必会表(Table)创建表MySQL CREATE TABLE Statement By Examples语法CREATE TABLE [IF NOT EXISTS] table_name( column_list) ENGINE=storage_engineIF NOT EXISTS是可选的,但推荐使用,它会先检查是否有有同名表,如果没有则创建。storage_engine MySql 支持多种存储引擎: MyISAM、InnoDB、MERGE、MEMORY (HEAP)、ARCHIVE、CSV、FEDERATED,如果没有指定,默认值是InnoDB在创建表的时候需要给定字段(或列名),格式如下:column_name data_type(length) [NOT NULL] [DEFAULT value] [AUTO_INCREMENT]如果需要使用主键,可以使用以下语法:PRIMARY KEY (col1,col2,…)例子CREATE TABLE IF NOT EXISTS tasks ( task_id INT AUTO_INCREMENT, title VARCHAR(255) NOT NULL, start_date DATE, due_date DATE, status TINYINT NOT NULL, priority TINYINT NOT NULL, description TEXT, PRIMARY KEY (task_id)) ENGINE=INNODB;数据检索(SELECT)语法SELECT [alias1.]column_1, [alias1.]column_2, …FROM table_1 [AS alias1],table_2 [AS alias2][INNER | LEFT |RIGHT] JOIN table_3 ON conditionsWHERE conditionsGROUP BY column_1HAVING group_conditionsORDER BY column_1LIMIT offset, length;WHERE子句MySQL WHEREWHERE子句除了用在SELECT中还可以在UPDATE、DELETE中使用比较运算符OperatorDescription=等于<> or !=不等于<小于>大于<=小于等于= | 大于等于AND 操作符MySQL AND Operator语法WHERE boolean_expression_1 AND boolean_expression_2ANDTRUEFALSENULLTRUETRUEFALSENULLFALSEFALSEFALSEFALSENULLNULLFALSENULLOR 操作符MySQL OR Operatorboolean_expression_1 OR boolean_expression_2ORTRUEFALSENULLTRUETRUETRUETRUEFALSETRUEFALSENULLNULLTRUENULLNULL需要注意的是AND操作符的优先级大于OR操作符BETWEENMySQL BETWEEN表示的是区间条件语法expr [NOT] BETWEEN begin_expr AND end_expr;expr在/不在区间begin_expr和end_expr之间expr、begin_expr和end_expr的数据类型必须相同例子SELECT productCode, productName, buyPriceFROM productsWHERE buyPrice BETWEEN 90 AND 100;LIKEMySQL LIKE语法expr LIKE ‘pattern’LIKE 支持以下通配符百分号%:匹配任何字符出现的任意次数(0次或多次)下划线_:匹配当个任意字符INMySQL IN语法SELECT column1,column2,…FROM table_name1WHERE (expr|column_1) [NOT] IN (‘value1’,‘value2’,…);如果column_1或表达式的结果在集合中有匹配,将返回1否则返回0例子SELECT officeCode, city, phone, countryFROM officesWHERE country IN (‘USA’ , ‘France’);IS NULLA Comprehensive Look at MySQL IS NULL Operator语法value IS [NOT] NULL注意:NULL表示无值,它与0、空字符、false不同GROUP BY[](http://www.mysqltutorial.org/…根据一个或多个列对结果集进行分组,通常而言,GROUP BY 会和SUM、AVG、MAX、MIN等函数使用语法SELECT c1, c2,…, cn, aggregate_function(ci)FROM tableWHERE where_conditionsGROUP BY c1 , c2,…,cn;使用GROUP BY需要注意以下几点:GROUP BY子句可以包含任意数目的列,因而可以对分组进行嵌套,更细致地进行数据分组。GROUP BY必须在FROM和WHREE之后,ORDER BY之前一般情况下,SELECT中出现的非聚合函数字段,GROUP BY语句中也应该存在,比如SELECT name, address, MAX(age) FROM t GROUP BY name;运行此sql会报错:ERROR 1055 (42000): Expression #2 of SELECT list is not in GROUPBY clause and contains nonaggregated column ‘mydb.t.address’ whichis not functionally dependent on columns in GROUP BY clause; thisis incompatible with sql_mode=only_full_group_by为了修复这个错误,我们应该把address字段从SELECT中去掉或者在GROUP BY中添加addressSELECT name, address, MAX(age) FROM t GROUP BY name,address;不过有一些特例,具体可以参考MySQL Handling of GROUP BY如果分组列中包含具有NULL值的行,则NULL将作为一个分组返回。如果列中有多行NULL值,它们将分为一组。如果在GROUP BY子句中嵌套了分组,数据将在最后指定的分组上进行汇总。换句话说,在建立分组时,指定的所有列都一起计算(所以不能从个别的列取回数据)。 GROUP BY子句中列出的每一列都必须是检索列或有效的表达式(但不能是聚集函数)。如果在SELECT中使用表达式,则必须在GROUP BY子句中指定相同的表达式。不能使用别名。HAVINGMySQL HAVING与GROUP BY配合使用,用于过滤分组语法HAVING conditionHAVING支持所有WHERE操作符(AND、OR、IN、BETWEEN、LIKE)ORDER BYMySQL ORDER BY: Sort a Result Set语法SELECT column1, column2,…FROM tblORDER BY column1 [ASC|DESC], column2 [ASC|DESC],…注意:先按column1排序再按column2排序,这里只有当column1中的值相同时才按照column2排序如果未指定ASC或DESC,默认ASCLIMITMySQL LIMIT语法SELECT column1,column2,…FROM tableLIMIT offset , count;LIMIT有两个参数:offset: 指定开始的位置,从0开始count: 代表要返回的行数子查询MySQL Subquery使用子查询的几种形式:在WHERE中使用SELECT lastName, firstNameFROM employeesWHERE officeCode IN (SELECT officeCode FROM offices WHERE country = ‘USA’);SELECT customerNumber, checkNumber, amountFROM paymentsWHERE amount = (SELECT MAX(amount) FROM payments);在FROM中使用SELECT MAX(items), MIN(items), FLOOR(AVG(items))FROM (SELECT orderNumber, COUNT(orderNumber) AS items FROM orderdetails GROUP BY orderNumber) AS lineitems;联结(JOIN)MySQL Join Made Easy For Beginners在实际业务中,我们经常把数据分表存放,那如何把多个表里的数据用一个SELECT语句查询出来呢?这就需要联结。联结分成以下几种类型:Cross join、Inner join、Left join和Right join下面通过一个例子来讲解它们之间的区别。首先我们创建两个表t1和t2CREATE TABLE t1 ( id INT PRIMARY KEY, pattern VARCHAR(50) NOT NULL); CREATE TABLE t2 ( id VARCHAR(50) PRIMARY KEY, pattern VARCHAR(50) NOT NULL);t1和t2表都有pattern列,现在我们插入一些数据INSERT INTO t1(id, pattern)VALUES(1,‘Divot’), (2,‘Brick’), (3,‘Grid’); INSERT INTO t2(id, pattern)VALUES(‘A’,‘Brick’), (‘B’,‘Grid’), (‘C’,‘Diamond’);Cross join我们先来看一下Cross join的用法:SELECT t1.id, t2.idFROM t1CROSS JOIN t2;运行结果如下:Cross join采用笛卡尔积的规则,实际上是将两个表相乘,得到一个组合表(3*3条数据)Inner joinSELECT t1.id, t2.idFROM t1 INNER JOIN t2 ON t1.pattern = t2.pattern;Inner join 实际上是对Cross join的条件过滤,它将不满足t1.pattern = t2.pattern的纪录过滤掉:Left join获取左表所有记录,即使右表没有对应匹配的记录SELECT t1.id, t2.idFROM t1 LEFT JOIN t2 ON t1.pattern = t2.patternORDER BY t1.id;Right join与Left join相反,用于获取右表所有记录,即使左表没有对应匹配的记录。SELECT t1.id, t2.idFROM t1 RIGHT JOIN t2 on t1.pattern = t2.patternORDER BY t2.id;组合查询(UNION)MySQL UNION用于组合多个SELECT查询语法SELECT column_listUNION [DISTINCT | ALL]SELECT column_listUNION [DISTINCT | ALL]SELECT column_list注意:UNION必须由两条或两条以上的SELECT语句组成,语句之间用关键字UNION分隔(因此,如果组合四条SELECT语句,将要使用三个UNION关键字)。UNION中的每个查询必须包含相同的列、表达式或聚集函数(不过,各个列不需要以相同的次序列出)。列数据类型必须兼容:类型不必完全相同,但必须是可以隐含转换的类型(例如,不同的数值类型或不同的日期类型)。ORDER BY子句排序。在用UNION组合查询时,只能使用一条ORDER BY子句,它必须位于最后一条SELECT语句之后。UNION和JOIN的区别数据插入和更新INSERT语法// 插入单行数据INSERT INTO table(c1,c2,…)VALUES (v11,v12,…);// 插入多行数据INSERT INTO table(c1,c2,…)VALUES (v11,v12,…), (v21,v22,…), … (vnn,vn2,…);注意:列和值需要一一对应UPDATEMySQL UPDATE语法UPDATE [LOW_PRIORITY] [IGNORE] table_name SET column_name1 = expr1, column_name2 = expr2, …[WHERE condition];UPDATE支持两种修饰符:LOW_PRIORITY:延迟更新操作直到当前表没有读取操作,不过只有部分存储引擎支持该修饰符,比如:MyISAM, MERGE, MEMORYIGNORE:允许Mysql在发生错误时继续更新操作DELETEMySQL DELETE语法DELETE FROM table_nameWHERE condition;如果WHERE条件没有指定,就会删除该表所有纪录 ...

March 14, 2019 · 2 min · jiezi

GROUP BY你都不会!ROLLUP,CUBE,GROUPPING详解

Group ByGroup By 谁不会啊?这不是最简单的吗?越是简单的东西,我们越会忽略掉他,因为我们不愿意再去深入了解它。1 小时 SQL 极速入门(一)1 小时 SQL 极速入门(二)1 小时 SQL 极速入门(三)——Oracle 分析函数SQL 高级查询——(层次化查询,递归)今天就带大家了解一下Group By 的新用法吧。ROLL UPROLL UP 搭配 GROUP BY 使用,可以为每一个分组返回一个小计行,为所有分组返回一个总计行。直接看例子,我们有以下数据表,包含工厂列,班组列,数量列三列。当向 ROLLUP 传入一列时,会得到一个总计行。SELECT factory, SUM(quantity)FROM productionGROUP BY ROLLUP(factory)ORDER BY factory结果:当向 ROLLUP 传递两列时,将会按照这两列进行分组,同时按照第一列的分组结果返回小计行。我们同时传入工厂和部门看一下。SELECT factory,department, SUM(quantity)FROM productionGROUP BY ROLLUP(factory, department)ORDER BY factory结果:可以看到对每一个工厂都有一个小计行,最后对所有的有一个总计行。也可以这样理解 如果 ROLLUP(A,B)则先对 A,B进行 GROUP BY,之后对 A 进行 GROUP BY,最后对全表 GROUP BY。如果 ROLLUP(A,B,C)则先对 A,B,C进行 GROUP BY ,然后对 A,B进行GROUP BY,再对 A 进行GROUP BY,最后对全表进行 GROUP BY.CUBECUBE 和 ROLLUP 对参数的处理是不同的,我们可以这样理解。如果 CUBE(A,B)则先对 A,B 进行 GROUP BY,之后对 A 进行 GROUP BY,然后对 B 进行 GROUP BY,最后对全表进行 GROUP BY.如果 CUBE(A,B,C)则先对 A,B,C 进行 GROUP BY,之后对 A,B ,之后对A,C ,之后对 B,C 之后对 A,之后对 B,之后对 C,最后对全表GROUP BY看一个简单的例子:SELECT factory,department, SUM(quantity)FROM productionGROUP BY CUBE(factory, department)ORDER BY factory,department;结果:可以看出来首先对 FACTORY,DEPARTMENT进行分组汇总,然后对FACTORY 分组汇总,之后对 DEPARTMENT 分组汇总,最后有一行全表汇总。GROUPINGGROUPING()函数只能配合 ROLLUP 和 CUBE 使用,GROUPING()接收一列,如果此列不为空则返回0,如果为空则返回1.我们用第一个ROLLUP例子举例SELECT GROUPING(factory), factory, department, SUM(quantity)FROM productionGROUP BY ROLLUP(factory, department)ORDER BY factory, department;结果:看到,最后一行的 FACTORY 为空,所以 GROUPING()返回 1.也可以与CUBE结合使用,方法是一样的。GROUPING SETSGROUPING SETS 与 CUBE 有点类似,CUBE是对参数进行自由组合进行分组。GROUPING SETS则对每个参数分别进行分组,GROUPING SETS(A,B)就代表先按照 A 分组,再按照 B分组。SELECT factory, department, SUM(quantity)FROM productionGROUP BY GROUPING SETS(factory, department)ORDER BY factory, department结果:可以看出来结果是按照工厂和部门分别分组汇总的。GROUPING_ID()GROUPING_ID()配合GROUPING()函数使用,GROUPING_ID(A,B)的值由GROUPING(A)与GROUPING(B)的值决定,如果GROUPING(A)为1,GROUPING(B)为0,则GROUPING_ID(A,B)的值为 10,十进制的 3.SELECT factory, department, GROUPING(factory), GROUPING(department), GROUPING_ID(factory,department), SUM(quantity)FROM productionGROUP BY CUBE(factory, department)ORDER BY factory, department;结果:有了GROUPING_ID列,我们就可以使用 HAVING 字句来对查询结果进行过滤。选择GROUPING_ID=0的就表示 FACTORY,DEPARTMENT两列都不为空。 ...

March 8, 2019 · 1 min · jiezi

WinCC中访问SQL SERVER数据库

有两种身份验证方式可以连接到SQL Server数据库,它们是Windows身份验证和SQL身份验证。在本文中,WinCC将使用Windows身份验证进行连接,但我展示了使用SQL身份验证连接到SQL Server的连接字符串。注意:在本文中,我假设您已经安装了SQL Server。 我使用过SQL Server 2008,但这里完成的所有步骤对SQL Server 2005都有效。在本文中,我创建了一个数据库,因此我们可以进行读取和插入。 对于许多实际情况,工厂或系统已经有一个数据库。 因此,唯一需要的就是在WinCC中操作。创建SQL Server数据库在“Database”上单击鼠标右键,然后单击“New Database…”(新建数据库)在“Database name:”上键入“SQL_WINCC”(或您想要的任何名称)在“Database> SQL_WINCC”下,右键单击“New Table …”(新表),然后创建这些列,如下图所示。最后将其命名为“Table_1”。SQL_WINCC会是这样…注意:我没有在“Table_1”中创建索引或主键,因为SQL Server配置不是本文的重点! 但在实际系统中,强烈建议创建它们。创建WinCC应用程序打开WinCC项目管理器,然后单击“New”(新建)选择“Single-User Project”,然后选择“OK”(确定)。将其命名为“WinccSql”,然后“Create”(创建)。WinCC Explorer看起来会是这样创建内部标签在“WinccSql”下的WinCC项目管理器中,双击“Tag Management”(变量管理)单击“Tag Management > Internal Tags”(标记管理>内部标记)并创建这12个标记。这些标签将在主屏幕中使用。 其中一些保存来自SQL Server的数据,而其他的保存插入到SQL Server的数据。配置屏幕在“WinccSql”下的WinCC项目管理器中,双击“Graphics Designer”(图形编辑器)。 将打开一个名为“NewPdl_1”的空白页面,就像这样。使用“矩形”,“静态文本”和“I / O域”对象可以像这样放置它们。在“属性> I / O域>输出/输入>输出值”中,必须将它们附加到标签,如下所示:附加到变量“TAG_STRING_INSERT”,“TAG_STRING_11”和“TAG_STRING_21”的对象将“属性> I / O域>输出/输入>数据格式”更改为STRING。创建VB脚本介绍与数据库通信的最简单方法是使用VB-Script,它基本上需要4个步骤。连接配置命令配置记录集配置管理从数据库返回的数据连接配置基本上具有以下参数:数据提供商 Provider数据服务器(服务器名称)初始目录(要连接的数据库)认证在本文中,我使用此连接字符串:Provider=SQLOLEDB;Data Source=MAWINWINCC;InitialCatalog=SQL_WINCC;Trusted_connection=yes;“Trusted_connection”表示连接将通过Windows身份验证完成。 对于SQL身份验证,连接字符串将如下所示:Provider=SQLOLEDB;Password=1234567890;Persist Security Info=True;User ID=sql_user;Initial Catalog=SQL_WINCC;Data Source=MAWIN\WINCC“Insert”按钮的VB脚本单击“插入”按钮并浏览到“事件>静态文本>鼠标>鼠标单击> VBS操作”。 现在在那里复制这个脚本。‘connection对象Dim cn’recordset记录集对象Dim rs’command命令对象Dim mcDim sqlDim column1Dim column2Dim column3Dim column4Set cn = CreateObject(“ADODB.Connection”)Set rs = CreateObject(“ADODB.Recordset”)Set mc = CreateObject(“ADODB.Command”)‘相当于是adUseClient枚举值,表示用在客户端中cn.CursorLocation = 3’连接字符串cn.ConnectionString = “Provider=SQLOLEDB;Data Source=MA1815\WINCC;Initial Catalog=SQL_WINCC;Trusted_connection=yes;” ‘打开连接cn.Open’Read the tags that will be inserted into databasecolumn1 = HMIRuntime.Tags.Item(“TAG_STRING_INSERT”).Read column2 = HMIRuntime.Tags.Item(“TAG_INT_1_INSERT”).Read column3 = HMIRuntime.Tags.Item(“TAG_INT_2_INSERT”).Read column4 = HMIRuntime.Tags.Item(“TAG_INT_3_INSERT”).Read’SQL语句sql = “INSERT INTO TABLE_1 (COLUMN_1, COLUMN_2, COLUMN_3, COLUMN_4) VALUES (’” & column1 & “’, " & column2 &”, " & column3 & “, " & column4 & “)"‘配置命令对象Set mc.ActiveConnection = cn’表示命令中的文字是一个SQL语句mc.CommandType = 1mc.CommandText = sql’执行命令rs.Open mc, , 2, 1’关闭连接cn.Close点击OK按钮Select按钮的脚本单击“选择”按钮并浏览到“事件>静态文本>鼠标>鼠标单击> VBS操作”。 现在在那里复制这个脚本Dim cn ‘connection 连接对象Dim rs ‘record set 记录集对象Dim mc ‘command 命令对象Dim iDim sqlDim column1Dim column2Dim column3Dim column4 Set cn = CreateObject(“ADODB.Connection”)Set rs = CreateObject(“ADODB.Recordset”)Set mc = CreateObject(“ADODB.Command”) ‘相当于是adUseClient枚举值,表示用在客户端中cn.CursorLocation = 3 ‘连接字符串cn.ConnectionString = “Provider=SQLOLEDB;Data Source=MA1815\WINCC;Initial Catalog=SQL_WINCC;Trusted_connection=yes;” ‘打开连接cn.Open’在本文中,我为了方便起见,限制在2个寄存器内,但您可以从SQL中读取一些数据,然后将它们写入网格中,比如sql = “SELECT TOP 2 COLUMN_1, COLUMN_2, COLUMN_3, COLUMN_4 FROM TABLE_1”‘配置Command对象Set mc.ActiveConnection = cn ‘表示CommandText中的是一个SQL语句mc.CommandType = 1mc.CommandText = sql ‘执行SQL命令rs.Open mc, , 2, 1 ‘假如找到了至少一条记录If rs.RecordCount > 0 Then rs.MoveFirst ‘循环以获取从数据库读取的所有数据 For i = 1 To rs.RecordCount ‘从数据库中读取数据 column1 = rs(0) column2 = rs(1) column3 = rs(2) column4 = rs(3) ‘TAG_STRING_11 或 TAG_STRING_21 或 … TAG_STRING_n1 HMIRuntime.Tags.Item(“TAG_STRING_” & i & “1” ).Write column1 ‘TAG_INT_12 或 TAG_INT_22 或 … TAG_INT_n2 HMIRuntime.Tags.Item(“TAG_INT_” & i & “2”).Write column2 ‘TAG_INT_13 或 TAG_INT_23 或 … TAG_INT_n3 HMIRuntime.Tags.Item(“TAG_INT_” & i & “3”).Write column3 ‘TAG_INT_14 或 TAG_INT_24 或 … TAG_INT_n4 HMIRuntime.Tags.Item(“TAG_INT_” & i & “4”).Write column4 ‘继续下一条 rs.MoveNext Next End If’关闭记录集rs.Close ‘关闭连接cn.Close现在,单击“OK”。完成了! 现在运行WinCC!:) ...

March 7, 2019 · 2 min · jiezi

实习面试笔记

项目经历数据库数据库事务(Transaction)的 ACID 特性原子性(Atomicity), 一致性(Consistency), 隔离型(Isolation), 持久性(Durability)原子性(A)是指事务中的操作不可拆分,只允许全部执行或者全部不执行一致性(C)指事务的执行不能破坏数据库的一致性,一致性也称为完整性。一个事务在执行后,数据库必须从一个一致性状态转变为另一个一致性状态隔离性(I)指并发的事务相互隔离,不能互相干扰持久性(D)指事务一旦提交,对数据的状态变更应该被永久保存数据库事务隔离级别有4个# 由低到高依次为Read uncommitted, 读到了未提交的事物, 只是 add 还没有 commitRead committed, 读到了上一次的commit, 也就是说还没有更新 最新的commitRepeatable read, 保证读取最新的 commit, 为此, 读取的时候不允许提交Serializable, 要求有很高的实时同步性# 这四个级别可以逐个解决脏读 、不可重复读 、幻读 这几类问题锁(并发控制的手段)关系数据模型的三个组成部分数据结构, 对数据的操作, 完整性约束参考链接数据库事务、隔离级别、锁的理解与整理SQLCRUDCreate, Read, Update, and Delete过程1.首先连接到数据库 conn = sqlite.connect(’test.db’)2.建立游标 cursor cursor = conn.cursor()3.建立表 create table user (id int, name varchar(20), score int )4.insert into 插入数据,注意占位符是 ? insert into user (id, name) values(1, “Alice”, 88) insert into user (id, name) values(2, “Bob”, 89) insert into user (id, name) values(3, “Cindy”, 88)5.select * from user where 查找数据 select * from user where id between 1 and 3 order by score asc6.cursor.close()7.conn.commit()8.conn.close()column, 列, 字段select * from userselect col1, col2 from user下面以这个表格 customers 为例, 展示 SQL 的语法idnameagecitypostalcodecountry1Alfreds25Berlin12209Germany2Ana15Mexico05021Mexico3Antonio20Mexico05023Mexico4Thomas30LondonWA11DPUK5Berglunds35LuleaS-958-22Swedenwhere 条件选择select * from customers where country=‘Germany’ and city=‘Berlin’order by 排序select * from customers order by country插入insert into customers (name, age, city, postalcode, country) values (‘Bart’, 10, ‘LA’, ‘4006’, ‘USA’)更新update customers set name = ‘Smith’, city = ‘Paris’ where id = 5删除delete from customers where name = ‘Berglunds’limit/top 不返回全部结果, 只返回有限数量的记录# SQLselect top 3 from customers# MySQLselect * from customers limit 3最大最小值select min(age) from customers统计 count, distinct# 统计有多少个不同的国家select count(distinct country) from customers平均值select avg(age) from customers求和select sum(age) from customers通配符# SQLselect * from customers where name like ‘B%’# sqliteselect * from customers where name like ‘B*‘选择范围# 离散select * from customers where country in (‘Germany’, ‘France’)# 连续select * from customers where age between 10 and 20连接表格inner join, (A ∩ B)left join, (A ∩ B) U Aright join, (A ∩ B) U Bfull outer join, (A U B)参考资料Java基本数据类型抽象类跟接口的区别只说我知道接口只定义函数的声明,而抽象类的函数可以有具体实现。然后他问我一个类能实现多少接口,一个类能继承多少个抽象类,我这才想起来原来这也是一个区别。。。开发模型MVC多线程错误和异常垃圾回收机制前端选择器getElementByName正则表达式ajax 参数数据结构排序复杂度稳定性实现代码Fib其他牛客网面试经历 (视频面试) ...

February 28, 2019 · 2 min · jiezi

Mysql数据库的条件查询语句

对于分析人员来讲,Mysql数据库应用最多的是select查询语句,此篇文章主要介绍Mysql数据库的查询语句。一、单表查询1.带条件的查询基本语法:select * from +表名称 +where 条件;1)范围查询:eg:where 字段 between 10 and 100;2)模糊查询eg:where 字段 like’%不确定%‘备注:%代表多个字符,_下划线代表一个字符,^a代表以a为开头的数据,a$代表以a为结尾的数据,[abc]匹配所包含的任意一个字符。2.多条件查询备注:当and和or同时使用时,and优先级高于or。如果想要优先带or的条件,对带or部分条件带括号即可。3.排序基本语法:order by 字段。备注:默认是升序,ASC升序,DESC降序限制记录数:limit 1004.聚合函数count():统计记录数avg():平均数max():最大值min():最小值sum():求和5.分组基本语法:select 字段1,字段2,聚合函数 from +表名称 +group by 字段1,字段2备注:group by和having 一起使用,主要是对分组结果进行过滤二、多表关联查询1.内连接–>inner join内连接即等值连接,获取两个表中字段匹配关系的记录,可省略写成join,可理解成集合概念中的“交集”,关联字段同时存在与两表的记录。2.左连接–>left join左连接,获取左边主表的全部记录,即便右表没有对应的数据。3.右连接–>right join右连接,获取右边主表的全部记录,即便左表没有对应的数据。4.连接符–>unionunion用于连接两个以上的select语句的结果,将结果组合到一个结果集中,并删除重复数据基本语法:select 字段 from 表1 union [all|distinct] select 字段 from 表2备注:union 即为 union distinct;若为union all,即返回带重复数据的结果集;在使用union时,所选出的内容显示会以前面的表的字段名称命名。

February 26, 2019 · 1 min · jiezi

JDBC

JDBC概述1.1.1什么是JDBCJDBC(JavaDataBase Connectivity,java数据库连接)是一种用于执行SQL语句的JavaAPI。JDBC是Java访问数据库的标准规范,可以为不同的关系型数据库提供统一访问,它由一组用Java语言编写的接口(大部分)和类组成。1.1.2什么是数据库驱动JDBC需要连接驱动,驱动是两个设备要进行通信,满足一定通信数据格式,数据格式由设备提供商规定,设备提供商为设备提供驱动软件,通过软件可以与该设备进行通信。今天我们使用的是mysql的驱动mysql-connector-java-5.1.37-bin.jarJDBC与数据库驱动的关系:接口与实现的关系。JDBC规范(掌握四个核心对象):DriverManager:用于注册驱动Connection: 表示与数据库创建的连接Statement: 操作数据库sql语句的对象ResultSet: 结果集或一张虚拟表1.2JDBC原理Java提供访问数据库规范称为JDBC,而生产厂商提供规范的实现类称为驱动JDBC是接口,驱动是接口的实现,没有驱动将无法完成数据库连接,从而不能操作数据库!每个数据库厂商都需要提供自己的驱动,用来连接自己公司的数据库,也就是说驱动一般都由数据库生成厂商提供。1.3JDBC入门案例1.3.1准备数据之前我们学习了sql语句的使用,并创建的分类表category,今天我们将使用JDBC对分类表进行增删改查操作。创建数据库create database day04;使用数据库use day04;创建分类表create table category(cid int PRIMARY KEY AUTO_INCREMENT,cname varchar(100));初始化数据insert into category (cname) values(‘家电’);insert into category (cname) values(‘服饰’);insert into category (cname) values(‘化妆品’);1.3.2导入驱动jar包创建lib目录,用于存放当前项目需要的所有jar包选择jar包,右键执行build path / Add to Build Path1.3.3开发步骤注册驱动.获得连接.获得执行sql语句的对象执行sql语句,并返回结果处理结果释放资源.1.3.4案例实现@Test//// 查询所有的分类信息public void demo1() throws Exception{// 注意:使用JDBC规范,采用都是 java.sql包下的内容//1 注册驱动Class.forName(“com.mysql.jdbc.Driver”);//2 获得连接String url = “jdbc:mysql://localhost:3306/mydb”;Connection conn = DriverManager.getConnection(url, “root”, “root”);//3获得执行sql语句的对象Statement stmt = conn.createStatement();//4执行SQL语句ResultSet rs = stmt.executeQuery(“select from category”);//5处理结果集while(rs.next()){ // 获得一行数据 Integer cid = rs.getInt(“cid”); String cname = rs.getString(“cname”); System.out.println(cid + " , " + cname);}//6释放资源rs.close();stmt.close();conn.close();}1.4API详解1.4.1API详解:注册驱动DriverManager.registerDriver(new com.mysql.jdbc.Driver());不建议使用原因有2个:导致驱动被注册2次。强烈依赖数据库的驱动jar解决办法:Class.forName(“com.mysql.jdbc.Driver”);1.4.2API详解:获得链接static Connection getConnection(String url, String user, String password)试图建立到给定数据库 URL 的连接。参数说明:url 需要连接数据库的位置(网址) user用户名password 密码例如:getConnection(“jdbc:mysql://localhost:3306/day06”, “root”, “root”);URL:SUN公司与数据库厂商之间的一种协议。jdbc:mysql://localhost:3306/day06协议 子协议IP : 端口号数据库mysql: jdbc:mysql://localhost:3306/day04或者jdbc:mysql:///day14(默认本机连接)oracle数据库: jdbc:oracle:thin:@localhost:1521:sid1.4.3java.sql.Connection接口:一个连接接口的实现在数据库驱动中。所有与数据库交互都是基于连接对象的。StatementcreateStatement(); //创建操作sql语句的对象1.4.4API详解:java.sql.Statement接口: 操作sql语句,并返回相应结果String sql = “某SQL语句”;获取Statement语句执行平台:Statement stmt = con.createStatement();常用方法:int executeUpdate(String sql); –执行insert update delete语句.ResultSet executeQuery(String sql); –执行select语句.boolean execute(String sql); –仅当执行select并且有结果时才返回true,执行其他的语句返回false.1.4.5API详解:处理结果集(注:执行insert、update、delete无需处理)ResultSet实际上就是一张二维的表格,我们可以调用其booleannext()方法指向某行记录,当第一次调用next()方法时,便指向第一行记录的位置,这时就可以使用ResultSet提供的getXXX(intcol)方法(与索引从0开始不同个,列从1开始)来获取指定列的数据:rs.next();//指向第一行rs.getInt(1);//获取第一行第一列的数据常用方法:Object getObject(int index) / Object getObject(String name) 获得任意对象String getString(int index)/ String getString(String name) 获得字符串int getInt(int index)/int getInt(String name) 获得整形double getDouble(int index)/ double getDouble(String name) 获得双精度浮点型1.4.6API详解:释放资源与IO流一样,使用后的东西都需要关闭!关闭的顺序是先得到的后关闭,后得到的先关闭。rs.close();stmt.close();con.close();1.5JDBC工具类“获得数据库连接”操作,将在以后的增删改查所有功能中都存在,可以封装工具类JDBCUtils。提供获取连接对象的方法,从而达到代码的重复利用。该工具类提供方法:public static Connection getConn ()。代码如下:public class JdbcUtils {private static String driver = “com.mysql.jdbc.Driver”;private static String url = “jdbc:mysql://localhost:3306/webdb_4”;private static String user = “root”;private static String password = “root”;static{ try { //注册驱动 Class.forName(driver); } catch (Exception e) { throw new RuntimeException(e); }}/** 获得连接 @return @throws SQLException /public static Connection getConnection() throws SQLException{ //获得连接 Connection conn = DriverManager.getConnection(url, user, password); return conn;}/** 释放资源 @param conn @param st @param rs /public static void closeResource(Connection conn , Statement st , ResultSet rs){ if(rs != null){ try { rs.close(); } catch (SQLException e) { } } if(st != null){ try { st.close(); } catch (SQLException e) { } } if(conn != null){ try { conn.close(); } catch (SQLException e) { } }}1.6JDBC增删改查操作1.6.1插入@Testpublic void demo01(){//添加Connection conn = null;Statement st = null;ResultSet rs = null;try { //1 获得连接 conn = JdbcUtils.getConnection(); //操作 //1) 获得语句执行者 st = conn.createStatement(); //2) 执行sql语句 int r = st.executeUpdate(“insert into category(cname) values(‘测试’)”); //3) 处理结果 System.out.println(r);} catch (Exception e) { throw new RuntimeException(e);} finally{ //释放资源 JdbcUtils.closeResource(conn, st, rs);}}1.6.2修改@Testpublic void demo02(){//修改Connection conn = null;Statement st = null;ResultSet rs = null;try { conn = JdbcUtils.getConnection(); st = conn.createStatement(); int r = st.executeUpdate(“update category set cname=‘测试2’ where cid = 4”); System.out.println(r);} catch (Exception e) { throw new RuntimeException(e);} finally{ JdbcUtils.closeResource(conn, st, rs);}}1.6.3删除@Testpublic void demo03(){//删除Connection conn = null;Statement st = null;ResultSet rs = null;try { conn = JdbcUtils.getConnection(); //操作 st = conn.createStatement(); int r = st.executeUpdate(“delete from category where cid = 4”); System.out.println(r);} catch (Exception e) { throw new RuntimeException(e);} finally{ JdbcUtils.closeResource(conn, st, rs);}}1.6.4通过id查询详情@Testpublic void demo04(){//通过id查询详情Connection conn = null;Statement st = null;ResultSet rs = null;try { conn = JdbcUtils.getConnection(); //操作 st = conn.createStatement(); rs = st.executeQuery(“select from category where cid = 30”); if(rs.next()){ String cid = rs.getString(“cid”); String cname = rs.getString(“cname”); System.out.println(cid + " @ " + cname ); } else { System.out.println(“没有数据”); }} catch (Exception e) { throw new RuntimeException(e);} finally{ JdbcUtils.closeResource(conn, st, rs);}}1.6.5查询所有@Testpublic void demo05(){//查询所有Connection conn = null;Statement st = null;ResultSet rs = null;try { conn = JdbcUtils.getConnection(); //操作 st = conn.createStatement(); rs = st.executeQuery(“select from category”); while(rs.next()){ String cid = rs.getString(“cid”); String cname = rs.getString(“cname”); System.out.println(cid + " @ " + cname ); }} catch (Exception e) { throw new RuntimeException(e);} finally{ JdbcUtils.closeResource(conn, st, rs);}}1.7预处理对象1.7.1SQL注入问题SQL注入:用户输入的内容作为了SQL语句语法的一部分,改变了原有SQL真正的意义。假设有登录案例SQL语句如下:SELECT FROM 用户表 WHERE NAME = 用户输入的用户名 AND PASSWORD = 用户输的密码;此时,当用户输入正确的账号与密码后,查询到了信息则让用户登录。但是当用户输入的账号为XXX 密码为:XXX’OR ‘a’=’a时,则真正执行的代码变为:SELECT FROM 用户表 WHERE NAME = ‘XXX’ AND PASSWORD =’ XXX’OR ’a’=’a’;此时,上述查询语句时永远可以查询出结果的。那么用户就直接登录成功了,显然我们不希望看到这样的结果,这便是SQL注入问题。为此,我们使用PreparedStatement来解决对应的问题。1.7.2API详解:预处理对象preparedStatement:预编译对象,是Statement对象的子类。特点:性能高会把sql语句先编译能过滤掉用户输入的关键字。PreparedStatement预处理对象,处理的每条sql语句中所有的实际参数,都必须使用占位符?替换。String sql = “select from user where username = ? and password = ?";PreparedStatement使用,需要通过以下3步骤完成:PreparedStatement预处理对象代码:获得预处理对象,需要提供已经使用占位符处理后的SQL语句PreparedStatement psmt = conn.prepareStatement(sql)设置实际参数void setXxx(int index, Xxx xx) 将指定参数设置指定类型的值参数1:index 实际参数序列号,从1开始。参数2:xxx 实际参数值,xxx表示具体的类型。例如:setString(2, “1234”) 把SQL语句中第2个位置的占位符?替换成实际参数 “1234"执行SQL语句:int executeUpdate(); –执行insert update delete语句.ResultSet executeQuery(); –执行select语句.boolean execute(); –执行select返回true 执行其他的语句返回false.1.7.3插入@Testpublic void demo01(){//添加:向分类表中添加数据Connection conn = null;PreparedStatement psmt = null;ResultSet rs = null;try { //1 获得连接 conn = JdbcUtils.getConnection(); //2 处理sql语句 String sql = “insert into category(cname) values(? )”; //3获得预处理对象 psmt = conn.prepareStatement(sql); //4设置实际参数 psmt.setString(1,“预处理”); //5执行 int r = psmt.executeUpdate(); System.out.println(r);} catch (Exception e) { throw new RuntimeException(e);} finally{ //6释放资源 JdbcUtils.closeResource(conn, psmt, rs);}}1.7.4更新@Testpublic void demo02(){//修改Connection conn = null;PreparedStatement psmt = null;ResultSet rs = null;try { conn = JdbcUtils.getConnection(); //1 sql语句 String sql = “update category set cname = ? where cid = ?”; //2 获得预处理对象 psmt = conn.prepareStatement(sql); //3设置实际参数 psmt.setString(1, “测试数据”); psmt.setInt(2, 4); //4执行 int r = psmt.executeUpdate(); System.out.println(r);} catch (Exception e) { throw new RuntimeException(e);} finally{ JdbcUtils.closeResource(conn, psmt, rs);}}1.7.5删除@Testpublic void demo03(){//删除Connection conn = null;PreparedStatement psmt = null;ResultSet rs = null;try { conn = JdbcUtils.getConnection(); //1 sql语句 String sql = “delete from category where cid = ?”; //2 获得预处理对象 psmt = conn.prepareStatement(sql); //3设置实际参数 psmt.setInt(1, 4); //4执行 int r = psmt.executeUpdate(); System.out.println(r);} catch (Exception e) { throw new RuntimeException(e);} finally{ JdbcUtils.closeResource(conn, psmt, rs);}}1.7.6查询所有@Testpublic void demo04(){//查询所有Connection conn = null;PreparedStatement psmt = null;ResultSet rs = null;try { conn = JdbcUtils.getConnection(); String sql = “select from category”; psmt = conn.prepareStatement(sql); rs = psmt.executeQuery(); while(rs.next()){ String cname = rs.getString(“cname”); System.out.println(cname); }} catch (Exception e) { throw new RuntimeException(e);} finally{ JdbcUtils.closeResource(conn, psmt, rs);}}1.7.7通过id查询详情@Testpublic void demo05(){//通过id查询Connection conn = null;PreparedStatement psmt = null;ResultSet rs = null;try { conn = JdbcUtils.getConnection(); String sql = “select * from category where cid = ?”; psmt = conn.prepareStatement(sql); psmt.setInt(1, 2); rs = psmt.executeQuery(); if(rs.next()){ System.out.println(“查询到”); } else { System.out.println(“查询不到”); }} catch (Exception e) { throw new RuntimeException(e);} finally{ JdbcUtils.closeResource(conn, psmt, rs);}} ...

February 24, 2019 · 4 min · jiezi

ElasticSearch重要概念及简单用法

接着上一篇ElasticSearch搭建的环境继续学习,从概念开始,本篇文章将介绍ElasticSearch中的一些重要概念及部分原理性概念,以下内容中简称为ES。一、ES部分名词解释1.NRT:英文全称为Near RrealTime。中文意思为近实时,从写入数据到可以被搜索到之间有一个小于1s的延迟,使用ES进行搜索和数据分析可以达到秒级的速度。注:由于ES写入数据到可以被索引到之间有延迟,所以对于业务中包含有保存幂等性需求时,需要注意; 在写完之后手动执行刷新操作,然后再查询,否则会可能出现数据写重的情况。2.cluster:表示由多个节点组成的ES集群(常见集群种类:HA,HB,HP,具体可自行查阅资料)。集群有一个名称,默认是elasticsearch,可以在配置文件中通过cluster.name字段手动指定,集群最小节点数可以为1个。3.node:集群中的节点。节点也有自己的名称,默认是随机分配的,默认情况下,节点启动之后,会自动去寻找名称为cluster.name字段所指定的集群。如果在默认不修改cluster.name的情况下,启动多个节点之后,它们会自动组成一个ES集群。4.document:文档。它是ES中的最小数据单元,通常使用JSON数据结构表示,每个index(索引)的type(类型)中,都可以存储多个Document。5.field:表示字段,具体指的是Document中的某一个数据字段。比如学生信息文档中的学号字段。6.index:索引。是多个有相似结构的文档数据的集合,类似于MySQL数据库中的数据库概念。7.type:类型。表示某个索引下面的某种相同数据结构的结合。在较低版本的ES中,一个索引中可以有多个type,高版本中一个索引下只能有一个类型,官方建议每个索引下最好只有一个type。如果一个index下有多个type,在不同的搜索场景下可能会相互有影响,比如:一个索引下面有一个用于统计分析的type和一个用于搜索的type,如果统计请求比较慢,有可能会阻塞到查询请求。8.shard:每个index会被拆分为多个shard,每个shard就会存放这个index的一部分数据,这此shard会散落在多台服务器上。有了shard就可以进行横向扩展,存储更多数据,让搜索和分析等操作分布到多台服务器上去执行,提升吞吐量和性能。shard又分为replica shard和primary shard,每个shard都是一个lucene index.9.replica:每个服务器随时可能故障或宕机,此时shard就可以会丢失,因此可以为每一个shard创建多个replica副本。replica可以在shard故障时提供备用服务。保证数据不丢失或者丢失很少,多个replica还可以提升搜索操作的吞吐量和性能。注意:primary shard:建立索引时一次设置,不能修改,默认5个;replica shard:可随时修改,默认1个),默认每个索引10个shard,5个primary shard, 5个replica shard,最小的高可用配置,是2台服务器。二、ES的文档数据格式的优点1.可以提供复杂的面向对象的数据结构;2.如果不适用ES,使用传统的关系型数据库,复杂的对象只能拍平,放到多个关联表中,查询的时候需要查询多个表,而且还得重新组合成复杂对象,特别麻烦;3.基于ES面向文档的特性,而且提供了倒排索引,所以可以胜任复杂的查询和检索需求;4.使用传统,流行的文档对象来存储,很容易处理;三、ES集群简单管理1.集群健康状态使用_cat相关api查看,如下:[root@es-master ~]# curl http://localhost:9200/_cat/health?pretty#返回结果1549092107 02:21:47 elasticsearch green 3 3 134 67 0 0 0 0 - 100.0%可以看出,如果集群中的所有节点都正常启动,整个集群的状态为green;2.集群的三种状态(1)红(red):不是所有的primary shard都是active状态的,部分索引有数据丢失了;(2)黄(yellow):每个索引的primary shard都是active状态的,但是部分replica shard不是active状态,处于不可用的状态,此时可以继续使用;(3)绿(green):每个索引的primary shard和replica shard都是active状态的;集群启动时状态变化过程:集群启动的时候,首先会有某些节点先启动,这些节点会被作为主节点,在所有的主节点未完全启动之前,集群此时处于red状态;当主节点全部启动之后,集群状态会变为yellow状态;所有的replica节点都启动完成之后,集群中的所有节点都已经齐全,此时集群变为green状态;3._cat中的重要api查看集群中的节点数[root@es-master ~]# curl http://localhost:9200/_cat/nodes?pretty192.168.199.12 29 95 12 0.00 0.08 0.06 mdi * es-node2192.168.199.11 41 88 25 0.64 0.75 0.57 mdi - es-node1192.168.199.11 20 88 25 0.64 0.75 0.57 mdi - es-node3表示集群中有三个节点,es-node1,es-node2,es-node3;查看集群中的分片数[root@es-master ~]# curl http://localhost:9200/_cat/shards?prettystudent_index 3 p STARTED 1 4.3kb 192.168.199.11 es-node1student_index 3 r STARTED 1 4.3kb 192.168.199.11 es-node3student_index 2 p STARTED 2 8.5kb 192.168.199.12 es-node2student_index 2 r STARTED 2 8.5kb 192.168.199.11 es-node3student_index 1 r STARTED 1 4.4kb 192.168.199.11 es-node1student_index 1 p STARTED 1 4.4kb 192.168.199.12 es-node2student_index 4 p STARTED 1 4.4kb 192.168.199.11 es-node1student_index 4 r STARTED 1 4.4kb 192.168.199.11 es-node3student_index 0 p STARTED 0 261b 192.168.199.12 es-node2student_index 0 r STARTED 0 261b 192.168.199.11 es-node3表示student_index索引默认有10个分片,5个primary和5个replica;查看集群中的索引列表[root@es-master ~]# curl http://localhost:9200/_cat/indices?vhealth status index uuid pri rep docs.count docs.deleted store.size pri.store.sizegreen open .monitoring-es-6-2019.01.27 kXMATwLHShGoGrAhOVPZqg 1 1 39439 432 48.3mb 24.1mbgreen open .monitoring-kibana-6-2019.01.27 PpgWm9PSRXKU385_pQis2g 1 1 1512 0 1mb 531.7kbgreen open .elastichq 19vJX4__TcunPjMCfybmdA 5 1 1 0 14kb 7kbgreen open student_index 8VHSS7wyQIadWBcNWJsGPQ 5 1 5 0 43.9kb 21.9kbgreen open student VXHrKYNcSRKELb7WSzToCw 5 1 2 0 23.8kb 11.9kb默认展示的是系统索引和自己创建的索引,参数v表示查看详细信息;_cat还有其他api,但是不是很常用,如果需要,可以通过如下命令查看即可[root@es-master ~]# curl http://localhost:9200/_cat=^.^=/_cat/allocation/_cat/shards/_cat/nodes/_cat/tasks…四、ES简单操作此处以电商系统商品搜索为例,使用kibana图形化操作界面,介绍ES的简单操作。1.创建用法:PUT /index_name/type_name/id{}例如:创建一个索引名称为shop_index,类型为productInfo,id为1的索引PUT /shop_index/productInfo/1{ “name”: “HuaWei Mate8”, “desc”: “Cheap and easy to use”, “price”: 2500, “producer”: “HuaWei Producer”, “tags”: [ “Cheap”, “Fast” ]}2.查询用法:GET /index_name/type_name/id例如:查询id为1的商品信息GET /shop_index/productInfo/1{ “_index”: “shop_index”, “_type”: “productInfo”, “_id”: “1”, “_version”: 1, “found”: true, “_source”: { “name”: “HuaWei Mate8”, “desc”: “Cheap and easy to use”, “price”: 2500, “producer”: “HuaWei Producer”, “tags”: [ “Cheap”, “Fast” ] }}3.修改方法一:替换,通过ID替换,如果文档存在,则直接覆盖用法:PUT /index_name/type_name/id{}例如:将商品价格修改为2400PUT /shop_index/productInfo/1{ “name”: “HuaWei Mate8”, “desc”: “Cheap and easy to use”, “price”: 2400, “producer”: “HuaWei Producer”, “tags”: [ “Cheap”, “Fast” ]}注意:替换某个文档时,需要带着文档中的所有字段,否则未带着的字段会丢失,切记!!!方法二:通过ID更新部分字段用法:POST /index_name/type_name/id/_update{}例如:将上述商品的价格改为2200POST /shop_index/productInfo/1/_update{ “doc”: { “price”: 2200 }}4.删除用法:DELETE /index_index/type_index/id例如:删除id为1的商品记录:DELETE /shop_index/productInfo/1本篇文章简单介绍了ES的一些重要概念及基本用法,为后续内容做铺垫,下篇文章将继续ES的多种搜索方式!欢迎评论转发! ...

February 22, 2019 · 2 min · jiezi

ORACLE 简单列转行操作-Pivot

ORACLE 简单列转行操作本文主要简单介绍下pivot 这个函数这个函数是oracle 11g的时候新加的,相比之前要自己拼语句要来的简单多该函数的固定用法to be continue…参考文档:Pivot 和 Unpivot

February 19, 2019 · 1 min · jiezi

SQL高级查询(层次化查询,递归)

SQL 高级查询前面我们写了一下 SQL 的极简入门,今天来说点高级查询。没看到的朋友可以点击下面链接查看。1 小时 SQL 极速入门(一)1 小时 SQL 极速入门(二)1 小时 SQL 极速入门(三)层次化查询层次化结构可以理解为树状数据结构,由节点构成。比如常见的组织结构由一个总经理,多个副总经理,多个部门部长组成。再比如在生产制造中一件产品会有多个子零件组成。举个简单的例子,如下图所示汽车作为根节点,下面包含发动机和车身两个子节点,而子节点又是由其他叶节点构成。(叶节点表示没有子节点的节点)假如我们要把这些产品信息存储到数据库中,会形成如下数据表。我们用 parent_product_id 列表示当前产品的父产品是哪一个。那么用 SQL 语句如何进行层次化查询呢?这里就要用到 CONNECT BY 和 START WITH 语法。我们先把 SQL 写出来,再来解释其中的含义。SELECT level, id, parent_product_id, nameFROM product START WITH id = 1 CONNECT BY prior id = parent_product_idORDER BY level查询结果如下:解释一下:LEVEL 列表示当前产品属于第几层级。START WITH 表示从哪一个产品开始查询,CONNECT BY PRIOR 表示父节点与子节点的关系,每一个产品的 ID 指向一个父产品。如果我们把 START WITH 的查询起点改为 id = 2,重新运行上面的 SQL 语句将会得到如下结果:因为 id=2 的产品是车身,我们就只能查到车身下面的子产品。当然,我们可以把查询结果美化一下,使其更有层次感,我们让根节点下面的 LEVEL 前面加几个空格即可。把上面的 SQL 稍微修改一下。为每个 LEVEL 前面增加 2*(LEVEL-1)个空格,这样第二层就会增加两个空格,第三层会增加四个空格。SELECT level, id, parent_product_id, LPAD(’ ‘, 2 * (level - 1)) || name AS nameFROM product START WITH id = 1 CONNECT BY prior id = parent_product_id查询结果已经有了层次感,如下图:递归查询除了使用上面我们说的方法,还可以使用递归查询得到同样的结果。递归会用到 WITH 语句。普通的 WITH 语句可以看作一个子查询,我们在 WITH 外部可以直接使用这个子查询的内容。当递归查询时,我们是在 WITH 语句内部来引用这个子查询。还是上面的例子,我们使用 WITH 语句来查询。WITH temp_product (product_level, id, parent_product_id,name) AS ( SELECT 0 AS product_level,id,parent_product_id,name FROM product WHERE parent_product_id IS NULL UNION ALL SELECT tp.product_level + 1,p.id, p.parent_product_id, p.name FROM product p JOIN temp_product tp ON p.parent_product_id=tp.id )SELECT product_level, id, parent_product_id, LPAD(’ ‘, 2 * product_level) || name AS NAMEFROM temp_product;第一条 SELECT 语句我们查询出来了根节点,并且设置为 level = 0,第二条SELECT 语句关联上 WITH 语句自身,并且 level 每层加 1 进行递归。查询结果如下:可以看到第一列是展示的产品层级,和我们上面查询出来的结果是一致的。同时使用 WITH 递归时还可以使用深度优先搜索和广度优先搜索,什么意思呢?广度优先就是在返回子行之前首先返回兄弟行,如上图,首先把车身和发动机两个兄弟行返回,之后是他们下面的子行。相反,深度优先就是首先返回一个父节点的子行再返回另一个兄弟行。我们只需要在 SELECT 语句上方加上下面语句即可实现深度优先搜索查询。 search depth FIRST BY id SET order_by_id结果如下,看到首先返回每个父节点下的子行,再返回另一个父节点。同理,广度优先使用的是下面的 SQL 语句 search breadth FIRST BY id SET order_by_id ...

February 17, 2019 · 1 min · jiezi

20分钟数据库索引设计实战

在后端开发的工作中如何轻松、高效地设计大量数据库索引呢?通过下面这四步,20分钟后你就再也不会为数据库的索引设计而发愁了。顺畅地阅读这篇文章需要了解数据库索引的组织方式,如果你还不熟悉的话,可以通过另一篇文章来快速了解一下——数据库索引融会贯通。这篇文章是一系列数据库索引文章中的第三篇,这个系列包括了下面四篇文章:数据库索引是什么?新华字典来帮你 —— 理解数据库索引融会贯通 —— 深入20分钟数据库索引设计实战 —— 实战数据库索引为什么用B+树实现? —— 扩展这一系列涵盖了数据库索引从理论到实践的一系列知识,一站式解决了从理解到融会贯通的全过程,相信每一篇文章都可以给你带来更深入的体验。1. 整理查询条件我们设计索引的目的主要是为了加快查询,所以,设计索引的第一步是整理需要用到的查询条件,也就是我们会在where子句、join连接条件中使用的字段。一般来说会整理程序中除了insert语句之外的所有SQL语句,按不同的表分别整理出每张表上的查询条件。也可以根据对业务的理解添加一些暂时还没有使用到的查询条件。对索引的设计一般会逐表进行,所以按数据表收集查询条件可以方便后面步骤的执行。2. 分析字段的可选择性整理出所有查询条件之后,我们需要分析出每个字段的可选择性,那么什么是可选择性呢?字段的可选择性指的就是字段的值的区分度,例如一张表中保存了用户的手机号、性别、姓名、年龄这几个字段,且一个手机号只能注册一个用户。在这种情况下,像手机号这种唯一的字段就是可选择性最高的一种情况;而年龄虽然有几十种可能,但是区分度就没有手机号那么大了;性别这样的字段则只有几种可能,所以可选择性最差。所以俺可选择性从高到低排列就是:手机号 > 年龄 > 性别。但是不同字段的值分布是不同的,有一些值的数量是大致均匀的,例如性别为男和女的值数量可能就差别不大,但是像年龄超过100岁这样的记录就非常少了。所以对于年龄这个字段,20-30这样的值就是可选择性很小的,因为每一个年龄都有非常多的记录;但是像100这样的值,那它的可选择性就非常高了。如果我们在表中添加了一个字段表示用户是否是管理员,那么在查询网站的管理员信息列表时,这个字段的可选择性就非常高。但是如果我们要查询的是非管理员信息列表时,这个字段的可选择性就非常低了。从经验上来说,我们会把可选择性高的字段放到前面,可选择性低的字段放在后面,如果可选择性非常低,一般不会把这样的字段放到索引里。3. 合并查询条件虽然索引可以加快查询的效率,但是索引越多就会导致插入和更新数据的成本变高,因为索引是分开存储的,所有数据的插入和更新操作都要对相关的索引进行修改。所以设计索引时还需要控制索引的数量,不能盲目地增加索引。一般我们会根据最左匹配原则来合并查询条件,尽可能让不同的查询条件使用同一个索引。例如有两个查询条件where a = 1 and b = 1和where b = 1,那么我们就可以创建一个索引idx_eg(b, a)来同时服务两个查询条件。同时,因为范围条件会终止使用索引中后续的字段,所以对于使用范围条件查询的字段我们也会尽可能放在索引的后面。4. 考虑是否需要使用全覆盖索引最后,我们会考虑是否需要使用全覆盖索引,因为全覆盖索引没有回表的开销,效率会更高。所以一般我们会在回表成本特别高的情况下考虑是否使用全覆盖索引,例如根据索引字段筛选后的结果需要返回其他字段或者使用其他字段做进一步筛选的情况。例如,我们有一张用户表,其中有年龄、姓名、手机号三个字段。我们需要查询在指定年龄的所有用户的姓名,已有索引idx_age_name(年龄, 姓名),目前我们使用下面这样的查询语句进行查询:SELECT *FROM 用户表WHERE 年龄 = ?;一般情况下,将一个索引优化为全覆盖索引有两种方式:增加索引中的字段,让索引字段覆盖SQL语句中使用的所有字段在这个例子中,我们可以创建一个同时包含所有字段的索引idx_all(年龄, 姓名, 手机号),以此提高查询的效率。减少SQL语句中使用的字段,使SQL需要的字段都包含在现有索引中在这个例子中,其实更好的方法是将SELECT子句修改为SELECT 姓名,因为我们的需求只是查询用户的姓名,并不需要手机号字段,去掉SELECT子句多余的字段不仅能够满足我们的需求,而且也不用对索引做修改。

February 14, 2019 · 1 min · jiezi

数据库索引为什么用B+树实现?

为什么大多数数据库索引都使用B+树来实现呢?这涉及到数据结构、操作系统、计算机存储层次结构等等复杂的理论知识,但是不用担心,这篇文章20分钟之后就会给你答案。这篇文章是一系列数据库索引文章中的最后一篇,这个系列包括了下面四篇文章:数据库索引是什么?新华字典来帮你 —— 理解数据库索引融会贯通 —— 深入20分钟数据库索引设计实战 —— 实战数据库索引为什么用B+树实现? —— 扩展这一系列涵盖了数据库索引从理论到实践的一系列知识,一站式解决了从理解到融会贯通的全过程,相信每一篇文章都可以给你带来更深入的体验。为什么使用B+树?大家在数学课上一定听说过一个例子,在一堆已经排好序的数字当中找出一个特定的数字的最好办法是一种叫“二分查找”的方式。具体的过程就是先找到这些数字中间的那一个数,然后比较目标数字是大于还是小于这个数;然后根据结果继续在前一半或者后一半数字中继续查找。这就类似于数据结构中的二叉树,二叉树就是如下的一种结构,树中的每个节点至多可以有两个子节点,而B+树每个节点则可以有N个子节点。这里就不具体展开讲解二叉树了,我们只需要知道,平衡的二叉树是内存中查询效率最高的一种数据结构就可以了。但是目前常用的数据库中,绝大多数的索引都是使用B+树实现的。那么为什么明明是二叉树查询效率最高,数据库中却偏偏要使用B+树而不是二叉树来实现索引呢?计算机存储层次结构计算机中的存储结构分为好几个部分,从上到下大致可以分为寄存器、高速缓存、主存储器、辅助存储器。其中主存储器,也就是我们常说的内存;辅助存储器也被称为外存,比较常见的就是磁盘、SSD,可以用来保存文件。在这个存储结构中,每一级存储的速度都比上一级慢很多,所以程序访问越上层存储中的数据,速度就会越快。有过编程经验的小伙伴都知道,程序运行过程中操作的基本都是内存,对外存中数据的访问往往需要写一些文件的读取和写入代码才能实现。这正是因为CPU的计算速度比存储的I/O速度(输入/输出速度)快很多所做的优化,因为CPU在每次计算完成之后就需要等待下一批的数据进入,这个等待的时间越短,计算机运行得越快。所以对于数据库索引来说,因为数据量很大,所以基本都是保存在外存中的,这样的话数据库读取一个索引节点的成本就非常大了。在数据量一样大的情况下,我们可以知道,B+树的单个节点中包含的值个数越多那么树中需要的节点总数就会越少,这样查询一次数据需要访问的节点数就更少了。如果你对B+树还不熟悉,可以到这篇文章中找到答案——数据库索引融会贯通 。如果我们把二叉树看做是特殊的B+树(每个节点只有一个值和前后两个指针的B+树),那么就可以得出结论:因为B+树的节点中包含的值个数(多个值)比二叉树(1个值)更多,所以在B+树中查询所需要的节点数就更少。那么如果每次读取的成本是一样的话,因为总成本=读取次数*单次读取成本,我们就可以证明B+树的查询成本就比二叉树小得多了。节点读取成本但是我们知道,读取更多数据肯定会需要更大的成本,那么为什么数据库索引使用B+树还是会比二叉树更好呢?这就需要一些更高深的操作系统知识来解释了。在现代的操作系统中,把数据从外存读到内存所使用的单位一般被称为“页”,每次读取数据都需要读入整数个的“页”,而不能读入半页或者0.8页。一页的大小由操作系统决定,常见的页大小一般为4KB=4096字节。所以不管我们是要读取1字节还是2KB,最后都是需要读入一个完整的4KB大小的页的,那么一个节点的读取成本就取决于需要读入的页数。在这样的情况下,如果一个节点的大小小于一页的大小,那么就会有一部分时间花在读取我们根本不需要的数据上(节点之外的数据),二叉树在这方面就会浪费很多时间;而如果一个节点的大小大于一页,哪怕是一页的整数倍,那我们也可能在一个节点的中间就找到了我们需要的指针进入了下一级的节点,这样这个指针后面的数据都白白读取了,如果不需要这些数据可能我们就可以少读几页了。所以,综上所述,数据库索引使用节点大小恰好等于操作系统一页大小的B+树来实现是效率最高的选择。

February 14, 2019 · 1 min · jiezi

数据库索引融会贯通

索引的各种规则纷繁复杂,不了解索引的组织形式就没办法真正地理解数据库索引。通过本文,你可以深入地理解数据库索引在数据库中究竟是如何组织的,从此以后索引的规则对于你将变得清清楚楚、明明白白,再也不需要死记硬背。顺畅地阅读这篇文章需要了解索引、联合索引、聚集索引分别都是什么,如果你还不了解,可以通过另一篇文章来轻松理解——数据库索引是什么?新华字典来帮你。这篇文章是一系列数据库索引文章中的第二篇,这个系列包括了下面四篇文章:数据库索引是什么?新华字典来帮你 —— 理解数据库索引融会贯通 —— 深入20分钟数据库索引设计实战 —— 实战数据库索引为什么用B+树实现? —— 扩展这一系列涵盖了数据库索引从理论到实践的一系列知识,一站式解决了从理解到融会贯通的全过程,相信每一篇文章都可以给你带来更深入的体验。索引的组织形式通过之前的内容,我们已经对数据库索引有了相当程度的抽象了解,那么在数据库中,索引实际是以什么样的形式进行组织的呢?同一张表上的多个索引又是怎样分工合作的呢?目前绝大多数情况下使用的数据库索引都是使用B+树实现的,下面就以MySQL的InnoDB为例,介绍一下数据库索引的具体实现。聚集索引下面是一个以B+树形式组织的拼音索引,在B+树中,每一个节点里都有N个按顺序排列的值,且每个值的中间和节点的头尾都有指向下一级节点的指针。在查找过程中,按顺序从头到尾遍历一个节点中的值,当发现要找的目标值恰好在一个指针的前一个值之前、后一个值之后时,就通过这个指针进入下一级节点。当最后到达叶子节点,也就是最下层的节点时,就能够找到自己希望查找的数据记录了。在上图中如果希望找到险字,那么我们首先通过拼音首字母在根节点上按顺序查找到了X和Y之间的指针,然后通过这个指针进入了第二级节点···, xia, xian, xiang, ···。之后在该节点上找到了xian和xiang之间的指针,这样就定位到了第519页开始的一个目标数据块,其中就包含了我们想要找到的险字。因为拼音索引是聚集索引,所以我们在叶子节点上直接就找到了我们想找的数据。非聚集索引下面是一个模拟部首索引的组织形式。我们由根节点逐级往下查询,但是在最后的叶子节点上并没有找到我们想找的数据,那么在使用这个索引时我们是如何得到最终的结果的呢?回忆之前字典中“检字表”的内容,我们可以看到,在每个字边上都有一个页码,这就相当于下面这一个索引中叶子节点上险字与院字中间的指针,这个指针会告诉我们真正的数据在什么地方。下图中,我们把非聚集索引(部首索引)和聚集索引(拼音索引)合在一起就能看出非聚集索引最后到底如何查找到实际数据了。非聚集索引叶子节点上的指针会直接指向聚集索引的叶子节点,因为根据聚集索引的定义,所有数据都是按聚集索引组织存储的,所以所有实际数据都保存在聚集索引的叶子节点中。而从非聚集索引的叶子节点链接到聚集索引的叶子节点查询实际数据的过程就叫做——回表。全覆盖索引那么如果我们只是想要验证险字的偏旁是否是双耳旁“阝”呢?这种情况下,我们只要在部首索引中阝下游的叶子节点中找到了险字就足够了。这种在索引中就获取到了SQL语句中需要的所有字段,所以不需要再回表查询的情况中,这个索引就被称为这个SQL语句的全覆盖索引。在实际的数据库中,非聚集索引的叶子节点上保存的“指针”就是聚集索引中所有字段的值,要获取一条实际数据,就需要通过这几个聚集索引字段的值重新在聚集索引上执行一遍查询操作。如果数据量不多,这个开销是非常小的;但如果非聚集索引的查询结果中包含了大量数据,那么就会导致回表的开销非常大,甚至超过不走索引的成本。所以全覆盖索引可以节约回表的开销这一点在一些回表开销很大的情况下就非常重要了。范围查询条件上图是一个联合索引idx_eg(col_a, col_b)的结构,如果我们希望查询一条满足条件col_a = 64 and col_b = 128的记录,那么我们可以一路确定地往下找到唯一的下级节点最终找到实际数据。这种情况下,索引上的col_a和col_b两个字段都能被使用。但是如果我们将查询条件改为范围查询col_a > 63 and col_b = 128,那么我们就会需要查找所有符合条件col_a > 63的下级节点指针,最后不得不遍历非常多的节点及其子节点。这样的话对于索引来说就得不偿失了,所以在这种情况下,数据库会选择直接遍历所有满足条件col_a > 63的记录,而不再使用索引上剩下的col_b字段。数据库会从第一条满足col_a > 63的记录开始,横向遍历之后的所有记录,从里面排除掉所有不满足col_b = 128的记录。这就是范围条件会终止使用联合索引上的后续字段的原因。

February 14, 2019 · 1 min · jiezi

DLA实现跨地域、跨实例的多AnalyticDB读写访问

介绍实时数据仓库ADB(AnalyticDB)云产品:https://www.aliyun.com/produc…数据湖分析服务DLA(Data Lake Analytics)云产品:https://www.aliyun.com/produc…数据湖分析DLA简介:https://yq.aliyun.com/article…DLA作为数据湖数据分析场景中的中枢,能够对云上众多数据存储、数据库等系统进行融合数据分析,为用户提供统一操作视角,打通系统边界,对分析结果进行回流。目前已经支持的数据存储、数据库系统包括:数据系统 源(查询) 目标(数据回流)OSS 支持 支持Table Store 支持 支持AnalyticDB 支持 支持RDS for MySQL 支持 支持用户自建MySQL 支持 支持RDS for PostgreSQL 支持 支持用户自建PostgreSQL 支持 支持RDS for SQL Server 支持 支持用户自建SQL Server 支持 支持云数据库Redis 支持 暂不支持用户自建Redis 支持 暂不支持云数据库MongoDB 支持 暂不支持用户自建MongoDB 支持 暂不支持PolarDB 支持 支持通过弹性网络技术,目前DLA支持了跨地域(region)的数据访问能力,上述的多个数据源同时通过DLA也具备了跨多个地域的融合分析能力,能够帮助用户实现多云(Multicloud: https://en.wikipedia.org/wiki…)场景下的数据融合分析。本文重点介绍通过DLA实现跨地域、跨实例的多AnalyticDB读写访问。涉及的网络结构如下图所示,假设用户使用的DLA服务位于上海region。场景示例ADB中的数据假设ADB的两个实例中,都已经存在如下两张表,对应ADB的DDL为:CREATE TABLE customer ( c_custkey int COMMENT ‘’, c_name varchar COMMENT ‘’, c_address varchar COMMENT ‘’, c_nationkey int COMMENT ‘’, c_phone varchar COMMENT ‘’, c_acctbal double COMMENT ‘’, c_mktsegment varchar COMMENT ‘’, c_comment varchar COMMENT ‘’, PRIMARY KEY (C_CUSTKEY,C_NATIONKEY))PARTITION BY HASH KEY (C_CUSTKEY) PARTITION NUM 32TABLEGROUP tpch_50x_groupOPTIONS (UPDATETYPE=‘realtime’)COMMENT ‘’;CREATE DIMENSION TABLE nation ( n_nationkey int COMMENT ‘’, n_name varchar COMMENT ‘’, n_regionkey int COMMENT ‘’, n_comment varchar COMMENT ‘’, PRIMARY KEY (N_NATIONKEY))OPTIONS (UPDATETYPE=‘realtime’)COMMENT ‘’;其中customer表有7,500,000条数据,nation表有25条数据。DLA映射北京region的ADB实例由于使用的是上海region的DLA服务,所以,需要通过公网访问该北京region的ADB实例的经典网络地址:ads-dla-test-f508cb23.cn-beijing-1.ads.aliyuncs.com:10006(跨region访问不能走VPC,网络不通)连接上海region的DLA服务,执行如下建库和建表命令,映射指向北京region ADB实例和customer表。CREATE DATABASE beijing_public_ads_dla_testWITH DBPROPERTIES (catalog = ‘ads’,location = ‘jdbc:mysql://ads-dla-test-f508cb23.cn-beijing-1.ads.aliyuncs.com:10006/ads_dla_test’,instance = ‘ads_dla_test’,user = ‘LT’,password = ‘’)COMMENT ‘’;CREATE EXTERNAL TABLE IF NOT EXISTS customer (c_custkey INT NULL COMMENT ‘’,c_name STRING NULL COMMENT ‘’,c_address STRING NULL COMMENT ‘’,c_nationkey INT NULL COMMENT ‘’,c_phone STRING NULL COMMENT ‘’,c_acctbal DOUBLE NULL COMMENT ‘’,c_mktsegment STRING NULL COMMENT ‘’,c_comment STRING NULL COMMENT ‘’);CREATE EXTERNAL TABLE IF NOT EXISTS nation ( n_nationkey int COMMENT ‘’, n_name varchar COMMENT ‘’, n_regionkey int COMMENT ‘’, n_comment varchar COMMENT ‘’);DLA映射上海region的ADB实例由于使用的是上海region的DLA服务,所以,可以通过VPC网络地址访问同上海region的ADB实例:dla-data-4d5443bf-vpc.cn-shanghai-1.ads.aliyuncs.com:10001注意:为ADB生成的VPC URL必须是为连接上海region的DLA服务,执行如下建库和建表命令,映射指向上海region ADB实例和customer表。CREATE DATABASE shanghai_vpc_dla_dataWITH DBPROPERTIES (catalog = ‘ads’,location = ‘jdbc:mysql://dla-data-4d5443bf-vpc.cn-shanghai-1.ads.aliyuncs.com:10001/dla_data’,instance = ‘dla_data’,user = ‘LM’,password = ‘’)COMMENT ‘’;CREATE EXTERNAL TABLE IF NOT EXISTS customer (c_custkey INT NULL COMMENT ‘’,c_name STRING NULL COMMENT ‘’,c_address STRING NULL COMMENT ‘’,c_nationkey INT NULL COMMENT ‘’,c_phone STRING NULL COMMENT ‘’,c_acctbal DOUBLE NULL COMMENT ‘’,c_mktsegment STRING NULL COMMENT ‘’,c_comment STRING NULL COMMENT ‘’);CREATE EXTERNAL TABLE IF NOT EXISTS nation ( n_nationkey int COMMENT ‘’, n_name varchar COMMENT ‘’, n_regionkey int COMMENT ‘’, n_comment varchar COMMENT ‘’);查询如下是按照国家统计客户的数量的查询示例,由于上述两个ADB实例中的两张表的数据相同,所以通过DLA查询,下面四个查询的结果是相同的。查询是通过上海region的DLA执行的:查询1:北京region ADB的customer表join北京region ADB的nation表;SELECT a.c_nationkey, b.n_name, count() as cntFROM beijing_public_ads_dla_test.customer aJOIN beijing_public_ads_dla_test.nation b ON a.c_nationkey = b.n_nationkeyGROUP BY a.c_nationkey, b.n_nameORDER BY cnt DESC;查询2:上海region ADB的customer表join上海region ADB的nation表;SELECT a.c_nationkey, b.n_name, count() as cntFROM shanghai_vpc_dla_data.customer aJOIN shanghai_vpc_dla_data.nation b ON a.c_nationkey = b.n_nationkeyGROUP BY a.c_nationkey, b.n_nameORDER BY cnt DESC;查询3:上海region ADB的customer表join北京region ADB的nation表;SELECT a.c_nationkey, b.n_name, count() as cntFROM shanghai_vpc_dla_data.customer aJOIN beijing_public_ads_dla_test.nation b ON a.c_nationkey = b.n_nationkeyGROUP BY a.c_nationkey, b.n_nameORDER BY cnt DESC;查询4:北京region ADB的customer表join上海region ADB的nation表;SELECT a.c_nationkey, b.n_name, count() as cntFROM beijing_public_ads_dla_test.customer aJOIN shanghai_vpc_dla_data.nation b ON a.c_nationkey = b.n_nationkeyGROUP BY a.c_nationkey, b.n_nameORDER BY cnt DESC;写入通过DLA执行INSERT from SELECT,将其他数据源的查询结果写入ADB数据源中。在DLA中执行如下语句,指向上海region的OSS bucket建库、建表:CREATE DATABASE tpch_50x_textWITH DBPROPERTIES (catalog = ‘oss’,location = ‘oss://oss-****/datasets/tpch/50x/text_date/’)COMMENT ‘’;CREATE EXTERNAL TABLE tpch_50x_text.nation (n_nationkey int,n_name string,n_regionkey int,n_comment string)ROW FORMAT DELIMITEDFIELDS TERMINATED BY ‘|‘STORED AS TEXTFILELOCATION ‘oss://oss-**/datasets/tpch/50x/text_date/nation_text’;更多关于DLA对OSS的数据查询,请参考:https://yq.aliyun.com/article… (教程:使用Data Lake Analytics + OSS分析CSV格式的TPC-H数据集)https://yq.aliyun.com/article… (教程:Data Lake Analytics + OSS数据文件格式处理大全)https://yq.aliyun.com/article… (Data Lake Analytics中OSS LOCATION的使用说明)https://yq.aliyun.com/article… (教程:如何使用Data Lake Analytics创建分区表)https://yq.aliyun.com/article… (使用Data Lake Analytics快速分析OSS上的日志文件)https://yq.aliyun.com/article… (教程:如何通过DLA实现数据文件格式转换)如下示例场景:通过上海region DLA服务,将上海region的OSS bucket数据写入上海region的ADB中:INSERT INTO shanghai_vpc_dla_data.nation SELECT * FROM tpch_50x_text.nation;通过上海region DLA服务,将上海region的OSS bucket数据写入北京region的ADB中:(由于需要跨region的公网访问,建议该模式下数据量不要太大)INSERT INTO beijing_public_ads_dla_test.nation SELECT * FROM tpch_50x_text.nation;通过上海region DLA服务,将上海region的ADB数据写入北京region的ADB中:(由于需要跨region的公网访问,建议该模式下数据量不要太大)INSERT INTO beijing_public_ads_dla_test.nation SELECT * FROM shanghai_vpc_dla_data.nation;通过上海region DLA服务,将北京region的ADB数据写入上海region的ADB中:(由于需要跨region的公网访问,建议该模式下数据量不要太大)INSERT INTO shanghai_vpc_dla_data.nation SELECT * FROM beijing_public_ads_dla_test.nation;本文作者:julian.zhou阅读原文本文为云栖社区原创内容,未经允许不得转载。 ...

February 13, 2019 · 3 min · jiezi

MySQL基础

本单元目标一、为什么要学习数据库二、数据库的相关概念 DBMS、DB、SQL三、数据库存储数据的特点四、初始MySQL MySQL产品的介绍 MySQL产品的安装 ★ MySQL服务的启动和停止 ★ MySQL服务的登录和退出 ★ MySQL的常见命令和语法规范 五、DQL语言的学习 ★ 基础查询 ★ 条件查询 ★ 排序查询 ★ 常见函数 ★ 分组函数 ★ 分组查询 ★ 连接查询 ★ 子查询 √ 分页查询 ★ union联合查询 √ 六、DML语言的学习 ★ 插入语句 修改语句 删除语句 七、DDL语言的学习 库和表的管理 √ 常见数据类型介绍 √ 常见约束 √ 八、TCL语言的学习 事务和事务处理 九、视图的讲解 √十、变量 十一、存储过程和函数 十二、流程控制结构 数据库的好处1.持久化数据到本地2.可以实现结构化查询,方便管理数据库相关概念1、DB:数据库,保存一组有组织的数据的容器2、DBMS:数据库管理系统,又称为数据库软件(产品),用于管理DB中的数据3、SQL:结构化查询语言,用于和DBMS通信的语言数据库存储数据的特点1、将数据放到表中,表再放到库中2、一个数据库中可以有多个表,每个表都有一个的名字,用来标识自己。表名具有唯一性。3、表具有一些特性,这些特性定义了数据在表中如何存储,类似java中 “类”的设计。4、表由列组成,我们也称为字段。所有表都是由一个或多个列组成的,每一列类似java 中的”属性”5、表中的数据是按行存储的,每一行类似于java中的“对象”。MySQL产品的介绍和安装MySQL服务的启动和停止方式一:计算机——右击管理——服务方式二:通过管理员身份运行net start 服务名(启动服务)net stop 服务名(停止服务)MySQL服务的登录和退出方式一:通过mysql自带的客户端只限于root用户方式二:通过windows自带的客户端登录:mysql 【-h主机名 -P端口号 】-u用户名 -p密码退出:exit或ctrl+CMySQL的常见命令1.查看当前所有的数据库show databases;2.打开指定的库use 库名3.查看当前库的所有表show tables;4.查看其它库的所有表show tables from 库名;5.创建表create table 表名( 列名 列类型, 列名 列类型, 。。。);6.查看表结构desc 表名;7.查看服务器的版本方式一:登录到mysql服务端select version();方式二:没有登录到mysql服务端mysql –version或mysql –VMySQL的语法规范1.不区分大小写,但建议关键字大写,表名、列名小写2.每条命令最好用分号结尾3.每条命令根据需要,可以进行缩进 或换行4.注释 单行注释:#注释文字 单行注释:– 注释文字 多行注释:/* 注释文字 /SQL的语言分类DQL(Data Query Language):数据查询语言 select DML(Data Manipulate Language):数据操作语言 insert 、update、deleteDDL(Data Define Languge):数据定义语言 create、drop、alterTCL(Transaction Control Language):事务控制语言 commit、rollbackSQL的常见命令show databases; 查看所有的数据库use 库名; 打开指定 的库show tables ; 显示库中的所有表show tables from 库名;显示指定库中的所有表create table 表名( 字段名 字段类型, 字段名 字段类型); 创建表desc 表名; 查看指定表的结构select * from 表名;显示表中的所有数据DQL语言的学习进阶1:基础查询语法:SELECT 要查询的东西【FROM 表名】;类似于Java中 :System.out.println(要打印的东西);特点:①通过select查询完的结果 ,是一个虚拟的表格,不是真实存在② 要查询的东西 可以是常量值、可以是表达式、可以是字段、可以是函数进阶2:条件查询条件查询:根据条件过滤原始表的数据,查询到想要的数据语法:select 要查询的字段|表达式|常量值|函数from 表where 条件 ;分类:一、条件表达式 示例:salary>10000 条件运算符: > < >= <= = != <>二、逻辑表达式示例:salary>10000 && salary<20000逻辑运算符: and(&&):两个条件如果同时成立,结果为true,否则为false or(||):两个条件只要有一个成立,结果为true,否则为false not(!):如果条件成立,则not后为false,否则为true三、模糊查询示例:last_name like ‘a%‘进阶3:排序查询语法:select 要查询的东西from 表where 条件order by 排序的字段|表达式|函数|别名 【asc|desc】进阶4:常见函数一、单行函数1、字符函数 concat拼接 substr截取子串 upper转换成大写 lower转换成小写 trim去前后指定的空格和字符 ltrim去左边空格 rtrim去右边空格 replace替换 lpad左填充 rpad右填充 instr返回子串第一次出现的索引 length 获取字节个数 2、数学函数 round 四舍五入 rand 随机数 floor向下取整 ceil向上取整 mod取余 truncate截断3、日期函数 now当前系统日期+时间 curdate当前系统日期 curtime当前系统时间 str_to_date 将字符转换成日期 date_format将日期转换成字符4、流程控制函数 if 处理双分支 case语句 处理多分支 情况1:处理等值判断 情况2:处理条件判断 5、其他函数 version版本 database当前库 user当前连接用户二、分组函数 sum 求和 max 最大值 min 最小值 avg 平均值 count 计数 特点: 1、以上五个分组函数都忽略null值,除了count() 2、sum和avg一般用于处理数值型 max、min、count可以处理任何数据类型 3、都可以搭配distinct使用,用于统计去重后的结果 4、count的参数可以支持: 字段、、常量值,一般放1 建议使用 count()进阶5:分组查询语法:select 查询的字段,分组函数from 表group by 分组的字段特点:1、可以按单个字段分组2、和分组函数一同查询的字段最好是分组后的字段3、分组筛选 针对的表 位置 关键字分组前筛选: 原始表 group by的前面 where分组后筛选: 分组后的结果集 group by的后面 having4、可以按多个字段分组,字段之间用逗号隔开5、可以支持排序6、having后可以支持别名进阶6:多表连接查询笛卡尔乘积:如果连接条件省略或无效则会出现解决办法:添加上连接条件一、传统模式下的连接 :等值连接——非等值连接1.等值连接的结果 = 多个表的交集2.n表连接,至少需要n-1个连接条件3.多个表不分主次,没有顺序要求4.一般为表起别名,提高阅读性和性能二、sql99语法:通过join关键字实现连接含义:1999年推出的sql语法支持:等值连接、非等值连接 (内连接)外连接交叉连接语法:select 字段,…from 表1【inner|left outer|right outer|cross】join 表2 on 连接条件【inner|left outer|right outer|cross】join 表3 on 连接条件【where 筛选条件】【group by 分组字段】【having 分组后的筛选条件】【order by 排序的字段或表达式】好处:语句上,连接条件和筛选条件实现了分离,简洁明了!三、自连接案例:查询员工名和直接上级的名称sql99SELECT e.last_name,m.last_nameFROM employees eJOIN employees m ON e.manager_id=m.employee_id;sql92SELECT e.last_name,m.last_nameFROM employees e,employees m WHERE e.manager_id=m.employee_id;进阶7:子查询含义:一条查询语句中又嵌套了另一条完整的select语句,其中被嵌套的select语句,称为子查询或内查询在外面的查询语句,称为主查询或外查询特点:1、子查询都放在小括号内2、子查询可以放在from后面、select后面、where后面、having后面,但一般放在条件的右侧3、子查询优先于主查询执行,主查询使用了子查询的执行结果4、子查询根据查询结果的行数不同分为以下两类:① 单行子查询 结果集只有一行 一般搭配单行操作符使用:> < = <> >= <= 非法使用子查询的情况: a、子查询的结果为一组值 b、子查询的结果为空 ② 多行子查询 结果集有多行 一般搭配多行操作符使用:any、all、in、not in in: 属于子查询结果中的任意一个就行 any和all往往可以用其他查询代替进阶8:分页查询应用场景:实际的web项目中需要根据用户的需求提交对应的分页查询的sql语句语法:select 字段|表达式,…from 表【where 条件】【group by 分组字段】【having 条件】【order by 排序的字段】limit 【起始的条目索引,】条目数;特点:1.起始条目索引从0开始2.limit子句放在查询语句的最后3.公式:select * from 表 limit (page-1)*sizePerPage,sizePerPage假如:每页显示条目数sizePerPage要显示的页数 page进阶9:联合查询引入:union 联合、合并语法:select 字段|常量|表达式|函数 【from 表】 【where 条件】 union 【all】select 字段|常量|表达式|函数 【from 表】 【where 条件】 union 【all】select 字段|常量|表达式|函数 【from 表】 【where 条件】 union 【all】…..select 字段|常量|表达式|函数 【from 表】 【where 条件】特点:1、多条查询语句的查询的列数必须是一致的2、多条查询语句的查询的列的类型几乎相同3、union代表去重,union all代表不去重DML语言插入语法:insert into 表名(字段名,…)values(值1,…);特点:1、字段类型和值类型一致或兼容,而且一一对应2、可以为空的字段,可以不用插入值,或用null填充3、不可以为空的字段,必须插入值4、字段个数和值的个数必须一致5、字段可以省略,但默认所有字段,并且顺序和表中的存储顺序一致修改修改单表语法:update 表名 set 字段=新值,字段=新值【where 条件】修改多表语法:update 表1 别名1,表2 别名2set 字段=新值,字段=新值where 连接条件and 筛选条件删除方式1:delete语句 单表的删除: ★delete from 表名 【where 筛选条件】多表的删除:delete 别名1,别名2from 表1 别名1,表2 别名2where 连接条件and 筛选条件;方式2:truncate语句truncate table 表名两种方式的区别【面试题】#1.truncate不能加where条件,而delete可以加where条件#2.truncate的效率高一丢丢#3.truncate 删除带自增长的列的表后,如果再插入数据,数据从1开始#delete 删除带自增长列的表后,如果再插入数据,数据从上一次的断点处开始#4.truncate删除不能回滚,delete删除可以回滚DDL语句库和表的管理库的管理:一、创建库create database 库名二、删除库drop database 库名表的管理:#1.创建表CREATE TABLE IF NOT EXISTS stuinfo( stuId INT, stuName VARCHAR(20), gender CHAR, bornDate DATETIME );DESC studentinfo;#2.修改表 alter语法:ALTER TABLE 表名 ADD|MODIFY|DROP|CHANGE COLUMN 字段名 【字段类型】;#①修改字段名ALTER TABLE studentinfo CHANGE COLUMN sex gender CHAR;#②修改表名ALTER TABLE stuinfo RENAME [TO] studentinfo;#③修改字段类型和列级约束ALTER TABLE studentinfo MODIFY COLUMN borndate DATE ;#④添加字段ALTER TABLE studentinfo ADD COLUMN email VARCHAR(20) first;#⑤删除字段ALTER TABLE studentinfo DROP COLUMN email;#3.删除表DROP TABLE [IF EXISTS] studentinfo;常见类型整型: 小数: 浮点型 定点型字符型:日期型:Blob类型:常见约束NOT NULLDEFAULTUNIQUECHECKPRIMARY KEYFOREIGN KEY数据库事务含义通过一组逻辑操作单元(一组DML——sql语句),将数据从一种状态切换到另外一种状态特点(ACID)原子性:要么都执行,要么都回滚一致性:保证数据的状态操作前和操作后保持一致隔离性:多个事务同时操作相同数据库的同一个数据时,一个事务的执行不受另外一个事务的干扰持久性:一个事务一旦提交,则数据将持久化到本地,除非其他事务对其进行修改相关步骤:1、开启事务2、编写事务的一组逻辑操作单元(多条sql语句)3、提交事务或回滚事务事务的分类:隐式事务,没有明显的开启和结束事务的标志比如insert、update、delete语句本身就是一个事务显式事务,具有明显的开启和结束事务的标志 1、开启事务 取消自动提交事务的功能 2、编写事务的一组逻辑操作单元(多条sql语句) insert update delete 3、提交事务或回滚事务使用到的关键字set autocommit=0;start transaction;commit;rollback;savepoint 断点commit to 断点rollback to 断点事务的隔离级别:事务并发问题如何发生?当多个事务同时操作同一个数据库的相同数据时事务的并发问题有哪些?脏读:一个事务读取到了另外一个事务未提交的数据不可重复读:同一个事务中,多次读取到的数据不一致幻读:一个事务读取数据时,另外一个事务进行更新,导致第一个事务读取到了没有更新的数据如何避免事务的并发问题?通过设置事务的隔离级别1、READ UNCOMMITTED2、READ COMMITTED 可以避免脏读3、REPEATABLE READ 可以避免脏读、不可重复读和一部分幻读4、SERIALIZABLE可以避免脏读、不可重复读和幻读设置隔离级别:set session|global transaction isolation level 隔离级别名;查看隔离级别:select @@tx_isolation;视图含义:理解成一张虚拟的表视图和表的区别: 使用方式 占用物理空间视图 完全相同 不占用,仅仅保存的是sql逻辑表 完全相同 占用视图的好处:1、sql语句提高重用性,效率高2、和表实现了分离,提高了安全性视图的创建语法:CREATE VIEW 视图名AS查询语句;视图的增删改查1、查看视图的数据 ★SELECT * FROM my_v4;SELECT * FROM my_v1 WHERE last_name=‘Partners’;2、插入视图的数据INSERT INTO my_v4(last_name,department_id) VALUES(‘虚竹’,90);3、修改视图的数据UPDATE my_v4 SET last_name =‘梦姑’ WHERE last_name=‘虚竹’;4、删除视图的数据DELETE FROM my_v4;某些视图不能更新包含以下关键字的sql语句:分组函数、distinct、group by、having、union或者union all常量视图Select中包含子查询joinfrom一个不能更新的视图where子句的子查询引用了from子句中的表视图逻辑的更新#方式一:CREATE OR REPLACE VIEW test_v7ASSELECT last_name FROM employeesWHERE employee_id>100;#方式二:ALTER VIEW test_v7ASSELECT employee_id FROM employees;SELECT * FROM test_v7;视图的删除DROP VIEW test_v1,test_v2,test_v3;视图结构的查看DESC test_v7;SHOW CREATE VIEW test_v7;存储过程含义:一组经过预先编译的sql语句的集合好处:1、提高了sql语句的重用性,减少了开发程序员的压力2、提高了效率3、减少了传输次数分类:1、无返回无参2、仅仅带in类型,无返回有参3、仅仅带out类型,有返回无参4、既带in又带out,有返回有参5、带inout,有返回有参注意:in、out、inout都可以在一个存储过程中带多个创建存储过程语法:create procedure 存储过程名(in|out|inout 参数名 参数类型,…)begin 存储过程体end类似于方法:修饰符 返回类型 方法名(参数类型 参数名,…){ 方法体;}注意1、需要设置新的结束标记delimiter 新的结束标记示例:delimiter $CREATE PROCEDURE 存储过程名(IN|OUT|INOUT 参数名 参数类型,…)BEGIN sql语句1; sql语句2;END $2、存储过程体中可以有多条sql语句,如果仅仅一条sql语句,则可以省略begin end3、参数前面的符号的意思in:该参数只能作为输入 (该参数不能做返回值)out:该参数只能作为输出(该参数只能做返回值)inout:既能做输入又能做输出调用存储过程call 存储过程名(实参列表)函数创建函数学过的函数:LENGTH、SUBSTR、CONCAT等语法:CREATE FUNCTION 函数名(参数名 参数类型,…) RETURNS 返回类型BEGIN 函数体END调用函数SELECT 函数名(实参列表)函数和存储过程的区别 关键字 调用语法 返回值 应用场景函数 FUNCTION SELECT 函数() 只能是一个 一般用于查询结果为一个值并返回时,当有返回值而且仅仅一个存储过程 PROCEDURE CALL 存储过程() 可以有0个或多个 一般用于更新流程控制结构系统变量一、全局变量作用域:针对于所有会话(连接)有效,但不能跨重启查看所有全局变量SHOW GLOBAL VARIABLES;查看满足条件的部分系统变量SHOW GLOBAL VARIABLES LIKE ‘%char%’;查看指定的系统变量的值SELECT @@global.autocommit;为某个系统变量赋值SET @@global.autocommit=0;SET GLOBAL autocommit=0;二、会话变量作用域:针对于当前会话(连接)有效查看所有会话变量SHOW SESSION VARIABLES;查看满足条件的部分会话变量SHOW SESSION VARIABLES LIKE ‘%char%’;查看指定的会话变量的值SELECT @@autocommit;SELECT @@session.tx_isolation;为某个会话变量赋值SET @@session.tx_isolation=‘read-uncommitted’;SET SESSION tx_isolation=‘read-committed’;自定义变量一、用户变量声明并初始化:SET @变量名=值;SET @变量名:=值;SELECT @变量名:=值;赋值:方式一:一般用于赋简单的值SET 变量名=值;SET 变量名:=值;SELECT 变量名:=值;方式二:一般用于赋表 中的字段值SELECT 字段名或表达式 INTO 变量FROM 表;使用:select @变量名;二、局部变量声明:declare 变量名 类型 【default 值】;赋值:方式一:一般用于赋简单的值SET 变量名=值;SET 变量名:=值;SELECT 变量名:=值;方式二:一般用于赋表 中的字段值SELECT 字段名或表达式 INTO 变量FROM 表;使用:select 变量名二者的区别: 作用域 定义位置 语法用户变量 当前会话 会话的任何地方 加@符号,不用指定类型局部变量 定义它的BEGIN END中 BEGIN END的第一句话 一般不用加@,需要指定类型分支一、if函数语法:if(条件,值1,值2)特点:可以用在任何位置二、case语句语法:情况一:类似于switchcase 表达式when 值1 then 结果1或语句1(如果是语句,需要加分号) when 值2 then 结果2或语句2(如果是语句,需要加分号)…else 结果n或语句n(如果是语句,需要加分号)end 【case】(如果是放在begin end中需要加上case,如果放在select后面不需要)情况二:类似于多重ifcase when 条件1 then 结果1或语句1(如果是语句,需要加分号) when 条件2 then 结果2或语句2(如果是语句,需要加分号)…else 结果n或语句n(如果是语句,需要加分号)end 【case】(如果是放在begin end中需要加上case,如果放在select后面不需要)特点:可以用在任何位置三、if elseif语句语法:if 情况1 then 语句1;elseif 情况2 then 语句2;…else 语句n;end if;特点:只能用在begin end中!!!!!!!!!!!!!!!三者比较: 应用场合if函数 简单双分支case结构 等值判断 的多分支if结构 区间判断 的多分支循环语法:【标签:】WHILE 循环条件 DO 循环体END WHILE 【标签】;特点:只能放在BEGIN END里面如果要搭配leave跳转语句,需要使用标签,否则可以不用标签leave类似于java中的break语句,跳出所在循环!!! ...

February 12, 2019 · 3 min · jiezi

四个月技术写作,我写了些什么?

从去年国庆节开始,我连续更新了 4 个月公众号,累计发布原创文章 40 篇。按照大多数个人订阅号的优良传统,号主应该在跨年的前后作年终总结。然而,一来我反应比较迟钝,没跟上节奏,二来当时我正在写比较重要的系列,没时间分心,所以还是慢了半拍。现在,创作出现了空档期,而身体也出现一种魔幻性的跨移——从几千里外的城市回到分别了几百天的农村。这仿佛就在营造一种仪式感,逼使我要把这未完成的任务做个了结。因此,现在我就来梳理梳理写出来的东西,说说我的想法吧。1、Python猫的故事这是我的主打系列,故事的主角是一只来自外太星(喵星)的猫。它外貌长什么样,我还没想好,你可以叠加所有猫的形象上去,这就是它的样子。然而,它绝不是一种固定形态的物种。编程语言(Python为主)、人类文化(文学+哲学)、人工智能、前沿科学(生物+量子物理)和幻想相交合,这些东西都会是我的灵感,也会是塑造这只猫的原力。我们认识周遭世界的过程是一种逐步扩大的过程,从点到线,到理得清的网,再到真正的网。一只作为讲述者的猫,在思考,在探知并试图融入陌生的星球的时候,会发生些什么认知层面上的结果呢?我有很多朦胧的念头。想要完全落实它们,简直不可能。有些东西就是说不清。不过,有了开端,有了大致的方向,就总是有了提起“笔”写下去的动机。有了Python,我能叫出所有猫的名字Python对象的身份迷思:从全体公民到万物皆数Python对象的空间边界:独善其身与开放包容2、Python进阶之路我接触 Python 的时间并不长,在工作中用到它的时间就更短了。因为清楚地意识到自己的基础并不扎实,所以,几个月以来,我花了不少时间系统性地学习了一些内容。写作前,搜集资料,查漏补缺;写作中,发散思考,融会贯通;发布后,聆听反馈,修正错误。时间过得真快,现在能拿得出手的也就仅仅是字符串系列、切片系列和迭代器系列了。我计划继续花些时间,把重要的知识梳理一遍。通过这个系列的写作,我想驱动自己走出一条 Python 进阶之路。然后以它为基础,再进行其它领域(爬虫、数据分析、深度学习、?)的转向。在准备生成器系列的时候,我一时起了翻译 PEP 的念头,就开启了翻译 PEP 的系列。现在试水了两篇生成器相关的,年后还会翻译一篇。关于翻译,我有一些想法,今后再细说。这个系列的一些内容,其实是在给 Python 猫系列打基础做铺垫。今后,我会避免两个系列的内容重叠,也不让它们失衡,因此会想办法给 Python猫 系列留下足够的写作余地。超强汇总:学习Python列表,只需这篇文章就够了学习Python操作JSON,网络数据交换不用愁给Python学习者的文件读写指南(含基础与进阶,建议收藏)再谈文件读写:判断文件的几种方法及其优劣对比Python中的“特权种族”是什么?详解Python拼接字符串的七种方式你真的知道Python的字符串是什么吗?你真的知道Python的字符串怎么用吗?Python是否支持复制字符串呢?join()方法的神奇用处与Intern机制的软肋Python进阶:切片的误区与高级用法Python进阶:迭代器与迭代器切片Python进阶:全面解读高级特性之切片!Python进阶:设计模式之迭代器模式为什么range不是迭代器?range到底是什么类型?[[译] PEP 255–简单的生成器](https://mp.weixin.qq.com/s/vj...[[译]PEP 342–增强型生成器:协程](https://mp.weixin.qq.com/s/M7...3、荐书系列关于荐书系列,我是受到了经常阅读的一些电影公众号的启发。如果有一部好电影,大家就会花很长篇幅去推介它,去评论它,去宣传它。对于一些非技术类的书籍,也很可能有此待遇。可是,我们却不怎么见到技术类书籍是这样的吧?除去出版社、作者和利益相关机构,你几乎看不到有人为一本技术书籍写书评(写笔记、画思维导图的倒是挺多)。于是,我决定来尝试一下。有几篇,我特意提到了豆瓣评分和评论,现在看来模仿的痕迹太重,这类玩意对技术类书籍来说,真不合适。纯探索阶段,希望今后能拿出更好的作品。这个系列,主要还有一个考虑:促使我自己去阅读,逼着自己学会总结归纳,多产生一些积累。《编写高质量代码改善 Python 程序的 91 个建议》《Python最佳实践指南》《黑客与画家》《Python源码剖析》《Python高性能编程 》4、杂七杂八这部分内容也是跟技术息息相关的,例如 Python社区动态、技术写作、编程思想、技术翻译等等。其中,关于社区治理模式投票的几篇文章,我最初以为是个热点,但后来意识到,真的没有多少人关心。(我该怀疑自己的关注点呢,还是怀疑别人?)值得庆幸的是,有篇文章被两位圈内大佬转载了!我乐了好久。这里就不提名字了,总之他们是我初学 Python 时就很佩服的人,因为看了他们的一些文章,我才动了写技术文章的念头。关于技术写作和翻译,我初见门道,今后还会多作总结分享。来自Kenneth Reitz大神的建议:避免不必要的面向对象编程学习Python,怎能不懂点PEP呢?再聊聊Python中文社区的翻译Python之父退位后,最高决策权花落谁家?这件正在发生的事,关乎所有的Python开发者……最新进展|关于Python治理模式的投票Python决策权的投票结果诞生了,“指导委员会”模式拔得头筹聊聊技术写作的个人体会大名鼎鼎的Requests库用了什么编码风格?5、写在最后我承认自己是一个不擅运营的人,虽然为了提升公众号的订阅数与阅读数,也做过一些运营的尝试,但是,跟圈内的很多号主相比,差得可不止一丝半点。四个月以来,我结识了很多技术写作的号主,他们有些人创号不久,但不仅技术扎实,而且抓选题、抓热点和写作风格都极其出色,很快就成为了“当红炸子鸡”;还有的大佬,持续耕耘了几年,坐拥数十万粉丝,立品牌、出书、出课程、开知识付费、开公司,替技术人走出了一条名利双收的榜样之路。他们令我羡慕。他们皆有值得我学习取经的优点。不过我也知道,坚持自己的原则、发展自己的特色更为重要。做人也好,写公众号也好,循着自己的本心与节奏,才不至于走歪了路。所以,在以上几个系列的写作方向上,我仍会继续坚持,沉下心来学习,思考和分享。这个阶段性的小结,既是一个交代,也是新的开端。—————–本文原创并首发于微信公众号【Python猫】,后台回复“爱学习”,免费获得20+本精选电子书。

February 3, 2019 · 1 min · jiezi

Mysql的基本语句操作

本篇文章主要介绍一下Mysql的基本操作。一、关于数据库的基本操作创建数据库 ->create database + 数据库名称查看数据库 ->show databases删除数据库 ->drop database + 数据库名称使用数据库 ->use + 数据库名称查看创建数据库的详细信息->show create databases + 数据库名称二、关于表的基本操作显示数据库中的表:show tables显示表的字段:desc + 表名称删除表:drop table + 表名称创建表:create table + 表名称(字段一 类型一,字段二 类型二)查看创建表的详细信息:show create table + 表名称三、修改表结构的操作修改表字段的类型->alter table +表名称+ modify +字段+字段类型添加表的字段-> alter table + 表名称 + add +字段+字段类型添加字段到指定位置->alter table +表名称 + add +字段+字段类型 +after 字段删除表字段->alter table + 表名称 +drop +字段名称修改指定字段名称->alter table +表名称 + change +原字段名称 +新字段名称 + 字段类型四、基本操作–增、删、改、查“增”—insert(1) 对一行所有字段增加数据语法:insert into +表名称 + values(值1,值2,……)(2) 对指定字段增加数据语法:insert into +表名称 +(字段1,字段2,……)+ values +(值1,值2,……)“删”—delete(1)删除表中指定数据语法:delete from +表名称 +where条件表达式(2)删除表中全部数据语法:delete from +表名称“改”—update(1)修改表中现存数据语法:update +表名称 +set 字段1=值1,字段2=值2 +where条件PS:若不带where条件,则是对整表记录指定字段进行更新。“查”—select(1)select的简单查询语法:select +字段1,字段2,字段3 +from +表名称 +where条件PS:若查询全表信息则将所有字段名换为通配符 *。 ...

January 30, 2019 · 1 min · jiezi

数据库简单介绍

在当今的大数据时代,数据库已成为必不可少的数据工具。什么是数据库呢,数据库(Database)是按照数据结构来组织、存储和管理数据的仓库。MYSQL是目前最为流行的关系型数据库系统,关系型数据库系统有以下5个特点:1.数据是以表格的形式出现;2.每行是一条记录;3.每列是该字段对应的数据域;4.行与列形成一张二维表;5.若干个二维表组成数据库。![数据库][1]MYSQL数据库有三大类的数据类型,分别是数字、日期/时间、字符串。数字类型: 1.整型:tinyint,smallint,mediumint,int,bigint2.浮点数:float,double,real,decimal日期/时间类型:1.date,time,datetime,timestamp,year字符串类型:1.字符串:char,varchar2.文本:tinytext,text,mediumtext,longtext3.二进制:tinyblob,blob,mediumblob,longblob

January 28, 2019 · 1 min · jiezi

终于等到你!阿里正式向 Apache Flink 贡献 Blink 源码

阿里妹导读:如同我们去年12月在 Flink Forward China 峰会所约,阿里巴巴内部 Flink 版本 Blink 将于 2019 年 1 月底正式开源。今天,我们终于等到了这一刻。阿里资深技术专家大沙,将为大家详细介绍本次开源的Blink主要功能和优化点,希望与业界同仁共同携手,推动Flink社区进一步发展。Blink简介Apache Flink是德国柏林工业大学的几个博士生和研究生从学校开始做起来的项目,早期叫做Stratosphere。2014年,StratoSphere项目中的核心成员从学校出来开发了Flink,同时将Flink计算的主流方向定位为流计算,并在同年将Flink捐赠Apache,后来快速孵化成为Apache的顶级项目。现在Flink是业界公认的最好的大数据流计算引擎。阿里巴巴在2015年开始尝试使用Flink。但是阿里的业务体量非常庞大,挑战也很多。彼时的Flink不管是规模还是稳定性尚未经历实践,成熟度有待商榷。为了把这么大的业务体量支持好,我们不得不在Flink之上做了一系列的改进,所以阿里巴巴维护了一个内部版本的Flink,它的名字叫做Blink。基于Blink的计算平台于2016年正式上线。截至目前,阿里绝大多数的技术部门都在使用Blink。Blink一直在阿里内部错综复杂的业务场景中锻炼成长着。对于内部用户反馈的各种性能、资源使用率、易用性等诸多方面的问题,Blink都做了针对性的改进。虽然现在Blink在阿里内部用的最多的场景主要还是在流计算,但是在批计算场景也有不少业务上线使用了。例如,在搜索和推荐的算法业务平台中,它使用Blink同时进行流计算和批处理。Blink被用来实现了流批一体化的样本生成和特征抽取这些流程,能够处理的特征数达到了数千亿,而且每秒钟处理数亿条消息。在这个场景的批处理中,我们单个作业处理的数据量已经超过400T,并且为了节省资源,我们的批处理作业是和流计算作业以及搜索的在线引擎运行在同样的机器上。所以大家可以看到流批一体化已经在阿里巴巴取得了极大的成功,我们希望这种成功和阿里巴巴内部的经验都能够带回给社区。Blink开源的背景其实从我们选择Flink的第一天开始我们就一直和社区紧密合作。过去的这几年我们也一直在把阿里对Flink 的改进推回社区。从2016年开始我们已经将流计算SQL的大部分功能,针对runtime的稳定性和性能优化做的若干重要设计都推回了社区。但是Blink本身发展迭代的速度非常快,而社区有自己的步伐,很多时候可能无法把我们的变更及时推回去。对于社区来说,一些大的功能和重构,需要达成共识后,才能被接受,这样才能更好地保证开源项目的质量,但是同时就会导致推入的速度变得相对较慢。经过这几年的开发迭代,我们这边和社区之间的差距已经变得比较大了。Blink 有一些很好的新功能,比如性能优越的批处理功能,在社区的版本是没有的。在过去这段时间里,我们不断听到有人在询问Blink的各种新功能。期望Blink尽快开源的呼声越来越大。我们一直在思考如何开源的问题,一种方案就是和以前一样,继续把各种功能和优化分解,逐个和社区讨论,慢慢地推回Flink。但这显然不是大家所期待的。另一个方案,就是先完整的尽可能的多的把代码开源,让社区的开发者能够尽快试用起来。第二个方案很快收到社区广大用户的支持。因此,从2018年年中开始我们就开始做开源的相关准备。经过半年的努力,我们终于把大部分Blink的功能梳理好,开源了出来。Blink开源的方式我们把代码贡献出来,是为了让大家能先尝试一些他们感兴趣的功能。Blink永远不会单独成为一个独立的开源项目来运作,他一定是Flink的一部分。开源后我们期望能找到办法以最快的方式将Blink merge到Flink中去。Blink开源只有一个目的,就是希望 Flink 做得更好。Apache Flink 是一个社区项目,Blink以什么样的形式进入 Flink 是最合适的,怎么贡献是社区最希望的方式,我们都要和社区一起讨论。在过去的一段时间内,我们在Flink社区征求了广泛的意见,大家一致认为将本次开源的Blink代码作为Flink的一个branch直接推回到Apache Flink项目中是最合适的方式。并且我们和社区也一起讨论规划出一套能够快速merge Blink到Flink master中的方案(具体细节可以查看Flink社区正在讨论的FLIP32)。我们期望这个merge能够在很短的时间内完成。这样我们之后的Machine Learning等其他新功能就可以直接推回到Flink master。相信用不了多久,Flink 和 Blink 就完全合二为一了。在那之后,阿里巴巴将直接使用Flink用于生产,并同时协助社区一起来维护Flink。本次开源的Blink的主要功能和优化点本次开源的Blink代码在Flink 1.5.1版本之上,加入了大量的新功能,以及在性能和稳定性上的各种优化。主要贡献包括,阿里巴巴在流计算上积累的一些新功能和性能的优化,一套完整的(能够跑通全部TPC-H/TPC-DS,能够读取Hive meta和data)高性能Batch SQL,以及一些以提升易用性为主的功能(包括支持更高效的interactive programming, 与zeppelin更紧密的结合, 以及体验和性能更佳的Flink web)。未来我们还将继续给Flink贡献在AI,IoT以及其他新领域的功能和优化。更多的关于这一版本Blink release的细节,请参考Blink代码根目录下的README.md文档。下面,我来分模块介绍下Blink主要的新的功能和优化点。Runtime为了更好的支持batch processing,以及解决阿里巴巴大规模生产场景中遇到的各种挑战,Blink对Runtime架构、效率、稳定性方面都做了大量改进。在架构方面,首先Blink引入了Pluggable ShuffleArchitecture,开发者可以根据不同的计算模型或者新硬件的需要实现不同的shuffle策略进行适配。此外Blink还引入新的调度架构,容许开发者根据计算模型自身的特点定制不同调度器。为了优化性能,Blink可以让算子更加灵活的chain在一起,避免了不必要的数据传输开销。在Pipeline Shuffle模式中,使用了ZeroCopy减少了网络层内存消耗。在BroadCast Shuffle模式中,Blink优化掉了大量的不必要的序列化和反序列化开销。此外,Blink提供了全新的JM FailOver机制,JM发生错误之后,新的JM会重新接管整个JOB而不是重启JOB,从而大大减少了JM FailOver对JOB的影响。最后,Blink也开发了对Kubernetes的支持。不同于Standalone模式在Kubernetes上的拉起方式,在基于Flink FLIP6的架构上基础之上,Blink根据job的资源需求动态的申请/释放Pod来运行TaskExecutor,实现了资源弹性,提升了资源的利用率。SQL/TableAPISQL/TableAPI架构上的重构和性能的优化是Blink本次开源版本的一个重大贡献。首先,我们对SQL engine的架构做了较大的调整。提出了全新的Query Processor(QP), 它包括了一个优化层(Query Optimizer)和一个算子层(Query Executor)。这样一来,流计算和批计算的在这两层大部分的设计工作就能做到尽可能的复用。另外,SQL和TableAPI的程序最终执行的时候将不会翻译到DataStream和DataSet这两个API上,而是直接构建到可运行的DAG上来,这样就使得物理执行算子的设计不完全依赖底层的API,有了更大的灵活度,同时执行代码也能够被灵活的codegen出来。唯一的一个影响就是这个版本的SQL和TableAPI不能和DataSet这个API进行互相转换,但仍然保留了和DataStream API互相转换的能力(将DataStream注册成表,或将Table转成DataStream后继续操作)。未来,我们计划把dataset的功能慢慢都在DataStream和TableAPI上面实现。到那时DataStream和SQL以及tableAPI一样,是一个可以同时描述bounded以及unbounded processing的API。除了架构上的重构,Blink还在具体实现上做了较多比较大的重构。首先,Blink引入了二进制的数据结构BinaryRow,极大的减少了数据存储上的开销以及数据在序列化和反序列化上计算的开销。其次,在算子的实现层面,Blink在更广范围内引入了CodeGen技术。由于预先知道算子需要处理的数据的类型,在QP层内部就可以直接生成更有针对性更高效的执行代码。Blink的算子会动态的申请和使用资源,能够更好的利用资源,提升效率,更加重要的是这些算子对资源有着比较好的控制,不会发生OutOfMemory 的问题。此外,针对流计算场景,Blink加入了miniBatch的执行模式,在aggregate、join等需要和state频繁交互且往往又能先做部分reduce的场景中,使用miniBatch能够极大的减少IO,从而成数量级的提升性能。除了上面提到的这些重要的重构和功能点,Blink还实现了完整的SQL DDL,带emit策略的流计算DML,若干重要的SQL功能,以及大量的性能优化策略。有了上面提到的诸多架构和实现上的重构。Blink的SQL/tableAPI在功能和性能方面都取得了脱胎换骨的变化。在批计算方面,首先Blink batch SQL能够完整的跑通TPC-H和TPC-DS,且性能上有着极大的提升。如上图所示,是这次开源的Blink版本和spark 2.3.1的TPC-DS的benchmark性能对比。柱状图的高度代表了运行的总时间,高度越低说明性能越好。可以看出,Blink在TPC-DS上和Spark相比有着非常明显的性能优势。而且这种性能优势随着数据量的增加而变得越来越大。在实际的场景这种优势已经超过 Spark的三倍。在流计算性能上我们也取得了类似的提升。我们线上的很多典型作业,它的性能是原来的3到5倍。在有数据倾斜的场景,以及若干比较有挑战的TPC-H query,流计算性能甚至得到了数十倍的提升。除了标准的Relational SQL API。TableAPI在功能上是SQL的超集,因此在SQL上所有新加的功能,我们在tableAPI也添加了相对应的API。除此之外,我们还在TableAPI上引入了一些新的功能。其中一个比较重要是cache功能。在批计算场景下,用户可以根据需要来cache计算的中间结果,从而避免不必要的重复计算。它极大的增强了interactive programming体验。我们后续会在tableAPI上添加更多有用的功能。其实很多新功能已经在社区展开讨论并被社区接受,例如我们在tableAPI增加了对一整行操作的算子map/flatMap/aggregate/flatAggregate(Flink FLIP29)等等。Hive的兼容性我们这次开源的版本实现了在元数据(meta data)和数据层将Flink和Hive对接和打通。国内外很多公司都还在用 Hive 在做自己的批处理。对于这些用户,现在使用这次Blink开源的版本,就可以直接用Flink SQL去查询Hive的数据,真正能够做到在Hive引擎和Flink引擎之间的自由切换。为了打通元数据,我们重构了Flink catalog的实现,并且增加了两种catalog,一个是基于内存存储的FlinkInMemoryCatalog,另外一个是能够桥接Hive metaStore的HiveCatalog。有了这个HiveCatalog,Flink作业就能读取Hive的metaData。为了打通数据,我们实现了HiveTableSource,使得Flink job可以直接读取Hive中普通表和分区表的数据。因此,通过这个版本,用户可以使用Flink SQL读取已有的Hive meta和data,做数据处理。未来我们将在Flink上继续加大对Hive兼容性的支持,包括支持Hive特有的query,data type,和Hive UDF等等。Zeppelin for Flink为了提供更好的可视化和交互式体验,我们做了大量的工作让Zeppelin能够更好的支持Flink。这些改动有些是在Flink上的,有些是在Zeppelin上的。在这些改动全部推回Flink和Zeppelin社区之前,大家可以使用这个Zeppelin image(具体细节请参考Blink代码里的docs/quickstart/zeppelin_quickstart.md)来测试和使用这些功能。这个用于测试的Zeppelin版本,首先很好的融合和集成了Flink的多种运行模式以及运维界面。使用文本SQL和tableAPI可以自如的查询Flink的static table和dynamic table。此外,针对Flink的流计算的特点,这一版Zeppelin也很好的支持了savepoint,用户可以在界面上暂停作业,然后再从savepoint恢复继续运行作业。在数据展示方面,除了传统的数据分析界面,我们也添加了流计算的翻牌器和时间序列展示等等功能。为了方便用户试用,我们在这一版zeppelin中提供3个built-in的Flink tutorial的例子: 一个是做StreamingETL的例子, 另外两个分别是做Flink Batch,Flink Stream的基础样例。Flink Web我们对Flink Web的易用性与性能等多个方面做了大量的改进,从资源使用、作业调优、日志查询等维度新增了大量功能,使得用户可以更方便的对Flink作业进行运维。在资源使用方面,新增了Cluster、TaskManager与Job三个级别的资源信息,使得资源的申请与使用情况一目了然。作业的拓扑关系及数据流向可以追溯至 Operator 级别,Vertex 增加了InQueue,OutQueue等多项指标,可以方便的追踪数据的反压、过滤及倾斜情况。TaskManager 和 JobManager 的日志功能得到大幅度加强,从Job、Vertex、SubTask 等多个维度都可以关联至对应日志,提供多日志文件访问入口,以及分页展示查询和日志高亮功能。另外,我们使用了较新的Angular 7.0 对Flink web进行了全面重构,页面运行性能有了一倍以上的提升。在大数据量情况下也不会发生页面卡死或者卡顿情况。同时对页面的交互逻辑进行了整体优化,绝大部分关联信息在单个页面就可以完成查询和比对工作,减少了大量不必要的跳转。未来的规划Blink迈出了全面开源的第一步,接下来我们会和社区合作,尽可能以最快的方式将Blink的功能和性能上的优化merge回Flink。本次的开源版本一方面贡献了Blink多年在流计算的积累,另一方面又重磅推出了在批处理上的成果。接下来,我们会持续给Flink社区贡献其他方面的功能。我们期望每过几个月就能看到技术上有一个比较大的亮点贡献到社区。下一个亮点应该是对机器学习的支持。要把机器学习支持好,有一系列的工作要做,包括引擎的功能,性能,和易用性。这里面大部分的工作我们已经开发完成,并且很多功能都已经在阿里巴巴内部服务上线了。除了技术上创新以及新功能之外,Flink的易用性和外围生态也非常重要。我们已经启动了若干这方面的项目,包括Python以及Go等多语言支持,Flink集群管理,Notebook,以及机器学习平台等等。这些项目有些会成为Flink自身的一部分贡献回社区,有些不是。但它们都基于Flink,是Flink生态的一个很好的补充。独立于Flink之外的那些项目,我们都也在认真的考虑开源出来。总之,Blink在开源的第一天起,就已经完全all-in的融入了Flink社区,我们希望所有的开发者看到我们的诚意和决心。未来,无论是功能还是生态,我们都会在Flink社区加大投入,我们也将投入力量做 Flink 社区的运营,让 Flink 真正在中国、乃至全世界大规模地使用起来。我们衷心的希望更多的人加入,一起把Apache Flink开源社区做得更好!本文作者:大沙阅读原文本文来自云栖社区合作伙伴“阿里技术”,如需转载请联系原作者。 ...

January 28, 2019 · 1 min · jiezi

我为什么建议你学Python?献上Python学习大礼包,拿走别客气!

编程语言首推Python,为什么这么说呢?Python在2017年世界脚本语言排行榜中排名第1,也是多领域首选语言,作为一种高级程序语言,其核心设计哲学是代码可读性和语法,能够让程序员用很少的代码来表达自己的想法。打个比方,同样一项工作C语言可能要1000行,java要100行,python可能只要10行。像Google,facebook,Yahoo,YouTube,还有美国宇航局NASA,著名的开源云计算平台openstack,还有国内的豆瓣都是用python写的。目前Python工程师正处于需求量大,人才供不应求的阶段,薪资一路也是水涨船高。北京Python工程师的薪资平均为18880每月,即使是刚刚毕业的应届毕业生,做Python在人工智能领域的薪资也在12500元每月。数据显示,2017年在雇主发布的职位说明中,Python技能需求增速达到174%,居于首位。未来十年将是大数据、人工智能爆发的时代,到时将会有大量的数据需要处理,而Python最大的优势,就是对数据的处理,有着得天独厚的优势。其实无论你的工作是什么,对每个人来说,学习如何编程都是很重要的一件事,编程不仅有助于丰富你的计算思维,还能提高决策性,让你在招聘中脱颖而出。学长为了大家能跟上互联网时代发展的趋势,特意为大家准备了一份Python大礼包供大家学习。这份大礼包有什么?如何获取呢?关注,转发,加QQ群:700341555即可免费领取,希望对你们有帮助!

January 25, 2019 · 1 min · jiezi

【SqlServer】统计索引使用情况解决DB的CPU高和IO高的问题

查看索引情况sp_helpindex 表名;显示索引使用情况user_seeks和user_scans字段都为0的,考虑是否为垃圾索引另外last_user_seek,last_user_scan如果是一个很早的时间,则考虑是否应用变化导致该索引不被使用了SELECT i.name indexname,user_seeks,user_scans,last_user_seek,last_user_scanFROM sys.dm_db_index_usage_stats sINNER JOIN sys.indexes i ONs.object_id = i.object_id ANDs.index_id = i.index_idWHERE database_id = db_id(‘ClntMgr’) AND s.object_id = object_id(‘IDVerifyTbl’);返回指定数据库、表、索引的碎片对于索引类型为HEAP,一般情况下碎片比例会较大原因:1.没有聚集索引的表称为堆,意思是其中存储的数据没有特定的顺序。2.在索引重建或者重新组织时,聚集索引依照聚集键和它重排序的数据页进行排序。3.但是堆不会在索引重建或重新组织期间被重新生成,所以会脱离控制的增长,占用的数据页比必要的多很多。SELECT OBJECT_NAME(f.object_id) 表名,i.name 索引名,f.index_type_desc 索引类型, f.avg_fragmentation_in_percent 碎片比例FROM sys.dm_db_index_physical_stats(DB_ID(‘库名’),OBJECT_ID(‘表名’),NULL,NULL,’limited’) fINNER JOIN sys.indexes i ONi.[object_id] = f.object_id ANDi.index_id = f.index_idORDER BY f.avg_fragmentation_in_percent DESC;在线重新生成表的所有索引ALTER INDEX ALL ON 库名.dbo.表名 REBUILD WITH (ONLINE = ON);重新组织表的所有索引ALTER INDEX all ON 库名.dbo.表名 REORGANIZE;查看表、索引占用磁盘空间情况SELECT name ‘表名’,convert (char(11), row_Count) as ‘数据条数’,(reservedpages * 8) ‘已用空间(KB)’,(pages * 8) ‘数据占用空间(KB)’,(CASE WHEN usedpages > pages THEN (usedpages - pages) ELSE 0 END) * 8 ‘索引占用空间(KB)’,(CASE WHEN reservedpages > usedpages THEN (reservedpages - usedpages) ELSE 0 END) * 8 ‘未用空间(KB)’,LTRIM (STR (reservedpages * 8/1024/1024, 15, 0) + ’ GB’) as ‘已用空间(GB)‘from(SELECT name,SUM (reserved_page_count) as reservedpages ,SUM (used_page_count) as usedpages ,SUM ( CASE WHEN (index_id < 2) THEN (in_row_data_page_count + lob_used_page_count + row_overflow_used_page_count) ELSE lob_used_page_count + row_overflow_used_page_count END ) as pages,SUM ( CASE WHEN (index_id < 2) THEN row_count ELSE 0 END ) as row_CountFROM sys.dm_db_partition_statsinner join sys.objects on sys.dm_db_partition_stats.object_id=sys.objects.object_idwhere type=‘U’group by sys.objects.nameunionSELECT sys.objects.name,sum(reserved_page_count) as reservedpages,sum(used_page_count) as usedpages,0 as pages,0 as row_countfrom sys.objects inner join sys.internal_tables on sys.objects.object_id = sys.internal_tables.parent_id inner join sys.dm_db_partition_stats on sys.dm_db_partition_stats.object_id=sys.internal_tables.object_idwhere sys.internal_tables.internal_type IN (202,204,211,212,213,214,215,216) group by sys.objects.name) torder by ‘已用空间(KB)’ desc查看表缺失的索引信息SELECT DatabaseName = DB_NAME(database_id),[Number Indexes Missing] = count(*)FROM sys.dm_db_missing_index_detailsGROUP BY DB_NAME(database_id)ORDER BY [Number Indexes Missing] DESC;确定开销最高的缺失索引column_usage的取值有如下几种情况:1.EqualityUsage代表在该列上做了相等运算;2.InequalityUsage代表在该列上做了不等运算;3.Include Cloumns代表包含性列此查询的结果(按"总开销"排序)显示最重要缺失索引的成本以及有关数据库/架构/表和缺失索引中所需列的信息。特别是,此脚本可确定哪些列在相等和不相等 SQL 语句中使用。另外,它还报告应将哪些其他列用作缺失索引中的包含性列。使用包含性列可以在不从基础页获取数据的情况下满足更多的覆盖查询,因而使用的 I/O 操作更少,从而提高性能。SELECT TOP 100[Total Cost] = ROUND(avg_total_user_cost * avg_user_impact * (user_seeks + user_scans),0), avg_user_impact, TableName = statement, [EqualityUsage] = equality_columns, [InequalityUsage] = inequality_columns, [Include Cloumns] = included_columnsFROM sys.dm_db_missing_index_groups gINNER JOIN sys.dm_db_missing_index_group_stats s ON s.group_handle = g.index_group_handleINNER JOIN sys.dm_db_missing_index_details d ON d.index_handle = g.index_handleORDER BY [Total Cost] DESC; ...

January 25, 2019 · 2 min · jiezi

数据库条件查询

排序查询/排序查询:对查询结构进行排序关键字:order by 字段名 [ASC|DESC]ASC:升序(从小到大),默认的DESC:降序(从大到小)/查询所有记录的name和price,结构按照价格 从大到小进行排序SELECT pname,price FROM product ORDER BY price DESC;1.查询所有商品信息,使用价格排序(降序)SELECT * FROM product ORDER BY price DESC;2.查询所有商品信息,在价格排序(降序)的基础上,以分类排序(降序)先将查询结果 在价格上进行降序,如果价格相同再以分类进行降序,如果价格不相同,不在对分类进行排序SELECT * FROM product ORDER BY price DESC,category_id DESC;3.显示商品的价格(去重复),并排序(降序)SELECT DISTINCT price FROM product ORDER BY price DESC;聚合查询/聚合查询:以前的查询都是横向记录查询而聚合查询是 纵向个数查询聚合查询的特点:查询到的结构 是单一值聚合函数:count:求记录数的聚合函数,count函数会自动忽略空值以下四个,通常用于数值的计算max:求最大值min:求最小值avg:求平均值sum:求和/查询统计多有商品的个数SELECT COUNT() FROM product;查询统计一共有多少个分类SELECT COUNT(category_id) FROM product;查询所有商品价格的最大值SELECT MAX(price) FROM product;查询所有商品价格的最小值SELECT MIN(price) FROM product;查询所有商品价格的最平均值SELECT AVG(price) FROM product;查询所有商品价格的总值SELECT SUM(price) FROM product;查询所有商品价格的最大值,最小值,平均值,总和SELECT MAX(price) AS 最大值,MIN(price) 最小值,AVG(price) 平均值,SUM(price) 总和 FROM product;分组查询/分组查询:把查询数据分为几个组关键字: group by 字段先分组,再查询,具体查询到的结果数,取决于能分多少组如果分组后 还有需要条件判断 那么请用having关键字where和having的区别:where 写在 基本查询后面的having 写在 分组查询后面的where后面是不能写 count sum等聚合函数having后面可以写count和sum等聚合函数/查询所有以category_id分组后的价格的总和具体有多少个总和 取决于可以分多少组SELECT SUM(price) FROM product GROUP BY category_id;0 统计所有商品的个数SELECT COUNT() FROM product;1 统计各个分类商品的个数SELECT COUNT() FROM product GROUP BY category_id;2 统计各个分类商品的个数,且只显示个数大于1的信息SELECT category_id, COUNT() 个数 FROM product GROUP BY category_id HAVING 个数 > 1;分页查询/分页查询:只查询记录中的一部分关键字: limit 数值1(下标,从0开始),数值2(需要查出来记录数)/SELECT * FROM product LIMIT 0,5;SELECT * FROM product LIMIT 5,5;SELECT * FROM product LIMIT 10,5;查询的公式: 假设每一页我要查询 n条第1页 limit (1-1)*n,n第2页 limit (2-1)n,n第100页 limit (100-1)*n,n第m页 limit (m-1)*n,n ...

January 24, 2019 · 1 min · jiezi

MaxCompute studio与权限那些事儿

背景知识MaxCompute拥有一套强大的安全体系,来保护项目空间里的数据安全。用户在使用MaxCompute时,应理解权限的一些基本概念:权限可分解为三要素,即主体(用户账号或角色),客体(表/资源/函数等),以及操作(与特定客体类型相关),详细参考 https://help.aliyun.com/document_detail/27935.html。授权有两种方式:ACL(基于对象,grant语句)和Policy(基于策略,policy file)。跨项目授权使用package:https://help.aliyun.com/document_detail/34602.html。可通过列标签实现表中列不同的访问控制:https://help.aliyun.com/document_detail/34604.html。为了方便用户更好的理解与使用MaxCompute权限,studio实现了以下功能:权限查看用户在project下有哪些权限,可通过show grants语句获得。studio编辑器已集成权限相关的语句(https://help.aliyun.com/document_detail/27936.html)) 通过快捷键(Windows: Ctrl + J , MAC: Command + J )唤出live template,然后搜索即可:另外,studio对此也提供了图形化的方式显示用户的权限。如下图,点击工具栏上的show privileges按钮,弹出Show user privileges对话框,点击search button, 下方就会显示用户在该project下的权限:json标签页是所有权限的汇总,点击table标签页,则显示用户在table上的权限。鼠标悬停在table标签页上,则提示table的权限说明:权限异常诊断当因缺少权限导致任务报鉴权失败异常时,可通过studio的权限异常诊断,快速寻找解决方案。如下图,点击工具栏上的权限异常诊断按钮,弹出权限异常诊断对话框,在上方文本框中输入完整的鉴权异常信息,然后点击ok按钮,则下方文本框会显示可能的解决方案:权限语句编写MaxCompute提供了一系列的权限语句,studio SQL编辑器已集成这些语句,用户可以利用studio来执行这些语句以完成相应的权限操作。具体的,通过快捷键(Windows: Ctrl + J , MAC: Command + J )唤出live template,然后搜索:另外,在编写授权语句过程中,也支持相应的代码智能提示:授权语句生成除了手写授权语句,studio也支持图形化给用户授权,点击工具栏上的show privileges按钮,弹出Show user privileges对话框,点击Grant privilege标签页,选择好授权对象,下方的SQL窗格就会同步显示其对应的授权语句,然后点击execute grant command,等待后台完成即可。studio中的权限添加MaxCompute project时,studio会尝试列举project下的所有客体到本机,即用户必须有project的list权限。显示表详情时,用户必须具备table的describe权限;显示自定义函数,则必须具备function的read权限。在编辑器中编写SQL,用到的table或function,则也必须有上述读权限。在编辑器中运行某条SQL,则必须具备SQL中表的select权限,同时还必须有project的CreateInstance权限以能提交SQL任务。开发好了UDF,要想发布,则必须有function的write权限。权限好文官方文档 https://help.aliyun.com/document_detail/27926.htmlMaxCompute安全管理指南 https://yq.aliyun.com/articles/686800本文作者:昊一阅读原文本文为云栖社区原创内容,未经允许不得转载。

January 24, 2019 · 1 min · jiezi

一篇文章入门SQL语句

SQL Structured Query Language,是专门用来查询关系型数据库的语言。也就是说不是关系型数据库,就不能用SQL查询了。MySQL的主要学习,其实都是集中在SQL上的。另外一部分,才是数据库的配置和速度优化。SQL语句的分类:DQL: 数据查询语句,如selectDML: 数据操作语句,如update/insert/deleteTPL: 事务处理语句,如commit/rollback/begin transactionDCL: 数据控制语句,即权限管理,如grant/revokeDDL: 数据定义语句,如create/dropCCL: 指针控制语句,通过指针完成表操作,如declare cursor语法结构一些特点:SQL不区分大小写每句话以;结束断行、缩紧无需任何符号,解释器在解释时会把所有空符号都会合并为一个空格。单行注释采用– ,多行注释采用/* … /值的引号可以同时用"和’,但是"兼容性更强。数据库名、表名、列名,都可以用反引号来包起来SQL的语法,是将一条语句拆分成几个组成部分:Clauses:主要命令,如 update/set/whereExpressions: 能产生值的语句,如"Jason",或age + 12。Predicates: 条件判断,即如果True则使用A值,否则B值。Queries: 即Select查询读取数据库的语句。Statements: 即一整条以;结尾的SQL语句SQL Style 编写风格参考 Simon Holywell:SQL Style Guide参考 Simon Holywell:SQL样式指南 · SQL Style Guide常用语句参考W3School:SQL Tutorial服务器查询:– 显示服务器中所有的数据库show databases ;– 进入一个数据库use 数据库名 ;– 显示当前所在数据库的信息select database() ;– 显示当前数据库所有表名 (MySQL)show tables;– 显示指定数据库所有表名 (MySQL)select table_name from information_schema.tables where table_schema=‘数据库名’ and table_type=‘base table’;– 显示指定表格的所有字段名(MySQL)select column_name from information_schema.columns where table_schema=‘数据库名’ and table_name=‘表名’;– 查看某表结构DESC 表名 ;数据库操作:– 创建一个数据库CREATE DATABASE 数据库名 CHARSET=utf-8 ;– 查看数据库的创建信息SHOW CREATE DATABASE 数据库名 ; – 删除数据库DROP DATABASE 数据库名 ;– 或,用反引号包起来DROP DATABASE 数据库名数据表操作:– 显示当前数据库中的所有表show tables ;– 创建表CREATE TABLE 表名 (字段 类型 约束, 字段 类型 约束, 字段 类型 约束….) ;– 如CREATE TABLE staff ( id int primary key not null auto_increment, name varchar(30));– 删除表DROP TABLE 表名 ;– 查看表的创建语句SHOW CREATE TABLE 表名 ;– 查看表结构DESC 表名 ;– 修改表:添加一个字段ALTER TABLE 表名 ADD 字段 类型 约束 ;– 修改表:修改一个字段ALTER TABLE 表名 MODIFY 字段 类型 约束 ;– 修改表:删除一个字段ALTER TABLE 表名 DROP 字段 ;– 添加一条记录INSERT INTO 表名 VALUES(字段1, 字段2, , 字段4) ;– CRUD 增删改查Create / Read / Update / Delete查询 SELECTselect查询永远是SQL中学习时间最长的。因为增删改都是固定模式,语句也很简单。但是查询拥有极多的方式方法和关键字,能够创造超多的组合搭配查询,且每种查询方式效率速度不一。所以SQL主要学的就是SELCT。最简单的select查询:SELECT filed1, field2, field3 FROM table_name ;SELECT filed1 AS age, field2 AS gender, field3 FROM table_name ;SELECT table_name.filed1, table_name.field2, table_name.field3 FROM table_name ;SELECT t.filed1, t.field2, t.field3 FROM table_name as t ;– 删除重复行SELECT DISTINCT field1 FROM table_name ;以下为各种Select语句的方式方法总结。条件 WHERE比较运算符:小于<, 大于>, 小于等于<=, 大于等于>=, 等于=, 不等于!=或<>逻辑运算符:and, or, not模糊查询:like: 用%替换1个字或多个字,_替换1个字,word包含指定的字wordrlike: 正则查询,如^周.$范围查询:field in (v1, v2, v3…), not in (…), between v1 and v2, not between v1 and v2空判断:field is null, field is not nullSELECT filed1, field2 FROM table_name WHERE field3 > 10;… WHERE field1 = “hello” AND field2 = “world” ;… WHERE filed1 LIKE “Hel%” or filed2 LIKE “Hel__” ;… WHERE field1 RLIKE “^He.$” ;… WHERE filed1 IN (12, 18, 19) and NOT IN (30, 40, 50) ;… WHERE field1 BETWEEN 10 AND 20 and NOT BETWEEN 40 AND 50 ;… WHERE filed1 IS NULL OR field3 IS NOT NULL ;排序 ORDER BY… WHERE … ORDER BY age, gender ;… WHERE … ORDER BY age ASC ;… WHERE … ORDER BY age ASC, id DESC, gender ASC ;内置聚合函数 FUNCTIONS内置函数能够处理一些很简单的计算问题。但是切记,查询一个函数值时不要查询其它字段,除非使用GROUP分组等方法。SELECT COUNT() FROM …SELECT MAX(age) FROM …SELECT SUM(age) FROM …SELECT AVG(age) FROM …SELECT ROUND( SUM(age)/COUNT(), 2 ) FROM …SELECT MAX(age) FROM …– 不允许:(因为逻辑不通,需要用到分组才行)– SELECT name, age, ROUND( SUM(age)/COUNT(), 2 ) FROM …分组 GROUP BYSQL分组是一个比较容易混淆的概念。SQL的分组是会完全破坏原先表结构的,然后生成一个统计表,纯粹是为了数量统计用的。分组GROUP单独使用是没什么意义的,除非是和聚合函数Functions一起用。分组的做法是:如果按gender分组,就只把gender一列取出来,做成一个unique的唯一gender列表,如男; 女,然后再创建一列,值对应的是每一种gender的记录条数。如果要查看各组的其它信息,需要用到特殊的函数group_concat(filed1):– 报错:– SELECT name FROM … GROUP BY gender ;– SELECT * FROM … GROUP BY gender ;– 显示gender的每种分组类别以及其下的记录条数!SELECT gender, COUNT() FROM … GROUP BY gender ;– 显示分组的平均SELECT gender, AVG() FROM … GROUP BY gender ;– 显示分组最大值SELECT gender, MAX() FROM … GROUP BY gender ;– 显示分组所包含的其它信息SELECT gender, GROUP_CONCAT(name) FROM … GROUP BY gender ;SELECT gender, GROUP_CONCAT(name, “_”, age) FROM … GROUP BY gender ;分组还有一个配合的关键字having,类似与where的筛选功能:SELECT gender, COUNT() FROM … GROUP BY gender HAVING COUNT() > 3 ;分页 LIMIT– 限制显示结果的条数SELECT … FROM … LIMIT 2 ;– 分页 格式为:LIMIT (第N页-1)每页个数, 每页个数– 第1页,每页2条SELECT … FROM … LIMIT 0,2 ;– 第2页,每页2条SELECT … FROM … LIMIT 2,2 ;– 第3页,每页2条SELECT … FROM … LIMIT 4,2 ;– 第4页,每页2条SELECT … FROM … LIMIT 6,2 ;连接 JOINSQL中的JOIN连接,实际上是用了数学上的集合概念。其中:Inner Join 内连接: 相当于A and B,代表两个集合(表)的交集,即表中某字段匹配上的条目。Full Outer Join 全连接:相当于A or B`,代表两个表的并集,即两表合并所有字段和数据为一个表,未匹配的数据中的空字段以null填充。Right Join 右连接:使用left表里所有数据,而right表中只保留匹配数据,且未匹配数据条目中的right表字段以null填充。Left Join 左连接:使用right表里所有数据,而left表中只保留匹配数据,且未匹配数据条目中的left表字段以null填充。怎么理解Left Join和Right Join?首先,两表匹配,各表都会有各自的未匹配数据条目。那么怎么处理这些未匹配数据,就是这些左右的考量目标。Left join,保留左表的未匹配数据。Right join,保留右表中的未匹配数据。Full join,保留所有未匹配数据。那么保留下的这些未匹配数据,肯定会有几个来自外面的字段是空的,这时候都统一以null填充。另外,SQL连接两表,不光要指定连接方式,还要指定主键-外键的对应关系,使用ON关键字。– 内连接SELECT … FROM tb1 INNER JOIN tb2 ON tb1.key = tb2.id ;– 左连接SELECT … FROM tb1 RIGHT JOIN tb2 …– 右连接SELECT … FROM tb1 LEFT JOIN tb2 …– 全连接 (MySQL不支持)SELECT … FROM tb1 FULL OUTER JOIN tb2 …– 差集连接 (MySQL不支持)SELECT … FROM tb1 FULL OUTER JOIN tb2 ON tb1.key = tb2.id WHERE tb1.key IS NULL OR tb2.id IS NULL;自关联自连接,连接的两个表都是同一个表,同样可以由内连接,外连接各种组合方式,按实际应用去组合。SELECT a., b. FROM tb1 a, tb2 as b WHERE a.[name] = b.[name] 联合 UnionUNION 操作符用于合并两个或多个 SELECT 语句的结果集。使用Union联合的前提条件:UNION 内部的 SELECT 语句必须拥有相同数量的列对应位置的列必须拥有相似的数据类型每条 SELECT 语句中的列的顺序必须相同。列出所有在中国和美国的不同的雇员名:SELECT E_Name FROM Employees_ChinaUNIONSELECT E_Name FROM Employees_USAUNION ALL 命令和 UNION 命令几乎是等效的,不过 UNION ALL 命令会列出所有的值。列出在中国和美国的所有的雇员:SELECT E_Name FROM Employees_ChinaUNION ALLSELECT E_Name FROM Employees_USA子查询实际上就是用( select …)子语句返回一个值,来方便主句查询。相当于bash脚本中的$(…)功能。SELECT … FROM … WHERE height = (SELECT MAX(height) FROM …) ...

January 24, 2019 · 3 min · jiezi

SQL 难点解决:集合及行号

【摘要】SQL 虽然有集合概念,但对于集合运算、特别是有序集合运算,提供的支持却很有限,经常要采用很费解的思路才能完成,计算效率也不佳。而集算器 SPL 在方面则要直观许多,可以按自然思维习惯写出运算。这里对 SQL 和集算器 SPL 在集合运算和行号相关运算方面进行了对比,如果需要了解更多,请前往乾学院:SQL 难点解决:集合及行号!1、 和集示例 1:重叠部分不重复计数时求多个时间段包含的总天数MySQL8:with recursive t(start,end) as (select date'2010-01-07’,date'2010-01-9’union all select date'2010-01-15’,date'2010-01-16’union all select date'2010-01-07’,date'2010-01-12’union all select date'2010-01-08’,date'2010-01-11’),t1(d,end) as (select start,end from tunion all select d+1,end from t1 where dselect count(distinct d) from t1;说明:此例先将各时间段转成时间段内所有日子对应的日期,然后再求不同日期的个数集算器SPL:A3: 对 A2 中的每一个时间段构造从 start 到 end 的日期序列A4: 求 A3 中所有日期序列的和A5: 求 A4 中不重复日期的个数2、 差集示例 1:列出英语人口和法语人口均超过 5% 的国家MySQL8:with t1(lang) as (select ‘English’ union all select ‘French’)select name from world.country cwhere not exists(select * from t1 where lang not in (select language from world.countrylanguage where percentage>=5 and countrycode=c.code));说明:此SQL只是演示通过双重否定实现差集为空集算器SPL:A4: 选出[“English”,”French”]与本组语言集合的差为空的组,意思就是选出语言集合包含English和French的组3、 交集示例 1:列出英语人口、法语人口、西班牙语人口分别超过 0.3%、0.2%、0.1% 的国家代码MySQL8:with t1 as (select countrycode from world.countrylanguage where language=‘English’ and percentage>0.3),t2 as (select countrycode from world.countrylanguage where language=‘French’ and percentage>0.2),t3 as (select countrycode from world.countrylanguage where language=‘Spanish’ and percentage>0.1)select countrycodefrom t1 join t2 using(countrycode) join t3 using(countrycode);说明:此例只是演示如何求解多个集合的交集集算器SPL:A3: 按次序依次查询英语人口超0.3%、法语人口超0.2%、西班牙语超0.1%的国家代码,并转成序列A5: A3中所有序列交集4、 根据行号取数据示例 1:计算招商银行 (600036) 2017 年第 3 个交易日和倒数第 3 个交易日的交易信息MySQL8:with t as (select *, row_number() over(order by tdate) rn from stktrade where sid=‘600036’ and tdate between ‘2017-01-01’ and ‘2017-12-31’)select tdate,open,close,volume from t where rn=3union allselect tdate,open,close,volume from t where rn=(select max(rn)-2 from t);集算器SPL:A3: 第 3 条记录和倒数第 3 条记录的和集示例2:计算招商银行(600036)最近20个交易日的平均收盘价MySQL8:with t as (select *, row_number() over(order by tdate desc) rn from stktrade where sid=‘600036’)select avg(close) avg20 from t where rn<=20;集算器SPL:A2: 将600036的交易记录按日期排序A3: 取从倒数20条到末尾的所有记录A4: 求A3中所有记录收盘价的平均值5、 求满足条件的记录的行号示例 1:计算招商银行 (600036)2017 年经过多少交易日收盘价达到 25 元MySQL8:with t as (select *, row_number() over(order by tdate) rn from stktrade where sid=‘600036’ and tdate between ‘2017-01-01’ and ‘2017-12-31’)select min(rn) from t where close>=25;集算器SPL:A3: 从前往后查找第 1 个收盘价达到 25 元的记录位置示例2:计算格力电器(000651) 2017年涨幅(考虑停牌)MySQL8:with t as (select * from stktrade where sid=‘000651’),t1(d) as (select max(tdate) from t where tdate<‘2017-01-01’),t2(d) as (select max(tdate) from t where tdate<‘2018-01-01’)select s2.close/s1.close-1 risefrom (select * from t,t1 where tdate=d) s1,(select * from t,t2 where tdate=d) s2;集算器SPL:A2: 数据按交易日从小到大排序A3: 从后往前查找交易日在2017-01-01之前的最后一条记录在序列中的行号A4: 求2016年收盘价A5: 求2017年收盘价,其中A2.m(-1)取倒数第1条记录,即2017年最后一个交易日对应的记录示例3:列出2017年信息发展(300469)交易量超过250万股时的交易信息及各日涨幅(考虑停牌)MySQL8:with t as (select *, row_number() over(order by tdate) rnfrom stktrade where sid=‘300469’ and tdate<=date ‘2017-12-31’),t1 as (select * from t where tdate>=date'2017-01-01’ and volume>=2500000)select t1.tdate, t1.close, t.volume, t1.close/t.close-1 risefrom t1 join t on t1.rn=t.rn+1;集算器SPL:A3: 求出2017年交易量超250万股所有记录的行号A4: 根据行号计算相应的日期、收盘价、交易量、涨幅6、 求最大值或最小值所在记录的行号示例 1:计算招商银行 (600036) 2017 年最早的最低价与最早的最高价间隔多少交易日MySQL8:with t as (select *, row_number() over(order by tdate) rn from stktrade where sid=‘600036’ and tdate between ‘2017-01-01’ and ‘2017-12-31’),t1 as (select * from t where close=(select min(close) from t)),t2 as (select * from t where close=(select max(close) from t))select abs(cast(min(t1.rn) as signed)-cast(min(t2.rn) as signed)) intevalfrom t1,t2;集算器SPL:A3: 从前往后找最大收盘价在序列中的行号A4: 从前往后找最小收盘价在序列中的行号示例2:计算招商银行 (600036) 2017 年最后的最低价与最后的最高价间隔多少交易日MySQL8:with t as (select *, row_number() over(order by tdate) rn from stktrade where sid=‘600036’ and tdate between ‘2017-01-01’ and ‘2017-12-31’),t1 as (select * from t where close=(select min(close) from t)),t2 as (select * from t where close=(select max(close) from t))select abs(cast(max(t1.rn) as signed)-cast(max(t2.rn) as signed)) intevalfrom t1,t2;集算器SPL:A3: 从后往前找最大收盘价在序列中的行号A4: 从后往前找最小收盘价在序列中的行号7、 有序集合间的对位计算示例 1:求 2018 年 3 月 6 日到 8 日创业板指 (399006) 对深证成指 (399001) 的每日相对收益率MySQL8:with t1 as (select *,close/lag(close) over(order by tdate) rise from stktrade where sid=‘399006’ and tdate between ‘2018-03-05’ and ‘2018-03-08’),t2 as (select *, close/lag(close) over(order by tdate) rise from stktrade where sid=‘399001’ and tdate between ‘2018-03-05’ and ‘2018-03-08’)select t1.rise-t2.risefrom t1 join t2 using(tdate)where t1.rise is not null;集算器SPL:A2: 依次查询399006和399001从2018年3月5日到8日的交易数据A4: 依次计算A2中2个序表从第2条记录到第4条记录的涨幅,也就是399006和399001从2018年3月6日到8日的每天涨幅A5: 对位相减,即可算出每日相对收益率 ...

January 23, 2019 · 3 min · jiezi

多表查询

微信公众号:菜鸟永恒第1章 多表关系实战1.1 实战1:省和市 方案1:多张表,一对多 方案2:一张表,自关联一对多1.2 实战2:用户和角色 (比如演员和扮演人物) 多对多关系1.3 实战3:角色和权限 (比如公司职位和开除等权限) 多对多关系1.4 实战4:客户和联系人(可选) 一对多:一个客户服务于多个联系人第2章 多表查询CREATE TABLE category ( cid int PRIMARY KEY , cname VARCHAR(50));CREATE TABLE products( pid int PRIMARY KEY , pname VARCHAR(50), price INT, flag VARCHAR(2), #是否上架标记为:1表示上架、0表示下架 category_id int, CONSTRAINT products_fk FOREIGN KEY (category_id) REFERENCES category (cid));2.1 初始化数据分类INSERT INTO category(cid,cname) VALUES(‘1’,‘家电’);INSERT INTO category(cid,cname) VALUES(‘2’,‘服饰’);INSERT INTO category(cid,cname) VALUES(‘3’,‘化妆品’);商品INSERT INTO products(pid, pname,price,flag,category_id) VALUES(‘1’,‘联想’,5000,‘1’,1);INSERT INTO products(pid, pname,price,flag,category_id) VALUES(‘2’,‘海尔’,3000,‘1’,1);INSERT INTO products(pid, pname,price,flag,category_id) VALUES(‘3’,‘雷神’,5000,‘1’,1);INSERT INTO products (pid, pname,price,flag,category_id) VALUES(‘4’,‘JACK JONES’,800,‘1’,2);INSERT INTO products (pid, pname,price,flag,category_id) VALUES(‘5’,‘真维斯’,200,‘1’,2);INSERT INTO products (pid, pname,price,flag,category_id) VALUES(‘6’,‘花花公子’,440,‘1’,2);INSERT INTO products (pid, pname,price,flag,category_id) VALUES(‘7’,‘劲霸’,2000,‘1’,2);INSERT INTO products (pid, pname,price,flag,category_id) VALUES(‘8’,‘香奈儿’,800,‘1’,3);INSERT INTO products (pid, pname,price,flag,category_id) VALUES(‘9’,‘相宜本草’,200,‘1’,3);2.2 多表查询交叉连接查询(基本不会使用-得到的是两个表的乘积) [了解] 语法:select * from A,B;内连接查询(使用的关键字 inner join – inner可以省略) 隐式内连接:select * from A,B where 条件; 显示内连接:select * from A inner join B on 条件;外连接查询(使用的关键字 outer join – outer可以省略) 左外连接:left outer join select * from A left outer join B on 条件; 右外连接:right outer join select * from A right outer join B on 条件;1.查询哪些分类的商品已经上架隐式内连接SELECT * FROM category c , products pWHERE c.cid = p.category_id and p.flag = ‘1’;内连接SELECT * FROM category cINNER JOIN products p ON c.cid = p.category_id WHERE p.flag = ‘1’;2.查询所有分类的商品个数左外连接INSERT INTO category(cid,cname) VALUES(4,‘奢侈品’);SELECT cname,COUNT(category_id) FROM category cLEFT OUTER JOIN products p ON c.cid = p.category_id GROUP BY cname;2.3 子查询子查询:一条select语句结果作为另一条select语法一部分(查询条件,查询结果,表等)。select ….查询字段 … from …表.. where … 查询条件3 子查询, 查询“化妆品”分类商品详情隐式内连接SELECT * FROM products p , category cWHERE p.category_id=c.cid AND c.cname = ‘化妆品’;子查询作为查询条件SELECT * FROM products pWHERE p.category_id = ( SELECT c.cid FROM category c WHERE c.cname=‘化妆品’);##作为另一张表SELECT * FROM products p , (SELECT * FROM category WHERE cname=‘化妆品’) c WHERE p.category_id = c.cid;查询“化妆品”和“家电”两个分类商品详情SELECT * FROM products pWHERE p.category_id in( SELECT c.cid FROM category c WHERE c.cname=‘化妆品’ or c.name=‘家电’);欢迎关注公众号:菜鸟永恒 点滴记录,共同进步。立志想成为大牛的菜鸟一枚,将会记录Java技术知识,,不妨来扫描二维码关注一下! 欢迎加小编微信 拉你进新建的技术交流群听说关注的人都变美变帅了觉得我的文章写得不错,不妨点一下好看并分享给朋友! ...

January 22, 2019 · 2 min · jiezi

【kafka KSQL】游戏日志统计分析(1)

【kafka KSQL】游戏日志统计分析(1)以游戏结算日志为例,展示利用KSQL对日志进行统计分析的过程。启动confluentcd ~/Documents/install/confluent-5.0.1/bin/confluent start查看kafka主题列表bin/kafka-topics –list –zookeeper localhost:2181创建接受游戏结算日志的topicbin/kafka-topics –create –zookeeper localhost:2181 –replication-factor 1 –partitions 4 –topic score-normalized使用生产者命令行工具往topic中写日志bin/kafka-console-producer –broker-list localhost:9092 –topic score-normalized> {“cost”:7, “epoch”:1512342568296,“gameId”:“2017-12-04_07:09:28_高手1区_200_015_185175”,“gameType”:“situan”,“gamers”: [{“balance”:4405682,“delta”:-60,“username”:“0791754000”}, {“balance”:69532,“delta”:-60,“username”:“70837999”}, {“balance”:972120,“delta”:-60,“username”:“abc6378303”}, {“balance”:23129,“delta”:180,“username”:“a137671268”}],“reason”:“xiayu”}使用消费者命令行工具查看日志是否正常写入bin/kafka-console-consumer –bootstrap-server localhost:9092 –topic score-normalized –from-beginning;; 可以看到{“cost”:7, “epoch”:1512342568296,“gameId”:“2017-12-04_07:09:28_高手1区_200_015_185175”,“gameType”:“situan”,“gamers”: [{“balance”:4405682,“delta”:-60,“username”:“0791754000”}, {“balance”:69532,“delta”:-60,“username”:“70837999”}, {“balance”:972120,“delta”:-60,“username”:“abc6378303”}, {“balance”:23129,“delta”:180,“username”:“a137671268”}],“reason”:“xiayu”}启动KSQL客户端bin/ksql http://localhost:8088可以看到ksql启动后的图标,和操作终端。ksql终端查看kafka topic列表ksql> show topics;打印topic中的消息PRINT ‘score-normalized’;可以看到:Format:STRING19-1-5 下午11时59分31秒 , NULL , {“cost”:7, “epoch”:1512342568296,“gameId”:“2017-12-04_07:09:28_\xE9\xAB\x98\xE6\x89\x8B1\xE5\x8C\xBA_200_015_185175”,“gameType”:“situan”,“gamers”: [{“balance”:4405682,“delta”:-60,“username”:“0791754000”}, {“balance”:69532,“delta”:-60,“username”:“70837999”}, {“balance”:972120,“delta”:-60,“username”:“abc6378303”}, {“balance”:23129,“delta”:180,“username”:“a137671268”}],“reason”:“xiayu”}其中:第一个逗号19-1-5 下午11时59分31秒表示消息时间。第二个逗号NULL为消息的Key,因为是从kafka-console-producer推送的,默认为NULL。后面的就是推送过来的消息内容。从topic score-normalized创建一个StreamCREATE STREAM SCORE_EVENT \ (epoch BIGINT, \ gameType VARCHAR, \ cost INTEGER, \ gamers ARRAY< \ STRUCT< \ username VARCHAR, \ balance BIGINT, \ delta BIGINT \ > \ >, \ gameId VARCHAR, \ tax BIGINT, \ reason VARCHAR) \ WITH ( KAFKA_TOPIC=‘score-normalized’, \ VALUE_FORMAT=‘JSON’);删除一个STREAMDROP STREAM stream_name ;如果有查询语句在查询该流,则会出现错误:Cannot drop USER_SCORE_EVENT. The following queries read from this source: []. The following queries write into this source: [CSAS_USER_SCORE_EVENT_2, InsertQuery_4, InsertQuery_5, InsertQuery_3]. You need to terminate them before dropping USER_SCORE_EVENT.需要用TERMINATE命令停止这些查询语句,然后再删除流:TERMINATE CSAS_USER_SCORE_EVENT_2;TERMINATE InsertQuery_4;从最早记录开始查询ksql> SET ‘auto.offset.reset’ = ’earliest’;从Stream中查询所有数据ksql> SELECT * FROM SCORE_EVENT;可以看到:1546702389664 | null | 1512342568296 | situan | 7 | [{USERNAME=0791754000, BALANCE=4405682, DELTA=-60}, {USERNAME=70837999, BALANCE=69532, DELTA=-60}, {USERNAME=abc6378303, BALANCE=972120, DELTA=-60}, {USERNAME=a137671268, BALANCE=23129, DELTA=180}] | 2017-12-04_07:09:28_高手1区_200_015_185175 | null | xiayu其中:第1列为记录的时间戳。第2列为记录的key。第3列以后就是消息中的各个字段的值,对应创建流时的顺序。倒数第2列的null,是因为消息中tax字段不存在。统计2017-12-04日的对局总数;; 增加一个game_date字段,用于统计CREATE STREAM SCORE_EVENT_WITH_DATE AS \ SELECT SUBSTRING(gameId, 0, 10) AS game_date, * \ FROM SCORE_EVENT; SELECT game_date, COUNT() \ FROM SCORE_EVENT_WITH_DATE \ WHERE game_date = ‘2017-12-04’ AND reason = ‘game’ \ GROUP BY game_date;目前KSQL还不支持类似下面的查询:SELECT COUNT() \ FROM SCORE_EVENT \ WHERE gameId LIKE ‘2017-12-04_%’;统计参与对局的总玩家数(去重)因为一条日志中包含多个玩家的对局信息,所以想法把每个玩家拆分成单独的事件整合各个玩家的事件到一个统一的流USER_SCORE_EVENT:CREATE STREAM USER_SCORE_EVENT AS \ SELECT epoch, gameType, cost, gameId, tax, reason, gamers[0]->username AS username, gamers[0]->balance AS balance, gamers[0]->delta AS delta \ FROM SCORE_EVENT; INSERT INTO USER_SCORE_EVENT \ SELECT epoch, gameType, cost, gameId, tax, reason, gamers[1]->username AS username, gamers[1]->balance AS balance, gamers[1]->delta AS delta \ FROM SCORE_EVENT; INSERT INTO USER_SCORE_EVENT \ SELECT epoch, gameType, cost, gameId, tax, reason, gamers[2]->username AS username, gamers[2]->balance AS balance, gamers[2]->delta AS delta \ FROM SCORE_EVENT; INSERT INTO USER_SCORE_EVENT \ SELECT epoch, gameType, cost, gameId, tax, reason, gamers[3]->username AS username, gamers[3]->balance AS balance, gamers[3]->delta AS delta \ FROM SCORE_EVENT;统计各个玩家总的对局数、输赢总数、贡献的总税收,并以此创建一个表USER_SCORE_TABLE:CREATE TABLE USER_SCORE_TABLE AS \ SELECT username, COUNT(*) AS game_count, SUM(delta) AS delta_sum, SUM(tax) AS tax_sum \ FROM USER_SCORE_EVENT \ WHERE reason = ‘game’ \ GROUP BY username;查看USER_SCORE_TABLE所有数据:ksql> SELECT * FROM USER_SCORE_TABLE;1546709338711 | 70837999 | 70837999 | 4 | -240 | 01546709352758 | 0791754000 | 0791754000 | 4 | -240 | 01546709338711 | a137671268 | a137671268 | 4 | 720 | 01546709352758 | abc6378303 | abc6378303 | 4 | -240 | 0查询某个玩家的对局数、输赢总数、贡献的总税收:ksql> SELECT * FROM USER_SCORE_TABLE WHERE username = ‘70837999’;输出:1546709338711 | 70837999 | 70837999 | 4 | -240 | 0统计玩家总数(去重)添加一个傀儡列用于统计:CREATE TABLE USER_SCORE_WITH_TAG AS \ SELECT 1 AS tag, * FROM USER_SCORE_TABLE;统计去重后的玩家总数SELECT tag, COUNT(username) \FROM USER_SCORE_WITH_TAG \GROUP BY tag;续KSQL WINDOW 功能。 ...

January 6, 2019 · 2 min · jiezi

TiDB 源码阅读系列文章(二十三)Prepare/Execute 请求处理

作者:苏立在之前的一篇文章《TiDB 源码阅读系列文章(三)SQL 的一生》中,我们介绍了 TiDB 在收到客户端请求包时,最常见的 Command — COM_QUERY 的请求处理流程。本文我们将介绍另外一种大家经常使用的 Command — Prepare/Execute 请求在 TiDB 中的处理过程。Prepare/Execute Statement 简介首先我们先简单回顾下客户端使用 Prepare 请求过程:客户端发起 Prepare 命令将带 “?” 参数占位符的 SQL 语句发送到数据库,成功后返回 stmtID。具体执行 SQL 时,客户端使用之前返回的 stmtID,并带上请求参数发起 Execute 命令来执行 SQL。不再需要 Prepare 的语句时,关闭 stmtID 对应的 Prepare 语句。相比普通请求,Prepare 带来的好处是:减少每次执行经过 Parser 带来的负担,因为很多场景,线上运行的 SQL 多是相同的内容,仅是参数部分不同,通过 Prepare 可以通过首次准备好带占位符的 SQL,后续只需要填充参数执行就好,可以做到“一次 Parse,多次使用”。在开启 PreparePlanCache 后可以达到“一次优化,多次使用”,不用进行重复的逻辑和物理优化过程。更少的网络传输,因为多次执行只用传输参数部分,并且返回结果 Binary 协议。因为是在执行的同时填充参数,可以防止 SQL 注入风险。某些特性比如 serverSideCursor 需要是通过 Prepare statement 才能使用。TiDB 和 MySQL 协议 一样,对于发起 Prepare/Execute 这种使用访问模式提供两种方式:Binary 协议:即上述的使用 COM_STMT_PREPARE,COM_STMT_EXECUTE,COM_STMT_CLOSE 命令并且通过 Binary 协议获取返回结果,这是目前各种应用开发常使用的方式。文本协议:使用 COM_QUERY,并且用 PREPARE,EXECUTE,DEALLOCATE PREPARE 使用文本协议获取结果,这个效率不如上一种,多用于非程序调用场景,比如在 MySQL 客户端中手工执行。下面我们主要以 Binary 协议来看下 TiDB 的处理过程。文本协议的处理与 Binary 协议处理过程比较类似,我们会在后面简要介绍一下它们的差异点。COM_STMT_PREPARE首先,客户端发起 COM_STMT_PREPARE,在 TiDB 收到后会进入 clientConn#handleStmtPrepare,这个函数会通过调用 TiDBContext#Prepare 来进行实际 Prepare 操作并返回 结果 给客户端,实际的 Prepare 处理主要在 session#PrepareStmt 和 PrepareExec 中完成:调用 Parser 完成文本到 AST 的转换,这部分可以参考《TiDB 源码阅读系列文章(五)TiDB SQL Parser 的实现》。使用名为 paramMarkerExtractor 的 visitor 从 AST 中提取 “?” 表达式,并根据出现位置(offset)构建排序 Slice,后面我们会看到在 Execute 时会通过这个 Slice 值来快速定位并替换 “?” 占位符。检查参数个数是否超过 Uint16 最大值(这个是 协议限制,对于参数只提供 2 个 Byte)。进行 Preprocess, 并且创建 LogicPlan, 这部分实现可以参考之前关于 逻辑优化的介绍,这里生成 LogicPlan 主要为了获取并检查组成 Prepare 响应中需要的列信息。生成 stmtID,生成的方式是当前会话中的递增 int。保存 stmtID 到 ast.Prepared (由 AST,参数类型信息,schema 版本,是否使用 PreparedPlanCache 标记组成) 的映射信息到 SessionVars#PreparedStmts 中供 Execute 部分使用。保存 stmtID 到 TiDBStatement (由 stmtID,参数个数,SQL 返回列类型信息,sendLongData 预 BoundParams 组成)的映射信息保存到 TiDBContext#stmts。在处理完成之后客户端会收到并持有 stmtID 和参数类型信息,返回列类型信息,后续即可通过 stmtID 进行执行时,server 可以通过 6、7 步保存映射找到已经 Prepare 的信息。COM_STMT_EXECUTEPrepare 成功之后,客户端会通过 COM_STMT_EXECUTE 命令请求执行,TiDB 会进入 clientConn#handleStmtExecute,首先会通过 stmtID 在上节介绍中保存的 TiDBContext#stmts 中获取前面保存的 TiDBStatement,并解析出是否使用 userCursor 和请求参数信息,并且调用对应 TiDBStatement 的 Execute 进行实际的 Execute 逻辑:生成 ast.ExecuteStmt 并调用 planer.Optimize 生成 plancore.Execute,和普通优化过程不同的是会执行 Exeucte#OptimizePreparedPlan。使用 stmtID 通过 SessionVars#PreparedStmts 获取到到 Prepare 阶段的 ast.Prepared 信息。使用上一节第 2 步中准备的 prepared.Params 来快速查找并填充参数值;同时会保存一份参数到 sessionVars.PreparedParams 中,这个主要用于支持 PreparePlanCache 延迟获取参数。判断对比判断 Prepare 和 Execute 之间 schema 是否有变化,如果有变化则重新 Preprocess。之后调用 Execute#getPhysicalPlan 获取物理计划,实现中首先会根据是否启用 PreparedPlanCache 来查找已缓存的 Plan,本文后面我们也会专门介绍这个。在没有开启 PreparedPlanCache 或者开启了但没命中 cache 时,会对 AST 进行一次正常的 Optimize。在获取到 PhysicalPlan 后就是正常的 Executing 执行。COM_STMT_CLOSE在客户不再需要执行之前的 Prepared 的语句时,可以通过 COM_STMT_CLOSE 来释放服务器资源,TiDB 收到后会进入 clientConn#handleStmtClose,会通过 stmtID 在 TiDBContext#stmts 中找到对应的 TiDBStatement,并且执行 Close 清理之前的保存的 TiDBContext#stmts 和 SessionVars#PrepareStmts,不过通过代码我们看到,对于前者的确直接进行了清理,对于后者不会删除而是加入到 RetryInfo#DroppedPreparedStmtIDs 中,等待当前事务提交或回滚才会从 SessionVars#PrepareStmts 中清理,之所以延迟删除是由于 TiDB 在事务提交阶段遇到冲突会根据配置决定是否重试事务,参与重试的语句可能只有 Execute 和 Deallocate,为了保证重试还能通过 stmtID 找到 prepared 的语句 TiDB 目前使用延迟到事务执行完成后才做清理。其他 COM_STMT除了上面介绍的 3 个 COM_STMT,还有另外几个 COM_STMT_SEND_LONG_DATA,COM_STMT_FETCH,COM_STMT_RESET 也会在 Prepare 中使用到。COM_STMT_SEND_LONG_DATA某些场景我们 SQL 中的参数是 TEXT,TINYTEXT,MEDIUMTEXT,LONGTEXT and BLOB,TINYBLOB,MEDIUMBLOB,LONGBLOB 列时,客户端通常不会在一次 Execute 中带大量的参数,而是单独通过 COM_SEND_LONG_DATA 预先发到 TiDB,最后再进行 Execute。TiDB 的处理在 client#handleStmtSendLongData,通过 stmtID 在 TiDBContext#stmts 中找到 TiDBStatement 并提前放置 paramID 对应的参数信息,进行追加参数到 boundParams(所以客户端其实可以多次 send 数据并追加到一个参数上),Execute 时会通过 stmt.BoundParams() 获取到提前传过来的参数并和 Execute 命令带的参数 一起执行,在每次执行完成后会重置 boundParams。COM_STMT_FETCH通常的 Execute 执行后,TiDB 会向客户端持续返回结果,返回速率受 max_chunk_size 控制(见《TiDB 源码阅读系列文章(十)Chunk 和执行框架简介》), 但实际中返回的结果集可能非常大。客户端受限于资源(一般是内存)无法一次处理那么多数据,就希望服务端一批批返回,COM_STMT_FETCH 正好解决这个问题。它的使用首先要和 COM_STMT_EXECUTE 配合(也就是必须使用 Prepared 语句执行), handleStmtExeucte 请求协议 flag 中有标记要使用 cursor,execute 在完成 plan 拿到结果集后并不立即执行而是把它缓存到 TiDBStatement 中,并立刻向客户端回包中带上列信息并标记 ServerStatusCursorExists,这部分逻辑可以参看 handleStmtExecute。客户端看到 ServerStatusCursorExists 后,会用 COM_STMT_FETCH 向 TiDB 拉去指定 fetchSize 大小的结果集,在 connClient#handleStmtFetch 中,会通过 session 找到 TiDBStatement 进而找到之前缓存的结果集,开始实际调用执行器的 Next 获取满足 fetchSize 的数据并返回客户端,如果执行器一次 Next 超过了 fetchSize 会只返回 fetchSize 大小的数据并把剩下的数据留着下次再给客户端,最后对于结果集最后一次返回会标记 ServerStatusLastRowSend 的 flag 通知客户端没有后续数据。COM_STMT_RESET主要用于客户端主动重置 COM_SEND_LONG_DATA 发来的数据,正常 COM_STMT_EXECUTE 后会自动重置,主要针对客户端希望主动废弃之前数据的情况,因为 COM_STMT_SEND_LONG_DATA 是一直追加的操作,客户端某些场景需要主动放弃之前预存的参数,这部分逻辑主要位于 connClient#handleStmtReset 中。Prepared Plan Cache通过前面的解析过程我们看到在 Prepare 时完成了 AST 转换,在之后的 Execute 会通过 stmtID 找之前的 AST 来进行 Plan 跳过每次都进行 Parse SQL 的开销。如果开启了 Prepare Plan Cache,可进一步在 Execute 处理中重用上次的 PhysicalPlan 结果,省掉查询优化过程的开销。TiDB 可以通过 修改配置文件 开启 Prepare Plan Cache, 开启后每个新 Session 创建时会初始化一个 SimpleLRUCache 类型的 preparedPlanCache 用于保存用于缓存 Plan 结果,缓存的 key 是 pstmtPlanCacheKey(由当前 DB,连接 ID,statementID,schemaVersion, snapshotTs,sqlMode,timezone 组成,所以要命中 plan cache 这以上元素必须都和上次缓存的一致),并根据配置的缓存大小和内存大小做 LRU。在 Execute 的处理逻辑 PrepareExec 中除了检查 PreparePlanCache 是否开启外,还会判断当前的语句是否能使用 PreparePlanCache。只有 SELECT,INSERT,UPDATE,DELETE 有可能可以使用 PreparedPlanCache 。并进一步通过 cacheableChecker visitor 检查 AST 中是否有变量表达式,子查询,“order by ?",“limit ?,?” 和 UnCacheableFunctions 的函数调用等不可以使用 PlanCache 的情况。如果检查都通过则在 Execute#getPhysicalPlan 中会用当前环境构建 cache key 查找 preparePlanCache。未命中 Cache我们首先来看下没有命中 Cache 的情况。发现没有命中后会用 stmtID 找到的 AST 执行 Optimize,但和正常执行 Optimize 不同对于 Cache 的 Plan, 我需要对 “?” 做延迟求值处理, 即将占位符转换为一个 function 做 Plan 并 Cache, 后续从 Cache 获取后 function 在执行时再从具体执行上下文中实际获取执行参数。回顾下构建 LogicPlan 的过程中会通过 expressionRewriter 将 AST 转换为各类 expression.Expression,通常对于 ParamMarkerExpr 会重写为 Constant 类型的 expression,但如果该条 stmt 支持 Cache 的话会重写为 Constant 并带上一个特殊的 DeferredExpr 指向一个 GetParam 的函数表达式,而这个函数会在执行时实际从前面 Execute 保存到 sessionVars.PreparedParams 中获取,这样就做到了 Plan 并 Cache 一个参数无关的 Plan,然后实际执行的时填充参数。新获取 Plan 后会保存到 preparedPlanCache 供后续使用。命中 Cache让我们回到 getPhysicalPlan,如果 Cache 命中在获取 Plan 后我们需要重新 build plan 的 range,因为前面我们保存的 Plan 是一个带 GetParam 的函数表达式,而再次获取后,当前参数值已经变化,我们需要根据当前 Execute 的参数来重新修正 range,这部分逻辑代码位于 Execute#rebuildRange 中,之后就是正常的执行过程了。文本协议的 Prepared前面主要介绍了二进制协议的 Prepared 执行流程,还有一种执行方式是通过二进制协议来执行。客户端可以通过 COM_QUREY 发送:PREPARE stmt_name FROM prepareable_stmt;EXECUTE stmt_name USING @var_name1, @var_name2,…DEALLOCTE PREPARE stmt_name来进行 Prepared,TiDB 会走正常 文本 Query 处理流程,将 SQL 转换 Prepare,Execute,Deallocate 的 Plan, 并最终转换为和二进制协议一样的 PrepareExec,ExecuteExec,DealocateExec 的执行器进行执行。写在最后Prepared 是提高程序 SQL 执行效率的有效手段之一。熟悉 TiDB 的 Prepared 实现,可以帮助各位读者在将来使用 Prepared 时更加得心应手。另外,如果有兴趣向 TiDB 贡献代码的读者,也可以通过本文更快的理解这部分的实现。 ...

January 4, 2019 · 3 min · jiezi

MySQL 索引及查询优化总结

本文由云+社区发表文章《MySQL查询分析》讲述了使用MySQL慢查询和explain命令来定位mysql性能瓶颈的方法,定位出性能瓶颈的sql语句后,则需要对低效的sql语句进行优化。本文主要讨论MySQL索引原理及常用的sql查询优化。一个简单的对比测试前面的案例中,c2c_zwdb.t_file_count表只有一个自增id,FFileName字段未加索引的sql执行情况如下:在上图中,type=all,key=null,rows=33777。该sql未使用索引,是一个效率非常低的全表扫描。如果加上联合查询和其他一些约束条件,数据库会疯狂的消耗内存,并且会影响前端程序的执行。这时给FFileName字段添加一个索引:alter table c2c_zwdb.t_file_count add index index_title(FFileName);再次执行上述查询语句,其对比很明显:在该图中,type=ref,key=索引名(index_title),rows=1。该sql使用了索引index_title,且是一个常数扫描,根据索引只扫描了一行。比起未加索引的情况,加了索引后,查询效率对比非常明显。MySQL索引通过上面的对比测试可以看出,索引是快速搜索的关键。MySQL索引的建立对于MySQL的高效运行是很重要的。对于少量的数据,没有合适的索引影响不是很大,但是,当随着数据量的增加,性能会急剧下降。如果对多列进行索引(组合索引),列的顺序非常重要,MySQL仅能对索引最左边的前缀进行有效的查找。下面介绍几种常见的MySQL索引类型。索引分单列索引和组合索引。单列索引,即一个索引只包含单个列,一个表可以有多个单列索引,但这不是组合索引。组合索引,即一个索引包含多个列。1、MySQL索引类型(1) 主键索引 PRIMARY KEY它是一种特殊的唯一索引,不允许有空值。一般是在建表的时候同时创建主键索引。当然也可以用 ALTER 命令。记住:一个表只能有一个主键。(2) 唯一索引 UNIQUE唯一索引列的值必须唯一,但允许有空值。如果是组合索引,则列值的组合必须唯一。可以在创建表的时候指定,也可以修改表结构,如:ALTER TABLE table_name ADD UNIQUE (column)(3) 普通索引 INDEX这是最基本的索引,它没有任何限制。可以在创建表的时候指定,也可以修改表结构,如:ALTER TABLE table_name ADD INDEX index_name (column)(4) 组合索引 INDEX组合索引,即一个索引包含多个列。可以在创建表的时候指定,也可以修改表结构,如:ALTER TABLE table_name ADD INDEX index_name(column1, column2, column3)(5) 全文索引 FULLTEXT全文索引(也称全文检索)是目前搜索引擎使用的一种关键技术。它能够利用分词技术等多种算法智能分析出文本文字中关键字词的频率及重要性,然后按照一定的算法规则智能地筛选出我们想要的搜索结果。可以在创建表的时候指定,也可以修改表结构,如:ALTER TABLE table_name ADD FULLTEXT (column)2、索引结构及原理mysql中普遍使用B+Tree做索引,但在实现上又根据聚簇索引和非聚簇索引而不同,本文暂不讨论这点。b+树介绍下面这张b+树的图片在很多地方可以看到,之所以在这里也选取这张,是因为觉得这张图片可以很好的诠释索引的查找过程。如上图,是一颗b+树。浅蓝色的块我们称之为一个磁盘块,可以看到每个磁盘块包含几个数据项(深蓝色所示)和指针(黄色所示),如磁盘块1包含数据项17和35,包含指针P1、P2、P3,P1表示小于17的磁盘块,P2表示在17和35之间的磁盘块,P3表示大于35的磁盘块。真实的数据存在于叶子节点,即3、5、9、10、13、15、28、29、36、60、75、79、90、99。非叶子节点不存储真实的数据,只存储指引搜索方向的数据项,如17、35并不真实存在于数据表中。查找过程在上图中,如果要查找数据项29,那么首先会把磁盘块1由磁盘加载到内存,此时发生一次IO,在内存中用二分查找确定29在17和35之间,锁定磁盘块1的P2指针,内存时间因为非常短(相比磁盘的IO)可以忽略不计,通过磁盘块1的P2指针的磁盘地址把磁盘块3由磁盘加载到内存,发生第二次IO,29在26和30之间,锁定磁盘块3的P2指针,通过指针加载磁盘块8到内存,发生第三次IO,同时内存中做二分查找找到29,结束查询,总计三次IO。真实的情况是,3层的b+树可以表示上百万的数据,如果上百万的数据查找只需要三次IO,性能提高将是巨大的,如果没有索引,每个数据项都要发生一次IO,那么总共需要百万次的IO,显然成本非常非常高。性质(1) 索引字段要尽量的小。通过上面b+树的查找过程,或者通过真实的数据存在于叶子节点这个事实可知,IO次数取决于b+数的高度h。假设当前数据表的数据量为N,每个磁盘块的数据项的数量是m,则树高h=㏒(m+1)N,当数据量N一定的情况下,m越大,h越小;而m = 磁盘块的大小/数据项的大小,磁盘块的大小也就是一个数据页的大小,是固定的;如果数据项占的空间越小,数据项的数量m越多,树的高度h越低。这就是为什么每个数据项,即索引字段要尽量的小,比如int占4字节,要比bigint8字节少一半。(2) 索引的最左匹配特性。当b+树的数据项是复合的数据结构,比如(name,age,sex)的时候,b+数是按照从左到右的顺序来建立搜索树的,比如当(张三,20,F)这样的数据来检索的时候,b+树会优先比较name来确定下一步的所搜方向,如果name相同再依次比较age和sex,最后得到检索的数据;但当(20,F)这样的没有name的数据来的时候,b+树就不知道下一步该查哪个节点,因为建立搜索树的时候name就是第一个比较因子,必须要先根据name来搜索才能知道下一步去哪里查询。比如当(张三,F)这样的数据来检索时,b+树可以用name来指定搜索方向,但下一个字段age的缺失,所以只能把名字等于张三的数据都找到,然后再匹配性别是F的数据了, 这个是非常重要的性质,即索引的最左匹配特性。建索引的几大原则(1) 最左前缀匹配原则对于多列索引,总是从索引的最前面字段开始,接着往后,中间不能跳过。比如创建了多列索引(name,age,sex),会先匹配name字段,再匹配age字段,再匹配sex字段的,中间不能跳过。mysql会一直向右匹配直到遇到范围查询(>、<、between、like)就停止匹配。一般,在创建多列索引时,where子句中使用最频繁的一列放在最左边。看一个补符合最左前缀匹配原则和符合该原则的对比例子。实例:表c2c_db.t_credit_detail建有索引(Flistid,Fbank_listid)不符合最左前缀匹配原则的sql语句:select * from t_credit_detail where Fbank_listid=‘201108010000199’G该sql直接用了第二个索引字段Fbank_listid,跳过了第一个索引字段Flistid,不符合最左前缀匹配原则。用explain命令查看sql语句的执行计划,如下图:从上图可以看出,该sql未使用索引,是一个低效的全表扫描。符合最左前缀匹配原则的sql语句:select * from t_credit_detail where Flistid=‘2000000608201108010831508721’ and Fbank_listid=‘201108010000199’G该sql先使用了索引的第一个字段Flistid,再使用索引的第二个字段Fbank_listid,中间没有跳过,符合最左前缀匹配原则。用explain命令查看sql语句的执行计划,如下图:从上图可以看出,该sql使用了索引,仅扫描了一行。对比可知,符合最左前缀匹配原则的sql语句比不符合该原则的sql语句效率有极大提高,从全表扫描上升到了常数扫描。(2) 尽量选择区分度高的列作为索引。比如,我们会选择学号做索引,而不会选择性别来做索引。(3) =和in可以乱序比如a = 1 and b = 2 and c = 3,建立(a,b,c)索引可以任意顺序,mysql的查询优化器会帮你优化成索引可以识别的形式。(4) 索引列不能参与计算,保持列“干净”比如:Flistid+1>‘2000000608201108010831508721‘。原因很简单,假如索引列参与计算的话,那每次检索时,都会先将索引计算一次,再做比较,显然成本太大。(5) 尽量的扩展索引,不要新建索引。比如表中已经有a的索引,现在要加(a,b)的索引,那么只需要修改原来的索引即可。索引的不足虽然索引可以提高查询效率,但索引也有自己的不足之处。索引的额外开销:(1) 空间:索引需要占用空间;(2) 时间:查询索引需要时间;(3) 维护:索引须要维护(数据变更时);不建议使用索引的情况:(1) 数据量很小的表(2) 空间紧张常用优化总结优化语句很多,需要注意的也很多,针对平时的情况总结一下几点:1、有索引但未被用到的情况(不建议)(1) Like的参数以通配符开头时尽量避免Like的参数以通配符开头,否则数据库引擎会放弃使用索引而进行全表扫描。以通配符开头的sql语句,例如:select * from t_credit_detail where Flistid like ‘%0’G这是全表扫描,没有使用到索引,不建议使用。不以通配符开头的sql语句,例如:select * from t_credit_detail where Flistid like ‘2%‘G很明显,这使用到了索引,是有范围的查找了,比以通配符开头的sql语句效率提高不少。(2) where条件不符合最左前缀原则时例子已在最左前缀匹配原则的内容中有举例。(3) 使用!= 或 <> 操作符时尽量避免使用!= 或 <>操作符,否则数据库引擎会放弃使用索引而进行全表扫描。使用>或<会比较高效。select * from t_credit_detail where Flistid != ‘2000000608201108010831508721’G(4) 索引列参与计算应尽量避免在 where 子句中对字段进行表达式操作,这将导致引擎放弃使用索引而进行全表扫描。select * from t_credit_detail where Flistid +1 > ‘2000000608201108010831508722’G(5) 对字段进行null值判断应尽量避免在where子句中对字段进行null值判断,否则将导致引擎放弃使用索引而进行全表扫描,如: 低效:select * from t_credit_detail where Flistid is null ;可以在Flistid上设置默认值0,确保表中Flistid列没有null值,然后这样查询: 高效:select * from t_credit_detail where Flistid =0;(6) 使用or来连接条件应尽量避免在where子句中使用or来连接条件,否则将导致引擎放弃使用索引而进行全表扫描,如: 低效:select * from t_credit_detail where Flistid = ‘2000000608201108010831508721’ or Flistid = ‘10000200001’;可以用下面这样的查询代替上面的 or 查询: 高效:select from t_credit_detail where Flistid = ‘2000000608201108010831508721’ union all select from t_credit_detail where Flistid = ‘10000200001’;2、避免select 在解析的过程中,会将’’ 依次转换成所有的列名,这个工作是通过查询数据字典完成的,这意味着将耗费更多的时间。所以,应该养成一个需要什么就取什么的好习惯。3、order by 语句优化任何在Order by语句的非索引项或者有计算表达式都将降低查询速度。方法:1.重写order by语句以使用索引; 2.为所使用的列建立另外一个索引 3.绝对避免在order by子句中使用表达式。4、GROUP BY语句优化提高GROUP BY 语句的效率, 可以通过将不需要的记录在GROUP BY 之前过滤掉低效:SELECT JOB , AVG(SAL)FROM EMPGROUP by JOBHAVING JOB = ‘PRESIDENT’OR JOB = ‘MANAGER’高效:SELECT JOB , AVG(SAL)FROM EMPWHERE JOB = ‘PRESIDENT’OR JOB = ‘MANAGER’GROUP by JOB5、用 exists 代替 in很多时候用 exists 代替 in 是一个好的选择: select num from a where num in(select num from b) 用下面的语句替换: select num from a where exists(select 1 from b where num=a.num)6、使用 varchar/nvarchar 代替 char/nchar尽可能的使用 varchar/nvarchar 代替 char/nchar ,因为首先变长字段存储空间小,可以节省存储空间,其次对于查询来说,在一个相对较小的字段内搜索效率显然要高些。7、能用DISTINCT的就不用GROUP BYSELECT OrderID FROM Details WHERE UnitPrice > 10 GROUP BY OrderID可改为:SELECT DISTINCT OrderID FROM Details WHERE UnitPrice > 108、能用UNION ALL就不要用UNIONUNION ALL不执行SELECT DISTINCT函数,这样就会减少很多不必要的资源。9、在Join表的时候使用相当类型的例,并将其索引如果应用程序有很多JOIN 查询,你应该确认两个表中Join的字段是被建过索引的。这样,MySQL内部会启动为你优化Join的SQL语句的机制。而且,这些被用来Join的字段,应该是相同的类型的。例如:如果你要把 DECIMAL 字段和一个 INT 字段Join在一起,MySQL就无法使用它们的索引。对于那些STRING类型,还需要有相同的字符集才行。(两个表的字符集有可能不一样)此文已由作者授权腾讯云+社区在各渠道发布获取更多新鲜技术干货,可以关注我们腾讯云技术社区-云加社区官方号及知乎机构号 ...

January 2, 2019 · 2 min · jiezi

SQL 难点解决:序列生成

1、 生成连续整数序列MySQL8: with recursive t(n) as (select 1union allselect n+1 from t where n<7)select * from t;Oracle:select level nfrom dual connect by level<=7;集算器 SPL:A1:构造从 1 到 7 的整数序列示例 1:百鸡问题,鸡翁一值钱五,鸡母一值钱三,鸡雏三值钱一。百钱买百鸡,问鸡翁、母、雏各几MySQL8: with recursive jg(n) as (select 1 union all select n+1 from jg where n<100/5),jm(n) as (select 1 union all select n+1 from jm where n<100/3),jc(n) as (select 3 union all select n+3 from jc where n<98)select jg.n jw, jm.n jm, jc.n jcfrom jg cross join jm cross join jcwhere jg.n5+jm.n3+jc.n/3=100 and jg.n+jm.n+jc.n=100集算器 SPL:A1:构造1到20的整数序列A2:构造1到33的整数序列A3:构造1到99且步长为3的整数序列A4:创建数据结构为(jw,jm,jc)的序表A5:对A1、A2、A3的数据进行嵌套循环,若满足于A1成员+A2成员+A3成员==100且A1成员5+A2成员3+A3成员/3==100则追加到A4序表中示例2:将指定列中冒号分隔的串划分成多行Oracle:with t(k,f) as (select 1 , ‘a1:a2:a3’ from dualunion all select 2, ‘b1:b2’ from dual),t1 as (select k,f, length(f)-length(replace(f,’:’,’’))+1 cnt from t),t2 as (select level n from dual connect by level<=(select max(cnt) from t1)),t3 as (select t1.k, t1.f, n, cnt,case when n=1 then 1 else instr(f,’:’,1,n-1)+1 end p1, case when n=cnt then length(f)+1 else instr(f,’:’,1,n) end p2from t1 join t2 on t2.n<=t1.cnt)select k,substr(f,p1,p2-p1) f from t3 order by k;集算器 SPL:A1:创建数据结构为(k,f)的序表,并追加2条记录(1, “a1:a2:a3)和(2,”b1:b2”)A2:将A1的字段f用冒号划分成序列并重新赋值给字段fA3:针对A1每条记录构造数据结构为(k,f)的序表,并根据字段f中成员构造记录(A1.k,f成员)追加到此序表中2、 生成连续日期序列MySQL8:with recursivet(d) as (select date'2018-10-03’union allselect d+1 from t where d<date'2018-10-09’)select d,dayofweek(d) w from t;集算器 SPL:A1:生成2018-10-03到2018-10-09的日期序列示例:列出2015-01-03到2015-01-07每天的销量汇总MySQL8:with recursivet(d,v) as (select date'2015-01-04’,30union all select date'2015-01-06’,50union all select date'2015-01-07’,50union all select date'2015-01-03’,40union all select date'2015-01-04’, 80),s(d) as (select date'2015-01-03’union allselect d+1 from s where d<date'2015-01-07’)select s.d, sum(t.v) vfrom s left join t on s.d=t.dgroup by s.d;集算器 SPL:A4:A2中记录按字段d的值对齐到A3A5:根据A4和A3对位构造统计后的序表3、 生成连续的工作日(不包含周六周日)序列MySQL8:with recursivet(d) as (select date'2018-10-03’union allselect d+1 from t where d<date'2018-10-09’)select d,dayofweek(d) w from twhere dayofweek(d)<=5;集算器 SPL:A1:构造从2018-10-03到2018-10-09不包含周六周日的日期序列A2:根据A1构造日期及相应周几的序表4、 根据序列生成表MySQL8:with recursive t1(n) as (select 1 union all select n+1 from t1 where n<14),t2(n, name) as (select n, concat(‘a’,n) name from t1)select max(if(n%4=1, name, null)) f1,max(if(n%4=2, name, null)) f2,max(if(n%4=3, name, null)) f3,max(if(n%4=0, name, null)) f4from t2group by floor((n+3)/4);集算器 SPL: ...

December 29, 2018 · 2 min · jiezi

TableStore实战:DLA+SQL实时分析TableStore

一、实战背景什么是DLA(DataLake Analytics数据湖)?他是无服务器化(Serverless)的云上交互式查询分析服务。作为分布式交互式分析服务,是表格存储计算生态的重要组成之一。为了使用户更好的了解DLA的功能、使用方式,创建了这一实战样例。基于DLA可以不用做任何ETL、数据搬迁等前置过程, 实现跨各种异构数据源进行大数据关联分析,并且支持数据回流到各个异构数据源,从而极大的节省成本、 降低延时和提升用户体验。基于JDBC,表格存储的控制台将SQL查询直接做了集成,数据为公共实例,用户不用开通服务也可免费体验表格存储的实时SQL分析、查询功能,样例如下所示:__官网控制台地址:__项目样例需求场景:黑五交易数据本实战案例中,我们从 https://www.kaggle.com/mehdidag/black-friday 上获取数据, 存储到TableStore,然后基于DLA做分析,带你切身感受下数据的价值!“Black Friday”,即“黑色星期五”,是美国人一年中购物最疯狂的日子,类似于中国的“双十一”购物狂欢节。一般黑色星期五的活动主要在线下,但逐渐也有往线上发展的趋势,比如Amazon就有针对黑色星期五做的线上销售活动, 与天猫双十一很相似。同样的,这样的活动会产生大量有意义的商业数据。我们在DLA中定义了一个叫blackfriday50w的表,映射到TableStore中的一个表,用来描述用户购买商品的。如下为示例数据的表结构、与真实数据截图二、表格存储(TableStore)方案准备工作若您对于DLA实时在线分析TableStore的功能感兴趣,希望开始自己系统的搭建之旅,只需按照如下步骤便可以着手搭建了:1、开通表格存储通过控制台开通表格存储服务,表格存储即开即用(后付费),采用按量付费方式,已为用户提供足够功能测试的免费额度。表格存储官网控制台、免费额度说明。2、创建实例通过控制台创建表格存储实例。3、导入数据该数据共有53.8万行,12个列,我们通过SDK将全量数据存储在TableStore的表。用户可通过控制台插入2条测试数据;开通DLA服务DLA服务开通用户进入产品介绍页,选择开通服务:https://www.aliyun.com/product/datalakeanalytics通过DLA控制台开通TableStore数据源开通数据源后创建服务访问点(择经典网络,若已有vpc,可选择vpc)登录CMS(账密会在开通服务后发送站内消息,消息中查看)创建DLA外表1)创建自己的DLA库(相关信息从上述过程中查找):mysql> create database hangzhou_ots_test with dbproperties ( catalog = ‘ots’, location = ‘https://instanceName.cn-hangzhou.ots-internal.aliyuncs.com’, instance = ‘instanceName’);Query OK, 0 rows affected (0.23 sec)#hangzhou_ots_test —请注意库名,允许字母、数字、下划线#catalog = ‘ots’, —指定为ots,是为了区分其他数据源,比如oss、rds等#location = ‘https://xxx’ —ots的endpoint,从实例上可以看到#instance = ‘hz-tpch-1x-vol'2)查看自己创建的库:mysql> show databases;+——————————+| Database |+——————————+| hangzhou_ots_test |+——————————+1 rows in set (0.22 sec)3)查看自己的DLA表:mysql> use hangzhou_ots_test;Database changedmysql> show tables;Empty set (0.30 sec)4)创建DLA表,映射到OTS的表:mysql> CREATE EXTERNAL TABLE tableName ( pk1 varchar(100) not NULL , pk2 int not NULL , col1 varchar(100) NULL , col2 varchar(100) NULL , PRIMARY KEY (pk1, pk2));Query OK, 0 rows affected (0.36 sec)## tableName —- TableStore中对应的表名(dla中会转换成小写后做映射)## pk2 int not NULL —- 如果是主键的话,必须要not null## PRIMARY KEY (pk1, pk2) —- 务必与ots中的主键顺序相同;名称的话也要对应5)查看自己创建的表和相关的DDL语句:mysql> show tables;+————+| Table_Name |+————+| tablename |+————+1 row in set (0.35 sec)6)开始查询和分析(用户可以分析自己的数据,符合mysql的语法)mysql> select count(*) from tablename;+——-+| _col0 |+——-+| 25 |+——-+1 row in set (1.19 sec)这样,一个TableStore在DLA中的关联外表创建成功,用户便可以通过JDBC、或者CMS控制台,根据自己的需求实时分析自己的TableStore表了。三、表格存储控制台展示如下为控制提供的SQL场景,用户可以仿照控制台中实例自己写一些需求SQL,开来尝试吧!最畅销的top10产品和销售量中高端产品占总体GMV的比例不同年龄段的消费客单价趋势高消费人群的性别和年龄趋势本文作者:潭潭 阅读原文本文为云栖社区原创内容,未经允许不得转载。 ...

December 28, 2018 · 1 min · jiezi

【MySQL经典案例分析】关于数据行溢出由浅至深的探讨

本文由云+社区发表一、从常见的报错说起 故事的开头我们先来看一个常见的sql报错信息: 相信对于这类报错大家一定遇到过很多次了,特别对于OMG这种已内容生产为主要工作核心的BG,在内容线的存储中,数据大一定是个绕不开的话题。这里的数据“大”,远不止存储空间占用多,其中也包括了单个(表)字段存储多、大,数据留存时间长,数据冗余多,冷热数据不明显导致的体量大,访问峰值随着热点变化明显,逻辑处理复杂导致数据存储压力放大等等。回到这个报错的问题上来,我们先来看一下这个表的结构:看到这里,我相信大家会有不同的处理方式了,这里就不对各种处理方式的优劣做比较了,仅仅叙述使用频率较高的两种处理方式。根据报错的指引,把两个大的varchar(22288)改成text、blob根据业务特点,缩小varchar的存储长度,或者按照规则拆分成多个小的vachar和char 这两种的处理方式也各有优缺点,把字段改成text或者blob,不仅增大了数据存储的容量,对这个字段的索引页只能采用前缀或者全文索引了,如果业务侧存储的是json格式的数据,5.7支持json数据类型是个不错的选择,可以针对单个子类进行查询和输出。同样如果缩小和拆分的话就比较依赖业务的场景和逻辑需求了,业务使用的逻辑上需要修改,工程量也需要评估。二、深入探索 接着我们再来深入分析下关于限制大小“65535”的一些容易混淆的概念。1、“65535”不是单个varchar(N)中N的最大限制,而是整个表非大字段类型的字段的bytes总合。———————————————————————————————Every table (regardless of storage engine) has a maximum row size of 65,535 bytes. Storage engines may place additional constraints on this limit, reducing the effective maximum row size.———————————————————————————————2、不同的字符集对字段可存储的max会有影响,例如,UTF8字符需要3个字节存储,对于VARCHAR(255)CHARACTER SET UTF8列,会占用255×3 =765的字节。故该表不能包含超过65,535/765=85这样的列。GBK是双字节的以此类推。3、可变长度列在评估字段大小时还要考虑存储列实际长度的字节数。例如,VARCHAR(255)CHARACTER SET UTF8列需要额外的两个字节来存储值长度信息,所以该列需要多达767个字节存储,其实最大可以存储65533字节,剩余两个字节存储长度信息。4、BLOB、TEXT、JSON列不同于varchar、char等字段,列长度信息独立于行长存储,可以达到65535字节真实存储5、定义NULL列会降低允许的最大列数。InnoDB表,NULL和NOT NULL列存储大小是一样MyISAM表,NULL列需要额外的空间记录其值是否为NULL。每个NULL需要一个额外的位(四舍五入到最接近的字节)。最大行长度计算如下: row length = 1 + (sum of column lengths) + (number of NULL columns + delete_flag + 7)/8 + (number of variable-length columns) 静态表,delete_flag = 1,静态表通过在该行记录一个位来标识该行是否已被删除。 动态表,delete_flag = 0,该标记存储在动态行首,动态表具体可以根据6、对于InnoDB表,NULL和NOT NULL列存储大小是一样7、InnoDB允许单表最多1000个列8、varchar主键只支持不超过767个字节或者768/2=384个双字节 或者767/3=255个三字节的字段 而GBK是双字节的,UTF8是三字节的9、不用的引擎对索引的限制有区别innodb每个列的长度不能大于767 bytes;所有组成索引列的长度和不能大于3072 bytesmyisam 每个列的长度不能大于1000 bytes,所有组成索引列的长度和不能大于1000 bytes三、真正的故障 下面来说下今天遇到的业务故障,线上业出现了大量的如下报错,导致程序无法写入数据:按照提示和正常的思路,我们先第一反应认为业务存在如下的问题:设置的表结构中字段超过了限制某个字段插入的数据长度超过了改字段设置的max值 接着查看了业务的库表结构,如下: 很快排除了第一个原因,因为首先业务的报错不是在建立表的时候出现的,如果是表中非大字段之和65535,在建表的时候就会出错,而业务是在写入的时候才报错的,而且通过库表结构也能发现大量的都是mediumblob类型字段,非大字段加起来远小于65535。 接着根据业务提供的具体SQL,appversion、datadata、elt_stamp、id这几个非大字段,也并没有超过限制,mediumblob类型字段最大可存储16M,业务的数据远远没有达到这个量级。按照报错的提示把 appversion、datadata、elt_stamp、id这几个非大字段均改成blob类型,还是无法解决。(根据之前的分析,必然不是问题的根源)。 冷静下来后,发现其实还有个细节被忽略掉了,业务的失败率不是100%,说明还是有成功的请求,通过对比成功和失败的sql,发现果然数据量差异的还是mediumblob类型字段。那么现在第一个想到的就是,max_allowed_packet这个参数,是不是调小了,是的单个请求超过大小被拒绝了,查了下配置的值(如下图),配置的大小1G,sql的数据长度远没有这么大,这个原因也排除了。 查到这里基本上排除了常见几个问题,接着再看一下另一个参数的限制:innodb_page_size,这个的默认值是16K,每个page两行数据,所以每行最大8k数据。查看了下数据表Row_format是Compact,那么我们可以推断问题的原因应该就是innodb默认的approach存储格式会把每个blob字段的前864个字节存储在page里,所以blob超过一定数量的话,单行大小就会超过8k,所以就报错了。通过对比业务写成功和失败的SQL也应征了这个推论,那么现在要怎么解决这个问题?业务拆分表,大字段进行分表存储通过解决Row_format的存储方式解决问题由于业务单表的存储条数并不大,而且业务逻辑不适合拆分,所以我们要在Row_format上来解决这个问题。 Barracuda文件格式下拥有两种新的行记录格式Compressed和Dynamic两种,新的两种格式对于存放BLOB的数据采用了完全的行溢出的方式,在数据页中只存放20个字节的指针,实际的数据都存放在BLOB Page中。Compressed行记录格式的另一个功能就是存储在其中的数据会以zlib的算法进行压缩。相关的变更操作就相对简单了: 1、 修改MySQL全局变量: SET GLOBAL innodb_file_format=‘Barracuda’; 2、平滑变更原表的属性: ROW_FORMAT=COMPRESSED四、继续学习 通过这个案例我们可以从中提炼出两个值得深入研究一下的点:1、关于innodb_page_size 从MySQL5.6开始,innodb_page_size可以设置Innodb数据页为8K,4K,默认为16K。这个参数在一开始初始化时就要加入my.cnf里,如果已经创建了表,再修改,启动MySQL会报错。那么在5.6的版本之前要修改这个值,怎么办?那只能是在源码上做点文章了,然后重新rebuild一下MySQL。 UNIV_PAGE_SIZE是数据页大小,默认的是16K,该值是可以设置必须为2的次方。对于该值可以设置成4k、8k、16k、32K、64K。同时更改了UNIV_PAGE_SIZE后需要更改UNIV_PAGE_SIZE_SHIFT 该值是2的多少次方为UNIV_PAGE_SIZE,所以设置数据页分别情况如下: 接着再来说一下innodb_page_size设置成不同值的对于mysql性能上的影响,测试的表含有1亿条记录,文件大小30G。 ①读写场景(50%读50%写) 16K,对CPU压力较小,平均在20% 8K,CPU压力为30%~40%,但select吞吐量要高于16K ②读场景(100%读) 16K和8K差别不明显 InnoDB Buffer Pool管理页面本身也有代价,Page数越多,那么相同大小下,管理链表就越长。因此当我们的数据行本身就比较长(大块插入),更大的页面更有利于提升速度,因为一个页面可以放入更多的行,每个IO写的大小更大,可以更少的IOPS写更多的数据。 当行长超过8K的时候,如果是16K的页面,就会强制转换一些字符串类型为TEXT,把字符串主体转移到扩展页中,会导致读取列需要多一个IO,更大的页面也就支持了更大的行长,64K页面可以支持近似32K的行长而不用使用扩展页。 但是如果是短小行长的随机读取和写入,则不适合使用这么大的页面,这会导致IO效率下降,大IO只能读取到小部分。2、关于Row_format Innodb存储引擎保存记录,是以行的形式存放的。在InnoDB 1.0.x版本之前,InnoDB 存储引擎提供了 Compact 和 Redundant 两种格式来存放行记录数据。MySQL 5.1 中的innodb_plugin 引入了新的文件格式:Barracuda,该文件格式拥有新的两种行格式:compressed和dynamic。并且把 compact 和 redundant 合称为Antelope。可以通过命令SHOW TABLE STATUS LIKE ’table_name’;来查看当前表使用的行格式,其中 row_format 列表示当前所使用的行记录结构类型。 MySQL 5.6 版本中,默认 Compact ,msyql 5.7.9 及以后版本,默认行格式由innodb_default_row_format变量决定,默认值是DYNAMIC,也可以在 create table 的时候指定ROW_FORMAT=DYNAMIC(通过这个可动态调整表的存储格式)。如果要修改现有表的行模式为compressed或dynamic,必须先将文件格式设置成Barracuda(set global innodb_file_format=Barracuda;)。再用ALTER TABLE tablename ROW_FORMAT=COMPRESSED;去修改才能生效,否则修改无效却无提示。①compact 如果blob列值长度 <= 768 bytes,不会发生行溢出(page overflow),内容都在数据页(B-tree Node);如果列值长度 > 768字节,那么前768字节依然在数据页,而剩余的则放在溢出页(off-page),如下图: 上面讲的blob或变长大字段类型包括blob、text、varchar,其中varchar列值长度大于某数N时也会存溢出页,在latin1字符集下N值可以这样计算:innodb的块大小默认为16kb,由于innodb存储引擎表为索引组织表,树底层的叶子节点为一双向链表,因此每个页中至少应该有两行记录,这就决定了innodb在存储一行数据的时候不能够超过8k,减去其它列值所占字节数,约等于N。②compressed或dynamic对blob采用完全行溢出,即聚集索引记录(数据页)只保留20字节的指针,指向真实存放它的溢出段地址: dynamic行格式,列存储是否放到off-page页,主要取决于行大小,它会把行中最长的那一列放到off-page,直到数据页能存放下两行。TEXT/BLOB列 <=40 bytes 时总是存放于数据页。可以避免compact那样把太多的大列值放到 B-tree Node,因为dynamic格式认为,只要大列值有部分数据放在off-page,那把整个值放入都放入off-page更有效。 compressed 物理结构上与dynamic类似,但是对表的数据行使用zlib算法进行了压缩存储。在long blob列类型比较多的情况下用,可以降低off-page的使用,减少存储空间(50%左右,可参见之前“【数据库评测报告】第三期:innodb、tokudb压缩性能”报告中的测试结果),但要求更高的CPU,buffer pool里面可能会同时存储数据的压缩版和非压缩版,所以也多占用部分内存。 最后参考了《高性能MySQL》,给出一些使用BLOB这类变长大字段类型的建议: ①大字段在InnoDB里可能浪费大量空间。例如,若存储字段值只是比行的要求多了一个字节,也会使用整个页面来存储剩下的字节,浪费了页面的大部分空间。同样的,如果有一个值只是稍微超过了32个页的大小,实际上就需要使用96个页面。 ②太长的值可能使得在查询中作为WHERE条件不能使用索引,因而执行很慢。在应用WHERE条件之前,MySQL需要把所有的列读出来,所以可能导致MySQL要求InnoDB读取很多扩展存储,然后检查WHERE条件,丢弃所有不需要的数据。 ③一张表里有很多大字段,最好组合起来单独存到一个列里面。让所有的大字段共享一个扩展存储空间,比每个字段用自己的页要好。 ④把大字段用COMPRESS()压缩后再存为BLOB,或者在发送到MySQL前在应用程序中进行压缩,可以获得显著的空间优势和性能收益。 ⑤扩展存储禁用了自适应哈希,因为需要完整的比较列的整个长度,才能发现是不是正确的数据。此文已由作者授权腾讯云+社区发布 ...

December 27, 2018 · 1 min · jiezi

[LeetCode] 181. Employees Earning More Than Their Managers

ProblemThe Employee table holds all employees including their managers. Every employee has an Id, and there is also a column for the manager Id.IdNameSalaryManagerId1Joe7000032Henry8000043Sam60000NULL4Max90000NULLGiven the Employee table, write a SQL query that finds out employees who earn more than their managers. For the above table, Joe is the only employee who earns more than his manager.EmployeeJoeSolutionselect e.Name as “Employee” from Employee e join Employee m on (e.ManagerId = m.Id) where e.Salary > m.Salary; ...

December 26, 2018 · 1 min · jiezi

[LeetCode] 177. Nth Highest Salary

ProblemWrite a SQL query to get the nth highest salary from the Employee table.IdSalary110022003300For example, given the above Employee table, the nth highest salary where n = 2 is 200. If there is no nth highest salary, then the query should return null.getNthHighestSalary(2)200SolutionCREATE FUNCTION getNthHighestSalary(N INT) RETURNS INTBEGIN SET N=N-1; RETURN ( SELECT DISTINCT Salary FROM Employee ORDER BY Salary DESC LIMIT 1 OFFSET N );END

December 26, 2018 · 1 min · jiezi

精读《手写 SQL 编译器 - 智能提示》

1 引言词法、语法、语义分析概念都属于编译原理的前端领域,而这次的目的是做 具备完善语法提示的 SQL 编辑器,只需用到编译原理的前端部分。经过连续几期的介绍,《手写 SQL 编译器》系列进入了 “智能提示” 模块,前几期从 词法到文法、语法,再到构造语法树,错误提示等等,都是为 “智能提示” 做准备。由于智能提示需要对词法分析、语法分析做深度定制,所以我们没有使用 antlr4 等语法分析器生成工具,而是创造了一个 JS 版语法分析生成器 syntax-parser。这次一口气讲完如何从 syntax-parser 到做一个具有智能提示功能的 SQL 编辑器。2 精读从语法解析、智能提示和 SQL 编辑器封装三个层次来介绍,这三个层次就像俄罗斯套娃一样具有层层递进的关系。为了更清晰展现逻辑层次,同时满足解耦的要求,笔者先从智能提示整体设计架构讲起。智能提示的架构syntax-parser 是一个 JS 版的语法分析器生成器,除了类似 antlr4 基本语法分析功能外,还支持专门为智能提示优化的功能,后面会详细介绍。整体架构设计如下图所示:首先需要实现 SQL 语法,我们利用语法分析器生成器 syntax-parser,生成一个 SQL 语法分析器,这一步其实是利用 syntax-parser 能力完成了 sql lexer 与 sql parser。为了解析语法树含义,我们需要在 sql parser 基础之上编写一套 sql reader,包含了一些分析函数解析语法树的语义。利用 monaco-editor 生态,利用 sql reader 封装 monaco-editor 插件,同时实现 用户 <=> 编辑器 间的交互,与 编辑器 <=> 语义分析器 间的交互。语法解析器syntax-parser 分为词法分析、语法分析两步。词法分析主要利用正则构造一个有穷自动机,大家都学过的 “编译原理” 里有更完整的解读,或者移步 精读《手写 SQL 编译器 - 词法分析》,这里主要介绍语法分析。词法分析的输入是语法分析输出的 Tokens。Tokens 就是一个个单词,Token 结构存储了单词的值、位置、类型。我们需要构造一个执行链条消费这些 Token,也就是可以执行文法扫描的程序。我们用四种类型节点描述文法,如下图所示:如果不了解文法概念,可以阅读 精读《手写 SQL 编译器 - 文法介绍》能消耗 Token 的只有 MatchNode 节点,ChainNode 节点描述先后关系(比如 expr -> name id),TreeNode 节点描述并列关系(比如 factor -> num | id),FunctionNode 是函数节点,表示还未展开的节点(如果把文法匹配比做迷宫探险,那这是个无限迷宫,无法穷尽展开)。如何用 syntax-parser 描述一个文法,可以访问文档,现在我们已经描述了一个文法树,应该如何解析呢?我们先找到一个非终结符作为根节点,深度遍历所有非终结符节点,遇到 MatchNode 时如果匹配,就消耗一个 Token 并继续前进,否则文法匹配失败。遇到 ChainNode 会按照顺序执行其子节点;遇到 FunctionNode(非终结符节点)会执行这个函数,转换为一个非 FunctionNode 节点,如下图所示:遇到 TreeNode 节点时保存这个节点运行状态并继续执行,在 MatchNode 匹配失败时可以还原到此节点继续尝试下个节点,如下图所示:这样就具备了最基本的语法分析功能,如需更详细阅读,可以移步 精读《手写 SQL 编译器 - 语法分析》。我们还做了一些优化,比如 First 集优化与路径缓存优化。限于篇幅,分布在以下几篇文章:精读《手写 SQL 编译器 - 回溯》精读《手写 SQL 编译器 - 语法树》精读《手写 SQL 编译器 - 错误提示》精读《手写 SQL 编译器 - 性能优化之缓存》SQL 编辑器重点在于如何做输入提示,也就是如何在用户光标位置给出恰当的提示。这就是我们定制 SQL 编辑器的原因,输入提示与语法检测需要分开来做,而语法树并不能很好解决输入提示的问题。智能提示为了找到一个较为完美的语法提示方案,通过查阅大量资料,我决定将光标作为一个 Token 考虑来实现智能提示。思考我们用 | 表示光标所在位置,那么下面的 SQL 应该如何处理?select | from b;从语法角度来看,它是错的,因为实际上是一个不完整语句 “select from b;“从提示角度来看,它是对的,因为这是一个正确的输入过程,光标位置再输入一个单词就正确了。你会发现,从语法和提示角度来看同一个输入,结果往往是矛盾的,所以我们需要分两条线程分别处理语法与提示。但输入错误时,我们是无法构造语法树的,而智能提示的时机往往都是语句语法错误的时机,用过 AST 工具的人都知道。可是没有语法树,我们怎么做到智能的提示呢?试想如下语句:select c.| from ( select * from dt;) c;面对上面这个语句,很显然 c. 没有写完,一般的语法树解析器提示你语法错误。你可能想到这几种方案:字符串匹配方式强行提示。但很显然这样提示不准确,没有完整语法树,是无法做精确解析的。而且当语法复杂时,字符串解析方案几乎无从下手。把光标位置用一个特殊的字符串补上,先构造一个临时正确的语句,生成 AST 后再找到光标位置。一般我们会采取第二种方案,看上去相对靠谱。处理过程是这样的:select c.$my_custom_symbol$ from …之后在 AST 中找到 $my_custom_symbol$ 字符串,对应的节点就是光标位置。实际上这可以解决大部分问题,除了关键字。这种方案唯有关键字场景不兼容,试想一下:select a |from b;# select a $my_custom_symbol$ b;你会发现,“补全光标文字” 法,在关键字位置时,会把原本正确的语句变成错误的语句,根本解析不出语法树。我们在 syntax-parser 解析引擎层就解决了这个问题,解决方案是 连同光标位置一起解析。两个假设我们做两个基本假设:需要自动补全的位置分为 “关键字” 与 “非关键字”。“非关键字” 位置基本都是由字符串构成的。关键字:因此针对第一种假设,syntax-parser 内置了 “关键字提示” 功能。因为 syntax-parser 可以拿到你配置的文法,因此当给定光标位置时,可以拿到当前位置前一个 Token,通过回溯和平行尝试,将后面所有可能性提示出来,如下图:输入是 select a |,灰色部分是已经匹配成功的部分,而我们发现光标位置前一个 Token 正是红色标识的 word,通过尝试运行推导,我们发现,桔红色标记的 ‘,’ 和 ‘from’ 都是 word 可能的下一个确定单词,这种单词就是 SQL 语法中的 “关键字”,syntax-parser 会自动告诉你,光标位置可能的输入是 [’,’, ‘from’]。所以关键字的提示已经在 syntax-parser 层内置解决了!而且无论语法正确与否,都不影响提示结果,因为算法是 “寻找光标位置前一个 Token 所有可能的下一个 Token”,这可以完全由词法分析器内置支持。非关键字:针对非关键字,我们解决方案和用特殊字符串补充类似,但也有不同:在光标位置插入一个新 Token,这个 Token 类型是特殊的 “光标类型”。在 word 解析函数加一个特殊判断,如果读到 “光标类型” Token,也算成功解析,且消耗 Token。因此 syntax-parser 总是返回两个 AST 信息:{ “ast”: {}, “cursorPath”: []}分别是语法树详细信息,与光标位置在语法树中的访问路径。对于 select a | 的情况,会生成三个 Tokens:[‘select’, ‘a’, ‘cursor’],对于 select a| 的情况,会生成两个 Tokens:[‘select’, ‘a’],也就是光标与字符相连时,不会覆盖这个字符。cursorPath 的生成也比 “字符串补充” 方案更健壮,syntax-parser 生成的 AST 会记录每一个 Token 的位置,最终会根据光标位置进行比对,进而找到光标对应语法树上哪个节点。对 .| 的处理:可能你已经想到了,.| 情况是很通用的输入场景,比如 user. 希望提示出 user 对象的成员函数,或者 SQL 语句表名存在项目空间的情况,可能 tableName 会存在 .| 的语法。.| 状况时,语法是错误的,此时智能提示会遇到挑战。根据查阅的资料,这块也有两种常见处理手法:在 . 位置加上特殊标识,让语法解析器可以正确解析出语法树。抹去 .,先让语法正确解析,再分析语法树拿到 . 前面 Token 的属性,推导出后面的属性。然而这两种方式都不太优雅,syntax-parser 选择了第三种方式:隔空打牛。通过抽象,我们发现,无论是 user.name 还是 udf:count() 这种语法,都要求在某个制定字符打出时(比如 . 或 :),提示到这个字符后面跟着的 Token。此时光标焦点在 . 而非之后的字符上,那我们何不将光标偷偷移到 . 之后,进行空光标 Token 补位呢!这样不但能完全复用之前的处理思想,还可以拿到我们真正想拿到的位置:select a(.|) from b;# select a. (|) from b对比后发现,第一行拥有 4 个 Token,语法错误,而经过修改的第二行拥有 5 个 Token(一个光标补位),语法正确,且光标所在位置等价于第一行我们希望提示的位置,此问题得以解决。SQL 编辑器封装我们拥有了内置 “智能提示” 功能的语法解析器,定制了一套自定义的 SQL 词法、文法描述,便完成了 sql-lexer 与 sql-parser 这一层。由于 SQL 文法完善工作非常庞大,且需要持续推进,这里举流计算中,申明动态维表的例子:CREATE TABLE dwd_log_pv_wl_ri( PRIMARY KEY(rowkey), PERIOD FOR SYSTEM_TIME) WITH ()要支持这种语法,我们在非终结符 tableOption 下增加两个分支即可:const tableOption = () => chain([ chain(stringOrWord, dataType)(), chain(“primary”, “key”, “(”, primaryKeyList, “)”)(), chain(“period”, “for”, “system_time”)() ])();sql-reader:为了方便解析 SQL 语法树,我们在 sql-reader 内置了几个常用方法,比如:找到距离光标位置最近的父节点。比如 select a, b, | from d 会找到这个 selectStatement。根据表源找到所有提供的字段。表源是指 from 之后跟的语法,不但要考虑嵌套场景,别名,分组,方言,还要追溯每个字段来源于哪张表(针对 join 或 union 的情况)。有了 sql-reader,我们可以保证在这种层层嵌套 + 别名混淆 + select * 这种复杂的场景下,仍然能追溯到字段的最原始名称,最原始的表名:这样上层业务拓展时,可以拿到足够准、足够多的信息,具有足够好的拓展型。monaco-editor plugin:我们也支持了更上层的封装,Monaco Editor 插件级别的,只需要填一些参数:获取表名、获取字段的回调函数就能 Work,统一了内部业务的调用方式:import { monacoSqlAutocomplete } from ‘@alife/monaco-sql-plugin’;// Get monaco and editor.monacoSqlAutocomplete(monaco, editor, { onInputTableField: async tableName => { // …}, onInputTableName: async () => { // … }, onInputFunctionName: async () => { // … }, onHoverTableName: async cursorInfo => { // … }, onHoverTableField: (fieldName, extra) => { // … }, onHoverFunctionName: functionName => { // … }});比如实现了 onInputTableField 接口,我们可以拿到当前表名信息,轻松实现字段提示:你也许会看到,上图中鼠标位置有错误提示(红色波浪线),但依然给出了正确的推荐提示。这得益于我们对 syntax-parser 内部机制的优化,将语法检查与智能提示分为两个模块独立处理,经过语法解析,虽然抛出了语法错误,但因为有了光标的加入,最终生成了语法树。再比如实现了 onHoverFunctionName,可以自定义鼠标 hover 在函数时的提示信息:<img width=400 src=“https://cdn.nlark.com/lark/0/...;>得益于 sql-reader,我们对 sql 语句做了层层解析,所以才能把自动提示做到极致。比如在做字段自动提示时,经历了如下判断步骤:而你只需要实现 onInputTableField,告诉程序每个表可以提供哪些字段,整个流程就会严格的层层检查表名提供对原始字段与 selectList 描述的输出字段,找到映射关系并逐级传递、校验,最终 Merge 后一直冒泡到当前光标位置所在语句,形成输入建议。4 总结整个智能提示的封装链条如下:syntax-parser -> sql-parser -> monaco-editor-plugin对应关系是:语法解析器生成器 -> SQL 语法解析器 -> 编辑器插件这样逻辑层次清晰,解耦,而且可以从任意节点切入,进行自定义,比如:从 syntax-parser 开始使用从最底层开始使用,也许有两个目的:上层封装的 sql-parser 不够好用,我重写一个 sql-parser’ 以及 monaco-editor-plugin’。我的场景不是 SQL,而是流程图语法、或 Markdown 语法的自动提示。针对这种情况,首先将目标文法找到,转化成 syntax-parser 的语法,比如:chain(word, “=>”, word);再仿照 sql-parser -> monaco-editor-plugin 的结构把上层封装依次实现。从 sql-parser 开始使用也许你需要的仅仅是一颗 SQL 语法树?或者你的输出目标不是 SQL 编辑器而是一个 UI 界面?那可以试试直接使用 sql-parser。sql-parser 不仅可以生成语法树,还能找到当前光标位置所在语法树的节点,找到 SQL 某个语法返回的所有字段列表等功能,基于它,甚至可以做 UI 与 SQL 文本互转的应用。从 monaco-editor-plugin 开始使用也许你需要支持自动提示的 SQL 编辑器,那太棒了,直接用 monaco-editor-plugin 吧,根据你的业务场景或个人喜好,实现一个定制的 monaco-editor 交互插件。目前我们只开源最底层的 syntax-parser,这也是业务无关的语法解析引擎生成器,期待您的使用与建议!讨论地址是:精读《手写 SQL 编译器 - 智能提示》 · Issue #118 · dt-fe/weekly如果你想参与讨论,请点击这里,每周都有新的主题,周末或周一发布。前端精读 - 帮你筛选靠谱的内容。 ...

December 24, 2018 · 3 min · jiezi

TiDB 源码阅读系列文章(二十二)Hash Aggregation

聚合算法执行原理在 SQL 中,聚合操作对一组值执行计算,并返回单个值。TiDB 实现了 2 种聚合算法:Hash Aggregation 和 Stream Aggregation。我们首先以 AVG 函数为例(案例参考 Stack Overflow),简述这两种算法的执行原理。假设表 t 如下:列 a列 b191-82-7261524SQL: select avg(b) from t group by a, 要求将表 t 的数据按照 a 的值分组,对每一组的 b 值计算平均值。不管 Hash 还是 Stream 聚合,在 AVG 函数的计算过程中,我们都需要维护 2 个中间结果变量 sum 和 count。Hash 和 Stream 聚合算法的执行原理如下。Hash Aggregate 的执行原理在 Hash Aggregate 的计算过程中,我们需要维护一个 Hash 表,Hash 表的键为聚合计算的 Group-By 列,值为聚合函数的中间结果 sum 和 count。在本例中,键为 列 a 的值,值为 sum(b) 和 count(b)。计算过程中,只需要根据每行输入数据计算出键,在 Hash 表中找到对应值进行更新即可。对本例的执行过程模拟如下。输入数据 a bHash 表 [key] (sum, count)1 9[1] (9, 1)1 -8[1] (1, 2)2 -7[1] (1, 2) [2] (-7, 1)2 6[1] (1, 2) [2] (-1, 2)1 5[1] (6, 3) [2] (-1, 2)2 4[1] (6, 3) [2] (3, 3)输入数据输入完后,扫描 Hash 表并计算,便可以得到最终结果:Hash 表avg(b)[1] (6, 3)2[2] (3, 3)1Stream Aggregation 的执行原理Stream Aggregate 的计算需要保证输入数据按照 Group-By 列有序。在计算过程中,每当读到一个新的 Group 的值或所有数据输入完成时,便对前一个 Group 的聚合最终结果进行计算。对于本例,我们首先对输入数据按照 a 列进行排序。排序后,本例执行过程模拟如下。输入数据是否为新 Group 或所有数据输入完成(sum, count)avg(b)1 9是(1, 9)前一个 Group 为空,不进行计算1 -8否(2, 1) 1 5否(3, 6) 2 -7是(1, -7)22 6否(2, -1) 2 4否(3, 3) 是 1因为 Stream Aggregate 的输入数据需要保证同一个 Group 的数据连续输入,所以 Stream Aggregate 处理完一个 Group 的数据后可以立刻向上返回结果,不用像 Hash Aggregate 一样需要处理完所有数据后才能正确的对外返回结果。当上层算子只需要计算部分结果时,比如 Limit,当获取到需要的行数后,可以提前中断 Stream Aggregate 后续的无用计算。当 Group-By 列上存在索引时,由索引读入数据可以保证输入数据按照 Group-By 列有序,此时同一个 Group 的数据连续输入 Stream Aggregate 算子,可以避免额外的排序操作。TiDB 聚合函数的计算模式由于分布式计算的需要,TiDB 对于聚合函数的计算阶段进行划分,相应定义了 5 种计算模式:CompleteMode,FinalMode,Partial1Mode,Partial2Mode,DedupMode。不同的计算模式下,所处理的输入值和输出值会有所差异,如下表所示:AggFunctionMode输入值输出值CompleteMode原始数据最终结果FinalMode中间结果最终结果Partial1Mode原始数据中间结果Partial2Mode中间结果进一步聚合的中间结果DedupMode原始数据去重后的原始数据以上文提到的 select avg(b) from t group by a 为例,通过对计算阶段进行划分,可以有多种不同的计算模式的组合,如:CompleteMode此时 AVG 函数的整个计算过程只有一个阶段,如图所示:Partial1Mode –> FinalMode此时我们将 AVG 函数的计算过程拆成两个阶段进行,如图所示:除了上面的两个例子外,还可能有如下的几种计算方式:聚合被下推到 TiKV 上进行计算(Partial1Mode),并返回经过预聚合的中间结果。为了充分利用 TiDB server 所在机器的 CPU 和内存资源,加快 TiDB 层的聚合计算,TiDB 层的聚合函数计算可以这样进行:Partial2Mode –> FinalMode。当聚合函数需要对参数进行去重,也就是包含 DISTINCT 属性,且聚合算子因为一些原因不能下推到 TiKV 时,TiDB 层的聚合函数计算可以这样进行:DedupMode –> Partial1Mode –> FinalMode。聚合函数分为几个阶段执行, 每个阶段对应的模式是什么,是否要下推到 TiKV,使用 Hash 还是 Stream 聚合算子等都由优化器根据数据分布、估算的计算代价等来决定。TiDB 并行 Hash Aggregation 的实现如何构建 Hash Aggregation 执行器构建逻辑执行计划 时,会调用 NewAggFuncDesc 将聚合函数的元信息封装为一个 AggFuncDesc。 其中 AggFuncDesc.RetTp 由 AggFuncDesc.typeInfer 根据聚合函数类型及参数类型推导而来;AggFuncDesc.Mode 统一初始化为 CompleteMode。构建物理执行计划时,PhysicalHashAgg 和 PhysicalStreamAgg 的 attach2Task 方法会根据当前 task 的类型尝试进行下推聚合计算,如果 task 类型满足下推的基本要求,比如 copTask,接着会调用 newPartialAggregate 尝试将聚合算子拆成 TiKV 上执行的 Partial 算子和 TiDB 上执行的 Final 算子,其中 AggFuncToPBExpr 函数用来判断某个聚合函数是否可以下推。若聚合函数可以下推,则会在 TiKV 中进行预聚合并返回中间结果,因此需要将 TiDB 层执行的 Final 聚合算子的 AggFuncDesc.Mode 修改为 FinalMode,并将其 AggFuncDesc.Args 修改为 TiKV 预聚合后返回的中间结果,TiKV 层的 Partial 聚合算子的 AggFuncDesc 也需要作出对应的修改,这里不再详述。若聚合函数不可以下推,则 AggFuncDesc.Mode 保持不变。构建 HashAgg 执行器时,首先检查当前 HashAgg 算子是否可以并行执行。目前当且仅当两种情况下 HashAgg 不可以并行执行:存在某个聚合函数参数为 DISTINCT 时。TiDB 暂未实现对 DedupMode 的支持,因此对于含有 DISTINCT 的情况目前仅能单线程执行。系统变量 tidb_hashagg_partial_concurrency 和 tidb_hashagg_final_concurrency 被同时设置为 1 时。这两个系统变量分别用来控制 Hash Aggregation 并行计算时候,TiDB 层聚合计算 partial 和 final 阶段 worker 的并发数。当它们都被设置为 1 时,选择单线程执行。若 HashAgg 算子可以并行执行,使用 AggFuncDesc.Split 根据 AggFuncDesc.Mode 将 TiDB 层的聚合算子的计算拆分为 partial 和 final 两个阶段,并分别生成对应的 AggFuncDesc,设为 partialAggDesc 和 finalAggDesc。若 AggFuncDesc.Mode == CompleteMode,则将 TiDB 层的计算阶段拆分为 Partial1Mode –> FinalMode;若 AggFuncDesc.Mode == FinalMode,则将 TiDB 层的计算阶段拆分为 Partial2Mode –> FinalMode。进一步的,我们可以根据 partialAggDesc 和 finalAggDesc 分别 构造出对应的执行函数。并行 Hash Aggregation 执行过程详述TiDB 的并行 Hash Aggregation 算子执行过程中的主要线程有:Main Thead,Data Fetcher,Partial Worker,和 Final Worker:Main Thread 一个:启动 Input Reader,Partial Workers 及 Final Workers等待 Final Worker 的执行结果并返回Data Fetcher 一个:按 batch 读取子节点数据并分发给 Partial WorkerPartial Worker 多个:读取 Data Fetcher 发送来的数据,并做预聚合将预聚合结果根据 Group 值 shuffle 给对应的 Final WorkerFinal Worker 多个:读取 PartialWorker 发送来的数据,计算最终结果,发送给 Main ThreadHash Aggregation 的执行阶段可分为如下图所示的 5 步:启动 Data Fetcher,Partial Workers 及 Final Workers。这部分工作由 prepare4Parallel 函数完成。该函数会启动一个 Data Fetcher,多个 Partial Worker 以及 多个 Final Worker。Partial Worker 和 Final Worker 的数量可以分别通过 tidb_hashgg_partial_concurrency 和 tidb_hashagg_final_concurrency 系统变量进行控制,这两个系统变量的默认值都为 4。DataFetcher 读取子节点的数据并分发给 Partial Workers。这部分工作由 fetchChildData 函数完成。Partial Workers 预聚合计算,及根据 Group Key shuffle 给对应的 Final Workers。这部分工作由 HashAggPartialWorker.run 函数完成。该函数调用 updatePartialResult 函数对 DataFetcher 发来数据执行 预聚合计算,并将预聚合结果存储到 partialResultMap 中。其中 partialResultMap 的 key 为根据 Group-By 的值 encode 的结果,value 为 PartialResult 类型的数组,数组中的每个元素表示该下标处的聚合函数在对应 Group 中的预聚合结果。shuffleIntermData 函数完成根据 Group 值 shuffle 给对应的 Final Worker。Final Worker 计算最终结果,发送给 Main Thread。这部分工作由 HashAggFinalWorker.run 函数完成。该函数调用 consumeIntermData 函数 接收 PartialWorkers 发送来的预聚合结果,进而 合并 得到最终结果。getFinalResult 函数完成发送最终结果给 Main Thread。Main Thread 接收最终结果并返回。TiDB 并行 Hash Aggregation 的性能提升此处以 TPC-H query-17 为例,测试并行 Hash Aggregation 相较于单线程计算时的性能提升。引入并行 Hash Aggregation 前,它的计算瓶颈在 HashAgg_35。该查询执行计划如下:在 TiDB 中,使用 EXPLAIN ANALYZE 可以获取 SQL 的执行统计信息。因篇幅原因此处仅贴出 TPC-H query-17 部分算子的 EXPLAIN ANALYZE 结果。HashAgg 单线程计算时:查询总执行时间 23 分 24 秒,其中 HashAgg 执行时间约 17 分 9 秒。HashAgg 并行计算时(此时 TiDB 层 Partial 和 Final 阶段的 worker 数量都设置为 16):总查询时间 8 分 37 秒,其中 HashAgg 执行时间约 1 分 4 秒。并行计算时,Hash Aggregation 的计算速度提升约 16 倍。 ...

December 21, 2018 · 3 min · jiezi

Vtiger CRM 几处SQL注入漏洞分析,测试工程师可借鉴

本文由云+社区发表0x00 前言干白盒审计有小半年了,大部分是业务上的代码,逻辑的复杂度和功能模块结构都比较简单,干久了收获也就一般,有机会接触一个成熟的产品(vtiger CRM)进行白盒审计,从审计的技术难度上来说,都比公司内的那些业务复杂得多,而真正要提高自己技术水平,更应该看的也是这些代码。vtiger CRM是一个客户关系管理系统。0x01 分析整体结构https://www.vtiger.com/open-source-crm/download-open-source/代码下载下来,本地搭建。使用phpstorm进行审计。主目录下的vtigerversion.php可以查看当前版本。整体代码目录 其中主要得功能实现就在modules目录当中,也是我们重点审计的地方。libraries目录是使用到的第三方的一些东西,includes目录是路由加载,封装系统函数的地方。整个系统代码量确实很多,真要审计完估计没有十天半个月是不行的,看了一个礼拜,只发现几个问题。0x02 modules/Calender/actions/feed.php SQL注入分析一个成熟的产品,审计的难点就在于各种类,对象的封装和继承,A调用B,B调用C,C调用D……Vtiger_BasicAjax_Action 这个对象,是modules下vtiger目录里的,而vtiger这个也是核心的module.回到feed.php,直接定位有漏洞的代码,103行后。我图中标的,也正是注入点的位置。$fieldName参数由逗号分割成数组,如果分成后的数组值为2则进入逻辑,然后参数进入SQL语句形成注入。虽然整个系统采用了PDO的查询方式,但是如果有SQL语句存在直接拼接的话,还是有注入的风险。这里payload不能使用逗号,可以采用 (select user())a join的方法绕过。往下走的话,SQL注入漏洞更是多不胜数。也没有再看的必要了。0x03 /modules/Documents/models/ListView.php SQL注入直接看漏洞代码可以看到sortorder参数又是直接拼接。此处是order by后的注入,只能用基于时间的盲注。直接上SQLmap吧,但是sqlmap的payload会使用>,尖括号因为xss防御,已经被过滤所以需要使用绕过脚本。 –tamper greatest 绕过。poc:index.php?module=Documents&parent=&page=1&view=List&viewname=22&orderby=filename&sortorder=and/**/sleep(5)&app=MARKETING&search_params=[]&tag_params=[]&nolistcache=0&list_headers=[%22notes_title%22,%22filename%22,%22modifiedtime%22,%22assigned_user_id%22,%22filelocationtype%22,%22filestatus%22]&tag=0x04 写在最后由于时间原因,只看了前几个模块,还有好多地方没有看。漏洞都很简单,真正花费时间的是走通逻辑,验证漏洞,不停地跳转查看函数调用,和各种类对象的继承。这也是白盒审计的头疼之处,要忍着性子看开发跳来跳去,没准哪个地方就跳错了。有点难受,还没找到getshell的地方。此文已由作者授权腾讯云+社区发布

December 21, 2018 · 1 min · jiezi

十分钟成为 Contributor 系列 | 支持 AST 还原为 SQL

作者:赵一霖背景知识SQL 语句发送到 TiDB 后首先会经过 parser,从文本 parse 成为 AST(抽象语法树),AST 节点与 SQL 文本结构是一一对应的,我们通过遍历整个 AST 树就可以拼接出一个与 AST 语义相同的 SQL 文本。对 parser 不熟悉的小伙伴们可以看 TiDB 源码阅读系列文章(五)TiDB SQL Parser 的实现。为了控制 SQL 文本的输出格式,并且为方便未来新功能的加入(例如在 SQL 文本中用 “*” 替代密码),我们引入了 RestoreFlags 并封装了 RestoreCtx 结构(相关源码):// RestoreFlags 中的互斥组:// [RestoreStringSingleQuotes, RestoreStringDoubleQuotes]// [RestoreKeyWordUppercase, RestoreKeyWordLowercase]// [RestoreNameUppercase, RestoreNameLowercase]// [RestoreNameDoubleQuotes, RestoreNameBackQuotes]// 靠前的 flag 拥有更高的优先级。const ( RestoreStringSingleQuotes RestoreFlags = 1 << iota …)// RestoreCtx is Restore context to hold flags and writer.type RestoreCtx struct { Flags RestoreFlags In io.Writer}// WriteKeyWord 用于向 ctx 中写入关键字(例如:SELECT)。// 它的大小写受 RestoreKeyWordUppercase,RestoreKeyWordLowercase 控制func (ctx *RestoreCtx) WriteKeyWord(keyWord string) { …}// WriteString 用于向 ctx 中写入字符串。// 它是否被引号包裹及转义规则受 RestoreStringSingleQuotes,RestoreStringDoubleQuotes,RestoreStringEscapeBackslash 控制。func (ctx *RestoreCtx) WriteString(str string) { …}// WriteName 用于向 ctx 中写入名称(库名,表名,列名等)。// 它是否被引号包裹及转义规则受 RestoreNameUppercase,RestoreNameLowercase,RestoreNameDoubleQuotes,RestoreNameBackQuotes 控制。func (ctx *RestoreCtx) WriteName(name string) { …}// WriteName 用于向 ctx 中写入普通文本。// 它将被直接写入不受 flag 影响。func (ctx *RestoreCtx) WritePlain(plainText string) { …}// WriteName 用于向 ctx 中写入普通文本。// 它将被直接写入不受 flag 影响。func (ctx *RestoreCtx) WritePlainf(format string, a …interface{}) { …}我们在 ast.Node 接口中添加了一个 Restore(ctx *RestoreCtx) error 函数,这个函数将当前节点对应的 SQL 文本追加至参数 ctx 中,如果节点无效则返回 error。type Node interface { // Restore AST to SQL text and append them to ctx. // return error when the AST is invalid. Restore(ctx *RestoreCtx) error …}以 SQL 语句 SELECT column0 FROM table0 UNION SELECT column1 FROM table1 WHERE a = 1 为例,如下图所示,我们通过遍历整个 AST 树,递归调用每个节点的 Restore() 方法,即可拼接成一个完整的 SQL 文本。值得注意的是,SQL 文本与 AST 是一个多对一的关系,我们不可能从 AST 结构中还原出与原 SQL 完全一致的文本,因此我们只要保证还原出的 SQL 文本与原 SQL 语义相同 即可。所谓语义相同,指的是由 AST 还原出的 SQL 文本再被解析为 AST 后,两个 AST 是相等的。我们已经完成了接口设计和测试框架,具体的Restore() 函数留空。因此只需要选择一个留空的 Restore() 函数实现,并添加相应的测试数据,就可以提交一个 PR 了!实现 Restore() 函数的整体流程请先阅读 Proposal、Issue在 Issue 中找到未实现的函数在 Issue-pingcap/tidb#8532 中找到一个没有被其他贡献者认领的任务,例如 ast/expressions.go: BetweenExpr。在 pingcap/parser 中找到任务对应文件 ast/expressions.go。在文件中找到 BetweenExpr 结构的 Restore 函数:// Restore implements Node interface.func (n *BetweenExpr) Restore(ctx *RestoreCtx) error { return errors.New(“Not implemented”)}实现 Restore() 函数根据 Node 节点结构和 SQL 语法实现函数功能。参考 MySQL 5.7 SQL Statement Syntax写单元测试参考示例在相关文件下添加单元测试。运行 make test,确保所有的 test case 都能跑过。提交 PRPR 标题统一为:parser: implement Restore for XXX 请在 PR 中关联 Issue: pingcap/tidb#8532示例这里以实现 BetweenExpr 的 Restore 函数 PR 为例,进行详细说明:首先看 ast/expressions.go:我们要实现一个 ast.Node 结构的 Restore 函数,首先清楚该结构代表什么短语,例如 BetweenExpr 代表 expr [NOT] BETWEEN expr AND expr (参见:MySQL 语法 - 比较函数和运算符)。观察 BetweenExpr 结构:// BetweenExpr is for “between and” or “not between and” expression.type BetweenExpr struct { exprNode // 被检查的表达式 Expr ExprNode // AND 左侧的表达式 Left ExprNode // AND 右侧的表达式 Right ExprNode // 是否有 NOT 关键字 Not bool}3. 实现 BetweenExpr 的 Restore 函数:// Restore implements Node interface.func (n *BetweenExpr) Restore(ctx *RestoreCtx) error { // 调用 Expr 的 Restore,向 ctx 写入 Expr if err := n.Expr.Restore(ctx); err != nil { return errors.Annotate(err, "An error occurred while restore BetweenExpr.Expr") } // 判断是否有 NOT,并写入相应关键字 if n.Not { ctx.WriteKeyWord(" NOT BETWEEN ") } else { ctx.WriteKeyWord(" BETWEEN ") } // 调用 Left 的 Restore if err := n.Left.Restore(ctx); err != nil { return errors.Annotate(err, "An error occurred while restore BetweenExpr.Left") } // 写入 AND 关键字 ctx.WriteKeyWord(" AND ") // 调用 Right 的 Restore if err := n.Right.Restore(ctx); err != nil { return errors.Annotate(err, "An error occurred while restore BetweenExpr.Right ") } return nil}接下来给函数实现添加单元测试, ast/expressions_test.go:// 添加测试函数func (tc *testExpressionsSuite) TestBetweenExprRestore(c *C) { // 测试用例 testCases := []NodeRestoreTestCase{ {“b between 1 and 2”, “b BETWEEN 1 AND 2”}, {“b not between 1 and 2”, “b NOT BETWEEN 1 AND 2”}, {“b between a and b”, “b BETWEEN a AND b”}, {“b between ’’ and ‘b’”, “b BETWEEN ’’ AND ‘b’”}, {“b between ‘2018-11-01’ and ‘2018-11-02’”, “b BETWEEN ‘2018-11-01’ AND ‘2018-11-02’”}, } // 为了不依赖父节点实现,通过 extractNodeFunc 抽取待测节点 extractNodeFunc := func(node Node) Node { return node.(*SelectStmt).Fields.Fields[0].Expr } // Run Test RunNodeRestoreTest(c, testCases, “select %s”, extractNodeFunc)}至此 BetweenExpr 的 Restore 函数实现完成,可以提交 PR 了。为了更好的理解测试逻辑,下面我们看 RunNodeRestoreTest:// 下面是测试逻辑,已经实现好了,不需要 contributor 实现func RunNodeRestoreTest(c *C, nodeTestCases []NodeRestoreTestCase, template string, extractNodeFunc func(node Node) Node) { parser := parser.New() for _, testCase := range nodeTestCases { // 通过 template 将测试用例拼接为完整的 SQL sourceSQL := fmt.Sprintf(template, testCase.sourceSQL) expectSQL := fmt.Sprintf(template, testCase.expectSQL) stmt, err := parser.ParseOneStmt(sourceSQL, “”, “”) comment := Commentf(“source %#v”, testCase) c.Assert(err, IsNil, comment) var sb strings.Builder // 抽取指定节点并调用其 Restore 函数 err = extractNodeFunc(stmt).Restore(NewRestoreCtx(DefaultRestoreFlags, &sb)) c.Assert(err, IsNil, comment) // 通过 template 将 restore 结果拼接为完整的 SQL restoreSql := fmt.Sprintf(template, sb.String()) comment = Commentf(“source %#v; restore %v”, testCase, restoreSql) // 测试 restore 结果与预期一致 c.Assert(restoreSql, Equals, expectSQL, comment) stmt2, err := parser.ParseOneStmt(restoreSql, “”, “”) c.Assert(err, IsNil, comment) CleanNodeText(stmt) CleanNodeText(stmt2) // 测试解析的 stmt 与原 stmt 一致 c.Assert(stmt2, DeepEquals, stmt, comment) }}**不过对于 ast.StmtNode(例如:ast.SelectStmt)测试方法有些不一样,由于这类节点可以还原为一个完整的 SQL,因此直接在 parser_test.go 中测试。**下面以实现 UseStmt 的 Restore 函数 PR 为例,对测试进行说明:Restore 函数实现过程略。给函数实现添加单元测试,参见 parser_test.go:在这个示例中,只添加了几行测试数据就完成了测试:// 添加 testCase 结构的测试数据{“use select”, true, “USE select”},{“use sel``ect”, true, “USE sel``ect”},{“use select”, false, “USE select”},我们看 testCase 结构声明:type testCase struct { // 原 SQL src string // 是否能被正确 parse ok bool // 预期的 restore SQL restore string}测试代码会判断原 SQL parse 出 AST 后再还原的 SQL 是否与预期的 restore SQL 相等,具体的测试逻辑在 parser_test.go 中 RunTest()、RunRestoreTest() 函数,逻辑与前例类似,此处不再赘述。加入 TiDB Contributor Club,无门槛参与开源项目,改变世界从这里开始吧(萌萌哒)。 ...

December 21, 2018 · 4 min · jiezi

TiDB EcoSystem Tools 原理解读系列(二)TiDB-Lightning Toolset 介绍

简介TiDB-Lightning Toolset 是一套快速全量导入 SQL dump 文件到 TiDB 集群的工具集,自 2.1.0 版本起随 TiDB 发布,速度可达到传统执行 SQL 导入方式的至少 3 倍、大约每小时 100 GB,适合在上线前用作迁移现有的大型数据库到全新的 TiDB 集群。设计TiDB 从 2017 年开始提供全量导入工具 Loader,它以多线程操作、错误重试、断点续传以及修改一些 TiDB 专属配置来提升数据导入速度。然而,当我们全新初始化一个 TiDB 集群时,Loader 这种逐条 INSERT 指令在线上执行的方式从根本上是无法尽用性能的。原因在于 SQL 层的操作有太强的保证了。在整个导入过程中,TiDB 需要:保证 ACID 特性,需要执行完整的事务流程。保证各个 TiKV 服务器数据量平衡及有足够的副本,在数据增长的时候需要不断的分裂、调度 Regions。这些动作确保 TiDB 整段导入的期间是稳定的,但在导入完毕前我们根本不会对外提供服务,这些保证就变成多此一举了。此外,多线程的线上导入也代表资料是乱序插入的,新的数据范围会与旧的重叠。TiKV 要求储存的数据是有序的,大量的乱序写入会令 TiKV 要不断地移动原有的数据(这称为 Compaction),这也会拖慢写入过程。TiKV 是使用 RocksDB 以 KV 对的形式储存数据,这些数据会压缩成一个个 SST 格式文件。TiDB-Lightning Toolset使用新的思路,绕过SQL层,在线下将整个 SQL dump 转化为 KV 对、生成排好序的 SST 文件,然后直接用 Ingestion 推送到 RocksDB 里面。这样批量处理的方法略过 ACID 和线上排序等耗时步骤,让我们提升最终的速度。架构TiDB-Lightning Toolset 包含两个组件:tidb-lightning 和 tikv-importer。Lightning 负责解析 SQL 成为 KV 对,而 Importer 负责将 KV 对排序与调度、上传到 TiKV 服务器。为什么要把一个流程拆分成两个程式呢?Importer 与 TiKV 密不可分、Lightning 与 TiDB 密不可分,Toolset 的两者皆引用后者为库,而这样 Lightning 与 Importer 之间就出现语言冲突:TiKV 是使用 Rust 而 TiDB 是使用 Go 的。把它们拆分为独立的程式更方便开发,而双方都需要的 KV 对可以透过 gRPC 传递。分开 Importer 和 Lightning 也使横向扩展的方式更为灵活,例如可以运行多个 Lightning,传送给同一个 Importer。以下我们会详细分析每个组件的操作原理。LightningLightning 现时只支持经 mydumper 导出的 SQL 备份。mydumper 将每个表的内容分别储存到不同的文件,与 mysqldump 不同。这样不用解析整个数据库就能平行处理每个表。首先,Lightning 会扫描 SQL 备份,区分出结构文件(包含 CREATE TABLE 语句)和数据文件(包含 INSERT 语句)。结构文件的内容会直接发送到 TiDB,用以建立数据库构型。然后 Lightning 就会并发处理每一张表的数据。这里我们只集中看一张表的流程。每个数据文件的内容都是规律的 INSERT 语句,像是:INSERT INTO tbl VALUES (1, 2, 3), (4, 5, 6), (7, 8, 9); INSERT INTO tbl VALUES (10, 11, 12), (13, 14, 15), (16, 17, 18);INSERT INTO tbl VALUES (19, 20, 21), (22, 23, 24), (25, 26, 27);Lightning 会作初步分析,找出每行在文件的位置并分配一个行号,使得没有主键的表可以唯一的区分每一行。此外亦同时将文件分割为大小差不多的区块(默认 256 MiB)。这些区块也会并发处理,让数据量大的表也能快速导入。以下的例子把文件以 20 字节为限分割成 5 块:Lightning 会直接使用 TiDB 实例来把 SQL 转换为 KV 对,称为「KV 编码器」。与外部的 TiDB 集群不同,KV 编码器是寄存在 Lightning 进程内的,而且使用内存存储,所以每执行完一个 INSERT 之后,Lightning 可以直接读取内存获取转换后的 KV 对(这些 KV 对包含数据及索引)。得到 KV 对之后便可以发送到 Importer。Importer因异步操作的缘故,Importer 得到的原始 KV 对注定是无序的。所以,Importer 要做的第一件事就是要排序。这需要给每个表划定准备排序的储存空间,我们称之为 engine file。对大数据排序是个解决了很多遍的问题,我们在此使用现有的答案:直接使用 RocksDB。一个 engine file 就相等于本地的 RocksDB,并设置为优化大量写入操作。而「排序」就相等于将 KV 对全写入到 engine file 里,RocksDB 就会帮我们合并、排序,并得到 SST 格式的文件。这个 SST 文件包含整个表的数据和索引,比起 TiKV 的储存单位 Regions 实在太大了。所以接下来就是要切分成合适的大小(默认为 96 MiB)。Importer 会根据要导入的数据范围预先把 Region 分裂好,然后让 PD 把这些分裂出来的 Region 分散调度到不同的 TiKV 实例上。最后,Importer 将 SST 上传到对应 Region 的每个副本上。然后通过 Leader 发起 Ingest 命令,把这个 SST 文件导入到 Raft group 里,完成一个 Region 的导入过程。我们传输大量数据时,需要自动检查数据完整,避免忽略掉错误。Lightning 会在整个表的 Region 全部导入后,对比传送到 Importer 之前这个表的 Checksum,以及在 TiKV 集群里面时的 Checksum。如果两者一样,我们就有信心说这个表的数据没有问题。一个表的 Checksum 是透过计算 KV 对的哈希值(Hash)产生的。因为 KV 对分布在不同的 TiKV 实例上,这个 Checksum 函数应该具备结合性;另外,Lightning 传送 KV 对之前它们是无序的,所以 Checksum 也不应该考虑顺序,即服从交换律。也就是说 Checksum 不是简单的把整个 SST 文件计算 SHA-256 这样就了事。我们的解决办法是这样的:先计算每个 KV 对的 CRC64,然后用 XOR 结合在一起,得出一个 64 位元的校验数字。为减低 Checksum 值冲突的概率,我们目时会计算 KV 对的数量和大小。若速度允许,将来会加入更先进的 Checksum 方式。总结和下一步计划从这篇文章大家可以看到,Lightning 因为跳过了一些复杂、耗时的步骤使得整个导入进程更快,适合大数据量的初次导入,接下来我们还会做进一步的改进。提升导入速度现时 Lightning 会原封不动把整条 SQL 命令抛给 KV 编码器。所以即使我们省去执行分布式 SQL 的开销,但仍需要进行解析、规划及优化语句这些不必要或未被专门化的步骤。Lightning 可以调用更底层的 TiDB API,缩短 SQL 转 KV 的行程。并行导入另一方面,尽管我们可以不断的优化程序代码,单机的性能总是有限的。要突破这个界限就需要横向扩展:增加机器来同时导入。如前面所述,只要每套 TiDB-Lightning Toolset 操作不同的表,它们就能平行导进同一个集群。可是,现在的版本只支持读取本机文件系统上的 SQL dump,设置成多机版就显得比较麻烦了(要安装一个共享的网络盘,并且手动分配哪台机读取哪张表)。我们计划让 Lightning 能从网路获取 SQL dump(例如通过 S3 API),并提供一个工具自动分割数据库,降低设置成本。在线导入TiDB-Lightning 在导入时会把集群切换到一个专供 Lightning 写入的模式。目前来说 Lightning 主要用于在进入生产环境之前导入全量数据,所以在此期间暂停对外提供服务还可以接受。但我们希望支持更多的应用场景,例如回复备份、储存 OLAP 的大规模计算结果等等,这些都需要维持集群在线上。所以接下来的一大方向是考虑怎样降低 Lightning 对集群的影响。 ...

December 19, 2018 · 2 min · jiezi

Android 数据库框架 DBFlow 的使用

DBFlow 是一个基于注解处理器开发的使用方便的 ORM Android 数据库,该库简化了很多多余的代码,并且提供了好用的 API 来处理与数据库的交互,让开发者专注 App 的开发。下面将从以下几个方面来学习 DBFlow 数据库框架的使用,具体如下:DBFlow 的优势配置 DBFlow创建数据库创建表插入数据删除数据更新数据查询数据案例DBFlow 的优势DBFlow 借鉴了一些其他优秀数据库框架的特性,下面是 DBFlow 的优势,具体如下:Extensibility(扩展性):对表类的继承类没有限制,可以是一个普通的 JavaBean,使用时为了方便推荐表类继承 BaseModel 类你可以扩展不同包中的非模型类,并将它们用作数据库表,此外,你可以将其他表的子类加入到 @Column 中,并且它们可以位于不同的包中。Speed(速度):该库基于 Java 的注解处理器生成,使用它对运行时性能几乎没有任何影响(反射仅仅用于生数据库模块的生成),可以节省生成样板代码的时间,支持模型缓存(多主键模型),在可能的情况下比原生的 SQLite 速度要快,支持懒加载、@ForeignKey、@OneToMany等使得查询更有效率。SQLite Query Flow(SQLite查询流):DBFlow 的查询尽可能的贴近原生 SQLite 查询,如:select(name, screenSize).from(Android.class).where(name.is(“Nexus 5x”)).and(version.is(6.0)).querySingle()Open Source(开源):DBFlow 时开源的,开源地址:GithubRobust(健壮性):支持 Trigger, ModelView, Index, Migration 以及内置的管理数据库的方式, 此外,还支持 SQLCipher, RXJava 等Multiple Databases, Multiple Modules(多数据库、多模型):无缝支持多数据库文件以及使用 DBFlow 的其他依赖中的数据库模型Built On SQLite(基于 SQLite):SQLite 是世界上使用最广泛的数据库引擎,它不仅限于某个平台。配置 DBFlow因为 DBFlow 任然不是官方发布的,你需要在项目的 build.gradle 文件中进行如下配置,具体如下:allprojects { repositories { jcenter() maven { url “https://jitpack.io” } }}然后,在 Module 对应的 build.gradle 文件中添加依赖,具体如下://为了方便可使用 def 关键字定义版本号def dbFlow_version = “4.2.4"dependencies { //… annotationProcessor “com.github.Raizlabs.DBFlow:dbflow-processor:${dbFlow_version}” compile “com.github.Raizlabs.DBFlow:dbflow-core:${dbFlow_version}” compile “com.github.Raizlabs.DBFlow:dbflow:${dbFlow_version}"}上面代码中的依赖只是针对于 Java,如果你要使用 Kotlin、RxJava 等要配置相对应的依赖即可。注意:升级新版本的 DBFlow 时,一定要删除旧版本的依赖,因为新旧版本的注解处理器可能不同,如果未移除旧版本,将会报如下错误,具体如下:java.lang.NoSuchMethodError: com.raizlabs.android.dbflow.annotation.Table.tableName()Ljava/lang/String然后,自定义 Application ,在相应的 onCreate() 方法中初始化 DBFlow,具体如下:/** * 自定义Application * @author jzman * create at 2018/4/16 0016 17:28 /public class MyApplication extends Application{ @Override public void onCreate() { super.onCreate(); //初始化DBFlow FlowManager.init(new FlowConfig.Builder(this).build()); //设置日志显示 FlowLog.setMinimumLoggingLevel(FlowLog.Level.V); }}最后,在 AndroidManifest.xml 文件中使用自定义的 Application,具体如下:<application android:name=".app.MyApplication” // …</application>此时,DBFlow 就引入当前项目中咯。创建数据库创建一个类并使用 @Database 注解来定义自己的数据库,该类应该要定义数据库的名称和数据库的版本,具体如下:/* * MyDatabase * @author jzman * create at 2018/4/17 0017 9:08 /@Database(name = MyDatabase.NAME, version = MyDatabase.VERSION)public class MyDatabase { //数据库名称 public static final String NAME = “MyDatabase”; //数据库版本号 public static final int VERSION = 1;}注意:如果以后要修改任意表的结构,为避免与旧版本数据库冲突一定要修改版本号,且保证版本号只升不降。创建表在已经创建好数据库的前提下就可以创建表了,表的模型类一般需要继承 BaseModel,并为模型类中的每个字段添加 @Column 注解,该注解将映射模型类的字段到对应表中的列,定义一张表具体如下:/* * NoteTable.java * @author jzman * create at 2018/4/17 0017 9:54 /@Table(database = MyDatabase.class)public class NoteTable extends BaseModel { @Column @PrimaryKey int id; @Column private String title; @Column private String date; @Column private String content; public String getTitle() { return title; } public void setTitle(String title) { this.title = title; } public String getDate() { return date; } public void setDate(String date) { this.date = date; } public String getContent() { return content; } public void setContent(String content) { this.content = content; }}注意:在一张表中至少必须定义一个字段作为主键(primary key),如果模型类中某个字段是私有的,一定要定义相应的 getter、setter 方法,否则会在创建表的环节失败,表的命名要使用驼峰命名法,否则可能会出现如此下问题:java.lang.IllegalArgumentException: expected type but was null插入数据使用 DBFlow 插入数据常用的有二种方式,具体如下:model.insert()SQLite.insert()前者用于单个模型类对象的的插入,创建完具体的对象后,调用 model.insert() 即可插入该对象所对应的记录;后者使用 SQLite Wrapper Language 来插入数据,类似于原生的 insert 语句,支持多列数据的插入,使用起来比较方便,具体参考如下:/* * 插入数据 * @param model /public void inseartData(NoteBean model){ //1.model,insert() model.setTitle(“title”); model.setDate(“2018-04-17”); model.setContent(“content”); model.insert(); //2.SQLite.insert() SQLite.insert(NoteBean.class) .columns(NoteBean_Table.title,NoteBean_Table.date,NoteBean_Table.content) .values(“title”,“2018-04-17”,“content”) .execute();}删除数据使用 DBFlow 删除数据常用的有二种方式,具体如下:model.delete():删除某条记录SQLite.delete():根据条件删除前者用于单个模型类对象的的删除,创建完具体的对象后,调用 model.delete() 即可删除该对象所对应的记录;后者使用 SQLite Wrapper Language 来条件删除数据,类似于原生的 delete 语句,使用起来比较方便,具体参考如下:/* * 删除数据 * @param model /public void deleteData(NoteBean model){ //1.model.delete() model.delete(); //2.SQLite.delete() SQLite.delete(NoteBean.class) .where(NoteBean_Table.title.is(“title”)) .and(NoteBean_Table.id.is(10)) .async() .execute(); //删除整张表 Delete.table(NoteBean.class); //删除多张表 Delete.table(NoteBean.class,NoteBean1.class);}更新数据使用 DBFlow 删除数据常用的有二种方式,具体如下:model.update():更新某条记录SQLite.update():根据条件更新某条记录前者用于单个模型类对象的的更新,创建完具体的对象后,调用 model.update() 即可更新该对象所对应的记录;后者使用 SQLite Wrapper Language 来条件删除数据,类似于原生的 update 语句,使用起来比较方便,具体参考如下:/* * 更新数据 * @param model /public void updateData(NoteBean model) { //1.model.update() model.update(); //2.SQLite.update() SQLite.update(NoteBean.class) .set(NoteBean_Table.title.eq(“title”), NoteBean_Table.content.eq(“content”)) .where(NoteBean_Table.id.is(10)) .async() .execute();}查询数据查询使用 SQLite.select() 方法,查询还有许多可以作为条件的关键字,这里就不在赘述了,下面是一个开发者做常用的查询,具体参考如下:/* * 查询数据 */public List<NoteBean> queryData(){ //根据条件查询 List<NoteBean> noteBeans = SQLite.select() .from(NoteBean.class) .where(NoteBean_Table.title.is(“title”)) .queryList(); return noteBeans;}注意:对于插入、更新操作可以使用 model.save() 方法。案例上文中介绍了 DBFlow 的配置以及增删改查等基本操作,DbFlow 还有其他比较高级的用法,比如使用 Transactions 来进行数据的安全操作等,下面写一个简单的案例来结束对 DBFlow 的学习,具体效果如下:更多关于 DBFlow 的知识请参考 DBFlow 的 GitBook 。可以关注微信公众号:jzman-blog,一起交流学习。 ...

December 19, 2018 · 2 min · jiezi

十分钟构建双十一交互分析大盘

提到双十一很多人第一印象是一张成交金额跳动的炫酷大屏,的确大盘在阿里双十一中几乎是每个团队标配,例如:CEO看业务:把重要数据放到一张大屏上,简洁而有冲击力运营看效果:把多个指标放在一起,有利于综合分析定制投放策略开发看流量:服务请求延时,排队情况,掌握实时性能与动态监控看水位:集中监控整体服务的状态,有利于快速的做出响应大盘方案选型典型方案是流式计算架构:数据采集:利用Agent、API、SDK等采集各源头数据中间存储:利用类Kafka软件进行生产系统和消费系统解耦实时计算:环节中最重要环节,订阅实时数据,通过计算规则对窗口中数据进行运算结果存储:计算结果数据存入SQL和NoSQL可视化:通过API调用结果数据进行展示在阿里集团内,有大量成熟的产品可以完成此类工作,一般可供选型的产品如下:对大盘进一步诉求虽然前期做了大量准备工作,但运营、研发和运维等除了看大盘外,还会围绕整个活动的运行做大量工作,例如:运营对每个类目的细节,下单数目,用户量预期水平等进行大量分析,对某些用户群体激活和促销研发关注请求流量,用户延时体验,定位和分析各种原因运维分析系统的水位,查看各资源的分布,调度资源以确保稳定性从上述例子中可以看到,除了数据的呈现之外,我们需要对大盘融入“交互式分析”的能力。给大家介绍一种选择:通过日志服务(LOG,原SLS)一站式的查询分析LogSearch/Analytics API 直接对接可视化大屏。 大屏的选择可选方案有日志服务Dashboard,Grafana,Datav等,也可以通过API、JDBC接口对接自己的可视化大屏和第三方的软件(例如Tableua)。日志㐏对三种大屏提供了插件,只需在配置中直接使用SQL进行计算,并展示结果。日志服务Dashboard主要面需要交互式分析的查询需求,例如发现错误时,需要下钻定位原因。跟踪到某个类目中查看细节,对比同比与环比数据。该方案有如下特点:实时性强从数据产生,秒级别即可在日志服务看到数据。动态刷新,秒级别即可计算出指标,展示在大屏上。秒级精度日志时间精确到秒级别。灵活查询使用SQL进行交互查询,可以进行探索式分析,快速进行假设和验证。可反复在原始数据上进行任意维度的计算。而流计算在计算完原始数据后,即抛弃了原始数据,若想回溯调整查询,基本不可能。所以日志服务的交互式查询可谓灵活很多。机器学习支持时序类机器学习函数,帮助发现业务规律与趋势分类与聚类函数,帮助发现与定位异常使用步骤以日志服务dashboard对接为例,要对接一个大屏,首先要接入数据,然后编写SQL,配置仪表盘视图。接入数据日志服务提供30+数据接入手段可以满足各种数据源诉求,具体参考文档。调试SQL+机器学习函数我们在日志服务查询页面,通过SQL语法,计算出需要的指标。SQL语法参考语法文档。配置视图并保存在配置好的大屏中,使用下钻深入分析结果:日志服务控制台内置14+类型视图,用于可视化展示SQL计算结果,具体参考文档基于日志服务大盘案例从日志到双十一大屏只要一步:LOG/SLS+DataV 打通DataV无缝支持LOG API,使用SQL进行实时计算,统计Nginx日志的PV、UV网络等指标。图:datav大屏5分钟搭建网站实时分析:Grafana+日志服务实战图:grafana大屏日志服务Nginx dashboard:使用日志服务内置的地图、饼图、折线图等可视化Nginx日志指标图:日志服务dashboardPangu 2.0 秒级监控Pangu2.0是阿里云自研的新一代普惠智能新存储系统。Pangu基于日志服务搭建秒级监控,Pangu 日志产生后,秒级采集到日志服务,并在秒级别计算出各个机器的IOPS、延时、吞吐。可迅速发现负载高的机器,及时跟进处理。Fuxi 双十一资源画像为混部保驾护航资源调度大屏:伏羲是阿里云自研的分布式调度系统。在双十一期间,为了监控所有集群的容量、负载信息,伏羲团队搭建了基于日志服务+dataV的可视化大屏。一张大屏囊括了所有的集群信息,看到这张大屏,就像拥有了一张地图,在双十一波涛汹涌的流量面前,做到胸有成竹。

December 18, 2018 · 1 min · jiezi

让 TiDB 访问多种数据源 | TiDB Hackathon 优秀项目分享

本文作者是来自 CC 组的兰海同学,他们的项目《让 TiDB 访问多种数据源》在本届 TiDB Hackathon 2018 中获得了二等奖。该项目可以让 TiDB 支持多种外部数据源的访问,针对不同数据源的特点会不同的下推工作,使 TiDB 成为一个更加通用的数据库查询优化和计算平台。我们队伍是由武汉大学在校学生组成。我们选择的课题是让 TiDB 接入若干外部的数据源,使得 TiDB 称为一个更加通用的查询优化和计算平台。为什么选这个课题刚开始我们选择课题是 TiDB 执行计划的实时动态可视化。但是填了报名单后,TiDB Robot 回复我们说做可视化的人太多了。我们担心和别人太多冲突,所以咨询了导师的意见,改成了 TiDB 外部数据源访问。这期间也阅读了 F1 Query 和 Calcite 论文,看了东旭哥(PingCAP CTO)在 PingCAP 内部的论文阅读的分享视频。感觉写一个简单 Demo,还是可行的。系统架构和效果展示如上图所示,TiDB 通过 RPC 接入多个不同的数据源。TiDB 发送利用 RPC 发送请求给远端数据源,远端数据源收到请求后,进行查询处理,返回结果。TiDB 拿到返回结果进一步的进行计算处理。我们通过定义一张系统表 foreign_register(table_name,source_type,rpc_info) 记录一个表上的数据具体来自哪种数据源类型,以及对应的 RPC 连接信息。对于来自 TiKV 的我们不用在这个表中写入,默认的数据就是来自 TiKV。我们想访问一张 PostgreSQL(后面简称为 PG)上的表:首先,我们在 TiDB 上定义一个表(记为表 a),然后利用我们 register_foreign(a,postgresql,ip#port#table_name) 注册相关信息。之后我们就可以通过 select * from a 来读取在 PG 上名为 table_name 的表。我们在设计各个数据源上数据访问时,充分考虑各个数据源自身的特点。将合适的操作下推到具体的数据源来做。例如,PG 本身就是一个完整的数据库系统,我们支持投影、条件、连接下推给 PG 来做。Redis 是一个内存键值数据库,我们考虑到其 Get 以及用正则来匹配键值很快,我们将在 Key 值列的点查询以及模糊匹配查询都推给了 Redis 来做,其他条件查询我们就没有进行下推。具体的运行效果如下:如图所示,我们在远程开了 3 个 RPC Server,负责接收 TiDB 执行过程中的外部表请求,并在内部的系统表中进行了注册三张表,并在 TiDB 本地进行了模式的创建——分别是remotecsv,remoteredis,remotepg,还有一张本地 KV Store 上的 localkv 表。我们对 4 张表进行 Join 操作,效果如图所示,说明如下。1. 远程 csv 文件我们不做选择下推,所以可以发现 csv 上的条件还是在 root(即本地)上做。2. 远程的 PG 表,我们会进行选择下推,所以可以发现 PG 表的 selection 被推到了 PG 上。3. 远程的 Redis 表,我们也会进行选择下推,同时还可以包括模型查询条件(Like)的下推。P.S. 此外,对于 PostgreSQL 源上两个表的 Join 操作,我们也做了Join 的下推,Join 节点也被推送到了 PostgreSQL 来做,具体的图示如下:如何做的由于项目偏硬核的,需要充分理解 TiDB 的优化器,执行器等代码细节。所以在比赛前期,我们花了两三天去研读 TiDB 的优化器,执行器代码,弄清楚一个简单的 Select 语句扔进 TiDB 是如何进行逻辑优化,物理优化,以及生成执行器。之前我们对 TiDB 这些细节都不了解,硬着去啃。发现 TiDB 生成完执行器,会调用一个 Open 函数,这个函数还是一个递归调用,最终到 TableReader 才发出数据读取请求,并且已经开始拿返回结果。这个和以前分析的数据库系统还有些不同。前期为了检验我们自己对 TiDB 的执行流程理解的是否清楚,我们尝试这去让 TiDB 读取本地 csv 文件。比赛正式开始,我们一方面完善 csv,不让其进行条件下推,因为我们远端 RPC 没有处理条件的能力,我们修改了逻辑计划的条件下推规则,遇到数据源是 csv 的,我们拒绝条件下推。另一方面,首先得啃下硬骨头 PostgreSQL。我们考虑了两种方案,第一种是拿到 TiDB 的物理计划后,我们将其转换为 SQL,然后发给 PG;第二种方案我们直接将 TiDB 的物理计划序列化为 PG 的物理计划,发给 PG。我们考虑到第二种方案需要给 PG 本身加接受物理计划的钩子,就果断放弃。可能两天都费在该 PG 代码上了。我们首先实现了 select * from pgtable。主要修改了增加 pgSelectResult 结构体实现对应的结构体。通过看该结构体以及其对应接口函数,大家就知道如何去读取一个数据源上的数据,以及是如何做投影下推。修改 Datasource 数据结构增加对数据源类型,RPC 信息,以及条件字符串,在部分物理计划内,我们也增加相关信息。同时根据数据源信息,在 (e*TableReaderExecutor)buildResp 增加对来源是 PG 的表处理。接着我们开始尝试条件下推:select * from pgtable where … 将 where 推下去。我们发现第一问题:由于我们的注册表里面没有记录外部源数据表的模式信息导致,下推去构建 SQL 的时候根本拿不到外部数据源 PG 上正确的属性名。所以我们暂时保证 TiDB 创建的表模式与 PG 创建的表模式完全一样来解决这个问题。条件下推,我们对条件的转换为字符串在函数 ExpressionToString 中,看该函数调用即可明白是如何转换的。当前我们支持等于、大于、小于三种操作符的下推。很快就到了 1 号下午了,我们主要工作就是进行 Join下推 的工作。Join 下推主要是当我们发现两个 Join 的表都来来自于同一个 PG 实例时,我们就将该 Join 下推给 PG。我们增加一种 Join 执行器:PushDownJoinExec。弄完 Join 已经是晚上了。而且中间还遇到几个 Bug,首先,PG 等数据源没有一条结果满足时的边界条件没有进行检查,其次是,在 Join 下推时,某些情况下 Join 条件未必都是在 On 子句,这个时候需要考虑 Where 子句的信息。最后一个,如果使得连接和条件同时下推没有问题。因为不同表的相同属性需要进行区分。主要难点就是对各个物理计划的结构体中的解析工作。到了晚上,我们准备开始着手接入 Redis。考虑到 Redis 本身是 KV 型,对于给定 Key 的 Get 以及给定 Key 模式的匹配是很快。我们直接想到对于 Redis,我们允许 Key 值列上的条件下推,让 Redis 来做过滤。因为 Redis 是 API 形式,我们单独定义一个简单请求协议,来区别单值,模糊,以及全库查询三种基本情况,见 RequestRedis 定义。Redis 整体也像是 PG 一样的处理,主要没有 Join 下推这一个比较复杂的点。我们之后又对 Explain 部分进行修改,使得能够打印能够反映我们现在加入外部数据源后算子真实的执行情况,可以见 explainPlanInRowFormat 部分代码。之后我们开始进行测试每个数据源上的,以及多个数据源融合起来进行测试。不足之处1. 我们很多物理计划都是复用 TiDB 本身的,给物理计划加上很多附属属性。其实最好是将这些物理计划单独抽取出来形成一个,不去复用。2. Cost 没有进行细致考虑,例如对于 Join 下推,其实两张 100 万的表进行 Join 可能使得结果成为 1000 万,那么网络传输的代价反而更大了。这些具体算子下推的代价还需要细致的考虑。比较痛苦的经历1. TiDB 不支持 Create Funtion,我们就只好写内置函数,就把另外一个 Parser 模块拖下来,自己修改加上语法,然后在加上自己设计的内置函数。2. 最痛苦还是两个方面,首先 Golang 语言,我们之前没有用得很多,经常遇到些小问题,例如 interface 的灵活使用等。其次就是涉及的 TiDB 的源码模块很多,从优化器、执行器、内置函数以及各种各样的结构。虽然思路很简单,但是改动的地方都很细节。收获比赛过程中,看到了非常多优秀选手,以及他们酷炫的作业,感觉还是有很长的路要走。Hackathon 的选手都好厉害,听到大家噼里啪啦敲键盘的声音,似乎自己也不觉得有多累了。人啊,逼一下自己,会感受到自己无穷的力量。通过这次活动,我们最终能够灵活使用 Golang 语言,对 TiDB 整体也有了更深入的认识,希望自己以后能够称为 TiDB 的代码贡献者。 最后非常感谢 PingCAP 这次组织的 Hackathon 活动,感谢导师团、志愿者,以及还有特别感谢导师张建的指导。TiDB Hackathon 2018 共评选出六个优秀项目,本系列文章将由这六个项目成员主笔,分享他们的参赛经验和成果。我们非常希望本届 Hackathon 诞生的优秀项目能够在社区中延续下去,感兴趣的小伙伴们可以加入进来哦。 ...

December 14, 2018 · 2 min · jiezi

TiDB Lab 诞生记 | TiDB Hackathon 优秀项目分享

本文由红凤凰粉凤凰粉红凤凰队的成员主笔,他们的项目 TiDB Lab 在本届 TiDB Hackathon 2018 中获得了二等奖。TiDB Lab 为 TiDB 培训体系增加了一个可以动态观测 TiDB / TiKV / PD 细节的动画教学 Lab,让用户可以一边进行真实操作一边观察组件之间的变化,例如 SQL 的解析,Region 的变更等等,从而生动地理解 TiDB 的工作原理。项目简介简介TiDB Lab,全称 TiDB Laboratory,是一个集 TiDB 集群状态的在线实时可视化与交互式教学的平台。用户可以一边对 TiDB 集群各个组件 TiKV、TiDB、PD 进行各种操作,包括上下线、启动关闭、迁移数据、插入查询数据等,一边在 TiDB Lab 上以动画形式观察操作对集群的影响,例如数据是怎么流动的,Region 副本在什么情况下发生了变更等等。通过 TiDB Lab 这种对操作进行可视化反馈的交互模式,用户可以快速且生动地理解 TiDB 内部原理。功能实时动态展示 TiDB、TiKV 节点的新增、启动与关闭。实时动态展示 TiDB 收到 SQL 后,物理算子将具体请求发送给某些 TiKV Region Leader 并获取数据的过程。实时动态展示各个 TiKV 实例上 Region 副本状态的变化,例如新增、删除、分裂。实时动态展示各个 TiKV 实例上 Region 副本内的数据量情况。浏览集群事件历史(事件指上述四条功能所展示的各项内容)并查看事件的详细情况,包括事件的具体数据内容、SQL Plan 等等。按 TiDB、TiKV 或 Region 过滤事件历史。对事件历史进行时间穿梭:回到任意事件发生时刻重新观察当时的集群状态,或按事件单步重放观察集群状态的变化。在线获取常用运维操作的操作指南。愿景我们其实为 TiDB Lab 规划了更大的愿景,但由于 Hackathon 时间关系,还来不及实现。我们希望能实现 TiDB Lab + TiDB 生态组件的沙盒,从而在 TiDB Lab 在线平台上直接提供命令执行与 SQL 执行功能。这样用户无需离开平台,无需自行准备机器下载部署,就可以直接在平台上根据提供的操作指南进行各类操作,并能观察操作带来的具体影响,形成操作与反馈的闭环,真正地实现零门槛浏览器在线教学。我们期望平台能提供用户以下的操作流程:1. 用户获得一个 TiDB Lab 账户并登录(考虑到沙盒是占用实际资源的,需要通过账户许可来限制避免资源快速耗尽)。2. 用户在 TiDB Lab 上获得若干虚拟机器的访问权限,每个机器处于同一内网并具有独立 IP。这些虚拟机器的实际实现是资源受限的虚拟机或沙盒,因为作为教学实验不需要占用很多资源。例如:平台为用户自动分配了 5 个 IP 独立的沙盒的访问权限,地址为 192.168.0.1 ~ 192.168.0.5。3. 用户在平台上进行第零章「架构原理」的学习。平台提供了一个默认的拓扑部署,用户可以在平台提供的在线 SQL Shell 中进行数据的插入、删除、更新等操作。用户通过平台观察到 SQL 是如何对应到 TiKV 存储节点上的,以及数据是怎么切分到不同 Region 的等等。4. 用户在平台上进行第一章「TiDB 部署」的学习,了解到可以通过 ansible 进行部署。教学样例是一个典型的 TiDB + TiKV 三副本部署。对于这个教学样例,平台告知用户 inventory.ini 具体内容应当写成什么样子。用户可以在平台提供的在线 Terminal 上修改 inventory 文件,并执行部署与集群启动命令。部署和启动均能在平台上实时反馈可视化。5. 用户继续进行后续章节学习,例如「TiDB 单一服务启动与关闭」。用户在可视化界面上点击某个刚才已经部署出来的节点,可以了解启动或关闭单个 TiDB 的命令。用户可以在平台提供的在线 Terminal 上执行这些命令,尝试启动或关闭单一 TiDB。6. 用户继续学习基础运维操作,例如「TiKV 扩容」。平台告知 inventory.ini 应当如何进行修改,用户可以根据指南在在线 Terminal 上进行实践,并通过可视化界面观察扩容的过程,例如其他节点上的副本被逐渐搬迁到新节点上。前期准备团队我们团队有三个人,是一个 PingCAP 同学与 TiDB 社区小伙伴钱同学的混合组队,其中 PingCAP 成员分别来自 TiKV 组与 OLAP 组。我们本着搞事情的想法,团队取名叫「红凤凰粉凤凰粉红凤凰」,想围观主持人念团队名称(然而机智的主持人小姐姐让我们自报团队名称)。原计划:干掉 gRPC鉴于从报名开始直到 Hackathon 正式开始前几小时,我们都在为原计划做准备,因此值得详细说一下…我们一开始规划的 Hackathon 项目是换掉 TiKV、TiDB 之间的 RPC 框架 gRPC,原因有几个:一是发现 TiKV、TiDB 中 gRPC 经常占用了大量 CPU,尤其是在请求较多但很简单的 benchmark 场景中经常比 Coprocessor 这块儿还高,这在客户机器 CPU 资源比较少的情况下是性能瓶颈;二是发现 gRPC 性能一般,在各类常用 RPC 框架的性能测试中 gRPC 经常是垫底水平;三是 gRPC 主要设计用于用户产品与 Google 服务进行通讯,因而考虑到了包括负载均衡友好、流量控制等方面,但对 TiKV 与 TiDB 这类内部通讯来说这些都是用不上的功能,为此牺牲的性能是无谓的开销;另外近期 TiKV 内部有一个实验是将多个 RPC 请求 batch 到一起再发送(当然处理时候再拆开一个一个执行原来的 handler),性能可以瞬间提高一倍以上,这也从侧面说明了 gRPC 框架自身开销很大,因为用户侧请求总量是一致的,处理模式也是一致的,唯一的区别就是 RPC 框架批量发送或一条一条发送。我们早在几周前就开始写简单 Echo Server 进行可行性验证和性能测试,是以下三个方面的正交:1. 协议:CapnProto RPC、brpc over gRPC、裸写 echo。2. 服务端:Rust、C++,对应用于 TiKV。3. 客户端:Golang,对应用于 TiDB。其中 TiKV 侧调研了 Rust 和 C++ 的服务端实现,原因是 Rust 可以通过 binding 方式调用 C++ 服务端。而 TiDB 侧客户端实现不包含 C++ 的原因是 Golang 进行 C / C++ FFI 性能很差,因此可以直接放弃 C/C++ 包装一层 binding 用于 TiDB 的想法。最后测试下来,有以下结果:CapnProto:序列化性能很高,但其 RPC 性能没有很突出。最重要的是,CapnProto 的 Golang Client 实现有 bug,并不能稳定地与 Rust 或 C++ 的 Server 进行 RPC 交互。作者回复说这是一个已知缺陷,涉及重构。这对于 Hackathon 来说是一个致命的问题,我们并没有充足的时间解决这个问题,直接导致我们放弃这个方案。brpc over gRPC:可以实现使用 brpc C++ 客户端 & gRPC Golang 服务端配合(注:brpc 没有 Golang 的实现)。但这个方案本质只是替换服务端的实现,并没有替换协议,并不彻底,我们不是特别喜欢。另外在这个换汤不换药的方案下,测试下来性能的提升有限,且随着 payload 越大会越小。我们最终觉得作为一个 Hackathon 项目如果仅有有限的性能提升(虽然可以在展示的时候掩盖缺陷只展示优点),那么意义不是很大,最终无法用于产品,因而放弃。裸写:我们三个成员都不是这方面的老司机,裸写大概是写不完的。。新计划:做一个用于培训的可视化在 Hackathon 开始的前一个晚上,我们决定推翻重来,于是 brain storm 了几个想法,最后觉得做一个用于教学的可视化比较可行,并且具有比较大的实际意义。另外,这个新项目「集群可视化」相比原项目「换 RPC」来说更适合 Hackathon,主要在于:1. 具有图形化界面,容易拿奖可以直观地展现成果。2. 并不是一个「非零即一」的任务。新项目有很多子功能,可以逐一进行实现,很稳妥,且不像老项目那样只有换完才知道效果。我们继续在这个「可视化」的想法上进行了进一步的思考,想到如果可以做成在线教学的模式,则可以进一步扩展其项目意义,形成一个完整的在线教学体系,因此最终决定了项目的 scope。具体实现在 TiKV、TiDB 与 PD 中各个关键路径上将发生的具体「事件」记录下来,并在前端进行一一可视化。事件我们将 TiDB Lab 进行可视化所需要的信号称为「事件」,并规划了以下「事件」:Ansible 事件:Ansible 部署 TiDBAnsible 事件:Ansible 部署 TiKVTiDB 事件:TiDB 启动TiDB 事件:TiDB 关闭TiDB 事件:TiDB 收到一条 SQLTiKV 事件:TiKV 启动TiKV 事件:TiKV 关闭TiKV 事件:TiKV 收到一条 KvGet 请求TiKV 事件:TiKV 收到一条 PreWrite / Commit 请求TiKV 事件:TiKV 收到一条 Coprocessor 请求TiKV 事件:TiKV Region Peer 创建TiKV 事件:TiKV Region Peer 删除TiKV 事件:TiKV Region 分裂TiKV 事件:TiKV Region Snapshot 复制TiKV 事件:TiKV Region 数据量发生显著变化最后,由于时间关系、技术难度和可视化需要,实际实现的是以下事件:TiDB 事件:TiDB 启动,若首次启动认为是新部署TiDB 事件:TiDB 关闭TiDB 事件:TiDB 收到 SQL 并发起 KvGet 读请求TiDB 事件:TiDB 收到 SQL 并发起 PreWrite / Commit 写入请求TiDB 事件:TiDB 收到 SQL 并发起 Coprocessor 读请求TiKV 事件:TiKV 启动,若首次启动认为是新部署TiKV 事件:TiKV 关闭PD 事件:TiKV Region Peer 创建(通过 Region 心跳实现)PD 事件:TiKV Region Peer 删除(通过 Region 心跳实现)PD 事件:TiKV Region 分裂(通过 Region 心跳实现)PD 事件:TiKV Region 数据量发生显著变化(通过 Region 心跳实现)可视化可视化部分由前端(lab-frontend)和事件收集服务(lab-gateway)组成。事件收集服务是一个简单的 HTTP Server,各个组件通过 HTTP Post 方式告知事件,事件收集服务将其通过 WebSocket 协议实时发送给前端。事件收集服务非常简单,使用的是 Node.js 开发,基于 ExpressJs 启动 HTTP Server 并基于 SocketIO 实现与浏览器的实时通讯。ExpressJs 收到事件 JSON 后将其通过 SocketIO 进行广播,总代码仅仅十几行。可视化前端采用 Vue 实现,动画使用 animejs 和 CSS3。通过模板实现的可视化一部分事件通过「由事件更新集群状态数据 – 由集群状态通过 Vue 渲染模板」进行可视化。这类可视化是最简单的,以 TiDB 启动与否为例,TiDB 的启动与否在界面上呈现为一个标签显示为「Started」或「Stopped」,那么就是一个传统的 Vue MVVM 流程:1. 数据变量 instances.x.online 代表 TiDB x 是否已启动。2. 收到 TiDB Started 事件后,更新 instances.x.online = true 。3. 收到 TiDB Stopped 事件后,更新 instances.x.online = false 。4. 前端模板上,根据 instances.x.online 渲染成 Started 或 Stopped 对应的界面。这类可视化的动画采用的是 CSS3 动画。由于 Region Peer 位置是由 left, top CSS 属性给出的,因此为其加上 transition,即可实现 Region Peer 在屏幕上显示的位置改变的动画。位置改变会主要发生在分裂时,分裂时 Region 列表中按顺序会新增一个,那么后面各个 Region 都要向后移动(或换到下一行等)。最后,使用 Vue Group Transition 功能,即可为 Region Peer 的新增与删除也加入动画效果。通过动画实现的可视化另一部分事件并不反应为一个持久化 DOM 的变化,例如 TiDB 收到 SQL 并发请求到某个具体 TiKV 上 Region peer 的事件,在前端展示为一个 TiDB 节点到 TiKV Region Peer 的过渡动画。动画开始前和动画结束后,DOM 没有什么变化,动画是一个临时的可视元素。这类动画通过 animejs 实现。<center>这个浮夸的开场动画效果也是 animjs 做的</center>时间穿梭时间穿梭是一个在目前前端框架中提供的很时髦的功能,我们准备借鉴一波。主要包括:1. 回退到任意一个历史事件发生的时刻展示集群的状态;2. 从当前事件开始往后进行单步可视化重现。时间穿梭的本质是需要实现两个基础操作:1. 对于单一事件实现正向执行,即事件发生后,更新对应集群数据信息(如果采用「通过模板实现的可视化」),或创建临时动画 DOM(如果采用「通过动画实现的可视化」)。另外允许跳过「通过动画实现的可视化」这一步。2. 对于单一事件实现反向执行,即撤销这个事件造成的影响。对于「通过模板实现的可视化」,我们需要根据事件内容反向撤销它对集群数据信息的修改。对于「通过动画实现的可视化」,我们什么都不用做。时间穿梭的功能可以通过组合这两个基础操作实现。1. 回退到任意历史事件发生时刻:若想要前往的事件早于当前呈现的事件,则对于这期间的事件逐一进行反向执行。若想要前往的事件晚于当前呈现的事件,则进行无动画的正向执行。2. 从当前事件开始单步可视化:执行一次有动画的正向执行。3. 实时展示新事件:执行一次有动画的正向执行。TiDB 事件收集TiDB 的历史事件收集略为 Hack。由于需要过滤任何非用户发起的查询(类似 GC 或者 meta 查询会由背景协程频繁发起打扰使用体验),因此在用户链接入口处添加了 context 标记一路携带到执行层,再修改相应的协程同步数据结构添加需要转发的标记信息。比较麻烦的是类似 Point Get 这样接口允许携带信息非常少的调用,只好将标记位编码进 Key 本身了。Plan 的可视化其实并没有花多少功夫,因为找到 TiDB 本身已经做了类似的功能,我们无非只是将这块代码直接偷来了。PD 事件收集原本不少事件希望在 TiKV 端完成侦听,不过显然 KV 一小时编译一次的效率无法满足 Hackathon 中多次试错的需要。因此我们改为在 PD 中侦测心跳和汇报事件。其实并没有什么神秘,在原本 PD 自己检查 Region 变更的代码拆分成 Region 和 Peer 变更:每次 PD 接到 Region 心跳会在 PD Cache 中进行 Version 和 confVer 变更的检测,主要涉及 Peer 的增加和减少等。而 Region Split 会单独由 RegionSplitReport 进行汇报,这里也会做一次 Hook。另外就是每次心跳会检查是否有未上报给 Lab 的 Region 信息,如果有就转换成 Peer 信息进行补发。TiKV 事件收集如上,原本我们计划了很多 TiKV 事件,但由于开发机器配置不佳,每次修改都要等待一小时进行一次编译,考虑到 Hackathon 上时间紧迫,因此最终大大缩减了 TiKV 上收集的事件数量,改为只收集启动和停止。基本架构是事件发生的时候,事件异步发送给一个 Channel,Channel 的另一端有一个异步的 worker 负责不断处理各个事件并通过 Hyper HTTP Client 发送出去。这个流程其实与 TiKV 中汇报 PD 事件有些类似,只是事件内容和汇报目标不一样。感悟以下文字 by 马晓宇 @ OLAP Team, PingCAP“由于比较能调侃干的活相对少一些,所以大王要我来巡山写感悟。”就像队长说的,这个项目原本是希望做一个 bRPC 替换 gRPC 的试验,只是由于种种原因临到 Hackathon 前夜我们才确定这是个大概率翻车的点子。于是乎我们只好在酒店里抓耳挠腮,一边讨论可能的补救措施。参加过、围观过几次 Hackathon,见过现场 Demo 效果最震撼的一次其实并不是一个技术上最优秀的作品,但是它的确赢得了大奖。那是一个脑洞奇大的点子:用 Kinect 体感加上 DirectX 的全 3D 展示做的网络流量实时展示;程序根据实时数据(举办方提供了实时网络测量统计信息)聚合显示不同粗细的炫酷弧光特效,而演示者则用手势操控地球模型的旋转。于是在完全不了解大家是否能搞定前端的情况下,我们还是很轻率地决定了要做个重前端的项目。至于展示什么?既然时间不够,那么秀一个需要生产上线的可视化工具,翻车的概率就大的多,不如直接定位为 For Educational Purpose Only。而且大概是过度解读了炫酷对于成功的重要性,因此队长也轻率地决定了这个项目的基调是「极尽浮夸」。就在这样友好且不靠谱的讨论氛围下,这个项目的策划出炉了。之后就是艰苦卓绝连绵无休的代码过程了。龙毛二哥,晓峰和胡家属的 TiNiuB Team 就在我们对面开工。讲道理这其实是我个人最喜欢的项目之一。第一天放学的时候,看着他们的进度其实我心里虚得不行:看看别人家的项目,我们的绝对主力还在折腾 TiDB Logo 动画,是我敢怒不敢言的状态。原来我想偷一下懒回家睡个觉啥的,就临时改变主意继续配合队长努力干活;钱同学也抱着「TiKV 一天只能 Build 24 次必须珍惜」的态度写着人生的第一个 Rust 项目。因此,这里必须鸣谢龙哥他们对我们的鞭策。事实证明,误打误撞但又深谋远虑的浮夸战略是有效的。Demo 的时候我很认真地盯着评委:在本项目最浮夸的 Logo 展示环节,大家的眼睛是发着光的,一如目睹了摩西分开红海。我个人认为这个动画 Logo 生生拉高了项目 50% 的评分。只是具体要说感悟的话(似乎好多年没写感悟了呢),首先这次我们三个组员虽然有两个是 PingCAP 员工,不过由于技能的缺口大于人力,因此负责的任务都不是自己所属的模块:队长是 TiKV 组的但是在写前端,我是 OLAP 组的但是在改 PD 和 DB,钱同学也在做自己从来没写过的 Rust(参赛前一周简单入门了一下),因此其实还蛮有挑战的(笑)。然后偶尔写写自己不熟悉的语言,搞搞自己不熟悉的模块,会有一种别样的新鲜刺激感,这大概就是所谓的路边野花更香吧(嗯)?除此之外,Hackathon 更像一个大型社交活动,满足了猫一样孤僻的程序员群体被隐藏的社交欲,有利于码农的身心发展,因此可以多搞搞 :) 。回到我们项目本身的话,其实 TiDB 的源码阅读或者其他介绍类文章其实并不能非常直观地帮助一头雾水的初学者理解这个系统。我们做这个项目的最大目的是能降低学习门槛,让所有人能以非常直观,互动的方式近距离理解她。所以希望这个项目能给大家带来方便吧。TiDB Hackathon 2018 共评选出六个优秀项目,本系列文章将由这六个项目成员主笔,分享他们的参赛经验和成果。我们非常希望本届 Hackathon 诞生的优秀项目能够在社区中延续下去,感兴趣的小伙伴们可以加入进来哦。 ...

December 14, 2018 · 4 min · jiezi

如何创建一个数据科学项目?

摘要: 在一个新的数据科学项目,你应该如何组织你的项目流程?数据和代码要放在那里?应该使用什么工具?在对数据处理之前,需要考虑哪些方面?读完本文,会让你拥有一个更加科学的工作流程。假如你想要开始一个新的数据科学项目,比如对数据集进行简单的分析,或者是一个复杂的项目。你应该如何组织你的项目流程?数据和代码要放在那里?应该使用什么工具?在对数据处理之前,需要考虑哪些方面?数据科学是当前一个不太成熟的行业,每个人都各成一家。虽然我们可以在网上参照各种模板项目、文章、博客等创建一个数据科学项目,但是目前也没有教科书对这些知识做一个统一的回答。每个数据科学家都是从经验和错误中不断的探索和学习。现在,我逐渐了解到什么是典型的“数据科学项目”,应该如何构建项目?需要使用什么工具?在这篇文章中,我希望把我的经验分享给你。工作流程尽管数据科学项目的目标、规模及技术所涉及的范围很广,但其基本流程大致如下:如上图所示,项目不同,其侧重点也会有所不同:有些项目的某个过程可能特别复杂,而另一些项目可能就不需要某一过程。举个例子来说,数据科学分析项目通常就不需要“部署”(Deployment)和“监控”(Monitoring)这两个过程。现在,我们逐一来细说各个过程。源数据访问不管是你接触到人类基因组还是iris.csv,通常都会有 “原始源数据”这一概念。数据有很多种形式,可以是固定的,也可以是动态变化的,可以存储在本地或云端。其第一步都是对源数据访问,如下所示:源数据是*.csv文件集合。使用Cookiecutter工具在项目的根文件夹中创建一个data/raw/子目录,并将所有的文件存储在这里;创建docs/data.rst文件描述源数据的含义。源数据是*.csv文件集合。启动SQL服务器,创建一个raw表,将所有的CSV文件作为单独的表导入。创建docs/data.rst文件描述源数据及SQL Server位置。源数据是基因组序列文件、患者记录、excel及word文档组合等,后续还会以不可预测的方式增长。这样可以在云服务器中创建SQL数据库,将表导入。你可以在data/raw/目录存储特别大的基因组序列,在data/raw/unprocessed目录存储excel和word文件;还可以使用DVC创建Amazon S3存储器,并将data/raw/目录推送过去;也可以创建一个Python包来访问外部网站;创建docs/data.rst目录,指定SQL服务器、S3存储器和外部网站。源数据中包含不断更新的网站日志。可以使用ELK stack 并配置网站以流式传输新日志。源数据包含10万张大小为128128像素的彩色图像,所有图像的大小则为100,0001281283,将其保存在HDF5文件images.h5中。创建一个Quilt数据包并将其推送给自己的私人Quilt存储库;创建/docs/data.rst文件,为了使用数据,必须首先使用quilt install mypkg/images导入工作区,然后再使用 from quilt.data.mypkg import images导入到代码中。源数据是模拟数据集。将数据集生成实现为Python类,并在README.txt文件中记录其使用。通常来说,在设置数据源的时候可以遵循以下规则:存储数据的方式有意义,另外还要方便查询、索引。保证数据易于共享,可以使用NFS分区、Amazon S3存储器、Git-LFS存储器、Quilt包等。确保源数据是只读状态,且要备份副本。花一定的时间,记录下所有数据的含义、位置及访问过程。上面这个步骤很重要。后续项目会你可能会犯任何错误,比如源文件无效、误用方法等等,如果没有记住数据的含义、位置及访问过程,那将很麻烦。数据处理数据处理的目的是将数据转化为“干净”的数据,以便建模。在多数情况下,这种“干净”的形式就是一个特征表,因此,“数据处理”通常归结为各种形式的特征工程(feature engineering),其核心要求是:确保特征工程的逻辑可维护,目标数据集可重现,整个管道可以追溯到源数据表述。计算图(computation graph)即满足以上要求。具体例子如下:根据cookiecutter-data-science规则,使用Makefile来描述计算图。通过脚本实现每个步骤,该脚本将一些数据文件作为输入,然后输出一个新的数据文件并存储在项目的data/interim或data/processed目录中。可以使用 make -j <njobs>命令进行并行运算。使用DVC来描述和执行计算图,其过程与上面类似,此外还有共享生成文件等功能。还可以使用Luigi、Airflow或其他专用工作流管理系统来描述和执行计算图。除此之外,还可以在基于web的精美仪表板上查看计算进度。所有源数据都以表的形式存储在SQL数据库中,在SQL视图中实现所有的特征提取逻辑。此外,还可以使用SQL视图来描述对象的样本。然后,你可以根据这些特征和样本视图创建最终的模型数据集。首先,允许用户轻松的跟踪当前所定义的特征,而不用存储在大型数据表中。特征定义仅在代码运行期间有效;其次,模型从部署到生产非常简单,假设实时数据库使用相同的模式,你就只需要复制相应的视图。此外,还可以使用CTE语句将所有的特征定义编译为模型最终预测的单个查询语句。在进行数据处理时,请注意一下问题:1.重复以计算图的形式处理数据。2.考虑计算基础架构。是否进行长时间计算?是否需要并行计算还是聚类?是否可以从具有跟踪任务执行的管理UI作业中获益?3.如果想要将模型部署到生产环境中,请确保系统支持该用例。如果正在开发一个包含JAVA Android应用程序模型,但是还是想用Python开发,为了避免不必要的麻烦,就可以使用一个专门设计的DSL,然后将这个DSL转换为Java或PMML之类的中间格式。4.考虑存储特征或临时计算的元数据。可以将每个特征列保存在单独的文件中,或使用Python函数注释。建模完成数据处理和特征设计后即可开始进行建模。在一些数据科学项目中,建模可以归结为单个m.fit(X,y)或某个按钮;而在其他项目中则可能会涉及数周的迭代和实验。通常来说,你可以从“特征工程”建模开始,当模型的输出构成了很多特征时,数据处理和建模这两个过程并没有明确的界限,它们都涉及到计算。尽管如此,将建模单独列出来作为一个步骤,仍然很有意义,因为这往往会涉及到一个特殊的需求:实验管理(experiment management)。具体例子如下:如果你正在训练一个模型,用于在iris.csv数据集中对Irises进行分类。你需要尝试十个左右的标准sklearn模型,每个模型都有多个不同的参数值,并且测试不同的特征子集。如果你正在设计一个基于神经网络的图像分类模型。你可以使用ModelDB(或其他实验管理工具,如TensorBoard,Sacred,FGLab,Hyperdash,FloydHub,Comet.ML,DatMo,MLFlow,…)来记录学习曲线和实验结果,以便选择最佳的模型。使用Makefile(或DVC、工作流引擎)实现整个管道。模型训练只是计算图中的一个步骤,它输出model-<id>.pkl 文件,将模型最终AUC值附加到CSV文件,并创建 model-<id>.html报告,还有一堆用于评估的模型性能报告。实验管理/模型版本控制的UI外观如下:模型部署在实际应用中,模型最终都要部署到生产环境中,一定要有一个有效的计划,下面有些例子:建模管道输出一个训练过模型的pickle文件。所有的数据访问和特征工程代码都是由一系列Python函数实现。你需要做的就是将模型部署到Python应用程序中,创建一个包含必要函数和模型pickle文件的Python包。管建模道输出一个训练过的模型的pickle文件。部署模型需要使用Flask创建一个REST服务将其打包为一个docker容器,并通过公司的Kubernetes云服务器提供服务。训练管道生成TensorFlow模型。可以将TensorFlow服务当做REST服务。每次更新模型时,都要创建测试并运行。训练管道生成PMML文件。你可以用Java中的JPMML库来读取,一定要确保PMML导出器中要有模型测试。训练管道将模型编译为SQL查询,将SQL查询编码到应用程序中。我们对模型部署做一下总结:1.模型部署的方式有很多种。在部署之前一定要了解实际情况,并提前做计划:是否需要将模型部署到其他语言编写的代码库中?如果使用REST服务,服务的负载时多少?能否进行批量预测?如果打算购买服务,费用是多少?如果决定使用PMML,那么就要确保它能够支持你的预期预处理逻辑。如果在训练期间使用第三方数据源,那么就要考虑是否在生产中能够与它们集成,以及如何在管道导出模型中对访问信息进行编码。2.模型一旦部署到生产环境,它就转变为一行行实际的代码,所以也要满足所有需求,因此,这就需要测试。在理想情况下,部署管道应该产生用于部署的模型包以及测试时需要的所有内容。模型监控将模型成功部署到生产环境,也许训练集中的输入分布与现实不同,模型需要重新练或重新校准;也许系统性能没有达到预期。因此,你需要收集模型性能的数据并对其进行监控。这就需要你设置一个可视化仪表板,具体事例如下:将模型的输入和输出保存在logstash或数据表中,设置Metabase(或Tableau,MyDBR,Grafana等)并创建可视化模型性能和校准指标报告。进一步探索和报告在整个数据科学项目中,你还需要尝试不同的假设,以生成图标和报告。这些任务与构建管道有所不同,主要体现在两个方面:首先,大部分任务不需要可再现性,即不用包含在计算图中。另外,也没必要使用模型的可重复性,在Jupyter中手动绘制图即可。其次,这些“进一步探索”的问题往往具有不可预测性:可能需要分析性能监控日志中的一个异常值;或者测试一个新的算法。这些探索会塞满你的笔记本中,团队中的其他人可能看不懂你的记录。因此按照日期排列子项目很重要。在项目中创建project目录,子文件夹命名格式为:projects/YYYY-MM-DD -项目名称。如下所示:./2017-01-19 - Training prototype/ (README, unsorted files)./2017-01-25 - Planning slides/ (README, slides, images, notebook)./2017-02-03 - LTV estimates/ README tasks/ (another set of date-ordered subfolders)./2017-02-10 - Cleanup script/ README script.py./… 50 folders more …注意,你可以根据需要自由组织每个子项目的内部目录,因为每个子项目很可能也是一个“数据科学项目”。在任何情况下,在每个子项目中都要有个README文件夹或README.txt文件,简要列出每个子项目目录的信息。如果项目列表太长,你需要重新组织项目目录,比如压缩一部分文件移动到存档文件夹中。“探索性”的任务有两种形式,即一次性分析和可重复性使用的代码,这时候建立一些约定很有必要。服务清单数据科学项目可能会依赖一些服务,可以指定提供以下9个关键服务,来描述期望:1.文件存储。任何一个数据科学项目都必须有个存储项目的地方,且需要整个团队共享。它是网络驱动器上的一个文件夹?还是Git存储库中的一个文件夹?2.数据服务。如何存储和访问数据?这里的“数据”指的是计算机读取或输出的所有内容,包括源数据、中间结果及第三方数据集访问、元数据、模型及报告等。3.版本。代码、数据、模型、报告和文档都需要有版本控制,另外一定要备份!4.元数据和文档。如何记录项目及子项目?是否有任何机器都可读的特征、脚本、数据集或模型的元数据?5.交互式计算。在交互式计算中,你选择JupyterLab、RStudio、ROOT、Octave还是Matlab?您是否为交互式并行计算设置了一个聚类(如ipyparallel或dask)?6.作业队列和调度程序。代码如何运行?是否需要安排定期维护?7.计算图。如何描述计算图并建立可重复性?8.实验管理。如何收集、查看和分析模型培训进度和结果?使用 ModelDB、Hyperdash还是 FloydHub?9.监控仪表板。如何收集和跟踪模型在生产环境中的具体表现?使用元数据库、Tableau、 PowerBI还是Grafana?最后,我总结了一个电子表格,包含了本文提到的所有工具,可自行下载使用。本文作者:【方向】阅读原文本文为云栖社区原创内容,未经允许不得转载。

December 14, 2018 · 1 min · jiezi

TiDB 源码阅读系列文章(二十一)基于规则的优化 II

在 TiDB 源码阅读系列文章(七)基于规则的优化 一文中,我们介绍了几种 TiDB 中的逻辑优化规则,包括列剪裁,最大最小消除,投影消除,谓词下推和构建节点属性,本篇将继续介绍更多的优化规则:聚合消除、外连接消除和子查询优化。聚合消除聚合消除会检查 SQL 查询中 Group By 语句所使用的列是否具有唯一性属性,如果满足,则会将执行计划中相应的 LogicalAggregation 算子替换为 LogicalProjection 算子。这里的逻辑是当聚合函数按照具有唯一性属性的一列或多列分组时,下层算子输出的每一行都是一个单独的分组,这时就可以将聚合函数展开成具体的参数列或者包含参数列的普通函数表达式,具体的代码实现在 rule_aggregation_elimination.go 文件中。下面举一些具体的例子。例一:下面这个 Query 可以将聚合函数展开成列的查询:select max(a) from t group by t.pk;被等价地改写成:select a from t;例二:下面这个 Query 可以将聚合函数展开为包含参数列的内置函数的查询:select count(a) from t group by t.pk;被等价地改写成:select if(isnull(a), 0, 1) from t;这里其实还可以做进一步的优化:如果列 a 具有 Not Null 的属性,那么可以将 if(isnull(a), 0, 1) 直接替换为常量 1(目前 TiDB 还没做这个优化,感兴趣的同学可以来贡献一个 PR)。另外提一点,对于大部分聚合函数,参数的类型和返回结果的类型一般是不同的,所以在展开聚合函数的时候一般会在参数列上构造 cast 函数做类型转换,展开后的表达式会保存在作为替换 LogicalAggregation 算子的 LogicalProjection 算子中。这个优化过程中,有一点非常关键,就是如何知道 Group By 使用的列是否满足唯一性属性,尤其是当聚合算子的下层节点不是 DataSource 的时候?我们在 (七)基于规则的优化 一文中的“构建节点属性”章节提到过,执行计划中每个算子节点会维护这样一个信息:当前算子的输出会按照哪一列或者哪几列满足唯一性属性。因此,在聚合消除中,我们可以通过查看下层算子保存的这个信息,再结合 Group By 用到的列判断当前聚合算子是否可以被消除。外连接消除不同于 (七)基于规则的优化 一文中“谓词下推”章节提到的将外连接转换为内连接,这里外连接消除指的是将整个连接操作从查询中移除。外连接消除需要满足一定条件:条件 1 : LogicalJoin 的父亲算子只会用到 LogicalJoin 的 outer plan 所输出的列条件 2 :条件 2.1 : LogicalJoin 中的 join key 在 inner plan 的输出结果中满足唯一性属性条件 2.2 : LogicalJoin 的父亲算子会对输入的记录去重条件 1 和条件 2 必须同时满足,但条件 2.1 和条件 2.2 只需满足一条即可。满足条件 1 和 条件 2.1 的一个例子:select t1.a from t1 left join t2 on t1.b = t2.pk;可以被改写成:select t1.a from t1;满足条件 1 和条件 2.2 的一个例子:select distinct(t1.a) from t1 left join t2 on t1.b = t2.b;可以被改写成:select distinct(t1.a) from t1;具体的原理是,对于外连接,outer plan 的每一行记录肯定会在连接的结果集里出现一次或多次,当 outer plan 的行不能找到匹配时,或者只能找到一行匹配时,这行 outer plan 的记录在连接结果中只出现一次;当 outer plan 的行能找到多行匹配时,它会在连接结果中出现多次;那么如果 inner plan 在 join key 上满足唯一性属性,就不可能存在 outer plan 的行能够找到多行匹配,所以这时 outer plan 的每一行都会且仅会在连接结果中出现一次。同时,上层算子只需要 outer plan 的数据,那么外连接可以直接从查询中被去除掉。同理就可以很容易理解当上层算子只需要 outer plan 的去重后结果时,外连接也可以被消除。这部分优化的具体代码实现在 rule_join_elimination.go 文件中。子查询优化 / 去相关子查询分为非相关子查询和相关子查询,例如:– 非相关子查询select * from t1 where t1.a > (select t2.a from t2 limit 1);– 相关子查询select * from t1 where t1.a > (select t2.a from t2 where t2.b > t1.b limit 1);对于非相关子查询, TiDB 会在 expressionRewriter 的逻辑中做两类操作:子查询展开即直接执行子查询获得结果,再利用这个结果改写原本包含子查询的表达式;比如上述的非相关子查询,如果其返回的结果为一行记录 “1” ,那么整个查询会被改写为:select * from t1 where t1.a > 1;详细的代码逻辑可以参考 expression_rewriter.go 中的 handleScalarSubquery 和 handleExistSubquery 函数。子查询转为 Join对于包含 IN (subquery) 的查询,比如:select * from t1 where t1.a in (select t2.a from t2);会被改写成:select t1.* from t1 inner join (select distinct(t2.a) as a from t2) as sub on t1.a = sub.a;如果 t2.a 满足唯一性属性,根据上面介绍的聚合消除规则,查询会被进一步改写成:select t1.* from t1 inner join t2 on t1.a = t2.a;这里选择将子查询转化为 inner join 的 inner plan 而不是执行子查询的原因是:以上述查询为例,子查询的结果集可能会很大,展开子查询需要一次性将 t2 的全部数据从 TiKV 返回到 TiDB 中缓存,并作为 t1 扫描的过滤条件;如果将子查询转化为 inner join 的 inner plan ,我们可以更灵活地对 t2 选择访问方式,比如我们可以对 join 选择 IndexLookUpJoin 实现方式,那么对于拿到的每一条 t1 表数据,我们只需拿 t1.a 作为 range 对 t2 做一次索引扫描,如果 t1 表很小,相比于展开子查询返回 t2 全部数据,我们可能总共只需要从 t2 返回很少的几条数据。注意这个转换的结果不一定会比展开子查询更好,其具体情况会受 t1 表和 t2 表数据的影响,如果在上述查询中, t1 表很大而 t2 表很小,那么展开子查询再对 t1 选择索引扫描可能才是最好的方案,所以现在有参数控制这个转化是否打开,详细的代码可以参考 expression_rewriter.go 中的 handleInSubquery 函数。对于相关子查询,TiDB 会在 expressionRewriter 中将整个包含相关子查询的表达式转化为 LogicalApply 算子。LogicalApply 算子是一类特殊的 LogicalJoin ,特殊之处体现在执行逻辑上:对于 outer plan 返回的每一行记录,取出相关列的具体值传递给子查询,再执行根据子查询生成的 inner plan ,即 LogicalApply 在执行时只能选择类似循环嵌套连接的方式,而普通的 LogicalJoin 则可以在物理优化阶段根据代价模型选择最合适的执行方式,包括 HashJoin,MergeJoin 和 IndexLookUpJoin,理论上后者生成的物理执行计划一定会比前者更优,所以在逻辑优化阶段我们会检查是否可以应用“去相关”这一优化规则,试图将 LogicalApply 转化为等价的 LogicalJoin 。其核心思想是将 LogicalApply 的 inner plan 中包含相关列的那些算子提升到 LogicalApply 之中或之上,在算子提升后如果 inner plan 中不再包含任何的相关列,即不再引用任何 outer plan 中的列,那么 LogicalApply 就会被转换为普通的 LogicalJoin ,这部分代码逻辑实现在 rule_decorrelate.go 文件中。具体的算子提升方式分为以下几种情况:inner plan 的根节点是 LogicalSelection则将其过滤条件添加到 LogicalApply 的 join condition 中,然后将该 LogicalSelection 从 inner plan 中删除,再递归地对 inner plan 提升算子。以如下查询为例:select * from t1 where t1.a in (select t2.a from t2 where t2.b = t1.b);其生成的最初执行计划片段会是:LogicalSelection 提升后会变成如下片段:到此 inner plan 中不再包含相关列,于是 LogicalApply 会被转换为如下 LogicalJoin :inner plan 的根节点是 LogicalMaxOneRow即要求子查询最多输出一行记录,比如这个例子:select *, (select t2.a from t2 where t2.pk = t1.a) from t1;因为子查询出现在整个查询的投影项里,所以 expressionRewriter 在处理子查询时会对其生成的执行计划在根节点上加一个 LogicalMaxOneRow 限制最多产生一行记录,如果在执行时发现下层输出多于一行记录,则会报错。在这个例子中,子查询的过滤条件是 t2 表的主键上的等值条件,所以子查询肯定最多只会输出一行记录,而这个信息在“构建节点属性”这一步时会被发掘出来并记录在算子节点的 MaxOneRow 属性中,所以这里的 LogicalMaxOneRow 节点实际上是冗余的,于是我们可以将其从 inner plan 中移除,然后再递归地对 inner plan 做算子提升。inner plan 的根节点是 LogicalProjection则首先将这个投影算子从 inner plan 中移除,再根据 LogicalApply 的连接类型判断是否需要在 LogicalApply 之上再加上一个 LogicalProjection ,具体来说是:对于非 semi-join 这一类的连接(包括 inner join 和 left join ),inner plan 的输出列会保留在 LogicalApply 的结果中,所以这个投影操作需要保留,反之则不需要。最后,再递归地对删除投影后的 inner plan 提升下层算子。inner plan 的根节点是 LogicalAggregation首先我们会检查这个聚合算子是否可以被提升到 LogicalApply 之上再执行。以如下查询为例:select , (select sum(t2.b) from t2 where t2.a = t1.pk) from t1;其最初生成的执行计划片段会是:将聚合提升到 LogicalApply 后的执行计划片段会是:即先对 t1 和 t2 做连接,再在连接结果上按照 t1.pk 分组后做聚合。这里有两个关键变化:第一是不管提升前 LogicalApply 的连接类型是 inner join 还是 left join ,提升后必须被改为 left join ;第二是提升后的聚合新增了 Group By 的列,即要按照 outer plan 传进 inner plan 中的相关列做分组。这两个变化背后的原因都会在后面进行阐述。因为提升后 inner plan 不再包含相关列,去相关后最终生成的执行计划片段会是:聚合提升有很多限定条件:LogicalApply 的连接类型必须是 inner join 或者 left join 。 LogicalApply 是根据相关子查询生成的,只可能有 3 类连接类型,除了 inner join 和 left join 外,第三类是 semi join (包括 SemiJoin,LeftOuterSemiJoin,AntiSemiJoin,AntiLeftOuterSemiJoin),具体可以参考 expression_rewriter.go 中的代码,限于篇幅在这里就不对此做展开了。对于 semi join 类型的 LogicalApply ,因为 inner plan 的输出列不会出现在连接的结果中,所以很容易理解我们无法将聚合算子提升到 LogicalApply 之上。LogicalApply 本身不能包含 join condition 。以上面给出的查询为例,可以看到聚合提升后会将子查询中包含相关列的过滤条件 (t2.a = t1.pk) 添加到 LogicalApply 的 join condition 中,如果 LogicalApply 本身存在 join condition ,那么聚合提升后聚合算子的输入(连接算子的输出)就会和在子查询中时聚合算子的输入不同,导致聚合算子结果不正确。子查询中用到的相关列在 outer plan 输出里具有唯一性属性。以上面查询为例,如果 t1.pk 不满足唯一性,假设 t1 有两条记录满足 t1.pk = 1,t2 只有一条记录 { (t2.a: 1, t2.b: 2) } ,那么该查询会输出两行结果 { (sum(t2.b): 2), (sum(t2.b): 2) } ;但对于聚合提升后的执行计划,则会生成错误的一行结果 { (sum(t2.b): 4) } 。当 t1.pk 满足唯一性后,每一行 outer plan 的记录都对应连接结果中的一个分组,所以其聚合结果会和在子查询中的聚合结果一致,这也解释了为什么聚合提升后需要按照 t1.pk 做分组。聚合函数必须满足当输入为 null 时输出结果也一定是 null 。这是为了在子查询中没有匹配的特殊情况下保证结果的正确性,以上面查询为例,当 t2 表没有任何记录满足 t2.a = t1.pk 时,子查询中不管是什么聚合函数都会返回 null 结果,为了保留这种特殊情况,在聚合提升的同时, LogicalApply 的连接类型会被强制改为 left join(改之前可能是 inner join ),所以在这种没有匹配的情况下,LogicalApply 输出结果中 inner plan 部分会是 null ,而这个 null 会作为新添加的聚合算子的输入,为了和提升前结果一致,其结果也必须是 null 。对于根据上述条件判定不能提升的聚合算子,我们再检查这个聚合算子的子节点是否为 LogicalSelection ,如果是,则将其从 inner plan 中移除并将过滤条件添加到 LogicalApply 的 join condition 中。这种情况下 LogicalAggregation 依然会被保留在 inner plan 中,但会将 LogicalSelection 过滤条件中涉及的 inner 表的列添加到聚合算子的 Group By 中。比如对于查询:select , (select count() from t2 where t2.a = t1.a) from t1;其生成的最初的执行计划片段会是:因为聚合函数是 count() ,不满足当输入为 null 时输出也为 null 的条件,所以它不能被提升到 LogicalApply 之上,但它可以被改写成:注意 LogicalAggregation 的 Group By 新加了 t2.a ,这一步将原本的先做过滤再做聚合转换为了先按照 t2.a 分组做聚合,再将聚合结果与 t1 做连接。 LogicalSelection 提升后 inner plan 已经不再依赖 outer plan 的结果了,整个查询去相关后将会变为:总结这是基于规则优化的第二篇文章,后续我们还将介绍更多逻辑优化规则:聚合下推,TopN 下推和 Join Reorder 。 ...

December 13, 2018 · 4 min · jiezi

TiDB 在量化派风控系统中的应用

作者:朱劲松,量化派研发中心系统架构师,主要参与了基础组件开发、API Gateway 等项目,现在致力于公司风控系统相关业务的架构设计和研发。一、公司简介量化派(QuantGroup)创办于 2014 年,是数据驱动的科技公司,是国家高新技术企业。量化派以「MOVE THE WORLD WITH DATA, ENLIGHTEN LIFE WITH AI」(数据驱动世界,智能点亮生活)为愿景,利用人工智能、机器学习、大数据技术。为金融、电商、旅游、出行、汽车供应链等多个领域的合作伙伴提供定制化的策略和模型,帮助提升行业效率。量化派已与国内外超过 300 家机构和公司达成深度合作,致力于打造更加有活力的共赢生态,推动经济的可持续发展。我司从 2017 年年中开始调研 TiDB,并在用户行为数据分析系统中搭建 TiDB 集群进行数据存储,经过一年多的应用和研究,积累了丰富的经验。同时,TiDB 官方推出 2.0 GA 版本,TiDB 愈发成熟,稳定性和查询效率等方面都有很大提升。我们于 2018 年 7 月部署 TiDB 2.0.5 版本,尝试将其应用于风控业务中。风控系统主要是在用户申请放款时,根据风控规则结合模型和用户特征进行实时计算并返回放款结果。二、业务背景风控系统中用到的数据主要可以分为两部分:一类是原始数据,用于分析用户当前的特征指标。一类是快照数据,用于计算历史指定时间点的特征指标,供模型训练使用。原始数据主要分为三种:产生自公司内各个产品线的业务系统数据。爬虫组提供的用户联系人、运营商、消费记录等数据。经过处理后的用户特征数据。由于我们的风控策略中用到了大量的模型,包括神经网络模型,评分模型等,这些模型的训练需要依靠大量的历史订单以及相关的用户特征,为了训练出更多精准、优秀的模型,就需要更多维度的特征,此时特征的准确性就直接影响了模型的训练结果,为此我们在回溯每一个订单的用户在指定时间的特征表现时,就需要用到数据快照。我们可以通过拉链表的方式来实现数据快照功能,简单说就是在每张表中增加三个字段,分别是new_id、start_time、end_time,每一次记录的更新都会产生一条新的数据,同时变更原有记录的end_time,以记录数据的变更历史。通过上面的介绍可以看到,业务数据和爬虫数据本身数据量就很大,再加上需要产生对应的拉链数据,数据量更是成倍增长。假设每条数据自创建后仅变更一次,那拉链表的数据量就已经是原始表的两倍了,而实际生产环境下数据的变更远不止一次。通过上述的介绍,我们总结风控系统下的数据存储需求应满足以下几点:业务数据。业务数据拉链表。爬虫数据,如联系人信息、运营商数据,消费记录等。爬虫数据拉链表。其他数据,如预处理数据等。三、当前方案以前方案主要是采用 HBase 进行数据存储。它的水平扩展很好的解决了数据量大的问题。但是在实际使用中,也存在着比较明显的问题,最明显的就是查询的 API 功能性较弱,只能通过 Key 来获取单条数据,或是通过 Scan API 来批量读取,这无疑在特征回溯时增加了额外的开发成本,无法实现代码复用。在实时计算场景中,为了降低开发成本,对于业务数据的获取则是通过访问线上系统的 MySQL 从库来进行查询;爬虫数据由于统一存放在 HBase 中,计算时需要将用到的数据全量拉取在内存中再进行计算。在回溯场景中,针对业务特征回溯,通过查询订单时间之前的数据进行特征计算,这种方式对于已经变更的数据是无能为力的,只能通过 HBase 里的数据快照来实现,但无形增加了很多的开发工作。3.1 TiDB 为我们打开一片新视野通过上面的介绍,我们知道要构建一个风控系统的实时数仓环境,需要满足下面几个特性:高可用,提供健壮、稳定的服务。支持水平弹性扩展,满足日益增长的数据需求。性能好,支持高并发。响应快。支持标准 SQL,最好是 MySQL 语法和 MySQL 协议,避免回溯时的额外开发。可以发现,TiDB 完美契合我们的每个需求。经过 TiDB 在用户行为数据分析系统中的长期使用,我们已经积累了一定的经验,在此过程中 TiDB 官方也给予了长期的技术支持,遇到的问题在沟通时也能够及时的反馈,而且还与我司技术人员进行过多次技术交流及线下分享,在此我们深表感谢。伴随着风控系统需求的持续增长,我们对整体架构进行了新一轮的优化,新的数据接入及存储架构如图 1。<center>图 1 优化后的架构图</center>通过图 1 可以看到,线上业务系统产生的数据统一存放在 MySQL 中,将这些孤立的数据归集在 TiDB 中,能够提供基于 SQL 的查询服务。通过 binlog 的方式直接从 MySQL 实例进行接入,接入后的数据以两种不同的形式分别存放:一种是去分库分表后的源数据,降低了实时特征计算的实现及维护成本。另一种是以拉链数据形式存储实现数据快照功能。经过调研,针对第一种场景,可以通过阿里的 otter 或者 TiDB 周边工具 Syncer 来快速实现,但对于第二个需求都没有现成的成熟解决方案。最终,我们基于阿里的 canal 进行客户端的定制化开发,分别按照不同的需求拼装合并 SQL 并写入到不同的 TiDB 集群中;同时还可以按需将部分表的数据进行组装并发送至 Kafka,用于准实时分析场景。对于来自爬虫组的数据,我们采用直接消费 Kafka 的方式组装 SQL 写入到 TiDB 即可。在实际是使用中,通过索引等优化,TiDB 完全可以支持线上实时查询的业务需求;在特征回溯时只需要通过增加查询条件就可以获得指定时间的特征结果,大大降低了开发成本。3.2 遇到的问题风控业务中用户特征提取的 SQL 相对都比较复杂,在实际使用中,存在部分 SQL 执行时间比在 MySQL 中耗时高。通过 explain 我们发现,他并没有使用我们创建的索引,而是进行了全表扫描,在进一步分析后还发现 explain 的结果是不确定的。经过与 TiDB 官方技术人员的沟通,我们进行了删除类似索引、analyze table 等操作,发现问题仍然存在。通过图 2 可以看到完全相同的 SQL 语句,其执行结果的差异性。最后按官方建议,我们采用添加 use index 的方式使其强制走索引,执行时间由 4 分钟变成了 < 1s,暂时解决了业务上的需求。<center>图 2 explain 示意图</center>同时 TiDB 技术人员也收集相关信息反馈给了研发人员。在整个问题的处理过程中,TiDB 的技术人员给予了高度的配合和及时的反馈,同时也表现出了很强的专业性,大大减少了问题排查的时间,我们非常感谢。四、展望目前我们已经搭建两个 TiDB 集群,几十个物理节点,百亿级数据量,受益于 TiDB 的高可用构架,上线以来一直稳定运行。如上,TiDB 在我们风控业务中的应用才只是开始,部分业务的迁移还有待进一步验证,但是 TiDB 给我们带来的好处不言而喻,为我们在数据存储和数据分析上打开了一片新视野。后续我们会继续加大对 TiDB 的投入,使其更好地服务于在线分析和离线分析等各个场景。我们也希望进一步增加与 PingCAP 团队的交流与合作,进行更深入的应用和研究,为 TiDB 的发展贡献一份力量。 ...

December 11, 2018 · 1 min · jiezi

Flink SQL 核心解密 —— 提升吞吐的利器 MicroBatch

之前我们在 Flink SQL 中支持了 MiniBatch, 在支持高吞吐场景发挥了重要作用。今年我们在 Flink SQL 性能优化中一项重要的改进就是升级了微批模型,我们称之为 MicroBatch,也叫 MiniBatch2.0。在设计和实现 Flink 的流计算算子时,我们一般会把“面向状态编程”作为第一准则。因为在流计算中,为了保证状态(State)的一致性,需要将状态数据存储在状态后端(StateBackend),由框架来做分布式快照。而目前主要使用的RocksDB,Niagara状态后端都会在每次read和write操作时发生序列化和反序列化操作,甚至是磁盘的 I/O 操作。因此状态的相关操作通常都会成为整个任务的性能瓶颈,状态的数据结构设计以及对状态的每一次访问都需要特别注意。微批的核心思想就是缓存一小批数据,在访问状态状态时,多个同 key 的数据就只需要发生一次状态的操作。当批次内数据的 key 重复率较大时,能显著降低对状态的访问频次,从而大幅提高吞吐。MicroBatch 和 MiniBatch 的核心机制是一样的,就是攒批,然后触发计算。只是攒批策略不太一样。我们先讲解触发计算时是如何节省状态访问频次的。微批计算MicroBatch 的一个典型应用场景就是 Group Aggregate。例如简单的求和例子:SELECT key, SUM(value) FROM T GROUP BY key如上图所示,当未开启 MicroBatch 时,Aggregate 的处理模式是每来一条数据,查询一次状态,进行聚合计算,然后写入一次状态。当有 N 条数据时,需要操作 2*N 次状态。当开启 MicroBatch 时,对于缓存下来的 N 条数据一起触发,同 key 的数据只会读写状态一次。例如上图缓存的 4 条 A 的记录,只会对状态读写各一次。所以当数据的 key 的重复率越大,攒批的大小越大,那么对状态的访问会越少,得到的吞吐量越高。攒批策略攒批策略一般分成两个维度,一个是延时,一个是内存。延时即控制多久攒一次批,这也是用来权衡吞吐和延迟的重要参数。内存即为了避免瞬间 TPS 太大导致内存无法存下缓存的数据,避免造成 Full GC 和 OOM。下面会分别介绍旧版 MiniBatch 和 新版 MicroBatch 在这两个维度上的区别。MiniBatch 攒批策略MiniBatch 攒批策略的延时维度是通过在每个聚合节点注册单独的定时器来实现,时间分配策略采用简单的均分。比如有4个 aggregate 节点,用户配置 10s 的 MiniBatch,那么每个节点会分配2.5s,例如下图所示:但是这种策略有以下几个问题:用户能容忍 10s 的延时,但是真正用来攒批的只有2.5秒,攒批效率低。拓扑越复杂,差异越明显。由于上下游的定时器的触发是纯异步的,可能导致上游触发微批的时候,下游也正好触发微批,而处理微批时会一段时间不消费网络数据,导致上游很容易被反压。计时器会引入额外的线程,增加了线程调度和抢锁上的开销。MiniBatch 攒批策略在内存维度是通过统计输入条数,当输入的条数超过用户配置的 blink.miniBatch.size 时,就会触发批次以防止 OOM。但是 size 参数并不是很好评估,一方面当 size 配的过大,可能会失去保护内存的作用;而当 size 配的太小,又会导致攒批效率降低。MicroBatch 攒批策略MicroBatch 的提出就是为了解决 MiniBatch 遇到的上述问题。MicroBatch 引入了 watermark 来控制聚合节点的定时触发功能,用 watermark 作为特殊事件插入数据流中将数据流切分成相等时间间隔的一个个批次。实现原理如下所示:MicroBatch 会在数据源之后插入一个 MicroBatchAssigner 的节点,用来定时发送 watermark,其间隔是用户配置的延时参数,如10s。那么每隔10s,不管数据源有没有数据,都会发一个当前系统时间戳的 watermark 下去。一个节点的当前 watermark 取自所有 channel 的最小 watermark 值,所以当聚合节点的 watermark 值前进时,也就意味着攒齐了上游的一个批次,我们就可以触发这个批次了。处理完这个批次后,需要将当前 watermark 广播给下游所有 task。当下游 task 收齐上游 watermark 时,也会触发批次。这样批次的触发会从上游到下游逐级触发。这里将 watermark 作为划分批次的特殊事件是很有意思的一点。Watermark 是一个非常强大的工具,一般我们用来衡量业务时间的进度,解决业务时间乱序的问题。但其实换一个维度,它也可以用来衡量全局系统时间的进度,从而非常巧妙地解决数据划批的问题。因此与 MiniBatch 策略相比,MicroBatch 具有以下优点:相同延时下,MicroBatch 的攒批效率更高,能攒更多的数据。由于 MicroBatch 的批次触发是靠事件的,当上游触发时,下游不会同时触发,所以不像 MiniBatch 那么容易引起反压。解决数据抖动问题(下一小节分析)我们利用一个 DAU 作业进行了性能测试对比,在相同的 allowLatency(6秒)配置的情况下,MicroBatch 能得到更高的吞吐,而且还能得到与 MiniBatch 相同的端到端延迟!另外,仍然是上述的性能测试对比,可以发现运行稳定后 MicroBatch 的队列使用率平均值在 50% 以下,而 MiniBatch 基本是一直处于队列满载下。说明 MicroBatch 比 MiniBatch 更加稳定,更不容易引起反压。MicroBatch 在内存维度目前仍然与 MiniBatch 一样,使用 size 参数来控制条数。但是将来会基于内存管理,将缓存的数据存于管理好的内存块中(BytesHashMap),从而减少 Java 对象的空间成本,减少 GC 的压力和防止 OOM。防止数据抖动所谓数据抖动问题是指,两层 AGG 时,第一层 AGG 发出的更新消息会拆成两条独立的消息被下游消费,分别是retract 消息和 accumulate 消息。而当第二层 AGG 消费这两条消息时也会发出两条消息。从前端看到就是数据会有抖动的现象。例如下面的例子,统计买家数,这里做了两层打散,第一层先做 UV 统计,第二级做SUM。SELECT day, SUM(cnt) totalFROM ( SELECT day, MOD(buy_id, 1024), COUNT(DISTINCT buy_id) as cnt FROM T GROUP BY day, MOD(buy_id, 1024))GROUP BY day当第一层count distinct的结果从100上升到101时,它会发出 -100, +101 的两条消息。当第二层的 SUM 会依次收到这两条消息并处理,假设此时 SUM 值是 900,那么在处理 -100 时,会先发出 800 的结果值,然后处理 +101 时,再发出 901 的结果值。从用户端的感受就是买家数从 900 降到了 800 又上升到了 901,我们称之为数据抖动。而理论上买家数只应该只增不减的,所以我们也一直在思考如何解决这个问题。数据抖动的本质原因是 retract 和 accumulate 消息是一个事务中的两个操作,但是这两个操作的中间结果被用户看到了,也就是传统数据库 ACID 中的隔离性(I) 中最弱的 READ UNCOMMITTED 的事务保障。要从根本上解决这个问题的思路是,如何原子地处理 retract & accumulate 的消息。如上文所述的 MicroBatch 策略,借助 watermark 划批,watermark 不会插在 retract & accumulate 中间,那么 watermark 就是事务的天然分界。按照 watermark 来处理批次可以达到原子处理 retract & accumulate 的目的。从而解决抖动问题。适用场景与使用方式MicroBatch 是使用一定的延迟来换取大量吞吐的策略,如果用户有超低延迟的要求的话,不建议开启微批处理。MicroBatch 目前对于无限流的聚合、Join 都有显著的性能提升,所以建议开启。如果遇到了上述的数据抖动问题,也建议开启。MicroBatch默认关闭,开启方式:# 攒批的间隔时间,使用 microbatch 策略时需要加上该配置,且建议和 blink.miniBatch.allowLatencyMs 保持一致blink.microBatch.allowLatencyMs=5000# 使用 microbatch 时需要保留以下两个 minibatch 配置blink.miniBatch.allowLatencyMs=5000# 防止OOM,每个批次最多缓存多少条数据blink.miniBatch.size=20000后续优化MicroBatch 目前只支持无限流的聚合和 Join,暂不支持 Window Aggregate。所以后续 Window Aggregate 会重点支持 MicroBatch 策略,以提升吞吐性能。另一方面,MicroBatch 的内存会考虑使用二进制的数据结构管理起来,提升内存的利用率和减轻 GC 的影响。本文作者:jark阅读原文本文为云栖社区原创内容,未经允许不得转载。 ...

December 7, 2018 · 2 min · jiezi

1 小时 SQL 极速入门(二)

1 小时 SQL 极速入门(二)公众号:【智能制造专栏】有对制造业感兴趣的可以关注我,了解更多。上篇我们说了 SQL 的基本语法,掌握了这些基本语法后,我们可以对单表进行查询及计算分析。但是一个大的系统,往往会有数十上百张表,而业务关系又错综复杂。我们要查的数据往往在好几张表中,而要从多张表中来获取信息就需要用到表联结了。先说说什么是联结,联结就是用一条 SELECT 语句从多个表中查询数据。通过联结,让多张表中的数据互相关联起来。联结又分为内联结、左外联结、右外联结、全外联结。别怕,我知道有些初学者看到这几个概念就头大,不过请继续往后看,看完后你肯定能看明白。在实际中,内联结和左联结应该是使用最多的,我几乎没用到过右连接与全外联结。对初学者来说,在这里迷惑的原因是去记这些概念,这是没必要的,我们只要在实际中抱着问题去用一次就可以完全掌握了。下面我们就开始:我们有下面三张表,一张订单表存放订单头信息,包括订单号、订单类型、订单数量、订单状态信息。一张订单明细表,存储订单的详细信息。包含订单号、订单类型、工序号、工序名称、工序状态、物料号、工位号一张物料表,存储订单工序用到的物料。包含物料号、物料名称。内联结我们先观察一下,订单头信息中只包含订单的数量、状态信息。订单明细表中包含着订单的详细信息,如工序信息,每道工序用到的物料,每道工序的名称,在哪个工位操作等信息。假如我们现在要查询订单号、订单数量、工序号、工序名称、工位等信息,只有一张表我们是查不到的,那么我们就要把这两张表结合起来。SELECT oh.orderno, oh.order_type, oh.quantity, od.order_line_no, od.order_line_name, od.workcenterFROM order_header ohINNER JOIN order_detail odON oh.orderno =od.ordernoAND oh.order_type=od.order_type解释下:我们用INNER JOIN 表示内连接,在 INNER JOIN 后写上我们需要关联的表,oh 和 od 表示别名,方便后面书写,不然后面我们就要用到表的全称来写了。这里我们要关联到订单明细表 order_detail,去取出订单详细信息。后面跟上 ON 关键字,表示条件,这里 ON 后面有两个条件。表示我们通过订单号和订单类型来把两个表中的数据关联起来,通过订单表中的订单号和订单类型作为条件来查找订单明细表中同样订单号和订单类型的订单的详细信息。我们看下结果:可以看到,我们查出了订单 1001 ,1002, 1003, 1004, 1005五个订单的总数量,各个工序的名称,在哪个工位生产等信息。细心的读者可能会注意到,在订单表中还有一个 1008 的订单,为什么没有查出来?那就接着往下看左联结相比于内联结,左联结使用 LEFT JOIN 来表示。我们先不看概念,我们直接把刚才的 SQL 语句改成左联结来看一下结果。SELECT oh.orderno, oh.order_type, oh.quantity, od.order_line_no, od.order_line_name, od.workcenterFROM order_header ohLEFT JOIN order_detail odON oh.orderno =od.ordernoAND oh.order_type=od.order_type;结果如下图:对比内联结的结果,我们发现了什么,我们发现最下面多了一行,1008 订单,而1008 后面的几个字段为空。我们看一下订单明细表会发现没有 1008 这个订单。这样子我们就明白了,内联结是两张表中都存在才能关联出来。而左联结的意思就是我们的主表中的所有行都会展示出来,如果在联结的表中找不到对应的,会默认为 null.右联结知道了左联结,右联结也就清楚了,右连接呢会把我们关联的表中的所有行都展示出来,不管主表中有没有匹配的行。右联结关键字为 RIGHT JOINSELECT oh.orderno, oh.order_type, oh.quantity, od.order_line_no, od.workcenterFROM order_header ohRIGHT JOIN order_detail odON oh.orderno =od.ordernoAND oh.order_type=od.order_type;可以看到,RIGHT JOIN 把关联的订单明细表中的所有行都显示了出来,但是订单主表中并没有 1006 和 1007 两个订单,所以这两行显示为 null多表联结多表联结就是超过两张表的联结,上面我们关联了订单表和订单明细表,现在我们想知道每道工序用到的物料,就需要关联到物料表。我们看到订单明细表中有 productid 字段,我们用这个关联到 product 表中。同时,后面我们也用了 ORDER BY 进行排序。SELECT oh.orderno, oh.order_type, oh.quantity, od.order_line_no, od.workcenter, p.productno, p.product_nameFROM order_header ohINNER JOIN order_detail odON oh.orderno =od.ordernoINNER JOIN product1 pON od.productid =p.IDAND oh.order_type=od.order_typeORDER BY orderno, order_line_no注意在使用联结时一定要注意联结条件,如果 联结条件不正确,就会得到不正确的结果。而且要注意,联结条件是必须的。UNION 与 UNION ALLUNION 与 UNION ALL 表示并集,可以把两个 SELECT 查询的结果合并成一个,前提是两个 SELECT 所查询的列数量和字段类型一致。不同的是 UNION 会去除重复行,而 UNION ALL 不会去除重复行。如果我们有两张表,都存有相似的信息。比如我们在一个其他表中也存储的有订单信息。举个栗子,order_header_bak 表中存有如下两条数据。我们用 UNION ALL 试一下SELECT orderno, order_type, order_statusFROM order_headerUNION ALLSELECT orderno, order_type, order_statusFROM order_header_bak;看到查出了 8 条信息,1001 订单有两条一样的信息。我们用 UNION 试一下SELECT orderno, order_type, order_statusFROM order_headerUNIONSELECT orderno, order_type, order_statusFROM order_header_bak看到只有 7 条数据了, 1001 订单只有一行数据。 ...

December 3, 2018 · 1 min · jiezi