【问题标题】:Mysql table with composite index but not primary key具有复合索引但没有主键的 Mysql 表
【发布时间】:2019-07-27 14:11:40
【问题描述】:

我需要一个表来存储一些评分,在这个表中我有一个复合索引(user_id,post_id)和其他列来识别不同的评分系统。

user_id - bigint
post_id - bigint
type - varchar
...

Composite Index (user_id, post_id)

在这个表中,我没有主键,因为主键需要是唯一的,而 INDEX 不需要是唯一的,在我的情况下,唯一性是一个问题。

例如我可以有

INSERT INTO tbl_rate
    (user_id,post_id,type)
VALUES
    (24,1234,'like'),
    (24,1234,'love'),
    (24,1234,'other');

PRIMARY KEY 的缺失可能导致性能问题?我的表结构好还是需要改?

谢谢

【问题讨论】:

  • 三个字段的组合就是你的PK
  • 有趣...谢谢
  • 如果不知道主要查询是什么,就无法真正判断索引。

标签: mysql sql


【解决方案1】:

几点:

听起来您只是在使用表的当前唯一性并将其作为主键。这样可行。由于局部性,自然键在查询方面具有一些优势。 (每个用户的数据存储在同一区域中)。并且因为该表是按该键聚集的,如果您按主列中的列进行搜索,则无需查找数据。

  1. 但是,使用像您选择的自然主键也对性能不利。

  2. 使用非常大的主键会使 innodb 中的所有其他索引都非常大,因为主键包含在每个索引值中。

  3. 使用自然主键不如 INSERT 的代理键快,因为除了更大之外,它不能每次都插入到表的末尾。它必须插入该用户的部分并发布等。

  4. 此外,如果您按时间搜索,您很可能会使用自然键在整个表格中搜索,除非时间是您的第一列。代理键在时间上往往是本地的,并且通常适合某些查询。

  5. 使用像您这样的自然键作为主键也很烦人。如果您想引用特定投票怎么办?你需要几个字段。此外,与大量 ORM 一起使用有点困难。

这是答案

我会创建您自己的代理键并将其用作主键,而不是依赖于 innodb 的内部主键,因为您将能够使用它进行更新和查找。

ALTER TABLE tbl_rate 
ADD id INT UNSIGNED NOT NULL AUTO_INCREMENT, 
ADD PRIMARY KEY(id);

但是,如果您确实创建了代理主键,我也会将您的键设为唯一键。相同的成本,但它强制执行正确性。

ALTER TABLE tbl_rate 
ADD UNIQUE ( user_id, post_id, type );

【讨论】:

  • “但是,如果你确实创建了一个代理主键,我也会让你的键成为唯一的。成本相同,但它强制正确性。” -> ALTER TABLE tbl_rate ADD KEY ( user_id, post_id, type );不会创建唯一键,它只是一个索引..
  • @GidonWise - 项目 2 仅适用于 2 个或更多二级索引。
【解决方案2】:

PRIMARY KEY 的缺失可能导致性能问题?

在 InnoDB 中是肯定的,因为 InnoDB 将使用算法来创建它自己的“ROWID”, 定义在dict0boot.ic

Returns a new row id.
@return the new id */
UNIV_INLINE
row_id_t
dict_sys_get_new_row_id(void)
/*=========================*/
{
    row_id_t    id;

    mutex_enter(&(dict_sys->mutex)); 

    id = dict_sys->row_id;

    if (0 == (id % DICT_HDR_ROW_ID_WRITE_MARGIN)) {

        dict_hdr_flush_row_id();
    }

    dict_sys->row_id++;

    mutex_exit(&(dict_sys->mutex));

    return(id);
}

该代码中的主要问题是mutex_enter(&(dict_sys->mutex));,如果一个线程已经在运行此代码,它会阻止其他线程访问。 这意味着它将像 MyISAM 一样锁定表。

% 可能需要几纳秒。相比起来这微不足道 其他一切。无论如何#define DICT_HDR_ROW_ID_WRITE_MARGIN 256

确实是的,Rick James,与上面提到的相比,这确实微不足道。 C/C++ 编译器将对它进行更多的微优化,以通过使 CPU 指令更轻来获得更高的性能。
上面仍然提到了主要的性能问题..

此外,模运算符 (%) 是 CPU 密集型指令。
但是如果 DICT_HDR_ROW_ID_WRITE_MARGIN 是 2 的幂,则取决于 C/C++ 编译器(和/或配置选项)是否可以优化。
就像(0 == (id & (DICT_HDR_ROW_ID_WRITE_MARGIN - 1))) 一样,因为位掩码要快得多,我相信DICT_HDR_ROW_ID_WRITE_MARGIN 确实有一个数字是2 的幂

【讨论】:

  • % 可能需要几纳秒。与其他一切相比,这微不足道。无论如何#define DICT_HDR_ROW_ID_WRITE_MARGIN 256
  • "% 可能需要几纳秒。与其他一切相比,这微不足道" @RickJames True 或多或少 C/C++ 编译器优化会优化它(很多)更好的是,互斥锁上的线程锁定是性能问题的主要问题。感谢您确认DICT_HDR_ROW_ID_WRITE_MARGIN 有一个关闭电源的数字2 我确实记得当时正确.. 我更新了这个问题确实更清楚了与其他相比,它确实是一个微优化..
  • 据我了解代码(感谢 JCole 在某处的解释),所有没有 PK 的表共享一个由该子例程维护的 6 字节数字。一次分配 256 个值,可能dict_hdr_flush_row_id() 是一个代价高昂的函数。任何剩余值都会在关机时丢失。 2^48 足够大,以至于“没有人”会用完 id。
  • 但底线是......你应该提供一个明确的PK。
  • 好吧,我总是印象深刻,或者以某种方式错误地记得这会在表格的基础上生成。当我很久以前(大约在 InnoDB 来到 MySQL 的时候)阅读源代码时。我猜我错了,然后当我手头有更多时间时可能会自己检查一下,@RickJames
猜你喜欢
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
相关资源
最近更新 更多