【问题标题】:MySQL: Using natural primary index or adding surrogate when tables are givenMySQL:在给定表时使用自然主索引或添加代理
【发布时间】:2015-06-08 14:08:41
【问题描述】:

我有 5 个文本字段要导入 MySQL/MariaDB 数据库。但是有两个问题:

(1) 文件非常大:0.5 GB 到 10 GB
(2) 所有相关键都有 40 个字符

第(1)点我必须接受它,我不能改变它。第2点是我关心的。网上有很多建议。例如,将枚举用于 varchar 或使用数字代理。将代理键添加到表中没有问题。但是必须将相同的代理键添加到其他表中。这就是我卡住的地方。

这里是关于文件/表格的具体信息:

  • invoice 表有 3 列和 20 行 Mio:

    • 具有不同值的invoice_id(主键)= 行数
    • 具有 4,000 个不同值的 praxis_id
    • 具有 4 个 Mio 不同值的患者 ID 所有列都是 CHAR(40) 并且固定长度为 40。
  • diagnose 表有 3 列和 25 个 Mio 行:

    • invoice_id CHAR(40) 1.4 Mio distinct id
    • diagnose_type
    • 诊断代码
  • 患者表有 5 列,5 行:

    • patient_id CHAR(40) 不唯一(4 Mio distinct pat_id)
    • praxis_id CHAR(40)
    • 出生年份、性别等

例如,我想将发票与诊断和患者一起加入。索引键是有意义的。一种方法是将 invoice.invoice_id 定义为主键,对于表 invoice 中的所有其他键,我将添加一个索引。与表诊断(invoice_id 与 INDEX)和患者(患者 ID 作为主键)相同。
问题在于将 invoice.invoice_id 定义为主键需要很长时间:

ALTER TABLE invoice_id ADD PRIMARY KEY(invoice_id);

一小时后,我终止了该进程。我认为性能问题是由表 invoice 中 invoice_id 的数据类型引起的。一个想法可能是在加载文本文件时添加一个自动递增代理键 invoice_id_surr。但是,如果我想加入表诊断,问题仍然存在,因为我必须加入表诊断的 invoice_id,它没有代理键 invoice_id_surr 作为外键。我可以在 diagnostic.invoice_id 上添加一个索引,但随后我失去了在表格发票上拥有代理键​​的优势。

我会对如何解决这个问题的策略感兴趣:几个已经存在的表可以连接在一起,但键是 CHAR(40) 并且没有索引。

感谢您的帮助。


更新 1:表格规范
- 键有 40 个字符 [0-9][A-Z]
- 这些是不会再改变的表(没有插入)

-- invoice_id is primary key (unique)
-- patient_id and praxis id for foreign key and not unique in this table
CREATE TABLE invoice (
  invoice_id             CHAR(40) DEFAULT NULL
, praxis_id              CHAR(40) DEFAULT NULL
, patient_id             CHAR(40) DEFAULT NULL
, PRIMARY KEY (invoice_id2)
) ENGINE = InnoDB
;  

LOAD DATA LOCAL INFILE 'C:/data/invoice.txt'
INTO TABLE invoice
FIELDS TERMINATED BY '\t'
LINES TERMINATED BY '\r\n'
IGNORE 1 LINES
;

-- invoice_id is not unique in this table
CREATE TABLE diagnose (
  invoice_id             CHAR(40)    DEFAULT NULL
, diagnose_katalog       VARCHAR(20) DEFAULT NULL
, diagnose_code          VARCHAR(20) DEFAULT NULL
) ENGINE = InnoDB
;
-- patient_id is not unique in this table since since patient may change praxis
CREATE TABLE patient (
  patient_id             CHAR(40)    DEFAULT NULL
, praxis_id              CHAR(40)    DEFAULT NULL
, sex                    CHAR(1)     DEFAULT NULL
, birth_year             SMALLINT UNSIGNED DEFAULT NULL
, zip_code               VARCHAR(20) DEFAULT NULL
) ENGINE = InnoDB
;

【问题讨论】:

    标签: mysql indexing primary-key surrogate-key natural-key


    【解决方案1】:

    出于各种原因,您应该避免在数据库中使用自然键作为主键,这很容易通过谷歌搜索找到。

    另外,“给定表格”是什么意思?您不应该坚持在文本文件和数据库表之间建立一一对应的关系。相反,您应该以最适合您需求的方式设计表格。努力规范化。

    例如,您说patient“table”中的patient_id(您的意思是文件,对吗?)不是唯一的。显然,您需要一张患者独特的表格;所以创建一个具有不同patient_id 及其属性的表。 patient_id 应该是该表上的唯一键,但会生成一个数字代理项(例如 auto_increment 字段)作为该表中的主键。之后,例如,创建一个praxis 表,将praxis_id 作为唯一键,并以数字代理作为主键。然后,您可以将patientpraxis 与第三个表连接起来,这适用于多对多关系。这样您就可以标准化您的数据库:具有其属性的患者始终是单个 patient 表中的单行;而现在您将同一患者多次输入到您当前的patient 文件/表中,这迟早会给您带来麻烦。

    【讨论】:

    • @zgguv 感谢您的回答。我没有说清楚。对于“给定”或“现有”表,我的意思是表文件已从外部数据库导出为文本文件,我无法更改它们。我不知道为什么他们使用 40 个字符的字符串作为键而不是数字。也许,我将这个键称为自然是错误的(自从它交付以来我就这样称呼它)。表 patient 没有唯一的 patient_id,因为它具有患者的历史信息。为简单起见,忘记 patient 表,只查看 invoicediagnose(1:n 关系)。 (续)
    • @zgguv (cont) 将数字代理键 invoice_ind2 添加到 invoice 没有问题。但是这个代理键也应该添加到 diagnose 作为外键,以便能够加入 invoiceinvoice_id2 = 诊断invoice_id2。所以我必须先添加一列diagnoseinvoice_id2update diagnose, invoice SET diagnose.invoice_id2 = invoice.invoice_id2 WHERE diagnose.invoice_id = invoice.invoice_id。我可以这样做,但我应该将索引添加到 diagnoseinvoice_id诊断invoice_id。那么,为什么不直接使用索引 invoice_id 进行连接呢?
    • 这是一个 Munchhausen 男爵问题:为了从海里出来,他会扯自己的头发。为了避免字符串作为连接的键,我必须使用这个字符串执行连接。
    • 我想你的数据不是静态的,但新数据会定期进入系统。代理键很好,因为您可以完全控制它们。如果有人决定更改现有发票 ID,开始向您发送非唯一发票 ID 等怎么办?也许这不太可能,但类似的事情确实发生在现实世界中。其次,您自己说您的密钥长度为 40 个字符。但是您只有几百万条记录,而一个 4 字节长的 int 键足以容纳超过 40 亿条记录。加入 4 字节列比加入 40 字节列要快得多。您只需在 char(40) 列上执行一次连接。
    • 你是如何加载数据的?几百万条记录真的不应该花那么长时间。
    【解决方案2】:

    你真的有CHAR(40),而不是VARCHAR(40)吗?值总是 40 个字符吗?表是CHARACTER SET utf8吗?

    CHAR(40) utf8 占用 120 个字节总是。如果您在这样的字段中存储“Z”,它仍然需要 120 个字节,而不仅仅是 1。即使您要导入 40 个字符,声明它VARCHAR 也会在加载时修剪尾随空白。

    至少我会这样做

    ALTER TABLE foo
        MODIFY col1 VARCHAR(40) ...,  -- the "..." is other options for the col
        MODIFY col2 VARCHAR(40) ...,
        ...;
    

    这可能对“第 (1) 点”有很大帮助。让一切变得更快。 (警告:完成 ALTER 需要很长时间。)

    “自然”主键并不邪恶。但在适当的地方使用它们。在您的情况下,出于业务原因,invoice_id 必须是唯一的,对吗?多久了? PK应该不错吧。

    在您尝试执行ALTER 之前,您桌上有什么钥匙?最初构建表时,您至少应该有PRIMARY KEY。 (但我想现在为时已晚。)

    请为每张桌子提供SHOW CREATE TABLE -- 我不得不做出太多猜测。

    添加代理 (AUTO_INCREMENT) 键为所有查找添加了一个间接级别;这可能会减慢一些SELECTs(除了需要更改许多 SELECTs)。

    向表中添加二级索引时,应以您拥有的SELECT 语句的用途为基础。我在index cookbook 中讨论了这个问题。在此处提供SELECTs 以供进一步讨论。不要盲目地为每列添加索引。

    听起来invoice_id 应该是invoicePRIMARY KEYdiagnose 中的INDEX

    如果可以重新加载数据,请声明字段VARCHAR,而不是CHAR,并在invoice 中包含PRIMARY KEY(invoice_id)

    【讨论】:

    • 感谢您提供有见地的意见和您的链接。该链接将作为我进一步使用 DB 的参考。稍后我将添加表格规范。目前:是的,我提到的所有键都有确切的 40 个字母数字字符。这就是我使用 CHAR(40) 的原因,否则我会使用 VARHCHAR(40)。使用新的表定义重新加载数据更简单。我使用 utf8mb4 来避免任何编码问题。我有德国变音符号法国口音(但键没有这样的标志)。是:invoice_idinvoice(主键)中是唯一的,但在 diagnose(外键)中是唯一的。
    • 我们看LOAD DATA声明;我不明白为什么VARCHAR 不起作用。对于法语和德语,大多数字符是 utf8 和 ut8mb4 中的 1 个字节;重音字符为 2 个字节。因此,utf8mb4 中的 40 个字符对于 CHAR(40) 占用 160 个字节,对于 VARCHAR(40) 通常占用不到 50 个字节。
    • 即使LOAD 必须进入CHAR(40),您也可以立即将ALTER 列更改为VARCHAR(40),并将表格缩小3 倍。
    • LOAD 不必进入 CHAR(40)。我将其更改为 VARCHAR(40)。现在,我明白了:由于 utf8mb4,CHAR(40) 优于 VARCHAR(40) 的优势丧失了。最初我想用数字键替换所有键(invoice_id、praxis_id、patient_id)。替换意味着:在添加数字键后删除 VARCHAR(40) 键。但也许,没有必要。另一种方法是使用 invoice_id 作为主键加载发票,并在发票和患者表中的患者 ID 和 praxis_id 上放置索引。
    • 看看切换到VARHAR 是否会改善情况。如果没有,请启动另一个线程,了解如何将它们变成INT ids(如果您需要帮助)。
    【解决方案3】:

    我自己回答我的问题。发布此帖子的原因是我提交了查询,例如使用ALTER TABLE ... 定义了一个主键,并且该过程在几个小时后没有停止。正如@zgguv 提到的,持续时间似乎不合理。我停止了查询并重新启动(有时在第三次之后),并且该过程在几分钟后完成(大约 10 分钟)。我不知道为什么有时查询会挂断。直到现在这从未发生在我身上,但我使用的桌子要小得多。经验教训是:

    • 应将长字符串键替换为数字键以加快选择速度。

    • 分别替换大小为 10 GB(文本表)的表的字符串键,20 个 Mio 行号是可行的(您只需拉自己的头发一次)。与索引字符串键之间的连接大约需要 10 分钟。

    • 如果查询持续时间超过 30'(挂断),则停止查询并重试。很高兴知道为什么会发生这种情况(InnoDB、MyISAM、HeidiSQL,...),但这是另一个问题。

    @zgguv 感谢您的支持和耐心。

    【讨论】:

      猜你喜欢
      • 2013-10-22
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 2017-12-28
      • 1970-01-01
      • 1970-01-01
      • 2012-11-06
      • 2011-08-11
      相关资源
      最近更新 更多