如何将 UPDATE 分解为子查询?
How can one break up an UPDATE into subqueries?
存在一个table:
CREATE TABLE person
(
id INT(10) PRIMARY KEY AUTO_INCREMENT,
nameFirst VARCHAR(255) DEFAULT '?',
nameSecond VARCHAR(255) DEFAULT '',
fatherNameFirst VARCHAR(255) DEFAULT NULL
);
注:其实还有其他列,一共18列,这里没有用到
目标是通过使用 child 的第二名来设置父亲的名字。它可以从俄语中的第二个名字(父名)预测出来,但并不总是正确的。所以我打算做一些可以自动完成的,有些会在以后手动完成。
于是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 条目的 table 上,现在我已经尝试使用 400k 的样本数据。问题是,在我的计算机使用一个 100% 的内核后大约一个小时后,查询尚未完成。
所以我在想是否可以将其分解为子查询,这样可以一个接一个地设置为 运行,但每个子查询应该需要 5-10 分钟。这样,如果我需要做某事,我可以终止当前 运行ning 一个,而不会浪费一天的 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
此时为空
您可以通过将 where nameFirst like 'A%'
添加到更新查询中来按字母顺序分解它 - 然后多次 运行 查询。
鉴于此示例数据:
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>;
连接到服务器的每个线程都使用自己的连接缓冲区。这就是为什么你应该只在会话中测试它。当您的服务器上有很多连接时,内存消耗可能会失控。
存在一个table:
CREATE TABLE person
(
id INT(10) PRIMARY KEY AUTO_INCREMENT,
nameFirst VARCHAR(255) DEFAULT '?',
nameSecond VARCHAR(255) DEFAULT '',
fatherNameFirst VARCHAR(255) DEFAULT NULL
);
注:其实还有其他列,一共18列,这里没有用到
目标是通过使用 child 的第二名来设置父亲的名字。它可以从俄语中的第二个名字(父名)预测出来,但并不总是正确的。所以我打算做一些可以自动完成的,有些会在以后手动完成。
于是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 条目的 table 上,现在我已经尝试使用 400k 的样本数据。问题是,在我的计算机使用一个 100% 的内核后大约一个小时后,查询尚未完成。
所以我在想是否可以将其分解为子查询,这样可以一个接一个地设置为 运行,但每个子查询应该需要 5-10 分钟。这样,如果我需要做某事,我可以终止当前 运行ning 一个,而不会浪费一天的 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
此时为空
您可以通过将 where nameFirst like 'A%'
添加到更新查询中来按字母顺序分解它 - 然后多次 运行 查询。
鉴于此示例数据:
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>;
连接到服务器的每个线程都使用自己的连接缓冲区。这就是为什么你应该只在会话中测试它。当您的服务器上有很多连接时,内存消耗可能会失控。