算法跑得很慢
㈠ 数据结构和算法在实际的软件开发中都有哪些
应用太多了。
基本上来说C#是基于面向对象语言,你所定义的所有类/结构体都算是数据结构,而且在.net类库中已经定义中诸多可用的类型以供使用。实际开发中根本就离不开结构与算法。
题主之所以有这样的问题,基本上认识到了很多程序员易犯的一个毛病——理论知识与实际应用中的脱节问题,不少程序员都说自己写程序用不上理论知识,或者是理论无用。我一直认为理论才是真正编程的指导,别说你所学的理论知识了,有时我们必须遵守一些软件活动上的标准/规范/规定。比如ISO29500标准有多少程序员读过或听说过?他实事就是关于openxml的一个国际标准,我们要想达到通用的程序,这些标准还是读一读的好。
扯回你的问题,什么是数据结构,什么是算法?如果你真的狭义理由数据结构,或者只是从课本上例子来说,数据结构被定义成一个只有属性成员的类或结构体才算是数据结构吗?事实上并不是,那么是不是只有链表/栈/队列才算是数据结构呢?可以说这是某些人狭义理解数据结构时的一种常规定势思维,但事实上来说,类或结构是数据结构的基本,否则你链表存在的实体到底是什么东西?所以数据结构包含着基本结构与狭义上的顺序表/链表/栈/队等存在实体的集体。为什么我说数据结构在实际运用中广泛体现呢?就数据结构而言,课本上只是为了讲明白结构而已,弱化了其中实体的真正含义,而且不语言的具体实现亦不尽相同,所以他们所讲的数据结构是基本理论的。
我来个例子:链表(C#语言)
publicclassMember
{
publicstringName{get;set;}
publicstringResponsibility{get;set;}
publicstringPosotion{get;set;}
}
publicclassMemberNode
{
publicMemberMember{get;set;}
publicMemberNext{get;set;}
}
//Node其他就是链表中的一个结点结构,这个结点结构除了指明当前的Member之下还指向下Next的下一个结构结构,它最终可以形成一个链表。这就是定义的一个链表。
从以上例子上你可以看出这是一个类似于课本的标准定义,但事实上在C#语法中存在泛型的特点,那么这类似的结构我们不须要一个个地定义了!所以在不同的语言中为了方便编程者,我们甚至可以把这样的结构进行简单化,从而达到一种最简单的使用方式。以C#为例,我们可以使用Node<T>来表示链表/List<T>表示顺序表/Stack<T>表示栈/Queue<T>表示队列,在这种情况下,我们只需要定义我们的泛型即可,结构链之类的本身使用泛型已经在类库中实现了——虽然你不用定义,但不代表不使用或者不用理解这其中的知识。而在课本讲理论的时候,他不可能附带泛型来讲的,所以很多人认为自己去定义数据结构才行,那才是“真正”的数据结构,其实不然。以链表为例,我们需要一个节点除了其实体意义之外,还存在指向下一结点的指针(其实是地址引用)才算是数据结构。根据课本,他们必须这么定义(C#):
publicclassMemberNode
{
publicstringName{get;set;}
publicstringResponsibility{get;set;}
publicstringPosition{get;set;}
publicMemberNodeNext{get;set;}
}
//死读书的只会承认这种才是真正的数据结构吧(链表节点)
事实上,链表讲的只是一种形式,能最终形成的一种组织数据结构的形式。这个代码会导致我们出现一种极大的误解——每个类型的结构都需要重新定义一次。如果有多个类型结构的话,我们会出现多个不同的定义,这会导致将来类的定义越来越多,对于维护上来说是比较麻烦的。由于设计模式/面向切片等各种开发方式的介入,我们会使用相对比较简单的形式。所以才会有我定义两个类的进步,而后可以出现泛型的更进一步。
你可以这样理解,这种课本上的结构,会导致我们造成每种结构基本上都需要重新定义一次,我最开始给出的例子可以使用继承的方式,实现某个基类的数据结构(下面的似乎也行,但在使用中可能会出现部分问题),而Node<T>则从根本上解决了这个问题,可以支撑多种类型。
所以此时在理解数据结构时,比如Node<T>,他不旦要求理解链表的节点,还要理解T泛型,那么在数据结构上来说,它指的不再是单一的节点结构,还在包括一个基础的类型。
换句话来说,你在C#等语言中已经不需要再做类似的定义了,只需要定义其基本结构类型即可。但课本上在讲知识的时候,它不可能只针对面向对象或支持泛型的语言来讲,若不支持泛型时,我们必须使用课本上或我最开始写的例子中的形式,若不支持继承的面向过程语言,那么课本上的知识就是硬性的规定,你必须以这种形式来说,而引用则使用指针引用的方式(面向对象的引用其实是一种引用型引用,也就是址引用或称地址引用,与指针类似)。
相信讲到这里你能明白,数据结构在不同的语言中只是变了个形而已,并不是必须是存在指针的才是,也不是只说表面上的那点东西。早期教程都是以fortain语言为主的,而且课本的目的是讲清道理,而不是一种规定。死读书的人以为用不到数据结构,其实他们一直在使用。
再来说一下算法,算法是什么?是解决问题的一种模式,比如解二元一次方程等等,所以算法的定义其实已经告诉你,顺序代码他也算是一种算法,不能说只有背包问题,八皇后问题,回溯问题才算是算法——你能明白吗?其实你正常写的就是一种算法,这种算法简单,就是顺序执行下来就可以了,他也是一种算法的,就算解二元一次方程组有固定的模式(算法),但不代表加减法就不是算法了!所以算法也是常用的东西,那么你学习的算法其实算是开辟思路的一种而已。算法自身的概念已经决定,基本上程序都是由结构与算法构成。我也来举个例子,怎么判断某个链表是否为循环链表?是你的回溯算法,贪心算法还是背包算法?它们只是在解决一些典型问题的一种通用方式而已,很显然,我的问题不是这种典型问题,但不代表他不典型,我们正常的算法是设计两个变量等于头元素,然后开始进入循环,一个变量每次向下推一,即找到他下一个节点,而另一个变量每次找到其孙节点,就算当于两个变量一个每次向下推进一次,而另一个每点推进两次(如果可能),如果不是循环链表,则进两次的那个会在链表总长度的一半时,遇到空引用,否则会在某一时间两指针引用同一对象(不是对象相等,而是引用相同的对象),什么意思呢,好象两个人在圆型跑道上跑步,一个每秒1米,另一个每秒2米,同时同地同向出发,最归跑得快的那个会追上跑得慢的那个!当然这种情况下你也可以给他起个名字,叫“追及算法”?如果只有你学的那几个典型算法是算法的话,这个算不算算法?
现在我们的问题是,如果语言层面上已经实现了这些东西,那么这些理论我们是否可以不用理解就可以了?答案是可以——如果你只是一个不思进取的程序员或允许bug乱飞的没有责任心的编程人员的话,可以不用理解——毕竟有些人只是“混”饭吃而已!
理解了不会去应用,这就是典型的理论联系不到实际,他们也不知道自己的代码将如何控制。我举一个例子,由于性能等各方面的要求,我们要使用多线程对某些数据进行处理。怎么处理?不好人会使用多线程——他们定义一个临界资源,然后让多个线程在读取数据表(DataSet)时进行阻塞,然后每个线程去处理那些超时长的问题,处理完的时个再按这种方式读取数据——这样有问题吗?没有,这也算是算法的一种!反正如果编程代码有功底的话没有任何问题的,这种代码算不算优雅呢——很多人认为代码的优雅就是代码编写过程的形式或是良好的编程习惯!这里边其实用不到数据结构与算法的。
好吧,我承认,但如果我们换一句思路来看看,如果我用一个线程负责读取数据,并不停地放入到一个队列中,而多个线程从队列中不停地读取处理这些放入的数据,这样如何?我的意思是说,并没有直接在DataSet中处理,而是选择使用队列的方式。
我们看一个问题,这个队列Queue<T>,一个线程用来插入数据,多个线程用来读取数据,而且要保证不能重复,那么我们可以使用队列的安全版本(CorrentQueue<T>,在.net中如果非线程安全的情况下,多线程使用实应该找到其对应的安全版本或者控制线程安全)。
插入线程如果发现队列中的长度(容量)较大时,可以暂缓插入。这样可以保证队列的长度基本固定,占用内存得到控制(不是DataSet批量读来一大堆),由于使用安全队列,所以各线程不用考虑线程之间的安全问题,每个线程从队中获得数据并删除,可以保证数据只被处理一次。当然还可以考虑优雅的通知机制,插入线程在插入数据时通知处理线程启动,如果插入速度过快,发现插入数量达指定的长度(比如30个),停止插入,插入线程阻塞;处理处理再次处理时可通知插入线程再进行插入。
这也算是一种算法吧?它可以让插入线程与处理线程同时工作,而使用DataSet那种常规的结果时,只能是等待处理完或加入多个控制条件进行控制,既然这么控制的话,何不直接使用队列的方式?CorrentQueue<T>中的T也完全可以是一条记录DataRow嘛!
如果你认为第一种是你经常使用方式,那么算法对于你来说学与不学无所谓的,你必须使用自己的编程/调试功底以保证你的代码尽量很少出错或不出错。而如果你认为第二种方案优雅一些的话,那么你会认为你学习的算法与结构还是有用的,理论与实践结合了。
我之所以举这么一个例子,其实告诉你的无非是几点非常重要的信息:
你有选择算法的自由(只不过是代码质量、后期维护的问题)
如果你知道的较多的算法与结构,你会有更多的选择。
算法或结构在实际使用中,所谓的典型问题并不是使用场景和书上描述一模一样(试想一下,我第二种考虑的例子中,是不是跟书上比他不典型?其实也是非常典型的)
分析问题时,应该拿要点,而不是整体去套。(如果整体去套用的话,你肯定会想不到使用哪种结构或算法)
不管是数据结构/算法/设计模式都要求是灵活运用,而不是场景对比使用,也不是生搬硬套。
试想一下,你的背包问题,怎么可能公司也让你分拆包装?你的八皇后问题公司恰好让你下棋?你的贪心算法公司恰好让你找零钱?你的回溯算法公司恰好让你走迷宫?学不能致用的原因就是太死板——这几个举个例子的场景你再遇到或理能遇到的机率是非常小的,所以如果觉得学了没用,那就真没用了——只不过不是算法没用,而是人没人!
讲个小故事:从前一个家人的板凳坏了,要找一个合适的两股叉的树杈重新制做一个板凳腿,让孩子到树园里找了半天,孩子回来说“我都没见过有向下叉的树杈!他老爹气得要死——怎么会可能有向下长的树杈呢!这孩子是不是笨——你就不会把地刨了找一个向下分叉的树根!
算法也是一样,迷宫找路可以使用回溯算法,但不是所有的回溯算法都用于迷宫找路——它还可以用来设计迷宫!嘿嘿嘿!
㈡ 都快2021年了,算法岗位应该怎样准备面试
说到算法岗位,现在网上的第一反应可能就是内卷,算法岗位也号称是内卷最严重的岗位。针对这个问题,其实之前我也有写过相关的文章。这个岗位竞争激烈不假,但我个人觉得称作内卷有些过了。就我个人的感觉,这几年的一个大趋势是从迷茫走向清晰。
早在2015年我在阿里妈妈实习的时候,那个时候我觉得其实对于算法工程师这个岗位的招聘要求甚至包括工作内容其实业内是没有一个统一的标准的。可以认为包括各大公司其实对这个岗位具体的工作内容以及需要的候选人的能力要求都不太一致,不同的面试官有不同的风格,也有不同的标准。
我举几个例子,第一个例子是我当初实习面试的时候,因为是本科生,的确对机器学习这个领域了解非常非常少,可以说是几乎没有。但是我依然通过了,通过的原因也很简单,因为有acm的获奖背景,面试的过程当中主要也都是一些算法题,都还算是答得不错。但是在交叉面试的时候,一位另一个部门的总监就问我有没有这块的经验?我很明确地说了,没有,但是我愿意学。
接着他告诉我,算法工程师的工作内容主要和机器学习相关,因此机器学习是基本的。当时我就觉得我凉了,然而很意外地是还是通过了面试。
核心能力
由于我已经很久没有接触校招了,所以也很难说校招面试应该怎么样准备,只能说说如果是我来招聘,我会喜欢什么样的学生。也可以理解成我理解的一个合格优秀的算法工程师应该有的能力。
模型理解
算法工程师和模型打交道,那么理解模型是必须的。其实不用说每一个模型都精通,这没有必要,面试的时候问的模型也不一定用得到。但更多地是看重这个人在学习的时候的习惯,他是浅尝辄止呢,还是会刨根究底,究竟能够学到怎样的地步。
在实际的工作当中我们可能会面临各种各样的情况,比如说新加了特征但是没有效果,比如升级了模型效果反而变差了等等,这些情况都是有可能发生的。当我们遇到这些情况之后,需要我们根据已知的信息来推理和猜测导致的原因从而针对性的采取相应的手段。因此这就需要我们对当前的模型有比较深入地了解,否则推导原因做出改进也就无从谈起。
所以面试的时候问起哪个模型都不重要,重要的是你能不能体现出你有过深入的研究和理解。
数据分析
算法工程师一直和数据打交道,那么分析数据、清洗数据、做数据的能力也必不可少。说起来简单的数据分析,这当中其实牵扯很多,简单来说至少有两个关键点。
第一个关键点是处理数据的能力,比如sql、hive、spark、MapRece这些常用的数据处理的工具会不会,会多少?是一个都不会呢,还是至少会一点。由于各个公司的技术栈不同,一般不会抱着候选人必须刚好会和我们一样的期待去招人,但是候选人如果一无所知肯定也是不行的。由于学生时代其实很少接触这种实践的内容,很多人对这些都一无所知,如果你会一两个,其实就是加分项。
第二个关键点是对数据的理解力,举个简单的例子,比如说现在的样本训练了模型之后效果不好,我们要分析它的原因,你该怎么下手?这个问题日常当中经常遇到,也非常考验算法工程师对数据的分析能力以及他的经验。数据是水,模型是船,我们要把船驶向远方,只懂船只构造是不行的,还需要对水文、天象也有了解。这样才能从数据当中捕捉到trick,对一些现象有更深入的看法和理解。
工程能力
虽然是算法工程师,但是并不代表工程能力不重要,相反工程能力也很重要。当然这往往不会成为招聘的硬性指标, 比如考察你之前做过什么工程项目之类的。但是会在你的代码测试环节有所体现,你的代码风格,你的编码能力都是你面试的考察点之一。
并不只是在面试当中如此,在实际工作当中,工程能力也很关键。往小了说可以开发一些工具、脚本方便自己或者是团队当中其他人的日常工作,往大了说,你也可以成为团队当中的开发担当,负责其团队当中最工程的工作。比如说复现一篇paper,或者是从头撸一个模型。这其实也是一种差异化竞争的手段,你合理地负担起别人负担不了的工作,那么自然就会成为你的业绩。
时代在变化,行业在发展,如今的校招会问些什么早已经和当年不同了。但不管怎么说,这个岗位以及面试官对于人才的核心诉求几乎是没有变过的,我们从核心出发去构建简历、准备面试,相信一定可以有所收获。
㈢ java循环越跑越慢为什么高手进
查询速度慢的原因很多,常见如下几种:
1、没有索引或者没有用到索引(这是查询慢最常见的问题,是程序设计的缺陷)
2、I/O吞吐量小,形成了瓶颈效应。
3、没有创建计算列导致查询不优化。
4、内存不足
5、网络速度慢
6、查询出的数据量过大(可以采用多次查询,其他的方法降低数据量)
7、锁或者死锁(这也是查询慢最常见的问题,是程序设计的缺陷)
8、sp_lock,sp_who,活动的用户查看,原因是读写竞争资源。
9、返回了不必要的行和列
10、查询语句不好,没有优化
可以通过如下方法来优化查询 :
1、把数据、日志、索引放到不同的I/O设备上,增加读取速度,以前可以将Tempdb应放在RAID0上,SQL2000不在支持。数据量(尺寸)越大,提高I/O越重要.
2、纵向、横向分割表,减少表的尺寸(sp_spaceuse)
3、升级硬件
4、根据查询条件,建立索引,优化索引、优化访问方式,限制结果集的数据量。注意填充因子要适当(最好是使用默认值0)。索引应该尽量小,使用字节数小的列建索引好(参照索引的创建),不要对有限的几个值的字段建单一索引如性别字段
5、提高网速;
6、扩大服务器的内存,Windows 2000和SQL server 2000能支持4-8G的内存。配置首凳虚拟内存:虚拟内存大小应基于计算机上并发运行的服务进行配置。运行 Microsoft SQL Server? 2000 时,可考虑将虚拟内存大小设置为计算机中安装的物理内存的 1.5 倍。如果另外安装了全文检索功能,并打算运行 Microsoft 搜索服务以便执行全文索引和查询,可考虑:将虚拟内存大小配置为至少是计算机中安装的物理内存的 3 倍。将 SQL Server max server memory 服务器配置选项配置为物理内存的 1.5 倍(虚拟内存大小设置的一半)。
7、增加服务器CPU个数;但是必须明白并行处理串行处理更需要资源例如内存。使用并行还是串行程是缓局MsSQL自动评估选择的。单个任务分解成多个任务,就可以在处理器上运行。例如耽搁查询的排序、连接、扫描和GROUP BY字句同时执行,SQL SERVER根据系统的负载情况决定最优的并行等级,复杂的需要消耗大量的CPU的查询最适合并行处理。但是更新操作UPDATE,INSERT,DELETE还不能并行处理。
8、如果是使用like进行查询的话,简单的使用index是不行的,但是全文索引,耗空间。 like 'a%' 使用索引 like '%a'扰芹让 不使用索引用 like '%a%' 查询时,查询耗时和字段值总长度成正比,所以不能用CHAR类型,而是VARCHAR。对于字段的值很长的建全文索引。
9、DB Server 和APPLication Server 分离;OLTP和OLAP分离
10、分布式分区视图可用于实现数据库服务器联合体。联合体是一组分开管理的服务器,但它们相互协作分担系统的处理负荷。这种通过分区数据形成数据库服务器联合体的机制能够扩大一组服务器,以支持大型的多层 Web 站点的处理需要。有关更多信息,参见设计联合数据库服务器。(参照SQL帮助文件'分区视图')
a、在实现分区视图之前,必须先水平分区表
b、在创建成员表后,在每个成员服务器上定义一个分布式分区视图,并且每个视图具有相同的名称。这样,引用分布式分区视图名的查询可以在任何一个成员服务器上运行。系统操作如同每个成员服务器上都有一个原始表的复本一样,但其实每个服务器上只有一个成员表和一个分布式分区视图。数据的位置对应用程序是透明的。
11、重建索引 DBCC REINDEX ,DBCC INDEXDEFRAG,收缩数据和日志 DBCC SHRINKDB,DBCC SHRINKFILE. 设置自动收缩日志.对于大的数据库不要设置数据库自动增长,它会降低服务器的性能。 在T-sql的写法上有很大的讲究,下面列出常见的要点:首先,DBMS处理查询计划的过程是这样的:
1、 查询语句的词法、语法检查
2、 将语句提交给DBMS的查询优化器
3、 优化器做代数优化和存取路径的优化
4、 由预编译模块生成查询规划
5、 然后在合适的时间提交给系统处理执行
6、 最后将执行结果返回给用户其次,看一下SQL SERVER的数据存放的结构:一个页面的大小为8K(8060)字节,8个页面为一个盘区,按照B树存放。
12、Commit和rollback的区别 Rollback:回滚所有的事物。 Commit:提交当前的事物. 没有必要在动态SQL里写事物,如果要写请写在外面如: begin tran exec(@s) commit trans 或者将动态SQL 写成函数或者存储过程。
13、在查询Select语句中用Where字句限制返回的行数,避免表扫描,如果返回不必要的数据,浪费了服务器的I/O资源,加重了网络的负担降低性能。如果表很大,在表扫描的期间将表锁住,禁止其他的联接访问表,后果严重。
14、SQL的注释申明对执行没有任何影响
15、尽可能不使用光标,它占用大量的资源。如果需要row-by-row地执行,尽量采用非光标技术,如:在客户端循环,用临时表,Table变量,用子查询,用Case语句等等。游标可以按照它所支持的提取选项进行分类: 只进 必须按照从第一行到最后一行的顺序提取行。FETCH NEXT 是唯一允许的提取操作,也是默认方式。可滚动性 可以在游标中任何地方随机提取任意行。游标的技术在SQL2000下变得功能很强大,他的目的是支持循环。有四个并发选项 READ_ONLY:不允许通过游标定位更新(Update),且在组成结果集的行中没有锁。 OPTIMISTIC WITH valueS:乐观并发控制是事务控制理论的一个标准部分。乐观并发控制用于这样的情形,即在打开游标及更新行的间隔中,只有很小的机会让第二个用户更新某一行。当某个游标以此选项打开时,没有锁控制其中的行,这将有助于最大化其处理能力。如果用户试图修改某一行,则此行的当前值会与最后一次提取此行时获取的值进行比较。如果任何值发生改变,则服务器就会知道其他人已更新了此行,并会返回一个错误。如果值是一样的,服务器就执行修改。 选择这个并发选项�OPTIMISTIC WITH ROW VERSIONING:此乐观并发控制选项基于行版本控制。使用行版本控制,其中的表必须具有某种版本标识符,服务器可用它来确定该行在读入游标后是否有所更改。在 SQL Server 中,这个性能由 timestamp 数据类型提供,它是一个二进制数字,表示数据库中更改的相对顺序。每个数据库都有一个全局当前时间戳值:@@DBTS。每次以任何方式更改带有 timestamp 列的行时,SQL Server 先在时间戳列中存储当前的 @@DBTS 值,然后增加 @@DBTS 的值。如果某 个表具有 timestamp 列,则时间戳会被记到行级。服务器就可以比较某行的当前时间戳值和上次提取时所存储的时间戳值,从而确定该行是否已更新。服务器不必比较所有列的值,只需比较 timestamp 列即可。如果应用程序对没有 timestamp 列的表要求基于行版本控制的乐观并发,则游标默认为基于数值的乐观并发控制。 SCROLL LOCKS 这个选项实现悲观并发控制。在悲观并发控制中,在把数据库的行读入游标结果集时,应用程序将试图锁定数据库行。在使用服务器游标时,将行读入游标时会在其上放置一个更新锁。如果在事务内打开游标,则该事务更新锁将一直保持到事务被提交或回滚;当提取下一行时,将除去游标锁。如果在事务外打开游标,则提取下一行时,锁就被丢弃。因此,每当用户需要完全的悲观并发控制时,游标都应在事务内打开。更新锁将阻止任何其它任务获取更新锁或排它锁,从而阻止其它任务更新该行。然而,更新锁并不阻止共享锁,所以它不会阻止其它任务读取行,除非第二个任务也在要求带更新锁的读取。滚动锁根据在游标定义的 SELECT 语句中指定的锁提示,这些游标并发选项可以生成滚动锁。滚动锁在提取时在每行上获取,并保持到下次提取或者游标关闭,以先发生者为准。下次提取时,服务器为新提取中的行获取滚动锁,并释放上次提取中行的滚动锁。滚动锁独立于事务锁,并可以保持到一个提交或回滚操作之后。如果提交时关闭游标的选项为关,则 COMMIT 语句并不关闭任何打开的游标,而且滚动锁被保留到提交之后,以维护对所提取数据的隔离。所获取滚动锁的类型取决于游标并发选项和游标 SELECT 语句中的锁提示。锁提示 只读 乐观数值 乐观行版本控制 锁定无提示 未锁定 未锁定 未锁定 更新 NOLOCK 未锁定 未锁定 未锁定 未锁定 HOLDLOCK 共享 共享 共享 更新 UPDLOCK 错误 更新 更新 更新 TABLOCKX 错误 未锁定 未锁定 更新其它 未锁定 未锁定 未锁定 更新 *指定 NOLOCK 提示将使指定了该提示的表在游标内是只读的。
16、用Profiler来跟踪查询,得到查询所需的时间,找出SQL的问题所在;用索引优化器优化索引
17、注意UNion和UNion all 的区别。UNION all好
18、注意使用DISTINCT,在没有必要时不要用,它同UNION一样会使查询变慢。重复的记录在查询里是没有问题的
19、查询时不要返回不需要的行、列
20、用sp_configure 'query governor cost limit'或者SET QUERY_GOVERNOR_COST_LIMIT来限制查询消耗的资源。当评估查询消耗的资源超出限制时,服务器自动取消查询,在查询之前就扼杀掉。SET LOCKTIME设置锁的时间
21、用select top 100 / 10 Percent 来限制用户返回的行数或者SET ROWCOUNT来限制操作的行
22、在SQL2000以前,一般不要用如下的字句: "IS NULL", "<>", "!=", "!>", "!<", "NOT", "NOT EXISTS", "NOT IN", "NOT LIKE", and "LIKE '%500'",因为他们不走索引全是表扫描。也不要在WHere字句中的列名加函数,如Convert,substring等,如果必须用函数的时候,创建计算列再创建索引来替代.还可以变通写法:WHERE SUBSTRING(firstname,1,1) = 'm'改为WHERE firstname like 'm%'(索引扫描),一定要将函数和列名分开。并且索引不能建得太多和太大。NOT IN会多次扫描表,使用EXISTS、NOT EXISTS ,IN , LEFT OUTER JOIN 来替代,特别是左连接,而Exists比IN更快,最慢的是NOT操作.如果列的值含有空,以前它的索引不起作用,现在2000的优化器能够处理了。相同的是IS NULL,"NOT", "NOT EXISTS", "NOT IN"能优化她,而"<>"等还是不能优化,用不到索引。
23、使用Query Analyzer,查看SQL语句的查询计划和评估分析是否是优化的SQL。一般的20%的代码占据了80%的资源,我们优化的重点是这些慢的地方。
24、如果使用了IN或者OR等时发现查询没有走索引,使用显示申明指定索引:
SELECT * FROM PersonMember (INDEX = IX_Title) WHERE processid IN ('男','女')
25、将需要查询的结果预先计算好放在表中,查询的时候再SELECT。这在SQL7.0以前是最重要的手段。例如医院的住院费计算。
26、MIN() 和 MAX()能使用到合适的索引。
27、数据库有一个原则是代码离数据越近越好,所以优先选择Default,依次为Rules,Triggers, Constraint(约束如外健主健CheckUNIQUE……,数据类型的最大长度等等都是约束),Procere.这样不仅维护工作小,编写程序质量高,并且执行的速度快。
28、如果要插入大的二进制值到Image列,使用存储过程,千万不要用内嵌INsert来插入(不知JAVA是否)。因为这样应用程序首先将二进制值转换成字符串(尺寸是它的两倍),服务器受到字符后又将他转换成二进制值.存储过程就没有这些动作: 方法:
Create procere p_insert as insert into table(Fimage) values (@image)
在前台调用这个存储过程传入二进制参数,这样处理速度明显改善。
29、Between在某些时候比IN速度更快,Between能够更快地根据索引找到范围。用查询优化器可见到差别。
select * from chineseresume where title in ('男','女')
Select * from chineseresume where title between '男' and '女'
是一样的。由于in会在比较多次,所以有时会慢些。
30、在必要是对全局或者局部临时表创建索引,有时能够提高速度,但不是一定会这样,因为索引也耗费大量的资源。他的创建同是实际表一样。
31、不要建没有作用的事物例如产生报表时,浪费资源。只有在必要使用事物时使用它。
32、用OR的字句可以分解成多个查询,并且通过UNION 连接多个查询。他们的速度只同是否使用索引有关,如果查询需要用到联合索引,用UNION all执行的效率更高.多个OR的字句没有用到索引,改写成UNION的形式再试图与索引匹配。一个关键的问题是否用到索引。
33、尽量少用视图,它的效率低。对视图操作比直接对表操作慢,可以用stored procere来代替她。特别的是不要用视图嵌套,嵌套视图增加了寻找原始资料的难度。我们看视图的本质:它是存放在服务器上的被优化好了的已经产生了查询规划的SQL。对单个表检索数据时,不要使用指向多个表的视图,直接从表检索或者仅仅包含这个表的视图上读,否则增加了不必要的开销,查询受到干扰.为了加快视图的查询,MsSQL增加了视图索引的功能。
34、没有必要时不要用DISTINCT和ORDER BY,这些动作可以改在客户端执行。它们增加了额外的开销。这同UNION 和UNION ALL一样的道理。
select top 20 ad.companyname,comid,position,ad.referenceid,worklocation,
convert(varchar(10),ad.postDate,120) as postDate1,workyear,degreedescription FROM
jobcn_query.dbo.COMPANYAD_query ad where referenceID in('JCNAD00329667','JCNAD132168','JCNAD00337748','JCNAD00338345',
'JCNAD00333138','JCNAD00303570','JCNAD00303569',
'JCNAD00303568','JCNAD00306698','JCNAD00231935','JCNAD00231933',
'JCNAD00254567','JCNAD00254585','JCNAD00254608',
'JCNAD00254607','JCNAD00258524','JCNAD00332133','JCNAD00268618',
'JCNAD00279196','JCNAD00268613') order by postdate desc
35、在IN后面值的列表中,将出现最频繁的值放在最前面,出现得最少的放在最后面,减少判断的次数。
36、当用SELECT INTO时,它会锁住系统表(sysobjects,sysindexes等等),阻塞其他的连接的存取。创建临时表时用显示申明语句,而不是
select INTO. drop table t_lxh begin tran select * into t_lxh from chineseresume
where name = 'XYZ' --commit
在另一个连接中SELECT * from sysobjects可以看到 SELECT INTO 会锁住系统表,Create table 也会锁系统表(不管是临时表还是系统表)。所以千万不要在事物内使用它!!!这样的话如果是经常要用的临时表请使用实表,或者临时表变量。
37、一般在GROUP BY 个HAVING字句之前就能剔除多余的行,所以尽量不要用它们来做剔除行的工作。他们的执行顺序应该如下最优:select 的Where字句选择所有合适的行,Group By用来分组个统计行,Having字句用来剔除多余的分组。这样Group By 个Having的开销小,查询快.对于大的数据行进行分组和Having十分消耗资源。如果Group BY的目的不包括计算,只是分组,那么用Distinct更快
38、一次更新多条记录比分多次更新每次一条快,就是说批处理好
39、少用临时表,尽量用结果集和Table类性的变量来代替它,Table 类型的变量比临时表好
40、在SQL2000下,计算字段是可以索引的,需要满足的条件如下:
a、计算字段的表达是确定的
b、不能用在TEXT,Ntext,Image数据类型
c、必须配制如下选项 ANSI_NULLS = ON, ANSI_PADDINGS = ON, …….
41、尽量将数据的处理工作放在服务器上,减少网络的开销,如使用存储过程。存储过程是编译好、优化过、并且被组织到一个执行规划里、且存储在数据库中的SQL语句,是控制流语言的集合,速度当然快。反复执行的动态SQL,可以使用临时存储过程,该过程(临时表)被放在Tempdb中。以前由于SQL SERVER对复杂的数学计算不支持,所以不得不将这个工作放在其他的层上而增加网络的开销。SQL2000支持UDFs,现在支持复杂的数学计算,函数的返回值不要太大,这样的开销很大。用户自定义函数象光标一样执行的消耗大量的资源,如果返回大的结果采用存储过程
42、不要在一句话里再三的使用相同的函数,浪费资源,将结果放在变量里再调用更快
43、SELECT COUNT(*)的效率教低,尽量变通他的写法,而EXISTS快.同时请注意区别: select count(Field of null) from Table 和 select count(Field of NOT null) from Table 的返回值是不同的!!!
44、当服务器的内存够多时,配制线程数量 = 最大连接数+5,这样能发挥最大的效率;否则使用 配制线程数量<最大连接数启用SQL SERVER的线程池来解决,如果还是数量 = 最大连接数+5,严重的损害服务器的性能。
45、按照一定的次序来访问你的表。如果你先锁住表A,再锁住表B,那么在所有的存储过程中都要按照这个顺序来锁定它们。如果你(不经意的)某个存储过程中先锁定表B,再锁定表A,这可能就会导致一个死锁。如果锁定顺序没有被预先详细的设计好,死锁很难被发现
46、通过SQL Server Performance Monitor监视相应硬件的负载 Memory: Page Faults / sec计数器如果该值偶尔走高,表明当时有线程竞争内存。如果持续很高,则内存可能是瓶颈。
Process:
1、% DPC Time 指在范例间隔期间处理器用在缓延程序调用(DPC)接收和提供服务的百分比。(DPC 正在运行的为比标准间隔优先权低的间隔)。 由于 DPC 是以特权模式执行的,DPC 时间的百分比为特权时间 百分比的一部分。这些时间单独计算并且不属于间隔计算总数的一部 分。这个总数显示了作为实例时间百分比的平均忙时。
2、%Processor Time计数器 如果该参数值持续超过95%,表明瓶颈是CPU。可以考虑增加一个处理器或换一个更快的处理器。
3、% Privileged Time 指非闲置处理器时间用于特权模式的百分比。(特权模式是为操作系统组件和操纵硬件驱动程序而设计的一种处理模式。它允许直接访问硬件和所有内存。另一种模式为用户模式,它是一种为应用程序、环境分系统和整数分系统设计的一种有限处理模式。操作系统将应用程序线程转换成特权模式以访问操作系统服务)。 特权时间的 % 包括为间断和 DPC 提供服务的时间。特权时间比率高可能是由于失败设备产生的大数量的间隔而引起的。这个计数器将平均忙时作为样本时间的一部分显示。
4、% User Time表示耗费CPU的数据库操作,如排序,执行aggregate functions等。如果该值很高,可考虑增加索引,尽量使用简单的表联接,水平分割大表格等方法来降低该值。 Physical Disk: Curretn Disk Queue Length计数器该值应不超过磁盘数的1.5~2倍。要提高性能,可增加磁盘。 SQLServer:Cache Hit Ratio计数器该值越高越好。如果持续低于80%,应考虑增加内存。 注意该参数值是从SQL Server启动后,就一直累加记数,所以运行经过一段时间后,该值将不能反映系统当前值。
47、分析select emp_name form employee where salary > 3000 在此语句中若salary是Float类型的,则优化器对其进行优化为Convert(float,3000),因为3000是个整数,我们应在编程时使用3000.0而不要等运行时让DBMS进行转化。同样字符和整型数据的转换。
48、查询的关联同写的顺序
select a.personMemberID, * from chineseresume a,personmember b where personMemberID
= b.referenceid and a.personMemberID = 'JCNPRH39681' (A = B ,B = '号码')
select a.personMemberID, * from chineseresume a,personmember b where a.personMemberID
= b.referenceid and a.personMemberID = 'JCNPRH39681' and b.referenceid = 'JCNPRH39681' (A = B ,B = '号码', A = '号码')
select a.personMemberID, * from chineseresume a,personmember b where b.referenceid
= 'JCNPRH39681' and a.personMemberID = 'JCNPRH39681' (B = '号码', A = '号码')
49、
(1)IF 没有输入负责人代码 THEN code1=0 code2=9999 ELSE code1=code2=负责人代码 END IF 执行SQL语句为: SELECT 负责人名 FROM P2000 WHERE 负责人代码>=:code1 AND负责人代码 <=:code2
(2)IF 没有输入负责人代码 THEN SELECT 负责人名 FROM P2000 ELSE code= 负责人代码 SELECT 负责人代码 FROM P2000 WHERE 负责人代码=:code END IF 第一种方法只用了一条SQL语句,第二种方法用了两条SQL语句。在没有输入负责人代码时,第二种方法显然比第一种方法执行效率高,因为它没有限制条件;在输入了负责人代码时,第二种方法仍然比第一种方法效率高,不仅是少了一个限制条件,还因相等运算是最快的查询运算。我们写程序不要怕麻烦
50、关于JOBCN现在查询分页的新方法(如下),用性能优化器分析性能的瓶颈,如果在I/O或者网络的速度上,如下的方法优化切实有效,如果在CPU或者内存上,用现在的方法更好。请区分如下的方法,说明索引越小越好。
begin
DECLARE @local_variable table (FID int identity(1,1),ReferenceID varchar(20))
insert into @local_variable (ReferenceID)
select top 100000 ReferenceID from chineseresume order by ReferenceID
select * from @local_variable where Fid > 40 and fid <= 60
end
和
begin
DECLARE @local_variable table (FID int identity(1,1),ReferenceID varchar(20))
insert into @local_variable (ReferenceID)
select top 100000 ReferenceID from chineseresume order by updatedate
select * from @local_variable where Fid > 40 and fid <= 60
end
的不同
begin
create table #temp (FID int identity(1,1),ReferenceID varchar(20))
insert into #temp (ReferenceID)
select top 100000 ReferenceID from chineseresume order by updatedate
select * from #temp where Fid > 40 and fid <= 60 drop table #temp
end
㈣ 求数据库应用题
数据库语言的目标
要说清这个目标,先要理解数据库是做什么的。
数据库这个软件,名字中有个“库”字,会让人觉得它主要是为了存储的。其实不然,数据库实现的重要功能有两条:计算、事务!也就是我们常说的 OLAP 和 OLTP,数据库的存储都是为这两件事服务的,单纯的存储并不是数据库的目标。
我们知道,SQL 是目前数据库的主流语言。那么,用 SQL 做这两件事是不是很方便呢?
事务类功能主要解决数据在写入和读出时要保持的一致性,实现这件事的难度并不小,但对于应用程序的接口却非常简单,用于操纵数据库读写的代码也很简单。如果假定目前关系数据库的逻辑存储模式是合理的(也就是用数据表和记录来存储数据,其合理性与否是另一个复杂问题,不在这里展开了),那么 SQL 在描述事务类功能时没什么大问题,因为并不需要描述多复杂的动作,复杂性都在数据库内部解决了。
但计算类功能却不一样了。
这里说的计算是个更广泛的概念,并不只是简单的加加减减,查找、关联都可以看成是某种计算。
什么样的计算体系才算好呢?
还是两条:写着简单、跑得快。
写着简单,很好理解,就是让程序员很快能写出来代码来,这样单位时间内可以完成更多的工作;跑得快就更容易理解,我们当然希望更短时间内获得计算结果。
其实 SQL 中的 Q 就是查询的意思,发明它的初衷主要是为了做查询(也就是计算),这才是 SQL 的主要目标。然而,SQL 在描述计算任务时,却很难说是很胜任的。
SQL为什么不行
先看写着简单的问题。
SQL 写出来很象英语,有些查询可以当英语来读和写(网上多得很,就不举例了),这应当算是满足写着简单这一条了吧。
且慢!我们在教科书上看到的 SQL 经常只有两三行,这些 SQL 确实算是写着简单的,但如果我们尝试一些稍复杂化的问题呢?
这是一个其实还不算很复杂的例子:计算一支股票最长连续上涨了多少天?用 SQL 写出来是这样的:
- selectmax(consecutive_day)from(selectcount(*) (consecutive_dayfrom(selectsum(rise_mark) over(orderbytrade_date) days_no_gainfrom(selecttrade_date,case when closing_price>lag(closing_price) over(order by trade_date)then 0 else 1 END rise_markfrom stock_price ) )group by days\_no\_gain)
- SELECTTOP 10x FROMT ORDERBYx DESC
- stock_price.sort(trade_date).group@o(closing_price
- T.groups(;top(-10,x))
这个语句的工作原理就不解释了,反正有点绕,同学们可以自己尝试一下。
这是润乾公司的招聘考题,通过率不足 20%;因为太难,后来被改成另一种方式:把 SQL 语句写出来让应聘者解释它在算什么,通过率依然不高。
这说明什么?说明情况稍有复杂,SQL 就变得即难懂又难写!
再看跑得快的问题,还是一个经常拿出来的简单例子:1 亿条数据中取前 10 名。这个任务用 SQL 写出来并不复杂:
但是,这个语句对应的执行逻辑是先对所有数据进行大排序,然后再取出前 10 个,后面的不要了。大家知道,排序是一个很慢的动作,会多次遍历数据,如果数据量大到内存装不下,那还需要外存做缓存,性能还会进一步急剧下降。如果严格按这句 SQL 体现的逻辑去执行,这个运算无论如何是跑不快的。然而,很多程序员都知道这个运算并不需要大排序,也用不着外存缓存,一次遍历用一点点内存就可以完成,也就是存在更高性能的算法。可惜的是,用 SQL 却写不出这样的算法,只能寄希望于数据库的优化器足够聪明,能把这句 SQL 转换成高性能算法执行,但情况复杂时数据库的优化器也未必靠谱。
看样子,SQL 在这两方面做得都不够好。这两个并不复杂的问题都是这样,现实中数千行的 SQL 代码中,这种难写且跑不快的情况比比皆是。
为什么 SQL 不行呢?
要回答这个问题,我们要分析一下用程序代码实现计算到底是在干什么。
本质上讲,编写程序的过程,就是把解决问题的思路翻译成计算机可执行的精确化形式语言的过程。举例来说,就象小学生解应用题,分析问题想出解法之后,还要列出四则运算表达式。用程序计算也是一样,不仅要想出解决问题的方法,还要把解法翻译成计算机能理解执行的动作才算完成。
用于描述计算方法的形式语言,其核心在于所采用的代数体系。所谓代数体系,简单说就是一些数据类型和其上的运算规则,比如小学学到的算术,就是整数和加减乘除运算。有了这套东西,我们就能把想做的运算用这个代数体系约定的符号写出来,也就是代码,然后计算机就可以执行了。
如果这个代数体系设计时考虑不周到,提供的数据类型和运算不方便,那就会导致描述算法非常困难。这时候会发生一个怪现象:翻译解法到代码的难度远远超过解决问题本身。
举个例子,我们从小学习用阿拉伯数字做日常计算,做加减乘除都很方便,所有人都天经地义认为数值运算就该是这样的。其实未必!估计很多人都知道还有一种叫做罗马数字的东西,你知道用罗马数字该怎么做加减乘除吗?古罗马人又是如何上街买菜的?
代码难写很大程度是代数的问题。
再看跑不快的原因。
软件没办法改变硬件的性能,CPU 和硬盘该多快就是多快。不过,我们可以设计出低复杂度的算法,也就是计算量更小的算法,这样计算机执行的动作变少,自然也就会快了。但是,光想出算法还不够,还要把这个算法用某种形式语言写得出来才行,否则计算机不会执行。而且,写起来还要比较简单,都要写很长很麻烦,也没有人会去用。所以呢,对于程序来讲,跑得快和写着简单其实是同一个问题,背后还是这个形式语言采用的代数的问题。如果这个代数不好,就会导致高性能算法很难实现甚至实现不了,也就没办法跑得快了。就象上面说的,用 SQL 写不出我们期望的小内存单次遍历算法,能不能跑得快就只能寄希望于优化器。
我们再做个类比:
上过小学的同学大概都知道高斯计算 1+2+3+…+100 的小故事。普通人就是一步步地硬加 100 次,高斯小朋友很聪明,发现 1+100=101、2+99=101、…、50+51=101,结果是 50 乘 101,很快算完回家午饭了。
听过这个故事,我们都会感慨高斯很聪明,能想到这么巧妙的办法,即简单又迅速。这没有错,但是,大家容易忽略一点:在高斯的时代,人类的算术体系(也是一个代数)中已经有了乘法!象前面所说,我们从小学习四则运算,会觉得乘法是理所当然的,然而并不是!乘法是后于加法被发明出来的。如果高斯的年代还没有乘法,即使有聪明的高斯,也没办法快速解决这个问题。
目前主流数据库是关系数据库,之所以这么叫,是因为它的数学基础被称为关系代数,SQL 也就是关系代数理论上发展出来的形式语言。
现在我们能回答,为什么 SQL 在期望的两个方面做得不够好?问题出在关系代数上,关系代数就像一个只有加法还没发明乘法的算术体系,很多事做不好是必然的。
关系代数已经发明五十年了,五十年前的应用需求以及硬件环境,和今天比的差异是很巨大了,继续延用五十年前的理论来解决今天的问题,听着就感觉太陈旧了?然而现实就是这样,由于存量用户太多,而且也还没有成熟的新技术出现,基于关系代数的 SQL,今天仍然是最重要的数据库语言。虽然这几十年来也有一些改进完善,但根子并没有变,面对当代的复杂需求和硬件环境,SQL 不胜任也是情理之中的事。
而且,不幸的是,这个问题是理论上的,在工程上无论如何优化也无济于事,只能有限改善,不能根除。不过,绝大部分的数据库开发者并不会想到这一层,或者说为了照顾存量用户的兼容性,也没打算想到这一层。于是,主流数据库界一直在这个圈圈里打转转。
SPL为什么能行
那么该怎样让计算写着更简单、跑得更快呢?
发明新的代数!有“乘法”的代数。在其基础上再设计新的语言。
这就是 SPL 的由来。它的理论基础不再是关系代数,称为离散数据集。基于这个新代数设计的形式语言,起名为SPL(Structured Process Language)。
SPL 针对 SQL 的不足(更确切地说法是,离散数据集针对关系代数的各种缺陷)进行了革新。SPL 重新定义了并扩展许多结构化数据中的运算,增加了离散性、强化了有序计算、实现了彻底的集合化、支持对象引用、提倡分步运算。
限于篇幅,这里不能介绍 SPL(离散数据集)的全貌。我们在这里列举 SPL(离散数据集)针对 SQL(关系代数)的部分差异化改进:
游离记录
离散数据集中的记录是一种基本数据类型,它可以不依赖于数据表而独立存在。数据表是记录构成的集合,而构成某个数据表的记录还可以用于构成其它数据表。比如过滤运算就是用原数据表中满足条件的记录构成新数据表,这样,无论空间占用还是运算性能都更有优势。
关系代数没有可运算的数据类型来表示记录,单记录实际上是只有一行的数据表,不同数据表中的记录也不能共享。比如,过滤运算时会复制出新记录来构成新数据表,空间和时间成本都变大。
特别地,因为有游离记录,离散数据集允许记录的字段取值是某个记录,这样可以更方便地实现外键连接。
有序性
关系代数是基于无序集合设计的,集合成员没有序号的概念,也没有提供定位计算以及相邻引用的机制。SQL 实践时在工程上做了一些局部完善,使得现代 SQL 能方便地进行一部分有序运算。
离散数据集中的集合是有序的,集合成员都有序号的概念,可以用序号访问成员,并定义了定位运算以返回成员在集合中的序号。离散数据集提供了符号以在集合运算中实现相邻引用,并支持针对集合中某个序号位置进行计算。
有序运算很常见,却一直是 SQL 的困难问题,即使在有了窗口函数后仍然很繁琐。SPL 则大大改善了这个局面,前面那个股票上涨的例子就能说明问题。
离散性与集合化
关系代数中定义了丰富的集合运算,即能将集合作为整体参加运算,比如聚合、分组等。这是 SQL 比 Java 等高级语言更为方便的地方。
但关系代数的离散性非常差,没有游离记录。而 Java 等高级语言在这方面则没有问题。
离散数据集则相当于将离散性和集合化结合起来了,既有集合数据类型及相关的运算,也有集合成员游离在集合之外单独运算或再组成其它集合。可以说 SPL 集中了 SQL 和 Java 两者的优势。
有序运算是典型的离散性与集合化的结合场景。次序的概念只有在集合中才有意义,单个成员无所谓次序,这里体现了集合化;而有序计算又需要针对某个成员及其相邻成员进行计算,需要离散性。
在离散性的支持下才能获得更彻底的集合化,才能解决诸如有序计算类型的问题。
离散数据集是即有离散性又有集合化的代数体系,关系代数只有集合化。
分组理解
分组运算的本意是将一个大集合按某种规则拆成若干个子集合,关系代数中没有数据类型能够表示集合的集合,于是强迫在分组后做聚合运算。
离散数据集中允许集合的集合,可以表示合理的分组运算结果,分组和分组后的聚合被拆分成相互独立的两步运算,这样可以针对分组子集再进行更复杂的运算。
关系代数中只有一种等值分组,即按分组键值划分集合,等值分组是个完全划分。
离散数据集认为任何拆分大集合的方法都是分组运算,除了常规的等值分组外,还提供了与有序性结合的有序分组,以及可能得到不完全划分结果的对位分组。
聚合理解
关系代数中没有显式的集合数据类型,聚合计算的结果都是单值,分组后的聚合运算也是这样,只有 SUM、COUNT、MAX、MIN 等几种。特别地,关系代数无法把 TOPN 运算看成是聚合,针对全集的 TOPN 只能在输出结果集时排序后取前 N 条,而针对分组子集则很难做到 TOPN,需要转变思路拼出序号才能完成。
离散数据集提倡普遍集合,聚合运算的结果不一定是单值,仍然可能是个集合。在离散数据集中,TOPN 运算和 SUM、COUNT 这些是地位等同的,即可以针对全集也可以针对分组子集。
SPL 把 TOPN 理解成聚合运算后,在工程实现时还可以避免全量数据的排序,从而获得高性能。而 SQL 的 TOPN 总是伴随 ORDER BY 动作,理论上需要大排序才能实现,需要寄希望于数据库在工程实现时做优化。
有序支持的高性能
离散数据集特别强调有序集合,利用有序的特征可以实施很多高性能算法。这是基于无序集合的关系代数无能为力的,只能寄希望于工程上的优化。
下面是部分利用有序特征后可以实施的低复杂度运算:
1) 数据表对主键有序,相当于天然有一个索引。对键字段的过滤经常可以快速定位,以减少外存遍历量。随机按键值取数时也可以用二分法定位,在同时针对多个键值取数时还能重复利用索引信息。
2) 通常的分组运算是用 HASH 算法实现的,如果我们确定地知道数据对分组键值有序,则可以只做相邻对比,避免计算 HASH 值,也不会有 HASH 冲突的问题,而且非常容易并行。
3) 数据表对键有序,两个大表之间对位连接可以执行更高性能的归并算法,只要对数据遍历一次,不必缓存,对内存占用很小;而传统的 HASH 值分堆方法不仅比较复杂度高,需要较大内存并做外部缓存,还可能因 HASH 函数不当而造成二次 HASH 再缓存。
4) 大表作为外键表的连接。事实表小时,可以利用外键表有序,快速从中取出关联键值对应的数据实现连接,不需要做 HASH 分堆动作。事实表也很大时,可以将外键表用分位点分成多个逻辑段,再将事实表按逻辑段进行分堆,这样只需要对一个表做分堆,而且分堆过程中不会出现 HASH 分堆时的可能出现的二次分堆,计算复杂度能大幅下降。
其中 3 和 4 利用了离散数据集对连接运算的改造,如果仍然延用关系代数的定义(可能产生多对多),则很难实现这种低复杂的算法。
除了理论上的差异, SPL 还有许多工程层面的优势,比如更易于编写并行代码、大内存预关联提高外键连接性能等、特有的列存机制以支持随意分段并行等。
再把前面的问题用 SPL 重写一遍有个直接感受。
一支股票最长连续上涨多少天:
计算思路和前面的 SQL 相同,但因为引入了有序性后,表达起来容易多了,不再绕了。
1 亿条数据中取前 10 名:
SPL 有更丰富的集合数据类型,容易描述单次遍历上实施简单聚合的高效算法,不涉及大排序动作。
这里还有更多 SPL 代码以体现其思路及大数据算法:
重磅!开源SPL交流群成立了
简单好用的SPL开源啦!
为了给感兴趣的小伙伴们提供一个相互交流的平台,
特地开通了交流群(群完全免费,不广告不卖课)
需要进群的朋友,可长按扫描下方二维码