如何将UPDATE分解为子查询?

时间:2015-10-12 10:25:30

标签: mysql sql-update

有一张表:

CREATE TABLE person
(
    id INT(10) PRIMARY KEY AUTO_INCREMENT,
    nameFirst VARCHAR(255) DEFAULT '?',
    nameSecond VARCHAR(255) DEFAULT '',
    fatherNameFirst VARCHAR(255) DEFAULT NULL
);

注意:实际上有其他列,总共18列,但这里没有使用它们。

目标是使用孩子的第二个名字设置父亲的名字。它可以用俄语的第二个名字(patronymic)来预测,但并不总是正确的。所以我打算做一些可以自动完成的工作,有些人会在以后手工完成。

所以UPDATE完成如下:

UPDATE person AS child
LEFT JOIN
  (SELECT DISTINCT nameFirst FROM person) AS parent
    ON CONCAT(parent.nameFirst,'овна')=child.nameSecond OR CONCAT(parent.nameFirst,'ович')=child.nameSecond
SET child.fatherNameFirst=parent.nameFirst;

最终它需要在一个包含> 2m条目的表上运行,现在我已尝试使用400k的示例数据。问题是,在我的计算机使用一个小时后,如果其核心为100%,则查询尚未完成。

所以我在考虑是否可以将其分解为子查询,因此这些可以设置为一个接一个地运行,但它们每个应该花费5-10分钟。这样,如果我需要做某事,我可以终止当前运行的一个而不会失去一天的CPU时间。

我试图添加:WHERE child.id<1000但是它仍然太长或影响不大(或许我误解了MariaDB如何打开此更新)。

如果样本数据实际上有助于人们更好地理解它:

select id, nameFirst, nameSecond from person limit 10;
+----+--------------------+----------------------------+
| id | nameFirst          | nameSecond                 |
+----+--------------------+----------------------------+
|  1 | Туликович          |                            |
|  2 | Август             | Михайлович                 |
|  3 | Август             | Христианович               |
|  4 | Александр          | Александрович              |
|  5 | Александр          | Христьянович               |
|  6 | Альберт            | Викторович                 |
|  7 | Альбрехт           | Александрович              |
|  8 | Амалия             | Андреевна                  |
|  9 | Амалия             | Ивановна                   |
| 10 | Ангелина           | Андреевна                  |
+----+--------------------+----------------------------+

fatherNameFirst此时为空。

2 个答案:

答案 0 :(得分:1)

您可以通过向更新查询添加where nameFirst like 'A%'按字母顺序将其拆分 - 然后多次运行查询。

答案 1 :(得分:1)

鉴于此样本数据:

CREATE TABLE person
(
    id INT(10) PRIMARY KEY AUTO_INCREMENT,
    nameFirst VARCHAR(255) DEFAULT '?',
    nameSecond VARCHAR(255) DEFAULT '',
    fatherNameFirst VARCHAR(255) DEFAULT NULL
) DEFAULT CHARSET=utf8;


INSERT INTO person
    (`id`, `nameFirst`, `nameSecond`)
VALUES
    (1, 'Туликович', NULL),
    (2, 'Август', 'Михайлович'),
    (3, 'Август', 'Христианович'),
    (4, 'Александр', 'Александрович'),
    (5, 'Александр', 'Христьянович'),
    (6, 'Альберт', 'Викторович'),
    (7, 'Альбрехт', 'Александрович'),
    (8, 'Амалия', 'Андреевна'),
    (9, 'Амалия', 'Ивановна'),
    (10, 'Ангелина', 'Андреевна')
;

使用您的查询,您可以获得此EXPLAIN输出:

+----+-------------+------------+------+---------------+------+---------+------+------+----------------------------------------------------+
| id | select_type | table      | type | possible_keys | key  | key_len | ref  | rows | Extra                                              |
+----+-------------+------------+------+---------------+------+---------+------+------+----------------------------------------------------+
|  1 | PRIMARY     | child      | ALL  | NULL          | NULL | NULL    | NULL |   10 | NULL                                               |
|  1 | PRIMARY     | <derived2> | ALL  | NULL          | NULL | NULL    | NULL |   10 | Using where; Using join buffer (Block Nested Loop) |
|  2 | DERIVED     | person     | ALL  | NULL          | NULL | NULL    | NULL |   10 | Using temporary                                    |
+----+-------------+------------+------+---------------+------+---------+------+------+----------------------------------------------------+

这可能是你能得到的最糟糕的事情。

让我们看看我们是否可以改写这个。首先,完全不需要这个子查询和DISTINCT

mysql > explain UPDATE person AS child
    -> LEFT JOIN person parent
    ->     ON CONCAT(parent.nameFirst,'овна')=child.nameSecond OR CONCAT(parent.nameFirst,'ович')=child.nameSecond
    -> SET child.fatherNameFirst=parent.nameFirst;
+----+-------------+--------+------+---------------+------+---------+------+------+----------------------------------------------------+
| id | select_type | table  | type | possible_keys | key  | key_len | ref  | rows | Extra                                              |
+----+-------------+--------+------+---------------+------+---------+------+------+----------------------------------------------------+
|  1 | SIMPLE      | child  | ALL  | NULL          | NULL | NULL    | NULL |   10 | NULL                                               |
|  1 | SIMPLE      | parent | ALL  | NULL          | NULL | NULL    | NULL |   10 | Using where; Using join buffer (Block Nested Loop) |
+----+-------------+--------+------+---------------+------+---------+------+------+----------------------------------------------------+

这消除了Using temporary。这很好。
使用nameFirst上的索引,我们可以进一步提高速度。

CREATE INDEX idx_person_nameFirst ON person(nameFirst);

然后再次explain

+----+-------------+--------+-------+---------------+----------------------+---------+------+------+-----------------------------------------------------------------+
| id | select_type | table  | type  | possible_keys | key                  | key_len | ref  | rows | Extra                                                           |
+----+-------------+--------+-------+---------------+----------------------+---------+------+------+-----------------------------------------------------------------+
|  1 | SIMPLE      | child  | ALL   | NULL          | NULL                 | NULL    | NULL |   10 | NULL                                                            |
|  1 | SIMPLE      | parent | index | NULL          | idx_person_nameFirst | 768     | NULL |   10 | Using where; Using index; Using join buffer (Block Nested Loop) |
+----+-------------+--------+-------+---------------+----------------------+---------+------+------+-----------------------------------------------------------------+

还不完美,但它使用了索引。这应该可以加快速度。

从现在开始,很难进一步优化。您可以通过调整join buffer size进行一些实验,但我建议您仅在会话中执行此操作。

SET SESSION join_buffer_size = <whatever value>;

连接到服务器的每个线程都使用自己的连接缓冲区。这就是为什么你应该只在一个会话中测试它的原因。当您的服务器上有很多连接时,内存消耗可能会失控。