具有许多重复值的 MySQL InnoDB B+Tree 索引的性能

Rei*_*eid 5 mysql innodb index btree

我试图用我们的数据库服务器诊断看似随机的性能问题。下面是一个简化的场景,希望足够通用,可以作为任何寻找相同答案的人的有用的未来参考。

假设我有一个(MySQL 5.6 w/ InnoDB)表

CREATE TABLE Example (
    id INT NOT NULL AUTO_INCREMENT,
    secondary_id INT DEFAULT NULL,
    some_data TEXT NOT NULL,
    PRIMARY KEY (id),
    KEY (secondary_id)
) ENGINE=InnoDB;
Run Code Online (Sandbox Code Playgroud)

大约有 1500 万行。但是,该secondary_idNULL几乎适用于所有行,因此索引的secondary_id基数非常非常低(在我们的示例中约为 30k)。在我们的例子中,当我们遇到我正在调查的性能问题时,服务器的进程列表显示了许多(100+)个表单查询:

UPDATE Example SET secondary_id = NULL, some_data = '...' WHERE id = 123;
Run Code Online (Sandbox Code Playgroud)

需要大约 90+ 秒才能完成,在此期间它们处于“更新”状态。(这些查询将在单独的事务中运行。)

我特别想知道从非空secondary_id到空的转换secondary_id是否会导致上述UPDATE. 也就是说,在这种情况下更新索引是否可能需要大量时间,因为有这么多行(约 1500 万)对该列 ( NULL)具有相同的值?

我想这个问题源于我不理解 B+Tree 索引如何为具有重复索引值的行存储行指针。我猜这个节点会有一个插入时间非常快的链表(或类似的东西),所以我猜我的问题的答案是“否”。但我想向专家们,即你们所有人,确认这一点。

我试图在这里做大量的研究,但我两手空空。可能最全面的帖子是this one,它解释了一些处理重复键的不同技术,但我特别在寻找InnoDB/MySQL的方法。

ype*_*eᵀᴹ 4

单曲90秒UPDATE听起来也太多了。可能涉及一些阻塞,应该进行调查。

除此之外,拥有 98% 相同 ( NULL) 值的列听起来也不好。您应该考虑将该列放在一个单独的表中(该表只有 30K 行)。这会让你的INSERT/DELETE/UPDATE程序变得有点复杂,但你可能会从较小的索引中获益。建议设计:

CREATE TABLE Example (
    id INT NOT NULL AUTO_INCREMENT,
    some_data TEXT NOT NULL,
    PRIMARY KEY (id)
) ENGINE = InnoDB ;

CREATE TABLE Example_secondary (
    id INT NOT NULL,
    secondary_id INT NOT NULL,
    PRIMARY KEY (id),
    INDEX (secondary_id),
    FOREIGN KEY (id)
      REFERENCES Example (id)
) ENGINE = InnoDB ;
Run Code Online (Sandbox Code Playgroud)

然后你的UPDATE

UPDATE Example 
SET secondary_id = NULL, 
    some_data = '...' 
WHERE id = 123 ;
Run Code Online (Sandbox Code Playgroud)

会成为:

BEGIN ;
    UPDATE Example 
    SET some_data = '...' 
    WHERE id = 123 ;

    DELETE FROM Example_secondary 
    WHERE id = 123 ;
COMMIT ;
Run Code Online (Sandbox Code Playgroud)