8.业务场景实践2

予早 2026-04-30 23:53:30
Categories: Tags:

索引应用

索引创建

-- create table时创建索引
CREATE TABLE table_name [col_name data_type] [UNIQUE|FULLTEXT|SPATIAL] [INDEX] [index_name] (col_name [length]) [ASC|DESC]
CREATE TABLE t1 (  id INT NOT NULL,     name CHAR(30) NOT NULL,     UNIQUE INDEX UniqIdx(id) );
-- alter table时创建索引
ALTER TABLE table_name ADD  [UNIQUE|FULLTEXT|SPATIAL] [INDEX|KEY] [index_name] (col_name[length],...) [ASC|DESC]
-- create index创建索引
CREATE [UNIQUE|FULLTEXT|SPATIAL] INDEX index_name  ON table_name (col_name [length],...) [ASC|DESC]
CREATE UNIQUE INDEX UniqidIdx ON book (bookId);

[!IMPORTANT]

CREATE TABLE tb_user (
`id` BIGINT, 
`name` VARCHAR (50), 
`email` VARCHAR (100), 
`age` INT, 
`created_time` DATETIME, 
`updated_time` DATETIME,
INDEX idx_name (name),
UNIQUE INDEX udx_email (email)
);

上述创建表语句会创建三个索引,idx_name、udx_email、GEN_CLUST_INDEXSHOW INDEX FROM tb_user;无法查到系统生成的索引,需要使用:

SELECT * 
FROM information_schema.innodb_indexes 
WHERE table_id = (
SELECT table_id 
FROM information_schema.innodb_tables 
WHERE NAME = 'db_service_xxx/tb_user'
);

索引使用场景

select,查询优化为覆盖索引

where,基于索引迅速匹配

group by 或 order by,基于索引最左前缀优化分组和排序

索引失效

索引失效场景:

  1. 左规则:索引列使用左模糊匹配或联合索引并未遵循最左前缀匹配
  2. 非裸列:索引以非裸列形式参与比较:参与计算、作为函数入参、类型转换(强制或隐式)
  3. 非常量:索引与非常量比较:
  4. 成本高:索引成本估算高于全表扫描或使用索引也需全表扫描造成索引失效

分析索引是否失效,有一个重要的前提条件,就是是否使用覆盖索引,能使用覆盖索引的情况通常更容易使用索引

场景分析和验证

select version();
-- 8.4.6

创建tb_user表并使用 Navicat 按注释所示规则生成数据。

DROP TABLE IF EXISTS `tb_user`;
CREATE TABLE `tb_user` (
  `id` BIGINT AUTO_INCREMENT COMMENT "自增主键。测试数据分布特点:非空,bigint 自增",
  `company_name` VARCHAR (120) COMMENT "公司名称。测试数据分布特点:中文名称或英文名称,5% NULL 值,随机生成",
  `birthday` DATE COMMENT "生日。测试数据分布特点:取值范围为 1970-01-01 - 2025-12-31,5% NULL 值,随机生成",
  `age` INT COMMENT "年龄。测试数据分布特点:取值范围为 0 - 100,5% NULL 值,随机生成",
  `lucky_number` INT COMMENT "幸运数。取值范围为 0 - 9,95% NULL 值,随机生成",
  `credit_card_visa` CHAR(16) COMMENT "VISA 银行卡号。测试数据分布特点:固定16位数字字符串,5% NULL 值,随机生成",
  `status` INT COMMENT "1-正常,2-限期封禁,3-永久封禁" COMMENT "用户状态。测试数据分布特点:非空,随机生成",
  `is_deleted` TINYINT (1) COMMENT "逻辑删除。测试数据分布特点:非空,随机生成",
  `created_time` DATETIME COMMENT "创建时间。测试数据分布特点:取值范围为 2000-01-01 - 2025-12-31,非空,随机生成",
  `updated_time` DATETIME COMMENT "删除时间。测试数据分布特点:取值范围为 2000-01-01 - 2025-12-31,非空,随机生成",
  PRIMARY KEY (`id`),
  INDEX idx_age (`age`),
  INDEX idx_lucky_number (`lucky_number`),
  INDEX idx_birthday (`birthday`),
  INDEX idx_birth_year ((YEAR(`birthday`))),
  INDEX idx_credit_card_visa (`credit_card_visa`),
  INDEX idx_status_is_deleted_created_time (`status`, `is_deleted`, `created_time`),
  INDEX idx_company_name (`company_name`)
) ENGINE = InnoDB DEFAULT CHARSET = utf8mb4 COLLATE = utf8mb4_unicode_ci;

左规则

索引左模糊匹配失效

-- idx_company_name
EXPLAIN SELECT * FROM tb_user WHERE company_name like '%工程%';
EXPLAIN SELECT * FROM tb_user WHERE company_name like '%工程';

联合索引不遵循最左匹配原则失效

-- idx_status_is_deleted_created_time
EXPLAIN SELECT * FROM tb_user WHERE is_deleted = false and created_time >= "2010-01-01 00:00:00"; -- 失效
EXPLAIN SELECT * FROM tb_user WHERE status = 1 and is_deleted = false and created_time >= "2010-01-01 00:00:00"; -- 有效
EXPLAIN SELECT * FROM tb_user WHERE created_time >= "2010-01-01 00:00:00" and is_deleted = false and status = 1; -- 有效

非裸列

索引参与计算失效

-- idx_age
EXPLAIN SELECT * FROM tb_user WHERE age - 5 = 60;
EXPLAIN SELECT * FROM tb_user WHERE age - 5 > 60;

索引作为函数入参失效

-- idx_birthday
EXPLAIN SELECT * FROM tb_user WHERE DAY(birthday) = 6; -- 失效
-- 可以建立函数索引,idx_birth_year
EXPLAIN SELECT * FROM tb_user WHERE YEAR(birthday) = 2000; -- 有效

索引发生类型转换失效

-- idx_credit_card_visa
EXPLAIN SELECT * FROM tb_user WHERE credit_card_visa = 4288432640196911; -- 失效
EXPLAIN SELECT * FROM tb_user WHERE CAST(credit_card_visa AS SIGNED) = 4288432640196911; -- 失效

非常量

-- 包括两列做比较的情况,无论哪种比较,无论是否另外一列为索引列
EXPLAIN SELECT * FROM tb_user WHERE age = lucky_number;
EXPLAIN SELECT * FROM tb_user WHERE age > lucky_number;
EXPLAIN SELECT * FROM tb_user WHERE age >= lucky_number;
EXPLAIN SELECT * FROM tb_user WHERE age < lucky_number;
EXPLAIN SELECT * FROM tb_user WHERE age <= lucky_number;

成本高

-- 索引条件 or 非索引条件失效
EXPLAIN SELECT * FROM tb_user WHERE status = 1 and is_deleted = false or updated_time >= "2020-01-01 00:00:00"; -- 失效
EXPLAIN SELECT * FROM tb_user WHERE status = 1 and is_deleted = false or updated_time = "2020-01-01 00:00:00"; -- 失效


-- 测试比较和分布对失效的影响,idx_age,age 取值范围为 [0,100],5% NULL,lucky_number 取值范围 [0,9] 95% NULL

EXPLAIN SELECT * FROM tb_user WHERE age = 50; -- 有效
EXPLAIN SELECT * FROM tb_user WHERE age != 50; -- 无效
EXPLAIN SELECT * FROM tb_user WHERE age > 20; -- 无效
EXPLAIN SELECT * FROM tb_user WHERE age >= 20; -- 无效
EXPLAIN SELECT * FROM tb_user WHERE age > 90; -- 有效
EXPLAIN SELECT * FROM tb_user WHERE age >= 90; -- 有效
EXPLAIN SELECT * FROM tb_user WHERE age < 10; -- 有效
EXPLAIN SELECT * FROM tb_user WHERE age <= 10; -- 有效
EXPLAIN SELECT * FROM tb_user WHERE age < 80; -- 无效
EXPLAIN SELECT * FROM tb_user WHERE age <= 80; -- 无效
EXPLAIN SELECT * FROM tb_user WHERE age = NULL; -- 不可能匹配
EXPLAIN SELECT * FROM tb_user WHERE age != NULL; -- 不可能匹配
EXPLAIN SELECT * FROM tb_user WHERE age IS NULL; -- 有效
EXPLAIN SELECT * FROM tb_user WHERE age IS NOT NULL; -- 无效
        
EXPLAIN SELECT * FROM tb_user WHERE lucky_number = 5; -- 有效
EXPLAIN SELECT * FROM tb_user WHERE lucky_number != 5; -- 有效
EXPLAIN SELECT * FROM tb_user WHERE lucky_number > 2; -- 有效
EXPLAIN SELECT * FROM tb_user WHERE lucky_number >= 2; -- 有效
EXPLAIN SELECT * FROM tb_user WHERE lucky_number > 8; -- 有效
EXPLAIN SELECT * FROM tb_user WHERE lucky_number >= 8; -- 有效
EXPLAIN SELECT * FROM tb_user WHERE lucky_number < 2; -- 有效
EXPLAIN SELECT * FROM tb_user WHERE lucky_number <= 2; -- 有效
EXPLAIN SELECT * FROM tb_user WHERE lucky_number < 8; -- 有效
EXPLAIN SELECT * FROM tb_user WHERE lucky_number <= 8; -- 有效
EXPLAIN SELECT * FROM tb_user WHERE lucky_number = NULL; -- 不可能匹配
EXPLAIN SELECT * FROM tb_user WHERE lucky_number != NULL; -- 不可能匹配
EXPLAIN SELECT * FROM tb_user WHERE lucky_number IS NULL; -- 有效
EXPLAIN SELECT * FROM tb_user WHERE lucky_number IS NOT NULL; -- 有效




-- in、exist 受资源限制,MySQL 根据实际情况评估是否走索引
-- 资源不足时失效
SET SESSION range_optimizer_max_mem_size = 4096;
EXPLAIN FORMAT=JSON SELECT * FROM tb_user WHERE credit_card_visa in (
"4310983056222098", "4544477230033842", "4135688111827145", "4391239892946610", "4676648722180867", "4747340267965861",
"4121788262052869", "4232073621890887", "4548999686134275", "4990111938171278", "4533137209486024", "4984823193078513",
"4106995884078169", "4865700157334906", "4490576803339116", "4732735347319285", "4166710925363513", "4704000218734441",
"4876167134235720", "4996326021285777", "4507900561804700", "4126978336448392", "4208236028529784", "4204321939836983",
"4386508113547382", "4072190443027911", "4182736799998050", "4406223091809145", "4241089434857268", "4462915462435951",
"4300701593938931", "4952196684377100"
)

-- 资源足够时走索引
SET SESSION range_optimizer_max_mem_size = 16384;
EXPLAIN FORMAT=JSON SELECT * FROM tb_user WHERE credit_card_visa in (
"4310983056222098", "4544477230033842", "4135688111827145", "4391239892946610", "4676648722180867", "4747340267965861",
"4121788262052869", "4232073621890887", "4548999686134275", "4990111938171278", "4533137209486024", "4984823193078513",
"4106995884078169", "4865700157334906", "4490576803339116", "4732735347319285", "4166710925363513", "4704000218734441",
"4876167134235720", "4996326021285777", "4507900561804700", "4126978336448392", "4208236028529784", "4204321939836983",
"4386508113547382", "4072190443027911", "4182736799998050", "4406223091809145", "4241089434857268", "4462915462435951",
"4300701593938931", "4952196684377100"
)
优化器成本模型

https://dev.mysql.com/doc/refman/8.4/en/cost-model.html

优化器成本模型中包含 SQL 执行过程中各种操作的成本数据,优化器会使用成本模型决策执行方案以生成执行计划。

优化器成本模型中成本数据由两部分组成:

  1. MySQL 内置默认成本常量,在代码中声明后编译而来

  2. 成本表,通用成本表mysql.server_cost、具体引擎成本表mysql.engine_cost,

    -- 服务器通用操作的优化器成本
    SELECT * FROM mysql.server_cost;
    -- 特定于具体存储引擎的操作的优化器成本
    SELECT * FROM mysql.engine_cost;
    

可配置的优化器成本模型的工作原理如下:

  1. 服务器在启动时将成本模型表读入内存,并在运行时使用内存中的值。表中指定的任何非NULL成本估算都优先于相应的编译默认成本常量。任何NULL估算都指示优化器使用编译默认值。
  2. 在运行时,当存储引擎被动态加载或执行FLUSH OPTIMIZER_COSTS语句时,服务器会重新读取成本表。
  3. 成本表使服务器管理员能够通过修改表中的条目轻松调整成本估算。通过将条目的成本设置为NULL,也很容易恢复为默认值。优化器使用内存中的成本值,因此对表的更改后应执行FLUSH OPTIMIZER_COSTS才能生效。
  4. 客户端会话开始时当前的内存中成本估算会在整个会话期间适用,直至会话结束。特别是,如果服务器重新读取成本表,任何已更改的估算仅适用于后续启动的会话。现有会话不受影响。
  5. 成本表特定于给定的服务器实例。服务器不会将成本表的更改复制到副本。

表统计信息

cardinality,基数

表中列包含不同值的数量,可以通过SHOW INDEX FROM tbl_name;查看,该值由统计采样得来。当查询涉及到有关联索引的列时,每列的基数会影响哪种访问方法最有效。

例如,对于具有唯一约束的列,不同值的数量等于表中的行数。如果一个表有一百万行,但某一列只有 10 个不同的值,那么每个值平均出现 100,000 次。因此,像 SELECT c1 FROM t1 WHERE c1 = 50; 这样的查询可能返回 1 行,也可能返回大量行,数据库服务器可能会根据 c1 的基数以不同方式处理该查询。

如果列中的值分布非常不均匀,那么基数可能不是确定最佳查询计划的好方法。例如,SELECT c1 FROM t1 WHERE c1 = x; 在 x=50 时可能返回 1 行,在 x=30 时可能返回一百万行。在这种情况下,你可能需要使用索引提示

选择性,selectivity

数据分布的一个属性,即一列中不同值的数量(其基数)除以表中的记录数,反映列唯一值比例。高选择性意味着列值相对独特,并且可以通过索引高效检索。如果您(或查询优化器)能够预测到WHERE子句中的一个测试仅匹配表中少量(或小比例)的行,那么如果先使用索引对该测试进行评估,整个查询往往会很高效。

平均值组大小,Average Value Group Size

表总行数 / 基数。

MySQL 会以如下方式使用 Average Value Group Size

随着某个索引的 Average Value Group Size 增大,该索引在上述两种用途中的有效性会降低,因为每次查找所对应的平均行数增加了。因此,为了使索引在优化器看来更有用,理想情况下,每个索引值应只对应表中少量的行。

innodb_stats_method

innodb_stats_method 用于控制 InnoDB 中 NULL 的统计方法,不同的设置会直接改变 Average Value Group Size 的计算结果,进而影响优化器判断索引是否“足够有选择性”:

  1. nulls_equal(默认):所有 NULL 视为同一组,可能高估索引值分布,导致优化器误判索引无效。若某列 NULL 值占 90%,统计信息会将所有 NULL 视为同一组,导致平均行组大小被严重高估,优化器误判索引无效。
  2. nulls_unequal:每个 NULL 视为独立组,可能低估索引选择性,导致优化器错误使用索引。
  3. nulls_ignored:忽略 NULL 值,适用于 <=> 比较操作。

eq_range_index_dive_limit

eq_range_index_dive_limit,当 IN (…)OR 列表元素个数超过该阈值(默认 200)时,优化器不再做 index dive,而是改用索引统计(Index Statistics)估算行数;若统计信息过期或分布极度倾斜,就可能导致优化器误判索引无效

联合索引中,出现范围查询,范围查询右侧索引列失效,由索引下推优化

降低索引IO

11

索引设计

索引命名规范

单值索引,建议以 idx_ 为开头,字母全部小写。

例如:alter table t1 add key idx_r1(r1);

组合索引,建议以 dx_multi_ 开头,字母全部小写。

例如:alter table t1 add key idx_multi_1(r1,r2,r3) ;

唯一索引,建议以 udx_ 为开头,字母全部小写;如果是多值唯一索引,则命名方式类似 udx_multi_1 等。

例如:
alter table t1 add unique key udx_f1(r1);
或者
alter table t1 add key udx_multi_1(r1,r2,r3);

全文索引,建议以 ft_ 开头,字母全部小写,并且建议默认用 ngram 插件。

例如:alter table t1 add fulltext ft_r1(r1) with parser ngram;

前缀索引,建议以 idx_ 开头,以 _prefix 结尾。

例如: alter table t1 add key idx_r1_prefix(r1(10));

函数索引,建议以 idx_func_ 开头,字母全部小写。

例如: alter table t1 add key idx_func_r1((mod(r1,4)));

按需建立,定期清理,限制数量

为经常需要查询、排序、分组和联合操作的字段建立索引,不建立非必要的索引,定期检查和清理收益小的索引

如果某个字段经常用来做查询条件,那么该字段的查询速度会影响整个表的查询速度。

因此,为这样的字段建立索引,可以提高整个表的查询速度。

经常需要ORDER BY、GROUP BY、DISTINCT和UNION等操作的字段,排序操作会浪费很多时间。

如果为其建立索引,可以有效地避免排序操作。

表中的数据被大量更新,或者数据的使用方式被改变后,原有的一些索引可能不再需要。

比如表中已经有a的索引,现在要加(a,b)的索引,那么只需要原来的索引就可以删除了

数据库管理员应当定期找出这些索引,将它们删除,从而减少索引对更新操作的影响。

尽量选择区分度高的列作为索引

区分度的公式是count(distinct col)/count(*),表示字段不重复的比例,比例越大我们扫描的记录数越少,

唯一键的区分度是1,而一些状态、性别字段可能在大数据面前区分度就 是0,

那可能有人会问,这个比例有什么经验值吗?

使用场景不同,这个值也很难确定,一般需要join的字段我们都要求是0.1以上,即平均1条扫描10条 记录

当单个索引字段查询数据很多,区分度都不是很大时,则需要考虑建立联合索引来提高查询效率

注意:选择索引的最终目的是为了使查询的速度变快。

优先建立唯一性索引,唯一性索引的值是唯一的,可以更快速的通过该索引来确定某条记录。

例如,学生表中学号是具有唯一性的字段。为该字段建立唯一性索引可以很快的确定某个学生的信息。

如果使用姓名的话,可能存在同名现象,从而降低查询速度。

优化列以优化索引

列可以为整型就不选字符串,整型建立索引更方便

如果索引的值很长,那么查询的速度会受到影响。,能用 CHAR(10) 就不用 CHAR(80),例如,对一个CHAR(100)类型的字段进行全文检索需要的时间肯定要比对CHAR(10)类型的字段需要的时间要多。

前缀优于全文

能用前缀就不用全文,如果索引字段的值很长,最好使用值的前缀来索引。

例如,TEXT和BLOG类型的字段,进行全文检索会很浪费时间。

如果只检索字段的前面的若干个字符,这样可以提高检索速度。

索引重建

索引重建情形

  1. 表上频繁发生update,delete操作;
  2. 表上发生了alter table ..move操作(move操作导致了rowid变化)

索引重建判断

  1. 一般看索引是否倾斜的严重,是否浪费了空间,对索引进行结构分析

    analyze index index_name validate structure;
    
  2. 在相同的session中查询index_stats表

    select height,DEL_LF_ROWS/LF_ROWS from index_stats;
    

    当查询的height>=4(索引的深度,即从根到叶节点的高度)或DEL_LF_ROWS/LF_ROWS>0.2的情况下,就应该考虑重建该索引。

索引重建方法

rebuild是快速重建索引的一种有效的办法,因为它是一种使用现有索引项来重建新索引的方法。如果重建索引时有其他用户在对这个表操作,尽量使用带online参数来最大限度的减少索引重建时将会出现的任何加锁问题。由于新旧索引在建立时同时存在,因此,使用这种重建方法需要有额外的磁盘空间可供临时使用,当索引建完后把老索引删除,如果没有成功,也不会影响原来的索引。利用这种办法可以用来将一个索引移到新的表空间。

rebuild重建索引的过程:

  1. Rebuild以index fast full scan或table full scan方式(采用那种方式取决于cost)读取原索引中的数据来构建一个新的索引,重建过程中有排序操作,rebuild online执行表扫描获取数据,重建过程中有排序的操作;

  2. Rebuild会阻塞DML操作,rebuild online不会阻塞DML操作;

  3. rebuild online时系统会产生一个SYS_JOURNAL_xxx的IOT类型的系统临时日志表,所有rebuild online时索引的变化都记录在这个表中,当新的索引创建完成后,把这个表的记录维护到新的索引中去,然后drop掉旧的索引,rebuild online就完成了。

重建索引过程中的注意事项:

  1. 执行rebuild操作时,需要检查表空间是否足够;

  2. 虽然说rebuild online操作允许DML操作,但还是建议在业务不繁忙时间段进行;

  3. Rebuild操作会产生大量Redo Log;

  4. 模糊查询语句中如何使用索引?

    在MySQL中模糊查询 mobile like ‘%8765’,这种情况是不能使用 mobile 上的索引的,那么如果需要根据手机号码后四位进行模糊查询,可以用一下方法进行改造。

    我们可以加入冗余列(MySQL5.7之后加入了虚拟列,使用虚拟列更合适,思路相同),比如 mobile_reverse,内部存储为 mobile 的倒叙文本,如 mobile为17312345678,那么 mobile_reverse 存储 87654321371,为 mobile_reverse 列建立索引,查询中使用语句 mobile_reverse like reverse(’%5678’) 即可。

    reverse 是 MySQL 中的反转函数,这条语句相当于 mobile_reverse like ‘8765%’ ,这种语句是可以使用索引的。

    左模糊优先考虑走Elasticsearch

数据库整体优化

数据库优化

软优化

1.查询语句优化

一般查询

首先我们可以用EXPLAIN或DESCRIBE(简写:DESC)命令分析一条查询语句的执行信息。

EXPLAIN SELECT * FROM `user`

其中会显示索引和查询数据读取数据条数等信息。

子查询

在MySQL中,尽量使用JOIN来代替子查询。因为子查询需要嵌套查询,嵌套查询时会建立一张临时表,临时表的建立和删除都会有较大的系统开销,而连接查询不会创建临时表,因此效率比嵌套子查询高。

或者将逻辑移交给后端代码完成,压力给到后端。

慢查询

慢查询,查询语句优化的主要目标

关于order by和limit联用

并非是先order by全部排完序,然后limit

实际上进行了优化,使用堆的形式,对于非排序字段会造成顺序的不确定性,每次limit结果不太一样,最好的优化是order by category,id,附加通过主键排序,这样有一个唯一的顺序

https://blog.csdn.net/weixin_39604280/article/details/111689218

mysql数据库中允许select后含有非group by中的列

0000

2. 插入语句优化

针对插入,我们可以通过禁用索引、禁用检查等方式来提高插入速度,在插入之后再启用索引和检查。影响插入速度的主要是索引、唯一性校验、一次插入记录条数等。针对这些情况,可以分别进行优化。

对于MyISAM引擎的表,常见的优化方法如下:

  1. 禁用索引

    对于非空表,插入记录时,MySQL会根据表的索引对插入的记录建立索引。如果插入大量数据,建立索引会降低插入记录的速度。为了解决这种情况,可以在插入记录之前禁用索引,数据插入完毕后再开启索引。对于空表批量导入数据,则不需要进行此操作,因为MyISAM引擎的表是在导入数据之后才建立索引的。或者对于innodb而言,主键最好是自增的,b+tree插入更高效,uuid就不是,可以使用雪花算法

  2. 禁用唯一性检查

    插入数据时,MySQL会对插入的记录进行唯一性校验。这种唯一性校验也会降低插入记录的速度。为了降低这种情况对查询速度的影响,可以在插入记录之前禁用唯一性检查,等到记录插入完毕后再开启。

  3. 使用批量插入

    插入多条记录时,可以使用一条INSERT语句插入一条记录,也可以使用一条INSERT语句插入多条记录。使用一条INSERT语句插入多条记录的情形如下,而这种方式的插入速度更快。

    INSERT INTO fruits VALUES ('x1', '101', 'mongo2', '5.7'), ('x2', '101', 'mongo3', '5.7'), ('x3', '101', 'mongo4', '5.7');
    
  4. 使用LOAD DATA INFILE批量导入

    当需要批量导入数据时,如果能用LOAD DATA INFILE语句,就尽量使用。因为LOAD DATA INFILE语句导入数据的速度比INSERT语句快。

对于InnoDB引擎的表,常见的优化方法如下:

  1. 禁用唯一性检查

    插入数据之前执行set unique_checks=0来禁止对唯一索引的检查,数据导入完成之后再运行set unique_checks=1。这个和MyISAM引擎的使用方法一样。

  2. 禁用外键检查

    插入数据之前执行禁止对外键的检查,数据插入完成之后再恢复对外键的检查。

  3. 禁用自动提交

    插入数据之前禁止事务的自动提交,数据导入完成之后,执行恢复自动提交操作。

3.合理使用索引

索引是提高数据库查询速度最重要的方法之一,使用索引的三大注意事项包括:

4.重构表,分解表、中间表、冗余字段

分解表:对于字段较多的表,如果某些字段使用频率较低,此时应当将其分离出来从而形成新的表。

中间表:对于将大量连接查询的表可以创建中间表,从而减少在查询时造成的连接耗时。

冗余字段:类似于创建中间表,增加冗余也是为了减少连接查询。

5.分析表、检查表、优化表

分析表主要是分析表中关键字的分布;检查表主要是检查表中是否存在错误;优化表主要是消除删除或更新造成的表空间浪费。

分析表:使用 ANALYZE 关键字,如ANALYZE TABLE user

检查表:使用 CHECK关键字,如CHECK TABLE user [option]。 option 只对MyISAM有效。共五个参数值:

优化表: 使用OPTIMIZE关键字,如OPTIMIZE [LOCAL|NO_WRITE_TO_BINLOG] TABLE user;

LOCAL|NO_WRITE_TO_BINLOG都是表示不写入日志,优化表只对VARCHAR、BLOB和TEXT有效,通过OPTIMIZE TABLE语句可以消除文件碎片,在执行过程中会加上只读锁。

硬优化

基础硬件:CPU、内存、磁盘

数据库参数

优化数据库参数可以提高资源利用率,从而提高MySQL服务器性能。MySQL服务的配置参数都在my.cnf或my.ini,下面列出性能影响较大的几个参数:

架构调整

分库分表、读写分离

因为数据库压力过大,首先一个问题就是高峰期系统性能可能会降低,因为数据库负载过高对性能会有影响。

另外一个,压力过大把你的数据库给搞挂了怎么办?

所以此时你必须得对系统做分库分表+读写分离,也就是把一个库拆分为多个库,部署在多个数据库服务上,这时作为主库承载写入请求。然后每个主库都挂载至少一个从库,由从库来承载读请求。

缓存集群

由缓存集群承载大部分读操作

优化场景

表中包含几千万条数据

建议按照如下顺序进行优化:

  1. 优化SQL和索引;
  2. 增加缓存,如memcached、redis;
  3. 读写分离,可以采用主从复制,也可以采用主主复制;
  4. 使用MySQL自带的分区表,这对应用是透明的,无需改代码,但SQL语句是要针对分区表做优化的;
  5. 做垂直拆分,即根据模块的耦合度,将一个大的系统分为多个小的系统;
  6. 做水平拆分,要选择一个合理的sharding key,为了有好的查询效率,表结构也要改动,做一定的冗余,应用也要改,sql中尽量带sharding key,将数据定位到限定的表上去查,而不是扫描全部的表。

潜在慢查询拖慢调用链路

优化MySQL的慢查询,可以按照如下步骤进行:

开启慢查询日志:

MySQL中慢查询日志默认是关闭的,可以通过配置文件my.ini或者my.cnf中的log-slow-queries选项打开,也可以在MySQL服务启动的时候使用–log-slow-queries[=file_name]启动慢查询日志。

启动慢查询日志时,需要在my.ini或者my.cnf文件中配置long_query_time选项指定记录阈值,如果某条查询语句的查询时间超过了这个值,这个查询过程将被记录到慢查询日志文件中。

分析慢查询日志:

直接分析mysql慢查询日志,利用explain关键字可以模拟优化器执行SQL查询语句,来分析sql慢查询语句。

常见慢查询优化:

  1. 索引没起作用的情况

    • 在使用LIKE关键字进行查询的查询语句中,如果匹配字符串的第一个字符为“%”,索引不会起作用。只有“%”不在第一个位置,索引才会起作用。
    • MySQL可以为多个字段创建索引。一个索引可以包括16个字段。对于多列索引,只有查询条件中使用了这些字段中的第1个字段时索引才会被使用。
    • 查询语句的查询条件中只有OR关键字,且OR前后的两个条件中的列都是索引时,查询中才使用索引。否则,查询将不使用索引。
  2. 优化数据库结构

    • 对于字段比较多的表,如果有些字段的使用频率很低,可以将这些字段分离出来形成新表。因为当一个表的数据量很大时,会由于使用频率低的字段的存在而变慢。
    • 对于需要经常联合查询的表,可以建立中间表以提高查询效率。通过建立中间表,把需要经常联合查询的数据插入到中间表中,然后将原来的联合查询改为对中间表的查询,以此来提高查询效率。
  3. 分解关联查询

    很多高性能的应用都会对关联查询进行分解,就是可以对每一个表进行一次单表查询,然后将查询结果在应用程序中进行关联,很多场景下这样会更高效。

  4. 优化LIMIT分页

    当偏移量非常大的时候,例如可能是limit 10000,20这样的查询,这是mysql需要查询10020条然后只返回最后20条,前面的10000条记录都将被舍弃,这样的代价很高。优化此类查询的一个最简单的方法是尽可能的使用索引覆盖扫描,而不是查询所有的列。然后根据需要做一次关联操作再返回所需的列。对于偏移量很大的时候这样做的效率会得到很大提升。

批量插入优化

主键优化

页间是双向链表,页内是单链表(?连续内存??)

B+树结构不变,用几个页看大小??

页合并

truncate,清除标记位

磁盘碎片整理

就是oracle的高水位和行移动和表收缩,只是mysql自动优化了,oracle要手动

111

SQL 批量后台执行

  1. 准备批量 SQL 文件 batch.sql

  2. 准备 shell 文件 batch_execute.sh

    #!/bin/bash
    mysql -u root -p[password] --database=db < batch.sql
    # 注意-p一定要和密码连着,密码中有shell特殊字符时加上单引号
    
  3. 授权

    chmod u+x batch_execute.sh
    
  4. 使用 nohup 后台执行脚本

    nohup ./batch_execute.sh > batch_execute.log 2>&1 &
    

SQL 性能分析和优化

SELECT 语句 性能分析和优化

优化现有 SELECT 语句

优化现有 UPDATE 语句

111

111

-- 职员表
drop table sqlexercise.staff;
create table if not exists staff(
id int auto_increment,
sid int(4),
sname varchar(20),
sex numeric(1) not null comment '0:未知 1:男 2:女',
did numeric(4),
leader numeric(4),
hiredate datetime,
primary key(id),
UNIQUE  INDEX  uidx_sid(sid  ASC),
INDEX  idx_sname(sname  ASC),
INDEX  idx_did_leader_hiredate(did,leader,hiredate)
);

也可以手动创建索引

show index from staff

慢查询日志

慢日志中记录所有执行实践超过long_query_time(单位为秒)的所有SQL语句的日志。默认情况下,慢查询日志没有开启,慢查询日志long_query_time默认值为10s。

# m.cnf
-- 设置开启慢查询日志,0表示关闭,1表示开启,默认值为0
slow_query_log=1
-- 设置慢查询阈值,单位为秒,默认值为10
long_query_time=2

profile

-- 查看每一条SQL耗时
show profiles;
-- 查看指定query_id的SQL语句各个阶段耗时情况
show profile for query query_id;
-- 查看指定query_id的SQL语句CPU使用情况
show profile cpu for query query_id;

explain

EXPLAIN或DESC获取MySQL执行SELECT的执行计划。

explain select * from staff;

全索引扫描

explain select id, leader, hiredate from staff;
explain select did, leader, hiredate from staff order by did, leader;
explain select did, leader, hiredate from staff order by did desc, leader desc;

若一正一反就不可利用索引排序,索引的升降,必须要做file_sort

这个时候需要反向建立索引

create index idx_desc_did_leader on staff(did desc, leader asc);
explain select id, leader, hiredate from staff order by leader, hiredate;

order by优化

order by优化的关键点在于using index。前提是select的字段用到了且没有多余字段,否则filesort。

  1. 根据排序字段建立合适索引,多字段排序时,同样遵从最左前缀匹配
  2. 尽量使用覆盖索引,可以using index
  3. 多字段排序,注意各个字段的升序和降序,然后再创建复合索引
  4. 若不可避免出现filesort,大数据量排序时,有可能会数据大小会超出排序缓冲区大小,这时会在磁盘进行外部排序,所以可以适当增加排序缓冲区大小sort_buffer_size,其默认值为256k。

https://blog.csdn.net/weixin_39604280/article/details/111689218

由于order by不保证排序稳定性,所以同一条语句中相同排序字段的数据顺序不一定,这样会导致使用limit时,同一条orderby limit语句返回的数据不一样。解决办法是使用一个额外的字段参与排序保证字段顺序的一致,推荐使用主键。

select * from table order by category, id;

limit优化

limit问题在于limit 2000000, 10,此时需要获取前2000010条数据然后舍弃前2000000条数据,查询的代价非常大。

MySQL 的limit m n工作原理就是先读取前面m+n条记录,然后抛弃前m条,读后面n条想要的,所以m越大,偏移量越大,性能就越差

优化思路:一般分页查询时,通过创建覆盖索引能够比较好地提高性能,可以通过覆盖索引加子查询形式进行优化。

count优化

https://dev.mysql.com/doc/refman/8.4/en/aggregate-functions.html#function_count

count(expr)返回 SELECT 语句检索的行中 expr 非 NULL 值的数量。

count(field)返回所有数据中满足field列非NULL的总行数

count(1)返回所有数据中满足1非NULL的总行数,由于1总是不等于NULL,故count(1)相当于求全表数据量,实际上count(-1)、count(0)同理,而count(NULL)则为0

count(*)返回表中所有数据总行数

count(1)与count(*)两者功能相同,不同的是,count(1)基于原本函数的作用获取结果,而count(*)则是专用于求表总数据量的语法。

count(*)在实现上与存储引擎有关。

对于MyISAM,由于该存储引擎没有行锁,只有表锁,每次写数据库要对整个表加锁,故其将表中数据总量存储在了表的元数据中,对于MyISAM来说,count(*)效率很高,而count(1)反而需要全表查询。

对于InnoDB,由于该存储引擎中有行锁,其无法直接存储表数据总量,因此每次都需全表扫描,这时count(*)与count(1)并无本质区别,据官方文件所言,两者性能没有任何区别。

所以当我们使用InnoDB时,count(*)和count(1)均可,但由于count(*)为标准语法,建议使用count(*)。

重点:没有索引是表锁

因为name不是唯一的所以在修改的时候锁的是整表,而id是主键约束所以只是行锁

111

行列转换

行转列

以mysql为例,首先,假设我们有一张分数表(tb_score),表中的数据如下图:

可以看出,这里行转列是将原来的subject字段的多行内容选出来,作为结果集中的不同列,并根据userid进行分组显示对应的score。通常,我们有两种方式来实现这种转换。

  1. 使用 CASE…WHEN…THEN 语句实现行转列,参考如下代码:

    SELECT userid, 
    SUM(CASE `subject` WHEN '语文' THEN score ELSE 0 END) as '语文', 
    SUM(CASE `subject` WHEN '数学' THEN score ELSE 0 END) as '数学', 
    SUM(CASE `subject` WHEN '英语' THEN score ELSE 0 END) as '英语', 
    SUM(CASE `subject` WHEN '政治' THEN score ELSE 0 END) as '政治'  
    FROM tb_score  GROUP BY userid
    

    注意,SUM() 是为了能够使用GROUP BY根据userid进行分组,因为每一个userid对应的subject=”语文”的记录只有一条,所以SUM() 的值就等于对应那一条记录的score的值。假如userid =’001’ and subject=’语文’ 的记录有两条,则此时SUM() 的值将会是这两条记录的和,同理,使用Max()的值将会是这两条记录里面值最大的一个。但是正常情况下,一个user对应一个subject只有一个分数,因此可以使用SUM()、MAX()、MIN()、AVG()等聚合函数都可以达到行转列的效果。

  2. 使用 IF() 函数实现行转列,参考如下代码:

    SELECT userid, 
    SUM(IF(`subject`='语文',score,0)) as '语文', 
    SUM(IF(`subject`='数学',score,0)) as '数学', 
    SUM(IF(`subject`='英语',score,0)) as '英语', 
    SUM(IF(`subject`='政治',score,0)) as '政治'  
    FROM tb_score  GROUP BY userid
    

    注意,IF(subject=’语文’,score,0) 作为条件,即对所有subject=’语文’的记录的score字段进行SUM()、MAX()、MIN()、AVG()操作,如果score没有值则默认为0。

KILL LONG TRANSACTION

杀掉运行时间超过指定秒数、仍未提交的 InnoDB 事务

-- 杀掉运行时间超过指定秒数、仍未提交的 InnoDB 事务,防止行锁与 MVCC 历史堆积
-- 参数:p_max_idle 事务最大允许“存活”时间(单位:秒)
DELIMITER $$

CREATE PROCEDURE kill_idle_transactions(IN p_max_idle INT)
BEGIN
  /* 变量:保存需要杀掉的线程 ID */
  DECLARE v_id BIGINT;

  /* 循环控制标志:0-未结束 1-已无数据 */
  DECLARE done INT DEFAULT 0;

  /* 1. 找出所有处于 RUNNING 状态且“存活”时间超过阈值的 InnoDB 事务 */
  DECLARE c CURSOR FOR
    SELECT trx_mysql_thread_id
      FROM information_schema.innodb_trx
     WHERE trx_state = 'RUNNING'
       AND TIMESTAMPDIFF(SECOND, trx_started, NOW()) > p_max_idle;

  /* 2. 当游标无数据可抓时,将 done 设为 1,结束循环 */
  DECLARE CONTINUE HANDLER FOR NOT FOUND SET done = 1;

  /* 3. 打开游标 */
  OPEN c;

  /* 4. 循环逐条处理 */
  lp: LOOP
    FETCH c INTO v_id;          -- 取一条线程 ID
    IF done THEN LEAVE lp; END IF;  -- 没数据就跳出循环

    KILL v_id;                  -- 强制断开该线程,回滚其事务
  END LOOP lp;

  /* 5. 关闭游标,释放资源 */
  CLOSE c;
END$$

DELIMITER ;
-- 用法示例:杀掉“存活”超过 5 分钟(300 秒)的事务
CALL kill_idle_transactions(300);
  1. 需要拥有 PROCESS 和 CONNECTION_ADMIN(或 SUPER)权限。
  2. 被杀掉的连接会收到 Lost connection to MySQL server during query 错误,应用端需做好重试/容错。
  3. 建议先在业务低峰期试运行,观察对应用的影响;可配合 pt-kill 等工具做更复杂的策略。

show_table_bloat

估算 InnoDB 表膨胀率(Data_free / Data_length),方便挑表做 OPTIMIZE

-- 将语句结束符临时改为 $$,避免存储过程体中的分号被客户端提前解析
DELIMITER $$

-- 创建存储过程:显示指定数据库中每张表的“膨胀”情况
-- 参数:dbname 要检查的数据库名
CREATE PROCEDURE show_table_bloat(IN dbname VARCHAR(64))
BEGIN
  -- 查询 information_schema.tables 获取表空间使用统计
  SELECT
    table_name,                                          -- 表名
    ROUND(data_length / 1024 / 1024, 2) AS data_mb,      -- 数据部分大小(MB)
    ROUND(index_length / 1024 / 1024, 2) AS idx_mb,      -- 索引部分大小(MB)
    ROUND(data_free / 1024 / 1024, 2) AS free_mb,        -- 已分配但未使用的空间(MB)
    ROUND(data_free / data_length * 100, 2) AS bloat_pct -- “空洞”占数据量的百分比
  FROM
    information_schema.tables
  WHERE
    table_schema = dbname      -- 只查看指定库
    AND data_length > 0        -- 排除空表,防止除零
  ORDER BY
    bloat_pct DESC;            -- 按膨胀率从高到低排序,先看到最浪费的表
END$$

-- 恢复默认语句结束符
DELIMITER ;
-- 用法示例:查看 mydb 数据库中各表的膨胀情况
CALL show_table_bloat('db_service_xxx');
  1. data_freeMySQL 8.0 且 innodb_file_per_table=ON 时,反映的是 独立表空间文件(*.ibd) 里未使用的碎片;如果表仍在共享系统表空间(ibdata1),则 data_free 指的是整个系统表空间的空闲区域,会看上去“虚高”。
  2. 结果仅作参考,实际回收碎片需执行 OPTIMIZE TABLE <表名>(会锁表并重建表)或使用在线工具(如 pt-online-schema-change)。

clean_old_logs

分批删除数据

-- 将语句结束符临时改为 $$,防止过程体中的分号被客户端提前解析
DELIMITER $$

-- 创建存储过程:分批清理过期日志,避免一次性大批量删除造成主从延迟或锁表
-- 参数:
--   p_days  :保留日志的天数,早于此刻的日志将被删除
--   p_limit :每批删除的行数,控制事务大小
CREATE PROCEDURE clean_old_logs(IN p_days INT, IN p_limit INT)
BEGIN
    -- 重复执行直到没有满足条件的记录
    REPEAT
        -- 删除早于指定时间的过期日志,每次最多删 p_limit 行
        DELETE FROM log_table
        WHERE create_time < DATE_SUB(NOW(), INTERVAL p_days DAY)
        LIMIT p_limit;

        -- 短暂休眠 0.1 秒,给从库应用日志、缓解 IO 压力
        DO SLEEP(0.1);

    -- 当上一批删除行数为 0 时结束循环(ROW_COUNT() 返回上一次 DML 影响的行数)
    UNTIL ROW_COUNT() = 0 END REPEAT;
END$$

-- 恢复默认语句结束符
DELIMITER ;

-- 用法示例:删除 30 天以前的日志,每批最多删 1000 行
-- CALL clean_old_logs(30, 1000);

使用 pt-archive

copy_table_to_backup

快速备份一张表到备份库

-- 将语句结束符临时改为 $$,防止过程体中的分号被客户端提前解析
DELIMITER $$

-- 存储过程:快速对指定表做“快照”备份
-- 参数:
--   p_src_db  : 源库名
--   p_src_tbl : 源表名
-- 效果:
--   1. 在固定库 backup 下创建与源表结构完全一致的新表,表名带时间戳
--   2. 将源表当前所有数据一次性插入到新表(全表 COPY)
CREATE PROCEDURE copy_table_to_backup(IN p_src_db VARCHAR(64), IN p_src_tbl VARCHAR(64))
BEGIN
    -- 1. 生成备份表名,格式:源表名_YYYYMMDD_HHMMSS
    SET @bak_tbl = CONCAT(p_src_tbl, '_', DATE_FORMAT(NOW(), '%Y%m%d_%H%i%s'));

    -- 2. 拼 CREATE TABLE ... LIKE 语句,实现“零数据”建表并保留索引、默认值等定义
    SET @sql = CONCAT('CREATE TABLE backup.', @bak_tbl, ' LIKE ', p_src_db, '.', p_src_tbl);
    PREPARE stmt FROM @sql; EXECUTE stmt; DEALLOCATE PREPARE stmt;

    -- 3. 拼 INSERT ... SELECT 语句,把源表当前数据整体写入备份表
    SET @sql = CONCAT('INSERT INTO backup.', @bak_tbl, ' SELECT * FROM ', p_src_db, '.', p_src_tbl);
    PREPARE stmt FROM @sql; EXECUTE stmt; DEALLOCATE PREPARE stmt;
END$$

-- 恢复默认语句结束符
DELIMITER ;

使用注意

  1. 两段过程都是整表 COPY,大表会锁表(5.7)或产生大量 redo/undo(8.0),不适合高峰期。
  2. 目标库(backup / archive)必须事先存在,否则 CREATE TABLE 报错。
  3. 不会自动清理旧备份,需额外脚本定期 DROPRENAME
  4. 若源表有触发器、外键,记得用 CREATE TABLE ... LIKE 后的二次脚本再处理。

show_disk_usage

  1. 查看所有库、表、索引总体磁盘占用
-- 临时更换语句结束符,防止过程体中的分号被提前提交
DELIMITER $$

/*
  存储过程:show_disk_usage
  功能     :查看实例中每个库的数据+索引占用磁盘空间(近似值)
  返回列   :
    db_name   : 数据库名
    total_mb  : 该库数据+索引总大小(MB)
    data_mb   : 该库数据部分大小(MB)
    idx_mb    : 该库索引部分大小(MB)
    tables    : 该库下表的数量
  排序     :按总大小降序,最占空间的库排在最前
  注意     :
  1. 仅统计 InnoDB/MyISAM 等 engine 实际写入 information_schema 的值;
  2. 对于共享表空间(ibdata1),data_free 不能按库拆分,因此这里只反映“逻辑”大小;
  3. 需要对该实例有 information_schema 查询权限。
*/
CREATE PROCEDURE show_disk_usage()
BEGIN
  SELECT table_schema  AS db_name,                                    -- 库名
         ROUND(SUM(data_length+index_length)/1024/1024, 2) AS total_mb,  -- 数据+索引总和
         ROUND(SUM(data_length)/1024/1024, 2)              AS data_mb,   -- 仅数据
         ROUND(SUM(index_length)/1024/1024, 2)             AS idx_mb,    -- 仅索引
         COUNT(*)                                          AS tables     -- 表个数
  FROM information_schema.tables
  GROUP BY table_schema
  ORDER BY total_mb DESC;   -- 谁最占空间排最前
END$$

DELIMITER ;

-- 调用示例:查看整实例各库磁盘使用
CALL show_disk_usage();

analyze_all_tables

批量给所有表 ANALYZE TABLE(更新统计信息)

DELIMITER $$
CREATE PROCEDURE analyze_all_tables(IN p_schema VARCHAR(64))
BEGIN
  DECLARE done INT DEFAULT 0;
  DECLARE v_table VARCHAR(64);
  DECLARE cur CURSOR FOR
    SELECT table_name
    FROM information_schema.tables
    WHERE table_schema = p_schema AND table_type = 'BASE TABLE';
  DECLARE CONTINUE HANDLER FOR NOT FOUND SET done = 1;

  OPEN cur;
  loop_tables: LOOP
    FETCH cur INTO v_table;
    IF done THEN LEAVE loop_tables; END IF;
    SET @sql = CONCAT('ANALYZE TABLE `', p_schema, '`.`', v_table, '`');
    PREPARE stmt FROM @sql;
    EXECUTE stmt;
    DEALLOCATE PREPARE stmt;
  END LOOP;
  CLOSE cur;
END$$
DELIMITER ;
CALL analyze_all_tables('your_db');

top_processlist

生成「当前连接」资源消耗总览(类似 mytop)

DELIMITER $$
CREATE PROCEDURE top_processlist()
BEGIN
  SELECT id,
         user,
         host,
         db,
         command,
         time AS seconds,
         state,
         LEFT(info, 100) AS sql_preview,
         current_memory AS mem_bytes
  FROM performance_schema.threads t
  JOIN performance_schema.events_statements_current s
    ON t.thread_id = s.thread_id
  ORDER BY time DESC;
END$$
DELIMITER ;
CALL top_processlist();

schema_to_md

把全库表结构一键导出成 Markdown 文档

DELIMITER $$
CREATE PROCEDURE schema_to_md(IN p_db VARCHAR(64))
BEGIN
  SELECT CONCAT('### `', table_name, '`',
                '\n| 字段 | 类型 | 可空 | 默认值 | 备注 |\n| --- | --- | --- | --- | --- |')
  UNION ALL
  SELECT CONCAT('| ', column_name, ' | ', column_type, ' | ',
                is_nullable, ' | ', IFNULL(column_default,''), ' | ', column_comment, ' |')
  FROM information_schema.columns
  WHERE table_schema = p_db
  ORDER BY table_name, ordinal_position;
END$$
DELIMITER ;
CALL schema_to_md('ecommerce');

表IO读写分析

SELECT 
    OBJECT_SCHEMA                          AS table_schema,
    OBJECT_NAME                            AS table_name,
    
    /* 细化读写 */
    COUNT_READ                             AS read_ops,
    COUNT_WRITE                            AS write_ops,
    SUM_TIMER_READ  / 1e12                 AS read_latency_s,
    SUM_TIMER_WRITE / 1e12                 AS write_latency_s,
    SUM_TIMER_WAIT  / 1e12                 AS total_latency_s,
    
    /* 百分比:快速定位热点 */
    ROUND(100 * SUM_TIMER_WAIT / 
          (SELECT SUM(SUM_TIMER_WAIT) FROM performance_schema.table_io_waits_summary_by_table), 2) 
    AS pct_io_time,
    
    /* 平均每次 I/O 延迟,单位毫秒 */
    ROUND((SUM_TIMER_WAIT / (COUNT_READ + COUNT_WRITE)) / 1e6, 3) AS avg_io_ms
FROM 
    performance_schema.table_io_waits_summary_by_table
WHERE 
    OBJECT_SCHEMA NOT IN ('mysql', 'performance_schema', 'information_schema', 'sys')
ORDER BY 
    total_latency_s DESC;

sys.statement_performance_analyzer

-- 把 performance_schema 里当前时刻的 events_statements_summary_by_digest 表内容复制到 sys.tmp_digests(临时表)里
CALL sys.statement_performance_analyzer('snapshot', NULL, NULL);
-- 最慢的 Top10
CALL sys.statement_performance_analyzer(
        'overall',                -- 动作
        NULL,                     -- in_table(或'NOW()',或自己保存的快照表)
        'with_runtimes_in_95th_percentile');  -- 过滤条件

-- I/O 最多的 Top10
CALL sys.statement_performance_analyzer(
        'overall',
        NULL,
        'with_full_table_scans');
SELECT * FROM information_schema.innodb_trx;-- 当前运行的事务
SELECT * FROM PERFORMANCE_SCHEMA.data_locks;-- 当前持有的锁
SELECT * FROM PERFORMANCE_SCHEMA.data_lock_waits;-- 锁等待关系
  
  
-- 事务中锁等待关系(包含等待者和阻塞者)
SELECT
  r.trx_id AS waiting_trx_id,
  r.trx_mysql_thread_id AS waiting_thread,
  r.trx_query AS waiting_query,
  b.trx_id AS blocking_trx_id,
  b.trx_mysql_thread_id AS blocking_thread,
  b.trx_query AS blocking_query 
FROM
  PERFORMANCE_SCHEMA.data_lock_waits w
  JOIN information_schema.innodb_trx b ON b.trx_id = w.blocking_engine_transaction_id
  JOIN information_schema.innodb_trx r ON r.trx_id = w.requesting_engine_transaction_id;
-- 索引优化,快速发现问题索引,删除前务必确认 **没有语句显式 FORCE/USE INDEX

-- 1. 查看重复/冗余索引
SELECT * FROM sys.schema_redundant_indexes;

-- 2. 查看从未被用过的索引
SELECT * FROM sys.schema_unused_indexes;
CALL sys.diagnostics ( 1, 1, 'current' );
SHOW PROCEDURE STATUS;
SHOW FUNCTION STATUS;


SELECT * FROM information_schema.routines




-- 查看当前锁等待
SELECT * FROM sys.innodb_lock_waits\G

-- 查看最慢SQL(总耗时)
SELECT * FROM sys.statement_analysis
ORDER BY total_latency DESC
LIMIT 10;

-- 查看冗余索引
SELECT * FROM sys.schema_redundant_indexes;

-- 查看未使用索引
SELECT * FROM sys.schema_unused_indexes;

将一张表的部分数据更新到另一张表,该如何操作呢?

参考答案

可以采用关联更新的方式,将一张表的部分数据,更新到另一张表内。参考如下代码:

update b set b.col=a.col from a,b where a.id=b.id;
update b set col=a.col from b inner join a on a.id=b.id;
update b set b.col=a.col from b left Join a on b.id = a.id;
### 补数据


# 更新数据
UPDATE `gatherone_oms`.`tb_alm_pitch_record` AS t0
JOIN (
  SELECT
    t1.`guidance_id`,
    t2.`account_id`
  FROM
    (
      SELECT DISTINCT
        `guidance_id` COLLATE utf8mb4_0900_ai_ci AS `guidance_id`
      FROM
        `gatherone_oms`.`tb_alm_pitch_record`
    ) t1
    LEFT JOIN (
      select
        `account_id`,
        `guidance_id`
      from
        `gatherone_plateform_data`.`tb_meta_guidance`
      GROUP BY
        `account_id`,
        `guidance_id`
    ) t2 ON t1.`guidance_id` = t2.`guidance_id`
) AS t3 ON t3.`guidance_id` = t0.`guidance_id`
SET
  t0.`account_id` = t3.`account_id`;

导出所有创建索引的语句

 SELECT CONCAT('ALTER TABLE `', TABLE_NAME, '` ', 'MODIFY COLUMN `', COLUMN_NAME, '` ',
               IF(UPPER(DATA_TYPE) = 'INT', REPLACE(SUBSTRING_INDEX(UPPER(COLUMN_TYPE), ')', 1), 'INT', 'INTEGER'),
                  UPPER(COLUMN_TYPE)), ') UNSIGNED NOT NULL AUTO_INCREMENT;')
 FROM information_schema.COLUMNS
 WHERE TABLE_SCHEMA = 'rz_gs'
   AND EXTRA = UPPER('AUTO_INCREMENT')
 ORDER BY TABLE_NAME ASC;