1 开始

1.1 慢查询

01.概念
    慢查询(Slow Query)指的是执行时间较长,性能较差的SQL查询语句,通常在数据库系统中会显著影响整体性能。
    慢查询可能导致响应时间增加,阻塞其他操作,从而影响用户体验或后台处理的效率。

02.mysql中获取慢查询语句
    a.临时开启
        -- 切换数据库
        use jeecgboot;
        -- 设置开启慢查询日志
        SET GLOBAL slow_query_log = 1;
        -- 设置慢查询阈值
        SET GLOBAL long_query_time=1;
        -- 查看慢查询是否开启
        SHOW VARIABLES LIKE '%slow_query_log%';
        -- 查看慢查询阈值
        show global variables like 'long_query_time';
    b.永久开启(不建议)
        # my.cnf
        [mysqld]
        # 开启慢查询
        slow_query_log=ON
        # 指定存储慢查询日志的文件。如果这个文件不存在,会自动创建
        slow_query_log_file=/var/lib/mysql/slow.log
        # 设置慢查询阈值(单位:s)
        long_query_time=1
    c.查看慢查询语句
        cat /var/lib/mysql/c65f6d071766-slow.log

03.使用druid获取慢查询语句
    略

1.2 sql查询流程

1.3 基于查询流程的优化

01.常用信息1
    a.尽量不要用*
        分析成本高
        获取数据开销大
        内存占用大
        无法利用覆盖索引
    b.SQL务必要写完整,不要使用缩写法
        select name "姓名" from user;
        select * from t1,t2 where t1.f = t2.f;
        -----------------------------------------------------------------------------------------------------
        所有缩写的写法,在MySQL底层都需要做一次转换,将其转换为完整的写法,
        因此简写的SQL会比完整的SQL多一步转化过程,如果你考虑极致程度的优化,也切记将SQL写成完整的语法。
        -----------------------------------------------------------------------------------------------------
        select name as "name" from user;
        select * from t1 t1 inner join t2 t2 on t1.f段 = t2.f;
    c.明确仅返回一条数据的语句可以使用limit x
        select * from t where name = 'c';
        select * from t where name = 'c' limit 1;
        上述这两条SQL语句都是根据姓名查询一条数据,但后者大多数情况下会比前者好,
        因为加上limit 1关键字后,当程序匹配到一条数据时就会停止扫描,如果不加的情况下会将所有数据都扫描一次。
        所以一般情况下,如果确定了只需要查询一条数据,就可以加上limit 1提升性能。

02.常用信息2
    a.小表驱动大表
        select * from test_sql_user u
        left join test_sql_tenant_actor tu on tu.actor_id = u.id
        left join test_sql_tenant t on tu.tenant_id = t.id
        limit 1000;
    b.连表查询时尽量不要关联太多表
        数据量会随表数量呈直线性增长,数据量越大检索效率越低。
        当关联的表数量过多时,无法控制好索引的匹配,涉及的表越多,索引不可控风险越大。
    c.尽量避免深分页的情况出现
        select xx,xx,xx from yyy limit 100000,10;
        上述这条SQL相当于查询第1W页数据,在MySQL的实际执行过程中,
        首先会查询出100010条数据,然后丢弃掉前面的10W条数据,将最后的10条数据返回,这个过程无异极其浪费资源。
        select xx,xx,xx from yyy where sort >= n limit 10;

2 索引计划

2.1 索引定义

2.2 索引类型

01.索引类型:
    唯一索引:唯一索引也是一种约束。唯一索引的属性列不能出现重复的数据,但是允许数据为 NULL,一张表允许创建多个唯一索引。
    普通索引:普通索引的唯一作用就是为了快速查询数据,一张表允许创建多个普通索引,并允许数据重复和 NULL。
    前缀索引:前缀索引只适用于字符串类型的数据。前缀索引是对文本的前几个字符创建索引,相比普通索引建立的数据更小,因为只取前几个字符。
    全文索引:全文索引主要是为了检索大文本数据中的关键字的信息,是目前搜索引擎数据库使用的一种技术。

02.索引方法:
    B-Tree:多路平衡查找树 ,B+ 树是 B 树的一种变体。B 树和 B+树中的 B 是 Balanced (平衡)的意思。
    HASH:哈希表是键值对的集合,通过键(key)即可快速取出对应的值(value),因此哈希表可以快速检索数据(接近 O(1))。

2.3 索引失效

01.索引失效
    对索引使用左或者左右模糊匹配,也就是 like %xx 或者 like %xx% 这两种方式都会造成索引失效。原因在于查询的结果可能是多个,不知道从哪个索引值开始比较,于是就只能通过全表扫描的方式来查询。
    对索引进行函数/对索引进行表达式计算,因为索引保持的是索引字段的原始值,而不是经过函数计算的值,自然就没办法走索引。
    对索引进行隐式转换相当于使用了新函数。
    WHERE 子句中的 OR语句,只要有条件列不是索引列,就会进行全表扫描。
    如果字段类型是字符串,where时一定用引号括起来,否则索引失效
    联合索引,查询时的条件列不是联合索引中的第一个列,索引失效。
    对索引列运算(如,+、-、*、/),索引失效。
    索引字段上使用(!= 或者 < >,not in)时,可能会导致索引失效。
    索引字段上使用is null, is not null,可能导致索引失效。
    左连接查询或者右连接查询查询关联的字段编码格式不一样,可能导致索引失效。
    mysql 估计使用全表扫描要比使用索引快,则不使用索引。

02.索引左前原则
    a.原则
        创建复合索引                                                    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)三个索引,这就是最左匹配原则。
        联合索引不满足最左原则,索引一般会失效。
    b.分析
        select u.* from test_sql_user u
        left join test_sql_tenant_actor tu on tu.actor_id = u.id
        left join test_sql_tenant t on tu.tenant_id = t.id
        where u.phone like '13896622%' and u.area > 7 and u.continuous_visit_days < 10 ;
        -----------------------------------------------------------------------------------------------------
        索引的最左前缀原则,可以是联合索引的最左N个字段。比如你建立一个组合索引(a,b,c),
        其实可以相当于建了(a),(a,b),(a,b,c)三个索引,大大提高了索引复用能力。
        -----------------------------------------------------------------------------------------------------
        当然,最左前缀也可以是字符串索引的最左M个字符。。比如,你的普通索引树是这样:
        这个SQL: select * from employee where name like '小%' order by age desc; 也是命中索引的。

03.为什么有时候有索引,却不走索引?
    a.分析
        比如 sex > 0 明明有索引,为什么不走?
        select u.* from test_sql_user u
        left join test_sql_tenant_actor tu on tu.actor_id = u.id
        left join test_sql_tenant t on tu.tenant_id = t.id
        where sex > 0 and u.area > 7 and u.continuous_visit_days < 10 ;
    b.原因
        当优化器发现,走了索引但是优化效果不明显时,就不会走索引了。

04.回表操作、覆盖索引
    a.分析
        select u.sex,u.area,u.continuous_visit_days from test_sql_user u
        left join test_sql_tenant_actor tu on tu.actor_id = u.id
        left join test_sql_tenant t on tu.tenant_id = t.id
        where sex > 0 and u.area > 7 and u.continuous_visit_days < 10 ;
    b.操作
        覆盖索引(covering index ,或称为索引覆盖)即从非主键索引中就能查到的记录,而不需要查询主键索引中的记录,
        避免了回表的产生减少了树的搜索次数,显著提升性能。
        如果一个索引包含了(或覆盖了)满足查询语句中字段与条件的数据就叫做覆盖索引
        -----------------------------------------------------------------------------------------------------
        背景:索引分为主键索引(聚簇索引)和非主键索引(二级索引),主键索引中存储了整行数据,二级索引存储主键数据。
        命中二级索引时,从二级索引中拿到数据主键,在用数据主键去主键索引中获取查询的数据。
        覆盖索引可以减少树的搜索次数,显著提升查询性能,所以使用覆盖索引是一个常用的性能优化手段。

05.字符串加索引
    直接创建完整索引,这样可能会比较占用空间。
    创建前缀索引,节省空间,但会增加查询扫描次数,并且不能使用覆盖索引。
    倒序存储,再创建前缀索引,用于绕过字符串本身前缀的区分度不够的问题。
    创建 hash 字段索引,查询性能稳定,有额外的存储和计算消耗,跟第三种方式一样,都不支持范围扫描。

06.大表如何添加索引
    如果一张表数据量级是千万级别以上的,那么,如何给这张表添加索引?
    我们需要知道一点,给表添加索引的时候,是会对表加锁的。如果不谨慎操作,有可能出现生产事故的。可以参考以下方法:
    1.先创建一张跟原表A数据结构相同的新表B。
    2.在新表B添加需要加上的新索引。
    3.把原表A数据导到新表B
    4.rename新表B为原表的表名A,原表A换别的表名;

2.4 explain

01.explain
    用法:EXPLAIN <SQL>
    mysql> explain select * from test_sql_user u;
    +----+-------------+-------+------------+------+---------------+------+---------+------+--------+----------+-------+
    | id | select_type | table | partitions | type | possible_keys | key  | key_len | ref  | rows   | filtered | Extra |
    +----+-------------+-------+------------+------+---------------+------+---------+------+--------+----------+-------+
    |  1 | SIMPLE      | u     | NULL       | ALL  | NULL          | NULL | NULL    | NULL | 644930 |   100.00 | NULL  |
    +----+-------------+-------+------------+------+---------------+------+---------+------+--------+----------+-------+
    1 row in set, 1 warning (0.01 sec)

02.常见属性
    a.id
        这是执行计划的ID值,一条SQL语句可能会出现多步执行计划,所以会出现多个ID值,这个值越大,表示执行的优先级越高:
        -----------------------------------------------------------------------------------------------------
        值相同,由上向下执行;
        select * from test_sql_user u
        left join test_sql_tenant_actor tu on tu.actor_id = u.id
        left join test_sql_tenant t on tu.tenant_id = t.id
        limit 1000;
        -----------------------------------------------------------------------------------------------------
        ID值不同,值越大优先级越高
        select * from test_sql_user u where u.id in (select actor_id from test_sql_tenant_actor ta where ta.is_main = 'Y' and ta.actor_id like '012ec46995e011e%')
        limit 1000;
        -----------------------------------------------------------------------------------------------------
        ID值为空,ID=null时,会放在最后执行。
    b.select_type
        当前执行的select语句其具体的查询类型:
        SIMPLE:简单的select查询语句,不包含union、子查询语句。
        PRIMARY:union或子查询语句中,最外层的主select语句。
        SUBQUEPY:包含在主select语句中的第一个子查询,如select ... xx = (select ...)。
        DERIVED:派生表,指包含在from中的子查询语句,如select ... from (select ...)。
        UNION:多条语句通过union组成的查询中,第二个以及更后面的select语句。
        UNION RESULT:union的结果集。
        这个字段主要是说明当前查询语句所属的类型,以及在整条大的查询语句中,当前这个查询语句所属的位置。
    c.table
        当前执行的select的哪一张表,可能是物理表,也有可能是子查询的结果
    d.type
        all:全表扫描,基于表中所有的数据,逐行扫描并过滤符合条件的数据。
        index:全索引扫描,和全表扫描类似,但这个是把索引树遍历一次,会比全表扫描要快。
        range:基于索引字段进行范围查询,如between、<、>、in....等操作时出现的情况。
        fulltext:基于全文索引字段,进行查询时出现的情况。
        ref:基于非主键或唯一索引字段查找数据时,会出现的情况。
        eq_ref:连表查询时,基于主键、唯一索引字段匹配数据的情况,会出现多次索引查找。
        const:通过索引一趟查找后就能获取到数据,基于唯一、主键索引字段查询数据时的情况。
        system:表中只有一行数据,这是const的一种特例。
        null:表中没有数据,无需经过任何数据检索,直接返回结果。
        性能排序:system > const > eq_ref > ref > fulltext > range > index > all
    e.possible_keys 和 key
        possible_keys: 表示 sql 内部分析出来的该条 sql 应该会被用到的索引
        key: 表示实际的 sql 执行过程当中被用到的索引是哪一个 我们可以通过判断 key 的值是否是 null 来判断索引是否失效没有被用上
        主要有两种情况需要注意:
        possible_keys有值,key也有值。代表我们创建的索引被正常使用了。
        possible_keys有值,key没有值。代表我们创建的索引但是索引没有用,这就是索引失效了。
    f.ref
        显示索引查找过程中,查询时会用到的常量或字段:
        const:如果显示这个,则代表目前是在基于主键字段值或数据库已有的常量(如null)查询数据。
        显示具体的字段名:表示目前会基于该字段查询数据。
    g.rows
        执行时,预计会扫描的行数,这个数字对于InnoDB表来说,其实有时并不够准确,但也具备很大的参考价值,
        如果这个值很大,在执行查询语句时,其效率必然很低,所以该值越小越好。
    h.extra
        该字段会包含MySQL执行查询语句时的一些其他信息,这个信息对索引调优而言比较重要,
        可以带来不小的参考价值,但这个字段会出现的值有很多种,如下:
        -----------------------------------------------------------------------------------------------------
        Using index:表示目前的查询语句,使用了索引覆盖机制拿到了数据。
        Using where:表示目前的查询语句无法从索引中获取数据,需要进一步做回表去拿表数据。
        Using temporary:表示MySQL在执行查询时,会创建一张临时表来处理数据。
        Using filesort:表示会以磁盘+内存完成排序工作,而完全加载数据到内存来完成排序。
        Select tables optimized away:表示查询过程中,对于索引字段使用了聚合函数。
        Using where;Using index:表示要返回的数据在索引中包含,但并不是索引的前导列,需要做回表获取数据。
        NULL:表示查询的数据未被索引覆盖,但where条件中用到了主键,可以直接读取表数据。
        Using index condition:和Using where类似,要返回的列未完全被索引覆盖,需要回表。
        Using join buffer (Block Nested Loop):连接查询时驱动表不能有效的通过索引加快访问速度时,会使用join-buffer来加快访问速度,在内存中完成Loop匹配。
        Impossible WHERE:where后的条件永远不可能成立时提示的信息,如where 1!=1。
        Impossible WHERE noticed after reading const tables:基于唯一索引查询不存在的值时出现的提示。
        const row not found:表中不存在数据时会返回的提示。
        distinct:去重查询时,找到某个值的第一个值时,会将查找该值的工作从去重操作中移除。
        Start temporary, End temporary:表示临时表用于DuplicateWeedout半连接策略,也就是用来进行semi-join去重。
        Using MRR:表示执行查询时,使用了MRR机制读取数据。
        Using index for skip scan:表示执行查询语句时,使用了索引跳跃扫描机制读取数据。
        Using index for group-by:表示执行分组或去重工作时,可以基于某个索引处理。
        FirstMatch:表示对子查询语句进行Semi-join优化策略。
        No tables used:查询语句中不存在from子句时提示的信息,如desc table_name;。

3 索引类型

3.1 分类1

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 等),它的存储方式和查询优化机制不同于传统索引。
        空间索引:专门为存储和检索地理数据设计。

3.2 分类2

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

01.普通索引(Index)
    a.特点
        允许字段值重复,不强制唯一性。
        是最基本的索引类型,没有额外的限制条件。
    b.作用
        提高查询性能,减少全表扫描。
        支持范围查询(<、>、BETWEEN、LIKE 等)。
        优化排序(ORDER BY)。
    c.适用场景
        高查询频率但不需要唯一性的字段,例如:状态字段、外键字段。
        经常需要排序或范围查询的字段,如:日期、价格等。

02.唯一索引(Unique Index)
    a.特点
        保证索引字段值唯一。
        如果表中插入重复值,数据库会报错。
        支持查询优化,与普通索引性能相近。
    b.作用
        提高查询效率。
        确保数据唯一性约束。
    c.适用场景
        唯一标识的字段,例如:用户名、邮箱、身份证号等。
        需要既提高查询效率,又避免重复数据的场景。

03.主键索引(Primary Key Index)
    a.特点
        是一种特殊的唯一索引,每张表只能有一个主键索引。
        主键索引字段不能为 NULL。
        默认通过主键建立聚簇索引(InnoDB)。
    b.作用
        确保记录的唯一性。
        用于快速定位记录,并作为其他索引的引用(如外键)。
    c.适用场景
        唯一标识记录的字段,例如 id(通常为表的主键)。

04.复合索引(Composite Index)
    a.特点
        包含多个字段组成的索引。
        查询时只有符合索引字段的顺序规则,才能有效利用索引。
    b.作用
        优化多条件查询。
        避免为多个字段分别建立单独的索引,从而降低索引开销。
    c.适用场景
        经常联合查询多个字段的场景,例如:(department_id, status)。
        注意:复合索引遵循“最左前缀匹配原则”,只有从左到右匹配时索引才会生效。

05.全文索引(Fulltext Index)
    a.特点
        用于全文搜索,与普通索引不同,主要处理非结构化文本数据。
        支持分词匹配和快速定位关键词。
    b.作用
        适合复杂的文本匹配,例如:搜索文章内容中的某些关键词。
    c.适用场景
        需要全文搜索的字段,例如博客、商品描述等。

06.空间索引(Spatial Index)
    a.特点
        用于存储和查询地理数据类型(如 Geometry、Point)。
        只能用于MyISAM或支持GIS的存储引擎(如InnoDB的一些最新版本)。
    b.作用
        提供地理空间数据的快速查询。
    c.适用场景
        地图服务、GIS应用中的位置坐标查询。

3.3 使用示例

00.示例:创建一个包含多种索引的表
    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;

01.主键索引 (Primary Key)
    a.定义
        PRIMARY KEY 是一种特殊的唯一索引,每个表只能有一个主键索引。
        主键索引会自动创建唯一索引,并且会自动加速对该字段的查询。
    b.特点
        主键字段值必须唯一且不能为空。
        每个表只能有一个主键索引。
    c.示例
        PRIMARY KEY (id)
        在本例中,id 字段是主键。

02.唯一索引 (Unique Index)
    a.定义
        UNIQUE 索引确保某个字段或字段组合中的值唯一,但允许 NULL 值。每个表可以有多个唯一索引。
    b.特点
        唯一索引的字段值不能重复。
        可以创建多个唯一索引。
    c.示例
        UNIQUE (username),
        UNIQUE (email)
        在本例中,username 和 email 字段都定义了唯一索引,确保每个用户名和电子邮件地址在表中唯一。

03.普通索引 (Regular Index)
    a.定义
        普通索引是最常见的索引类型,用于加速查询操作。它不保证数据的唯一性。
    b.特点
        适用于查询频繁的字段。
        可以多次添加普通索引。
    c.示例
        INDEX idx_department_status (department_id, status)
        INDEX idx_status (status)
        在本例中,department_id 和 status 字段分别有单独的普通索引,以及一个复合索引 idx_department_status,用于加速基于部门和状态的查询。

04.复合索引 (Composite Index)
    a.定义
        复合索引是基于多个字段创建的索引,适用于涉及多个字段查询的情况。
        复合索引的查询优化作用仅在查询中涉及索引的所有字段时才有效。
    b.特点
        复合索引的顺序很重要,查询中使用到的字段应该与复合索引的字段顺序一致。
        如果查询只涉及复合索引中的前缀字段,索引仍然有效。
    c.示例
        INDEX idx_department_status (department_id, status)
        在本例中,idx_department_status 是一个复合索引,包含 department_id 和 status 字段。
        当查询中同时使用这两个字段时,复合索引会提高查询效率。

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

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

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

3.4 [重]普通索引

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.普通索引 vs 唯一索引
    | 普通索引      | 唯一索引                      |
    |--------------|-------------------------------|---------------------
    | 是否允许重复  | 允许                          | 不允许
    | 主要作用      | 提高查询性能                  | 提高查询性能,同时强制唯一性约束
    | 适用场景      | 高频查询字段(如外键、状态等) | 需要确保值唯一的字段(如用户名、邮箱)
    | 查询性能      | 略低于唯一索引                | 性能稍高,但差异不明显

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

3.5 [重]唯一索引

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.需要联合唯一约束的字段组合
        如复合唯一性约束的场景,例如 (username, email) 确保每个用户名对应唯一的邮箱。
    d.约束与优化双重需求
        唯一索引同时满足数据约束和性能优化需求,适用于保证数据完整性和高效查询的场景。

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

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

06.唯一索引的适用实例
    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 '手机号,必须唯一'
    );
    ---------------------------------------------------------------------------------------------------------
    username、email 和 phone_number 字段均被定义为唯一索引,确保各自值在表中唯一,避免重复用户。

07.唯一索引与其他索引的对比
    | 对比点          | 唯一索引              | 主键索引             | 普通索引
    |-----------------|----------------------|----------------------|------------
    | 字段值唯一性     | 是                   | 是(且非空)          | 否
    | 允许字段为 NULL  | 是                   | 否                   | 是
    | 主要作用         | 查询优化 + 唯一性约束 | 数据完整性 + 主键标识 | 查询优化
    | 适用场景         | 唯一但非主键的字段    | 唯一标识字段          | 非唯一字段的高频查询

08.唯一索引的总结
    唯一索引不仅是一种高效的查询优化工具,还能提供强大的数据完整性约束。
    适合用于既需要唯一性约束,又需要优化查询性能的字段或字段组合。
    使用时需注意对插入、更新操作性能的影响,以及空值处理的特殊性。

3.6 [重]主键索引

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

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

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

06.主键索引与其他索引的对比
    | 对比点        | 主键索引                  | 唯一索引                    | 普通索引
    |--------------|---------------------------|-----------------------------|------------
    | 是否唯一      | 是                        | 是                          | 否
    | 允许 NULL     | 否                        | 是                          | 是
    | 是否自动创建  | 是                         | 否                         | 否
    | 存储方式      | InnoDB 默认使用聚簇索引     | 普通索引或辅助索引          | 普通索引
    | 适用场景      | 标识记录的唯一字段,如 id   | 唯一但非主键字段,如 邮箱    | 查询频繁的非唯一字段

3.7 [重]复合索引

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

03.复合索引的适用场景
    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';

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

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

06.复合索引的设计建议
    1.根据查询需求设计字段顺序
        优先将高选择性字段(区分度高的字段)放在索引的最左侧。
        按常用的查询字段组合顺序排列。
    2.避免过多字段 复合索引字段过多会增加维护成本,建议控制在 3-4 个以内。
    3.尽量覆盖查询需求 如果复合索引覆盖查询的所有字段,可实现覆盖索引查询,提升性能。

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

08.复合索引与其他索引的对比
    | 对比点       | 复合索引                 | 主键索引      | 唯一索引        | 普通索引       
    |------------ -|-------------------------|--------------|-----------------|------------
    | 字段数       | 多个字段                 | 单个字段      | 单个或多个字段  | 单个字段       
    | 是否唯一     | 否                       | 是           | 是              | 否          
    | 匹配顺序限制 | 是(遵循左前缀原则)       | 否          | 否              | 否          
    | 适用场景     | 多字段联合查询、排序、分组 | 唯一标识字段 | 唯一但非主键字段 | 查询频繁的非唯一字段 

09.复合索引的总结
    复合索引是优化多字段联合查询和排序的有效工具。
    使用时需注意字段顺序,遵循左前缀原则。
    设计复合索引时,应根据查询需求优先考虑高选择性字段。
    避免冗余设计,尽量减少字段数量以控制维护成本。

3.8 全文索引

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

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

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

06.全文索引的设计建议
    a.选择合适的字段
        仅为真正需要全文检索的字段建立全文索引,避免无意义的存储开销。
    b.考虑存储引擎兼容性
        使用 InnoDB 时,确保 MySQL 版本在 5.6 及以上。
    c.配合分词优化中文搜索
        对于中文或其他复杂语言,建议引入分词插件(如 NLPIR 或 Sphinx)。
    d.结合布尔模式实现高级搜索
        使用布尔模式的逻辑操作符实现精准控制的查询功能。

07.全文索引的实例
    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);

08.全文索引与其他索引的对比
    | 对比点      | 全文索引             | 普通索引     | 主键索引      | 复合索引       
    |-------------|---------------------|--------------|--------------|------------
    | 适用字段类型 | TEXT, CHAR, VARCHAR | 任意字段类型  | 任意字段类型 | 任意字段类型     
    | 支持模糊查询 | 是                   | 否          | 否           | 否          
    | 相关性排序   | 是                   | 否          | 否           | 否          
    | 多字段联合   | 支持(需分开定义)    | 不适合       | 不适合       | 最适合        
    | 适用场景     | 非结构化文本数据      | 精确匹配     | 主键标识     | 多字段联合查询、排序 

09.全文索引的总结
    全文索引是一种强大的文本搜索工具,适合大规模非结构化数据的关键词匹配和复杂搜索需求。
    使用自然语言模式或布尔模式,根据场景优化查询结果。
    对中文等复杂语言需配合分词工具使用,以提升分词准确性和查询性能。
    在设计中需合理评估性能与存储开销,避免滥用。

3.9 空间索引

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

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

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

06.空间索引的设计建议
    1.明确查询需求 空间索引仅适用于几何范围查询和距离运算,普通文本或数字查询无需使用。
    2.选择合适的字段类型 字段需定义为 GEOMETRY 或其子类型(如 POINT、POLYGON)。
    3.优化空间分布 数据分布需均衡,避免几何数据聚集造成的索引效率下降。
    4.结合 GIS 函数 配合如 ST_Contains、ST_Within 等函数,优化空间查询逻辑。
    5.避免频繁更新 对于经常变动的几何数据,索引维护代价较高,需综合评估。

07.空间索引的实例
    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);

08.空间索引与其他索引的对比
    | 对比点      | 空间索引                 | 普通索引         | 主键索引      | 全文索引    | 复合索引    
    |-------------|-------------------------|------------------|--------------|-------------|---------
    | 适用字段类型 | GEOMETRY                | 任意字段类型      | 任意字段类型 | 文本字段类型 | 任意字段类型  
    | 支持多维数据 | 是                      | 否               | 否           | 否          | 否       
    | 支持模糊查询 | 否                      | 否               | 否           | 是          | 否       
    | 查询方式     | 空间操作(如范围、距离) | 精确匹配或范围查询 | 精确匹配     | 关键词匹配  | 联合字段查询  
    | 适用场景     | 地理位置与空间数据       | 单字段或数值查询  | 唯一标识查询  | 文本搜索    | 多字段联合查询 

09.空间索引的总结
    空间索引是用于地理数据处理的强大工具,在需要处理地理坐标或其他几何数据的场景中表现尤为突出。
    优化范围查询与距离运算,是地理信息系统的重要组成部分。
    结合 GIS 函数灵活处理复杂几何操作。
    在设计中需注意字段类型限制及存储引擎兼容性,以确保索引有效性。

4 函数指南

4.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;

4.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;

4.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'

4.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

4.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;

4.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

4.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);

4.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)')

4.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}$');

4.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();

4.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;

4.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;

4.13 字符串模式匹配函数

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

4.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;

4.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;

4.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;