我们目前有一个包含90列的表,随着表的增长和业务需求的变化,我们不得不改变表格(添加/删除cols&索引)。
|------ (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,reviews_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
此外,如果第二种方法更好,您如何查询否定结果,其中报价尚未获得批准?
答案 0 :(得分:0)
数据库设计因应用程序而异,对于一个实现而言非常好的事情对另一个实现来说会很糟糕。您已经确定了一些对您很重要的事情:
无法看到数据库的完整性以及您如何使用它,这些都是我遵循的原则:
这只是一个很好的设计。您可以在应用程序和数据表之间创建适配器层,这样您就可以在数据库(以及视图/存储过程)中进行所需的更改,而无需更改应用程序本身。解耦系统使维护变得更加容易。这也有利于安全,就好像外人可以通过你的存储过程访问数据的唯一方法,你已经消除了一些攻击途径。 (还讨论了DBMS是否会缓存存储过程的执行计划,使它们比类似的查询更快地执行,但我不是DBA或DBDev,所以我没有接触这一点)。
我一次又一次地看到的一件事是,每次在生产系统中出现需求时,都会将列添加到表中并将其称为一天。比重写一堆查询或查看表结构容易得多。这是一个糟糕的设计。如果您已经按照我的第一条建议限制了应用程序层所需的更改,那么您已经限制了以正确方式实际解决表更改所需的工作。您应始终评估数据是否属于相关行,还是应将其卸载到自己的表中。你不应该害怕从根本上改变你的数据库,因为有时它是必要的。
查看您提供的数据,我认为您的第二个选择是可以的。您已经确定了许多实际代表相同内容的列("状态更改"或者当您放置"引用操作"发生时)并从主表中卸载辅助表。这非常好,可能会有效。你可以进一步欺骗"通过将状态卸载到自己的表上来使表更快,并使用整数来表示它而不是字符串(因为字符串对数据库不重要,并且整数的索引和搜索速度要快得多)。
这并不是说宽表是坏事,有时表只需要宽。您只需要评估数据是否真正属于数据行所代表的实体。
您需要使用DBMS的执行计划工具,并了解每个查询的确如何运作。更改连接顺序可以大大改变查询返回速度,您不应该害怕在查询中使用表变量和临时表。它们都是您可以使用的工具。
由于您具体提出了这个问题,我将解决它。这需要以一种不同的方式考虑您的查询(因此,如果您没有,您应该考虑参加一门课程或通过关系代数的教科书,它会使理解数据库变得更加容易)。
您的原始查询可以轻松找到报价未获批准的内容。表中的全部内容:approved_at为null。简单,容易腻,没有问题。但是,现在,它不是在主表的列中,而是在它自己的表中,它也代表了可以采取的所有其他操作。你需要稍微解决问题。
您想要找到所有订单中的所有订单,没有任何动作来表示它已被批准。在SQL中看起来像:
select quote_id from quotes_action where quote_id not in
(select quote_id from quotes_action where action = 'approved');
您需要与您的团队坐下来讨论您希望如何推进此产品。花几天或几个星期真正深入思考它。头脑风暴......黑客马拉松....做一些事情来找到你喜欢的解决方案,让你的产品更好,更易于维护。我们所有人都处于这样一种情况,即我们拥有一种无法维护的产品,而这种产品本来可以修复,但超出了这一点。尽量不要达到这一点,并在有机会的时候修复它。