【问题标题】:Where to process data? Db or locally?在哪里处理数据?数据库还是本地?
【发布时间】:2014-05-02 22:29:48
【问题描述】:

我们正在研究一种解决方案,该解决方案会针对位于三个不同国家/地区的三个不同公共数据库发出许多搜索请求。 例如,搜索从一个数据库中获取数据并将它们作为参数传递给另一个数据库。该参数是一个列表,每个项目需要与OR 运算符进行逻辑连接。因此,我们最终得到了一个 sql select 语句,其中在 where 子句中链接了多达 1000 个 OR 运算符。

现在我的问题是 1000 或 500 甚至 5000 个逻辑 ANDOR select 语句中的运算符会使 db 变慢,我是否应该更好地向 pc 请求所有数据并执行我的电脑上的匹配。

数据量在 5000 到 10000 条记录之间,我们谈论的是公共数据库,因此数量不断增长。

比如这样一个sql语句:

select * from some_table
where .. and .. or .. or.. or..
      or.. or.. or.. or.. or.. or.. (1000 times)

如果我将所有数据提取到我的电脑上,我可以有一个 LINQ 语句来进行过滤。

你建议我做什么?有这方面的经验吗?

对不起,如果这是重复的,请在 cmets 中告诉我,我会删除这个问题。

编辑:

应该考虑到很多用户可能同时访问数据库。

【问题讨论】:

  • 如果您可以管理它,请尝试将 id 批量加载到您可以加入的临时表中(大多数数据库都有此实用程序)。这肯定会允许 cleaner 查询,并且可能比连接动态查询的值更快...
  • 数以千计的OR 布尔结果并不像看起来那么吓人。例如,如果您将TRUE 表示为 1,FALSE 表示为 0,则在一系列 OR 语句中,以这种方式转换的所有数千个布尔结果的总和只需大于 0 即可满足条件。
  • @Pascual 你是什么意思?我怎么能告诉 select 语句使用 1 和 0 ? :)
  • @devhedgehog,这更像是原始帖子的旁白,但我很乐意解释。我在我的 Github 帐户上设置了一个模拟查询。看看this example,让我知道你的想法。
  • 如果你能拥有 1000 个箱子并且运行速度很快,我就买它。 :) :) :)

标签: c# sql oracle linq


【解决方案1】:

我认为你应该尝试一下。

创建一个尽可能简单但又足够复杂以实现现实的示例,然后使用某种形式的基准测试运行它

最适合你的就是你应该选择做什么。

编辑:

也就是说,在一条 SQL 语句中包含如此多的 and's 和 or's 听起来确实很复杂和混乱。除非这样做有真正的好处(?),否则我可能会尝试找到一种更清洁的方法来做到这一点,例如,按照您的建议,将操作分成几个步骤并应用 Linq 或类似的东西,即使这只是为了使解决方案更易于管理。

【讨论】:

  • 我也是这么想的,只是没时间尝试。我需要对此的意见和经验。现在我无法花三天时间想出几个测试用例并将它们编码出来。我希望在这种情况下可能有一个明确的规则适用。我在问你们。
  • 好的。不过似乎有点冒险,因为很难知道不同的策略在你的情况下会如何发挥作用,因为它可能取决于很多因素,从你获取的数据到运行它的硬件。然后使用这里的答案,但我仍然建议至少做一个最小的概念证明来测试它,即使它非常简单和小——只是在你继续之前验证它没有完全偏离目标实现一些更高级的东西。
  • 我现在在想,即使我将所有内容都提取到我的电脑上,我仍然需要进行过滤,这需要时间。如果在数据库中完成过滤也需要时间,因此我认为它对我来说是一个固定常数。但是,如果我让数据库进行过滤工作,我需要将更少的数据传输到我的电脑,我会看到性能提升。这正是我现在的想法。我错了吗?你有没有做过这样的事情?
  • 嗨@devhedgehog,你对这种情况的最新想法是正确的。两个关键概念:减少 I/O 和在数据库和工作站/最终用户之间推送的数据量。过滤数据并不是真正的固定常数。与数据库服务器引擎相比,工作站在执行过滤任务方面相当差。
  • 工作站是PC,数据库服务器引擎总是比工作站强。至少通常是这样。正确的? :)
【解决方案2】:

答案是——依赖

  1. 公共数据库上的数据有多大?如果您要查询 Google,则无法获取所有数据。

  2. 可以合理地假设这些公共数据库比您的家用电脑具有更强大的硬件和数据库调优。

  3. 有没有一个选项可以让你从那些公共数据库中被列入黑名单?

  4. 顺序重要吗?如果您先查询 db 1 然后再查询 db 2 会更快然后查询 db 2 然后再查询 db 1 ?

主要是尝试和错误,什么最适合您并且是可能的。

【讨论】:

  • 它现在有大约 10 000 条记录,但可能会增长。我认为硬件是正常的,我猜。我不知道硬件。顺序确实很重要,一个数据库保存有关人员的信息,另一个保存有关活动的信息。搜索就像查找与伦敦市人相关的所有活动。我需要先去 person db 并从伦敦获取所有信息,然后再去第二个 db 查找他们的活动。
【解决方案3】:

我总是了解到,运行包含数百个 OR 语句的查询对性能不利。但是,即使在 12g 上运行此处的示例,使用主键索引查询带有 orin 的表似乎也不会改变执行计划。

所以我说:没关系。您可以考虑的唯一因素是可读性、查询长度等。

不过,我个人更喜欢where in

请参阅this 其他带有示例数据的有用问题。

【讨论】:

  • 我不认为这很奇怪,因为我需要一个数据库中的数据来选择另一个数据库。数据库需要一起发挥作用。对于这么多 Or 运算符的性能有多差,您有任何事实吗?
  • 也许这个参考有用:stackoverflow.com/a/16608145/993547。另一个答案中也有一些测试数据。
  • 基本上,该引用告诉我 IN 将在内部翻译为 OR 运算符。仍然不能帮助我做出哪个决定。
  • 所以答案是,我应该在数据库中做所有事情并通过网络将准确的结果发送到我的电脑?
  • @devhedgehog:在我看来,数据过滤是数据库构建的目的,没有其他东西有这种性能。
【解决方案4】:

使用单个查询在数据库中处理这一切。批处理类似的操作通常是您可以为数据库性能做的最好的事情。

查询中最昂贵的部分是从磁盘读取数据。一旦数据在内存中,过滤掉几千个条件是少量的工作。您的本地处理器可能比数据库服务器快。但这没关系,因为如果您返回所有记录,您的机器将花费太多时间在不必要的 IO 上。

此外,如果您每秒运行该查询一百次,SQL 查询中的 5000 个条件才是问题。

【讨论】:

    【解决方案5】:

    使用多个布尔运算符的 ORACLE 上的 SQL 查询

    评论:我与CRYSTAL REPORTS,一位数据库报表设计师合作了很多年。它是最早的基于 GUI 的拖放工具之一,它使没有太多数据库背景的开发人员可以更轻松地构建具有多个表和过滤条件的查询。权衡是该工具在后台写入SQL;很多时候,这是一个严重的性能消耗,因为运行报告文件的工作站必须吸收被查询的数据库表的整个内容,只是为了在客户端系统上本地运行过滤过程。那是十多年前的事了,但我看到其他下一代工具也能自动生成真正糟糕的 SQL 代码。

    任何软件都无法弥补糟糕的数据库设计。您不会在第一时间就做好一切(正如其他人已经注意到的那样),但是当产品在现实世界中使用PERFORMANCESCALABILITY 的需求时,稍加规划可以提供一些喘息的空间。

    演示架构和测试数据

    以下解决方案是在ORACLE 11g Release 2 RDBMS 系统上设计的。第一个表可以由数据库VIEWINLINE QUERYSUB QUERYMATERIALIZED VIEW 甚至CURSOR 输出表示,因此本示例中讨论的“属性”可能来自多个表源并加入标准。

     CREATE TABLE  "ZZ_DATA_ATTRIBUTES" 
        (   "DATA_ID" NUMBER(10,0) NOT NULL ENABLE, 
            "NAME" VARCHAR2(50), 
            "AGE" NUMBER(5,0), 
            "HH_SIZE" NUMBER(5,0), 
            "SURVEY_SCORE" NUMBER(5,0), 
            "DMA_REGION" VARCHAR2(100), 
            "LAST_CONTACT" DATE, 
            CONSTRAINT "ZZ_DATA_ATTRIBUTES_PK" PRIMARY KEY ("DATA_ID") ENABLE
        )
     /
    
     CREATE SEQUENCE   "ZZ_DATA_ATTRIBUTES_SEQ"  MINVALUE 1 MAXVALUE
     9999999999999999999999999999 INCREMENT BY 1 START WITH 41 CACHE 20 NOORDER  NOCYCLE
     /
    
     CREATE OR REPLACE TRIGGER  "BI_ZZ_DATA_ATTRIBUTES" 
       before insert on "ZZ_DATA_ATTRIBUTES"               
       for each row  
     begin   
       if :NEW."DATA_ID" is null then 
         select "ZZ_DATA_ATTRIBUTES_SEQ".nextval into :NEW."DATA_ID" from sys.dual; 
       end if; 
     end; 
    
     /
     ALTER TRIGGER  "BI_ZZ_DATA_ATTRIBUTES" ENABLE
     /
    

    SEQUENCETRIGGER 对象仅用于每个表上主键的唯一、自动递增值。

     CREATE TABLE  "ZZ_CONDITION_RESULTS" 
        (   "RESULT_ID" NUMBER(10,0) NOT NULL ENABLE, 
            "DATA_ID" NUMBER(10,0) NOT NULL ENABLE, 
            "COND_ONE" NUMBER(10,0), 
            "COND_TWO" NUMBER(10,0), 
            "COND_THREE" NUMBER(10,0), 
            "COND_FOUR" NUMBER(10,0), 
            "COND_FIVE" NUMBER(10,0), 
            CONSTRAINT "ZZ_CONDITION_RESULTS_PK" PRIMARY KEY ("RESULT_ID") ENABLE
        )
     /
    
     ALTER TABLE  "ZZ_CONDITION_RESULTS" ADD CONSTRAINT "ZZ_CONDITION_RESULTS_FK" 
     FOREIGN KEY ("DATA_ID") REFERENCES  "ZZ_DATA_ATTRIBUTES" ("DATA_ID") ENABLE
     /
    
     CREATE SEQUENCE   "ZZ_CONDITION_RESULTS_SEQ"  MINVALUE 1 MAXVALUE
     9999999999999999999999999999 INCREMENT BY 1 START WITH 1 CACHE 20 NOORDER  NOCYCLE
     /
    
     CREATE OR REPLACE TRIGGER  "BI_ZZ_CONDITION_RESULTS" 
       before insert on "ZZ_CONDITION_RESULTS"               
       for each row  
     begin   
       if :NEW."RESULT_ID" is null then 
         select "ZZ_CONDITION_RESULTS_SEQ".nextval into :NEW."RESULT_ID" from sys.dual; 
       end if; 
     end; 
    
     /
     ALTER TRIGGER  "BI_ZZ_CONDITION_RESULTS" ENABLE
     /
    

    ZZ_CONDITION_RESULTS 应该是TABLE 类型。它将包含每个单独的布尔 OR 标准的结果。虽然 1000 列实际上可能不可行,但最初的方法将展示如何排列大量布尔输出能够快速识别和隔离感兴趣的组合和模式。

    样本数据

    您可以选择自己的数据值,但创建这些值是为了让示例正常工作。我选择了MARKETING 的主题,其中汇总的数据是我们虚构的公司收集的关于他们客户的不同属性:客户姓名、年龄、hh_size(家庭规模)、一些基准调查的评分结果、DMA(人口营销地区)地区和上次联系客户的日期。

    使用 Oracle 包结构定义的布尔参数

    最初的设计是通过Oracle PL/SQL Package Object来计算业务逻辑。例如,在 OP 中:

     select * from some_table
     where .. and .. or .. or.. or..
      or.. or.. or.. or.. or.. or.. (1000 times)
    

    每个空白都是包中的一个单独的 Oracle 函数调用。结果表示为每个被评估属性记录的列值。

     create or replace package ZZ_PKG_MARKETING_DEMO as
    
     c_result_true   constant   pls_integer:= 1;
     c_result_false  constant   pls_integer:= 0;
    
     cursor attrib_cur is
        select data_id, name, age, hh_size, survey_score, dma_region,
           last_contact
           from zz_data_attributes;
    
     TYPE attrib_record_type IS RECORD (  
        data_id   zz_data_attributes.data_id%TYPE,
        name      zz_data_attributes.name%TYPE,
        age       zz_data_attributes.age%TYPE,
        hh_size   zz_data_attributes.hh_size%TYPE,
        survey_score   zz_data_attributes.survey_score%TYPE,
        dma_region     zz_data_attributes.dma_region%TYPE,
        last_contact   zz_data_attributes.last_contact%TYPE
        );
    
     function evaluate_cond_one (
        p_attrib_rec   attrib_record_type) return pls_integer;
    
     function evaluate_cond_two (
        p_attrib_rec   attrib_record_type) return pls_integer;
    
     function evaluate_cond_three (
        p_attrib_rec   attrib_record_type) return pls_integer;
    
     function evaluate_cond_four (
        p_attrib_rec   attrib_record_type) return pls_integer;
    
     function evaluate_cond_five (
        p_attrib_rec   attrib_record_type) return pls_integer;
    
     procedure main_driver;
    
     end;​
    
    
    
     create or replace package body "ZZ_PKG_MARKETING_DEMO" is
    
     function evaluate_cond_one (
        p_attrib_rec   attrib_record_type) return pls_integer
     as
     begin
      -- Checks if person is from a DMA Region in California.
       IF p_attrib_rec.dma_region like 'CA%'
         THEN return c_result_true;
         ELSE return c_result_false;
         END IF;
    
     end EVALUATE_COND_ONE;
    
    
     function evaluate_cond_two (
        p_attrib_rec   attrib_record_type) return pls_integer
     as
    
     c_begin_age_range   constant   zz_data_attributes.age%TYPE:= 20;
     c_end_age_range     constant   zz_data_attributes.age%TYPE:= 35;
    
     begin
     -- Part 1 of 2 Checks if person belongs to the 20 to 35 years age bracket
      IF p_attrib_rec.age between c_begin_age_range and c_end_age_range
         THEN return c_result_true;
         ELSE return c_result_false;
         END IF;
    
     end EVALUATE_COND_TWO;
    
    
     function evaluate_cond_three (
        p_attrib_rec   attrib_record_type) return pls_integer
     as
    
     c_lowest_age   constant   zz_data_attributes.age%TYPE:= 45;
    
     begin
     -- Part 2 of 2 Checks if person is from age 45 and up demographic.
      IF p_attrib_rec.age >= c_lowest_age
         THEN return c_result_true;
         ELSE return c_result_false;
         END IF;
    
     end EVALUATE_COND_THREE;
    
    
     function evaluate_cond_four (
        p_attrib_rec   attrib_record_type) return pls_integer
     as
    
     c_cutoff_score  CONSTANT  zz_data_attributes.survey_score%TYPE:= 1200;
    
     begin
     -- Checks if person's survey score is higher than c_cutoff_score
      IF p_attrib_rec.survey_score >= c_cutoff_score
         THEN return c_result_true;
         ELSE return c_result_false;
         END IF;
    
     end EVALUATE_COND_FOUR;
    
    
     function evaluate_cond_five (
        p_attrib_rec   attrib_record_type) return pls_integer
     as
    
     c_last_contact_period  CONSTANT  pls_integer:= -750;
    
     -- Note current date is anchored to a static value so the data output
     --   in this example will still work regardless of how old this post
     --   may get.
    
     c_current_date   CONSTANT   zz_data_attributes.last_contact%TYPE:=
       to_date('03/25/2014','MM/DD/YYYY');
    
     begin
     -- Checks if person's last contact date has been in the last 750
     -- days.
      IF p_attrib_rec.last_contact >= 
            (c_current_date + c_last_contact_period)
         THEN return c_result_true;
         ELSE return c_result_false;
         END IF;
    
     end EVALUATE_COND_FIVE;
    
    
     procedure MAIN_DRIVER
     as
    
         v_rec_attr   attrib_record_type;
         v_rec_cond   zz_condition_results%ROWTYPE;
    
     begin
        for i in attrib_cur
        loop
        -- Set the input record variable with the attribute values queried by the
        -- current cursor.
    
            v_rec_attr.data_id := i.data_id;
            v_rec_attr.name    := i.name;
            v_rec_attr.age     := i.age;
            v_rec_attr.hh_size := i.hh_size;
            v_rec_attr.survey_score := i.survey_score;
            v_rec_attr.dma_region := i.dma_region;
            v_rec_attr.last_contact := i.last_contact;
    
        -- Set each condition column value equal to their matching package function.
    
            v_rec_cond.cond_one  := evaluate_cond_one(p_attrib_rec => v_rec_attr);
            v_rec_cond.cond_two  := evaluate_cond_two(p_attrib_rec => v_rec_attr);
            v_rec_cond.cond_three:= evaluate_cond_three(p_attrib_rec => v_rec_attr);
            v_rec_cond.cond_four := evaluate_cond_four(p_attrib_rec => v_rec_attr);
            v_rec_cond.cond_five := evaluate_cond_five(p_attrib_rec => v_rec_attr);
    
            INSERT INTO zz_condition_results (data_id, cond_one, cond_two,
               cond_three, cond_four, cond_five)
            VALUES 
               ( v_rec_attr.data_id,
                 v_rec_cond.cond_one,
                 v_rec_cond.cond_two,
                 v_rec_cond.cond_three,
                 v_rec_cond.cond_four,
                 v_rec_cond.cond_five );
    

    结束循环;
    提交;

    结束 MAIN_DRIVER;

    结束“ZZ_PKG_MARKETING_DEMO”; ​

    PL/SQL 注释: 有些人可能不熟悉CUSTOM DATA TYPES,例如在过程MAIN_DRIVER 的包中定义的RECORD VARIABLE TYPE。它们为正在处理的数据提供了更易于处理和参考的标识。

    简单英语中的布尔算术(嗯,有点)

    可以修改名为ATTRIB_CURCURSOR 以对单个记录或更小的输入数据集进行操作。现在,调用MAIN_DRIVER 过程来处理属性数据源中的所有记录(同样,这不必是单个表)。

     BEGIN
        ZZ_PKG_MARKETING_DEMO.MAIN_DRIVER;
     END;
    

    现在每个示例条件都已针对所有样本记录进行了评估,有几种更简单的方法来评估布尔值,当前捕获为“1”(对于TRUE)和“0”(对于@987654353)的值@)。

    如果只需要满足这一系列条件中的一个(如在OR 运算符的长链中),那么WHERE 子句应如下所示:

     WHERE COND_ONE = 1 OR COND_TWO = 1 OR COND_THREE = 1 OR COND_FOUR = 1 OR COND_FIVE = 1
    

    一种速记方法可以是:

     WHERE (COND_ONE + COND_TWO + COND_THREE + COND_FOUR + COND_FIVE) > 0
    

    这能买到什么?通过在填充数据记录时处理其他静态评估(自定义条件)可以获得性能提升。一个很好的理由是每个询问此标准的后续查询都不需要再次处理业务逻辑。我们还通过具有非常、非常、非常低的基数(两个!)的决策值来利用优势

    WHERE 过滤条件的第二个“速记”示例是关于最终方法将如何管理“数千个”布尔评估的线索。

    可扩展性:如何连续执行数千次

    假设这种方法可以扩展到 OP 中提出的数量级是不切实际的。最后一个问题:这个解决方案如何适用于 N 千个布尔值链?

    提示:PIVOT 你的结果。

    大量布尔条件的可扩展表设计

    这里还有一个表格模型,其中包含示例数据的适合方式:

    获取五个样本条件之间的多个OR关系所需的SQL可以通过聚合查询来完成:

     -- For multiple OR relations:
    
     SELECT DATA_ID
       FROM ZZ_CONDITION_PIVOT
      GROUP BY DATA_ID
     HAVING SUM(RESULT) > 0
    

    退伍军人可能会注意到,使用支持的数据库ANALYTICAL FUNCTIONS 可以进一步简化此语法。

    这种设计应该是低维护的,在实现期间或之后引入了任意数量的布尔条件。表格设计应始终保持不变。

    让我知道你的想法,看起来讨论已经转移到其他问题和贡献者上,所以这可能足够让你开始。前进!

    【讨论】:

      猜你喜欢
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 2014-02-02
      • 1970-01-01
      • 2018-10-18
      相关资源
      最近更新 更多