Glean 拾遗
日刊 /2026-09-18 / 数据库技能不是加分项:从 2006 年 MySQL 分面搜索说起

数据库技能不是加分项:从 2006 年 MySQL 分面搜索说起

原文 renegadeotter.com 收录 2026-09-18 06:01 阅读 14 min
AI 解读

2006 年,作者在纽约杂志数字团队用 MySQL 4 加 Perl 给时装周做分面搜索:秀场图按「2006」「bag」「red」等标签分类,用户可下钻筛选,每个属性还要带精确计数。当时 Solr facets 尚不存在,Autonomy 的计数不对,Endeca 刚出隐身期,三个人的团队只能自己啃 SQL,靠 EXPLAIN、GROUP BY 和反复调 MySQL 服务器参数把延迟压下去。 二十年后作者看到的却是相反趋势:工程师给普通规模的问题上 DynamoDB 这类「行星级」数据库,却对自己正在用的关系库缺乏基本掌握。文中复述一次电商事故——商品列表页要 10 秒以上,且无流量时一样慢,同一页面同时踩了缺索引、ORM 循环逐条查询(单页 200–500 条 SQL)、SELECT 全部列三个坑。作者的主张是:现代 RDBMS 在被证明有罪之前都是清白的,举证责任几乎全在工程师身上;文末给出排障顺序(慢查询日志 → 高频查询 → EXPLAIN → 只取必要列 → 必要时写原生 SQL)与三类反模式。适合后端与数据工程师。

原文 14 分钟
原文 renegadeotter.com ↗
§ 1

It’s 2006, and the New York Magazine digital team set out to create a new search experience for its Fashion Week portal. It was one of those projects where technical feasibility was not even discussed with the tech team - a common occurrence back then. Agile was still new, let alone in publishing. It was just a vision, a real friggin’ moonshot, and 10 to 12 weeks to develop the wireframed version of the product. There would be almost no time left for proper QA. Fashion Week does not start slowly but rather goes from zero to sixty in a blink.

那是 2006 年,纽约杂志数字团队着手为其时装周门户打造全新的搜索体验。这类项目在当时很常见:技术可行性压根没跟技术团队讨论过。敏捷开发还很新鲜,更不用说在出版业了。这只是一个愿景,一次真正的豪赌,而且只有 10 到 12 周时间来开发产品的线框版本。几乎不会留出时间做像样的 QA。时装周不会慢慢开始,而是眨眼间就从零飙到六十。

§ 2

The vision? Thousands of near-real-time fashion show images, each one with its sub-items categorized: “2006”, “bag”, “red”, “leather”, and so on. A user will land on the search page and have the ability to “drill down” and narrow the results based on those properties. To make things much harder, all of these properties would come with exact counts.

愿景是什么?数千张近实时时装秀图片,每张图片的子项都分门别类:“2006”、“包”、“红色”、“皮革”等等。用户进入搜索页面后,能够根据这些属性“下钻”并缩小结果范围。更麻烦的是,所有这些属性都需要附带精确的计数。

§ 3

The workflow was going to be intense. Photographers will courier their digital cartridges from downtown NYC to our offices on Madison Avenue, where the images will be processed, tagged by interns, and then indexed every hour by our Perl script, reading the tags from the embedded EXIF information. Failure to build the search product on our side would have collapsed the entire ecosystem already in place, primed and ready to rumble.

工作流将会非常紧张。摄影师会把数码存储卡从纽约市中心快递到我们麦迪逊大道的办公室,在那里图片被处理、由实习生打标签,然后每小时由我们的 Perl 脚本读取嵌入的 EXIF 信息中的标签并建立索引。如果我们这边没能建成搜索产品,整个已经就位、蓄势待发的生态系统就会崩溃。

§ 4

“Oh! Just use the facets in Solr, dude”. Yeah, not so fast - dude. In 2006 that kind of technology didn’t even exist yet. I sat through multiple enterprise search engine demos, and none of the products (which cost a LOT of money) could do a deep faceted search. We already had an Autonomy license and my first try proved that… it just couldn’t do it. It was supposed to be able to, but the counts were all wrong. Endeca (now owned by Oracle), came out of stealth when the design part of the project was already underway. Too new, too raw, too risky. The idea was just a little too ambitious for its time, especially for a tiny team in a non-tech company.

“哦!直接用 Solr 的分面功能啊,老兄。” 是啊,别急——老兄。2006 年那种技术根本还不存在。我参加了多场企业搜索引擎的演示,没有一个产品(而且都贵得要命)能做出深度分面搜索。我们已经有 Autonomy 的许可证,我第一次尝试就证明……它根本做不到。它本应能做到,但计数全是错的。Endeca(现已被 Oracle 收购)在项目设计阶段已经开始后才走出隐身模式。太新、太生、太冒险。这个想法对于当时来说有点太野心勃勃了,尤其是对于一个非科技公司的小团队。

§ 5

So here we were, a team of three, myself and two consultants, writing Perl for the indexing script, query-parsing logic, and modeling the data - in MySQL 4. It was one of those projects where one single insurmountable technical risk would have sunk the whole thing. I will cut the story short and spare you the excitement. We did it, and then we went out to celebrate at a karaoke bar (where I got my very first work-stress-related severe hangover) 🤮

于是我们就这样:一个三人团队,我和两位顾问,用 Perl 编写索引脚本、查询解析逻辑,并在 MySQL 4 中为数据建模。这类项目只要有一个无法逾越的技术风险,整个项目就会沉没。长话短说,我就不细说那些激动人心的过程了。我们做成了,然后去卡拉 OK 酒吧庆祝(在那里我经历了人生第一次因工作压力导致的严重宿醉)🤮

§ 6

For someone who was in charge of the SQL model and queries, it was days and days of tuning those, timing every query and studying the EXPLAIN output to see what else I could do to squeeze another 50ms out of the database. In the end, it was a combination of trial and error, digging deep into MySQL server settings, and crafting GROUP BY queries that would make you nauseous. The MySQL query analyzer was fidgety back then, and sometimes re-arranging the fields in the SELECT clause could change a query’s performance. Imagine if SELECT field1, field2 FROM my_table was faster than SELECT field2, field1 FROM my_table. Why would it do that? I have no idea to this day, and I don’t even want to know.

Unfortunately, I lost examples of this work, but the Way Back Machine has proof of our final product.

对于负责 SQL 模型和查询的人来说,那是日复一日的调优,给每个查询计时,研究 EXPLAIN 输出,看看还能做些什么从数据库里再榨出 50 毫秒。最终,这是反复试验、深入挖掘 MySQL 服务器设置,以及编写让人反胃的 GROUP BY 查询的组合结果。那时的 MySQL 查询分析器很不稳定,有时重新排列 SELECT 子句中的字段就能改变查询性能。想象一下,SELECT field1, field2 FROM my_table 比 SELECT field2, field1 FROM my_table 更快。为什么会这样?我至今也不明白,而且我根本不想知道。

不幸的是,我丢失了这项工作的示例,但 Wayback Machine 上有我们最终产品的证据。

§ 7

The point here is - if you really know your database, you can do pretty crazy things with it, and with the modern generation of storage technologies and beefier hardware, you don’t even need to push the limits - it should easily handle what I refer to as “common-scale”.

这里的重点是——如果你真正了解你的数据库,你就能用它做出相当疯狂的事情,而有了现代存储技术和更强大的硬件,你甚至不需要挑战极限——它应该能轻松处理我所说的“common-scale”(常见规模)。

§ 8

The fading art of SQL

In the past few years I have been noticing an unsettling trend - software engineers are eager to use exotic “planet-scale” databases for pretty rudimentary problems, while at the same time not having a good grasp of the very powerful relational database engine they are likely already using, let alone understanding the technology’s more advanced and useful capabilities. The SQL layer is buried so deep beneath libraries and too clever by a half ORMs that it all just becomes high-level code.

Why is it slow?

No idea - let's add Cassandra to it!

SQL 技艺的衰落

过去几年,我注意到一个令人不安的趋势——软件工程师急于用奇异的“行星级”数据库解决相当初级的问题,同时却对他们很可能已经在使用的强大关系数据库引擎缺乏良好的掌握,更不用说理解这项技术更高级、更有用的能力了。SQL 层被埋藏在库和过于聪明的 ORM 之下太深,一切都变成了高级代码。

为什么慢?

不知道——让我们加个 Cassandra 吧!

§ 9

Modern hardware certainly allows us to go way up from the CPU into the higher abstraction layers, while it wasn’t that uncommon in the past to convert certain functions to assembly code in order to squeeze every bit of performance out of the processor. Now compute and storage is cheaper - it’s true - but abusing this abundance has trained us laziness and complacency. Suddenly, that Cloud bill is a wee too high, and heavens knows how much energy the world is burning by just running billions of auto-generated “Squeel” queries every second against mammoth database instances.

现代硬件当然允许我们远离 CPU,走向更高的抽象层,而在过去,为了从处理器中榨出每一点性能,将某些函数转换成汇编代码并不罕见。现在计算和存储更便宜了——这是事实——但滥用这种富裕已经训练出了我们的懒惰和自满。突然之间,云账单有点太高了,天知道全世界每秒对着巨型数据库实例运行数十亿条自动生成的“Squeel”查询烧掉了多少能源。

§ 10

The morning of my first job interview in 2004, I was on a subway train memorizing the nine levels of database normalization. Or is it five levels? I don’t remember, and It doesn’t even matter - no one will ever ask you this now in a software engineer interview.

2004 年我第一次求职面试的那个早晨,我在地铁上背诵数据库范式的九个级别。还是五个级别?我记不清了,而且这根本不重要——现在软件工程师面试中永远不会有人问你这个问题。

§ 11

Just skimming through the table of contents of your database of choice, say the now freshly in vogue Postgres, you will find an absolute treasure trove of features fit to handle everything but the most gruesome planet-scale computer science problems. Petabyte-sized Postgres boxes, replicated, are effortlessly running now as you are reading this.

只要浏览一下你所选数据库的目录,比如现在正流行的 Postgres,你就会发现一个绝对的功能宝库,足以处理除了最可怕的行星级计算机科学问题之外的一切。PB 级大小的 Postgres 实例,带有复制,此刻正在你阅读时毫不费力地运行着。

§ 12

The trick is to not expect your database or your ORM to read your mind. Speaking of…

ORMs are not magic

诀窍是不要指望你的数据库或 ORM 能读懂你的心思。说到这个……

ORM 不是魔法

§ 13

I was a new hire at an e-commerce outfit, and right off the bat I was thrown into fixing serious performance issues with the company’s product catalog pages. Just a straight-forward, paginated grid of product images. How hard could it be? Believe it or not - it be. The pages took over 10 seconds to load, sometimes longer, the database was struggling, and the solution was to “just cache it”. One last datapoint - this was not a high-traffic site. The pages were dead-slow even if there was no traffic at all. That’s a rotten sign that something is seriously off.

我曾是一家电商公司的新员工,一上来就被扔去修复公司产品目录页面的严重性能问题。只是一个直截了当的、分页的产品图片网格。能有多难?信不信由你——就是很难。页面加载超过 10 秒,有时更长,数据库不堪重负,解决方案是“缓存一下”。最后一个数据点——这不是一个高流量网站。即使完全没有流量,页面也慢得要死。这是一个糟糕的迹象,说明某个地方出了严重问题。

§ 14

After looking a bit closer, I realized that I hit the motherlode - all top three major database and coding mistakes in one.

❌ Mistake #1: There is no index

The column that was hit in every single mission-critical query had no index. None. After adding the much-needed index in production, you could practically hear MySQL exhaling in relief. Still, the performance was not quite there yet, so I had to dig deeper, now in the code.

再仔细一看,我意识到自己挖到了金矿——三大数据库和编码错误全在同一个项目里。

❌ 错误 #1:没有索引

每个关键任务查询都会命中的列根本没有索引。完全没有。在生产环境加上急需的索引后,你几乎能听到 MySQL 松了一口气。但性能还没有完全到位,所以我不得不深入挖掘,这次是在代码里。

§ 15

❌ Mistake #2: Assuming each ORM call is free

Activating the query logs locally and reloading a product listing page, I see… 200, 300, 500 queries fired off just to load one single page. What the shit? Turns out, this was the result of a classic ORM abuse of going through every record in a loop, to the effect of:

for product_id in product_ids: product = my_orm.products.get(id=product_id) products.append(product)

The high number of queries was also due the fact that some of this logic was nested. The obvious solution is to keep the number of queries in each request to a minimum, using ORM capabilities to join and combine the data into one single blob. This is what relational databases do - it’s in the name.

What is happening above is that each separate query needs to travel to the database, get parsed, transformed, analyzed, planned, executed, and then travel back to the caller. It is one of the most expensive operations you can do, and ORMs will happily do the worst possible thing for you in terms of performance. How does that ORM call translate to SQL? If it’s not what you think it should be, is it an ORM limitation or are you just not using the right library call? Is it a particular flavor of non-ANSI vendor SQL that your choice of ORM has a tough time with? Do you ultimately need to drop into raw SQL for this call but not the others? And so on.

❌ 错误 #2:以为每次 ORM 调用都是免费的

在本地激活查询日志并重新加载产品列表页面,我看到……仅仅加载一个页面就触发了 200、300、500 条查询。什么鬼?原来,这是经典的 ORM 滥用:在循环中遍历每条记录,效果就像:

for product_id in product_ids: product = my_orm.products.get(id=product_id) products.append(product)

查询数量高也是因为其中一些逻辑是嵌套的。显而易见的解决方案是让每个请求中的查询数量保持最少,利用 ORM 的能力将数据连接并合并成一个单一的数据块。这正是关系数据库该做的事——名字里就写着呢。

上面发生的情况是,每个独立的查询都需要传到数据库,经过解析、转换、分析、规划、执行,然后再传回调用方。这是你能做的最昂贵的操作之一,而 ORM 会乐于在性能方面为你做最糟糕的事情。那个 ORM 调用如何翻译成 SQL?如果它不是你想象的那样,是 ORM 的限制,还是你只是没有使用正确的库调用?是不是你的 ORM 难以处理某种非 ANSI 的厂商 SQL?你是否最终需要为这个调用(而不是其他调用)直接写原始 SQL?等等。

§ 16

❌ Mistake #3: Pulling in the world

To make matters worse, the amount of data here was relatively small, but there were dozens and dozens of columns. What do ORMs usually do by default in order to make your life “easier”? They send the whole thing, all the columns, clogging your network pipes with the data that you don’t even need. It is a form of toxic technical debt, where the speed of development will eventually start eating into performance.

I spent hours within the same project hacking the dark corners of the Dango admin, overriding default ORM queries to be less “eager”. This led to a much better office-facing experience.

❌ 错误 #3:拉取整个世界

更糟的是,这里的数据量相对较小,但却有几十个列。ORM 默认通常会做什么来让你的生活“更轻松”?它们把整个东西、所有列都发过来,用你根本不需要的数据堵塞网络管道。这是一种有毒的技术债,开发的快速最终会开始侵蚀性能。

我在同一个项目中花了数小时破解 Dango admin 的黑暗角落,覆盖默认的 ORM 查询,让它们不那么“急切”。这带来了好得多的面向办公室的体验。

§ 17

Performance IS a feature

Serious, mission-critical systems have been running on classic and boring relational databases for decades, serving thousands of requests per second. These systems have become more advanced, more capable, and more relevant. They are wonders of computer science, one can claim. You would think that an ancient database like Postgres (in development since 1982) is in some kind of legacy maintenance mode at this point, but the opposite is true. In fact, the work has been only accelerating, with the scale and features becoming pretty impressive. What took multiple queries just a few years ago now takes a single one.

Why is this significant? It has been known for a long time, as discovered by Amazon, that every additional 100ms of a user waiting for a page to load loses a business money. We also know now that from a user’s perspective, the maximum target response time for a web page is around 100 milliseconds:

A delay of less than 100 milliseconds feels instant to a user, but a delay between 100 and 300 milliseconds is perceptible. A delay between 300 and 1,000 milliseconds makes the user feel like a machine is working, but if the delay is above 1,000 milliseconds, your user will likely start to mentally context-switch.

The “just add more CPU and RAM if it’s slow” approach may have worked for a while, but many are finding out the hard way that this kind of laziness is not sustainable in a frugal business environment where costs matter.

性能即特性

严肃的关键任务系统几十年来一直运行在经典而无聊的关系数据库上,每秒处理数千个请求。这些系统已经变得更先进、更强大、更相关。可以说,它们是计算机科学的奇迹。你可能会以为像 Postgres(自 1982 年开发至今)这样的古老数据库现在处于某种遗留维护模式,但事实恰恰相反。事实上,工作只是在加速,规模和功能变得相当惊人。几年前需要多个查询的事情,现在只需一个。

为什么这很重要?很久以前,亚马逊就发现,用户等待页面加载每多 100 毫秒,企业就会损失金钱。我们现在也知道,从用户的角度来看,网页的最大目标响应时间大约是 100 毫秒:

延迟低于 100 毫秒对用户来说感觉是即时的,但 100 到 300 毫秒之间的延迟是可以感知的。300 到 1,000 毫秒之间的延迟让用户感觉机器在工作,但如果延迟超过 1,000 毫秒,你的用户很可能开始在心里切换上下文。

“如果慢就加更多 CPU 和 RAM”的方法也许管用了一阵子,但许多人正在痛苦地发现,这种懒惰在成本至关重要的节俭商业环境中是不可持续的。

§ 18

Database anti-patterns

Knowing what not to do is as important as knowing what to do. Some of the below mistakes are all too common:

❌ Anti-pattern #1. Using exotic databases for the wrong reasons

Technologies like DynamoDB are designed to handle scale at which Postgres and MySQL begin to fail. This is achieved by denormalizing, duplicating the data aggressively, where the database is not doing much real-time data manipulation or joining. Your data is now modeled after how it is queried, not after how it is related. Regular relational concepts disintegrate at this insane level of scale. Needless to say, if you are resorting to this kind of storage for “common-scale” problems, you are already solving problems you don’t have.

数据库反模式

知道不该做什么和知道该做什么一样重要。下面的一些错误实在太常见了:

❌ 反模式 #1:出于错误理由使用异域数据库

像 DynamoDB 这样的技术是为处理 Postgres 和 MySQL 开始失效的规模而设计的。这是通过反规范化、积极复制数据来实现的,数据库不做太多实时数据操作或连接。你的数据现在是根据查询方式来建模,而不是根据关联方式来建模。常规的关系概念在这种疯狂的规模下瓦解。不用说,如果你为“common-scale”问题诉诸这种存储,你已经在解决你根本没有的问题。

§ 19

❌ Anti-pattern #2. Caching things unnecessarily

Caching is a necessary evil - but it’s not always necessary. There is an entire class of bugs and on-call issues that stem from stale cached data. Read-only database replicas are a classic architecture pattern that is still very much not outdated, and it will buy you insane levels of performance before you have to worry about anything. It should not be a surprise that mature relational databases already have query caching in place - it just has to be tuned for your specific needs.

Cache invalidation is hard. It adds more complexity and states of uncertainty to your system. It makes debugging more difficult. I received more emails from content teams than I care for throughout my career that wondered “why is the data not there, I updated it 30 minutes ago?!”

Caching should not act as a bandaid for bad architecture and non-performant code.

❌ 反模式 #2:不必要地缓存

缓存是一种必要的恶——但并不总是必要。有一整类 bug 和 on-call 问题都源于过期的缓存数据。只读数据库副本是一种经典的架构模式,至今远未过时,它能在你开始担心任何事情之前为你带来疯狂级别的性能。成熟的关系数据库已经有了查询缓存,这不应该让人惊讶——只是需要根据你的具体需求进行调优。

缓存失效很难。它给系统增加了更多复杂性和不确定性状态。它让调试更加困难。在我的职业生涯中,我收到过比我希望的更多的来自内容团队的邮件,他们想知道“为什么数据不在那里,我 30 分钟前就更新了?!”

缓存不应充当糟糕架构和低性能代码的创可贴。

§ 20

❌ Anti-pattern #3. Storing everything and a kitchen sink

As much punishment as an industry-standard database can take, it’s probably not a good idea to not care at all about what’s going into it, treating it like a data landfill of sorts. Management, querying, backups, migrations - all becomes painful once the DB grows substantially. Even if that is of no concern as you are using a managed cloud DB - the costs should be. An RDBMS is a sophisticated piece of technology, and storing data in it is expensive.

❌ 反模式 #3:存储一切,连厨房水槽都不放过

尽管行业标准数据库能承受很多折磨,但完全不在乎往里放什么、把它当作某种数据垃圾填埋场,可能并不是个好主意。管理、查询、备份、迁移——一旦数据库大幅增长,一切都会变得痛苦。即使你使用的是托管云数据库,这不成问题——成本也应该成问题。RDBMS 是一种精密技术,在里面存储数据是昂贵的。

§ 21

Figure out common-scale first

It is fairly easy to make a beefy Postgres or a MySQL database grind to a halt if you expect it to do magic without any extra work. “It’s not web-scale, boss. Our 2 million records seem to be too much of a lift. We need DynamoDB, Kafka, and event sourcing!”

A relational database is not some antiquated technology that only us tech fossils choose to be experts in, a thing that can be waved off like an annoying insect. “Here we React and GraphQL all the things, old man”. In legal speak, a modern RDBMS is innocent until proven guilty, and the burden of proof should be extremely high - and almost entirely on you.

先搞清楚 common-scale

如果你指望一个肥壮的 Postgres 或 MySQL 数据库不做任何额外工作就能施展魔法,它很容易就会停下来。“老板,这不是 web-scale。我们 200 万条记录似乎太重了。我们需要 DynamoDB、Kafka 和事件溯源!”

关系数据库不是什么过时的技术,只有我们这些技术老古董才选择成为专家,也不是可以像烦人的虫子一样被挥手赶走的东西。“老头子,我们用 React 和 GraphQL 搞定一切。”用法律术语来说,现代 RDBMS 是无罪推定的,举证责任应该极高——而且几乎完全在你身上。

§ 22

Finally, if I have to figure out “why it’s slow”, my approximate runbook is:

Compile a list of unique queries, from logging, slow query log, etc.

Look at the most frequent queries first

Use EXPLAIN to check slow query plans for index usage

Select only the data that needs to travel across the wire

If an ORM is doing something silly without a workaround, pop the hood and get dirty with the raw SQL plumbing

最后,如果我必须弄清楚“为什么慢”,我大致的排查清单是:

从日志、慢查询日志等中整理出唯一查询列表。

先看最频繁的查询

用 EXPLAIN 检查慢查询计划中的索引使用情况

只选择需要跨网络传输的数据

如果 ORM 在做傻事且没有变通方法,就打开引擎盖,直接动手搞原始 SQL 管道

§ 23

Most importantly, study your database (and SQL). Learn it, love it, use it, abuse it. Spending a couple of days just leafing through that Postgres manual to see what it can do will probably make you a better engineer than spending more time on the next flavor-of-the-month JavaScript framework hotness. Again.

最重要的是,学习你的数据库(和 SQL)。了解它、爱上它、使用它、滥用它。花几天时间翻阅那本 Postgres 手册,看看它能做什么,很可能比花更多时间在下一个每月流行的 JavaScript 框架热点上更能让你成为更好的工程师。再说一遍。

§ 24

Latest

I am not your Cloud person

Further reading

Use the index, Luke - SQL Indexing and Tuning e-Book

Don’t do this - a Postgres WIKI

最新

我不是你的云专家

进一步阅读

Use the index, Luke - SQL 索引与调优电子书

Don’t do this - 一个 Postgres WIKI

打开原文 ↗