【问题标题】:MYSQL DB Normalization & Query IndexesMYSQL DB 规范化和查询索引
【发布时间】:2016-11-21 14:28:32
【问题描述】:

我们目前有一个包含 90 列的表,随着表的增长和业务需求的变化,我们不得不大量更改表(添加/删除列和索引)。

|------ (Table name: quotes)
|Column|Type|Null|Default
|------
|//**id**//|int(11)|No|
....
|completed_at|datetime|Yes|NULL
|reviewed_at|datetime|Yes|NULL
|marked_dud_at|datetime|Yes|NULL
|closed_at|datetime|Yes|NULL
|subscribed_at|datetime|Yes|NULL
|admin_checked_at|datetime|Yes|NULL
|priced_at|datetime|Yes|NULL
|number_verified_at|datetime|Yes|NULL
|created_at|datetime|Yes|NULL
|deleted_at|datetime|Yes|NULL

对于申请,我们的工作人员不断查询上述数据的各种变化,例如已完成(completed_at)、已检查(admin_checked_at)和未删除、已审核(deleted_at、review_at)的位置

我们认为将其中一些列卸载到自己的行中可能会更容易,我们将其称为quotes_actions,然后在查询时进行一些连接。

|------  (Table name: quotes_actions)
|Column|Type|Null|Default
|------
|//**id**//|int(11)|No|
|quote_id|int(11)|No|
|action|varchar(100)|No|
|user_id|int(11)|No|
|time|datetime|Yes|NULL
|created_at|datetime|Yes|NULL

一个例子是 action = 'completed' 使用字段,索引覆盖 quote_id 和 action。

我们已经在 150,000 行上将数据拆分为这种格式,它并不比使用正确索引查询原始数据库快也不慢。

有没有人有这方面的经验并对每种方法有任何建议或陷阱?添加覆盖索引并根据需要向原始表添加列需要花费大量时间,而第二种方法已设置好索引,但引入了更多的连接和更复杂的查询。

0.09s
select * from `quotes` 
where `completed_at` is not null 
and `approved_at` is not null 
and deleted_at is null

=>

0.0005s
select * from `quotes_new` 
inner join quotes_actions as q1 on q1.action = 'completed' and q1.quote_id = quotes_new.id
inner join quotes_actions as q2 on q2.action = 'approved' and q2.quote_id = quotes_new.id
where quotes_new.deleted_at is null

此外,如果第二种方法更好,如果报价未获批准,您如何查询否定结果?

【问题讨论】:

  • 停止所有这些改动。与团队坐下来,弄清楚如何规范您的数据库。
  • 遵循 3 个 NF 来规范化您的数据库,而不是形成这种尴尬的解决方案
  • 不确定这是否真的是一个规范化问题。您的第一个表没有显示规范化问题 IMOH。这里的问题更多的是一个不成熟的产品。与团队进行头脑风暴可能确实有帮助。但是看到“quotes_action”列的数量,并考虑到您定期添加越来越多的列,为清楚起见,我将采用第二个解决方案。您仍然可以制作一些 VIEWS 来恢复原始表格布局并方便查询。
  • 其中一些是连续的吗?重要的是“状态”——创建,然后审查,然后定价,然后完成?如果是这样,您可能需要将“状态”记录为单个有序数字并放弃日期。
  • 你的选项 2 有点 EAV 的味道,太糟糕了。

标签: mysql indexing database-normalization


【解决方案1】:

数据库设计因应用程序而异,对于一种实现来说很好的东西对另一种实现来说就很糟糕。您已经确定了一些对您很重要的事情:

  • 数据访问速度(至少不会降低当前性能)
  • 能够响应应用程序需求/更改
  • 限制查询的复杂性

如果无法查看数据库的整体性以及您是如何使用它的,我会遵循以下原则:

尽可能使用存储过程和视图

这只是很好的设计。您在应用程序和数据表之间创建了一个适配器层,它允许您在数据库(以及视图/存储的过程)中进行所需的任何更改,而无需更改应用程序本身。解耦您的系统使维护变得更加容易。这对安全也有好处,就好像外人可以访问数据的唯一方法是通过您的存储过程,您已经消除了一些攻击途径。 (还有关于 DBMS 是否会缓存存储过程的执行计划,使它们比类似的查询执行得更快的争论,但我不是 DBA 或 DBDev,所以我不涉及)。

尝试限制表格宽度

我一次又一次看到的一件事是,每当生产系统中出现需求时,就会在表中添加一列,然后他们就收工了。比重写一堆查询或查看表结构要容易得多。这是一个糟糕的设计。如果您已经按照我的第一条建议限制了应用程序层所需的更改,那么您已经限制了以正确方式实际解决表更改所需的工作。您应该始终评估数据是否属于有问题的行,或者是否应该将其卸载到自己的表中。您不应该害怕从根本上改变您的数据库,因为有时这是必要的。

查看您提供的数据,我认为您的第二个选项还可以。您已经确定了许多实际上代表相同事物的列(“状态更改”或您所说的“发生的“引用操作”)并将其从主表卸载到辅助表。这很好,并且可能会有效。您可以进一步“作弊”,通过将状态卸载到自己的表上,并使用整数而不是字符串来表示它(因为字符串对数据库无关紧要,整数索引和搜索)。

这并不是说宽桌子是一件坏事,有时桌子只需要宽。您只需要评估数据是否真的属于数据行所代表的实体。

以新的方式处理查询

您将希望使用 DBMS 的执行计划工具并了解每个查询的实际工作原理。更改连接的顺序可以极大地改变查询返回速度,您不应该害怕在查询中使用表变量和临时表。它们都是您可以使用的工具。

查询否定结果

既然你专门问了这个问题,我会解决它。这需要以稍微不同的方式考虑您的查询(因此,如果您还没有,您应该考虑学习一门课程或阅读关系代数的教科书,这会让您更容易理解数据库)。

您的原始查询使查找报价未获批准的内容变得容易。一切都在表中:approved_at 为空。简单,轻松,没有问题。然而,现在,它不是在主表的列中,而是在它自己的表中,它也代表了可以采取的所有其他操作。你需要把问题分解一下。

你想找到所有订单中没有任何动作表明它被批准的集合。在 SQL 中看起来像:

 select quote_id from quotes_action where quote_id not in 
           (select quote_id from quotes_action where action = 'approved');

最后的想法

您需要与您的团队坐下来讨论您希望如何推进该产品。花几天或几周的时间认真思考一下。头脑风暴......黑客马拉松......做一些事情来找到你喜欢的解决方案,让你的产品更好,更易于维护。我们都遇到过这样的情况,我们有一个无法维护的产品,本可以在某个时候修复,但超出了那个点。尽量不要达到这一点,并在有机会的时候解决它。

【讨论】:

  • 我不同意“尽可能”专门为 MySQL 使用存储过程和视图,因为存储过程和视图在 MySQL 中的实现效率低且难以开发。它可能会导致比它解决的问题更多的问题。此建议可能适用于其他 RDBMS 产品,例如 Oracle 或 Microsoft SQL Server。
  • @BillKarwin 你有关于他们在 MySQL 中效率低下的来源吗?从学校开始,我就没有在 MySQL 中完成存储过程工作。但是,“很难开发”本身绝不是不做某事的好理由,尤其是如果它减少了以后的工作量。
  • 这是前 MySQL 社区经理 Jay Pipes 的博客,描述了存储过程在 MySQL 中是如何编译的。 joinfu.com/2010/05/mysql-stored-procedures-aint-all-that
  • @BillKarwin 看起来从那时起一些问题已经被清理了(在 cmets 中提到),但是,就像我提到的,我没有进入关于缓存的辩论,也没有推荐存储过程他们的性能改进。为了安全性和可维护性,我推荐它们。
猜你喜欢
  • 1970-01-01
  • 1970-01-01
  • 2016-06-14
  • 1970-01-01
  • 1970-01-01
  • 2012-01-10
  • 1970-01-01
  • 2012-12-25
  • 1970-01-01
相关资源
最近更新 更多