1 索引、优化

1.1 [0]优化:5次

00.总结
    覆盖索引:通过创建覆盖索引,可以显著提高查询性能
    数据量控制:通过条件过滤和分表策略,减少查询的数据量
    索引优化:根据查询特点选择合适的索引策略,包括普通索引、覆盖索引和强制索引
    查询优化:结合业务场景,选择合适的查询优化策略,如小表驱动大表

01.数据生成
    a.表结构
        CREATE TABLE `orders` (
          `order_id` int NOT NULL AUTO_INCREMENT,
          `user_id` int DEFAULT NULL,
          `order_date` date NOT NULL,
          `total_amount` decimal(10,2) NOT NULL,
          PRIMARY KEY (`order_id`),
          KEY `idx_user_id` (`user_id`) USING BTREE,
          KEY `idx_user_amount` (`user_id`,`total_amount`) USING BTREE
        ) ENGINE=InnoDB AUTO_INCREMENT=1 DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_general_ci;
        -----------------------------------------------------------------------------------------------------
        CREATE TABLE `users` (
          `user_id` int NOT NULL AUTO_INCREMENT,
          `username` varchar(50) COLLATE utf8mb4_general_ci NOT NULL,
          `email` varchar(100) COLLATE utf8mb4_general_ci NOT NULL,
          `created_at` timestamp NULL DEFAULT CURRENT_TIMESTAMP,
          PRIMARY KEY (`user_id`),
          KEY `idx_user_id` (`user_id`) USING BTREE
        ) ENGINE=InnoDB AUTO_INCREMENT=1 DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_general_ci;
    b.数据生成存储过程
        a.用户数据生成
            CREATE DEFINER=`root`@`localhost` PROCEDURE `create_users`()
            BEGIN
                DECLARE i INT DEFAULT 0;
                DECLARE total_users INT DEFAULT 1000;
                DECLARE rnd_username VARCHAR(50);
                DECLARE rnd_email VARCHAR(100);

                WHILE i < total_users DO
                    SET rnd_username = CONCAT('User', FLOOR(1 + RAND() * 10000000));
                    SET rnd_email = CONCAT(rnd_username, '@example.com');
                    INSERT INTO users (username, email) VALUES (rnd_username, rnd_email);
                    SET i = i + 1;
                END WHILE;
            END
        b.订单数据生成
            CREATE DEFINER=`root`@`localhost` PROCEDURE `generate_orders`()
            BEGIN
                DECLARE i INT DEFAULT 0;
                DECLARE total_users INT DEFAULT 1000;
                DECLARE total_orders_per_user INT DEFAULT 1000;
                DECLARE rnd_user_id INT;
                DECLARE rnd_order_date DATE;
                DECLARE rnd_total_amount DECIMAL(10, 2);
                DECLARE j INT DEFAULT 0;

                WHILE i < total_users DO
                    SELECT user_id INTO rnd_user_id FROM users LIMIT i, 1;
                    WHILE j < total_orders_per_user DO
                        SET rnd_order_date = DATE_ADD('2020-01-01', INTERVAL FLOOR(RAND() * 1096) DAY);
                        SET rnd_total_amount = ROUND(RAND() * 1000, 2);
                        INSERT INTO orders (user_id, order_date, total_amount) VALUES (rnd_user_id, rnd_order_date, rnd_total_amount);
                        SET j = j + 1;
                    END WHILE;
                    SET j = 0;
                    SET i = i + 1;
                END WHILE;
            END

02.查询优化
    a.原始查询
        a.代码
            SELECT a.*, sum(b.total_amount) as total
            FROM users a
            LEFT JOIN orders b ON a.user_id = b.user_id
            GROUP BY a.user_id;
        b.问题
            在没有索引的情况下,查询需要全表扫描,耗时较长
    b.优化策略
        a.普通索引
            为查询条件创建索引
            -------------------------------------------------------------------------------------------------
            CREATE INDEX idx_orders_user_id ON orders (user_id);
            CREATE INDEX idx_orders_total_amount ON orders (total_amount);
            CREATE INDEX idx_users_user_id ON users (user_id);
            -------------------------------------------------------------------------------------------------
            结果:查询速度未显著提升,可能由于回表机制导致
        b.覆盖索引
            创建包含所有查询列的索引
            -------------------------------------------------------------------------------------------------
            CREATE INDEX idx_orders_user_id_total_amount ON orders (user_id, total_amount);
            -------------------------------------------------------------------------------------------------
            结果:查询时间显著降低,因为避免了回表操作
        c.减少数据量
            通过条件过滤减少查询的数据量
            -------------------------------------------------------------------------------------------------
            SELECT a.*, sum(b.total_amount) as total
            FROM users a
            LEFT JOIN orders b ON a.user_id = b.user_id
            WHERE a.user_id > 1033
            GROUP BY a.user_id;
            -------------------------------------------------------------------------------------------------
            结果:进一步降低查询时间
        d.小表驱动大表
            尝试使用小表驱动大表的策略
            -------------------------------------------------------------------------------------------------
            SELECT a.*, sum(b.total_amount) as total
            FROM users a
            LEFT JOIN (SELECT user_id, total_amount FROM orders WHERE user_id > 1033) b
            ON a.user_id = b.user_id
            GROUP BY a.user_id;
            -------------------------------------------------------------------------------------------------
            结果:在此场景中效果不明显,但在特定业务场景中可能有效
        e.强制索引
            在特定情况下使用强制索引
            -------------------------------------------------------------------------------------------------
            SELECT a.*, sum(b.total_amount) as total
            FROM users a
            LEFT JOIN orders b FORCE INDEX (idx_orders_user_id_total_amount)
            ON a.user_id = b.user_id
            WHERE b.user_id IN (1033, 1034, 1035, 1036, 1037, 1038)
            GROUP BY a.user_id;
            -------------------------------------------------------------------------------------------------
            结果:在某些复杂查询中可能有效

03.总结
    a.覆盖索引
        通过创建覆盖索引,可以显著提高查询性能
    b.数据量控制
        通过条件过滤和分表策略,减少查询的数据量
    c.索引优化
        根据查询特点选择合适的索引策略,包括普通索引、覆盖索引和强制索引
    d.查询优化
        结合业务场景,选择合适的查询优化策略,如小表驱动大表

1.2 [1]汇总:6种

00.汇总
    a.分类标准
        a.按照 功能 分类
            普通索引
            唯一索引
            主键索引
            复合索引
            外键索引
            全文索引
        b.按照 数据结构 分类
            B+树索引
            哈希索引
        c.按照 存储位置 分类
            聚簇索引
            非聚簇索引
    b.图示
        索引类型   特点                                       作用                                    适用场景
        普通索引   允许重复,基本的查询优化工具                 提高查询效率,支持范围查询、排序         高频查询字段,如状态、分类、外键
        唯一索引   保证字段值唯一                              优化查询,同时提供数据唯一性约束         用户名、邮箱、身份证号等需要唯一性的字段
        主键索引   表的唯一标识,自动创建聚簇索引               快速定位记录,作为其他表的外键引用       表的主键字段(如 id)
        复合索引   多字段联合索引,遵循最左前缀匹配原则         优化多条件查询,减少单独索引的存储开销    多条件联合查询场景,如 (字段A, 字段B)
        全文索引   适用于全文匹配,支持非结构化文本的搜索优化    提供文本字段的高效关键词查询            文本搜索场景,如文章内容或商品描述
        空间索引   适用于地理空间数据类型查询                   提供地理数据(坐标、区域)的快速定位     地图服务或GIS应用
    c.总结
        主键索引:保证字段唯一并作为表的主标识,不能重复
        唯一索引:确保字段值唯一,允许多个唯一索引
        普通索引:提高查询性能,不要求字段值唯一
        复合索引:适用于多个字段的联合查询,提高查询效率

01.按照 功能 分类
    a.分类
        普通索引:最基本的索引,没有任何约束
        唯一索引:与普通索引类似,但具有唯一性约束
        主键索引:特殊的唯一索引,不允许有空值
        复合索引:将多个列组合在一起创建索引,可以覆盖多个列
        全文索引:MySQL 自带的全文索引只能用于 InnoDB、MyISAM ,并且只能对英文进行全文检索,一般使用全文索引引擎
        空间索引
        -----------------------------------------------------------------------------------------------------
        外键索引:只有InnoDB类型的表才可以使用外键索引,保证数据的一致性、完整性和实现级联操作
    b.对比
        对比点         主键索引                    唯一索引                     普通索引
        是否唯一       是                          是                           否
        允许 NULL      否                          是                           是
        是否自动创建   是                          否                           否
        存储方式       InnoDB 默认使用聚簇索引      普通索引或辅助索引            普通索引
        适用场景       标识记录的唯一字段,如 id    唯一但非主键字段,如 邮箱      查询频繁的非唯一字段

02.按照 数据结构 分类
    a.分类
        a.分类
            B+树索引
            哈希索引
        b.对比
            索引类型     数据结构        备注
            普通索引     B+树            默认实现
            唯一索引     B+树            保证唯一性
            主键索引     B+树            唯一且非空
            复合索引     B+树            适合多条件查询
            全文索引     倒排索引        支持全文检索
            空间索引     R 树 / B+树     取决于存储引擎(MyISAM / InnoDB)
        c.总结
            在 MySQL 中,InnoDB 是默认存储引擎,绝大多数索引(普通索引、唯一索引、主键索引、复合索引)均基于 B+树
            哈希索引仅适用于特定场景(如 MEMORY 存储引擎或 InnoDB 自适应哈希索引)
    b.B+树索引:B+树是一种平衡树数据结构,支持范围查询和排序操作
        普通索引:使用 B+树存储,适合快速查找单个值或范围查询
        唯一索引:基于 B+树,与普通索引类似,但保证索引列中的值唯一
        主键索引:特殊的唯一索引,基于 B+树,索引列值必须唯一且非空
        复合索引:基于 B+树,包含多个列,可以优化多条件查询
        部分空间索引:如果在 InnoDB 引擎中存储支持 ST_Geometry 类型的列,这种空间索引会退化为普通的 B+树索引
    c.哈希索引
        哈希索引(特殊情况):MySQL 中的 MEMORY 存储引擎默认使用哈希索引
                            哈希索引基于哈希表实现,适合等值查询,但不支持范围查询
                            InnoDB 引擎的自适应哈希索引(Adaptive Hash Index,AHI)在频繁查询某些数据时,会动态生成哈希索引
                            普通索引和唯一索引在部分存储引擎中可能以哈希形式存储
    d.其他索引
        全文索引:全文索引的实现与 B+树无关,它基于倒排索引(Inverted Index)。用于全文检索操作,适合处理大文本数据
        空间索引:MyISAM 存储引擎中的空间索引基于R 树数据结构,适用于存储和查询地理空间数据。如果使用 InnoDB 引擎,空间索引会被降级为 B+树索引

03.按照 索引的存储位置 分类
    a.分类
        a.聚簇索引(Clustered Index)
            数据和索引存储在同一个文件中,索引叶子节点保存了数据行
            一个表只能有一个聚簇索引,因为数据行只能有一种物理存储顺序
            通常,表的主键索引是聚簇索引
        b.非聚簇索引(Non-Clustered Index)
            索引和数据存储分离,索引的叶子节点保存的是数据行的指针(通常是主键或行的地址)
            非聚簇索引可以有多个,适用于需要快速查找数据的场景
        c.对比
            总结表
            索引类型    存储类型           说明
            普通索引    非聚簇索引         数据和索引分离,叶子节点存储指针
            唯一索引    非聚簇索引         数据和索引分离,强制值唯一
            主键索引    聚簇索引(默认)    数据和索引存储在一起,叶子节点存储行数据
            复合索引    聚簇/非聚簇索引    包含主键列为聚簇索引,否则是非聚簇索引
            全文索引    非聚簇索引         专用于全文搜索,叶子节点存储文本的倒排索引
            空间索引    非聚簇索引         专用于地理数据,支持 GIS 操作
        d.总结
            InnoDB 存储引擎下,主键索引默认是聚簇索引
            MyISAM 存储引擎下,所有索引都是非聚簇索引,因为它将数据和索引完全分开存储
    b.聚簇索引
        普通索引:它独立于主键索引,与数据分开存储,叶子节点存储的是指向实际数据行的地址
        主键索引:如果表没有主键或合适的唯一索引,InnoDB 会自动生成一个隐藏的聚簇索引
        复合索引:复合索引可以是聚簇索引或非聚簇索引,具体取决于是否是主键。如果复合索引包含主键列,则是聚簇索引;否则是非聚簇索引
    c.非聚簇索引
        唯一索引:类似于普通索引,但强制列值必须唯一
        复合索引:复合索引可以是聚簇索引或非聚簇索引,具体取决于是否是主键。如果复合索引包含主键列,则是聚簇索引;否则是非聚簇索引
        全文索引:用于全文搜索,适用于大文本字段(CHAR, TEXT 等),它的存储方式和查询优化机制不同于传统索引
        空间索引:专门为存储和检索地理数据设计

1.3 [1]示例:6种

00.总结
    主键索引:保证字段唯一并作为表的主标识,不能重复
    唯一索引:确保字段值唯一,允许多个唯一索引
    普通索引:提高查询性能,不要求字段值唯一
    复合索引:适用于多个字段的联合查询,提高查询效率

01.综合示例
    a.假设我们有一个用户表 users,表中包括以下字段
        id(主键)
        username(唯一)
        email(唯一)
        department_id(普通索引)
        status(普通索引)
        created_at(用于复合索引)
    b.示例
        CREATE TABLE users (
            id BIGINT AUTO_INCREMENT PRIMARY KEY,  -- 主键索引
            username VARCHAR(50) NOT NULL,          -- 唯一索引
            email VARCHAR(100) NOT NULL,            -- 唯一索引
            department_id INT,                      -- 普通索引
            status VARCHAR(10),                     -- 普通索引
            created_at DATETIME,                    -- 用于复合索引
            updated_at DATETIME,
            UNIQUE (username),                      -- 唯一索引
            UNIQUE (email),                         -- 唯一索引
            INDEX idx_department_status (department_id, status), -- 复合索引
            INDEX idx_status (status)               -- 普通索引
        ) ENGINE=InnoDB DEFAULT CHARSET=utf8;

02.唯一索引
    a.表结构
        CREATE TABLE sys_user (
            id VARCHAR(32) PRIMARY KEY,
            username VARCHAR(100) UNIQUE COMMENT '用户名,必须唯一',
            email VARCHAR(100) UNIQUE COMMENT '邮箱,必须唯一',
            phone_number VARCHAR(15) UNIQUE COMMENT '手机号,必须唯一'
        );
    b.效果
        username、email 和 phone_number 字段均被定义为唯一索引,确保各自值在表中唯一,避免重复用户

03.普通索引
    a.表结构
        INDEX idx_department_status (department_id, status)
        INDEX idx_status (status)
    b.效果
        在本例中,department_id 和 status 字段分别有单独的普通索引
        以及一个复合索引 idx_department_status,用于加速基于部门和状态的查询

04.复合索引
    a.假设有一个员工信息表,表结构如下
        CREATE TABLE sys_user (
            id VARCHAR(32) PRIMARY KEY,
            username VARCHAR(100),
            department_id INT,
            age INT,
            email VARCHAR(100)
        );
    b.常用查询包括:
        按 username 和 department_id 查询
        按 username 查询
        按 username 和 department_id 排序
    c.可创建如下复合索引
        CREATE INDEX idx_user_name_department ON sys_user (username, department_id)
    d.效果
        查询 username 和 department_id 条件的性能提升
        查询 username 条件也能使用索引
        查询仅 department_id 条件则无法使用索引

05.全文索引
    a.假设有一个文章表,包含文章标题和内容字段:
        CREATE TABLE articles (
            id INT PRIMARY KEY,
            title VARCHAR(200),
            content TEXT
        );
    b.为 content 字段创建全文索引
        CREATE FULLTEXT INDEX idx_article_content ON articles(content);
    c.使用自然语言模式查询
        SELECT * FROM articles WHERE MATCH(content) AGAINST('database');
    d.使用布尔模式查询
        SELECT * FROM articles WHERE MATCH(content) AGAINST('+MySQL -PostgreSQL' IN BOOLEAN MODE);

06.空间索引
    a.假设有一个位置表,存储地理坐标点
        CREATE TABLE locations (
            id INT PRIMARY KEY,
            location POINT NOT NULL,
            description VARCHAR(200),
            SPATIAL INDEX(location)
        ) ENGINE=InnoDB;
    b.查询指定范围内的点
        SELECT * FROM locations
        WHERE ST_Within(location, ST_GeomFromText('POLYGON((...))'));
    c.计算与某点的距离
        SELECT * FROM locations
        WHERE ST_Distance(location, ST_GeomFromText('POINT(120 30)')) < 5;
    d.判断点是否在多边形内
        SELECT * FROM locations
        WHERE ST_Contains(ST_GeomFromText('POLYGON((...))'), location);

1.4 [1]组合:搭配、冲突

01.索引之间的搭配使用
    a.主键索引
        每个表只能有一个主键索引,并且主键索引字段的值必须唯一
        主键索引通常是根据表的唯一标识字段(如 id)来创建的
        主键索引通常不与其他唯一索引发生冲突,因为它本身已经是唯一的
    b.唯一索引
        一个表中可以有多个唯一索引
        唯一索引字段值必须唯一,通常用于确保 email 或 username 等字段的唯一性
        唯一索引不与主键索引冲突,但是可以与其他唯一索引共存
    c.普通索引
        普通索引适用于优化查询性能,特别是对于高频查询的字段
        一个表可以有多个普通索引
        普通索引不会与主键或唯一索引冲突,可以和它们一起使用
    d.复合索引
        复合索引通常用于查询中涉及多个字段的情况。例如,department_id 和 status 常常一起用作查询条件,因此使用复合索引会提高查询效率
        复合索引与其他普通索引、唯一索引等不会冲突,但是它会受到字段顺序的影响
        如果查询中只涉及复合索引的前几个字段,复合索引依然会生效。如果查询中没有涉及复合索引的前缀字段,索引将不会被利用

02.哪些索引之间可能会发生冲突
    a.主键索引与唯一索引
        主键索引和唯一索引都要求字段值唯一,但主键索引是强制性的且不能为 NULL,而唯一索引允许 NULL 值
        两个索引可以共存,但主键索引在逻辑上优先
    b.复合索引与单字段索引
        复合索引可以提高多个字段联合查询的效率,但如果字段的查询顺序与复合索引字段顺序不匹配
        复合索引可能不会被有效利用。对于单字段查询,复合索引可能无法完全代替普通索引
        -----------------------------------------------------------------------------------------------------
        例如:查询 department_id 和 status 字段的复合索引会优化 WHERE department_id = ? AND status = ? 的查询
        但如果查询只使用 status 字段,复合索引可能不会生效。在这种情况下,最好为 status 单独建立普通索引

1.5 [2]主键索引

00.主键索引 (Primary Key)
    a.定义
        PRIMARY KEY 是一种特殊的唯一索引,每个表只能有一个主键索引
        主键索引会自动创建唯一索引,并且会自动加速对该字段的查询
    b.特点
        主键字段值必须唯一且不能为空
        每个表只能有一个主键索引
    c.示例
        PRIMARY KEY (id)
        在本例中,id 字段是主键
    d.作用
        确保记录的唯一性
        用于快速定位记录,并作为其他索引的引用(如外键)
    e.适用场景
        唯一标识记录的字段,例如 id(通常为表的主键)
    f.主键索引与其他索引的对比
        对比点          主键索引                    唯一索引                    普通索引
        是否唯一        是                          是                         否
        允许 NULL       否                          是                         是
        是否自动创建    是                          否                          否
        存储方式        InnoDB 默认使用聚簇索引      普通索引或辅助索引           普通索引
        适用场景        标识记录的唯一字段,如 id    唯一但非主键字段,如 邮箱     查询频繁的非唯一字段

01.作用
    a.唯一标识每条记录
        主键索引保证主键字段的值是唯一的,不允许重复,也不允许为 NULL。它是表中记录的唯一标识
        -----------------------------------------------------------------------------------------------------
        CREATE TABLE sys_user (
            id VARCHAR(32) PRIMARY KEY,
            username VARCHAR(100)
        );
        在此表中,每条记录的 id 字段值必须唯一
    b.快速定位记录
        主键索引提供了高效的数据检索能力,当通过主键查询时,数据库可以直接定位到所需数据,而无需扫描全表
        -----------------------------------------------------------------------------------------------------
        SELECT * FROM sys_user WHERE id = '123';
        主键索引加速了查询过程
    c.作为外键的引用
        主键通常被其他表用作外键,建立数据之间的逻辑关联。例如:
        CREATE TABLE sys_order (
            order_id VARCHAR(32) PRIMARY KEY,
            user_id VARCHAR(32),
            FOREIGN KEY (user_id) REFERENCES sys_user(id)
        );
    d.支持数据完整性约束
        主键索引可以防止重复数据插入,从而保证数据的完整性

02.特点
    a.唯一性
        主键索引强制字段值唯一,不允许重复
    b.自动创建索引
        定义主键时,数据库会自动为主键字段创建一个唯一索引,称为主键索引
    c.默认使用聚簇索引(InnoDB 引擎)
        在 InnoDB 存储引擎中,主键索引默认采用 聚簇索引 结构,数据存储按主键顺序排列
        如果没有定义主键,InnoDB 会选择一个唯一的非空字段作为主键;若没有,数据库会自动创建一个隐藏的主键
    d.占用存储空间
        主键索引会占用额外的存储空间,但其性能提升通常能够弥补这点
    e.无法更新字段为空
        主键字段不能为空(NOT NULL),更新为 NULL 会导致约束错误

03.优点
    a.查询性能最优
        主键索引因其唯一性和聚簇特性,使得基于主键的查询效率最高
    b.保证数据完整性
        主键索引防止重复记录,并确保字段非空,是数据库强一致性的重要保障
    c.排序优化
        聚簇索引按主键顺序存储数据,某些情况下可以避免排序操作

04.缺点
    a.字段不可重复或为空
        如果业务允许字段值重复或为空,主键索引就不适用
    b.频繁更新或删除主键字段影响性能
        主键字段更新或删除会导致索引重建,影响性能,因此主键字段应尽量固定
    c.对插入性能有一定影响
        由于聚簇索引按顺序存储数据,主键索引可能会增加插入操作的开销,尤其是主键为非递增值时

05.适用场景
    a.唯一标识记录的字段
        主键适用于唯一标识每条记录的字段,例如:id、订单号、学号等
    b.高频查询字段
        对主键字段的查询或更新非常频繁,主键索引能够显著提高性能
    c.多表关联中的外键引用
        主键通常是其他表外键的引用字段,用于确保数据的一致性和完整性

1.6 [2]唯一索引

00.唯一索引 (Unique Index)
    a.定义
        UNIQUE 索引确保某个字段或字段组合中的值唯一,但允许 NULL 值。每个表可以有多个唯一索引
    b.特点
        唯一索引的字段值不能重复
        可以创建多个唯一索引
    c.示例
        UNIQUE (username)
        UNIQUE (email)
        在本例中,username 和 email 字段都定义了唯一索引,确保每个用户名和电子邮件地址在表中唯一
    d.作用
        提高查询效率
        确保数据唯一性约束
    e.适用场景
        唯一标识的字段,例如:用户名、邮箱、身份证号等
        需要既提高查询效率,又避免重复数据的场景
    f.唯一索引与其他索引的对比
        对比点            唯一索引                主键索引               普通索引
        字段值唯一性      是                     是(且非空)            否
        允许字段为 NULL   是                     否                     是
        主要作用          查询优化 + 唯一性约束   数据完整性 + 主键标识   查询优化
        适用场景          唯一但非主键的字段      唯一标识字段            非唯一字段的高频查询
    g.唯一索引的总结
        唯一索引不仅是一种高效的查询优化工具,还能提供强大的数据完整性约束
        适合用于既需要唯一性约束,又需要优化查询性能的字段或字段组合
        使用时需注意对插入、更新操作性能的影响,以及空值处理的特殊性

01.作用
    a.确保字段值唯一性
        唯一索引保证字段值在整个表中是唯一的,如果尝试插入或更新重复的值,数据库会报错
        -----------------------------------------------------------------------------------------------------
        CREATE TABLE sys_user (
            id VARCHAR(32) PRIMARY KEY,
            username VARCHAR(100) UNIQUE
        );
        在此表中,username 字段的值必须唯一
    b.加速查询性能
        唯一索引不仅用于约束数据,还可以优化查询性能。对于被频繁查询的唯一字段,唯一索引能显著提高检索速度
        -----------------------------------------------------------------------------------------------------
        SELECT * FROM sys_user WHERE username = 'Alice';
        数据库利用唯一索引直接定位记录,无需全表扫描
    c.支持 WHERE 条件优化
        唯一索引可以优化基于该字段的精确匹配、范围查询和排序操作。例如:
        -----------------------------------------------------------------------------------------------------
        SELECT * FROM sys_user WHERE username > 'Alice' ORDER BY username;
    d.用作联合索引的一部分
        唯一索引可以是复合索引的一部分,用于优化多字段的联合查询
        -----------------------------------------------------------------------------------------------------
        CREATE UNIQUE INDEX uniq_user_email_phone
        ON sys_user (email, phone_number);
        该索引确保 email 和 phone_number 的组合值在表中唯一

02.特点
    a.字段值唯一
        唯一索引强制字段值不能重复,但允许字段值为 NULL(与主键索引不同,主键字段值必须非空)
    b.查询性能提升
        唯一索引和普通索引的查询性能相当,但因其值唯一性,可使查询优化器更快判断结果
    c.自动约束机制
        唯一索引自带约束功能,避免在应用层额外校验重复数据
    d.字段更新限制
        如果字段已被定义为唯一索引,在更新时需确保新值也满足唯一性,否则更新操作会失败

03.优点
    a.强制数据唯一性
        避免插入重复数据,提高数据完整性
    b.查询优化
        提高精确匹配查询的速度,减少全表扫描
    c.适合组合字段的唯一性约束
        能灵活处理联合唯一约束的需求

04.缺点
    a.插入或更新性能影响
        唯一性检查可能增加插入或更新操作的开销,尤其是在数据量大或索引字段不连续增长的情况下
    b.对空值的限制
        唯一索引允许字段值为 NULL,但多个记录中的 NULL 不视为冲突。如果需要严格约束非空唯一性,应使用主键
    c.字段变化的成本
        如果唯一索引字段频繁变更,会增加索引的维护开销

05.适用场景
    a.需要唯一性的字段
        如用户名、邮箱、手机号、身份证号等
    b.高频查询的字段
        唯一索引优化了查询性能,适合对高频率查询的字段使用,例如商品编号、员工编号
    c.需要联合唯一约束的字段组合
        如复合唯一性约束的场景,例如 (username, email) 确保每个用户名对应唯一的邮箱
    d.约束与优化双重需求
        唯一索引同时满足数据约束和性能优化需求,适用于保证数据完整性和高效查询的场景

1.7 [2]普通索引

00.普通索引 (Regular Index)
    a.定义
        普通索引是最常见的索引类型,用于加速查询操作。它不保证数据的唯一性
    b.特点
        适用于查询频繁的字段
        可以多次添加普通索引
    c.作用
        提高查询性能,减少全表扫描。
        支持范围查询(<、>、BETWEEN、LIKE 等)
        优化排序(ORDER BY)
    d.适用场景
        高查询频率但不需要唯一性的字段,例如:状态字段、外键字段
        经常需要排序或范围查询的字段,如:日期、价格等
    e.普通索引 vs 唯一索引
        对比点          普通索引                        唯一索引
        是否允许重复    允许                            不允许
        主要作用        提高查询性能                    提高查询性能,同时强制唯一性约束
        适用场景        高频查询字段(如外键、状态等)   需要确保值唯一的字段(如用户名、邮箱)
        查询性能        略低于唯一索引                  性能稍高,但差异不明显

01.作用
    a.加速查询性能
        普通索引用于快速定位数据,减少数据库扫描行数。
        例如,查询用户所属部门时,如果 department_id 被建立了普通索引,
        数据库可以直接从索引中找到相关记录,而无需全表扫描。
        -----------------------------------------------------------------------------------------------------
        SELECT * FROM sys_user WHERE department_id = '123';
        如果 department_id 有普通索引,查询速度会显著提升。
    b.支持范围查询
        普通索引可以优化范围查询,如 <、>、BETWEEN 和 LIKE(非前缀匹配时有限制)。例如:
        -----------------------------------------------------------------------------------------------------
        SELECT * FROM sys_user WHERE department_id BETWEEN '100' AND '200';
    c.用于排序优化
        当查询中带有 ORDER BY 子句时,普通索引可以避免对结果集进行额外的排序操作:
        SELECT * FROM sys_user WHERE department_id = '123' ORDER BY create_time DESC;
    d.支持部分联合索引查询
        在复合索引中,普通索引可以被用于联合查询的部分匹配。
        例如,对于联合索引 (department_id, status),普通索引能优化如下查询:
        SELECT * FROM sys_user WHERE department_id = '123';
    e.减轻数据库 I/O 压力
        普通索引减少了数据库需要扫描的记录数量,优化了磁盘读写,尤其在数据量较大时效果更为显著。

02.特点
    非唯一性:普通索引允许字段值重复,与唯一索引不同。例如,多个用户可以属于同一个 department_id。
    多用在频繁查询字段上:普通索引适合对高查询频率字段(非唯一字段)进行优化,而非用于唯一性约束。
    占用存储空间:建立索引会增加数据库存储开销,尤其是频繁更新的字段索引,可能带来额外的维护成本。
    仅对查询有效:普通索引主要用来提高查询效率,对插入、更新和删除操作无特殊优化作用。

03.适用场景
    经常出现在 WHERE 子句中的字段:如 department_id。
    频繁用于排序的字段:如 create_time。
    联合索引中的非主导字段:如 (department_id, status) 中的 status。
    数据分组查询的字段:如 GROUP BY department_id。
    普通索引虽无唯一性限制,但其灵活性更高,是数据库性能优化中不可或缺的一部分。

1.8 [2]复合索引

00.复合索引 (Composite Index)
    a.定义
        复合索引是基于多个字段创建的索引,适用于涉及多个字段查询的情况
        复合索引的查询优化作用仅在查询中涉及索引的所有字段时才有效
    b.特点
        复合索引的顺序很重要,查询中使用到的字段应该与复合索引的字段顺序一致
        如果查询只涉及复合索引中的前缀字段,索引仍然有效
    c.示例
        INDEX idx_department_status (department_id, status)
        在本例中,idx_department_status 是一个复合索引,包含 department_id 和 status 字段
        当查询中同时使用这两个字段时,复合索引会提高查询效率
    d.作用
        优化多条件查询
        避免为多个字段分别建立单独的索引,从而降低索引开销
    e.适用场景
        经常联合查询多个字段的场景,例如:(department_id, status)
        注意:复合索引遵循“最左前缀匹配原则”,只有从左到右匹配时索引才会生效
    f.复合索引的设计建议
        1.根据查询需求设计字段顺序
            优先将高选择性字段(区分度高的字段)放在索引的最左侧。
            按常用的查询字段组合顺序排列。
        2.避免过多字段 复合索引字段过多会增加维护成本,建议控制在 3-4 个以内。
        3.尽量覆盖查询需求 如果复合索引覆盖查询的所有字段,可实现覆盖索引查询,提升性能。
    g.复合索引与其他索引的对比
        对比点         复合索引                   主键索引        唯一索引          普通索引
        字段数         多个字段                   单个字段        单个或多个字段    单个字段
        是否唯一       否                         是             是                否
        匹配顺序限制   是(遵循左前缀原则)        否             否                否
        适用场景       多字段联合查询、排序、分组  唯一标识字段    唯一但非主键字段   查询频繁的非唯一字段
    h.复合索引的总结
        复合索引是优化多字段联合查询和排序的有效工具
        使用时需注意字段顺序,遵循左前缀原则
        设计复合索引时,应根据查询需求优先考虑高选择性字段
        避免冗余设计,尽量减少字段数量以控制维护成本

01.作用
    a.优化多字段联合查询 复合索引可显著提升针对多个字段的联合查询的性能
        CREATE INDEX idx_user_name_department ON sys_user (username, department_id);
        SELECT * FROM sys_user WHERE username = 'Alice' AND department_id = 3;
        查询时,复合索引能够直接锁定 username 和 department_id 的组合,而无需对单独字段分别扫描。
    b.提高排序和分组性能 复合索引对多个字段的排序或分组操作也能起到优化作用
        SELECT * FROM sys_user ORDER BY username, department_id;
    c.部分字段匹配查询 复合索引允许匹配部分字段,这取决于字段的顺序(左前缀原则,见下方“特点”)
        SELECT * FROM sys_user WHERE username = 'Alice';
        此查询仍可使用 idx_user_name_department 索引,但如果仅查询 department_id,复合索引则无法优化。

02.特点
    a.覆盖多个字段
        复合索引包含多个字段,适合优化这些字段的联合使用
    b.遵循左前缀原则
        复合索引按字段顺序建立,仅支持从左到右依次匹配的查询条件
        如果字段顺序为 (username, department_id)
        可以优化:username,username + department_id
        无法优化:department_id 或反向查询
    c.比多个单字段索引更高效
        与为每个字段分别建立单字段索引相比,复合索引减少了存储空间,并避免查询优化器选择错误的索引
    d.适合高频联合查询
        复合索引专为多条件查询设计,适合优化多个字段联合查询的场景
    e.存储与更新成本更高
        复合索引因索引多个字段,维护开销比单字段索引大,尤其是索引字段变更频繁时

03.优点
    a.联合优化多字段查询
        减少了对单字段索引的多次扫描,提升了查询效率
    b.减少索引数量
        通过一个复合索引即可优化多字段查询,避免为每个字段单独建立索引,节省存储
    c.支持覆盖索引
        如果查询字段完全包含在复合索引中,可避免回表,进一步提升性能
    d.适合多字段联合的高效排序
        对多个字段的联合排序操作有天然优势

04.缺点
    a.字段顺序限制
        查询时必须遵循复合索引的字段顺序,查询条件无法跳跃字段或逆序匹配
    b.插入/更新成本较高
        索引维护需要额外的资源消耗,字段越多,成本越大
    c.选择性要求高
        如果复合索引字段的选择性不高(值重复率高),性能提升有限
    d.查询条件不匹配时无法使用
        如果查询条件不包含复合索引的首字段,则索引完全无法使用

05.适用场景
    a.多字段联合查询频繁 如在员工信息表中,经常按用户名和部门联合查询
        SELECT * FROM sys_user WHERE username = 'Alice' AND department_id = 3;
    b.多字段排序或分组操作 对多个字段进行排序或分组时,可使用复合索引优化
        SELECT * FROM sys_user ORDER BY username, department_id;
    c.复合索引字段为主过滤条件 多字段查询条件同时作为主要过滤条件时,复合索引能提升性能
        略
    d.需要覆盖索引查询 如果查询所需字段完全包含在复合索引中,可实现覆盖索引查询,避免回表
        SELECT username, department_id FROM sys_user WHERE username = 'Alice';

1.9 [2]全文索引

00.全文索引(Fulltext Index)
    a.特点
        用于全文搜索,与普通索引不同,主要处理非结构化文本数据
        支持分词匹配和快速定位关键词
    b.作用
        适合复杂的文本匹配,例如:搜索文章内容中的某些关键词
    c.适用场景
        需要全文搜索的字段,例如博客、商品描述等
    d.设计建议
        a.选择合适的字段
            仅为真正需要全文检索的字段建立全文索引,避免无意义的存储开销
        b.考虑存储引擎兼容性
            使用 InnoDB 时,确保 MySQL 版本在 5.6 及以上
        c.配合分词优化中文搜索
            对于中文或其他复杂语言,建议引入分词插件(如 NLPIR 或 Sphinx)
        d.结合布尔模式实现高级搜索
            使用布尔模式的逻辑操作符实现精准控制的查询功能
    e.全文索引与其他索引的对比
        对比点        全文索引               普通索引      主键索引        复合索引
        适用字段类型   TEXT, CHAR, VARCHAR   任意字段类型  任意字段类型    任意字段类型
        支持模糊查询   是                    否            否             否
        相关性排序     是                    否            否             否
        多字段联合     支持(需分开定义)     不适合        不适合         最适合
        适用场景       非结构化文本数据       精确匹配      主键标识       多字段联合查询、排序
    f.全文索引的总结
        全文索引是一种强大的文本搜索工具,适合大规模非结构化数据的关键词匹配和复杂搜索需求
        使用自然语言模式或布尔模式,根据场景优化查询结果
        对中文等复杂语言需配合分词工具使用,以提升分词准确性和查询性能
        在设计中需合理评估性能与存储开销,避免滥用

01.作用
    a.高效的关键词搜索
        全文索引优化了基于文本内容的关键词匹配查询
        -----------------------------------------------------------------------------------------------------
        CREATE FULLTEXT INDEX idx_article_content ON articles(content);
        SELECT * FROM articles WHERE MATCH(content) AGAINST('MySQL');
        -----------------------------------------------------------------------------------------------------
        此查询可快速检索含有“MySQL”关键词的记录
    b.支持自然语言查询
        全文索引支持自然语言模式,能根据词频计算出文档与查询的相关性,排序结果按匹配度优先
        -----------------------------------------------------------------------------------------------------
        SELECT * FROM articles WHERE MATCH(content) AGAINST('database design' IN NATURAL LANGUAGE MODE);
    c.优化大文本查询
        对于字段类型为 TEXT 或 VARCHAR 的大文本,全文索引能显著提升复杂查询性能,避免全表扫描
    d.多关键词组合搜索
        全文索引支持逻辑操作符,如 +(必须包含)、-(排除)等,适合多关键词复杂查询
        -----------------------------------------------------------------------------------------------------
        SELECT * FROM articles WHERE MATCH(content) AGAINST('+MySQL -PostgreSQL' IN BOOLEAN MODE);

02.特点
    a.针对文本数据设计
        专为 CHAR、VARCHAR 和 TEXT 类型字段设计,适合处理非结构化数据
    b.基于词库建立
        全文索引会根据文本内容分词(tokenize)并建立词典,查询时利用词典快速定位相关文档
    c.支持两种查询模式
        自然语言模式(Natural Language Mode):按相关性排序结果,适合一般文本搜索
        布尔模式(Boolean Mode):支持精确控制的逻辑操作符,适合高级查询需求
    d.性能与存储空间开销
        全文索引需要额外的存储空间来维护词典结构,但对于大文本查询,其性能优势远超普通索引
    e.不适合短字段
        如果字段内容非常短(如名称、代码),使用普通索引通常更高效

03.优点
    a.显著提升文本查询效率
        相比 LIKE 模糊查询,全文索引的查询速度更快,尤其在数据量大时优势明显
    b.支持复杂的查询逻辑
        布尔模式提供了丰富的逻辑操作符,能灵活满足各种查询需求
    c.相关性排序功能
        自然语言模式可根据词频计算相关性,自动优化结果排序
    d.降低全表扫描风险
        能快速定位匹配文档,无需扫描整个表

04.缺点
    a.适用数据类型有限
        仅支持 CHAR、VARCHAR 和 TEXT 类型字段
    b.不支持所有存储引擎
        全文索引主要适用于 InnoDB 和 MyISAM 存储引擎(InnoDB 从 MySQL 5.6 开始支持)
    c.对实时更新敏感
        全文索引的更新成本较高,对于频繁插入或更新的大量文本,可能影响性能
    d.语言依赖性
        分词效果依赖于语言支持,非英文文本(如中文)需要特殊的分词插件或配置
    e.无效于短文本匹配
        对短字段(如姓名、编码)的查询,普通索引或复合索引表现更优

05.适用场景
    a.文章或文档内容搜索
        如博客系统中的文章搜索、新闻网站的内容检索等
    b.评论或产品描述匹配
        适合用于匹配用户评论、产品描述等非结构化数据的关键词
    c.支持模糊查询的场景
        当需要支持模糊查询或多关键词组合搜索时,全文索引能显著提升性能
    d.自然语言相关性查询
        如果查询需要根据关键词的相关性排序(如搜索引擎),全文索引是理想选择

1.10 [2]空间索引

00.空间索引(Spatial Index)
    a.特点
        用于存储和查询地理数据类型(如 Geometry、Point)
        只能用于MyISAM或支持GIS的存储引擎(如InnoDB的一些最新版本)
    b.作用
        提供地理空间数据的快速查询
    c.适用场景
        地图服务、GIS应用中的位置坐标查询
    d.空间索引的设计建议
        1.明确查询需求 空间索引仅适用于几何范围查询和距离运算,普通文本或数字查询无需使用
        2.选择合适的字段类型 字段需定义为 GEOMETRY 或其子类型(如 POINT、POLYGON)
        3.优化空间分布 数据分布需均衡,避免几何数据聚集造成的索引效率下降
        4.结合 GIS 函数 配合如 ST_Contains、ST_Within 等函数,优化空间查询逻辑
        5.避免频繁更新 对于经常变动的几何数据,索引维护代价较高,需综合评估
    e.空间索引与其他索引的对比
        对比点        空间索引                   普通索引           主键索引        全文索引      复合索引
        适用字段类型   GEOMETRY                  任意字段类型       任意字段类型    文本字段类型   任意字段类型
        支持多维数据   是                        否                 否             否            否
        支持模糊查询   否                        否                 否             是            否
        查询方式       空间操作(如范围、距离)   精确匹配或范围查询  精确匹配       关键词匹配     联合字段查询
        适用场景       地理位置与空间数据         单字段或数值查询   唯一标识查询    文本搜索      多字段联合查询
    f.空间索引的总结
        空间索引是用于地理数据处理的强大工具,在需要处理地理坐标或其他几何数据的场景中表现尤为突出
        优化范围查询与距离运算,是地理信息系统的重要组成部分
        结合 GIS 函数灵活处理复杂几何操作
        在设计中需注意字段类型限制及存储引擎兼容性,以确保索引有效性

01.作用
    a.优化地理位置范围查询 空间索引能够快速检索特定范围内的地理位置数据
        SELECT * FROM locations WHERE ST_Contains(ST_GeomFromText('POLYGON((...))'), location);
    b.此查询快速返回位于指定多边形区域内的点
        支持地理距离计算 空间索引结合 GIS 函数,可用于计算地理实体之间的距离,适合附近位置查询
        SELECT * FROM locations WHERE ST_Distance(location, POINT(120, 30)) < 5;
    c.优化复杂空间操作 空间索引提升了复杂空间操作的性能,如点在多边形内检测、两几何图形是否相交等
        略
    d.存储和查询多维数据 空间索引支持二维甚至多维数据的高效存储和查询,适合处理地理坐标及其他几何数据
        略

02.特点
    1.专为空间数据设计 主要用于 GEOMETRY 类型字段,包括点(Point)、线(LineString)、多边形(Polygon)等
    2.基于 R-Tree 结构 空间索引通常基于 R-Tree 数据结构实现,能够高效管理和查询多维空间数据
    3.支持复杂几何操作 空间索引结合 GIS 函数,如 ST_Contains、ST_Within、ST_Distance 等,处理复杂几何查询
    4.存储引擎限制 仅 MyISAM 存储引擎支持传统空间索引,InnoDB 从 MySQL 5.7 开始支持空间索引,但需在 SPATIAL 索引中使用
    5.不支持模糊匹配 空间索引适用于精准几何查询或距离运算,不支持模糊匹配操作
    6.与 GIS 函数配合使用 查询需要结合 GIS 函数完成,查询表达式的灵活性依赖于 GIS 函数库的强大程度

03.优点
    1.高效的空间查询 空间索引显著提升了范围查询和几何运算的性能,避免全表扫描
    2.支持多维空间数据 可处理包含多个维度的空间数据(如纬度和经度)
    3.复杂空间逻辑 与 GIS 函数结合,可实现复杂空间操作,如计算相交区域、距离等
    4.专用索引结构 R-Tree 索引结构专为多维数据设计,性能优于普通 B-Tree 索引

04.缺点
    1.支持字段类型有限 空间索引仅适用于 GEOMETRY 数据类型,不支持其他数据类型
    2.存储引擎限制:MyISAM 支持传统空间索引,但不支持事务。InnoDB 支持空间索引,但需 MySQL 5.7 或以上版本
    3.额外存储开销 空间索引需要维护复杂的 R-Tree 结构,占用额外的存储空间
    4.语言依赖 查询表达式依赖 GIS 函数库,不支持直接使用标准 SQL 表达式
    5.更新代价高 空间索引更新(如插入、删除、更新几何数据)可能带来较高的性能开销

05.适用场景
    1.地图相关应用 适用于处理地图数据,如在导航系统、地理信息系统(GIS)中存储地标、路线和区域数据
    2.定位与周边搜索 如在电商或社交应用中,查询附近的商家、好友、服务等
    3.地理范围过滤 用于限定查询范围,如筛选特定城市或区域的用户或事件
    4.复杂几何分析 用于地理数据的高级分析,例如判断一个点是否位于特定的多边形区域内

1.11 [3]覆盖索引

01.定义
    覆盖索引(Covering Index)是指一个索引包含了查询所需的所有列的数据
    因此查询可以直接从索引中获取结果,而不需要访问表中的实际数据行
    这种索引可以显著提高查询性能,因为它减少了磁盘I/O操作

02.原理
    a.说明
        在MySQL中,当执行查询时,数据库通常需要访问表中的数据行来获取所需的列
        然而,如果一个索引包含了查询所需的所有列,MySQL可以直接从索引中获取数据,而不需要访问表
        这种情况称为覆盖索引
    b.覆盖索引的优点
        减少I/O操作:因为不需要访问表中的数据行,减少了磁盘I/O
        提高查询速度:索引通常比表小,访问索引比访问表更快

03.常用API
    在MySQL中,覆盖索引不需要特殊的API来创建
    它是通过创建合适的复合索引来实现的
    可以使用`CREATE INDEX`语句来创建包含查询所需列的索引

04.使用步骤
    a.识别查询列
        确定查询中需要的所有列
    b.创建复合索引
        使用`CREATE INDEX`语句创建包含所有查询列的索引
    c.执行查询
        MySQL会自动使用覆盖索引来优化查询

05.场景及代码示例
    a.场景1:简单查询
        a.说明
            假设有一个表`employees`,包含以下列:`id`、`name`、`department`、`salary`
            我们需要查询某个部门的员工姓名
        b.代码
            CREATE TABLE employees (
                id INT PRIMARY KEY,
                name VARCHAR(100),
                department VARCHAR(100),
                salary DECIMAL(10, 2)
            );

            -- 创建覆盖索引
            CREATE INDEX idx_department_name ON employees(department, name);

            -- 查询使用覆盖索引
            SELECT name FROM employees WHERE department = 'Sales';
            ---
        c.说明
            索引`idx_department_name`包含了查询所需的所有列(`department`和`name`)
            因此查询可以直接从索引中获取数据
    b.场景2:复杂查询
        a.说明
            假设我们需要查询某个部门的员工姓名和工资
        b.代码
            -- 创建覆盖索引
            CREATE INDEX idx_department_name_salary ON employees(department, name, salary);

            -- 查询使用覆盖索引
            SELECT name, salary FROM employees WHERE department = 'Sales';
        c.说明
            索引`idx_department_name_salary`包含了查询所需的所有列(`department`、`name`和`salary`)
            因此查询可以直接从索引中获取数据

05.注意事项
    a.索引大小
        覆盖索引可能会增加索引的大小,因此需要权衡索引大小和查询性能
    b.更新开销
        更多的索引可能会增加插入、更新和删除操作的开销
    c.选择性
        确保索引的选择性高,以便充分利用覆盖索引的优势

1.12 [3]深度分页

01.普通分页
    a.优化1:通过增加主键排序
        order by id
    b.优化2:如果需要根据时间排序,就给常用的字段增加索引,包括时间字段
        order by create_time

02.深度分页
    a.定义
        查询偏移量过大的分页
        SELECT * FROM t_xxx ORDER BY id LIMIT 1000000, 20
    b.优化方案
        a.强制索引force index(不推荐)
            不建议添加强制索引来进行sql优化,主要有以下几种缺点:
            影响选择性最佳的索引:强制使用索引可能会影响数据库引擎选择性最佳的索引,导致查询性能下降
            增加更新操作的时间:强制使用索引后,数据库更新操作的时间会增加,因为索引文件需要被更新
            降低查询的灵活性:如果强制使用索引过于固定,会降低查询的灵活性,不方便后期维护
        b.ID范围查询
            如果那种不需要页码的场景下,比如滑动加载(消息列表这种)
            还有那种只有上下页按钮点击的网站分页,我们可以通过where id > #{上次查询的最后一条记录的id} 进行优化
            # 查询指定 ID 范围的数据
            SELECT * FROM t_xxx WHERE id > 1000000 AND id <= 1000020 ORDER BY id
            # 也可以通过记录上次查询结果的最后一条记录的ID进行下一页的查询
            SELECT * FROM t_xxx WHERE id > 1000000 LIMIT 20
        c.子查询+INNER JOIN
            先根据时间字段(create_time)或者id排序查询到id
            SELECT id FROM t_xxx ORDER BY create_time DESC LIMIT 1000000,20
            这个子查询先查出来,作为临时表,然后再让主表join这个临时表去联表查询需要的t_xxx对应的信息字段
            SELECT * FROM t_xxx INNER JOIN (SELECT id FROM t_xxx WHERE name = 'xxx' ORDER BY id LIMIT 1000000,20) AS t_temp ON t_xxx.id = t_temp.id
        d.子查询+ID过滤
            通过子查询+ID过滤优化的方式进行优化
            SELECT * FROM t_xxx WHERE name = 'xxx' AND id >(SELECT id FROM t_xxx WHERE name = 'xxx' ORDER BY id LIMIT 1000000,1) ORDER BY id LIMIT 20
    c.总结
        a.产品方向
            参考谷歌/百度搜索分页,每次只能跳转到当前页前后10页,也就是最多可以跳10页,要想达到深分页情况需要耐心
            前端不支持跳页,只能通过上下页这种
        b.技术方向
            1.即使没有排序条件,也最好带上主键或者时间排序
            2.对排序字段添加索引
            3.通过子查询+INNER JOIN的方式
            4.通过子查询+ID过滤的方式(这其实是3的变种,要求id是自增的)
            5.每次查询记录上一次查询的id,然后基于这个id查询下一次(类似4)
            6.调整mysql相关配置,比如sort_buffer_size大小(注意并不是越大越好,根据情况设置)
            7.如果是基于内容搜索的话,可以使用Elasticsearch这种全文搜索引擎来进行优化

1.13 [3]索引、约束

00.索引、约束
    索引:查询效率
    约束:数据完整性

01.六种索引:查询效率
    a.普通索引(Index)
        最常用的索引类型,用于加速查询,没有特殊的约束
        示例:CREATE INDEX idx_name ON users(name);
    b.唯一索引(Unique Index)
        确保索引列中的值是唯一的,类似于 UNIQUE 约束
        示例:CREATE UNIQUE INDEX idx_email ON users(email);
    c.主键索引(Primary Key Index)
        主键自动创建一个唯一索引,值必须唯一且非空
        示例:PRIMARY KEY (id)
    d.复合索引(Composite Index 或 Multi-Column Index)
        在多个列上创建的索引,用于优化多列条件的查询
        示例:CREATE INDEX idx_name_age ON users(name, age);
    e.全文索引(Full-Text Index)
        用于全文搜索,支持 MATCH 和 AGAINST 操作。适合处理大文本数据(如文章或评论)
        示例:CREATE FULLTEXT INDEX idx_content ON articles(content);
    f.空间索引(Spatial Index)
        用于存储和查询地理数据,适用于 GIS(地理信息系统)
        必须基于 MyISAM 表或 InnoDB 的 SPATIAL 列类型
        示例:CREATE SPATIAL INDEX idx_location ON geo_data(location);

02.六大约束:数据完整性
    a.NOT NULL(非空约束)
        保证字段值不能为 NULL,即要求字段必须有值
        示例:name VARCHAR(50) NOT NULL
    b.UNIQUE(唯一约束)
        保证字段的值在该列中唯一,不能重复,但可以有 NULL 值(NULL 被认为是唯一的)
        示例:email VARCHAR(100) UNIQUE
    c.PRIMARY KEY(主键约束)
        主键是表中的唯一标识,要求字段值唯一且不能为 NULL
        一张表只能有一个主键。
        示例:id INT PRIMARY KEY
    d.FOREIGN KEY(外键约束)
        用于建立表与表之间的关联关系,外键字段的值必须与主表对应字段的值匹配
        示例:FOREIGN KEY (order_id) REFERENCES orders(id)
    e.CHECK(检查约束)
        检查字段值是否符合某些条件,MySQL 8.0 及以上版本支持
        示例:age INT CHECK (age >= 18)
    f.DEFAULT(默认值约束)
        为字段设置一个默认值,如果插入数据时未指定值,则使用默认值
        示例:status VARCHAR(20) DEFAULT 'active'

1.14 [3]索引、回表

00.索引、回表
    a.回答
        索引:加速查询
        回表:为了获取索引中未包含的完整数据
              通过辅助索引(二级索引)获取数据时,当索引中的字段无法覆盖选择列,才会出现所谓的“回表”现象
    b.关系
        关系:回表并不是因为通过索引找不到数据而触发的,而是因为索引中不包含查询所需的所有列数据时才需要进行的操作

01.定义
    发生在非聚簇索引查询
    当查询的列不完全包含在索引中时,MySQL需要通过索引找到主键值,然后回到表中获取完整的数据行,这一过程称为回表
    在使用二级索引(非聚簇索引)作为条件进行查询时,由于二级索引中只存储了索引字段的值和对应的主键(聚簇索引)键值,因此需要根据主键中去聚簇索引查找实际的数据行,这个过程被称为回表

02.工作原理
    1.索引查找:MySQL使用辅助索引查找满足条件的索引键值,辅助索引中存储的是索引列的值和对应的主键值
    2.回表操作:找到索引键值后,MySQL需要通过主键值回到表中,获取完整的数据行,这是因为辅助索引中不包含所有列的数据

03.何时会发生回表
    当查询的列不完全包含在索引中时,需要回表
    使用覆盖索引(即查询的所有列都在索引中)可以避免回表

04.如何优化回表
    使用覆盖索引:通过在查询中选择合适的索引列,确保查询的所有列都在索引中,从而避免回表
    选择合适的索引:根据查询的实际需求,创建合适的索引以减少不必要的回表操作

05.代码示例
    -- 查询需要回表,因为order_date不在索引中
    SELECT order_date FROM orders WHERE customer_id = 123;

    -- 使用覆盖索引避免回表
    -- 假设创建了组合索引 (customer_id, order_date)
    CREATE INDEX idx_customer_order ON orders (customer_id, order_date);

    -- 现在查询可以通过覆盖索引避免回表
    SELECT order_date FROM orders WHERE customer_id = 123;

1.15 [3]索引覆盖、索引下推

01.覆盖索引
    a.定义
        覆盖索引(Covering Index)指的是一个查询语句可以通过索引直接获取所需的数据,而无需访问数据表
        这种情况下,索引包含了查询所需的所有列
    b.优点
        减少了回表操作,降低了 I/O 开销
        提高了查询效率,因为所有数据都可以从索引中获取
    c.使用条件
        查询的列必须全部包含在索引中
        查询条件必须符合索引的最左前缀匹配原则
    d.示例
        表 `covering_index_sample` 中有索引 `idx_key1_key2(key1, key2)`
        查询 `SELECT key2 FROM covering_index_sample WHERE key1 = 'keytest';` 可以使用覆盖索引
        查询 `SELECT key1 FROM covering_index_sample WHERE key2 = 'keytest';` 无法使用索引,因为不符合最左前缀匹配
        查询 `SELECT key2, key3 FROM covering_index_sample WHERE key1 = 'keytest';` 无法使用覆盖索引,因为 `key3` 不在索引中

02.索引下推
    a.定义
        索引下推(Index Condition Pushdown)是 MySQL 5.6 引入的一种优化技术
        允许在存储引擎层面进行更复杂的索引条件过滤,从而减少回表次数
    b.优点
        减少了回表次数,提高了查询效率
        在有 `LIKE` 条件的查询中尤其有效
    c.使用条件
        联合索引中某个非前导列由于索引失效需要进行扫表时,可以使用索引下推优化
    d.示例
        表 `people` 中有索引 `(zipcode, lastname, firstname)`
        查询 `SELECT * FROM people WHERE zipcode='95054' AND lastname LIKE '%etrunia%' AND address LIKE '%Main Street%';` 使用索引下推技术
        联合索引 `(a, b)`,查询 `SELECT d FROM t2 WHERE a = "ni" AND b = 1;` 可以使用索引下推,因为 `b` 的类型不匹配导致索引失效,但索引下推可以减少回表次数

1.16 [4]groupby、orderby

00.汇总
    操作          无索引时的开销              有索引优化策略                关键参数
    GROUP BY      临时表+文件排序             松散/紧凑索引扫描,覆盖索引    tmp_table_size、sql_mode
    ORDER BY      内存/磁盘排序               索引顺序扫描,延迟关联分页     sort_buffer_size、max_length_for_sort_data
    联合使用       双重临时表+排序            索引履盖分组和排序字段         联合索引设计,查询重写

01.GROUP BY工作原理
    a.概述
        GROUP BY 用于按指定列对数据进行分组,通常结合聚合函数(如 SUM、COUNT、MAX 等)使用
    b.执行流程
        a.无索引时的处理
            a.全表扫描
                读取所有满足条件的行
            b.构建临时表
                将数据按 GROUP BY 列分组,存储分组键和中间聚合结果
            c.排序与聚合
                默认按分组字段隐式排序(类似 ORDER BY)
                若未显式关闭排序(ORDER BY NULL),可能触发文件排序(Using filesort)
            d.返回结果
                遍历临时表输出分组后的数据
        b.有索引时的优化
            a.松散索引扫描(Loose Index Scan)
                当 GROUP BY 列是索引的最左前缀且无范围查询时,直接跳过索引中的重复值,仅读取每组的第一行
                示例:索引 (a, b),查询 GROUP BY a
            b.紧凑索引扫描(Tight Index Scan)
                按索引顺序逐行读取数据,隐式利用索引有序性完成分组
                示例:索引 (a, b),查询 GROUP BY a, b
    c.性能优化策略
        a.索引设计
            创建联合索引,确保 GROUP BY 列是索引的最左前缀
            使用覆盖索引(包含所有查询字段)避免回表
            示例:ALTER TABLE orders ADD INDEX idx_user_status (user_id, status);
        b.避免隐式排序
            SELECT category, COUNT(*) FROM products
            GROUP BY category ORDER BY NULL; -- 禁用默认排序
        c.监控临时表
            SHOW STATUS LIKE 'Created_tmp%'; -- 查看临时表使用情况

02.ORDER BY工作原理
    a.概述
        ORDER BY 用于对结果集排序,其执行效率高度依赖索引和内存管理
    b.执行流程
        a.无索引时的处理
            a.全表扫描
                读取所有满足条件的行
            b.排序缓冲区(sort_buffer)
                若数据量小于 sort_buffer_size,在内存中排序(单路排序)
                若数据量过大,使用磁盘临时文件进行多路归并排序(双路排序)
            c.返回结果
                按排序后的顺序输出数据
        b.有索引时的优化
            若排序字段与索引顺序一致,直接按索引顺序读取数据(避免排序)
            支持正向或反向扫描索引(MySQL 8.0+ 支持降序索引)
    c.性能优化策略
        a.索引设计
            排序字段与索引顺序一致(包括升降序)
            示例:索引 (created_at DESC),查询 ORDER BY created_at DESC
        b.调整排序缓冲区
            sort_buffer_size = 8M           -- 增大内存缓冲区
            max_length_for_sort_data = 8192 -- 控制单行数据长度,优先单路排序
        c.避免深分页
            -- 低效写法(扫描前100010行)
            SELECT * FROM logs ORDER BY id LIMIT 100000, 10;
            -- 高效写法(基于游标)
            SELECT * FROM logs WHERE id > 100000 ORDER BY id LIMIT 10;

03.GROUP BY 与 ORDER BY 的联合使用
    a.执行顺序
        a.默认顺序
            先执行 GROUP BY 分组,再对分组结果排序
        b.优化思路
            若 ORDER BY 字段与 GROUP BY 字段一致,可省略 ORDER BY(分组后默认有序)
            若不一致,需确保索引覆盖分组和排序字段
    b.优化示例
        a.场景:统计每个用户的最新订单并按时间倒序输出
            a.低效写法(临时表 + 排序)
                SELECT user_id, MAX(created_at)
                FROM orders
                GROUP BY user_id
                ORDER BY MAX(created_at) DESC;
            b.优化方案(覆盖索引 + 延迟关联)
                ALTER TABLE orders ADD INDEX idx_user_created (user_id, created_at);
                SELECT o.user_id, o.created_at
                FROM orders o
                INNER JOIN (
                  SELECT user_id, MAX(created_at) AS max_time
                  FROM orders
                  GROUP BY user_id
                ) AS tmp ON o.user_id = tmp.user_id AND o.created_at = tmp.max_time
                ORDER BY o.created_at DESC;

1.17 [4]UNIQUE、NORMAL

00.问题
    推荐【保留UNIQUE索引】、【删除NORMAL索引】
    在同一个字段上同时定义UNIQUE索引和NORMAL索引不会直接导致冲突,但会引发一些不必要的资源消耗和潜在的混淆

01.UNIQUE唯一索引的作用
    UNIQUE索引确保字段的值具有唯一性,并对该字段的值进行唯一性检查
    UNIQUE索引也可以提高查询性能,因为它本质上也是一种索引

02.NORMAL普通索引的作用
    NORMAL索引没有唯一性限制,主要用于提高查询速度
    多余的普通索引可能会在插入、更新或删除数据时增加索引维护的成本

03.为什么可能会冲突?
    索引冗余:在一个字段上同时有 UNIQUE 索引和 NORMAL 索引,会导致两个索引都需要维护,但实际上 UNIQUE 索引已经包含了 NORMAL 索引的功能。UNIQUE 索引不仅提供了查询加速,还提供了唯一性约束
    混淆维护:多个索引可能让开发人员或数据库管理员在优化查询时感到困惑,不清楚应该利用哪个索引
    性能问题:虽然不会直接冲突,但多余的索引会增加磁盘空间占用,并在写操作(如插入、更新、删除)时增加索引更新的开销

04.在字段上,通常不需要同时存在UNIQUE和NORMAL索引。如果你的需求是确保字段唯一性,并且需要加速查询:
    保留UNIQUE索引:它同时提供唯一性约束和查询加速
    删除NORMAL索引:普通索引是多余的,因为UNIQUE索引已经涵盖了查询优化

1.18 [4]Binlog、Redolog、Undolog

00.汇总
    特性        binlog                 redolog                undolog
    功能        记录数据库整体变更     保证事务持久性         用于事务回滚
    格式        二进制格式,可读性差   二进制格式,可读性差   逆操作的信息
    作用范围    记录整个数据库变更     记录事务级别修改       记录事务级别逆操作
    存储位置    存储在主数据库上       存储在磁盘上的文件     存储在磁盘上的文件
    生命周期    可以根据配置保留时间   持久存在直到事务结束   持久存在直到事务结束

01.Binlog(Binary Log)
    a.定义
        Binlog是MySQL的二进制日志,用于记录所有对数据库进行更改的SQL语句。它主要用于数据恢复和主从复制
    b.原理
        a.记录更改
            Binlog记录所有导致数据更改的SQL语句(如INSERT、UPDATE、DELETE)
        b.数据恢复
            通过重放Binlog,可以将数据库恢复到某个时间点
        c.主从复制
            从库通过读取主库的Binlog来实现数据同步
    c.常用API
        SHOW BINARY LOGS;                  --查看所有Binlog文件
        SHOW BINLOG EVENTS IN 'log_name';  --查看指定Binlog文件中的事件
        mysqlbinlog                        --用于解析和重放Binlog文件的命令行工具
    d.使用步骤
        1.启用Binlog:在MySQL配置文件中设置log-bin参数
        2.查看Binlog:使用SHOW BINARY LOGS;查看Binlog文件
        3.恢复数据:使用mysqlbinlog工具重放Binlog文件
    e.示例
        -- 查看所有Binlog文件
        SHOW BINARY LOGS;
        -- 查看指定Binlog文件中的事件
        SHOW BINLOG EVENTS IN 'mysql-bin.000001';

02.Redolog
    a.定义
        Redolog是InnoDB存储引擎的重做日志,用于记录事务的物理更改
        它确保事务的持久性,即使在系统崩溃后也能恢复已提交的事务
    b.原理
        a.记录物理更改
            Redolog记录事务对数据页的物理更改
        b.事务持久性
            在事务提交时,确保其更改已写入Redolog
        c.崩溃恢复
            在系统崩溃后,通过重放Redolog恢复已提交的事务
    c.常用API
        Redolog是InnoDB内部管理的,开发者无法直接操作。可以通过以下方式查看Redolog状态
        SHOW ENGINE INNODB STATUS;  --查看InnoDB引擎状态,包括Redolog信息
    d.使用步骤
        1.事务提交:在事务提交时,确保其更改已写入Redolog
        2.崩溃恢复:在系统崩溃后,通过重放Redolog恢复已提交的事务
    e.示例
        -- 查看InnoDB引擎状态
        SHOW ENGINE INNODB STATUS;

03.Undolog
    a.定义
        Undolog是InnoDB存储引擎的回滚日志,用于记录事务的逻辑更改。它支持事务的回滚和一致性读
    b.原理
        a.记录逻辑更改
            Undolog记录事务的逻辑更改,以便在事务回滚时撤销更改
        b.事务回滚
            在事务回滚时,通过Undolog撤销未提交的更改
        c.一致性读
            支持MVCC(多版本并发控制),提供一致性读
    c.常用API
        Undolog是InnoDB内部管理的,开发者无法直接操作。可以通过以下方式查看Undolog状态:
        SHOW ENGINE INNODB STATUS; --查看InnoDB引擎状态,包括Undolog信息
    d.使用步骤
        1.事务开始:在事务开始时,记录逻辑更改到Undolog
        2.事务回滚:在事务回滚时,通过Undolog撤销未提交的更改
        3.一致性读:通过Undolog支持一致性读
    e.示例
        -- 查看InnoDB引擎状态
        SHOW ENGINE INNODB STATUS;
04.总结
    a.Binlog
        用于记录SQL语句,支持数据恢复和主从复制
    b.Redolog
        用于记录物理更改,确保事务持久性和崩溃恢复
    c.Undolog
        用于记录逻辑更改,支持事务回滚和一致性读

1.19 [5]B+树索引、Hash索引

00.总结
    B+树索引:适合需要范围查询、排序和前缀匹配的场景,支持更广泛的查询类型
    Hash索引:适合需要快速等值查询的场景,但不支持范围查询和排序

01.B+树索引
    a.范围查询
        支持范围查询,可以有效处理<、<=、>、>=等操作
        适用于需要进行区间查找的场景
    b.联合索引的最左侧原则
        支持联合索引的最左侧匹配原则,可以利用索引的前缀部分进行查询优化
    c.排序支持
        支持ORDER BY排序操作,因为B+树本身是有序的
    d.模糊查询
        支持LIKE 'abc%'形式的模糊查询,可以利用索引进行优化
    e.适用场景
        适合需要范围查询、排序和前缀匹配的场景

02.Hash索引
    a.等值查询效率
        在等值查询(如=)上效率更高,因为通过哈希函数可以快速定位数据
    b.范围查询
        不支持范围查询,因为哈希函数不保留数据的顺序
    c.联合索引
        不支持联合索引的最左侧原则
    d.排序支持
        不支持ORDER BY排序操作,因为哈希表不维护数据的顺序
    e.模糊查询
        无法进行模糊查询,因为哈希索引不支持部分匹配
    f.适用场景
        适合需要快速等值查询的场景

1.20 [5]聚簇索引、非聚簇索引

00.总结
    a.聚簇索引
        适合主键查询和范围查询,数据行按索引键顺序存储
        每个表只能有一个聚簇索引
    b.非聚簇索引
        适合快速查找非主键列,提供额外的访问路径
        可以有多个非聚簇索引,灵活性更高

01.聚簇索引
    a.定义
        数据表的实际数据存储按照聚簇索引的顺序进行排列
        每个表只能有一个聚簇索引,因为数据行本身只能按一种顺序存储
    b.特点
        聚簇索引的叶子节点包含了数据行本身
        数据行按索引键的顺序存储,物理上连续
    c.主键索引
        默认情况下,InnoDB使用主键作为聚簇索引
        如果没有主键,则选择第一个唯一索引作为聚簇索引
    d.优点
        对于主键查询性能较高,因为数据行按主键排序,检索更高效
        有利于范围查询,因为数据物理上连续存储

02.非聚簇索引
    a.定义
        数据表的数据行和索引数据是分开的,索引叶子节点包含了数据行的地址(通常是主键)
    b.特点
        非聚簇索引的叶子节点包含指向实际数据行的指针(主键值)
        可以有多个非聚簇索引,每个索引提供不同的排序和访问路径
    c.用途
        提供额外的索引路径,加快对非主键列的查询
        适用于需要快速查找非主键列的场景

2 索引指南

2.1 [1]定义

01.定义
    索引,类似于书籍的目录,想找到一本书的某个特定的主题,需要先找到书的目录,定位对应的页码
    MySQL 中存储引擎使用类似的方式进行查询,先去索引中查找对应的值,然后根据匹配的索引找到对应的数据行

02.优点
    提高数据的检索速度,降低数据库IO成本:使用索引的意义就是通过缩小表中需要查询的记录的数目从而加快搜索的速度
    降低数据排序的成本,降低CPU消耗:索引之所以查的快,是因为先将数据排好序,若该字段正好需要排序,则正好降低了排序的成本

03.缺点
    占用存储空间:索引实际上也是一张表,记录了主键与索引字段,一般以索引文件的形式存储在磁盘上
    降低更新表的速度:表的数据发生了变化,对应的索引也需要一起变更,从而减低的更新速度。否则索引指向的物理数据可能不对,这也是索引失效的原因之一

2.2 [1]建表:18条

00.汇总
    01.命名
    02.字段类型
    03.字段长度
    04.字段个数
    05.主键
    06.外键
    07.索引
    08.唯一索引
    09.NOT NULL
    10.存储引擎
    11.时间字段
    12.金额字段
    13.json字段
    14.大字段
    15.几余字段
    16.注释
    17.字符集
    18.排序规则

01.命名
    a.字段命名
        中国人的命名习惯就是中式英语,所以起名时每个人就有每个人的特色,在进行表设计时,尽可能的进行统一,要么全部中文拼音,要么全部英文,切记不要混用,那样真的太难受了。
        给表、字段名给个好名字,真的太重要了,最好做到见名知意。
        推荐一个起名网站,再不济就去 ChatGPT 起名。
        unbug.github.io/codelf/
        就拿用户名来说举个例子:
        正例:text 代码解读复制代码用户名:username
        反例:text 代码解读复制代码用户名:yong_hu_name,name等
        需要注意的是,见名知意不错,切记名字也不可太长。
    b.大小写
        在国产数据库中,有的会默认搞成全大写,小写的有的反而不兼容会有 Bug 产生,而站在视觉感官上来说,小写加下划线的形式更加易读,更加直观,所以在命名时,还是那句话,统一规则。
        要么全部大写加下划线,要么全部小写加下划线,禁止大小写混用。
        推荐全部小写加下划线的形式。
        大小写混用的拉出去砍了。
        正例:text 代码解读复制代码产品名称:product_name,PRODUCT_NAME
        反例:text 代码解读复制代码产品名称:product_NAME,PRODUCT_name
    c.分隔符
        在给字段起名时,很多场景下单个单词无法满足我们的命名要求,那么多个单词怎么连接呢?
        推荐使用_下划线进行连接。
        还有驼峰形式或者不使用连接符,这些都是禁止的,有的框架在使用驼峰时会遇到转换问题。
        使用连接符可读性太差,这谁家好人能一眼读出来一大长串啊是不是。
        正例:text 代码解读复制代码产品名称:product_name,PRODUCT_NAME
        反例:text 代码解读复制代码产品名称:productname,productName,product@name
    d.关键字
        上面说过起名要见名知意,但是也要避免与数据库中的关键字冲突,比如工作中经常用过的status。
        比如涉及到关键字的,可以假如业务来进行区分。
        text 代码解读复制代码创建时间:create_time
        更新时间:update_time
        删除状态:delete_status,deleted
    e.索引名
        索引的命名由索引的类型来分类,因为索引有很多种,主键、唯一、普通、联合、空间等,
        通过索引名称可以一眼看出来是普通索引还是唯一索引,或者联合索引那么这个索引的名称就是规范的。
        比如联合索引按照字段顺序进行命名,唯一索引加入前缀uniq。
    f.表名
        字段相关名称的说完了,还有表名在提一下,表的命名中,除了体现当前表含义外最好加入业务前缀。
        比如订单相关的表用order_前缀。

02.字段类型
    a.满足业务需求的情况下,尽可能选择占用存储空间小的字段类型。
    b.字段长度固定的可以选择char,不固定的可以选择varchar。
    c.是否这种true或者false的字段,可以使用bit类型。
    d.枚举字段可以tinyint类型。
    e.主键使用bigint类型。
    f.金额字段可以使用decimal或者换算单位存bigint。
    g.时间字段使用datetime或者timestamp或者转换时间戳存bigint。

03.字段长度
    上面字段类型的选择中提到了长度,接下来我们就重点说一下长度的选择。
    varchar(255) 中 255 代表的是字符长度。而在 MySQL中,除了 varchar 和 char 代表的是字符长度之外,其他的类型都是字节长度。
    bigint  的实际长度是 8 个字节,bigint(4) 代表当不满 4 个字节的时候,前面填充0(前提是开启了自动填充)。
    当超过4个字节时按照实际情况展示。
    比如现在的数据是 12345,展示的时候也是展示12345。
    但是需要注意的是有的 MySQL 客户端只会展示 4 个字节,比如展示成 1234,所以 bigint(4)中的 4 表示的是显示长度,实际占用还是8个字节。

04.字段个数
    大家在看数据库表优化时应该经常听到的就是减少表的字段个数,防止宽表的发生。
    所以我们在建表时最好控制一下字段数量,我上家单位涉及的业务类型的表,那真的是字段巨多,对于这种场景,我们可以大表拆分小表,每个表拥有一个共同的唯一ID做主键进行关联。
    建议每个表的字段数量控制在20个,如果字段太多,表中数据存储量大了之后严重影响查询效率。

05.主键
    不知道你们有没有遇到过,我是遇到过表连个主键都没有,全是普通的列,索引更不用说当然也没有了。
    之所以每个表都需要有个主键是因为,主键索引相比其他的索引在查询时可以避免回表,提升查询效率。而且主键索引也是唯一索引,可以作为业务的去重。
    在单体数据库中使用默认的自增ID做主键即可,效率还是很高的。在分布式环境中,最好还是使用递增的分布式ID算法,保证全局唯一。
    需要注意的是,主键建议保存与业务无关的值,方便后面扩展。
    分布式ID生成算法可以看下之前的这篇文章:全网最全的分布式ID分析

06.外键
    说完主键说一下外键,这个避免使用吧。
    说实话,不好用,外键本来的作用是保证数据的一致,关联表少的时候还没啥,等关联表数量上来之后,在进行删除等操作时,性能是非常差的。
    除了外键还有就是触发器以及存储过程,每次一见到开源框架中有这些就头疼。

07.索引
    表的主键索引是必须的,对于其他的索引,根据自己的业务场景进行添加即可,但是一个表的索引数量尽量也不要太多,建议单表索引数量不要超过5个。
    创建索引时尽可能的考虑索引覆盖,最左前缀,索引下推等优化方案,还不了解的链接我放下面了。
    MySQL索引(二)常见的索引优化方案有哪些
    MySQL索引(一)
    需要注意的是对于重复性较高的字段也不建议创建索引,因为这样没意义。

08.唯一索引
    这里为什么会单独把唯一索引揪出来呢,还是因为有坑啊,大家在使用唯一索引时,如果是单个字段倒还好,如果是多个字段的,那你一定要注意了,如果有 null 值的出现,唯一性约束可能会失效哦,对于唯一索引的坑下一篇文章单独拿出来讲讲。

09.NOT NULL
    建议大家在设计表时,能确保不会出现 NULL值的列设置为 NOT NULL ,这是因为当存储引擎是 Innodb 时,对于NULL值会占用更多的空间,且查询时 NULL 值也会造成索引失效,查询条件只能用时IS NULL或者IS NOT NULL 进行判断。
    因此建议能定义为NOT NULL,就定义为NOT NULL。
    定义为NOT NULL也有好处,当 INSERT 时如果漏掉了某个字段的值,直接报错提醒出来,多么明显的报错。
    还有一种情况就是在现有的表中增加字段,此时历史数据中对于新增加的字段是没有值的,因为设置NOT NULL 的字段尽量也都赋一个默认值。

10.存储引擎
    这个应该没什么说的了,大部分都是使用的 Innodb,如果不是,去看看改一下吧。如果你的业务场景适合其他引擎或者你们有自己开发的引擎,当我没说。
    如果你不知道为啥使用 Innodb,那么现在你知道了,因为 Innodb 支持事务,且性能越来越优秀。

11.时间字段
    a.datetime
        datetime 存储的时间范围更广,在MySQL中,可以表示从 1000-01-01 到 9999-12-31 之间的日期和时间。
        datetime 不涉及时区转换。
        datetime 不支持自动更新。
    b.timestamp
        timestamp 存储范围较窄,在MySQL 中,可以表示从 1970-01-01 00:00:00 UTC 到 2038-01-19 03:14:07 UTC 的日期时间。
        timestamp 通常以 UTC 存储,所以需要进行时区转换, 比较适合存储跨时区的数据。
        timestamp 在 MySQL 中还可以设置更新时间字段,设置为自动更新。
    c.说明
        需要注意的是,在给时间设置默认值时,不要设置0000-00-00 00:00:00 ,防止查询时时间转换报错。
        出了上面几种,还可以使用 bigint 存储时间戳的形式,除了可读性以及需要转换外,好像也没啥大问题,你们有用这种方式存储时间的吗。

12.金额字段
    金额字段想到的就是浮点类型 float,double,decimal等。
    而 float、double 会丢失精度就算了还是别用了,因此还是推荐你用 decimal ,但是需要注意 decimal 使用时的几个坑,还不了解没关系,链接我放下面了。
    不掌握BigDecimal的四大坑你敢用吗?
    如果你还是不想用 decimal ,那么再推荐你一种,转换为分或者更小的货币单位,使用 bigint 存储。

13.json字段
    这个字段一直是我不想用的,因为兼容不好。如果后期需要切换数据库,假如正好你切换的数据库不支持json类型,那么恭喜你,改代码吧。
    这段时间正好新需求,试了一下这个 json 字段,感觉用起来还是不错的,前提是兼容 json 格式。
    不好的地方就是对数据的处理查询上还是没有那么方便。
    一句话,能不用还是不用吧,建议 json 类型直接存储 varchar,然后代码中转换一下更好,毕竟不用考虑兼容问题啊。

14.大字段
    如果你用了json,那么不可避免的会有大字段的可能,大字段的定义就是占用存储空间多的字段。
    对于大文本如果直接定义为 text 类型,可能会浪费存储空间。如果业务可以对该字段进行一个最大长度的限制,那么我们可以使用 varchar 类型进行存储,效率更高。
    还有一个类型就是 blob ,直接存储文件内容。如果你们也这样做,建议还是换了吧,这个设计有点不合理了,
    上个存储保存个文件地址多好。

15.冗余字段
    在设计表的时候,为了查询的性能考虑,可能会冗余一些信息字段,比如说某个表中需要记录用户的 userId,当我们需要用户名称的时候,还需要通过 userId 进行关联查询获取 username ,那么我们就可以冗余 username 到我们的表中,提升我们的查询效率。
    相当于空间换时间的概念,牺牲这一点空间,减少的却是 join 查询的时间,对查询性能的提升很有帮助。
    不能光说好的地方,也有坏的地方,有存储的地方就得有维护,容易造成数据的不一致。
    所以在使用中也是根据自己的业务综合评估,选择一个更适合自己业务的方法。

16.注释
    表注释以及字段注释,与代码开发中的代码注释没差别,都得写清楚啊,假如是个状态值1、2、3、4、5的,不写注释时间长了你知道是什么意思吗?
    需要注意一点哈,写的注释注意与代码中的保持同步,别到了最后一个字段好多个含义,最后弄的自己都要分不清哪个是什么意思那不悲催了。

17.字符集
    a.gbk
        gbk 包含了 GB2312 标准中的所有字符,不支持 Unicode 标准,所以只能在中国使用,在处理多语言时能力有限。
    b.utf8
        utf8是一种可变长度的 Unicode 编码方法,兼容性也好,是一种广泛使用的标准,支持多种语言。缺点就是不支持emoji 表情。
    c.utf8mb4
        utf8mb4 是 utf8 的一个扩展,也是 MySQL 中的推荐字符集,尤其是支持表情符号和特殊字符。

18.排序规则
    a.utf8mb4_general_ci
        utf8mb4_general_ci 的排序规则对大小写是不敏感的,简单地说就是a与A相等,他会认为这俩是相同的字符。
    b.utf8mb4_bin
        utf8mb4_bin是区分大小写的,a与A会被认为是不同的字符。

2.3 [1]案例:10个

00.不推荐子查询、JOIN
    a.原因
        a.性能问题
            子查询在执行时,MySQL需要创建临时表来存储内层查询的结果,查询完毕后再删除这些临时表
            这会增加CPU和IO资源的消耗,产生慢查询
            JOIN操作本身效率也是硬伤,特别是当数据量很大时,性能难以保证
        b.索引失效
            子查询可能导致索引失效,因为MySQL会将查询强行转换为联接来执行
            这使得子查询不能首先被执行,如果外表很大,性能上会出问题
        c.查询优化器的复杂度
            子查询会影响查询优化器的判断,导致不够优化的执行计划
            相比之下,联表查询更容易被优化器理解和处理
        d.数据传输开销
            子查询可能导致大量不必要的数据传输,因为每个子查询都需要将结果返回给主查询
            而联表查询则可以通过一次查询返回所需的所有数据,减少数据传输的开销
        e.维护成本
            使用JOIN写的SQL语句在修改表的schema时比较复杂,成本较大,尤其是在系统较大时,不易维护
    b.解决
        a.应用层关联
            在业务层单表查询出数据后,作为条件给下一个单表查询,减少数据库层的负担
        b.使用IN代替子查询
            如果子查询结果集比较小,可以考虑使用“IN”操作符进行查询,这在数据量较小的情况下,查询效率更高
        c.使用WHERE EXISTS
            WHERE EXISTS是一种比“IN”更好的方案,它会检查子查询是否返回结果集,查询速度能够明显提高
        d.改写为JOIN
            使用JOIN查询来替代子查询,不需要建立临时表,速度更快,如果查询中使用索引,性能会更好
    c.案例
        01.查询所有有库存的商品信息
        02.使用EXISTS优化子查询
        03.使用JOIN代替子查询
        04.优化子查询以减少数据量
        05.使用索引覆盖
        06.使用临时表优化复杂查询
        07.使用窗口函数替代子查询
        08.优化子查询以避免全表扫描
        09.使用LIMIT子句限制子查询返回数据量
        10.使用JOIN代替子查询以利用索引

01.查询所有有库存的商品信息
    a.原始查询(使用子查询)
        SELECT * FROM products WHERE id IN (SELECT product_id FROM inventory WHERE stock > 0);
    b.优化方案(使用EXISTS)
        SELECT * FROM products WHERE EXISTS (SELECT 1 FROM inventory WHERE inventory.product_id = products.id AND inventory.stock > 0);
    c.说明
        这个优化方案可以大幅提升查询速度,改善用户体验

02.使用EXISTS优化子查询
    a.原始查询
        SELECT * FROM orders WHERE customer_id IN (SELECT customer_id FROM customers WHERE country = 'USA');
    b.优化方案
        SELECT * FROM orders WHERE EXISTS (SELECT 1 FROM customers WHERE orders.customer_id = customers.customer_id AND customers.country = 'USA');
    c.说明
        使用EXISTS代替IN子查询可以减少回表查询的次数,提高查询效率

03.使用JOIN代替子查询
    a.原始查询
        SELECT * FROM orders WHERE customer_id IN (SELECT customer_id FROM customers WHERE country = 'USA');
    b.优化方案
        SELECT o.* FROM orders o JOIN customers c ON o.customer_id = c.customer_id WHERE c.country = 'USA';
    c.说明
        使用JOIN代替子查询可以减少子查询的开销,并且更容易利用索引

04.优化子查询以减少数据量
    a.原始查询
        SELECT * FROM orders WHERE customer_id IN (SELECT customer_id FROM customers);
    b.优化方案
        SELECT * FROM orders WHERE customer_id IN (SELECT customer_id FROM customers WHERE active = 1);
    c.说明
        限制子查询返回的数据量,减少主查询需要检查的行数,提高查询效率

05.使用索引覆盖
    a.原始查询
        SELECT customer_id FROM customers WHERE country = 'USA';
    b.优化方案
        CREATE INDEX idx_country ON customers(country);
        SELECT customer_id FROM customers WHERE country = 'USA';
    c.说明
        为country字段创建索引,使得子查询可以直接在索引中找到数据,避免回表查询。

06.使用临时表优化复杂查询
    a.原始查询
        SELECT * FROM orders WHERE customer_id IN (SELECT customer_id FROM customers WHERE last_order_date > '2023-01-01');
    b.优化方案
        CREATE TEMPORARY TABLE temp_customers AS SELECT customer_id FROM customers WHERE last_order_date > '2023-01-01';
        SELECT * FROM orders WHERE customer_id IN (SELECT customer_id FROM temp_customers);
    c.说明
        对于复杂的子查询,使用临时表存储中间结果,简化查询并提高性能。

07.使用窗口函数替代子查询
    a.原始查询
        SELECT employee_id, salary, (SELECT AVG(salary) FROM employees WHERE department_id = e.department_id) AS avg_salary FROM employees e;
    b.优化方案
        SELECT employee_id, salary, AVG(salary) OVER (PARTITION BY department_id) AS avg_salary FROM employees;
    c.说明
        使用窗口函数替代子查询,提高查询效率。

08.优化子查询以避免全表扫描
    a.原始查询
        SELECT * FROM users WHERE username IN (SELECT username FROM orders WHERE order_date = '2024-01-01');
    b.优化方案
        CREATE INDEX idx_order_date ON orders(order_date);
        SELECT * FROM users WHERE username IN (SELECT username FROM orders WHERE order_date = '2024-01-01');
    c.说明
        为order_date字段创建索引,避免全表扫描,提高子查询效率。

09.使用LIMIT子句限制子查询返回数据量
    a.原始查询
        SELECT * FROM orders WHERE customer_id IN (SELECT customer_id FROM customers WHERE country = 'USA');
    b.优化方案
        SELECT * FROM orders WHERE customer_id IN (SELECT customer_id FROM customers WHERE country = 'USA' LIMIT 100);
    c.说明
        使用LIMIT子句限制子查询返回的数据量,减少主查询需要处理的数据量,提高查询效率。

10.使用JOIN代替子查询以利用索引
    a.原始查询
        SELECT * FROM transactions WHERE product_id IN (SELECT product_id FROM products WHERE category = 'Equity');
    b.优化方案
        SELECT t.* FROM transactions t JOIN products p ON t.product_id = p.product_id WHERE p.category = 'Equity';
    c.说明
        使用JOIN代替子查询,并且可以更容易地利用products表上的category索引

2.4 [1]失效:20个

00.汇总
    01.违反最左前缀原则
    02.索引例参与运算
    03.隐式类型转换
    04.OR连接非索引例
    05.范围查询阻断索引
    06.不等于(!= / <>)查询
    07.LIKE左模糊匹配
    08.索引选择性过低
    09.ORDERBY排序方向混乱
    10.使用NOTIN条件
    11.多表JOIN字符集不一致
    12.使用函数处理索引例
    13.使用变量表达式
    14.索引|列存在NULL值
    15.分页深度过大
    16.使用MATCH AGAINST全文索引
    17.强制类型转换
    18.统计信息不准确
    19.使用派生表
    20.索引合并效率低下

01.违反最左前缀原则
    a.场景
        联合索引(a,b,c),但查询条件仅包含b和c
    b.原理
        B+树按a->b->c顺序构建,缺失最左列导致无法定位起始节点
    c.案例
        -- 失效
        SELECT * FROM table WHERE b=2 AND c=3;
        -- 有效
        SELECT * FROM table WHERE a=1 AND b=2;

02.索引列参与运算
    a.场景
        WHERE a+1 = 5
    b.原理
        索引存储原始值,计算后的值无法匹配索引结构
    c.案例
        -- 失效
        SELECT * FROM table WHERE YEAR(create_time) = 2023;
        -- 有效
        SELECT * FROM table WHERE create_time BETWEEN '2023-01-01' AND '2023-12-31';

03.隐式类型转换
    a.场景
        字符串字段使用数字查询WHERE code = 123(code为VARCHAR)
    b.原理
        MySQL执行类型转换相当于调用CAST()函数
    c.案例
        -- 失效(code为VARCHAR)
        SELECT * FROM table WHERE code = 123;
        -- 有效
        SELECT * FROM table WHERE code = '123';

04.OR连接非索引列
    a.场景
        WHERE a=1 OR b=2(仅a有索引)
    b.原理
        OR条件需要全表扫描验证非索引列
    c.案例
        -- 失效(假设b无索引)
        SELECT * FROM table WHERE a=1 OR b=2;
        -- 优化方案
        SELECT * FROM table WHERE a=1
        UNION
        SELECT * FROM table WHERE b=2;

05.范围查询阻断索引
    a.场景
        联合索引(a,b,c),条件a>1 AND b=2
    b.原理
        范围查询后,后续索引列无法保证有序性
    c.案例
        -- 仅使用a的索引
        SELECT * FROM table WHERE a>1 AND b=2;
        -- 有效使用索引
        SELECT * FROM table WHERE a=1 AND b=2;

06.不等于(!= / <>)查询
    a.场景
        WHERE status != 1
    b.原理
        需要扫描大部分数据时,优化器放弃索引
    c.例外
        覆盖索引时可能仍使用索引

07.LIKE左模糊匹配
    a.场景
        WHERE name LIKE '%张'
    b.原理
        B+树无法反向匹配字符串后缀
    c.优化
        LIKE '张%'可使用索引

08.索引选择性过低
    a.场景
        性别字段索引(值分布集中)
    b.原理
        优化器认为全表扫描成本更低
    c.判断标准
        COUNT(DISTINCT col)/COUNT(*) < 0.2时慎用索引

09.ORDER BY排序方向混乱
    a.场景
        ORDER BY a ASC, b DESC(索引为a ASC, b ASC)
    b.原理
        排序方向不一致导致无法利用索引排序

10.使用NOT IN条件
    a.场景
        WHERE id NOT IN (1,2,3)
    b.原理
        等价于多个不等于条件,需全表验证

11.多表JOIN字符集不一致
    a.场景
        关联字段字符集不同(utf8 vs utf8mb4)
    b.原理
        字符集转换导致索引失效

12.使用函数处理索引列
    a.场景
        WHERE LOWER(name) = 'alice'
    b.原理
        函数处理后的值与索引存储值不匹配

13.使用变量表达式
    a.场景
        WHERE a = @var(变量类型与列类型不一致)
    b.原理
        隐式类型转换导致索引失效

14.索引列存在NULL值
    a.场景
        WHERE col IS NULL(允许NULL的列)
    b.原理
        NULL值在索引中特殊存储,可能不走索引

15.分页深度过大
    a.场景
        LIMIT 100000, 10
    b.原理
        优化器可能放弃使用索引进行偏移量计算

16.使用MATCH AGAINST全文索引
    a.场景
        混合使用全文索引和普通索引
    b.原理
        两种索引机制不兼容导致部分索引失效

17.强制类型转换
    a.场景
        WHERE CAST(id AS CHAR) = '123'
    b.原理
        显式类型转换破坏索引匹配

18.统计信息不准确
    a.场景
        数据分布变化后未执行ANALYZE TABLE
    b.原理
        优化器基于过时统计信息做出错误决策

19.使用派生表
    a.场景
        SELECT * FROM (SELECT * FROM t) WHERE id=1
    b.原理
        派生表可能破坏索引下推优化

20.索引合并效率低下
    a.场景
        同时使用多个单列索引
    b.原理
        索引合并的代价可能高于全表扫描

2.5 [1]优化:30个

00.汇总
    01.慢查询
    02.连接查询性能问题
    03.子查询性能问题
    04.过度使用通配符%的LIKE查询
    05.大批量插入或更新
    06.频繁的重复查询
    07.过度使用分组和聚合函数
    08.大量重复数据的查询
    09.过度使用OR条件的查询
    10.大型数据分页查询
    11.使用不必要的列
    12.频繁更新的表
    13.未使用索引的外键约束
    14.大型查询的分批处理
    15.未使用存储过程的重复逻辑
    16.未使用合适的数据类型
    17.大量写操作导致的锁竞争
    18.频繁使用数据库函数和表达式
    19.未使用合适的索引策略
    20.大量数据的联合操作
    21.数据分布不均匀的索引
    22.过度使用子查询
    23.未使用批量操作
    24.过度使用内存表
    25.缺乏定期统计和优化
    26.未使用合适的数据库引擎
    27.使用强制类型转换
    28.未优化的长事务
    29.未优化的存储过程
    30.未考虑DB服务器配置和硬件资源

01.慢查询
    a.问题原因
        通常是由于查询中涉及大量数据、缺乏索引或者SQL语句本身写得不够有效率所导致
    b.优化策略
        添加索引以加速检索
        优化SQL查询语句,使用更有效率的方法
        限制返回的行数
    c.示例
        -- 原始查询
        SELECT COUNT(*) FROM orders WHERE order_date BETWEEN '2024-01-01' AND '2024-03-01';
        -- 优化后的查询
        CREATE INDEX idx_order_date ON orders (order_date);
        SELECT COUNT(*) FROM orders WHERE order_date BETWEEN '2024-01-01' AND '2024-03-01';

02.连接查询性能问题
    a.问题原因
        连接查询(特别是跨多个表的连接)可能会导致性能下降,尤其是在没有合适索引的情况下
    b.优化策略
        确保连接字段上存在索引
        尽可能使用INNER JOIN替代其他类型的连接,因为INNER JOIN的性能通常更好
    c.示例
        -- 原始查询
        SELECT o.*, c.name
        FROM orders o
        JOIN customers c ON o.customer_id = c.id;
        -- 优化后的查询
        CREATE INDEX idx_customer_id ON orders (customer_id);
        SELECT o.*, c.name
        FROM orders o
        JOIN customers c ON o.customer_id = c.id;

03.子查询性能问题
    a.问题原因
        子查询可能会执行多次,导致性能下降
    b.优化策略
        将子查询转换为连接查询或者联合查询
        优化子查询本身,确保其尽可能高效
    c.示例
        -- 原始查询
        SELECT product_id, product_name
        FROM products
        WHERE price > (SELECT AVG(price) FROM products);
        -- 优化后的查询
        SELECT p.product_id, p.product_name
        FROM products p
        JOIN (SELECT AVG(price) AS avg_price FROM products) AS avg_table
        WHERE p.price > avg_table.avg_price;

04.过度使用通配符%的LIKE查询
    a.问题原因
        以%开头的 LIKE 查询会导致索引失效,因此性能较差
    b.优化策略
        尽可能避免在LIKE查询中使用%开头的通配符
        如果不可避免,考虑全文搜索或者其他更适合的索引方法
    c.示例
        -- 原始查询
        SELECT * FROM products WHERE product_name LIKE 'abc%';
        -- 优化后的查询
        SELECT * FROM products WHERE product_name >= 'abc' AND product_name < 'abd';

05.大批量插入或更新
    a.问题原因
        大量数据的插入或更新可能会导致性能下降,特别是在事务中进行时
    b.优化策略
        使用批量插入或更新操作,减少事务的开销
        禁用或延迟索引更新,以减少插入或更新操作的负担
    c.示例
        -- 原始插入操作
        INSERT INTO orders (order_id, customer_id, order_date) VALUES (1, 1001, '2024-03-15');
        INSERT INTO orders (order_id, customer_id, order_date) VALUES (2, 1002, '2024-03-15');
        -- 优化后的批量插入操作
        INSERT INTO orders (order_id, customer_id, order_date) VALUES
        (1, 1001, '2024-03-15'), (2, 1002, '2024-03-15'), ...;

06.频繁的重复查询
    a.问题原因
        重复执行相同的查询会造成资源浪费,尤其是在查询结果较大或复杂时。
    b.优化策略
        使用缓存技术,如数据库查询缓存或应用程序级别的缓存,避免重复查询相同的数据
        考虑使用预编译语句或存储过程,以减少解析和编译的开销
    c.示例
        -- 原始查询
        SELECT stock_quantity FROM products WHERE product_id = 123;
        -- 优化后的查询(使用缓存)
        SELECT stock_quantity FROM cached_stock WHERE product_id = 123;

07.过度使用分组和聚合函数
    a.问题原因
        对大数据集进行分组和聚合操作可能会导致性能下降,尤其是在没有适当索引或者内存不足的情况下
    b.优化策略
        确保使用合适的索引以加速分组和聚合操作
        考虑使用汇总表(Summary Tables)或者其他预计算技术,避免在运行时进行大量聚合计算
    c.示例
        -- 原始查询
        SELECT product_id, AVG(amount) AS avg_amount
        FROM sales
        GROUP BY product_id;
        -- 优化后的查询(使用汇总表)
        SELECT product_id, avg_amount
        FROM product_sales_summary;

08.大量重复数据的查询
    a.问题原因
        当查询中存在大量重复的数据时,可能会导致不必要的资源消耗,降低查询效率
    b.优化策略
        使用 DISTINCT 关键字去除重复数据,但要注意使用得当,避免过多的内存消耗
        考虑是否有更好的数据模型或者查询方式来避免重复数据的产生
    c.示例
        -- 原始查询
        SELECT COUNT(customer_id) FROM orders;
        -- 优化后的查询(使用DISTINCT)
        SELECT COUNT(DISTINCT customer_id) FROM orders;

09.过度使用 OR 条件的查询
    a.问题原因
        当查询中存在多个OR条件时,数据库可能无法有效使用索引,导致全表扫描,降低性能
    b.优化策略
        将OR条件转换为使用IN或者UNION操作,以提高查询效率
        确保在OR条件中的每个字段都有索引,以帮助数据库优化查询计划
    c.示例
        -- 原始查询
        SELECT COUNT(*) FROM orders WHERE customer_id = 1001 OR product_id = 123;
        -- 优化后的查询(使用UNION)
        SELECT COUNT(*) FROM (
            SELECT * FROM orders WHERE customer_id = 1001
            UNION
            SELECT * FROM orders WHERE product_id = 123
        ) AS combined_orders;

10.大型数据分页查询
    a.问题原因
        当需要获取大型数据集的分页结果时,传统的LIMIT OFFSET方法可能导致性能下降,特别是在偏移量较大时
    b.优化策略
        使用基于游标的分页(cursor-based pagination)来避免OFFSET的性能问题
        考虑在应用层面或者缓存层面进行分页处理,减轻数据库的负担
    c.示例
        -- 原始分页查询
        SELECT * FROM orders LIMIT 50 OFFSET 1000;
        -- 优化后的查询(使用游标)
        SELECT * FROM orders WHERE order_id > (SELECT order_id FROM orders ORDER BY order_id LIMIT 1 OFFSET 1000) LIMIT 50;

11.使用不必要的列
    a.问题原因
        当查询中包含不必要的列时,数据库引擎可能会浪费时间和资源来获取这些不需要的数据
    b.优化策略
        只选择查询中需要的列,避免选择不必要的列
        如果存在大对象(如BLOB或CLOB),尽量避免在查询中选择这些对象,除非确实需要
    c.示例
        -- 原始查询
        SELECT * FROM orders;
        -- 优化后的查询(只选择需要的列)
        SELECT order_id, order_date FROM orders;

12.频繁更新的表
    a.问题原因
        当表上有大量更新操作时,可能会导致表锁定和性能下降
    b.优化策略
        尽可能减少更新操作,考虑是否可以将数据操作转移到非热点表上
        将更新操作批量处理,以减少事务开销
    c.示例
        -- 原始更新操作
        UPDATE login_logs SET last_login = NOW() WHERE user_id = 123;
        -- 优化后的更新操作(批量处理)
        UPDATE login_logs
        SET last_login = NOW()
        WHERE user_id IN (123, 124, 125);

13.未使用索引的外键约束
    a.问题原因
        虽然外键约束可以确保数据完整性,但如果没有为外键字段创建索引,可能会导致性能下降
    b.优化策略
        确保为外键字段创建索引,以加速相关的数据操作
        在数据库设计阶段考虑外键索引的需要性
    c.示例
        -- 创建外键约束
        ALTER TABLE orders ADD CONSTRAINT fk_customer_id FOREIGN KEY (customer_id) REFERENCES customers(id);
        -- 为外键字段创建索引
        CREATE INDEX idx_customer_id ON orders (customer_id);

14.大型查询的分批处理
    a.问题原因
        当查询涉及大量数据时,一次性处理可能会导致内存和 CPU 资源过度消耗
    b.优化策略
        将大型查询分成多个较小的批次进行处理,以减少每个批次的负载
        使用游标或者分页技术来处理大型数据集
    c.示例
        -- 原始查询
        SELECT * FROM users;
        -- 优化后的查询(使用游标)
        DECLARE cursor_name CURSOR FOR SELECT * FROM users;
        OPEN cursor_name;
        FETCH NEXT FROM cursor_name;
        -- 逐行处理数据...
        CLOSE cursor_name;

15.未使用存储过程的重复逻辑
    a.问题原因
        当有重复的业务逻辑需要在多个地方执行时,未使用存储过程可能导致代码重复和维护困难
    b.优化策略
        将重复的逻辑封装到存储过程中,以便在需要时可以重复使用
        使用存储过程可以减少网络通信开销,并且可以通过权限控制提高安全性
    c.示例
        -- 原始逻辑
        SELECT SUM(total_amount) FROM orders WHERE customer_id = 123;
        -- 优化后的存储过程
        CREATE PROCEDURE CalculateOrderTotal(IN customer_id INT)
        BEGIN
          SELECT SUM(total_amount) FROM orders WHERE customer_id = customer_id;
        END;

16.未使用合适的数据类型
    a.问题原因
        选择不恰当的数据类型可能会导致存储空间浪费和性能下降
    b.优化策略
        使用最合适的数据类型来存储数据,避免过大或过小的数据类型
        尽量使用整型来存储整数数据,使用定点数或浮点数来存储小数数据
    c.示例
        -- 原始表定义
        CREATE TABLE orders (order_id INT, quantity VARCHAR(10));
        -- 优化后的表定义
        CREATE TABLE orders (order_id INT, quantity INT);

17.大量写操作导致的锁竞争
    a.问题原因
        当有大量写操作时,可能会导致锁竞争,降低数据库的并发性能
    b.优化策略
        尽量减少长时间持有锁的操作,例如尽快提交或回滚事务
        考虑分批处理写操作,以减少锁竞争的影响
    c.示例
        -- 原始下单操作
        BEGIN TRANSACTION;
        INSERT INTO orders (order_id, customer_id, order_date) VALUES (1, 1001, '2024-03-15');
        -- 更多写操作...
        COMMIT;
        -- 优化后的下单操作(分批处理)
        BEGIN TRANSACTION;
        INSERT INTO orders (order_id, customer_id, order_date) VALUES (1, 1001, '2024-03-15');
        COMMIT;
        -- 分批处理更多下单操作...

18.频繁使用数据库函数和表达式
    a.问题原因
        频繁使用复杂的数据库函数和表达式可能会导致计算开销增加,影响查询性能
    b.优化策略
        尽量减少使用数据库函数和表达式,尽可能在应用层或缓存层进行计算
        对于经常使用的表达式,考虑将其结果存储在列中,以减少重复计算
    c.示例
        -- 原始查询
        SELECT SUM(total_amount * (1 + tax_rate)) FROM orders;
        -- 优化后的查询(使用预计算结果)
        ALTER TABLE orders ADD COLUMN total_with_tax DECIMAL(10, 2);
        UPDATE orders SET total_with_tax = total_amount * (1 + tax_rate);
        SELECT SUM(total_with_tax) FROM orders;

19.未使用合适的索引策略
    a.问题原因
        选择不当的索引策略可能会导致索引失效或不必要的索引开销
    b.优化策略
        定期检查和优化索引,确保其适应实际查询需求
        根据查询模式选择合适的索引类型,例如B-Tree索引、哈希索引等
    c.示例
        -- 原始索引策略
        CREATE INDEX idx_order_date ON orders (order_date);
        -- 优化后的索引策略(创建覆盖索引)
        CREATE INDEX idx_order_date_amount ON orders (order_date, total_amount);

20.大量数据的联合操作
    a.问题原因
        在执行大量数据的联合操作(UNION、UNION ALL)时,数据库可能需要对结果集进行排序和去重,导致性能下降
    b.优化策略
        尽量避免使用联合操作,如果必须使用,确保各个子查询的结果集尽量小且已经过滤,以减少排序和去重的开销
        考虑使用其他方法代替联合操作,如JOIN、子查询等
    c.示例
        -- 原始查询
        SELECT * FROM table1
        UNION
        SELECT * FROM table2;
        -- 优化后的查询(使用JOIN)
        SELECT DISTINCT t1.* FROM table1 t1
        JOIN table2 t2 ON t1.id = t2.id;

21.数据分布不均匀的索引
    a.问题原因
        如果索引数据分布不均匀,可能会导致部分索引页过度填充,而另一部分过度稀疏,影响查询性能
    b.优化策略
        定期重新组织索引或重新构建索引,以平衡数据分布
        根据具体情况,考虑使用分区表或者其他分布均匀的数据结构
    c.示例
        -- 原始索引
        CREATE INDEX idx_username ON users (username);
        -- 优化后的索引(重新组织)
        ALTER INDEX idx_username REORGANIZE;

22.过度使用子查询
    a.问题原因
        过度使用子查询可能会导致查询执行效率低下,尤其是嵌套子查询的情况下
    b.优化策略
        将子查询转换为JOIN操作,以减少查询的嵌套层次
        尽量避免在SELECT子句中使用子查询
    c.示例
        -- 原始查询
        SELECT user_id, (SELECT MAX(order_date) FROM orders WHERE orders.user_id = users.user_id) AS latest_order_date FROM users;
        -- 优化后的查询(使用JOIN)
        SELECT users.user_id, MAX(orders.order_date) AS latest_order_date
        FROM users
        LEFT JOIN orders ON users.user_id = orders.user_id
        GROUP BY users.user_id;

23.未使用批量操作
    a.问题原因
        频繁执行单条数据操作可能会导致数据库连接开销增加,从而降低性能
    b.优化策略
        尽量使用批量操作来替代单条数据操作,以减少数据库连接开销
        对于大批量数据的操作,可以考虑使用批量导入或批量更新的方式,减少交互次数
    c.示例
        -- 原始更新操作
        UPDATE orders SET status = 'shipped' WHERE order_id = 1;
        UPDATE orders SET status = 'shipped' WHERE order_id = 2;
        -- 更多单条更新操作...
        -- 优化后的批量更新操作
        UPDATE orders SET status = 'shipped' WHERE order_id IN (1, 2, ...);

24.过度使用内存表
    a.问题原因
        虽然内存表在某些情况下可以提高查询速度,但过度使用内存表可能会导致内存消耗过大,甚至影响系统的稳定性
    b.优化策略
        评估内存表的使用情况,确保仅在适当的情况下使用内存表
        对于大数据量或需要持久化存储的数据,避免使用内存表,而是选择合适的磁盘存储方式
    c.示例
        -- 原始查询(使用内存表进行排序)
        SELECT * FROM orders ORDER BY order_date;
        -- 优化后的查询(使用磁盘排序)
        SELECT * FROM orders ORDER BY order_date;

25.缺乏定期统计和优化
    a.问题原因
        未定期统计表的数据分布和索引使用情况,未进行数据库性能优化可能导致查询性能逐渐下降
    b.优化策略
        定期执行统计分析,评估表的数据分布和索引使用情况
        根据统计结果,定期优化数据库的索引、表结构或者查询语句
    c.示例
        -- 定期执行统计分析
        ANALYZE TABLE orders;
        -- 定期优化数据库索引
        OPTIMIZE TABLE orders;

26.未使用合适的数据库引擎
    a.问题原因
        选择不合适的数据库引擎可能导致性能下降,无法充分发挥数据库的优势
    b.优化策略
        根据具体的业务需求和性能要求选择合适的数据库引擎,如InnoDB、MyISAM等
        评估和比较不同数据库引擎的特性,选择最适合当前业务场景的引擎
    c.示例
        -- 使用InnoDB引擎存储事务性数据
        CREATE TABLE orders (order_id INT, customer_id INT, order_date DATE) ENGINE=InnoDB;
        -- 使用MyISAM引擎存储非事务性数据
        CREATE TABLE logs (log_id INT, log_message TEXT) ENGINE=MyISAM;

27.使用强制类型转换
    a.问题原因
        频繁使用强制类型转换可能会导致查询执行效率低下,尤其是在大数据量的情况下
    b.优化策略
        尽量避免在查询中使用强制类型转换,而是尽量保持数据类型一致性
        如果必须进行类型转换,尽可能在应用程序层面完成,而不是在数据库层面
    c.示例
        -- 原始查询(使用强制类型转换)
        SELECT * FROM orders WHERE DATE(order_date) = '2024-03-15';
        -- 优化后的查询(避免强制类型转换)
        SELECT * FROM orders WHERE order_date = '2024-03-15';

28.未优化的长事务
    a.问题原因
        长时间运行的事务可能会阻塞其他事务,影响数据库的并发性能
    b.优化策略
        尽量减少事务的持续时间,尽早提交或者回滚事务
        如果事务必须长时间运行,考虑是否可以使用行级锁而不是表级锁
    c.示例
        -- 原始事务
        BEGIN TRANSACTION;
        -- 处理订单数据...
        COMMIT;

        -- 优化后的事务(分批提交)
        BEGIN TRANSACTION;
        -- 处理部分订单数据...
        COMMIT;
        -- 继续处理剩余的订单数据...

29.未优化的存储过程
    a.问题原因
        存储过程中可能存在未优化的SQL语句,导致存储过程执行效率低下
    b.优化策略
        定期评估和优化存储过程中的SQL语句,确保其执行效率
        使用合适的索引、避免不必要的循环或递归等方式优化存储过程
    c.示例
        -- 原始存储过程
        CREATE PROCEDURE CalculateOrderTotal(IN order_id INT)
        BEGIN
          DECLARE total DECIMAL(10, 2);
          SELECT SUM(amount) INTO total FROM order_details WHERE order_id = order_id;
          SELECT total;
        END;

        -- 优化后的存储过程(使用索引)
        CREATE PROCEDURE CalculateOrderTotal(IN order_id INT)
        BEGIN
          DECLARE total DECIMAL(10, 2);
          SELECT SUM(amount) INTO total FROM order_details WHERE order_id = order_id;
          SELECT total;
        END;

30.未考虑DB服务器配置和硬件资源
    a.问题原因
        数据库服务器配置不合理或硬件资源不足可能会导致数据库性能低下。
    b.优化策略
        根据数据库负载情况和性能需求合理配置数据库服务器的硬件资源,包括CPU、内存、磁盘等
        定期监控数据库服务器的性能指标,进行性能调优和升级。
    c.示例
        -- 原始查询数据库服务器性能指标
        SHOW STATUS;
        -- 优化后的升级硬件资源
        增加CPU核心数、内存容量或者使用更快的磁盘。

2.6 [1]类型:3类,6种

01.三种分类
    a.按照功能分类
        普通索引
        唯一索引
        主键索引
        复合索引
        外键索引
        全文索引
    b.数据结构
        B+树索引
        哈希索引
    c.存储位置
        聚簇索引
        非聚簇索引

02.六种类型
    索引类型   特点                                       作用                                    适用场景
    普通索引   允许重复,基本的查询优化工具                 提高查询效率,支持范围查询、排序         高频查询字段,如状态、分类、外键
    唯一索引   保证字段值唯一                              优化查询,同时提供数据唯一性约束         用户名、邮箱、身份证号等需要唯一性的字段
    主键索引   表的唯一标识,自动创建聚簇索引               快速定位记录,作为其他表的外键引用       表的主键字段(如 id)
    复合索引   多字段联合索引,遵循最左前缀匹配原则         优化多条件查询,减少单独索引的存储开销    多条件联合查询场景,如 (字段A, 字段B)
    全文索引   适用于全文匹配,支持非结构化文本的搜索优化    提供文本字段的高效关键词查询            文本搜索场景,如文章内容或商品描述
    空间索引   适用于地理空间数据类型查询                   提供地理数据(坐标、区域)的快速定位     地图服务或GIS应用

2.7 [1]原理:B-Tree索引

00.总结
    B-Tree索引适用于大多数查询场景,支持范围查询和排序
    Hash索引适用于等值查询,但不支持范围查询
    最左匹配原则是复合索引的一个重要特性,影响索引的使用方式
    InnoDB的聚簇索引在主键查询时更高效,而MyISAM的非聚簇索引在某些场景下可能需要额外的寻址操作

01.两种索引方式
    a.B-Tree索引
        B-Tree索引是最常见的索引类型,适用于大多数的数据库引擎
        B-Tree索引通过维护一个平衡的树结构来加速数据的查找、插入和删除操作
        B+Tree是B-Tree的一种变体,通常用于数据库索引。B+Tree的叶子节点存储实际的数据或指向数据的指针
    b.Hash索引
        Hash索引通过哈希函数将键值映射到哈希表中,以实现快速查找
        适用于等值查询(如=),但不支持范围查询(如<、>)
        Hash索引不维护数据的顺序,因此不支持排序操作

02.最左匹配特性
    当B+Tree的数据项是复合的数据结构(如索引 (name, age, sex))时,B+Tree是按照从左到右的顺序来建立搜索树的
    这意味着在查询时,索引可以用于匹配从左开始的连续列。例如,索引可以用于查询name或name, age,但不能用于仅查询age或sex

03.MyISAM索引与InnoDB索引的区别
    a.InnoDB索引
        聚簇索引:InnoDB的主键索引是聚簇索引,叶子节点存储着行数据。因此,主键索引查询非常高效
        非主键索引:叶子节点存储的是主键和其他带索引的列数据。查询时,如果能做到覆盖索引(即查询的列都在索引中),会非常高效
    b.MyISAM索引
        非聚簇索引:MyISAM的索引是非聚簇索引,叶子节点存储的是行数据的地址。查询时需要通过地址再寻址一次才能得到数据

2.8 [2]优化:语法顺序

01.大小写
    SQL不区分大小写
    Linux区分大小写

02.MySQL语法顺序
    SELECT
    FROM
    JOIN
    ON
    WHERE
    GROUP BY
    HAVING
    UNION
    ORDER BY
    LIMIT

03.SQL执行顺序
    (8)   SELECT
    (9)   DISTINCT
    (11)  <Top Num> <select list>
    (1)   FROM [left_table]
    (3)   <join_type> JOIN <right_table>
    (2)   ON <join condition>
    (4)   WHERE <where condition>
    (5)   GROUP BY <group_by_list>
    (6)   WITH <CUBE | RollUP>
    (7)   HAVING <having_condition>
    (10)  ORDER BY <order by_list>   ASC升序 | DESC降序

    f    o  j    w     g       w    h      s      d        o      top
    from on join where groupby with having select distinct orderby top

    from                2张表生成一个笛卡尔积                 生成“虚表1”
    on                  2张表进行一个关联匹配                 生成“虚表2”
    join                左/右关联,根据左/右表添加一个外部行  生成“虚表3”
    where               行筛选                                生成“虚表4”
    groupby             列分组                                生成“虚表5”
    with                所选列中值的组合聚合                  生成“虚表6”
    having              符合分组条件进行筛选                  生成“虚表7”
    select              选择列                                生成“虚表8”
    distinct            删除重复数据行                        生成“虚表9”
    orderby             排序生成游标                          生成“虚表10”
    <Top 数字>          选择前几条                            生成“虚表11”
    <select list>       将执行list结果返回

2.9 [2]优化:优化汇总

00.总结优化
    1.避免SELECT*,,只查询必要的字段
    2.避免在SQL中进行函数计算等操作,使得无法命中索引
    3.避免使用%LIKE
    4.联合索引不满足最左匹配原则
    5.无索引字段进行order by
    6.连表查询但对应字段字符集不同

01.常见优化1
    00.常见优化
        临时表stat.*允许使用,速度加快
        不使用||,使用CONCAT('你好', '世界')
        前端筛选框,写到最小表的WHERE语句中,缩小筛选范围
        前端筛选框,SELECT $radio/100.0 AS val, '$sDay' AS sDay, '$eDay' AS eDay FROM DUAL; 跟大表连接ON 1=1,速度会变慢
        GROUP BY,尽可能分组字段变少,GROUP BY XL_ID, IMAXLONG_20 执行速度快,GROUP BY XL_ID, XL_NAME, IMAXLONG_20, VOLTAGE_TYPE 执行速度慢
        WHERE 条件1 && 条件2 || 条件3,每个条件就是一个boolean,&&代表AND,||代表OR
    01.常见优化1
        where子句:【日期:BETWEEN走索引,>=<不走索引】、【in代替!=、!<>】、【exists代替in、not exists替代not in】、【INSTR替代like、not like】、【in代替or、union all代替or,between代替or】
        NULL值:where中使用默认值代替null,查询时尽量不要对字段做空值判断
        等号前:尽量避免在where条件中=前对字段做任何运算,避免在where中对字段进行表达式操作,避免在索引列上使用内置函数
        等号后:where后面的字段,留意其数据类型的隐式转换。如NAME=110
        where 1=1:当数据量大时,避免使用where 1=1的条件,默认使用该条件,数据库引擎会放弃索引进行全表扫描。
        过滤数据多的条件往前放:调整Where字句中的连接顺序,最快速度缩小结果集
    02.常见优化2
        避免子查询(SELECT * FROM t1 WHERE id = (SELECT id FROM t2 WHERE name = 'chackca')),使用join代替
        inner join 、left join、right join,优先使用inner join
        避免使用HAVING字句,提高group by语句的效率
        order by条件要与where中条件一致,否则order by不会利用索引进行排序
        不建议UNION操作符,推荐UNION ALL操作符
        去重distinct过滤字段要少

02.常见优化2
    01.常见索引
        建立索引:常用查询条件和连接条件的列上建立索引、利用覆盖索引、适当使用前缀索引、正确使用联合索引,避免过多的列使用复合索引、更新频繁的列慎用索引、避免范围查询数据量过多
        关于临时表
        表连接不宜太多,一般5个以内
        索引不宜太多,一般5个以内
        索引不适合建在有大量重复数据的字段上,比如性别,排序字段应创建索引
        复合索引最左特性,where条件仅包含复合索引非前置列
        复合索引最左特性
        SQL语句没有匹配到索引时使用表锁、行级锁增删改查匹配到索引时使用行锁
    02.常见建表
        尽量使用数值替代字符串类型
        使用varchar代替char
        尽量把所有列定义为NOT
        批量插入性能提升
        清空表时优先使用truncate
        操作delete或者update语句,加个limit或者循环分批次删除
    03.多表优化
        多表关联查询时,小表在前,大表在后
        先2、3个小表组建成1个表,再将这个表distinct去重,最后将这个表与大表连接
        在MySQL中,执行 from 后的表关联查询是从左往右执行的(Oracle相反),第一张表会涉及到全表扫描,所以将小表放在前面,先扫小表,扫描快效率较高,在扫描后面的大表,或许只扫描大表的前100行就符合返回条件并return了。例如:表1有50条数据,表2有30亿条数据
        使用表的别名:当在SQL语句中连接多个表时,请使用表的别名并把别名前缀于每个列名上。这样就可以减少解析的时间并减少哪些友列名歧义引起的语法错误。
    04.SQL务必要写完整,不要使用缩写法
        select user_name "姓名" from zz_users;                                        --为字段取别名的简单写法
        select user_name as "姓名" from zz_users;                                     --为字段取别名的完整写法
        select * from 表1,表2... where 表1.字段 = 表2.字段 ...;                       --内连表查询的简单写法
        select * from 表1 别名1 inner join 表2 别名2 on 别名1.字段 = 别名2.字段;      --内连表查询的完整写法
        因为隐式的这种写法,在MySQL底层都需要做一次转换,将其转换为完整的写法,因此简写的SQL会比完整的SQL多一步转化过程,如果你考虑极致程度的优化,也切记将SQL写成完整的语法。

2.10 [2]优化:优化手段1

01.避免在where子句中使用【日期:BETWEEN走索引,>=<不走索引】、【!=、!<>】、【exists代替in、not exists替代not in】、【INSTR替代like、not like】、【in代替or、union all代替or,between代替or】
    日期    Hutools的DateUtil.parse(),代替Java的sdf
    日期    SQL尽可能使用RQ时,BETWEEN走索引,>=<不走索引
    日期    SQL尽可能存本来类型,不使用CAST转换,Java去做类型转换
    日期    DATE_FORMAT是函数,TO_CHAR、TO_DATE、TO_TIMESTAMP涉及到类型转换
    日期    SQL存在隐式转换,有3种精确度为日、分、秒;使用MONTH(SYSDATE)、YEAR(SYSDATE)函数控制精度
    ---------------------------------------------------------------------------------------------------------
    反例    SELECT * FROM user WHERE salary!=5000
    反例    SELECT * FROM user WHERE salary<>5000
    理由    使用!=和<>很可能会让索引失效
            应尽量避免在where子句中使用!=或<>操作符,否则引擎将放弃使用索引而进行全表扫描
            实现业务优先,实在没办法,就只能使用,并不是不能使用
    ---------------------------------------------------------------------------------------------------------
    示例    SELECT * FROM t WHERE id IN (2,3)
    优化    如果是连续数值,可以用between代替。如下:SELECT * FROM t WHERE id BETWEEN 2 AND 3
    优化    如果是子查询,可以用exists代替。如下:
            select * from A where A.id in (select id from B);                     --不走索引
            select * from A where exists (select * from B where B.id = A.id);     --走索引
    ---------------------------------------------------------------------------------------------------------
    反例    select * from citys where name like '%大连' (不使用索引)
            select * from citys where name like '%大连%' (不使用索引)
    正例    select * from citys where name like '大连%' (使用索引)
    正例    NAME LIKE '%A%'     等于 INSTR('A',NAME)
    正例    NAME NOT LIKE '%A%' 等于 INSTR('A',NAME)=0
    理由    首先尽量避免模糊查询,如果必须使用,不采用全模糊查询,也应尽量采用右模糊查询, 即like ‘…%’,是会使用索引的;
            左模糊like ‘%...’无法直接使用索引,但可以利用reverse + function index的形式,变化成 like ‘…%’;
            全模糊查询是无法优化的,一定要使用的话建议使用搜索引擎。
    ---------------------------------------------------------------------------------------------------------
    反例    SELECT * FROM t WHERE id = 10 OR id = 20 OR id = 30;
    反例    SELECT * FROM t WHERE id IN (10,20,30);
    理由    另外,MySQL对于IN做了相应的优化,即将IN中的常量全部存储在一个数组里面,而且这个数组是排好序的。但是如果数值较多,产生的消耗也是比较大的。再例如:
            select id from table_name where num in(1,2,3)
            对于连续的数值,能用 between 就不要用 in 了;再或者使用连接来替换。
    反例    SELECT * FROM user WHERE id=1 OR salary=5000
    正例    使用union all
            SELECT * FROM user WHERE id=1
            UNION ALL
            SELECT * FROM user WHERE salary=5000
    正例    分开两条sql写
            SELECT * FROM user WHERE id=1
            SELECT * FROM user WHERE salary=5000
    理由    使用or可能会使索引失效,从而全表扫描;
            对于or没有索引的salary这种情况,假设它走了id的索引,但是走到salary查询条件时,它还得全表扫描;
            也就是说整个过程需要三步:全表扫描+索引扫描+合并。如果它一开始就走全表扫描,直接一遍扫描就搞定;
            虽然mysql是有优化器的,出于效率与成本考虑,遇到or条件,索引还是可能失效的;

02.NULL值:where中使用默认值代替null,查询时尽量不要对字段做空值判断
    反例    SELECT * FROM user WHERE age IS NOT NULL
    优化    可以给字段添加默认值0,对0值进行判断
    正例    SELECT * FROM user WHERE age>0
    理由    并不是说使用了is null或者 is not null就会不走索引了,这个跟mysql版本以及查询成本都有关;
            如果mysql优化器发现,走索引比不走索引成本还要高,就会放弃索引,这些条件 !=,<>,is null,is not null经常被认为让索引失效;
            其实是因为一般情况下,查询的成本高,优化器自动放弃索引的;
            如果把null值,换成默认值,很多时候让走索引成为可能,同时,表达意思也相对清晰一点;
    ---------------------------------------------------------------------------------------------------------
    反例    select * from xxx where yyy is null;
    反例    select * from xxx where yyy not is null;
    理由    当出现基于字段做空值判断的情况时,会导致索引失效,因为判断null的情况不会走索引,
            因此切记要避免这样的情况,一般在设计字段结构的时候,请使用not null来定义字段,
            同时如果想为空的字段,可以设计一个0、""这类空字符代替,一方面要查询空值时可通过查询空字符的方式走索引检索,
            同时也能避免MyBatis注入对象属性时触发空指针异常。

03.等号前:尽量避免在where条件中=前对字段做任何运算,避免在where中对字段进行表达式操作,避免在索引列上使用内置函数
    反例    SELECT * FROM user WHERE DATE_ADD(birthday,INTERVAL 7 DAY) >=NOW();
    正例    SELECT * FROM user WHERE  birthday >= DATE_ADD(NOW(),INTERVAL 7 DAY);
    理由    SQL解析时,如果字段相关的是表达式就进行全表扫描
            使用索引列上内置函数,索引失效
            字段干净无表达式,索引生效
            注意在SELECT SUBSTR(NAME, 0, 2) 使用内置函数,然后再SELECT该子表,NAME索引仍会失效
    ---------------------------------------------------------------------------------------------------------
    可以将表达式、函数操作移动到等号右侧。如下:
    SELECT * FROM T WHERE score/10 = 9        --全表扫描
    SELECT * FROM T WHERE score = 10*9`       --走索引
    ---------------------------------------------------------------------------------------------------------
    select * from zz_users where user_id * 2 = 8;
    select * from zz_users where trim(user_name) = "熊猫";
    zz_users用户表中user_id、user_name字段上都创建了索引,但上述这类情况都不会走索引,因为MySQL优化器在生成执行计划时,
    发现这些=前面涉及到了逻辑运算,因此就不会继续往下走了,会将具体的运算工作留到执行时完成,也正是由于优化器没有继续往下走,
    因此不会为运算完成后的字段选择索引,最终导致索引失效走全表查询。

04.等号后:where后面的字段,留意其数据类型的隐式转换。
    使用索引      SELECT * FROM user WHERE NAME='110'
    未使用索引    SELECT * FROM user WHERE NAME=110
    使用索引      SELECT * FROM user WHERE NAME IN ('110','111','112')
    未使用索引    SELECT * FROM user WHERE NAME IN (110,111,112)
    因为不加单引号时,是字符串跟数字的比较,它们类型不匹配
    MySQL会做隐式的类型转换,把它们转换为数值类型再做比较
    ---------------------------------------------------------------------------------------------------------
    CREATE TABLE test (
        id INT NOT NULL PRIMARY KEY,
        var VARCHAR(50) NOT NULL
        num INT NOT NULL,
        INDEX `idx var`(`var`) USING BTREE
    ) ENGINE=InnoDB DEFAULT CHARSET=utf8;
    UPDATE test SET num = 10 WHERE var =  1;       将左侧val从'1'变成1,索引失效,行锁变为表锁
    UPDATE test SET num = 10 WHERE id  = '1';     将id为int类型,将右侧'1'变成1,索引正常
    ---------------------------------------------------------------------------------------------------------
    如下SQL语句由于索引对列类型为varchar,但给定的值为数值,涉及隐式类型转换,造成不能正确走索引。
    select col1 from table where col_varchar=123;

05.where 1=1:当数据量大时,避免使用where 1=1的条件。通常为了方便拼装查询条件,我们会默认使用该条件,数据库引擎会放弃索引进行全表扫描。如下:
    SELECT username, age, sex FROM T WHERE 1=1
    优化方式:用代码拼装sql时进行判断,没 where 条件就去掉 where,有where条件就加 and。

06.过滤数据多的条件往前放:调整Where字句中的连接顺序,最快速度缩小结果集
    MySQL采用从左往右,自上而下的顺序解析where子句。根据这个原理,应将过滤数据多的条件往前放,最快速度缩小结果集。

2.11 [2]优化:优化手段2

01.尽量避免使用子查询
    SELECT * FROM t1 WHERE id = (SELECT id FROM t2 WHERE name = 'chackca');
    其子查询在Mysql5.5版本里,内部执行计划是这样:先查外表再匹配内表,而不是先查内表t2,当外表的数据很大时,查询速度会非常慢。
    在MariaDB10/Mysql5.6版本里,采用join关联方式对其进行了优化,这条SQL语句会自动转换为:
    SELECT t1.* FROM t1 JOIN t2 on t1.id = t2.id
    但请注意的是:优化只针对SELECT有效,对UPDATE/DELETE子查询无效,固生产环境应避免使用子查询
    由于MySQL的优化器对于子查询的处理能力比较弱,所以不建议使用子查询,可以改写成Inner Join,
    之所以 join 连接效率更高,是因为 MySQL不需要在内存中创建临时表

02.inner join 、left join、right join,优先使用inner join
    三种连接如果结果相同,优先使用inner join,如果使用left join左边表尽量小。
    inner join 内连接,只保留两张表中完全匹配的结果集;
    left join会返回左表所有的行,即使在右表中没有匹配的记录;
    right join会返回右表所有的行,即使在左表中没有匹配的记录;
    如果inner join是等值连接,返回的行数比较少,所以性能相对会好一点;
    使用了左连接,左边表数据结果尽量小,条件尽量放到左边处理,意味着返回的行数可能比较少;
    这是mysql优化原则,就是小表驱动大表,小的数据集驱动大的数据集,从而让性能更优;

03.避免使用HAVING字句,提高group by语句的效率
    反例    先分组,再过滤   select job, avg(salary) from employee group by job having job ='develop' or job = 'test';
    正例    先过滤,后分组   select job,avg(salary) from employee where job ='develop' or job = 'test' group by job;
    理由    可以在执行到该语句前,把不需要的记录过滤掉
    理由    避免使用HAVING字句,因为HAVING只会在检索出所有记录之后才对结果集进行过滤,而where则是在聚合前刷选记录,如果能通过where字句限制记录的数目,那就能减少这方面的开销。HAVING中的条件一般用于聚合函数的过滤,除此之外,应该将条件写在where字句中。
    理由    where和having的区别:where后面不能使用组函数

04.order by条件要与where中条件一致,否则order by不会利用索引进行排序
    SELECT * FROM ALL_INDEXES WHERE 1=1 AND TABLE_NAME='DSS_JZZT_MX';                                                                                                                    查看索引
    ALTER TABLE "OMS_DATACENTER"."DSS_JZZT_MX" ADD CONSTRAINT "CONS134307167" PRIMARY KEY("JZID", "RQ")                                                                                  增加索引
    SELECT JZID, STATE, RQ FROM OMS_DATACENTER.DSS_JZZ_MX WHERE 1=1 AND JZID IN ('1', '2', '3') AND RQ BETWEEN '2024-04-18 00:00:00' AND '2024-04-18 23:59:59' ORDER BY JZID, RQ;        1=1全表检索
    SELECT JZID, STATE, RQ FROM OMS_DATACENTER.DSS_JZZ_MX WHERE RQ BETWEEN '2024-04-18 00:00:00' AND '2024-04-18 23:59:59' ORDER BY JZID, RQ;                                            WHERE没有JZID,违背复合索引最左特性                                           5m
    SELECT JZID, STATE, RQ FROM OMS_DATACENTER.DSS_JZZ_MX WHERE JZID IN ('1', '2', '3', ..., '189') AND RQ BETWEEN '2024-04-18 00:00:00' AND '2024-04-18 23:59:59' ORDER BY JZID, RQ;    WHERE使用JZID IN,符合复合索引最左特性                                        23s 937ms
    SELECT JZID, STATE, RQ FROM OMS_DATACENTER.DSS_JZZ_MX WHERE JZID BETWEEN '11011400' AND '11011900' AND RQ BETWEEN '2024-04-18 00:00:00' AND '2024-04-18 23:59:59' ORDER BY JZID, RQ; WHERE使用JZID BETWEEN,符合复合索引最左特性,但VARCHAR比较数字,出现隐形转换  3m 16s 501ms
    ---------------------------------------------------------------------------------------------------------
    SELECT * FROM t order by age;                     --不走age索引
    SELECT * FROM t where age > 0 order by age;       --走age索引
    对于上面的语句,数据库的处理顺序是:
    第一步:根据where条件和统计信息生成执行计划,得到数据。
    第二步:将得到的数据排序。当执行处理数据(order by)时,数据库会先查看第一步的执行计划,看order by 的字段是否在执行计划中利用了索引。如果是,则可以利用索引顺序而直接取得已经排好序的数据。如果不是,则重新进行排序操作。
    第三步:返回排序后的数据。
    当order by 中的字段出现在where条件中时,才会利用索引而不再二次排序,更准确的说,order by 中的字段在执行计划中利用了索引时,不用排序操作。
    这个结论不仅对order by有效,对其他需要排序的操作也有效。比如group by 、union 、distinct等。

05.不建议UNION操作符,推荐UNION ALL操作符
    UNION在进行表链接后会筛选掉重复的记录,所以在表链接后会对所产生的结果集进行排序运算,删除重复的记录再返回结果。
    实际大部分应用中是不会产生重复的记录,最常见的是过程表与历史表UNION。如:
    select username,tel from user
    union
    select departmentname from department
    这个SQL在运行时先取出两个表的结果,再用排序空间进行排序删除重复的记录,最后返回结果集,
    如果表数据量大的话可能会导致用磁盘进行排序。推荐方案:采用UNION ALL操作符替代UNION,因为UNION ALL操作只是简单的将两个结果合并后就返回。

06.去重distinct过滤字段要少
    带distinct的语句占用cpu时间高于不带distinct的语句
    当查询很多字段时,如果使用distinct,数据库引擎就会对数据进行比较,过滤掉重复数据
    然而这个比较、过滤的过程会占用系统资源,如cpu时间

2.12 [2]优化:创建原则

00.索引创建原则
    a.选择合适的列
        最适合索引的列是出现在WHERE子句中的列,或连接子句中的列,而不是出现在SELECT关键字后的列
    b.基数大
        索引列的基数(即不同值的数量)越大,索引效果越好,因为高基数意味着更好的选择性
    c.复合索引
        根据情况创建复合索引,复合索引可以提高查询效率,尤其是在多个条件一起使用时
    d.避免过多索引
        创建过多的索引会额外占用磁盘空间,并降低写操作效率
    e.主键选择
        主键尽可能选择较短的数据类型,可以有效减少索引的磁盘占用,提高查询效率
    f.字符串索引
        对字符串进行索引时,应该定制一个前缀长度,可以节省大量的索引空间

01.建立索引
    常用查询条件和连接条件的列上建立索引
    利用覆盖索引
    适当使用前缀索引
    正确使用联合索引,避免过多的列使用复合索引
    更新频繁的列慎用索引
    避免范围查询数据量过多

02.关于临时表
    避免频繁创建和删除临时表,以减少系统表资源的消耗;
    在新建临时表时,如果一次性插入数据量很大,那么可以使用 select into 代替 create table,避免造成大量 log;
    如果数据量不大,为了缓和系统表的资源,应先create table,然后insert;
    如果使用到了临时表,在存储过程的最后务必将所有的临时表显式删除。先 truncate table ,然后 drop table ,这样可以避免系统表的较长时间锁定;

03.表连接不宜太多,一般5个以内
    关联的表个数越多,编译的时间和开销也就越大
    每次关联内存中都生成一个临时表
    应该把连接表拆开成较小的几个执行,可读性更高
    如果一定需要连接很多表才能得到数据,那么意味着这是个糟糕的设计了
    阿里规范中,建议多表联查三张表以下

04.索引不宜太多,一般5个以内
    索引并不是越多越好,虽其提高了查询的效率,但却会降低插入和更新的效率;
    索引可以理解为一个就是一张表,其可以存储数据,其数据就要占空间;
    索引表的数据是排序的,排序也是要花时间的;
    insert或update时有可能会重建索引,如果数据量巨大,重建将进行记录的重新排序,所以建索引需要慎重考虑,视具体情况来定;
    一个表的索引数最好不要超过5个,若太多需要考虑一些索引是否有存在的必要;

05.索引不适合建在有大量重复数据的字段上,比如性别,排序字段应创建索引

06.复合索引最左特性,where条件仅包含复合索引非前置列
    复合(联合)索引包含key_part1,key_part2,key_part3三列,但SQL语句没有包含索引前置列"key_part1",按照MySQL联合索引的最左匹配原则,不会走联合索引。
    select col1 from table where key_part2=1 and key_part3=2

07.复合索引最左特性
    创建复合索引                                                       ALTER TABLE employee ADD INDEX idx_name_salary (name,salary)
    满足复合索引的最左特性,哪怕只是部分,复合索引生效                 SELECT * FROM employee WHERE NAME='哪吒编程'
    没有出现左边的字段,则不满足最左特性,索引失效                     SELECT * FROM employee WHERE salary=5000
    复合索引全使用,按左侧顺序出现 name,salary,索引生效               SELECT * FROM employee WHERE NAME='哪吒编程' AND salary=5000
    虽然违背了最左特性,但MySQL执行SQL时会进行优化,底层进行颠倒优化   SELECT * FROM employee WHERE salary=5000 AND NAME='哪吒编程'
    复合索引也称为联合索引,当我们创建一个联合索引的时候,如(k1,k2,k3),相当于创建了(k1)、(k1,k2)和(k1,k2,k3)三个索引,这就是最左匹配原则。
    联合索引不满足最左原则,索引一般会失效。

2.13 [2]优化:使用建议

00.索引使用建议
    a.不能盲目建立索引
        索引会占用额外的存储空间
        每次数据修改时,索引也需要更新,增加了维护成本
        过多的索引可能导致写操作性能下降
    b.避免重复值多的字段
        对于重复值多的字段(如性别),索引的选择性较低,不能显著提高检索速度
        但在某些场景下(如定时任务状态),即使重复值多,索引仍能有效过滤大量数据,提高查询效率
    c.避免长字段索引
        长字段(如TEXT、LONGTEXT)占用大量内存,索引扫描时加载至内存耗时
        可能导致性能下降,因为其他缓存数据可能被踢出内存
    d.考虑查询与修改频率
        如果表的修改频率远高于查询频率,索引可能会减慢修改效率
        在这种情况下,应谨慎建立索引,以避免得不偿失
    e.对频繁查询的字段建立索引
        在WHERE子句中经常使用的字段适合建立索引
        如果多个条件经常一起查询,可以考虑联合索引,减少索引数量
    f.对排序、分组、去重字段建立索引
        ORDER BY、GROUP BY、DISTINCT操作通常需要排序、分组或去重,索引可以加快这些操作

01.尽量使用数值替代字符串类型
    正例    主键(id):primary key优先使用数值类型int,tinyint
            性别(sex):0代表女,1代表男;数据库没有布尔类型,mysql推荐使用tinyint
    理由    因为引擎在处理查询和连接时会逐个比较字符串中每一个字符;
            而对于数字型而言只需要比较一次就够了;
            字符会降低查询和连接的性能,并会增加存储开销;

02.使用varchar代替char
    反例    address char(100) DEFAULT NULL COMMENT '地址'
    正例    address varchar(100) DEFAULT NULL COMMENT '地址'
    理由    varchar变长字段按数据内容实际长度存储,存储空间小,可以节省存储空间;
            char按声明大小存储,不足补空格;
            其次对于查询来说,在一个相对较小的字段内搜索,效率更高;

03.尽量把所有列定义为NOT NULL
    NOT NULL列更节省空间,NULL列需要一个额外字节作为判断是否为 NULL的标志位。
    NULL列需要注意空指针问题,NULL列在计算和比较的时候,需要注意空指针问题。

04.批量插入性能提升
    多条提交    INSERT INTO user (id,username) VALUES(1,'哪吒编程'); INSERT INTO user (id,username) VALUES(2,'妲己');
    批量提交    INSERT INTO user (id,username) VALUES(1,'哪吒编程'),(2,'妲己');
    理由      默认新增SQL有事务控制,导致每条都需要事务开启和事务提交,而批量处理是一次事务开启和提交,效率提升明显,达到一定量级,效果显著,平时看不出来。

05.清空表时优先使用truncate
    truncate table在功能上与不带 where子句的 delete语句相同:二者均删除表中的全部行。但 truncate table比 delete速度快,且使用的系统和事务日志资源少。
    delete语句每次删除一行,并在事务日志中为所删除的每行记录一项。truncate table通过释放存储表数据所用的数据页来删除数据,并且只在事务日志中记录页的释放。
    truncate table删除表中的所有行,但表结构及其列、约束、索引等保持不变。新行标识所用的计数值重置为该列的种子。如果想保留标识计数值,请改用 DELETE。如果要删除表定义及其数据,请使用 drop table语句。
    对于由 foreign key约束引用的表,不能使用 truncate table,而应使用不带  where子句的 DELETE 语句。由于 truncate table不记录在日志中,所以它不能激活触发器。
    truncate table不能用于参与了索引视图的表。

06.操作delete或者update语句,加个limit或者循环分批次删除
    降低写错SQL的代价,清空表数据可不是小事情,一个手抖全没了,删库跑路?如果加limit,删错也只是丢失部分数据,可以通过binlog日志快速恢复的。
    SQL效率很可能更高,SQL中加了limit 1,如果第一条就命中目标return, 没有limit的话,还会继续执行扫描表。
    避免长事务,delete执行时,如果age加了索引,MySQL会将所有相关的行加写锁和间隙锁,所有执行相关行会被锁住,如果删除数量大,会直接影响相关业务无法使用。
    数据量大的话,容易把CPU打满,如果你删除数据量很大时,不加 limit限制一下记录数,容易把cpu打满,导致越删越慢。
    锁表,一次性删除太多数据,可能造成锁表,会有lock wait timeout exceed的错误,所以建议分批操作。

2.14 [2]优化:2个示例

01.示例1
    WITH stat AS (
        --基础表
        SELECT XL_ID,XL_NAME,IMAXLONG_20,VOLTAGE_TYPE,V00,V01,V02,V03,V04,V05,V06,V07,V08,V09,V10,V11,V12,V13,V14,V15,V16,V17,V18,V19,V20,V21,V22,V23,V24,V25,V26,V27,V28,V29,V30,V31,V32,V33,V34,V35,V36,V37,V38,V39,V40,V41,V42,V43,V44,V45,V46,V47,V48,V49,V50,V51,V52,V53,V54,V55,V56,V57,V58,V59
        FROM OMS_DATACENTER.SG_DEV_ACLINE_H1_MEA_2024
        WHERE MEAS_TYPE='00002401'
        AND CREATE_TIME BETWEEN '2024-04-09' AND '2024-04-09 23:00:00'
    ), nj AS (
        --一天中的最大电流
        SELECT line.ID, ROUND(MAX(pow.max_value),2) AS ZDDL
        FROM SG_DATACENTER.SG_DEV_ACLINE_B line
        INNER JOIN SG_DATACENTER.SG_DEV_ACLINE_H_STA_POW_2024 pow ON pow.ID = line.ID AND pow.MEAS_TYPE = '00002401'
        WHERE pow.CREATE_DATE BETWEEN '2024-04-09' AND '2024-04-09 23:00:00'
        GROUP BY line.ID
    ), lj AS (
        --地区信息
        SELECT line.ID, power.NAME AS POWER_NAME
        FROM SG_DATACENTER.SG_DEV_ACLINE_B line
        INNER JOIN SG_DATACENTER.SG_CON_PWRGRID_B power ON power.ID = line.GRID_ID
    ), sj AS (
        --重载时长
        SELECT XL_ID,V00+V01+V02+V03+V04+V05+V06+V07+V08+V09+V10+V11+V12+V13+V14+V15+V16+V17+V18+V19+V20+V21+V22+V23+V24+V25+V26+V27+V28+V29+V30+V31+V32+V33+V34+V35+V36+V37+V38+V39+V40+V41+V42+V43+V44+V45+V46+V47+V48+V49+V50+V51+V52+V53+V54+V55+V56+V57+V58+V59 AS ZZSC FROM (
            SELECT XL_ID
                ,SUM(CASE WHEN ABS(V00)>=ROUND((65/100),2) THEN 1 ELSE 0 END) AS V00,SUM(CASE WHEN ABS(V01)>=ROUND((65/100),2) THEN 1 ELSE 0 END) AS V01,SUM(CASE WHEN ABS(V02)>=ROUND((65/100),2) THEN 1 ELSE 0 END) AS V02,SUM(CASE WHEN ABS(V03)>=ROUND((65/100),2) THEN 1 ELSE 0 END) AS V03,SUM(CASE WHEN ABS(V04)>=ROUND((65/100),2) THEN 1 ELSE 0 END) AS V04,SUM(CASE WHEN ABS(V05)>=ROUND((65/100),2) THEN 1 ELSE 0 END) AS V05
                ,SUM(CASE WHEN ABS(V06)>=ROUND((65/100),2) THEN 1 ELSE 0 END) AS V06,SUM(CASE WHEN ABS(V07)>=ROUND((65/100),2) THEN 1 ELSE 0 END) AS V07,SUM(CASE WHEN ABS(V08)>=ROUND((65/100),2) THEN 1 ELSE 0 END) AS V08,SUM(CASE WHEN ABS(V09)>=ROUND((65/100),2) THEN 1 ELSE 0 END) AS V09,SUM(CASE WHEN ABS(V10)>=ROUND((65/100),2) THEN 1 ELSE 0 END) AS V10,SUM(CASE WHEN ABS(V11)>=ROUND((65/100),2) THEN 1 ELSE 0 END) AS V11
                ,SUM(CASE WHEN ABS(V12)>=ROUND((65/100),2) THEN 1 ELSE 0 END) AS V12,SUM(CASE WHEN ABS(V13)>=ROUND((65/100),2) THEN 1 ELSE 0 END) AS V13,SUM(CASE WHEN ABS(V14)>=ROUND((65/100),2) THEN 1 ELSE 0 END) AS V14,SUM(CASE WHEN ABS(V15)>=ROUND((65/100),2) THEN 1 ELSE 0 END) AS V15,SUM(CASE WHEN ABS(V16)>=ROUND((65/100),2) THEN 1 ELSE 0 END) AS V16,SUM(CASE WHEN ABS(V17)>=ROUND((65/100),2) THEN 1 ELSE 0 END) AS V17
                ,SUM(CASE WHEN ABS(V18)>=ROUND((65/100),2) THEN 1 ELSE 0 END) AS V18,SUM(CASE WHEN ABS(V19)>=ROUND((65/100),2) THEN 1 ELSE 0 END) AS V19,SUM(CASE WHEN ABS(V20)>=ROUND((65/100),2) THEN 1 ELSE 0 END) AS V20,SUM(CASE WHEN ABS(V21)>=ROUND((65/100),2) THEN 1 ELSE 0 END) AS V21,SUM(CASE WHEN ABS(V22)>=ROUND((65/100),2) THEN 1 ELSE 0 END) AS V22,SUM(CASE WHEN ABS(V23)>=ROUND((65/100),2) THEN 1 ELSE 0 END) AS V23
                ,SUM(CASE WHEN ABS(V24)>=ROUND((65/100),2) THEN 1 ELSE 0 END) AS V24,SUM(CASE WHEN ABS(V25)>=ROUND((65/100),2) THEN 1 ELSE 0 END) AS V25,SUM(CASE WHEN ABS(V26)>=ROUND((65/100),2) THEN 1 ELSE 0 END) AS V26,SUM(CASE WHEN ABS(V27)>=ROUND((65/100),2) THEN 1 ELSE 0 END) AS V27,SUM(CASE WHEN ABS(V28)>=ROUND((65/100),2) THEN 1 ELSE 0 END) AS V28,SUM(CASE WHEN ABS(V29)>=ROUND((65/100),2) THEN 1 ELSE 0 END) AS V29
                ,SUM(CASE WHEN ABS(V30)>=ROUND((65/100),2) THEN 1 ELSE 0 END) AS V30,SUM(CASE WHEN ABS(V31)>=ROUND((65/100),2) THEN 1 ELSE 0 END) AS V31,SUM(CASE WHEN ABS(V32)>=ROUND((65/100),2) THEN 1 ELSE 0 END) AS V32,SUM(CASE WHEN ABS(V33)>=ROUND((65/100),2) THEN 1 ELSE 0 END) AS V33,SUM(CASE WHEN ABS(V34)>=ROUND((65/100),2) THEN 1 ELSE 0 END) AS V34,SUM(CASE WHEN ABS(V35)>=ROUND((65/100),2) THEN 1 ELSE 0 END) AS V35
                ,SUM(CASE WHEN ABS(V36)>=ROUND((65/100),2) THEN 1 ELSE 0 END) AS V36,SUM(CASE WHEN ABS(V37)>=ROUND((65/100),2) THEN 1 ELSE 0 END) AS V37,SUM(CASE WHEN ABS(V38)>=ROUND((65/100),2) THEN 1 ELSE 0 END) AS V38,SUM(CASE WHEN ABS(V39)>=ROUND((65/100),2) THEN 1 ELSE 0 END) AS V39,SUM(CASE WHEN ABS(V40)>=ROUND((65/100),2) THEN 1 ELSE 0 END) AS V40,SUM(CASE WHEN ABS(V41)>=ROUND((65/100),2) THEN 1 ELSE 0 END) AS V41
                ,SUM(CASE WHEN ABS(V42)>=ROUND((65/100),2) THEN 1 ELSE 0 END) AS V42,SUM(CASE WHEN ABS(V43)>=ROUND((65/100),2) THEN 1 ELSE 0 END) AS V43,SUM(CASE WHEN ABS(V44)>=ROUND((65/100),2) THEN 1 ELSE 0 END) AS V44,SUM(CASE WHEN ABS(V45)>=ROUND((65/100),2) THEN 1 ELSE 0 END) AS V45,SUM(CASE WHEN ABS(V46)>=ROUND((65/100),2) THEN 1 ELSE 0 END) AS V46,SUM(CASE WHEN ABS(V47)>=ROUND((65/100),2) THEN 1 ELSE 0 END) AS V47
                ,SUM(CASE WHEN ABS(V48)>=ROUND((65/100),2) THEN 1 ELSE 0 END) AS V48,SUM(CASE WHEN ABS(V49)>=ROUND((65/100),2) THEN 1 ELSE 0 END) AS V49,SUM(CASE WHEN ABS(V50)>=ROUND((65/100),2) THEN 1 ELSE 0 END) AS V50,SUM(CASE WHEN ABS(V51)>=ROUND((65/100),2) THEN 1 ELSE 0 END) AS V51,SUM(CASE WHEN ABS(V52)>=ROUND((65/100),2) THEN 1 ELSE 0 END) AS V52,SUM(CASE WHEN ABS(V53)>=ROUND((65/100),2) THEN 1 ELSE 0 END) AS V53
                ,SUM(CASE WHEN ABS(V54)>=ROUND((65/100),2) THEN 1 ELSE 0 END) AS V54,SUM(CASE WHEN ABS(V55)>=ROUND((65/100),2) THEN 1 ELSE 0 END) AS V55,SUM(CASE WHEN ABS(V56)>=ROUND((65/100),2) THEN 1 ELSE 0 END) AS V56,SUM(CASE WHEN ABS(V57)>=ROUND((65/100),2) THEN 1 ELSE 0 END) AS V57,SUM(CASE WHEN ABS(V58)>=ROUND((65/100),2) THEN 1 ELSE 0 END) AS V58,SUM(CASE WHEN ABS(V59)>=ROUND((65/100),2) THEN 1 ELSE 0 END) AS V59
            FROM stat GROUP BY XL_ID, IMAXLONG_20
        )
    ), hj AS (
        -- 小表:基础表+一天中的最大电流+地区信息
        SELECT DISTINCT stat.XL_ID
            ,stat.XL_NAME
            ,lj.POWER_NAME
            ,DECODE(stat.VOLTAGE_TYPE, '1003', '500kV', '1005', '220kV') AS VOLTAGE_NAME
            ,stat.IMAXLONG_20
            ,nj.ZDDL
            ,CONCAT(ROUND(ROUND(nj.ZDDL/stat.IMAXLONG_20,2)*100), '%') AS FZL
        FROM stat
        INNER JOIN lj ON stat.XL_ID = lj.ID
        INNER JOIN nj ON stat.XL_ID = nj.ID
    --     WHERE VOLTAGE_TYPE in (1003, 1005)
    --     AND INSTR('山西电网', POWER_NAME)
    --     AND INSTR(XL_NAME,'风运二线')
    )
    SELECT hj.*, sj.ZZSC
    FROM hj INNER JOIN sj ON hj.XL_ID = sj.XL_ID
    ORDER BY XL_NAME;

02.示例2
    SELECT DISTINCT lineEnd.NAME, CONCAT(LEFT(lineEnd.OWNER,4),'00') AS OWNER, line.LOAD_TYPE
    FROM SG_DATACENTER.SG_DEV_ACLINEEND_B lineEnd
    LEFT JOIN SG_DATACENTER.SG_DEV_ACLINE_B line
    ON lineEnd.LINE_ID = line.ID
    WHERE line.LOAD_TYPE IS NOT NULL;

    在这段SQL中,LEFT JOIN会保留lineEnd表中所有的记录,即使line表中没有匹配的记录。
    这是LEFT JOIN的特性:会保留左表(lineEnd)中的所有记录,并在右表(line)中找不到匹配记录时用NULL填充。
    但是,WHERE子句中的line.LOAD_TYPE IS NOT NULL会在最终结果集中过滤掉line.LOAD_TYPE为NULL的记录。

    SELECT DISTINCT lineEnd.NAME, CONCAT(LEFT(lineEnd.OWNER,4),'00') AS OWNER, line.LOAD_TYPE
    FROM SG_DATACENTER.SG_DEV_ACLINEEND_B lineEnd
    LEFT JOIN SG_DATACENTER.SG_DEV_ACLINE_B line
    ON lineEnd.LINE_ID = line.ID AND line.LOAD_TYPE IS NOT NULL
    WHERE line.LOAD_TYPE IS NOT NULL;

    在这段SQL中,LEFT JOIN的ON子句中添加了line.LOAD_TYPE IS NOT NULL的条件。
    这意味着只有在line.LOAD_TYPE不为NULL时,才会进行连接。
    这样做的结果是,如果line.LOAD_TYPE为NULL,那么即使在lineEnd.LINE_ID = line.ID条件满足的情况下,也不会进行连接。

    总结
    第一段SQL:LEFT JOIN之后,WHERE子句过滤掉line.LOAD_TYPE为NULL的记录。
    第二段SQL:LEFT JOIN过程中,如果line.LOAD_TYPE为NULL,则不会进行连接。
    这两种写法最终的结果集可能是一样的,因为WHERE子句和LEFT JOIN的ON条件都确保了line.LOAD_TYPE不为NULL。然而,处理的方式不同:
    第一段SQL会先进行LEFT JOIN,然后再过滤。
    第二段SQL在JOIN时已经排除了line.LOAD_TYPE为NULL的记录。
    在某些数据库管理系统中,第二种方式可能会更高效,因为它减少了需要进行的连接操作。

2.15 [3]创建:失效场景

01.索引失效的场景
    01.查询条件包含or,可能导致索引失效
    02.如何字段类型是字符串,where时一定用引号括起来,否则索引失效
    03.like通配符可能导致索引失效
    04.联合索引,查询时的条件列不是联合索引中的第一个列,索引失效
    05.在索引列上使用mysql的内置函数,索引失效
    06.对索引列运算(如,+、-、*、/),索引失效
    07.索引字段上使用(!= 或者 < >,not in)时,可能会导致索引失效
    08.索引字段上使用is null, is not null,可能导致索引失效
    09.左连接查询或者右连接查询查询关联的字段编码格式不一样,可能导致索引失效
    10.mysql估计使用全表扫描要比使用索引快,则不使用索引

02.索引失效的场景
    a.在索引列上使用函数或表达式
        如果在查询中对索引列使用了函数或表达式,索引可能无法使用。例如:SELECT * FROM table WHERE YEAR(date_column) = 2021
    b.使用不等于(<>)或者 NOT 操作符
        这些操作符通常会导致索引失效,因为它们会导致全表扫描
    c.使用 LIKE 操作符,但通配符在最前面
        如果 LIKE 的模式串是以“%”或“_”开头,索引无法使用。例如:SELECT * FROM table WHERE column LIKE '%abc'
    d.OR操作符
        如果查询条件中使用了 OR,并且 OR 两边的条件涉及不同的索引,这些索引可能无法使用
    e.查询条件中列类型是字符串,没有使用引号
        可能会因为类型不同发生隐式转换,使索引失效
    f.对于组合索引,不是使用组合索引最左边的字段
        如果没有使用组合索引的最左边字段,索引不会被使用

2.16 [3]创建:适合场景

01.索引适合的场景
    a.对中大型表,索引非常有效
        索引可以显著提高查询性能
    b.经常用于查询的字段
        在 WHERE 子句中经常使用的字段适合建立索引
    c.经常用于连接的字段
        建立索引可以加快连接速度
    d.经常需要排序的字段
        索引已经排好序,可以加快排序查询速度
    e.对经常在 ORDER BY、GROUP BY、DISTINCT 后面的字段建立索引
        索引可以帮助加快这些操作的速度

2.17 [3]创建:不适合场景

01.索引不适合的场景
    a.数据表较小
        当表中的数据量很小,数据库优化器可能会选择全表扫描而不是使用索引
    b.频繁更新的列
        对于经常进行更新、删除或插入操作的列,使用索引可能会导致性能下降
    c.性别字段
        性别字段的选择性较低,独立索引效果有限
    d.WHERE条件中用不到的字段
        不适合建立索引
    e.表记录较少
        比如只有几百条数据,没必要加索引
    f.参与列计算的列
        不适合建索引
    g.区分度不高的字段
        如性别,只有男/女/未知三个值,查询效率不会提高

02.性别字段要建立索引吗?
    性别字段通常不适合建立索引。因为性别字段的选择性(区分度)较低,独立索引效果有限
    如果性别字段又很少用于查询,表的数据规模较小,那么建立索引反而会增加额外的存储空间和维护成本
    如果性别字段确实经常用于查询条件,数据规模也比较大,可以将性别字段作为复合索引的一部分,与选择性较高的字段一起加索引,会更好一些

2.18 [3]创建:最左前缀原则

01.最左前缀原则,也叫最左匹配原则,或者最左前缀匹配原则
    最左匹配原则是指在使用联合索引(即包含多列的索引)时,查询条件从索引的最左列开始并且不跳过中间的列
    如果一个复合索引包含(col1, col2, col3),那么它可以支持 col1、col1,col2 和 col1, col2, col3 的查询优化,但不会优化只有 col2 或 col3 的查询
    也就说,在进行查询时,如果没有遵循最左前缀,那么索引可能不会被利用,导致查询效率降低

2.19 [4]索引并不是越多越好

01.回答
    不是

02.原因
    a.索引会占据磁盘空间
        略
    b.索引虽然会提高查询效率,但是会降低更新表的效率
        比如每次对表进行增删改操作,MySQL 不仅要保存数据,还有保存或者更新对应的索引文件

2.20 [4]索引为什么会加快查询

01.减少磁盘I/O操作
    a.磁盘I/O是瓶颈
        数据库文件存储在磁盘上,而磁盘 I/O 是数据库操作中最耗时的部分之一
        没有索引时,数据库需要进行全表扫描(Sequential Scan),即读取表中的每一行数据来查找匹配的行,时间复杂度为 O(n)
    b.索引减少I/O
        有了索引,数据库可以直接跳到索引指示的数据位置,而不必扫描整张表,从而大大减少了磁盘 I/O 操作的次数

02.高效的数据结构
    a.B+树索引
        MySQL 的 InnoDB 存储引擎默认使用 B+ 树作为索引的数据结构
        B+ 树是一种平衡树,查询效率非常高,时间复杂度为 O(logN)
        通过 B+ 树,数据库可以快速定位到数据所在的磁盘位置

03.索引文件较小
    a.索引文件体积小
        索引文件相较于数据库文件,体积小得多
        查找到索引之后,再映射到数据库记录,减少了需要读取的数据量,从而提高查询效率

04.类比书籍目录
    a.索引如同目录
        索引就好像书的目录,通过目录去查找对应的章节内容会比一页一页地翻书快很多
        这种直接定位的方式大大提高了查找速度

2.21 [4]索引创建时会不会锁表?5.6前-表锁,5.6.7后-online ddl

01.MySQL 5.6之前
    a.回答
        会
    b.说明
        创建索引时会锁表,这意味着在索引创建过程中,其他会话(除了SELECT查询)会被阻塞
        因此,在早期版本中,在线上环境中创建索引需要特别小心

02.MySQL 5.6.7及之后
    a.回答
        不会
    b.说明
        引入了Online DDL技术,允许在创建索引时不阻塞其他会话
        所有的DML操作(如INSERT、UPDATE、DELETE、SELECT)可以与索引创建并发执行

2.22 [5]insert:有主键-行锁,没有主键-表锁

01.insert会导致行锁还是表锁?
    只影响新插入的行:行锁
    没有主键或唯一索引:表锁

2.23 [5]update:有索引-锁行,无索引-锁表

01.UPDATE是锁行还是锁表?
    a.InnoDB行锁
        通过给索引上的索引项加锁实现
        如果UPDATE语句的WHERE条件使用了索引,则锁行;否则,锁表
    b.并发执行
        如果两个UPDATE语句同时执行,第一个语句触发行锁,第二个语句如果没有索引则会等待行锁释放后再锁表
    c.INSERT和DELETE
        通常锁行

02.具体原理
    a.介绍
        InnoDB行锁是通过给索引上的索引项加锁来实现的
        只有通过索引条件检索数据,InnoDB才使用行级锁,否则,InnoDB将使用表锁
    b.当执行update语句时
        where中的过滤条件列,如果用到索引,就是锁行;如果无法用索引,就是锁表
    c.如果两个update语句同时执行
        第一个先执行触发行锁,但是第二个没有索引触发表锁,因为有个行锁住了,所以还是会等待行锁释放,才能锁表
    d.当执行insert或者delete语句时
        锁行

2.24 [5]delete:有索引-行锁,无索引-表锁

01.delete操作会导致行锁还是表锁
    当WHERE条件使用了索引:行锁
    没有使用索引:表锁

2.25 [5]select…for update:索引-行锁,普通-表锁

01.使用索引/主键
    如果查询条件使用了索引或主键,SELECT ... FOR UPDATE会加【行锁】

02.普通字段
    如果查询条件没有使用索引或主键,SELECT ... FOR UPDATE会加【表锁】

2.26 [6]alter table:只有表锁

01.alter table操作会导致行锁还是表锁?
    涉及对表结构的修改:表锁,必须确保在修改期间没有其他事务对表进行操作

2.27 [6]drop table:只有表锁

01.drop table操作会导致行锁还是表锁?
    删除整个表:表锁,必须确保在删除期间没有其他事务对表进行操作

2.28 [6]lock table:只有表锁(读锁/写锁)

01.lock tables操作会导致行锁还是表锁?
    lock tables操作显式地对表加锁,可以是读锁(共享锁)或写锁(排他锁)
    1.读锁允许其他事务读取但不允许修改
    2.写锁则不允许其他事务进行任何操作

2.29 [6]create index:5.6前-表锁,5.6.7后-online ddl

01.create index操作会导致行锁还是表锁?
    在mysql 5.6之前:表锁
    在mysql 5.6.7及之后:使用online ddl技术可以避免表锁,允许并发dml操作

3 函数指南

3.1 字符串函数

01.基础字符串函数
    a.LENGTH/LEN/LENGTH - 获取字符串长度
        -- MySQL
        SELECT LENGTH('Hello World');  -- 11
        -- SQL Server
        SELECT LEN('Hello World');    -- 11
        -- Oracle
        SELECT LENGTH('Hello World') FROM DUAL;  -- 11
    b.CHAR_LENGTH - 获取字符数(区别于字节长度)
        -- MySQL & Oracle
        SELECT CHAR_LENGTH('你好');  -- 2
    c.SUBSTRING/SUBSTR - 截取字符串
        -- MySQL & SQL Server
        SELECT SUBSTRING('Hello World', 1, 5);  -- 'Hello'
        SELECT SUBSTRING('Hello World', -5);     -- 'World'
        -- Oracle
        SELECT SUBSTR('Hello World', 1, 5) FROM DUAL;
    d.LEFT/RIGHT - 从左/右截取
        -- MySQL & SQL Server
        SELECT LEFT('Hello World', 5);   -- 'Hello'
        SELECT RIGHT('Hello World', 5);  -- 'World'
    e.REPLACE - 替换字符串
        -- 所有数据库通用
        SELECT REPLACE('Hello World', 'World', 'SQL');  -- 'Hello SQL'
    f.STUFF - 字符串替换(SQL Server特有)
        SELECT STUFF('Hello World', 1, 5, 'Hi');  -- 'Hi World'
    g.POSITION/INSTR/CHARINDEX - 查找子字符串位置
        -- MySQL
        SELECT POSITION('World' IN 'Hello World');  -- 7
        -- Oracle
        SELECT INSTR('Hello World', 'World') FROM DUAL;  -- 7
        -- SQL Server
        SELECT CHARINDEX('World', 'Hello World');  -- 7
    h.REVERSE - 反转字符串
        -- 所有数据库
        SELECT REVERSE('Hello');  -- 'olleH'
    i.SPACE - 生成空格字符串
        -- SQL Server & MySQL
        SELECT 'Hello' + SPACE(1) + 'World';  -- 'Hello World'
    j.REPEAT/REPLICATE - 重复字符串
        -- MySQL
        SELECT REPEAT('SQL', 3);  -- 'SQLSQLSQL'
        -- SQL Server
        SELECT REPLICATE('SQL', 3);  -- 'SQLSQLSQL'

02.高级字符串函数
    a.FORMAT - 格式化字符串
        -- MySQL & SQL Server
        SELECT FORMAT(123456.789, 2);  -- '123,456.79'
    b.STRING_SPLIT(SQL Server)/SPLIT_STRING(MySQL) - 字符串分割
        -- SQL Server
        SELECT value FROM STRING_SPLIT('a,b,c', ',');
        -- MySQL
        SELECT SUBSTRING_INDEX('a,b,c', ',', 1);  -- 'a'
    c.GROUP_CONCAT/STRING_AGG - 字符串聚合
        -- MySQL
        SELECT GROUP_CONCAT(name SEPARATOR ',') FROM employees;
        -- SQL Server
        SELECT STRING_AGG(name, ',') FROM employees;
        -- Oracle
        SELECT LISTAGG(name, ',') WITHIN GROUP (ORDER BY name) FROM employees;

3.2 数值函数

01.基础数学函数
    a.ROUND/TRUNC/TRUNCATE - 截断
        -- 所有数据库
        SELECT ROUND(123.456, 2);  -- 123.46
        -- Oracle
        SELECT TRUNC(123.456, 2) FROM DUAL;  -- 123.45
        -- MySQL
        SELECT TRUNCATE(123.456, 2);  -- 123.45
    b.MOD - 取模
        -- 所有数据库
        SELECT MOD(10, 3);  -- 1
    c.SQRT - 平方根
        SELECT SQRT(16);  -- 4
    d.SIGN - 获取数字符号
        SELECT SIGN(-10);  -- -1
        SELECT SIGN(10);   -- 1
        SELECT SIGN(0);    -- 0

02.高级数学函数
    a.LOG/LOG10/LN - 对数运算
        SELECT LOG(10, 100);  -- 2
        SELECT LOG10(100);    -- 2
        SELECT LN(2.7);       -- 0.993
    b.EXP - 指数运算
        SELECT EXP(1);  -- 2.718281828459045
    c.RAND/RANDOM - 随机数
        -- MySQL & SQL Server
        SELECT RAND();
        -- Oracle
        SELECT DBMS_RANDOM.VALUE FROM DUAL;

3.3 日期时间函数

01.获取日期时间
    a.NOW/GETDATE/SYSDATE - 当前日期时间
        -- MySQL
        SELECT NOW();
        -- SQL Server
        SELECT GETDATE();
        -- Oracle
        SELECT SYSDATE FROM DUAL;
    b.CURDATE/CURRENT_DATE - 当前日期
        -- MySQL
        SELECT CURDATE();
        -- Oracle & SQL Server
        SELECT CURRENT_DATE;
    c.CURTIME/CURRENT_TIME - 当前时间
        -- MySQL
        SELECT CURTIME();
        -- Oracle & SQL Server
        SELECT CURRENT_TIME;

02.日期时间处理
    a.DATE_ADD/DATEADD - 日期加减
        -- MySQL
        SELECT DATE_ADD('2024-03-12', INTERVAL 1 DAY);
        SELECT DATE_ADD('2024-03-12', INTERVAL 1 MONTH);
        SELECT DATE_ADD('2024-03-12', INTERVAL 1 YEAR);
        -- SQL Server
        SELECT DATEADD(day, 1, '2024-03-12');
        SELECT DATEADD(month, 1, '2024-03-12');
        SELECT DATEADD(year, 1, '2024-03-12');
    b.DATE_FORMAT/FORMAT - 日期格式化
        -- MySQL
        SELECT DATE_FORMAT('2024-03-12', '%Y年%m月%d日');  -- '2024年03月12日'
        -- SQL Server
        SELECT FORMAT(GETDATE(), 'yyyy年MM月dd日');
    c.EXTRACT/DATEPART - 提取日期部分
        -- MySQL & Oracle
        SELECT EXTRACT(YEAR FROM '2024-03-12');
        SELECT EXTRACT(MONTH FROM '2024-03-12');
        SELECT EXTRACT(DAY FROM '2024-03-12');
        -- SQL Server
        SELECT DATEPART(year, '2024-03-12');
        SELECT DATEPART(month, '2024-03-12');
        SELECT DATEPART(day, '2024-03-12');
    d.LAST_DAY - 获取月末日期
        -- MySQL & Oracle
        SELECT LAST_DAY('2024-03-12');  -- '2024-03-31'

3.4 条件和控制函数

01.常用函数
    a.IF/IIF - 条件判断
        -- MySQL
        SELECT IF(1 > 0, 'True', 'False');
        -- SQL Server
        SELECT IIF(1 > 0, 'True', 'False');
    b.IFNULL/ISNULL/NVL - NULL值处理
        -- MySQL
        SELECT IFNULL(NULL, 'Default');
        -- SQL Server
        SELECT ISNULL(NULL, 'Default');
        -- Oracle
        SELECT NVL(NULL, 'Default') FROM DUAL;
    c.NULLIF - 相等返回NULL
        SELECT NULLIF(10, 10);  -- NULL
        SELECT NULLIF(10, 20);  -- 10
    d.GREATEST/LEAST - 最大最小值
        -- MySQL & Oracle
        SELECT GREATEST(1, 2, 3, 4, 5);  -- 5
        SELECT LEAST(1, 2, 3, 4, 5);     -- 1

3.5 窗口函数

01.常用函数
    a.ROW_NUMBER/RANK/DENSE_RANK - 排序
        SELECT
            name,
            salary,
            ROW_NUMBER() OVER (ORDER BY salary DESC) as row_num,
            RANK() OVER (ORDER BY salary DESC) as rank_num,
            DENSE_RANK() OVER (ORDER BY salary DESC) as dense_rank_num
        FROM employees;
    b.FIRST_VALUE/LAST_VALUE - 首尾值
        SELECT
            name,
            department,
            salary,
            FIRST_VALUE(salary) OVER (PARTITION BY department ORDER BY salary DESC) as highest_salary,
            LAST_VALUE(salary) OVER (PARTITION BY department ORDER BY salary DESC
                RANGE BETWEEN UNBOUNDED PRECEDING AND UNBOUNDED FOLLOWING) as lowest_salary
        FROM employees;
    c.LAG/LEAD - 前后行
        SELECT
            name,
            department,
            salary,
            LAG(salary) OVER (PARTITION BY department ORDER BY salary) as prev_salary,
            LEAD(salary) OVER (PARTITION BY department ORDER BY salary) as next_salary
        FROM employees;
    d.NTILE - 分组
        SELECT
            name,
            salary,
            NTILE(4) OVER (ORDER BY salary) as quartile
        FROM employees;

3.6 JSON函数(MySQL 5.7+)

01.常用函数
    a.JSON_EXTRACT - 提取JSON值
        SELECT JSON_EXTRACT('{"name": "John", "age": 30}', '$.name');  -- "John"
    b.JSON_OBJECT - 创建JSON对象
        SELECT JSON_OBJECT('name', 'John', 'age', 30);
    c.JSON_ARRAY - 创建JSON数组
        SELECT JSON_ARRAY(1, 2, 3, 4, 5);
    d.JSON_CONTAINS - 检查JSON包含
        SELECT JSON_CONTAINS('{"a": 1, "b": 2}', '1', '$.a');  -- 1

3.7 加密和安全函数

01.常用函数
    a.MD5 - MD5加密
        -- MySQL & SQL Server
        SELECT MD5('password');
    b.SHA1/SHA2 - SHA加密
        -- MySQL
        SELECT SHA1('password');
        SELECT SHA2('password', 256);
    c.ENCRYPT/DECRYPT - 加密解密
        -- MySQL
        SET @key = 'secret_key';
        SET @encrypted = AES_ENCRYPT('text', @key);
        SELECT AES_DECRYPT(@encrypted, @key);

3.8 XML函数(SQL Server)

01.常用函数
    a.FOR XML PATH - 生成XML
        SELECT name, age
        FROM employees
        FOR XML PATH('employee'), ROOT('employees')
    b.XML数据类型方法
        DECLARE @xml XML
        SET @xml = '<root><child>value</child></root>'
        SELECT @xml.value('(/root/child)[1]', 'varchar(50)')

3.9 正则表达式函数

01.常见函数
    a.REGEXP/RLIKE - 正则匹配(MySQL)
        SELECT 'hello' REGEXP '^h';  -- 1
        SELECT 'hello' RLIKE 'l+';   -- 1
    b.REGEXP_LIKE - 正则匹配(Oracle)
        SELECT * FROM employees WHERE REGEXP_LIKE(email, '^[A-Za-z]+@[A-Za-z]+\.[A-Za-z]{2,4}$');

3.10 系统信息函数

01.常见函数
    a.VERSION - 数据库版本
        -- MySQL
        SELECT VERSION();
        -- SQL Server
        SELECT @@VERSION;
        -- Oracle
        SELECT * FROM V$VERSION;
   b.USER/CURRENT_USER - 当前用户
        -- 所有数据库
        SELECT USER;
        SELECT CURRENT_USER;
    c.DATABASE/DB_NAME - 当前数据库
        -- MySQL
        SELECT DATABASE();
        -- SQL Server
        SELECT DB_NAME();

3.11 高级聚合函数

01.常见函数
    a.GROUPING SETS - 多维度聚合
        SELECT department, location, COUNT(*)
        FROM employees
        GROUP BY GROUPING SETS (
            (department, location),
            (department),
            (location),
            ()
        );
    b.CUBE - 所有可能的组合
        SELECT department, location, COUNT(*)
        FROM employees
        GROUP BY CUBE (department, location);
    c.ROLLUP - 层次聚合
        SELECT
            COALESCE(department, 'Total') as department,
            COALESCE(location, 'Subtotal') as location,
            COUNT(*) as employee_count,
            AVG(salary) as avg_salary
        FROM employees
        GROUP BY ROLLUP (department, location);
    d.PIVOT - 行转列
        -- SQL Server
        SELECT *
        FROM (
            SELECT department, location, salary
            FROM employees
        ) AS SourceTable
        PIVOT (
            AVG(salary)
            FOR location IN ([New York], [London], [Tokyo])
        ) AS PivotTable;

3.12 统计和数学函数

01.常见函数
    a.PERCENTILE_CONT/PERCENTILE_DISC - 百分位数
        SELECT
            PERCENTILE_CONT(0.5) WITHIN GROUP (ORDER BY salary) as median_salary,
            PERCENTILE_DISC(0.5) WITHIN GROUP (ORDER BY salary) as discrete_median
        FROM employees;
    b.CORR - 相关系数
        SELECT CORR(salary, performance_score)
        FROM employees;
    c.STDDEV/VARIANCE - 标准差和方差
        SELECT
            department,
            AVG(salary) as avg_salary,
            STDDEV(salary) as salary_stddev,
            VARIANCE(salary) as salary_variance
        FROM employees
        GROUP BY department;
    d.FIRST/LAST - 组内第一个/最后一个值
        -- Oracle
        SELECT
            department,
            FIRST_VALUE(salary) OVER (PARTITION BY department ORDER BY hire_date) as first_salary,
            LAST_VALUE(salary) OVER (
                PARTITION BY department
                ORDER BY hire_date
                RANGE BETWEEN UNBOUNDED PRECEDING AND UNBOUNDED FOLLOWING
            ) as last_salary
        FROM employees;

3.13 字符串模式匹配函数

01.常见函数
    a.LIKE模式匹配增强
        -- 复杂LIKE模式
        SELECT * FROM employees
        WHERE
            name LIKE '[A-M]%' -- SQL Server, 以A到M开头的名字
            AND email LIKE '%@__%.__%'; -- 标准email模式

3.14 条件和流程控制增强

01.常见函数
    a.CHOOSE - 索引选择
        -- SQL Server
        SELECT CHOOSE(2, 'First', 'Second', 'Third');  -- 返回 'Second'
    b.复杂CASE表达式
        SELECT
            employee_name,
            salary,
            CASE
                WHEN salary <= (SELECT AVG(salary) FROM employees) THEN 'Below Average'
                WHEN salary <= (SELECT AVG(salary) + STDDEV(salary) FROM employees) THEN 'Average'
                WHEN salary <= (SELECT AVG(salary) + 2*STDDEV(salary) FROM employees) THEN 'Above Average'
                ELSE 'Exceptional'
            END as salary_category
        FROM employees;

3.15 表分析函数

01.常见函数
    a.PERCENT_RANK - 百分比排名
        SELECT
            name,
            salary,
            PERCENT_RANK() OVER (ORDER BY salary) as salary_percentile
        FROM employees;
    b.CUME_DIST - 累积分布
        SELECT
            name,
            salary,
            CUME_DIST() OVER (ORDER BY salary) as salary_distribution
        FROM employees;

3.16 实用复合函数示例

01.常见函数
    a.年龄计算
        -- MySQL
        SELECT
            name,
            birthdate,
            TIMESTAMPDIFF(YEAR, birthdate, CURDATE()) as age,
            DATE_ADD(birthdate,
                    INTERVAL TIMESTAMPDIFF(YEAR, birthdate, CURDATE()) YEAR) as last_birthday,
            DATE_ADD(birthdate,
                    INTERVAL TIMESTAMPDIFF(YEAR, birthdate, CURDATE()) + 1 YEAR) as next_birthday
        FROM employees;
    b.工龄分析
        SELECT
            name,
            hire_date,
            CASE
                WHEN DATEDIFF(YEAR, hire_date, GETDATE()) < 2 THEN 'Junior'
                WHEN DATEDIFF(YEAR, hire_date, GETDATE()) < 5 THEN 'Intermediate'
                WHEN DATEDIFF(YEAR, hire_date, GETDATE()) < 10 THEN 'Senior'
                ELSE 'Expert'
            END as experience_level
        FROM employees;
    c.薪资分析
        WITH salary_stats AS (
            SELECT
                department,
                AVG(salary) as avg_salary,
                STDDEV(salary) as salary_stddev
            FROM employees
            GROUP BY department
        )
        SELECT
            e.name,
            e.department,
            e.salary,
            s.avg_salary,
            (e.salary - s.avg_salary) / s.salary_stddev as z_score,
            PERCENT_RANK() OVER (PARTITION BY e.department ORDER BY e.salary) as dept_percentile
        FROM employees e
        JOIN salary_stats s ON e.department = s.department;
    d.考勤分析
        WITH daily_attendance AS (
            SELECT
                employee_id,
                attendance_date,
                check_in_time,
                check_out_time,
                CASE
                    WHEN check_in_time > '09:00:00' THEN 'Late'
                    WHEN check_out_time < '17:00:00' THEN 'Early Leave'
                    ELSE 'Normal'
                END as attendance_status
            FROM attendance
        )
        SELECT
            e.name,
            COUNT(*) as total_days,
            SUM(CASE WHEN a.attendance_status = 'Late' THEN 1 ELSE 0 END) as late_days,
            SUM(CASE WHEN a.attendance_status = 'Early Leave' THEN 1 ELSE 0 END) as early_leave_days,
            FORMAT(COUNT(*) * 1.0 /
                   (SELECT COUNT(DISTINCT attendance_date) FROM attendance), 'P') as attendance_rate
        FROM employees e
        JOIN daily_attendance a ON e.id = a.employee_id
        GROUP BY e.name;
    e.销售分析
        WITH monthly_sales AS (
            SELECT
                YEAR(sale_date) as year,
                MONTH(sale_date) as month,
                SUM(amount) as total_sales,
                COUNT(DISTINCT customer_id) as customer_count
            FROM sales
            GROUP BY YEAR(sale_date), MONTH(sale_date)
        )
        SELECT
            year,
            month,
            total_sales,
            customer_count,
            total_sales / customer_count as avg_customer_value,
            LAG(total_sales) OVER (ORDER BY year, month) as prev_month_sales,
            total_sales - LAG(total_sales) OVER (ORDER BY year, month) as sales_growth,
            FORMAT((total_sales - LAG(total_sales) OVER (ORDER BY year, month)) /
                   LAG(total_sales) OVER (ORDER BY year, month), 'P') as growth_rate
        FROM monthly_sales;