为什么添加二级索引没有提高选择性能?

har*_*ash 4 mysql index optimization

我在 MySQL 中有一个名为 的表,messages如下所示:

id (primary key)   | description    |  created_at(timestamp) 
Run Code Online (Sandbox Code Playgroud)

该表应该保存我的应用程序用户之间的聊天消息。因此,对它的写操作次数会很高。

该应用程序有一个 API,可返回两个时间戳之间的所有消息。这个查询也很常见,但少于写操作的次数。

我用 100 个并发连接和该表中的大约 15000 行运行 mysqlslap,总时间约为 8.6 秒。

然后我在 上添加了一个二级索引created_at,希望在两次搜索之间在更短的时间内获得结果,但是对于相同的输入,我增加了 0.3 秒。

为什么我没有看到显着的性能提升?

编辑:

这是我的桌子的样子:

DROP TABLE IF EXISTS `mssg`;

CREATE TABLE `mssg` (
  `id` INTEGER NULL AUTO_INCREMENT DEFAULT NULL,
  `body` MEDIUMTEXT NULL DEFAULT NULL,
  `length` VARCHAR NULL DEFAULT NULL,
  `created_at` TIMESTAMP NULL DEFAULT NULL,
  PRIMARY KEY (`id`)
);
Run Code Online (Sandbox Code Playgroud)

这就是我添加索引的方式:

ALTER TABLE testalter_tbl ADD INDEX (created_at);
Run Code Online (Sandbox Code Playgroud)

通常,select 语句会返回大约 150 到 350 条消息

ype*_*eᵀᴹ 6

我将跳过我对CREATE TABLE从错误到侮辱性分类的声明的评论,例如声明所有列NULL(一行的(NULL, NULL, NULL, NULL)含义是什么?)并专注于提出的问题。

8.6 秒和 8.9 秒之间的差异可以忽略不计。据我们所知,这两个测试的运行时间几乎相同。如果您想正确测试,您应该运行更长的测试并使用更大的表(尝试使用 15K、150K、1500K、15M 行),并查看在具有或不具有特定索引之间效率是否发生变化或保持不变。

您还应该检查为 2 种情况下的查询创建的执行计划(在不同的表大小下),看看幕后发生了什么,使用了哪些索引,如果有的话,等等。

我的预测是,随着表变大,索引将变得更有用,因为查询将需要索引查找以查找所需时间范围内的(行 ID),然后从集群 PK 索引中进行一些额外查找(我假设您使用 InnoDB。)

如果没有索引,则每次都必须进行全表扫描。表格越大,您会看到 2 个测试用例(带和不带索引)之间的差异越大。


如果您的绝大多数查询类似于以下内容,则从较小的时间范围内获取行:

SELECT <columns_wanted>
FROM mssg
WHERE created_at >= @start_timestamp
  AND created_at < @end_timestamp ;
Run Code Online (Sandbox Code Playgroud)

那么我建议(假设您使用 InnoDB)另一种设计:制作(created_at)表的集群键。由于您可能有 2 行或更多行具有完全相同的时间戳,因此您可以将主键设置为 以(create_at, id)实现此效果(因为 InnoDB 仅允许唯一键用于集群)。您还需要一个额外的指数(id)AUTO_INCREMENT就可以了。这可以声明UNIQUE或不声明,这是您的选择:

CREATE TABLE mssg (
  mssg_id INTEGER NOT NULL AUTO_INCREMENT,
  body MEDIUMTEXT NULL,
  length VARCHAR(20) NULL,
  created_at TIMESTAMP NOT NULL DEFAULT CURRENT_TIMESTAMP,
  PRIMARY KEY (created_at, mssg_id),
  -- UNIQUE
    INDEX mssg_id_ix (mssg_id)             -- this is needed for the AutoIncrement
);
Run Code Online (Sandbox Code Playgroud)

这样,您的查询需要的所有行都将存储在集群 (PK) 索引的连续页面中,并且查询的效率 - 只要它们要求的时间范围很小 - 就不会受到桌子。


进一步评论,部分与问题相关;

  • 为什么所有列都声明为NULL?我建议您将它们全部更改为NOT NULL除了那些有特定原因允许空值的列。
  • 为什么mediumtext要留言?你要存储这么大的消息吗?如果您可以减小尺寸,则性能可能会更好,例如VARCHAR(250).
  • 为什么length声明为 asVARCHAR而不是INTEGER
  • 此外,如果要存储文本的长度,则根本不需要它。您可以随时使用适当的文本功能获取长度。
  • 如果需要,您可以声明该TIMESTAMP列以获取默认值CURRENT_TIMESTAMP(如果应用程序尚未提供该列。)

  • 它在答案中。InnoDB 不允许您为聚集索引选择任何键。PK 用作聚类索引。由于 PK 必须是唯一的,如果我使用 `(created_at)`,你可能有 2 行具有完全相同的时间戳,而第二行会因重复键错误而被拒绝。 (2认同)