在第一次解决事情的时候,我们都有可能会犯许多错误。这篇文章介绍了某人忘记在某些数据库上配置索引并最终导致全表扫描(或类似)的事情。其导致的问题并不仅仅是速度慢,而且还花费了相当多额外的金钱,因为某些供应商是按查询行或类似的方式收费。
很多文章也在讨论这种情况,这里我想描述一下我遇到的一个非常糟糕的数据库模式,然后告诉你它对系统性能的影响。
该系统旨在延缓发送或阻止发送当时(2002 年底)大量传播的“开放代理”垃圾邮件。这些垃圾邮件发送者会使用开放代理,然后通过代理连接到的邮件服务器,然后尝试发送垃圾邮件。他们伪造了你能想到的所有东西——HELO、MAIL FROM,并且也没有两次使用相同的开放代理,因此根据邮件本身并不能有效地阻止发送。
我们有开放代理主机的列表,但我们发现也有许多“合法”的组织在使用它们,屏蔽列表内的主机会影响某些正常使用它们的用户。老板已经下令不再使用这些列表。我们需要使用其他方式来完成这项工作。
根据观察,我们可以将 IP 地址、HELO 字符串、FROM 地址和 TO 地址存储在一个表中,并在我们第一次看到该特定元组(或“四元组”)时发回 4xx“临时故障”错误。正确执行 SMTP 的真正邮件服务器会在某个时间点重试,通常是 15 分钟到一个小时后。如果它确实在经过了足够长时间后重试,我们将允许它发送。
很简单吧?但是实现的数据库绝对是糟糕的。使用标准的 MySQL 还不算是坏处。不好的部分是架构。表中的行如下所示:
id | ip | helo | m_from | m_to | time | ...
“漂亮!”,你可能会想。不就应该是这四列加上一个时间吗?是的,但问题在于它是如何存储的。那些东西是字符串(数据库中的 varchars)。因此,表中每一行都看起来像这样:
1 | ip1 | blahblah | evil@spammer.somewhere | victim1@our.domain | ...
2 | ip2 | foobar | another@evil.spammer | victim2@our.domain | ...
3 | ip3 | MAILSERV | legit@person | user@our.domain | ...
4 | ip4 | foobar | another@evil.spammer | victim1@our.domain | ...
每一行都是重量级的:它里面存储了实际的原始字符串。现在想想数据库必须做些什么来匹配行。我们可能会写出这样的SQL:
SELECT whatever FROM quads WHERE ip='ip1' AND HELO='thing_they_sent_us' AND m_from='whatever' AND m_to='some_user'
可怜的数据库啊!它必须从第一行开始一直进行字符串比较!这意味着它必须读取表中的每一行,然后将查询中的字符与它在该行中找到的字符进行比较。一旦其中一列不匹配,它可能会中止一行,但它仍然很慢而且很讨厌。
你知道应该怎么做吗?它应该进行规范化。某人应该听说过诸如 “第三范式”之类的东西。但当时实际上并没有,所以它就这样持续地运行,不断地消耗内存、CPU 和磁盘空间,而且速度非常慢。
这种情况持续了一段时间——也许是几个月?甚至更长时间。我必须翻查日志,看看系统在这东西的重压下呻吟了多久。
不过,事情终于发生了变化。整个系统进行了重新设计,为每组值提供一个表格。现在有一个 IP 地址表、一个 HELO 字符串表、一个 FROM 地址表和一个 TO 地址表。每个表都非常简单,其架构如下:
id | value
每个表都设置“id”是主键,“value”是唯一的,所以它有一个索引。这意味着您可以要求数据库执行一些涉及查找给定值的 id 的操作,并且它会高效地为您找到它,而无需阅读整张表。
然后大表被更改为对每一行使用这些在小表中的id值,其中任何给定条目都是来自其他表(ip、helo、from、to)的id之一。这是一个外键关系。
无论如何,表中的前四行现在可能看起来像这样:
1 | 10 | 11 | 15 | 2 | ...
2 | 84 | 99 | 44 | 3 | ...
3 | 91 | 71 | 16 | 4 | ...
4 | 89 | 99 | 44 | 2 | ...
然后,使用 (ip, helo, from, to) 作为输入设置一个多列唯一约束,因此也就是添加了一个索引。现在,您可以要求数据库查找 (10, 11, 15, 2) 或其他任何内容,并且由于该索引,它会相对有效地执行操作。
后面,我们推出了一个可以快速有效地处理这些数据的系统,并达到其预期目的,即延缓或阻止大多数开放代理滥用的行为。
到此,您认为那个在库表字段中放入大量原始字符串并且对外键关系一无所知的无知程序员会怎么做?
Well,她刚刚为你写了这篇文章。没错, 我就是那个毫无头绪的新手,想出了一个完全荒谬的对 SQL 数据库的滥用,该数据库运行缓慢、臃肿,而且任何有经验的人一眼就能看出它显然是错误的。
我的观点是:每个人都会经历这个过程,尤其是在没有指导或参考的环境中时。考虑到我们行业倾向于辞退 35 岁以上的“老年人”,到时候我们会有一大群人四处游荡,然后尝试任何其他可能的职业,想想应该会挺让人惊讶的吧?
这是一个很严重的问题,我们都对此负有部分责任。我现在正试图通过创作化解部分“危机”。你呢?
{测试窝原创译文,译者:lukeaxu}