【问题标题】:MySQL: Joins vs. Bitwise operator, and performance thereofMySQL:联接与位运算符及其性能
【发布时间】:2023-03-10 19:49:01
【问题描述】:

关于这个主题有很多问题,但我的问题更具体的是性能问题。

关于一个对象,我想跟踪多个“属性”,每个“属性”都有多个离散“值”(每个属性都有 3 到 16 个有效“值”。)例如,考虑跟踪军事人员.属性/值可能是(不是真实的,我完全编造了这些):

属性:{values}
languages_spoken: {english, spanish, russian, chinese, …. }
certificates: {infantry, airborne, pilot, tank_driver…..}
approved_equipment: {m4, rocket_launcher, shovel, super_secret_radio_thingy….}
approved_operations: {reconnaissance, logistics, invasion, cooking, ….}
awards_won: {medal_honor, purple_heart, ….}

……等等。

这样做的一个方法 - 我想要这样做的方式 - 是有一个人员表和一个属性表:

personnel table => [id, name, rank, address…..]
personnel_attributes table => [personnel_id, attribute_id, value_id]

连同相关的属性和值表。

因此,如果 pesonnel_id=31415 被批准用于后勤,则 person_attributes 表中将有以下条目:

personnel_id | attribute_id | value_id
31415 | 3 | 2

其中 3 = attribute_id 表示“approved_operations”,2 = value_id 表示“logistics”(抱歉,格式空间未对齐。)

然后搜索所有会说英语或西班牙语、步兵或空降兵、并且可以操作铲子或 super_secret_radio_thingy 的人员将类似于:

SELECT t1.personnel_id FROM personnel_attributes t1, personnel_attributes t2, personnel_attributes t3
WHERE ((t1.attribute_id = 1 and t1.value_id = 1) OR (t1.attribute_id = 1 and t1.value_id = 2))
AND ((t2.attribute_id = 2 and t1.value_id = 1) OR (t2.attribute_id = 2 and t1.value_id = 2))
AND ((t3.attribute_id = 3 and t1.value_id = 3) OR (t3.attribute_id = 3 and t1.value_id = 4))
AND t2.personnel_id = t1.personnel_id
AND t3.personnel_id = t1.personnel_id;

假设这不是编写 SQL 查询的完全愚蠢的方法,问题是它非常慢(即使有看似相关的索引。)

所以我正在玩弄按位运算符,其中每个属性都是表中的一列,每个值都是一点。相同的搜索是:

SELECT personnel_id FROM personnel_attributes
WHERE language & b'00000011'
AND certificates & b'00000011'
AND approved_operations & b'00001100';

我知道这会进行全表扫描,但在我对 350,000 个样本人员和每个 16 个属性的实验中,第一种方法需要 20 秒,而按位方法需要 38 毫秒!

我在这里做错了吗?这些是我应该期待的性能结果吗?

谢谢!

【问题讨论】:

  • 您可能只是看到查询的结果在连续运行时被缓存在内存中。运行EXPLAIN 看看它是否真的不同

标签: mysql sql performance join bit-manipulation


【解决方案1】:

使用按位运算需要计算所有行。我相信您的问题可以通过更改您的原始 SELECT 语句以及您加入表格的方式来解决:

为了使其更易于阅读,我将属性值更改为单词而不是整数,这样在阅读我的示例时就不那么容易混淆了,但显然你可以将它们保留为整数,它的概念仍然有效:

CREATE TABLE PERSONNEL (
    ID INT,
    NAME VARCHAR(20)
)

CREATE TABLE PERSONNEL_ATTRIBUTES (
    PERSONNEL_ID INT,
    ATTRIB_ID INT,
    ATTRIB_VALUE VARCHAR(20)
)

INSERT INTO PERSONNEL VALUES (1, 'JIM SMITH')
INSERT INTO PERSONNEL VALUES (2, 'JANE DOE')
INSERT INTO PERSONNEL_ATTRIBUTES VALUES (1, 1, 'English')
INSERT INTO PERSONNEL_ATTRIBUTES VALUES (1, 1, 'Spanish')
INSERT INTO PERSONNEL_ATTRIBUTES VALUES (1, 1, 'Russian')
INSERT INTO PERSONNEL_ATTRIBUTES VALUES (1, 3, 'Logistics')
INSERT INTO PERSONNEL_ATTRIBUTES VALUES (1, 3, 'Infantry')
INSERT INTO PERSONNEL_ATTRIBUTES VALUES (2, 1, 'English')
INSERT INTO PERSONNEL_ATTRIBUTES VALUES (2, 3, 'Infantry')

SELECT P.ID, P.NAME, PA1.ATTRIB_VALUE AS DESIRED_LANGUAGE, PA2.ATTRIB_VALUE AS APPROVED_OPERATION
FROM PERSONNEL P
JOIN PERSONNEL_ATTRIBUTES PA1 ON P.ID = PA1.PERSONNEL_ID AND PA1.ATTRIB_ID = 1
JOIN PERSONNEL_ATTRIBUTES PA2 ON P.ID = PA2.PERSONNEL_ID AND PA2.ATTRIB_ID = 3
WHERE PA1.ATTRIB_VALUE = 'Spanish' AND (PA2.ATTRIB_VALUE = 'Infantry' OR PA2.ATTRIB_VALUE = 'Airborne')

【讨论】:

  • 我尝试过(或改编),但实际上花了更长的时间。像 10 倍以上(213 秒对 16 秒)除非我真的在这里完全做某事,否则我认为这种连接方法不会起作用。在这种情况下,尽管有其局限性,但位运算符似乎要快得多。
  • 确保在关键字段上有索引。如果你这样做了,那么它不应该花那么长时间。
【解决方案2】:

使用 django-bitfield 或单独的标志表有同样的问题。

受您的实验启发,我使用了一个 3.5m 的记录表 (innodb) 并创建了 count() 并检索了两个变体的查询。结果令人惊讶:大约 5 秒对 40 秒位域获胜。

【讨论】:

    猜你喜欢
    • 2016-04-12
    • 1970-01-01
    • 2017-03-14
    • 2012-02-23
    • 1970-01-01
    • 1970-01-01
    • 2010-10-30
    • 1970-01-01
    相关资源
    最近更新 更多