技术 2021.11.26 58 阅读

少了一个数据库索引,让我们多花了5000美元

作者讲了一个亲身经历的案例,SQL 语句少建了一个索引,而数据库服务商按照读取的行数收费,导致费用暴增。

· · ·

 

 为什么会发生这样的事

在Superwall 我们在构建一个SDK能够在对的时间给对的人提供更优质的报价从而帮助app开发者提高收入。

 

提供这项服务,我们需要持续追踪我们所有客户的用户,也就是最终用户。为了简单起见,假设我们与5家公司合作,每家工资下载量约为5k次,每日总共25k次,或者一个月750k用户。为了持续追踪这些用户,我们把他们存在关系数据库的“users”表里。我之所以说关系型数据库因为我们用的事PlanetScale提供的serverless数据库产品。PlanetScale给了我们非常大的折扣,所以我们最终选择了他们的服务,

 

我们设计的表结构

 

为了持续追踪这些用户,我们建立了两张表,他们看起来像ApplicationUser and ApplicationUserAlias。ApplicationUser和ApplicationUserAlias是一对多的关系。这两张表是必须的因为我们在客户告诉我们用户之前会给用户提供一个随机id做事件追踪。所以可能我知道的是$SuperwallAlias:..., 和custom_id190390930.

 

CREATE TABLE `ApplicationUser` (
  `id` int NOT NULL AUTO_INCREMENT,
  PRIMARY KEY (`id`),
 ) ENGINE=InnoDB;
CREATE TABLE `ApplicationUserAlias` (
  `id` int NOT NULL AUTO_INCREMENT,
  -- Some identifier by which we know the user, a lot of apps use uuids
  `vendorId` varchar(191) CHARACTER SET utf8mb4 COLLATE utf8mb4_unicode_ci NOT NULL,
  -- The "foreign key" back to ApplicationUser 
  `applicationUserId` int NOT NULL,
  PRIMARY KEY (`id`),
 ) ENGINE=InnoDB;

没有外键

PlantScale,或者更具体说Vitess奇怪的缺乏对外间的支持。“在线数据迁移"根本不兼容外键,这是一种在不锁定任何内容的情况下更改数据库结构的方法。没什么大不了的,有很多解决办法。

 

可是,没有外键的后果之一就是无法增加自动创建索引。“mysql要求对外键进行索引;如果你创建的表有外键约束但是在给定列上没有索引,会自动创建。”在不知情的情况下,我一直依靠于Mysql基于外键自动创建的索引来帮助我查询的更快。根据父母查找孩子情况很常见。我们的情况是所有用户查找别名。

 

为了查找用户所有别名,我们运行这个查询语句。我们会简单的查找applicationUserId,在外键自动创建的索引下这个查询会非常的“廉价”。如果没有了索引就变成了全表扫描。

 

SELECT id, vendorId FROM ApplicationUserAlias WHERE applicationUserId = x LIMIT 100;

 

读取的行数

对于大多数数据提供者来说,这个错误指挥导致读取变慢。结果,在PlanesScales里使用的时候我们并没有注意。。。直到我们收到了账单。

 

PlanetScale价格基于“读取的行数”,我脑子里将它翻译为“返回的行数”。在前面的示例中,返回最多100行ApplicationUserAlias,读取每1000万行1.5美元,我们可以轻松的承担返回100行的费用,或者我是这么认为的。

 

仔细查看定价页面,它的定义是“在你的PlanetScale数据库里查询期间检索或检查行,或任何类似变动“。动词是“检查”,所以我们的简单查询只是为了查找100个别名,但实际上则是检查整个用户表,在我们服务的第一个月,该表已经超过100万行数据。

每个请求造成的查询会花费我们0.15刀

 

回到我们的数据,我们每个小时会给节点发送约280个请求,与之匹配的是在PlanetScale里产生每天约1k刀的账单。

 

解决

解决的办法非常简单,我们只需要用mysql为我们生成一个索引。

CREATE INDEX `ApplicationUserAlias.applicationUserId_index`
  ON `ApplicationUserAlias`(`applicationUserId`);

 

这句话是根据账单秀了一下。

 

为了解决这个问题,我还用了mysql explain分析,单这事另外一篇文章。

 

摆脱困境,展望未来

使劲舔舔PlanetScale爹

 

https://briananglin.me/posts/spending-5k-to-learn-how-database-indexes-work/