-- 最慢方式:每行一条 INSERT,每条自动提交
SET autocommit = 1;
FOR row IN 待插入数据:
INSERT INTO target_table(col1, col2, col3, ...)
VALUES (...);
-- 每条 INSERT 执行完,MySQL 自动提交一次
END FOR
②单条insert + 显式事务
[SQL] 纯文本查看复制代码
-- 单条 INSERT,但是使用显式事务
SET autocommit = 0;
row_count_in_transaction = 0
FOR row IN 待插入数据:
INSERT INTO target_table(col1, col2, col3, ...)
VALUES (...);
row_count_in_transaction = row_count_in_transaction + 1
IF row_count_in_transaction == 50 THEN
COMMIT;
row_count_in_transaction = 0
END IF
END FOR
COMMIT;
SET autocommit = 1;
③多值 insert + 无显式事务
[SQL] 纯文本查看复制代码
-- 多值 INSERT,但是 autocommit = 1
SET autocommit = 1;
batch_values = []
FOR row IN 待插入数据:
把 row 加入 batch_values
IF batch_values 数量 == 50 THEN
INSERT INTO target_table(col1, col2, col3, ...)
VALUES
(...),
(...),
(...),
-- 一共 50 行
(...);
-- autocommit = 1,所以这条 INSERT 执行完自动提交
清空 batch_values
END IF
END FOR
IF batch_values 不为空 THEN
INSERT INTO target_table(col1, col2, col3, ...)
VALUES
(...),
(...);
END IF
④ 事务 + 多值insert
[SQL] 纯文本查看复制代码
-- 推荐方式:事务 + 多值 INSERT
SET autocommit = 0;
batch_values = []
sql_count_in_transaction = 0
FOR row IN 待插入数据:
把 row 加入 batch_values
IF batch_values 数量 == 50 THEN
INSERT INTO target_table(col1, col2, col3, ...)
VALUES
(...),
(...),
(...),
-- 一共 50 行
(...);
清空 batch_values
sql_count_in_transaction = sql_count_in_transaction + 1
IF sql_count_in_transaction == 20 THEN
COMMIT;
sql_count_in_transaction = 0
END IF
END IF
END FOR
-- 处理最后不足 50 行的数据
IF batch_values 不为空 THEN
INSERT INTO target_table(col1, col2, col3, ...)
VALUES
(...),
(...);
END IF
COMMIT;
SET autocommit = 1;
SELECT
table_name,
column_name,
column_type
FROM information_schema.columns
WHERE table_schema = 'crawler_db'
AND data_type IN ('text', 'mediumtext', 'longtext', 'varchar')
ORDER BY table_name;
输出:
(5)summary
它不是直接优化 SQL 的工具,而是 MySQL 的“数据库元数据查询系统”。它能告诉你数据库里有什么、表结构是什么、字段是什么、索引怎么建、表占多少空间、分区怎么分。数据库优化之前,先用它做体检。
在实际优化数据库中,可以采用一下的排查顺序
UPDATE crawl_url_queue
SET status = 1
WHERE id = 1001;
第二种写法,读并锁定-先读后改
[SQL] 纯文本查看复制代码
START TRANSACTION;
SELECT status FROM crawl_url_queue WHERE id = 1001 FOR UPDATE;
-- 应用层判断 status=0
UPDATE crawl_url_queue SET status = 1 WHERE id = 1001;
COMMIT;
这种的应用场景是,你需要根据当前行的状态来决定是否更新,并且这个“判断-执行”操作必须是原子的。
③意向锁IS / IX (无感表级锁)
意向锁是表级锁,但它不是用来真正锁住整张表的,而是用来告诉数据库:我接下来准备在这张表的某些行上加共享锁或排他锁。事务准备对某些行加共享锁时,表上会有 IS 锁;
事务准备对某些行加排他锁时,表上会有 IX 锁。
为什么有意向锁,解决了什么问题:
[Asm] 纯文本查看复制代码
假设没有意向锁。现在事务 A 修改了第 100 行,也就是在第 100 行上持有一个排他锁 (X)。
这时,事务 B 想要干一件大事,比如执行 ALTER TABLE 或 LOCK TABLES ... WRITE,它需要给整张表加一个排他锁 (X)。
数据库怎么判断能不能加这个表锁?它必须确认:这张表里现在没有其他事务持有任何一行的排他锁。
在没有意向锁的情况下,数据库只有一个笨办法:从头到尾遍历这张表的所有行,检查每一行是否有锁。 对于一张有千万行数据的爬虫任务表,这个检查过程本身就会造成巨大的性能灾难,完全不可行。
有了意向锁,事情就简单了:
事务 A 在修改第 100 行时,会自动在表级别留下一个标记,叫意向排他锁 (IX)。意思是:“注意,本事务打算或正在某些行上持有排他锁。”
事务 B 想加表级排他锁时,只需要看一眼表上有没有冲突的意向锁,它发现表上已经有了 IX 锁,就知道表里有行被锁住了,于是直接等待,无需检查任何一行
用极小的代价,把一个 O(n) 的行扫描判断,变成了 O(1) 的表级标志位判断,让表锁和行锁能够高效共存。
START TRANSACTION;
SELECT id, url
FROM crawl_task
WHERE status = 0
ORDER BY updated_at
LIMIT 1
FOR UPDATE;
UPDATE crawl_task
SET status = 1,
updated_at = NOW()
WHERE id = 上一步查到的id;
COMMIT;
2. 按范围分 range分区
分区键的形式:必须是一个整数表达式,如 YEAR(date)、TO_DAYS(date) 或直接是整数列,表达式结果必须是整数,边界定义:VALUES LESS THAN (整数值)
[SQL] 纯文本查看复制代码
CREATE TABLE log_by_year (
id BIGINT NOT NULL,
created_at DATETIME NOT NULL,
content VARCHAR(200),
PRIMARY KEY (id, created_at)
) ENGINE=InnoDB
PARTITION BY RANGE (YEAR(created_at)) (
PARTITION p2024 VALUES LESS THAN (2025),
PARTITION p2025 VALUES LESS THAN (2026),
PARTITION p2026 VALUES LESS THAN (2027),
PARTITION pmax VALUES LESS THAN MAXVALUE
);
即
YEAR(created_at) < 2025 进入 p2024
YEAR(created_at) < 2026 进入 p2025
YEAR(created_at) < 2027 进入 p2026
更大的值进入 pmax
3. 按列值范围分 RANGE COLUMNS 分区
分区键的形式:直接说过一个或多个列名,不需要包装为表达式。原生支持 int、data、datetime、char、varchar等多种类型。边界定义VALUES LESS THAN (值),值的类型须与列类型匹配,如日期、字符串
按照created_at 的时间范围分区。
[SQL] 纯文本查看复制代码
CREATE TABLE crawl_log_range (
id BIGINT NOT NULL AUTO_INCREMENT,
domain VARCHAR(100) NOT NULL,
url_hash CHAR(32) NOT NULL,
status_code INT NOT NULL,
cost_ms INT NOT NULL,
created_at DATETIME NOT NULL,
PRIMARY KEY (id, created_at),
KEY idx_created_at(created_at),
KEY idx_domain_created(domain, created_at)
) ENGINE=InnoDB
PARTITION BY RANGE COLUMNS(created_at) (
PARTITION p202601 VALUES LESS THAN ('2026-02-01'),
PARTITION p202602 VALUES LESS THAN ('2026-03-01'),
PARTITION p202603 VALUES LESS THAN ('2026-04-01'),
PARTITION p202604 VALUES LESS THAN ('2026-05-01'),
PARTITION p202605 VALUES LESS THAN ('2026-06-01'),
PARTITION p202606 VALUES LESS THAN ('2026-07-01'),
PARTITION pmax VALUES LESS THAN (MAXVALUE)
);
其中
PARTITION p202606 VALUES LESS THAN ('2026-07-01')
表示
小于 '2026-07-01',并且不属于前面分区的数据,会进入 p202606。
区别
4. 按固定值列表分 :LIST 分区
[SQL] 纯文本查看复制代码
CREATE TABLE crawl_source_log (
id BIGINT NOT NULL AUTO_INCREMENT,
source_id INT NOT NULL,
url_hash CHAR(32) NOT NULL,
status_code INT NOT NULL,
created_at DATETIME NOT NULL,
PRIMARY KEY (id, source_id),
KEY idx_source_created(source_id, created_at)
) ENGINE=InnoDB
PARTITION BY LIST (source_id) (
PARTITION p_jd VALUES IN (1),
PARTITION p_tmall VALUES IN (2),
PARTITION p_pdd VALUES IN (3),
PARTITION p_other VALUES IN (4,5,6,7,8,9)
);
source_id = 1 的数据进入 p_jd
source_id = 2 的数据进入 p_tmall
source_id = 3 的数据进入 p_pdd
source_id 在 4~9 的数据进入 p_other
5. 按列值列表分 LIST COLUMNS 分区
[SQL] 纯文本查看复制代码
CREATE TABLE crawl_platform_log (
id BIGINT NOT NULL AUTO_INCREMENT,
platform VARCHAR(20) NOT NULL,
url_hash CHAR(32) NOT NULL,
created_at DATETIME NOT NULL,
PRIMARY KEY (id, platform)
) ENGINE=InnoDB
PARTITION BY LIST COLUMNS(platform) (
PARTITION p_ecommerce VALUES IN ('jd', 'tmall', 'pdd'),
PARTITION p_content VALUES IN ('zhihu', 'weibo', 'bilibili'),
PARTITION p_other VALUES IN ('other')
);
platform 是 jd、tmall、pdd,进入电商分区;
platform 是 zhihu、weibo、bilibili,进入内容平台分区。