【问题标题】:Efficient MySQL Queries on a Normalized Table with Complex and/or Conditions具有复杂和/或条件的规范化表上的高效 MySQL 查询
【发布时间】:2014-08-16 05:53:33
【问题描述】:

我有下表:

create table x (
    id integer, 
    property integer
);

我需要高效地运行查询来测试给定 ID 的多个属性值。例如,我可能想要一个查询来获取所有具有满足条件的属性的 id:1 而不是(2 或 3 或 4 或 5)

如果我的所有属性都在布尔列中('t1' = true 如果属性值 1 存在 id,否则为 false 等),那么我可以简单地运行这个非常快速的查询(假设 y 是这样一个表):

select * from y where t1 and not (t2 or t3 or t4 or t5);

不幸的是,我有数千个属性,所以这行不通。此外,查询没有押韵或理由,因此虽然我可以将属性组捆绑到概念关系中,但查询边界不尊重这一点。此外,这些查询是(间接)由用户确定的,因此在预期中创建视图将无济于事。最后,我们将不断添加具有新属性的数据集,其关系可能是新的、模糊的或交叉的,这意味着尝试创建关系表可能会成为维护的噩梦。

因此我选择了原始模式。

为了完成我的查询,我首先尝试在查询中涉及的字段上创建一个数据透视,然后进行查询:

create table pivot as (
    select 
        id,
        max(if(property=1,true,false)) as 't1',
        max(if(property=2,true,false)) as 't2',
        max(if(property=3,true,false)) as 't3',
        max(if(property=4,true,false)) as 't4',
        max(if(property=5,true,false)) as 't5'
    from x);
select * from pivot where t1 and not (t2 or t3 or t4 or t5);

但是,这非常慢。事实上,它比未经优化的自制解决方案要慢。

我知道我可以生成带有子查询的复杂查询,但有限的测试表明性能会更差(除非我的查询结构不正确)。

我可以做些什么来加快查询速度?

【问题讨论】:

  • 是什么让你觉得巨大太大了?
  • 它太大了,因为它 > MySQL 的最大列数 (4,096)。将数据拆分为多个表可以解决这个问题,但代价是查询多个表——可能有数千个表。
  • 表格中有哪些行?据推测,布尔值是根据其他信息计算得出的,因此可能有一种方法可以对整个表进行不同的编码。
  • @philipxy -- 我不明白您所说的“表格中有哪些行?”是什么意思。计算的布尔值很少。绝大多数是测量的。所以每个属性都是一个测量 ID。
  • 实测:好的。关于“表中有哪些行?”:有些行是表,有些则不是。对于与标题匹配的每个基表行,我们决定它是否进入。例如,Employee(i,n,a) 是“员工 [i] 的姓名为 [n] 并居住在 [a]”的行。它们“满足”了“关系”的“特征谓词”。特征谓词通常就是我们所说的表格的“意义”。我可以说,你能规范化布尔表吗?规范化表正在从特征谓词中删除 AND。

标签: mysql database normalization database-normalization


【解决方案1】:

如果你有:

P(id,property) -- thing [i] has property #[property]

你想要的:

x(i,p1,p2,p3,p4,p5) -- thing [i] has property p1 per [p1] and ...

这符合您的需要吗?

CREATE VIEW x AS
    SELECT id,
        property=p1 AS p1,
        property=p2 AS p2,
        property=p3 AS p3,
        property=p4 AS p4,
        property=p5 AS p5
    FROM P

大概你正在做这样的事情:

--  for A(id,...)
SELECT id,... FROM A JOIN x USING(id) WHERE t1 AND NOT (t2 OR t3 OR t4 OR t5)

您只需要 A 行,其中 id 为 P(id,t1) 而不是 ...。

(如果您确实创建了一个包含多个布尔列的表,您希望最小化存储,因为您可以使用 BIT (1)(BIT (m) 需要 (m+7)/8 个字节)。您可以使用它们就像布尔值一样。

不要自己将多个位编码为值,这会阻止 DBMS 进行优化。)

【讨论】:

  • 谢谢,但这比我现在做的效率低。现在,如果 id 属性对的值为 true,我只存储 id 和属性。如果为假,则根本不会存储该条目。通过使用 BIT,我最终不仅需要在表中添加一个额外的列,而且还必须存储所有错误值。我想这会减慢我的查询速度并显着增加我的表大小。
  • “数据透视表”每次都会变化,所以没有固定的视图。另外,我基本上是在做 CREATE VIEW 所做的事情,这样做并不会更快:(。我对透视表的查询也不需要连接,因为那时所有列都已到位,所以在哪里子句就足够了。
  • 无法告诉您是否需要一个表达式 inline vs view 或者您是否需要更早地合并它(即 P(id,property))(例如 pre-旋转)因为您还没有发布相关的 DDL。 (您自己的问题提供了一个创建表,所以我只是回应了这一点。)您没有提供足够的信息来回答您的问题。定义上的优化是语义正确表达式的不足,因为实现关心使用的特定语法。您必须提供(最少的)示例表定义和有问题的查询。最好是可执行的。
  • 实际上,我提供的是实际的 DDL 和实际的示例查询,字段名称已更改。我还分析了查询,这就是我知道它们比自制解决方案慢的原因。如果我遗漏了什么,请告诉我。我会调整措辞以明确这是我的实际表格,我会输入一些硬数字。
  • 您还没有给出 DDL 和所有相关的查询,例如 A、B、C、X、Y,例如“用户确定的查询”,例如涉及哪些索引?
【解决方案2】:

我假设id 不是唯一的,并且现有记录(some_id, property_id) 意味着该属性是on

首先,我注意到I need to efficiently run queries that test multiple property values for a given idI may want a query that gets all ids with a property satisfying the condition: 1 and not (2 or 3 or 4 or 5) 可能会导致完全不同的查询。

但这是我的想法。更多假设:

  • 总是有一个积极的条件会削减 id 的很大一部分(否则我会将几个属性合并到一个额外的属性中,成为这样的条件)
  • 任何一对(id, property) 都是唯一的(你甚至有相应的唯一索引)

现在,如果您在(property, id) 上有一个索引,那么以下查询将从覆盖索引中获取所有匹配的ids(很快):

SELECT id
FROM t1 
WHERE property = 150;

如果此查询导致的结果集明显小于整个表,您可以为另一个属性创建另一个快速相关子查询,这将显着减少结果集。此子查询将需要另一个覆盖索引(id, property),并且它需要相应的 UNIQUE 索引:

SELECT id
FROM t1 
WHERE property = 150 
  AND NOT EXISTS (
    SELECT 1
    FROM t2
    WHERE t2.id = t1.id AND t2.property = 130
  )
  AND NOT EXISTS (
    SELECT 1
    FROM t2
    WHERE t2.id = t1.id AND t2.property = 90
  );

如果先前的相关子查询结果为 false,则不会对该行执行所有后续子查询。这就是为什么顺序至关重要。

您将需要使用属性顺序,并且可能在执行查询的代码中对其进行硬编码。

UPD:那么,恐怕你别无选择。您可以做的最好的事情是一次遍历索引并计算您需要的内容。那么查询的速度将主要取决于表中的行数。因此,再次假设您在 (id, property) 上有 UNIQUE 索引,您可以编写如下内容:

SELECT id
FROM t1
GROUP BY id
HAVING 
  COUNT(IF(property=150, 1, NULL))
  AND NOT COUNT(IF(property=130, 1, NULL))
  AND NOT COUNT(IF(property=90, 1, NULL));

【讨论】:

  • 你的假设是正确的,我希望避免这个答案:)。由于用户确定查询,因此需要一些逻辑来优化它们的结构。我会从一个直接的映射到一个非平凡的(并且可能是棘手的)优化问题。到目前为止,我所知道的(以及我收到的回复)暗示我最好使用我的专有文件格式。
  • 宾果游戏! Select/Having 比我之前使用的查询要快得多,并且优于自制解决方案。答案被接受、投票和赏金。谢谢!
【解决方案3】:

您是否考虑过构建一个由 (id, propertybin) 组成的表,该表的填充方式如下:

INSERT LookupTable (id, propertybin)
SELECT id, sum(1 << property)
FROM MyTable
GROUP BY id

现在,propertybin 为每个 ID 保存一个二进制值,其中每个位表示 MyTable 中是否存在从 0 到 63 的属性。我意识到你有超过 64 个属性,但我认为这个想法可以扩展到保存更多属性 - 因为你现在有 1 列能够保存有关 64 个属性的信息,所以你“只”需要,比如 100 个这样的列来保存6400个属性的信息。

现在,要找到满足某个条件的所有 id,您将使用按位逻辑。例如,要查找所有 id 的属性为 0,但不是 1、2、3 或 4,请使用:

SELECT id
FROM LookupTable
WHERE ((propertybin ^ 0x1E) & 0x1F) = 0x1F

在这里,我们使用异或位运算符^ 和值 0x1E = 二进制 11110 来过滤非条件。我们使用 AND 位运算符&amp;,其值为 0x1F = 二进制 11111,表示我们只对前 5 位感兴趣。

或者,可以使用足够大的 VARBINARY 类型列,从而消除每列 64 个属性的限制。但是,我不确定 MySQL 提供的过滤 VARBINARY 值中某些位的可能性。

【讨论】:

  • 有趣的想法,但不幸的是,这会降低灵活性/可扩展性,这是我希望 RDBMS 开始的主要原因,所以这不是一个选择。
猜你喜欢
  • 1970-01-01
  • 1970-01-01
  • 2012-10-08
  • 1970-01-01
  • 2012-08-27
  • 2015-03-06
  • 1970-01-01
  • 2012-04-16
  • 2015-03-30
相关资源
最近更新 更多