-- 只改表的默认值:以后新加的列用 utf8mb4,已有的列不动ALTER TABLE t DEFAULT CHARACTER SET utf8mb4;-- 把已有列和数据都转过去ALTER TABLE t CONVERT TO CHARACTER SET utf8mb4;-- 改单列的类型ALTER TABLE t MODIFY COLUMN v BIGINT;
没索引:8.0.18 之前只能走 BNL,把一批驱动表的行放进 join buffer,再扫被驱动表两两比较,约 M × N 次。
hash join 分两步:
构建:把小表按连接列建一张内存哈希表。
探测:扫一遍大表,每行算哈希去表里找。
两张表各扫一遍,约 M + N。内存放不下时按哈希分片落盘,再一片片处理。
sql
EXPLAIN FORMAT=TREE SELECT * FROM o JOIN m ON m.code = o.member_code;-- -> Inner hash join (o.member_code = m.code)-- -> Table scan on o-- -> Hash-- -> Table scan on m
但 EXPLAIN 里看到 Using index condition,不等于真省了回表。用 (name, age) 索引、10 万行数据试了两条:
sql
-- 1. name 和 age 都用来定扫描起止SELECT * FROM tuser WHERE name = '张1' AND age > 30;-- 2. 只有 name 能定起止,age 只能过滤SELECT * FROM tuser WHERE name LIKE '张1%' AND age = 10;
分别开关 optimizer_switch='index_condition_pushdown=on/off',看 InnoDB 交给 Server 的行数:
查询
ICP 开
ICP 关
1
6
6
2
375
22213
第 1 条扫到的每一行本来就满足条件,下推的判断次次为真,一行都没省。第 2 条里 age 定不了范围,下推才真正在叶子上丢掉了 2 万多行。
ALTER TABLE d ADD INDEX iv(v), ALGORITHM=INSTANT;-- ERROR: ALGORITHM=INSTANT is not supported for this operation. Try ALGORITHM=COPY/INPLACE.ALTER TABLE d MODIFY v BIGINT, ALGORITHM=INPLACE;-- ERROR: Cannot change column type INPLACE. Try ALGORITHM=COPY.
建了 cb(c, b),WHERE c = 1 ORDER BY b 还是 Using filesort:
sql
EXPLAIN SELECT * FROM geek WHERE c = 1 ORDER BY b;-- key: c Extra: Using filesortEXPLAIN SELECT * FROM geek FORCE INDEX (cb) WHERE c = 1 ORDER BY b;-- key: cb Extra: NULL
RR 隔离级别下做个实验:事务 B 开始后、第一次读之前,让 C 把 k 从 1 改成 7 并提交。
sql
-- BBEGIN;-- (这时 C 提交了 k = 7)SELECT k FROM t WHERE id = 1; -- 7-- 换成这个START TRANSACTION WITH CONSISTENT SNAPSHOT;-- (这时 C 提交了 k = 7)SELECT k FROM t WHERE id = 1; -- 1
BEGIN 只是标记事务开始,执行到第一条普通 SELECT 时才建快照。WITH CONSISTENT SNAPSHOT 当场就建(只在 RR 下有意义)。
SELECT balance FROM acct WHERE id = 1; -- 够 100UPDATE acct SET balance = balance - 100 WHERE id = 1;
两步之间,别人可能已经扣过了。调高隔离级别解决不了这个问题。两种正确写法:
sql
-- 1. 一条带条件的 UPDATE,看影响行数是不是 1UPDATE acct SET balance = balance - 100WHERE id = 1 AND balance >= 100;-- 2. 先锁住再判断BEGIN;SELECT balance FROM acct WHERE id = 1 FOR UPDATE;-- 判断够不够,再 UPDATECOMMIT;
联合索引 (name, age) 的叶子先按 name 排,name 相同的再按 age 排。所以 age 只在 name 完全相同的那一小组里有序。
sql
-- 两列都能定扫描起止:name 固定后,age 是连续的一段WHERE name = '张三' AND age > 10-- 只有 name 能定起止:'张%' 本身是范围,这段里 name 各不相同,age 来回跳WHERE name LIKE '张%' AND age > 10-- 一列都用不上:age 在整条叶子链上不连续WHERE age = 10