当前位置: 首页 > article >正文

后端java工程师经验之谈,工作7年,mysql使用心得

mysql

工作7年,mysql使用心得

  • mysql
    • 1.创建变量
    • 2.创建存储过程
      • 2.1:WHILE循环
      • 2.2:repeat循环
      • 2.3:loop循环
      • 2.4:存储过程,游标
      • 2.5:存储过程,有输入参数和输出参数
    • 3.三种注释写法
    • 4.case when then else end 写法
    • 5.if相关方法
    • 6.if语句
    • 7.创建方法
      • 7.1示例
    • 8.删除重复数据只保留最小id
    • 9.[`INSERT INTO ... SELECT`](https://dev.mysql.com/doc/refman/8.0/en/insert-select.html)标准的SQL语法
    • 10.SQL查询每个用户首次购买商品
    • 11.update
      • 11.1update多表连接更新
      • 11.2update ... where exists
    • 12.内连接inner join 和左连接left join
      • 12.1 内连接
      • 12.2左连接和右连接
    • 13.等值连接和非等值连接
    • 看到【有】就联想到inner join ,看到【没有】就联想到left join
      • 1. **更全面的理解**
        • 1.1 **`INNER JOIN`:关注匹配的记录**
        • 1.2 **`LEFT JOIN`:保留左表的所有记录**
        • 1.3 **其他连接类型**
      • 2. **如何选择合适的连接类型?**
        • 2.1 **明确查询目标**
        • 2.2 **考虑连接的方向**
        • 2.3 **利用 `NULL` 来识别缺失的记录**
      • 3. **总结:简化规则 vs. 灵活应用**
      • 4. **练习和实践**
      • 5. **示例对比**
        • 5.1 **`INNER JOIN` 示例**
        • 5.2 **`LEFT JOIN` 示例**
      • 6. **结论**
    • 在什么情景下,我们应该考虑非等值连接?
      • 1. **为什么会有等值连接和非等值连接?**
        • 1.1 **等值连接(Equi-Join)**
        • 1.2 **非等值连接(Non-Equi-Join)**
      • 2. **什么时候应该考虑使用非等值连接?**
        • 2.1 **范围查询**
        • 2.2 **部分匹配**
        • 2.3 **复杂业务逻辑**
        • 2.4 **分段聚合**
      • 3. **非等值连接的性能优化建议**
      • 4. **总结**
    • 14.自连接
    • 15.jpa
      • 15.1 jpa调用存储过程
      • 15.2 jpa多条件 in /and /or 分页
      • 15.3 jpa 多条件、分页、分组 dto/vo --使用spring-data-jpa
      • @query 原生sql
    • 16.mybatis plus
      • 16.1 创建PaginationInterceptor bean对象
      • 16.2在数据源中设置mybatis plus分页插件
      • 16.3mybatis plus分页查询条件
      • 16.4 mybatis plus and or
      • 16.4 mybatis plus update
      • 16.5 mybatis 批量插入和批量更新和批量查询
      • 16.6 mybatis xml foreach in
      • 16.7 in多个列的写法
      • 16.8 mybatis 映射文件中,if标签判断字符串相等:
      • 16.9 加载树形结构
    • 17.JdbcTemplate 批量操作
      • 17.2NamedParameterJdbcTemplate
      • 17.2NamedParameterJdbcTemplate
        • 17.2.1 批量更新插入batchUpdate
        • 17.2.2 多条件分页和in
    • 18.月份不足两位补零
    • 19.创建连续日期
      • 19.1使用变量和循环生成序列
      • 19.2使用递归生成序列
      • 19.3 UNION ALL 操作符来生成序列
      • 19.4 在mybatis中写变量生成序列
    • 20.创建随机函数
    • 21. mysql有哪些系统表,分别有什么作用?
    • 22.mysql分割字符串
    • 23.修改表的AUTO_INCREMENT
    • 24.使用with RECURSIVE 生成序列
      • 24.1 获取本周的日期序列
      • 24.2 获取本月的日期序列
      • 24.2 获取本年的日期序列
      • 24.3 获取本年的日期序列2
      • 24.4 获取数字序列
      • 24.5 根据传入的开始时间和结束时间生成日期序列,并左联构造出来的表
    • 25.获取当前几号
    • 26.获取当前日期
    • 27.获取本月最后一天
    • 28.获取本月第一天
    • 获取当年第一天
    • 29.使用with RECURSIVE 获取层级结构关系
    • 30.普通表表达式(CTE)common table expression
    • 示例
    • 31.mybatis批量插入:INSERT INTO...ON DUPLICATE KEY UPDATE
    • 32. in多个列的写法
    • 33.mysql 生成列【就是表中一列是通过其它动态生成的】
    • 34.窗口函数【参考mysql窗口函数(Window Functions)详解】
      • 1. **窗口函数的基本语法**
      • 34.1 排名及排序
    • 35.mysql排序
    • 36.mysql 年月日时分秒与秒数互转
    • 37.临时修改mysql为北京时间
    • 38.mysql获取图片路径前缀
    • 39. mysql 传过来的是个日期范围,数据库也是个日期范围的查询
  • sql 语句优化
    • 0:mysql语句执行顺序
    • 1:查询表中的信息
    • 2:in、exists、left join 小表驱动大表
      • 2.1使用exists替代left join
    • 3:mysql对T-SQL标准的扩展
    • 4:mysql 索引
      • 4.1:创建索引
      • 4.2:查看索引和删除索引
      • 4.3示例:有where、有and or 、有like、有order by 有limit
    • 5:like优化
    • 6.优化过程optimizer trace
    • 7.性能瓶颈定位
    • 8.sql mode
    • 9.mysql 变量、状态 、连接数
    • 10.HikariCP数据库连接池常用参数
    • max-lifetime的作用
    • max-lifetime和idle-timeout有什么区别?
    • max-lifetime和idle-timeout分别和数据库的什么配置参数有关?
    • 如果在程序中,max-lifetime配置了30分钟,数据库服务器wait_timeout配置了1小时,以哪个为准?
    • maximum-pool-size与数据库中的什么配置有关?
    • spring.datasource.jdbc-url连接字符串的连接参数
    • 11.mybatis中文等值判断
    • 12.mybatis批量更新
    • 执行计划EXPLAIN
      • EXPLAIN输出列
      • select_type类型
      • type类型
      • Extra类型
    • 口诀
    • 优化指南
    • 使用performance_schema
    • 使用information_schema
    • 使用 show full processlist
      • 一:有几个常见的原因会导致大量的连接处于"Sleep"状态:
      • 二:如果在执行 `SHOW FULL PROCESSLIST` 命令时发现大量的空闲连接,同时其他用户却无法获取数据库连接并出现连接获取失败的提示
    • performance_schema.events_statements_current的每个字段的含义
    • 批量查询连接进程id
      • 锁等待超时
      • mysql事务等待超时
      • ID
      • USER
    • performance_schema.events_statements_current的每个字段的含义
    • 批量查询连接进程id
      • 锁等待超时
      • mysql事务等待超时
      • ID
      • USER

1.创建变量

如果想获取查询语句中的一个字段值可以用select给变量赋值,如下:
select @num=字段名 from 表名 where ……
mysql中变量不用事前申明,在用的时候直接用”@变量名”使用就可以了。
第一种用法:set @num=1;set @num:=1; //这里要使用变量来保存数据,直接使用@num变量
第二种用法:select @num:=1;select @num:=字段名 from 表名 where ……
注意上面两种赋值符号,使用set时可以用”=”或”:=”,但是使用select时必须用”:=赋值”
set @var=0;
select *,@var:=date_task_id  from date_details_task where task_num=1 and task_type=5 and finish_num !=1 and DATE_FORMAT(create_time,'%Y-%m-%d') in ('2020-11-21')

2.创建存储过程

2.1:WHILE循环

-- 如果有存储过程,删除该存储过程
drop PROCEDURE  if EXISTS test_two;
DELIMITER //
-- 创建存储过程
CREATE PROCEDURE test_two ()
BEGIN
DECLARE i INT DEFAULT 10;-- DECLARE 声明只能在begin...end 和存储过程语句中,否则创建变量直接用set
	WHILE i < 100 DO
		SET i = i + 1;
		select i;
	END WHILE;
END;
//
-- 调用存储过程
CALL test_two ();
DELIMITER ;


创建存储过程 批量插入数据库

-- 如果有存储过程,删除该存储过程
drop PROCEDURE  if EXISTS test_two2;
DELIMITER //
-- 创建存储过程
CREATE PROCEDURE test_two2 ()
BEGIN
DECLARE i INT DEFAULT 1;-- DECLARE 声明只能在begin...end 和存储过程语句中,否则创建变量直接用set
	WHILE i <=90 DO
		INSERT INTO `yushu_jdb`.`user_vip_module_power` ( `vip_level`, `module`, `num_limit`, `create_time`, `update_time`) VALUES ( 1, 1, 1, NOW(), NOW());
    set i=i+1;
	END WHILE;
END ;
//


-- 调用存储过程
CALL test_two2();
DELIMITER ;

TRUNCATE table user_vip_module_power

2.2:repeat循环

-- 第三种 repeat 循环:直到条件满足退出循环
  /*repeat 循环语法
  repeat
      循环体
  until 条件 end repeat;
  */
  drop procedure if exists sum55;#删除存储过程
  DELIMITER //
  create procedure sum55(a int)
  begin
       declare sum int default 0;
      declare i int default 1;
       repeat -- 循环开始
           set sum=sum+i;
           set i=i+1;
       until i>a end repeat; -- 循环结束
       select sum; -- 输出结果
  end;
  //
call sum55(100);-- 执行存储过程
DELIMITER ;

2.3:loop循环

-- 第二种 loop 循环
  /*loop 循环语法:
  loop_name:loop
          if 条件 THEN -- 满足条件时离开循环
               leave loop_name;  -- 和 break 差不多都是结束循环
       end if;
  end loop;
  */
-- 删除存储过程
drop procedure if exists  sums;
DELIMITER //
create procedure sums(a int)
begin
        declare sum int default 0;
        declare i int default 1;
        loop_name:loop -- 循环开始
            if i>a then 
                leave loop_name;  -- 判断条件成立则结束循环  好比java中的 break
            end if;
            set sum=sum+i;
            set i=i+1;
        end loop;  -- 循环结束
        select sum; -- 输出结果
end;
//
 -- 执行存储过程
call sums(100);
DELIMITER ;

2.4:存储过程,游标

delimiter //  # 定义//为一句sql的结束标志,取消;的所代表的意义
drop procedure if exists test;  # 如果存在名字为test的procedure则删除
create procedure test()  # 创建(创建函数使用的关键字为function 函数名())
begin
    declare old_pro varchar(30);  # 声明变量
    declare temp_id int;
    declare flag int default 0;
    # 这是重点,定义一个游标来记录sql查询的结果(此处的知识点还有SQL的模糊查询,见补充)
    declare s_list cursor for select id, province from temp_table where like "%省"; 
    # 为下面while循环建立一个退出标志,当游标遍历完后将flag的值设置为1
    declare continue handler for not found set flag=1;
    open s_list;  # 打开游标
    # 将游标中的值赋给定义好的变量,实现for循环的要点
        fetch s_list into temp_id, old_pro;
        while flag <> 1 do
            # sql提供了字符串的切分,有left、right、substring、substring_index
            # 在T-SQL中,局部变量必须以@作为前缀,声明方式set,select还有点差别
            set @temp_s = substring_index(old_pro, "省", 1);
            # 根据id的唯一性,利用当前查询到的记录中的字段值来实现更新
            update temp_table set province=@temp_s where id=temp_id;
            # 游标往后移(此处的游标是不是让你想起了C里面的指针)
            fetch s_list into temp_id, old_pro;
        end while;
        #select * from temp_table;
    close s_list;  # 关闭游标
end
//
delimiter ;  # 重新定义;为一句sql的结束标志,取消//的所代表的意义
call test(); # 调用

/**
SQL具有四种匹配模式
1、%:表示零个或多个字符;
2、_:表示任意单个字符;
3、[]:表示括号内所列字符中的任意一个(类似正则);
4、[^]:取反(类似正则);
注:若匹配中包含通配符则使用“[]”将特殊字符括起来即可(相当于转义)
*/

2.5:存储过程,有输入参数和输出参数

-- phone 使用like 是为了兼容教师角色,pwd是为了兼容新创建的账号,start_time不为空是为了...
select * from t_user_info  where  phone like '13907966696%' and (pwd is  null or pwd ='') and start_time is not null;

-- 如果有存储过程,删除该存储过程
drop PROCEDURE  if EXISTS update_temp_account_user;
-- 修改语句结束符为// 
DELIMITER //
-- 创建存储过程,mobilePhone、startTime、endTime为输入入参,limitTime为输出参数
CREATE PROCEDURE update_temp_account_user(IN mobilePhone VARCHAR(50),IN startTime VARCHAR(50), IN endTime VARCHAR(50), OUT limitTime VARCHAR(100))
BEGIN
    SET limitTime = CONCAT('{"day":"', startTime, ',', endTime, '"}');
		update t_user_info  as t1 ,  (select user_id from t_user_info  where  phone like  CONCAT('',mobilePhone,'%') and (pwd is  null or pwd ='') and start_time is not null)  as t2
		set start_time =startTime,end_time=endTime,limit_time=limitTime where t1.user_id=t2.user_id;
		
		select * from t_user_info  where  phone like  CONCAT('',mobilePhone,'%') and (pwd is  null or pwd ='') and start_time is not null;
END;
//

-- 改回来,修改语句结束符为; 
DELIMITER ;

-- 调用存储过程就两行
-- 定义输出参数
SET @output_param = '';
                    --   手机号         临时账号开始时间       临时账号结束时间       输出参数
CALL update_temp_account_user('13907966696','2023-05-04 09:00:00', '2023-05-04 12:00:00', @output_param);

SELECT @output_param;
DELIMITER $$
CREATE PROCEDURE `test_edu_deyuke`()
begin
    declare _activity_id varchar(100);  # 声明变量
  declare _is_need_institution int(11);  # 声明变量
    declare flag int default 0;
    # 这是重点,定义一个游标来记录sql查询的结果(此处的知识点还有SQL的模糊查询,见补充)
DECLARE s_list CURSOR FOR SELECT activity_id, IF(institution_id is null or institution_id='',0,1) as is_need_institution FROM t_activity_info where act_status=13 and activity_id not in (select activity_id from t_activity_progress GROUP BY activity_id)  GROUP BY activity_id;
    # 为下面while循环建立一个退出标志,当游标遍历完后将flag的值设置为1
    declare continue handler for not found set flag=1;
   # 打开游标
    open s_list; 
    # 将游标中的值赋给定义好的变量,实现for循环的要点
        fetch s_list into _activity_id, _is_need_institution;
        while flag <> 1 do
				   # 0不需要出行机构
					IF _is_need_institution=0 THEN
					    INSERT INTO t_activity_progress (snow_flake_id,activity_id,progress_name,is_ok,activity_node,create_time,update_time )
              VALUES
				       ('1',_activity_id,'学校选课',1,1,NOW(),NOW()),
							 ('1',_activity_id,'提交出行方案',1,3,NOW(),NOW()),
							 ('1',_activity_id,'活动审核备案',1,5,NOW(),NOW()),
							 ('1',_activity_id,'报名网签',1,6,NOW(),NOW()),
							 ('1',_activity_id,'开展活动',1,7,NOW(),NOW()),
							 ('1',_activity_id,'自我陈述与评价',1,8,NOW(),NOW()),
							 ('1',_activity_id,'点评与评价',1,9,NOW(),NOW()),
							 ('1',_activity_id,'形成个人档案',0,10,NOW(),NOW()),
							 ('1',_activity_id,'对接综评获得学分',0,11,NOW(),NOW());
				  ELSEIF _is_need_institution=1 THEN
							INSERT INTO t_activity_progress (snow_flake_id,activity_id,progress_name,is_ok,activity_node,create_time,update_time )
              VALUES
				       ('1',_activity_id,'学校选课',1,1,NOW(),NOW()),
							 ('1',_activity_id,'机构提交出行方案',1,2,NOW(),NOW()),
							 ('1',_activity_id,'审核出行方案',1,4,NOW(),NOW()),
							 ('1',_activity_id,'活动审核备案',1,5,NOW(),NOW()),
							 ('1',_activity_id,'报名网签',1,6,NOW(),NOW()),
							 ('1',_activity_id,'开展活动',1,7,NOW(),NOW()),
							 ('1',_activity_id,'自我陈述与评价',1,8,NOW(),NOW()),
							 ('1',_activity_id,'点评与评价',1,9,NOW(),NOW()),
							 ('1',_activity_id,'形成个人档案',0,10,NOW(),NOW()),
							 ('1',_activity_id,'对接综评获得学分',0,11,NOW(),NOW());
					END IF;
            # 游标往后移
        fetch s_list into _activity_id, _is_need_institution;
        end while;
        
    close s_list;  # 关闭游标
END $$
DELIMITER ;

3.三种注释写法

#MySql--三种注释写法
#需要特别注意  --   这种注释后面要加一个空格

#  
/* */
-- 空格

4.case when then else end 写法

#CASE 子句可以用于任何可以使用表达式的地方。【when后面无逗号】
#如果省略了ELSE子句而且没有匹配的条件,结果为null,可能会查出错误的结果。使用 end case 替代 end 来终止case
#写法一:只适合单值匹配,不适合多值匹配 即不能使用or 如: when (0 0r 1) then .... 错误写法不
# 因为 0 or 1的结果是1 ,就会变成case type when 1 then .... 如果type选项中没有1 就会一直执行else
CASE case_value
    WHEN when_value THEN statement_list
    [WHEN when_value THEN statement_list] ...
    [ELSE statement_list]
END CASE
#写法二:适合单值匹配,也适合多值匹配
CASE
    WHEN search_condition THEN statement_list
    [WHEN search_condition THEN statement_list] ...
    [ELSE statement_list]
END CASE
/*
	先写一个子查询,目的是为了找出date_task_id相同的4条数据中,其中两条数据的之和
	子查询使用到了两种case when then else end 写法,when不能与or连用,但可以与and 连用
	为了找出第一个数据与(其中两条数据的之和)的差值,在外面又嵌套了一个查询
*/
select *,(flowClick-addCartAndGoodsUp) as '差值' from (
  select date_task_id,
    sum(case when task_type=2 then task_num  else 0 end ) as flowClick,
	sum(case task_type when 3 then task_num when 4 then task_num else 0 end ) as addCartAndGoodsUp,
   sum(case when task_type=5 then task_num  else 0 end) as shopUp
 from date_details_task where  date_task_id=155128
) as r 

(case xxx when 0 then '' when 1 then '' when 2 then '' when 3 then '' else '未知' end ) as levelStr
#由于分组不支持中文别名,只支持英文别名,如果需要显示中文别名,需要再外面多加一层
select a.num as '数量', a.trainType as '快车类型' from 
(select count(id) as num,
 (case 
	when type=0 or type=7 then '快车-普通点击' 
  when type=1 or type=10 then '快车-加入购物车'
	when type=2 or type=11 then '快车-加车并提单' 
	when type=3 or type=12 then '快车-关注商品' 
	when type=4 or type=13 then '快车-关注店铺' 
	when type=5 or type=8 then '快车-展现提升任务' 
	when type=6 or type=9  then '快车-点击提升任务'
else '' end) as trainType
from aaaa GROUP BY trainType 
) a
 ;

5.if相关方法

IF(expr1,expr2,expr3)
#如果if表达式1为真(表达式1不等于0和表达式1不等于null),返回表达式2,否则表达式3
select IF(2 >3 or 3>2,1,0) from dual;

IFNULL(expr1,expr2)
#如果表达式1不为null,返回表达式1否则返回表达式2,类似于三元运算符

NULLIF(expr1,expr2)
#如果表达式1=表达式2返回null,否则返回表达式1
#类似于CASE WHEN expr1 = expr2 THEN NULL ELSE expr1 END.
#返回类型与第一个参数一致

6.if语句

#以if开头,以 end if 结束
IF search_condition THEN statement_list
[ELSEIF search_condition THEN statement_list] ...
[ELSE statement_list]
END IF;
#semicolon 分号,delimiter分隔符

# 示例
DELIMITER $$
 
CREATE PROCEDURE GetCustomerLevel(
    in  p_customerNumber int(11), 
    out p_customerLevel  varchar(10))
BEGIN
    DECLARE creditlim double;
 
    SELECT creditlimit INTO creditlim
    FROM customers
    WHERE customerNumber = p_customerNumber;
 
    IF creditlim > 50000 THEN
        SET p_customerLevel = 'PLATINUM';
    ELSEIF (creditlim <= 50000 AND creditlim >= 10000) THEN
        SET p_customerLevel = 'GOLD';
    ELSEIF creditlim < 10000 THEN
        SET p_customerLevel = 'SILVER';
    END IF;
 
END $$

DELIMITER ;

7.创建方法

CREATE
    [DEFINER = user]
    PROCEDURE sp_name ([proc_parameter[,...]])
    [characteristic ...] routine_body
proc_parameter:
    [ IN | OUT | INOUT ] param_name type

CREATE
    [DEFINER = user]
    FUNCTION sp_name ([func_parameter[,...]])
    RETURNS type
    [characteristic ...] routine_body

func_parameter:
    param_name type

type:
    Any valid MySQL data type

characteristic: {
    COMMENT 'string'
  | LANGUAGE SQL
  | [NOT] DETERMINISTIC
  | { CONTAINS SQL | NO SQL | READS SQL DATA | MODIFIES SQL DATA }
  | SQL SECURITY { DEFINER | INVOKER }
}

routine_body:
    Valid SQL routine statement

7.1示例

#将默认的分隔符;改为//
DELIMITER //
#RETURNS关键字只能在方法中使用,这是硬性规定的,它声明了返回值类型,方法体必须有RETURN关键字
#如果返回类型不一致,会强制转换
CREATE FUNCTION SimpleCompare(n INT, m INT) RETURNS VARCHAR(20)

  BEGIN
    DECLARE s VARCHAR(20);

    IF n > m THEN SET s = '>';
    ELSEIF n = m THEN SET s = '=';
    ELSE SET s = '<';
    END IF;

    SET s = CONCAT(n, ' ', s, ' ', m);

    RETURN s;
  END 
  //
#分隔符改回来
DELIMITER ;
#像调用其它sql语句一样调用自己写方法
select SimpleCompare(1,2);

8.删除重复数据只保留最小id

#先查出要删除的数据
select *  from  qrcode WHERE
id NOT IN (SELECT min(id) FROM qrcode GROUP BY type HAVING count(id) >=1)

#将要查询的数据使用group_concat函数用串联起来
select GROUP_CONCAT(id) from qrcode WHERE 
id  IN (SELECT min(id) FROM qrcode GROUP BY type HAVING count(id) >=1) order by type

-- 删除多余的数据
DELETE from t_stu_eva_norm where id not in (
-- 得再包装一层
select * from (select min(id)  from t_stu_eva_norm where  is_delete=0  group by student_id,act_id,act_course_id,norm_id) as a
)


-- 删除多余的数据
DELETE from t_trip_plan_new where id not in (
-- 得再包装一层
select * from (select min(id)  from t_trip_plan_new GROUP BY activity_id) as a
)

9.INSERT INTO ... SELECT标准的SQL语法

MySQL服务器不支持SELECT ... INTO TABLE 的Sybase SQL扩展。
但是支持SELECT ... INTO 变量

相反,MySQL Server支持 INSERT INTO ... SELECT标准的SQL语法,这基本上是相同的

INSERT INTO table1 (fld_id) 
SELECT table2.fld_order_id FROM table2 WHERE table2.fld_order_id > 100;
   
也可以使用   
CREATE TABLE ... SELECT
CREATE TABLE new_tbl [AS] SELECT * FROM orig_tbl;

10.SQL查询每个用户首次购买商品

SELECT g.name,g.product FROM goods g 
INNER JOIN (SELECT MIN(time) AS firstTime FROM  goods GROUP BY name ) tmp 
ON tmp.firstTime=g.time;

11.update

11.1update多表连接更新

#更新两表联查中满足条件的值
UPDATE items,#items表
       (SELECT id, retail / wholesale AS markup, quantity FROM items)
       AS discounted #查询部分作为第二张表
    SET items.retail = items.retail * 0.9
    WHERE discounted.markup >= 1.3
    AND discounted.quantity < 100
    AND items.id = discounted.id;
#示例二:一定要起别名
update (select id from task where end_date is null) as t,task t1 
set t1.end_date =STR_TO_DATE(DATE_FORMAT(t1.start_date,'%Y-%m-%d'),'%Y-%m-%d') 
where t.id=t1.id

11.2update … where exists

#使用update ... where exists ,exists子查询要多套一层
#否则会出现 You can't specify target table 'aaaa' for update in FROM clause
#错误的意思是说,不能先select出同一表中的某些值,再update这个表(在同一语句中)
#当需要使用用到多张表的条件,但是查询的结果字段又只包含在某张表里的时候可以使用exists语句
update aaaa aa set aa.json_str ='{}'
where exists (select * from (select t.id from aaaa t where t.id =1) a where a.id=aa.id)

12.内连接inner join 和左连接left join

12.1 内连接

#在两个表的内部联接中,第一个表的每一行与第二个表的每一行合并(联接)。
#假设第一张表中有n1行,第二张表中有n2行,则INNER JOIN产生n1 × n2行的所有组合-称为笛卡尔乘积或叉积。
#不加where条件或on条件就会产生笛卡尔积
#inner join...on 会将两表中都有数据筛选出来,只存在在一张表中的数据会过滤掉
#以下等效
 SELECT * FROM t1 INNER JOIN t2 ON t1.id = t2.id;
 SELECT * FROM t1 JOIN t2 ON t1.id = t2.id; -- 默认的 JOIN 是 INNER JOIN
 SELECT * FROM t1 CROSS JOIN t2 ON t1.id = t2.id;  -- join/cross join/inner join 同义词
       
-- 你可以使用 using 关键词,如果关联的条件是相同名称:ON t1.id = t2.id 可以写成 USING (id);
SELECT * FROM t1 INNER JOIN t2 USING (id);
SELECT * FROM t1 INNER JOIN t2 WHERE t1.id = t2.id;  -- 使用where替代on
# inner join 简写      
 SELECT * FROM t1, t2 WHERE t1.id = t2.id;-- 使用逗号操作符+where
        

12.2左连接和右连接

#INNER JOIN (ON或USING)一起产生在两个表中都找到的行。
#OUTER JOIN可以产生在一个表中存在但不在另一个表中的行。
#OUTER JOIN有两种:
	#LEFT JOIN产生左表中的所有行,但可能不在右表中;
	#RIGHT JOIN产生的行位于右表中的所有行,但可能不在左侧表中。
#在中LEFT JOIN,当左表中的行与右表不匹配时,仍会选择该行,但会与右表中所有NULL的“假”记录组合在一起。
SELECT t1.id, t1.desc FROM t1 LEFT JOIN t2 USING (id) WHERE t2.id IS NULL;
# 报错 where 语句不能使用在outer join中
SELECT * FROM t1 LEFT JOIN t2 WHERE t1.id = t2.id; -- 报错 where 语句不能使用在outer join中
       

13.等值连接和非等值连接

MySQL 中的连接(Join)操作用于将两个或多个表中的数据组合起来进行查询。根据连接的方式,连接操作可以分为等值连接和非等值连接。
等值连接
等值连接(Equi Join)指连接操作中使用相等关系(=)进行连接的方式,即将两个表中具有相同键值的记录组合在一起。等值连接是连接操作中最常用的方式之一。
在 MySQL 中,等值连接可以使用 JOIN 或者 INNER JOIN 关键字来实现。例如,下面的查询将 orders 表和 customers 表按照 customer_id 进行等值连接:
SELECT orders.order_id, customers.customer_name
FROM orders
JOIN customers
ON orders.customer_id = customers.customer_id;


非等值连接
非等值连接(Non-equijoin)指连接操作中使用非相等关系(<><=>=<> 等)进行连接的方式,即将两个表中键值之间的某种关系组合在一起进行查询。非等值连接一般比等值连接更加复杂,也更容易出现问题。

在 MySQL 中,非等值连接可以使用 LEFT JOIN 或者 RIGHT JOIN 关键字来实现。例如,下面的查询将 orders 表和 products 表按照 product_id 和 order_date 进行非等值连接:

SELECT orders.order_id, products.product_name
FROM orders
LEFT JOIN products
ON orders.product_id = products.product_id AND orders.order_date >= products.start_date;

在这个查询中,使用了 LEFT JOIN 关键字将 orders 表作为主表,然后使用 ON 子句指定了连接条件,其中包含一个非等值条件 orders.order_date >= products.start_date。这个查询将返回所有满足条件的记录,同时还包括那些在 orders 表中有记录但在 products 表中没有记录的记录。

当两个表之间没有相同的键值进行连接时,就需要使用非等值连接来组合数据。以下是一些示例:
使用 BETWEEN 子句进行非等值连接
假设有两个表 orders 和 discounts,需要将所有满足以下条件的记录组合在一起:订单的金额在打折表中的折扣金额范围内。

使用 BETWEEN 子句进行非等值连接
假设有两个表 orders 和 discounts,需要将所有满足以下条件的记录组合在一起:订单的金额在打折表中的折扣金额范围内。

SELECT o.order_id, o.order_total, d.discount_rate
FROM orders o
JOIN discounts d
ON o.order_total BETWEEN d.discount_min_amount AND d.discount_max_amount;

在这个查询中,使用 BETWEEN 子句来比较 orders 表中的 order_total 字段和 discounts 表中的 discount_min_amount 和 discount_max_amount 字段的值,以确定哪些订单可以享受相应的折扣。

使用子查询进行非等值连接
假设有两个表 orders 和 customers,需要找到在 2022 年之前首次下单的所有客户信息。

SELECT c.customer_id, c.customer_name, MIN(o.order_date) AS first_order_date
FROM customers c
JOIN orders o
ON c.customer_id = o.customer_id
WHERE (SELECT MIN(order_date) FROM orders WHERE customer_id = c.customer_id) < '2022-01-01'
GROUP BY c.customer_id;

使用条件表达式进行非等值连接
假设有两个表 products 和 sales,需要找到所有销售额超过 10000 的产品名称和销售额。

SELECT p.product_name, s.sales_total
FROM products p
JOIN sales s
ON p.product_id = s.product_id AND s.sales_total > 10000;

在这个查询中,使用条件表达式 s.sales_total > 10000 来筛选符合条件的销售记录,并使用非等值连接将这些记录与产品表中的相应产品进行组合。

看到【有】就联想到inner join ,看到【没有】就联想到left join

你的理解方向是对的,但为了更准确地应用 SQL 连接类型,建议你不要仅仅依赖“看到有就联想到 INNER JOIN,看到没有就联想到 LEFT JOIN”这种简单的规则。虽然这种思维方式可以帮助你在某些情况下快速做出选择,但它可能会忽略一些重要的细节和更复杂的场景。

1. 更全面的理解

1.1 INNER JOIN:关注匹配的记录
  • 适用场景:当你只关心两个表中 都有匹配记录的行 时,使用 INNER JOIN 是最合适的。

  • 例子

    • 找出所有 有登录记录的用户
    • 找出所有 有订单的客户
    • 找出所有 有评论的商品
-- 找出有登录记录的用户
SELECT u.user_id, u.username
FROM users u
INNER JOIN logs l ON u.user_id = l.user_id;

在这个例子中,INNER JOIN 只返回那些在 logs 表中有登录记录的用户,而不会返回那些没有登录记录的用户。

1.2 LEFT JOIN:保留左表的所有记录
  • 适用场景:当你希望 保留左表中的所有记录,即使右表中没有匹配的记录时,使用 LEFT JOIN 是最合适的。你可以通过 WHERE 子句进一步过滤出那些在右表中没有匹配记录的行。

  • 例子

    • 找出 近 30 天内没有登录记录的用户
    • 找出 没有订单的客户
    • 找出 没有评论的商品
-- 找出近 30 天内没有登录记录的用户
SELECT u.user_id, u.username
FROM users u
LEFT JOIN logs l ON u.user_id = l.user_id AND l.login_date >= DATE_SUB(CURDATE(), INTERVAL 30 DAY)
WHERE l.user_id IS NULL;

在这个例子中,LEFT JOIN 返回所有用户,但通过 WHERE l.user_id IS NULL,我们只选择了那些在 logs 表中没有近 30 天内登录记录的用户。

1.3 其他连接类型
  • RIGHT JOIN:与 LEFT JOIN 相反,保留右表中的所有记录,即使左表中没有匹配的记录。通常情况下,RIGHT JOIN 可以通过调整表的顺序并使用 LEFT JOIN 来实现相同的效果,因此它在实际应用中较少使用。

  • FULL JOIN:返回两个表中的所有记录,无论是否匹配。未匹配的列将被填充为 NULLFULL JOIN 在 MySQL 中不直接支持,但在其他数据库(如 PostgreSQL)中可以使用。

  • CROSS JOIN:返回两个表的笛卡尔积,即每个表中的每一行都与另一个表中的每一行组合。通常用于生成所有可能的组合。

2. 如何选择合适的连接类型?

要选择合适的连接类型,关键是要明确你的 查询目标数据需求。以下是一些帮助你做出正确选择的步骤:

2.1 明确查询目标
  • 你想要哪些表中的数据?

    • 如果你只关心两个表中都有匹配记录的行,那么 INNER JOIN 是合适的选择。
    • 如果你希望保留某个表中的所有记录,即使另一个表中没有匹配的记录,那么 LEFT JOINRIGHT JOIN 是合适的选择。
  • 你希望返回哪些行?

    • 如果你只关心匹配的行,使用 INNER JOIN
    • 如果你希望返回所有行,即使没有匹配的记录,使用 LEFT JOINRIGHT JOIN,并结合 WHERE 子句来过滤出特定的行。
2.2 考虑连接的方向
  • 哪个表是你关注的核心?
    • 如果你希望保留 左表中的所有记录,使用 LEFT JOIN
    • 如果你希望保留 右表中的所有记录,使用 RIGHT JOIN
2.3 利用 NULL 来识别缺失的记录
  • LEFT JOINRIGHT JOIN 的一个重要特性是,当没有匹配的记录时,右表或左表的列会被填充为 NULL。你可以通过 WHERE 子句中的 IS NULLIS NOT NULL 来筛选出这些行。

3. 总结:简化规则 vs. 灵活应用

虽然你可以简化为:

  • 看到“有”就联想到 INNER JOIN:如果你只关心两个表中都有匹配记录的行。
  • 看到“没有”就联想到 LEFT JOIN:如果你希望保留某个表中的所有记录,并找出那些在另一个表中没有匹配记录的行。

但为了编写更高效、更灵活的查询,建议你:

  • 明确查询目标:清楚你想要返回哪些表中的数据,以及你希望返回哪些行。
  • 考虑连接的方向:根据你关注的核心表,选择 LEFT JOINRIGHT JOIN
  • 利用 NULL 来识别缺失的记录:通过 WHERE 子句中的 IS NULLIS NOT NULL 来筛选出特定的行。

4. 练习和实践

通过多做练习,你会逐渐形成一种直觉,能够快速判断出哪种连接方式最适合当前的需求。每次遇到新的查询需求时,尝试问自己:

  • 我需要返回哪些表中的数据?
  • 我希望返回哪些行?
  • 我是否需要排除某些行?
  • 哪个表应该作为左表或右表?

通过不断的练习,你会更加熟练地选择合适的连接类型,并能够更自信地编写高效的 SQL 查询。

5. 示例对比

为了更好地理解 INNER JOINLEFT JOIN 的区别,让我们通过一个具体的例子来对比它们的行为。

5.1 INNER JOIN 示例

假设我们有两个表:userslogs

  • users 表包含所有用户的记录。
  • logs 表包含用户的登录记录。
-- 找出有登录记录的用户
SELECT u.user_id, u.username
FROM users u
INNER JOIN logs l ON u.user_id = l.user_id;

结果

  • 只返回那些在 logs 表中有登录记录的用户。
  • 没有登录记录的用户将被排除在外。
5.2 LEFT JOIN 示例
-- 找出近 30 天内没有登录记录的用户
SELECT u.user_id, u.username
FROM users u
LEFT JOIN logs l ON u.user_id = l.user_id AND l.login_date >= DATE_SUB(CURDATE(), INTERVAL 30 DAY)
WHERE l.user_id IS NULL;

结果

  • 返回所有用户,但只选择那些在 logs 表中没有近 30 天内登录记录的用户。
  • 有登录记录的用户将被排除在外。

6. 结论

  • INNER JOIN 适用于 只关心匹配记录的场景
  • LEFT JOIN 适用于 保留左表中的所有记录,并筛选出那些在右表中没有匹配记录的行。

虽然你可以简化为“看到‘有’就联想到 INNER JOIN,看到‘没有’就联想到 LEFT JOIN”,但为了编写更高效、更灵活的查询,建议你根据具体的查询目标和数据需求,灵活选择合适的连接类型。

在什么情景下,我们应该考虑非等值连接?

MySQL 中的 等值连接非等值连接 是两种不同的连接方式,它们的设计目的是为了满足不同类型的数据关联需求。理解这两者的区别以及何时使用非等值连接,可以帮助你编写更高效、更灵活的 SQL 查询。下面我将详细解释为什么 MySQL 会有这两种连接类型,并探讨在什么情景下你应该考虑使用非等值连接。

1. 为什么会有等值连接和非等值连接?

1.1 等值连接(Equi-Join)
  • 定义:等值连接是指两个表之间的连接条件是基于相等运算符(=)的连接。也就是说,只有当两个表中的某个列的值相等时,才会生成匹配的行。

  • 常见用途

    • 主键-外键关联:最常见的等值连接场景是通过主键和外键进行表之间的关联。例如,订单表中的 customer_id 列与客户表中的 id 列相等时,表示该订单属于某个特定的客户。
    • 自连接:有时你需要在同一张表中进行关联,例如查询员工及其上级经理的关系,可以通过 employee_idmanager_id 进行等值连接。
  • 优点

    • 性能优化:等值连接通常可以利用索引进行快速查找,尤其是在主键-外键关系中,MySQL 的查询优化器可以非常高效地处理这些连接。
    • 简单直观:等值连接的逻辑非常清晰,容易理解和实现。
1.2 非等值连接(Non-Equi-Join)
  • 定义:非等值连接是指连接条件不是基于相等运算符的连接。它可以使用其他比较运算符(如 <, >, <=, >=, <>, BETWEEN, IN 等)来定义连接条件。非等值连接通常用于更复杂的查询场景,例如查找某个范围内或满足特定条件的数据。

  • 常见用途

    • 范围查询:当你需要查找某个范围内的数据时,非等值连接非常有用。例如,查询某个时间段内的订单,或者查找价格在某个区间内的产品
    • 部分匹配:有时你需要根据某些不完全相等的条件进行连接。例如,查找某个日期之前的所有记录,或者查找某个数值大于某个阈值的记录
    • 复杂业务逻辑:在某些业务场景中,连接条件可能涉及多个字段或复杂的逻辑表达式。例如,查询某个用户在过去 30 天内是否有过购买行为,或者查找某个产品的库存量是否低于安全库存。
  • 优点

    • 灵活性:非等值连接提供了更大的灵活性,允许你根据更复杂的条件进行数据关联,而不仅仅是简单的相等条件。
    • 适应更多业务场景:许多现实世界的业务需求无法通过简单的等值连接来满足,非等值连接可以更好地应对这些复杂的需求。
  • 挑战

    • 性能问题:非等值连接可能会导致更复杂的查询计划,尤其是在处理大表时,可能会影响查询性能。MySQL 的查询优化器可能无法像等值连接那样高效地利用索引,因此需要特别注意索引的设计和查询的优化。
    • 难以优化:由于非等值连接的条件更加复杂,MySQL 的优化器可能无法像等值连接那样轻松地选择最优的执行计划。因此,编写非等值连接查询时,开发者需要更加谨慎,确保查询的效率。

2. 什么时候应该考虑使用非等值连接?

非等值连接适用于以下几种典型场景:

2.1 范围查询

当你需要根据某个范围内的值进行连接时,非等值连接是非常有用的。例如,假设你有两个表:一个是订单表 orders,另一个是促销活动表 promotions。你想找出所有发生在某个促销活动期间的订单。你可以使用非等值连接来实现这一点:

SELECT o.order_id, p.promotion_name
FROM orders o
JOIN promotions p ON o.order_date BETWEEN p.start_date AND p.end_date;

在这个例子中,BETWEEN 是一个非等值连接条件,表示订单的日期必须在促销活动的开始日期和结束日期之间。这种类型的查询非常适合处理时间范围、价格区间等场景。

2.2 部分匹配

有时你需要根据某些不完全相等的条件进行连接。例如,假设你有一个用户表 users 和一个日志表 logs,你想找出最近 30 天内有登录记录的用户。你可以使用非等值连接来实现这一点:

SELECT u.user_id, u.username
FROM users u
JOIN logs l ON u.user_id = l.user_id AND l.login_date >= DATE_SUB(CURDATE(), INTERVAL 30 DAY);

在这个例子中,l.login_date >= DATE_SUB(CURDATE(), INTERVAL 30 DAY) 是一个非等值连接条件,表示只选择最近 30 天内的登录记录。这种类型的查询非常适合处理时间窗口、阈值条件等场景。

2.3 复杂业务逻辑

在某些业务场景中,连接条件可能涉及多个字段或复杂的逻辑表达式。例如,假设你有一个产品表 products 和一个库存表 inventory,你想找出所有库存量低于安全库存的产品。你可以使用非等值连接来实现这一点:

SELECT p.product_id, p.product_name
FROM products p
JOIN inventory i ON p.product_id = i.product_id AND i.stock_quantity < p.safety_stock;

在这个例子中,i.stock_quantity < p.safety_stock 是一个非等值连接条件,表示只选择库存量低于安全库存的产品。这种类型的查询非常适合处理复杂的业务规则和多条件关联。

2.4 分段聚合

有时你需要根据某个字段的值进行分段聚合。例如,假设你有一个销售表 sales,你想按不同的销售额区间统计销售数量。你可以使用非等值连接来实现这一点:

WITH sales_ranges AS (
    SELECT 0 AS min_sales, 1000 AS max_sales UNION ALL
    SELECT 1000, 5000 UNION ALL
    SELECT 5000, 10000
)
SELECT sr.min_sales, sr.max_sales, COUNT(s.sale_id) AS sale_count
FROM sales s
JOIN sales_ranges sr ON s.sales_amount >= sr.min_sales AND s.sales_amount < sr.max_sales
GROUP BY sr.min_sales, sr.max_sales;

在这个例子中,s.sales_amount >= sr.min_sales AND s.sales_amount < sr.max_sales 是一个非等值连接条件,表示根据销售额的不同区间进行分段聚合。这种类型的查询非常适合处理分段统计、分组分析等场景。

3. 非等值连接的性能优化建议

由于非等值连接的条件更加复杂,可能会导致查询性能下降。为了提高非等值连接的性能,建议采取以下措施:

  • 索引设计:确保连接条件中涉及的列上有适当的索引,尤其是对于非等值连接中的范围条件(如 BETWEEN, IN 等),索引可以显著提高查询性能。例如,在上面的时间范围查询中,order_datestart_dateend_date 应该有索引。

  • 避免全表扫描:非等值连接可能会导致全表扫描,尤其是在没有合适索引的情况下。尽量通过优化查询条件或添加索引来避免全表扫描。

  • 使用覆盖索引:如果查询只涉及索引中的列,MySQL 可以直接从索引中获取数据,而不需要访问表中的实际数据行,从而提高查询性能。

  • 简化连接条件:尽量减少非等值连接条件的复杂性,避免使用过多的逻辑运算符或嵌套条件。复杂的连接条件可能会使查询优化器难以选择最优的执行计划。

  • 使用 EXPLAIN 分析查询:使用 EXPLAIN 语句来分析查询的执行计划,确保 MySQL 正确地使用了索引并选择了高效的执行路径。如果发现查询性能不佳,可以根据 EXPLAIN 的结果进行调整。

4. 总结

  • 等值连接 是基于相等运算符的连接,适用于简单的主键-外键关联和自连接等场景。它具有良好的性能和易用性,是最常见的连接类型。

  • 非等值连接 使用其他比较运算符(如 <, >, <=, >=, BETWEEN, IN 等),适用于更复杂的查询场景,如范围查询、部分匹配、复杂业务逻辑和分段聚合等。虽然非等值连接提供了更大的灵活性,但也可能带来性能挑战。

  • 何时使用非等值连接:当你需要根据范围、阈值、复杂条件或分段聚合进行数据关联时,非等值连接是一个非常有用的工具。然而,在使用非等值连接时,务必注意性能优化,确保查询能够高效执行。

通过合理选择连接类型并优化查询,你可以编写出既高效又灵活的 SQL 查询,满足各种业务需求。如果你对查询性能有较高的要求,建议使用 EXPLAIN 语句来分析查询的执行计划,并根据需要调整索引和查询条件。

14.自连接

参考mysql自连接处理层次结构数据.md

15.jpa

15.1 jpa调用存储过程

@Query(value = " call p_production_plan_check(:inParam1,:inParam2,:inParam3,:inParam4)", nativeQuery = true)
  List<ProductionPlan> pPlanCheck(@Param("inParam1") String calStart,@Param("inParam2") String calEnd,@Param("inParam3") String workshopcode,@Param("inParam4") String orderno)

外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传

15.2 jpa多条件 in /and /or 分页

 public void SynchronousDzgjData(){
        List<DzgjVipPayOrder> resultList = dzgjVipPayOrderRepository.findAll(((root, query, cb) -> {
            List<Predicate> list = new ArrayList<>();
            if (StringUtils.isNotBlank(dateTime)) {
                LocalDateTime dayOfMonthStart = DateTimeUtil.getDateStart(DateTimeUtil.getFirstDayOfMonth(DateTimeUtil.fromString2LocalDateTime(dateTime)));
                LocalDateTime dayOfMonthEnd = DateTimeUtil.getDateEnd(DateTimeUtil.getlastDayOfMonth(DateTimeUtil.fromString2LocalDateTime(dateTime)));
                list.add(cb.between(root.get("payTime"), dayOfMonthStart, dayOfMonthEnd));
            }
            //首单
            list.add(cb.equal(root.get("firstPay"), 1));
            获取订单状态大于等于1的,0未支付
            list.add(cb.greaterThanOrEqualTo(root.get("status"), 1));
            使用 jpa in
            list.add(root.get("userId").in(userIdList));
            // 指定按照创建时间排序
           query.orderBy(cb.desc(root.get("payTime")));
            //如果需要or连接 只需要再new 一个List 放or的参数,最终放入list
            //记得将条件放入if判断中,否则会出现0=1的情况
            //list.add(cb.or(orlist.toArray()))
            return cb.and(list.toArray(new Predicate[list.size()]));
        }));
    }
 /**
     * 京店宝【获取退款申请列表】
     * 多条件查询注意加if判断,否则会出现0=1的情况
     * @param pageNo
     * @param pageSize
     * @param refundType
     * @param search
     * @return
     */
    public CommonResponse<PageResponse<JdbRefundVO>> getJdbRefundList(Integer pageNo, Integer pageSize, Integer refundType, String search) {
        try {
            //封装请求参数
            PageRequest pageRequest = new PageRequest(pageNo - 1, pageSize);
            //分页查询
            Page<JdbRefund> page = jdbRefundRepository.findAll((root, query, cb) -> {
                List<Predicate> predicateList = new ArrayList<>();
                List<Predicate> predicateOrList = new ArrayList<>();
                if (StringUtils.isNotBlank(search)) {
                    predicateOrList.add(cb.like(root.get("name"), String.format("%%%s%%", search)));
                    predicateOrList.add(cb.like(root.get("phone"), String.format("%%%s%%", search)));
                    predicateOrList.add(cb.like(root.get("jdbAccount"), String.format("%%%s%%", search)));
                }
                //or 组合
                if (predicateOrList.size() > 0) {
                    predicateList.add(cb.or(predicateOrList.toArray(new Predicate[predicateOrList.size()])));
                }
                //and 组合
                if (refundType != null && refundType != -1) {
                    predicateList.add(cb.equal(root.get("checkStatus"), refundType));
                }
                //先按照审核未审核的排,之后再按照创建时间排
                query.orderBy(cb.asc(root.get("checkStatus")), cb.desc(root.get("createTime")));

                return cb.and(predicateList.toArray(new Predicate[predicateList.size()]));
            }, pageRequest);
            if (page != null && page.hasContent()) {
                List<JdbRefundVO> list = page.getContent().stream().map(jdbRefund -> mapToJdbRefundVO(jdbRefund)).collect(Collectors.toList());
                return new CommonResponse<>(new PageResponse<>((int) page.getTotalElements(), pageNo, list));
            }
            return new CommonResponse<>(new PageResponse<>(0, pageNo, Collections.emptyList()));
        } catch (Exception e) {
            ExceptionUtil.err(getClass(), e);
            return ExceptionUtil.errResponse();
        }
    }

15.3 jpa 多条件、分页、分组 dto/vo --使用spring-data-jpa

@query 原生sql

     /**
     * \n#pageable\n
     * \n--  #pageable\n
     * order by ?#{#pageable} 固定写法
     * 多条件、分页、分组、使用jpa返回Dto 很麻烦,慎用
     *
     * @param userId
     * @param dateTime
     * @param pageable
     * @return
     */
    @Query(value = "SELECT cast(DATE_FORMAT(create_time,'%Y-%m-%d') AS char)  as `date`, cast(ratio_agent as char) as ratio, cast(IFNULL(SUM(IF(order_type=1,amount,-amount)),0.00) AS char) as amount ,cast(IFNULL(SUM(commission_agent),0.00) AS char) as commission FROM tao_order where  user_id =:userId AND DATE_FORMAT(create_time,'%Y-%m')= DATE_FORMAT(:dateTime,'%Y-%m') group by DATE_FORMAT(create_time,'%Y-%m-%d') order by create_time desc \n#pageable\n",
            countQuery = "select count(0) FROM tao_order where  user_id =:userId AND DATE_FORMAT(create_time,'%Y-%m')= DATE_FORMAT(:dateTime,'%Y-%m') group by DATE_FORMAT(create_time,'%Y-%m-%d') ",
            nativeQuery = true)
    Page<Object[]> getAgentDetailList(@Param("userId") Long userId, @Param("dateTime") String dateTime, Pageable pageable);

16.mybatis plus

16.1 创建PaginationInterceptor bean对象

package cn.jingdianbao.user.commons.config;

import com.baomidou.mybatisplus.enums.DBType;
import com.baomidou.mybatisplus.plugins.PaginationInterceptor;
import org.mybatis.spring.annotation.MapperScan;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;

/**
 * 类名: MybatisPlusConfig
 * 描述:
 * 日期: 2021/4/12-16:10
 *
 * @author 林建辉
 */
@Configuration
public class MybatisPlusConfig {
    @Bean
    public PaginationInterceptor paginationInterceptor() {
        return new PaginationInterceptor().setDialectType(DBType.MYSQL.getDb());
    }
}

16.2在数据源中设置mybatis plus分页插件

//创建数据源并设置mybatis分页插件
package cn.jingdianbao.user.commons.config;

import com.alibaba.druid.pool.DruidDataSource;
import com.alibaba.druid.spring.boot.autoconfigure.DruidDataSourceBuilder;
import com.baomidou.mybatisplus.MybatisConfiguration;
import com.baomidou.mybatisplus.entity.GlobalConfiguration;
import com.baomidou.mybatisplus.mapper.LogicSqlInjector;
import com.baomidou.mybatisplus.plugins.PaginationInterceptor;
import com.baomidou.mybatisplus.spring.MybatisSqlSessionFactoryBean;
import com.baomidou.mybatisplus.toolkit.GlobalConfigUtils;
import org.apache.ibatis.plugin.Interceptor;
import org.apache.ibatis.session.SqlSessionFactory;
import org.mybatis.spring.SqlSessionTemplate;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.beans.factory.annotation.Qualifier;
import org.springframework.boot.autoconfigure.jdbc.DataSourceBuilder;
import org.springframework.boot.context.properties.ConfigurationProperties;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.context.annotation.Primary;
import org.springframework.core.env.Environment;
import org.springframework.core.io.support.PathMatchingResourcePatternResolver;
import org.springframework.jdbc.datasource.DataSourceTransactionManager;

import javax.sql.DataSource;


/**
 * @author xiyatu
 * @date 2019/3/13 11:26
 * Description 数据源配置
 */
@Configuration
public class DataSourceConfiguration {


    @Autowired
    private Environment environment;

    @Autowired
    private PaginationInterceptor paginationInterceptor;

    @Bean(name = "dataSource")
    @ConfigurationProperties(prefix = "spring.datasource")
    public DruidDataSource dataSource() {
        return DruidDataSourceBuilder.create().build();
    }

    /**
     * 手动配置事务管理器
     *
     * @param dataSource
     * @return
     */
    @Bean
    public DataSourceTransactionManager transactionManager(@Qualifier("dataSource") DataSource dataSource) {
        return new DataSourceTransactionManager(dataSource);
    }

    /**
     * 配置sessionFactory
     *
     * @param dataSource
     * @return
     * @throws Exception
     */
    @Bean(name = "jdbSqlSessionFactory")
    @Primary
    public SqlSessionFactory jdbSqlSessionFactory(@Qualifier("dataSource") DataSource dataSource) throws Exception {
        MybatisSqlSessionFactoryBean mybatisSqlSessionFactoryBean = new MybatisSqlSessionFactoryBean();
        mybatisSqlSessionFactoryBean.setDataSource(dataSource);
        //配置枚举
        mybatisSqlSessionFactoryBean.setTypeEnumsPackage(environment.getProperty("mybatis-plus.type-enums-package"));
        mybatisSqlSessionFactoryBean.setGlobalConfig(getGlobalConfiguration());
        //映射文件
        mybatisSqlSessionFactoryBean.setMapperLocations(new PathMatchingResourcePatternResolver().getResources("classpath:mapper/*.xml"));
        MybatisConfiguration configuration = new MybatisConfiguration();
        //下划线隐射驼峰
        configuration.setMapUnderscoreToCamelCase(true);
        configuration.setCacheEnabled(true);
        mybatisSqlSessionFactoryBean.setConfiguration(configuration);
        // 关键代码 设置 MyBatis-Plus 分页插件 by linjianhui
        Interceptor[] plugins = {paginationInterceptor};
        mybatisSqlSessionFactoryBean.setPlugins(plugins);

        return mybatisSqlSessionFactoryBean.getObject();
    }

    private GlobalConfiguration getGlobalConfiguration() {
        GlobalConfiguration globalConfiguration = GlobalConfigUtils.defaults();
        //设置sql逻辑拦截
        globalConfiguration.setSqlInjector(new LogicSqlInjector());
        //设置逻辑删除字段值,已删除为1
        globalConfiguration.setLogicDeleteValue("1");
        //设置逻辑删除字段值,未删除为1
        globalConfiguration.setLogicNotDeleteValue("0");
        //字段策略 0:"忽略判断",1:"非 NULL 判断",2:"非空判断"
        globalConfiguration.setFieldStrategy(2);
        return globalConfiguration;
    }

    @Bean(name = "jdbSqlSessionTemplate")
    @Primary
    public SqlSessionTemplate jdbSqlSessionTemplate(@Qualifier("jdbSqlSessionFactory") SqlSessionFactory sqlSessionFactory) {
        return new SqlSessionTemplate(sqlSessionFactory);
    }


}

16.3mybatis plus分页查询条件

 /**
     * 描述:
     *
     * @param invitationCodeList
     * @param sourceTypeList
     * @param pageNo
     * @param pageSize
     * @param friendAccount
     * @param startTime
     * @param endTime
     * @return
     * @author 林建辉
     * @date: 2021/4/12 17:01
     */
    public CommonResponse<PageResponse<UserInfoData>> getInviteListByInvitationCodeListAndSourceTypeList(List<String> invitationCodeList, List<Integer> sourceTypeList, Integer pageNo, Integer pageSize, String friendAccount, String startTime, String endTime) {
        //分页对象
        Page<UserAuth> page = new Page<>(pageNo, pageSize);
        //查询条件
        Wrapper<UserAuth> warpper = Condition.create()
                .in(UserAuth.INVITATION_CODE, invitationCodeList)
                .in(UserAuth.SOURCE_TYPE, sourceTypeList)
                .like(StringUtils.isNotBlank(friendAccount), UserAuth.PHONE_NUMBER, friendAccount, SqlLike.DEFAULT);
        //在这种情况下,第二个重载lt(boolean condition, R column, Object val)会有错误,改为if判断模式
        if (StringUtils.isNotBlank(startTime)) {
            warpper.ge(UserAuth.CREATE_TIME, DateTimeUtil.fromString2LocalDate(startTime, DateTimeUtil.FORMAT_DATE).atStartOfDay());
        }
        if (StringUtils.isNotBlank(endTime)) {
            warpper.le(UserAuth.CREATE_TIME, DateTimeUtil.fromString2LocalDate(endTime, DateTimeUtil.FORMAT_DATE).atTime(LocalTime.MAX));
        }
        //按照时间倒序排
        warpper.orderBy(UserAuth.CREATE_TIME, false);

        page.setRecords(userAuthMapper.selectPage(page, warpper));
        if (page.getTotal() <= 0) {
            return new CommonResponse<>(new PageResponse<>(0, pageNo, Collections.emptyList()));
        }
        //不会为空
        List<UserInfoData> resultList = page.getRecords().stream().map(this::getUserInfoData).collect(Collectors.toList());

        return new CommonResponse<>(new PageResponse<>(page.getTotal(), pageNo, resultList));
    }

16.4 mybatis plus and or

 List<KeywordGroupTagInfo> list = keywordGroupTagInfoService.list(new QueryWrapper<KeywordGroupTagInfo>()
                .eq(KeywordGroupTagInfo.DATE, date == null ? LocalDate.now().toString() : date.toString())
                .eq(KeywordGroupTagInfo.OS_TYPE, osType == null ? 1 : 1)
                .eq(skuId != null, KeywordGroupTagInfo.SKU_ID, skuId)
                .eq(keywordId != null, KeywordGroupTagInfo.KEYWORD_ID, keywordId)
                .and(qw->qw.eq(KeywordGroupTagInfo.VISIT_NUM, 0)
                        .or().eq(KeywordGroupTagInfo.ADD_CART_NUM, 0)
                        .or().eq(KeywordGroupTagInfo.AGE_GROUP, "[]"))
                );

16.4 mybatis plus update

  /*  String oldSkuId = monitor.getSkuId();
            monitor.setKeyword(keyword);
            monitor.setSkuId(skuId);
            jdbSubSkuMonitorMapper.updateById(monitor);
            jdbSubSkuMonitorHistoryMapper.update(null, new UpdateWrapper<JdbSubSkuMonitorHistory>()
                    .eq(JdbSubSkuMonitorHistory.MONITOR_ID, monitor.getId()).eq(JdbSubSkuMonitorHistory.SKU_ID, oldSkuId)
                    .set(JdbSubSkuMonitorHistory.SKU_STATUS, -1));*/

16.5 mybatis 批量插入和批量更新和批量查询

 /**
     * 批量插入
     *
     * @author linjianhui
     *
     * @date 2023/06/21
     **/
    int batchInsert(@Param("list") List<TTirpPlanCheckResource> list);


    /**
     * 批量更新
     *
     * @author linjianhui
     * @date 2023/06/21
     **/
    int batchUpdate(@Param("list") List<TTirpPlanCheckResource> list);
  <!-- 在MyBatis中执行批量插入,可以使用foreach元素迭代对象集合,逐个插入-->
    <insert id="batchInsert" useGeneratedKeys="true" keyColumn="id" keyProperty="id" parameterType="java.util.List">
        INSERT INTO t_tirp_plan_check_resource (trip_id,activity_id,resource_name,file_templates,tip_msg,is_necessary,is_customize,is_default,default_url,create_time,update_time)
        VALUES
        <foreach collection="list" item="item" separator=",">
            (#{item.tripId}, #{item.activityId}, #{item.resourceName}, #{item.fileTemplates}, #{item.tipMsg},#{item.isNecessary},
            #{item.isCustomize},#{item.isDefault},#{item.defaultUrl},#{item.createTime}, #{item.updateTime})
        </foreach>
    </insert>

    <!-- 在MyBatis中执行批量更新,可以使用foreach元素迭代对象集合,使用;分隔符逐个更新-->
    <update id="batchUpdate" useGeneratedKeys="true" keyColumn="id" keyProperty="id" parameterType="java.util.List">
        <foreach collection="list" item="item" separator=";">
            UPDATE t_tirp_plan_check_resource SET 1 = 1 WHERE id = #{item.id}
        </foreach>
    </update>

16.6 mybatis xml foreach in

<select id="selectByActIdAndActCourseIdAndStuIds" resultType="com.project.entity.api.TStuEvaNorm">
        select * from t_stu_eva_norm where 1=1
        <if test=' actId !=null and actId !="" '> and act_id = #{actId} </if>
        <if test=' actCourseId !=null and actCourseId !="" '> and act_course_id = #{actCourseId} </if>
        <if test=' stuIds !=null and stuIds.size > 0 '> 
                     and student_id in
        <foreach item="item" collection="stuIds" open="(" separator="," close=")">
                #{item}
        </foreach>     
        </if>
    </select>

      @Select("<script>" +
            "select * from t_stu_eva_norm where 1=1" +
            "<if test='actId != null and actId != \"\"'> and act_id = #{actId} </if>" +
            "<if test='actCourseId != null and actCourseId != \"\"'> and act_course_id = #{actCourseId} </if>" +
            "<if test='stuIds != null and stuIds.size > 0'> and student_id in" +
            "<foreach item=\"item\" collection=\"stuIds\" open=\"(\" separator=\",\" close=\")\">" +
            "#{item}" +
            "</foreach>" +
            "</if>" +
            "</script>")
    List<TStuEvaNorm> selectByActIdAndActCourseIdAndStuIds(@Param("actId") String actId,@Param("actCourseId") String actCourseId,@Param("stuIds") List<String> stuIds);

16.7 in多个列的写法

-- in多个列的写法,多个列用()包裹起来,中间用逗号分割【where的字段多个列用()包裹起来,中间用逗号分割】【精确匹配】
-- 格式如下:
SELECT * FROM your_table WHERE (column1, column2) IN ((value1, value2), (value3, value4), (value5, value6));

SELECT * FROM t_stu_eva_norm where (student_id,act_course_id) in (('stu-1131241147339898882','1144289496976588800'),('stu-1131241147352481792','1144289496976588800'));

16.8 mybatis 映射文件中,if标签判断字符串相等:

<if test="status=='1'.toString()">

<if test = 'status== "1"'>

16.9 加载树形结构

    public List<MatTypeVO> treeVO2() {
        // 一次性加载所有材料类型
        List<MatTypeVO> allTypes = imatTypeService.listVO(new MatTypeFormRequest());
        // 使用 Collectors.groupingByConcurrent 构建 parentId 到 子节点列表 的映射表
        Map<Integer, List<MatTypeVO>> parentIdToChildrenMap = allTypes.stream().collect(Collectors.groupingByConcurrent(MatTypeVO::getParentId));
        // 根节点列表
        List<MatTypeVO> roots = parentIdToChildrenMap.getOrDefault(Integer.valueOf(0), new ArrayList<>());
        // 并行构建树结构
        buildTreeParallel(roots, parentIdToChildrenMap);
        return roots;
    }

    private void buildTreeParallel(List<MatTypeVO> nodes, Map<Integer, List<MatTypeVO>> parentIdToChildrenMap) {
        nodes.parallelStream().forEach(node -> {
            List<MatTypeVO> children = parentIdToChildrenMap.getOrDefault(node.getId(), new ArrayList<>());
            if (!children.isEmpty()) {
                node.setChildren(children);
                buildTreeParallel(children, parentIdToChildrenMap);
            }
        });
    }

17.JdbcTemplate 批量操作

在我们做后端服务Dao层开发,特别是大数据批量插入的时候,这时候普通的ORM框架(Mybatis、hibernate、JPA)就无法满足程序对性能的要求了。当然我们又不可能使用原生的JDBC进行操作,那样尽管效率会高,但是复杂度会上升。
综合考虑我们使用Spring中的JdbcTemplate和具名参数namedParameterJdbcTemplate来进行批量操作。
JdbcTemplate提供的主要方法:
	execute方法:可以用于执行任何SQL语句,一般用于执行DDL语句;
	update方法及batchUpdate方法:update方法用于执行新增、修改、删除等语句;batchUpdate方法用于执行批	 处理相关语句;
	query方法及queryForXXX方法:用于执行查询相关语句;
	call方法:用于执行存储过程、函数相关语句。

17.2NamedParameterJdbcTemplate

public void batchSave(){
  List<Object[]> batchArgs=new ArrayList<Object[]>();
  batchArgs.add(new Object[]{1,"小明",21});
  batchArgs.add(new Object[]{2,"小红",22});
  batchArgs.add(new Object[]{3,"露西",23});
  String sql = "insert into user (username,password) values (?,?)";
  jdbcTemplate.batchUpdate(sql, batchArgs);
}
以上基本实现了批量插入功能,但是当数据库字段比较多的时候,再以?占位符的形式编码的话就可能不是那么好一 一对应了,这里spring还提供了SimpleJdbcTemplate(Spring3.1+ 以后被标记为过时,到Spring 4.3则被完全移除,后面这个完全能满足需求)NamedParameterJdbcTemplate模板引擎。
NamedParameterJdbcTemplate
相信使用过Hibernate的同学都知道,HQL中可以使用?或者:*的方式在外部配置查询参数。在 Spring JDBC 框架中,也提供了一种绑定 SQL 参数的方式,使用具名参数(named parameter)。
我们只需要在使用NamedParameterJdbcTemplate类中使用@Autowired进行注入即可:

17.2NamedParameterJdbcTemplate

17.2.1 批量更新插入batchUpdate
@Autowired
private NamedParameterJdbcTemplate namedParameterJdbcTemplate;

public void batchSave(){
 List<User> list = new ArrayList<User>();
 //新增用户
 list.add(new AppStudent(1,"张三",21));
 list.add(new AppStudent(1,"李四",22));
 list.add(new AppStudent(1,"王二麻子",23));
 //批量转数组
 SqlParameterSource[] beanSources = SqlParameterSourceUtils.createBatch(list.toArray());
 String sql = "INSERT INTO app_student(class_id,name,age) VALUES (:classId,:name,:age)";
 namedParameterJdbcTemplate.batchUpdate(sql, beanSources);
}
17.2.2 多条件分页和in
 public ResponseList<List<JdbOrderVo>> getOrderAll(int id, int pageNo, int pageSize, String startDate, String
            endDate, String search, int userId) {
        //判断是否是BD角色
        Boolean isBD = AuthUtil.isContainsRole(Constants.BD);
        //sql 参数map对象
        MapSqlParameterSource sqlParam = new MapSqlParameterSource();
        StringBuilder sql = new StringBuilder();
        sql.append(" select * from jdb_order where 1=1 ");
        if (isBD) {
            sqlParam.addValue("userId", userId);
            sql.append(" and user_id=:userId ");
        }
        if (StringUtils.isNotBlank(search)) {
            sqlParam.addValue("search", search);
            sql.append(" and account=:search ");
        }
        if (StringUtils.isNotBlank(startDate)) {
            sqlParam.addValue("startDate", startDate);
            sql.append(" and DATE_FORMAT(buy_time,'%Y-%m-%d')>=:startDate ");
        }
        if (StringUtils.isNotBlank(endDate)) {
            sqlParam.addValue("endDate", endDate);
            sql.append(" and DATE_FORMAT(buy_time,'%Y-%m-%d') <=:endDate ");
        }

        Integer totalCount = namedParameterJdbcTemplate.queryForObject(String.format("select count(0) from (%s) a", sql.toString()), sqlParam, Integer.class);

        sql.append(" order by buy_time desc,id desc limit :index,:pageSize ");
        sqlParam.addValue("index", (pageNo - 1) * pageSize);
        sqlParam.addValue("pageSize", pageSize);

        Log.getSql().info("京店宝订单管理列表sql:\r\n{},参数map:\r\n{}", sql.toString(), sqlParam);
        List<JdbOrder> jdbOrderList = namedParameterJdbcTemplate.query(sql.toString(), sqlParam, new BeanPropertyRowMapper<>(JdbOrder.class));
        List<JdbOrderVo> resultList = getResult(jdbOrderList);

        return new ResponseList(200, "", pageSize, totalCount, pageNo, resultList);

    }
 public Map<Integer, Double> monthMoneyListUserIdForJdb(List<Integer> userIdList, String yearMonth) {

        Map<Integer, Double> resultMap = new HashMap<>();
        if (CollectionUtils.isEmpty(userIdList)) {
            return resultMap;
        }
        StringBuffer sql = new StringBuffer();
        sql.append("SELECT DATE_FORMAT(create_time, '%Y-%m') as yearMonth, IFNULL(SUM(buy_money), 0) as month ,user_id AS id FROM jdb_order ");
        sql.append("  WHERE DATE_FORMAT(create_time, '%Y-%m') =:yearMonth ");
        sql.append("  AND user_id in(:userIdList)  AND is_performance=1 GROUP BY user_id");
        LogUtils.SQL.info("OrderService.monthMoneyListUserId.sql====={},yearMonth={},userIdList={}", sql.toString(), yearMonth, userIdList);
        MapSqlParameterSource sqlParam = new MapSqlParameterSource("yearMonth", yearMonth).addValue("userIdList", userIdList);
        List<Db> dbList = namedParameterJdbcTemplate.query(sql.toString(), sqlParam, new BeanPropertyRowMapper<>(Db.class));
        if (CollectionUtils.isNotEmpty(dbList)) {
            dbList.stream().map(db -> resultMap.put(db.getId(), db.getMonth())).collect(Collectors.toList());
        }
        return resultMap;
    }

//单线程分页低效率插入
public void addManagePresentVipRecordPhone() {
        int from = 0;
        int size = 100;
        List<ManageJdbVipGift> records = manageJdbVipGiftMapper.listRecord(null, from, size);
        while (!CollectionUtils.isEmpty(records)) {
            for (ManageJdbVipGift manageJdbVipGift : records) {
                UserAuth user = userService.getUserByParam(new UserAuth().setUserId(manageJdbVipGift.getUserId()).setSourceType(OsType.jingdianbao.getValue()));
                if (user != null) {
                    manageJdbVipGiftMapper.updatePhone(manageJdbVipGift.getId(), user.getPhoneNumber());
                }
            }
            from += size;
            records = manageJdbVipGiftMapper.listRecord(null, from, size);
        }
    }

`yushu-diagnosis`.

sync_

18.月份不足两位补零

 //月份不足2位补0
 String YMonth = String.format("%s-%02d", year, i);

19.创建连续日期

select d.currDate,IF(b.money is not null, b.money,0) from (select date_format(date_add('2021-01-01',interval t.help_topic_id month),'%Y-%m') as currDate  from mysql.help_topic t where t.help_topic_id<=11) d left join 
(SELECT SUM(recharge_money) as money,DATE_FORMAT(recharge_time,'%Y-%m') as ym FROM order_info  WHERE user_id=5837 AND is_performance=1 group by ym) b on d.currDate=b.ym


### show create table mysql.help_topic
CREATE TABLE `help_topic` (
  `help_topic_id` int(10) unsigned NOT NULL,
  `name` char(64) NOT NULL,
  `help_category_id` smallint(5) unsigned NOT NULL,
  `description` text NOT NULL,
  `example` text NOT NULL,
  `url` text NOT NULL,#有test字段,不要select *
  PRIMARY KEY (`help_topic_id`),
  UNIQUE KEY `name` (`name`)
) /*!50100 TABLESPACE `mysql` */ ENGINE=InnoDB DEFAULT CHARSET=utf8 STATS_PERSISTENT=0 COMMENT='help topics'

19.1使用变量和循环生成序列

SET @i := 0;
SELECT @i:=@i+1 AS num FROM any_table LIMIT 5;

19.2使用递归生成序列

WITH RECURSIVE seq AS (
  SELECT 1 AS num
  UNION ALL
  SELECT num+1 FROM seq WHERE num < 5
)
SELECT * FROM seq;

19.3 UNION ALL 操作符来生成序列

SELECT 1 AS num UNION ALL
SELECT 2 UNION ALL
SELECT 3 UNION ALL
SELECT 4 UNION ALL
SELECT 5;

19.4 在mybatis中写变量生成序列

<select id="getSequence" resultType="java.lang.Integer">
  SET @num := 0;
  SELECT @num := @num + 1 AS num FROM any_table LIMIT 5;
</select>

20.创建随机函数

select FLOOR(RAND() * 1000)

21. mysql有哪些系统表,分别有什么作用?

MySQL 中有很多系统表(system tables),它们存储了 MySQL 数据库的元数据信息、系统配置信息和其他有用的信息。下面是一些常用的系统表及其作用:
mysql.user: 存储 MySQL 的用户帐户和权限信息。
mysql.db: 存储 MySQL 的数据库级别的权限信息。
mysql.tables_priv: 存储 MySQL 的表级别的权限信息。
mysql.columns_priv: 存储 MySQL 的列级别的权限信息。
mysql.procs_priv: 存储 MySQL 的存储过程和函数级别的权限信息。
mysql.host: 存储 MySQL 允许连接的主机列表。
mysql.help_topic: 存储 MySQL 帮助信息的元数据。
mysql.time_zone、mysql.time_zone_name 和 mysql.time_zone_transition: 存储 MySQL 时区信息的元数据。
mysql.event: 存储 MySQL 中计划事件的元数据。
information_schema.tables: 存储 MySQL 所有表的信息,例如表名、列名、数据类型等。
information_schema.columns: 存储 MySQL 所有列的信息,例如列名、数据类型、约束等。
information_schema.statistics: 存储 MySQL 所有表的索引信息,例如索引名称、索引类型、列名等。
information_schema.key_column_usage: 存储 MySQL 所有外键的信息,例如外键名称、关联表名、关联列名等。
这些系统表可以帮助您了解 MySQL 数据库中的元数据信息,了解表和列的结构、索引和外键,以及授权和访问控制信息。但是,请注意,除非您对 MySQL 数据库的运作和元数据信息有深入的理解,否则不建议直接修改这些系统表中的数据。

select * from information_schema.columns;
select * from  information_schema.tables;
-- 包含了所有的帮助手册,非常管用
select * from mysql.help_topic;

22.mysql分割字符串

SELECT SUBSTRING_INDEX(SUBSTRING_INDEX('11,22,33', ',', n), ',', -1) as value
FROM (SELECT 1 as n UNION ALL SELECT 2 UNION ALL SELECT 3) as nums
WHERE n <= LENGTH('11,22,33') - LENGTH(REPLACE('11,22,33', ',', '')) + 1;

| value |
|-------|
| 11    |
| 22    |
| 33    |

SUBSTRING_INDEX('要分割的字符串','分割符:如逗号','分割的次数')
这里的子查询(SELECT 1 as n UNION ALL SELECT 2 UNION ALL SELECT 3) as nums表示将字符串分割成多少个子字符串,这里设置了3个子字符串,因此将会把"11,22,33"分割成3个字符串,即"11""22""33"。如果要分割更多的子字符串,可以添加更多的UNION ALL SELECT子句。

SUBSTRING_INDEX函数是将指定字符串按指定的分隔符进行分割,可以指定要分割的字符串的位置。FIND_IN_SET函数则用于在一个逗号分隔的字符串中查找子字符串,并返回它在逗号分隔字符串中的位置。

23.修改表的AUTO_INCREMENT

ALTER TABLE t_class AUTO_INCREMENT = 868;

24.使用with RECURSIVE 生成序列

WITH RECURSIVE date_range AS (
  SELECT CURDATE() AS geneDate
  UNION ALL
  SELECT DATE_ADD(geneDate, INTERVAL 1 DAY)
  FROM date_range
  WHERE DATE_ADD(geneDate, INTERVAL 1 DAY) <= LAST_DAY(CURDATE() + INTERVAL 1 MONTH)
)
SELECT geneDate FROM date_range;

-- LAST_DAY函数获取日期的最后一天

24.1 获取本周的日期序列

WITH RECURSIVE week_days AS (
    -- 基础情况:选择本周的第一天(即当前日期所在的星期一)
    SELECT 
        DATE_SUB(CURDATE(), INTERVAL WEEKDAY(CURDATE()) DAY) AS day_date,
        1 AS day_of_week

    UNION ALL

    -- 递归部分:生成接下来的 6 天
    SELECT 
        DATE_ADD(day_date, INTERVAL 1 DAY),
        day_of_week + 1
    FROM week_days
    WHERE day_of_week < 7
)
SELECT 
    day_date,
    day_of_week,
    DAYNAME(day_date) AS day_name
FROM week_days;

24.2 获取本月的日期序列

WITH RECURSIVE month_days AS (
    -- 基础情况:选择本月的第一天
    SELECT 
        DATE_FORMAT(CURDATE(), '%Y-%m-01') AS day_date,  -- 本月的第一天
        1 AS day_of_month

    UNION ALL

    -- 递归部分:生成接下来的每一天
    SELECT 
        DATE_ADD(day_date, INTERVAL 1 DAY),
        day_of_month + 1
    FROM month_days
    WHERE DATE_ADD(day_date, INTERVAL 1 DAY) <= LAST_DAY(day_date)  -- 确保不超过本月最后一天
)
SELECT 
    day_date,
    day_of_month,
    DAYNAME(day_date) AS day_name
FROM month_days;

24.2 获取本年的日期序列

WITH RECURSIVE year_days AS (
    -- 基础情况:选择本年的第一天
    SELECT 
        DATE_FORMAT(CURDATE(), '%Y-01-01') AS day_date,  -- 本年的第一天
        1 AS day_of_year

    UNION ALL

    -- 递归部分:生成接下来的每一天
    SELECT 
        DATE_ADD(day_date, INTERVAL 1 DAY),
        day_of_year + 1
    FROM year_days
    WHERE DATE_ADD(day_date, INTERVAL 1 DAY) <= DATE_FORMAT(CURDATE(), '%Y-12-31')  -- 确保不超过本年的最后一天
)
SELECT 
    day_date,
    day_of_year,
    DAYNAME(day_date) AS day_name
FROM year_days;

24.3 获取本年的日期序列2


SELECT DATE_ADD(DATE_FORMAT(CURDATE(),'%Y-01-01'),INTERVAL units.i+10*tens.i+100*hundreds.i DAY) AS day_date,units.i+10*tens.i+100*hundreds.i+1 AS day_of_year,DAYNAME(DATE_ADD(DATE_FORMAT(CURDATE(),'%Y-01-01'),INTERVAL units.i+10*tens.i+100*hundreds.i DAY)) AS day_name FROM (
SELECT 0 AS i UNION ALL
SELECT 1 UNION ALL
SELECT 2 UNION ALL
SELECT 3 UNION ALL
SELECT 4 UNION ALL
SELECT 5 UNION ALL
SELECT 6 UNION ALL
SELECT 7 UNION ALL
SELECT 8 UNION ALL
SELECT 9) AS units CROSS JOIN (
SELECT 0 AS i UNION ALL
SELECT 1 UNION ALL
SELECT 2 UNION ALL
SELECT 3 UNION ALL
SELECT 4 UNION ALL
SELECT 5 UNION ALL
SELECT 6 UNION ALL
SELECT 7 UNION ALL
SELECT 8 UNION ALL
SELECT 9) AS tens CROSS JOIN (
SELECT 0 AS i UNION ALL
SELECT 1 UNION ALL
SELECT 2 UNION ALL
SELECT 3) AS hundreds WHERE units.i+10*tens.i+100*hundreds.i<=DATEDIFF(DATE_FORMAT(CURDATE(),'%Y-12-31'),DATE_FORMAT(CURDATE(),'%Y-01-01')) ORDER BY day_of_year;

24.4 获取数字序列

WITH RECURSIVE numbers AS (
    -- 基础情况:从 1 开始
    SELECT 1 AS n

    UNION ALL

    -- 递归部分:每次递增 1
    SELECT n + 1
    FROM numbers
    WHERE n < 1000
)
SELECT n
FROM numbers;
-- 递归深度限制:MySQL 默认的递归深度是 1000 次。对于生成 1 到 10 万的数字序列,
-- 你需要调整 cte_max_recursion_depth 参数。可以通过以下命令临时调整递归深度
SET SESSION cte_max_recursion_depth = 100000;

24.5 根据传入的开始时间和结束时间生成日期序列,并左联构造出来的表


 WITH RECURSIVE date_series AS (
    -- 基础部分:初始化序列的起始日期
    SELECT 
        start_date AS current_date1,
        DATE_FORMAT(start_date, '%Y.%m') AS formatted_date
    FROM (SELECT '2023-01-01' AS start_date, '2025-12-31' AS end_date) AS input_dates
    WHERE start_date <= end_date

    UNION ALL

    -- 递归部分:生成下一个月份的日期
    SELECT 
        DATE_ADD(current_date1, INTERVAL 1 MONTH) AS current_date1,
        DATE_FORMAT(DATE_ADD(current_date1, INTERVAL 1 MONTH), '%Y.%m') AS formatted_date
    FROM date_series
    WHERE DATE_ADD(current_date1, INTERVAL 1 MONTH) <= (SELECT end_date FROM (SELECT '2025-03-01' AS start_date, '2025-12-31' AS end_date) AS input_dates)
),
mat_price AS (
    SELECT '2025-01-15' AS publish_date, 100.5 AS tax_exclude_amount, 0 AS del_flag, '2' AS types, 4401 AS city, 44 AS province, 17729 AS mat_stand UNION ALL
    SELECT '2025-01-20', 150.75, 0, '2', 4401, 44, 17729 UNION ALL
    SELECT '2024-12-30', 200.0, 0, '2', 4401, 44, 17729 UNION ALL
    SELECT '2024-12-25', 250.0, 0, '2', 4401, 44, 17729 UNION ALL
    SELECT '2023-03-10', 300.0, 0, '2', 4401, 44, 17729 UNION ALL
    SELECT '2023-03-15', 350.0, 0, '2', 4401, 44, 17729
)
SELECT 
    ds.formatted_date,
    FORMAT(IFNULL(mp.price, 0),2) AS price,
     FORMAT(IFNULL(AVG(mp.price) OVER (
        ORDER BY ds.formatted_date 
        ROWS BETWEEN 2 PRECEDING AND CURRENT ROW
    ), 0), 2) AS moving_avg_3_months
FROM date_series  as ds
LEFT JOIN (
    SELECT 
        DATE_FORMAT(publish_date, '%Y.%m') AS yearMonth, 
        FORMAT(IFNULL(AVG(tax_exclude_amount), 0), 2) AS price  
    FROM mat_price 
    WHERE del_flag = 0 
      AND types = '2'  
      AND city = 4401 
      AND province = 44 
      AND mat_stand = 17729 
      AND publish_date >= '2023-01-01' 
      AND publish_date <= '2025-12-31'
    GROUP BY yearMonth
) as mp ON ds.formatted_date = mp.yearMonth
ORDER BY ds.formatted_date;

25.获取当前几号

select DAYOFMONTH(CURDATE()) FROM dual;

26.获取当前日期

select CURDATE() FROM dual;

27.获取本月最后一天

select LAST_DAY(CURDATE()) FROM dual;

28.获取本月第一天

SELECT DATE_FORMAT(DATE_SUB(CURDATE(), INTERVAL DAYOFMONTH(CURDATE()) - 1 DAY), '%Y-%m-%d') AS first_day_of_month;

获取当年第一天

	SELECT MAKEDATE(YEAR(CURDATE()), 1) AS first_day_of_first_half;
	
	SELECT STR_TO_DATE(CONCAT(YEAR(CURDATE()), '-01-01'), '%Y-%m-%d') AS first_day_of_first_half;

29.使用with RECURSIVE 获取层级结构关系

-- 通过教育局id获取教育局层级
-- 获取当前教育局和上级所有教育局层级
WITH RECURSIVE education_hierarchy AS (
  SELECT edu_id, parent_id
  FROM t_education_info
  WHERE edu_id = 'edu-1116361870136049664'
  UNION ALL
  SELECT t.edu_id, t.parent_id
  FROM t_education_info t
  INNER JOIN education_hierarchy eh ON t.edu_id = eh.parent_id
)
SELECT edu_id
FROM education_hierarchy;

-- 重点在于关联条件的顺序
-- 获取当前教育局和下属教育局层级
WITH RECURSIVE education_hierarchy AS (
  SELECT edu_id, parent_id
  FROM t_education_info
  WHERE edu_id = 'edu-1116361870136049664'
  UNION ALL
  SELECT t.edu_id, t.parent_id
  FROM t_education_info t
  INNER JOIN education_hierarchy eh ON t.parent_id = eh.edu_id
)
select edu_id,edu_name,classify,classify_name from t_education_info where  edu_id in  (
SELECT edu_id
FROM education_hierarchy) order by classify;

30.普通表表达式(CTE)common table expression

WITH子句递归查询实现组织层级、创建临时表、子查询。

/**

这个SQL查询语句的作用是通过给定的教育局ID获取教育局的层级结构。

首先,我们使用了MySQL的递归查询功能,并定义了一个递归查询表education_hierarchy。该表包含了教育局ID和其上级教育局的ID。

递归查询的初始条件是给定的教育局ID,即edu_id = 'edu-1125739114860118016'。然后,通过递归地连接t_education_info表,我们获取上级教育局的ID。

最后,我们从递归查询表中选择所有的教育局ID,即SELECT edu_id FROM education_hierarchy。

执行这个查询语句,您将获得给定教育局ID的教育局层级结构 



A common table expression

普通表表达式(CTE)是一个命名的临时结果集,它存在于单个语句的范围内,并且可以在该语句的后面被引用,可能是多次引用。下面的讨论描述了如何编写使用CTE的语句。


为了指定常见的表的表达式,可以使用一个有一个或多个逗号分隔的子句的WITH子句。每个子句提供了一个产生结果集的子查询,并将一个名称与子查询联系起来。下面的例子在WITH子句中定义了名为cte1和cte2的CTE,并在WITH子句后面的顶层SELECT中引用了它们:

WITH
  cte1 AS (SELECT a, b FROM table1),
  cte2 AS (SELECT c, d FROM table2)
SELECT b, d FROM cte1 JOIN cte2
WHERE cte1.a = cte2.c;


with 
  名称1 as (子句),
	名称2 as (子句)
select xxx from 名称1 INNER JOIN 名称2  where xxx ;

WITH子句在MySQL中的妙用是可以创建递归查询,用于处理具有层级结构的数据。递归查询可以通过连接表自身来实现,从而实现对层级数据的查询和处理。

递归查询通常使用WITH RECURSIVE关键字来定义一个递归查询表。在递归查询表中,我们可以定义初始条件和递归条件,然后通过递归地连接表自身来获取所需的结果。

请注意,WITH子句还可以用于其他类型的查询,例如创建临时表或子查询。但在这个上下文中,我们主要关注递归查询的用法 


cte_name命名了一个单独的普通表表达式,并可以在包含WITH子句的语句中作为表的引用。

AS(子查询)的子查询部分被称为 "CTE的子查询",是产生CTE结果集的原因。AS后面的括号是必须的。

如果一个普通表表达式的子查询是指它自己的名字,那么它就是递归的。如果WITH子句中的任何CTE是递归的,必须包括RECURSIVE关键字。更多信息,请参阅递归普通表表达式。

为一个给定的CTE确定列名的过程如下:

如果在CTE名称后面有一个括号内的名称列表,这些名称就是列名:



一个CTE可以引用自己,也可以引用其他CTE:

一个自我引用的CTE是递归的。

一个 CTE 可以引用同一 WITH 子句中先前定义的 CTE,但不能引用后来定义的 CTE。

这个约束排除了相互递归的 CTE,即 cte1 引用 cte2,cte2 引用 cte1。其中一个引用必须是对后来定义的CTE的引用,这是不允许的。

一个给定查询块中的CTE可以引用在更外层的查询块中定义的CTE,但不能引用在更内层的查询块中定义的CTE。

为了解决对具有相同名称的对象的引用,派生表隐藏CTE;CTE隐藏基表、TEMPORARY表和视图。名称解析是通过在同一查询块中搜索对象来进行的,然后在没有找到具有该名称的对象时,依次进入外部块。

像派生表一样,在MySQL 8.0.14之前,CTE不能包含外部引用。这是MySQL的限制,在MySQL 8.0.14中被取消了,而不是SQL标准的限制。关于递归CTE的额外语法考虑,请参见递归公共表表达式。


第一个SELECT为CTE产生最初的一条或多条记录,并且不引用CTE名称。第二个SELECT产生额外的记录,并且通过在FROM子句中引用CTE名称来进行递归。当这个部分没有产生新的记录时,递归就结束了。因此,一个递归CTE由一个非递归SELECT部分和一个递归SELECT部分组成。

每个SELECT部分本身可以是多个SELECT语句的联合。

CTE结果列的类型只从非递归SELECT部分的列类型中推断出来,并且这些列都是可空的。对于类型的确定,递归SELECT部分被忽略了。

如果非递归和递归部分被UNION DISTINCT分开,重复的行将被消除。这对于执行递归封闭的查询很有用,可以避免无限循环。

递归部分的每个迭代只对前一个迭代产生的记录进行操作。如果递归部分有多个查询块,每个查询块的迭代将以未指定的顺序安排,每个查询块对其前一次迭代或前一次迭代结束后的其他查询块产生的记录进行操作。

WITH RECURSIVE cte (n) AS
(
  SELECT 1
  UNION ALL
  SELECT n + 1 FROM cte WHERE n < 5
)
SELECT * FROM cte;

前面显示的递归CTE子查询有一个非递归的部分,它检索了一条记录来产生初始行集:

在每个迭代中,SELECT产生一个新的值,比前一个行集的n的值大1。第一次迭代在初始行集(1)上操作,产生1+1=2;第二次迭代在第一次迭代的行集(2)上操作,产生2+1=3;以此类推。这样一直持续到递归结束,当n不再小于5的时候,递归就结束了。

如果CTE的递归部分比非递归部分产生更宽的列值,可能有必要在非递归部分扩大该列以避免数据截断。考虑一下这个语句:

【WITH子句在MySQL中的妙用是可以创建递归查询,用于处理具有层级结构的数据】。递归查询可以通过连接表自身来实现,从而实现对层级数据的查询和处理。

递归查询通常使用WITH RECURSIVE关键字来定义一个递归查询表。在递归查询表中,我们可以定义初始条件和递归条件,然后通过递归地连接表自身来获取所需的结果。

*/
-- 打印我要好好学习100次
WITH RECURSIVE luoxiaomin (n,study) AS
(
  SELECT 1,'好好学习' as study
  UNION ALL
  SELECT n + 1,'好好学习' as study FROM luoxiaomin WHERE n < 100
)
SELECT * FROM luoxiaomin;

示例

-- 查询任务,下单数据不能全部为0
set @var=0;
select * from (
	select *,
	@var:=date_task_id, 
	(select max(task_num)-(select sum(task_num) from date_details_task where  	   date_task_id=@var and task_type !=2)  from  date_details_task where date_task_id=@var ) as other
	from date_details_task where task_num=1 and task_type=5 and finish_num !=1 and DATE_FORMAT(create_time,'%Y-%m-%d') in ('2020-11-21')
) as a where a.other <=0
select *,ROW_NUMBER() OVER(Order by a.薪水) AS 排名  from table as a order by a.薪水

set @a=0;
select concat('第',(@a:=@a+1),'名') as sort ,姓名,薪水 fromorder by 薪水;

31.mybatis批量插入:INSERT INTO…ON DUPLICATE KEY UPDATE

  <!-- 在MyBatis中执行批量插入,可以使用foreach元素迭代对象集合,逐个插入-->
    <insert id="batchInsert" parameterType="java.util.List">
        INSERT INTO t_school_act_check_conf (school_id, snow_flake_id, area_type, need_first_dept, need_second_dept, need_third_dept, first_education_id,first_education_name, second_education_id,second_education_name, third_education_id,third_education_name, create_time, update_time)
        VALUES
        <foreach collection="list" item="item" separator=",">
            (#{item.schoolId}, #{item.snowFlakeId}, #{item.areaType}, #{item.needFirstDept}, #{item.needSecondDept}, #{item.needThirdDept}, #{item.firstEducationId},#{item.firstEducationName}, #{item.secondEducationId},#{item.secondEducationName}, #{item.thirdEducationId},#{item.thirdEducationName}, #{item.createTime}, #{item.updateTime})
        </foreach>
    </insert>



这两种写法都可以实现批量插入并在存在时更新、不存在时插入的操作。第一种写法使用了foreach标签来循环处理每个元素,而第二种写法则是直接将整个列表作为参数进行插入操作。

通常来说,第一种写法更适合处理大批量数据,因为它可以将多个插入语句合并为一个较大的语句。而第二种写法更适合处理少量数据,因为它更直观,易于理解和维护,性能更好
第二种写法更好:

注意两种写法的separator元素值的写法separator=";"和separator=","

<insert id="batchInsertOrUpdateScheduleClass" parameterType="java.util.List">
    <foreach collection="list" item="item" index="index" separator=";">
        INSERT INTO t_bas_schedule_class (schedule_id, class_id, class_name, is_delete, is_enable, create_time, update_time)
        VALUES (#{item.scheduleId}, #{item.classId}, #{item.className}, #{item.isDelete}, #{item.isEnable}, #{item.createTime}, #{item.updateTime})
        ON DUPLICATE KEY UPDATE
        class_name = VALUES(class_name), is_delete = VALUES(is_delete), is_enable = VALUES(is_enable), update_time = VALUES(update_time)
    </foreach>
</insert>


<insert id="batchInsertOrUpdate" parameterType="java.util.List">
    INSERT INTO t_bas_schedule_class (schedule_id, class_id, class_name, is_delete, is_enable, create_time, update_time)
    VALUES
    <foreach collection="list" item="item" separator=",">
        (#{item.scheduleId}, #{item.classId}, #{item.className}, #{item.isDelete}, #{item.isEnable}, #{item.createTime}, #{item.updateTime})
    </foreach>
    ON DUPLICATE KEY UPDATE
    class_name = VALUES(class_name), is_delete = VALUES(is_delete), is_enable = VALUES(is_enable), update_time = VALUES(update_time)
</insert>

32. in多个列的写法

-- in多个列的写法,多个列用()包裹起来,中间用逗号分割【where的字段多个列用()包裹起来,中间用逗号分割】【精确匹配】
-- 格式如下:
SELECT * FROM your_table WHERE (column1, column2) IN ((value1, value2), (value3, value4), (value5, value6));

SELECT * FROM t_stu_eva_norm where (student_id,act_course_id) in (('stu-1131241147339898882','1144289496976588800'),('stu-1131241147352481792','1144289496976588800'));

33.mysql 生成列【就是表中一列是通过其它动态生成的】

-- 创建一个直角三角形表,边长为a,b 那么斜边 就是c ,动态生成【在创建表字段的同时就指定好计算规则】
CREATE TABLE triangle (
  sidea DOUBLE,
  sideb DOUBLE,
  sidec DOUBLE AS (SQRT(sidea * sidea + sideb * sideb))
);
INSERT INTO triangle (sidea, sideb) VALUES(1,1),(3,4),(6,8);

34.窗口函数【参考mysql窗口函数(Window Functions)详解】

窗口函数是 SQL 中的一种强大工具,它允许你在不改变原始表行数的情况下对数据进行聚合计算。与传统的 GROUP BY 不同,窗口函数不会将多行合并为一行,而是可以在每一行上应用聚合操作,同时保留原始的行结构。这使得窗口函数非常适合用于复杂的分析任务,例如:

  • 移动平均:计算某个时间段内的平均值。
  • 排名:为每行数据分配一个排名。
  • 累计和:计算到当前行为止的累计总和。
  • 前后行比较:比较当前行与前几行或后几行的数据。

1. 窗口函数的基本语法

窗口函数的通用语法如下:

window_function(expression) OVER ( [PARTITION BY partition_expression] [ORDER BY order_expression] [frame_clause] )

  • window_function:这是你想要应用的聚合函数或排名函数,例如 SUM()AVG()ROW_NUMBER()RANK() 等。
  • expression:要应用于窗口函数的列或表达式。
  • OVER:指定窗口的范围,告诉 SQL 引擎如何划分数据并应用窗口函数。
  • PARTITION BY:可选。类似于 GROUP BY,但它不会减少行数。它将数据划分为多个分区(或“窗口”),并在每个分区内独立应用窗口函数。
  • ORDER BY:可选。指定窗口内数据的排序顺序。这对于需要按顺序处理的函数(如 ROW_NUMBER()LAG())非常重要。
  • frame_clause:可选。定义窗口的“框架”,即窗口函数作用的行范围。你可以指定从当前行向前或向后扩展的行数。

窗口函数(Window Functions)允许你在不改变行数的情况下对数据进行聚合计算。常见的窗口函数包括 ROW_NUMBER(), RANK(), DENSE_RANK(), LAG(), LEAD(), SUM() OVER(), AVG() OVER() 等。

例如,计算每个用户的累计订单金额:

SELECT 
    user_id, 
    order_id, 
    order_amount, 
    SUM(order_amount) OVER (PARTITION BY user_id ORDER BY order_date) AS cumulative_amount
FROM orders;
  • 解释
    • SUM(order_amount) OVER (PARTITION BY user_id ORDER BY order_date) 计算每个用户的累计订单金额,按 order_date 排序。

34.1 排名及排序

SELECT
	val,
	ROW_NUMBER() OVER w AS '排名序号',
	RANK() OVER w AS '稀疏排名',
	DENSE_RANK() OVER w AS '密集排名' 
FROM (
SELECT 1 AS val UNION ALL
SELECT 3 AS val UNION ALL
SELECT 5 AS val UNION ALL
SELECT 4 AS val UNION ALL
SELECT 4 AS val UNION ALL
SELECT 8 AS val UNION ALL
SELECT 9 AS val UNION ALL
SELECT 7 AS val UNION ALL
SELECT 6 AS val) AS sub_table
WINDOW w AS (ORDER BY val);

-- 结果
val	排名序号	稀疏排名	密集排名
1	1	1	1
3	2	2	2
4	3	3	3
4	4	3	3
5	5	5	4
6	6	6	5
7	7	7	6
8	8	8	7
9	9	9	8																																																																																														

35.mysql排序

1. 常规排序:
- 按照某一列的升序排序:SELECT * FROM table_name ORDER BY column_name ASC;
- 按照某一列的降序排序:SELECT * FROM table_name ORDER BY column_name DESC;
- 按照多个字段的优先级进行排序:SELECT * FROM table_name ORDER BY column_name1 DESC, column_name2 ASC;

3. 指定字段值顺序排序:
- 数据库中name列前三行 为"John","Bob", "Alice"
现在,我们想按照指定的顺序对name进行排序,可以使用FIELD()函数来实现。例如,我们希望按照"Bob", "Alice", "John"的顺序进行排序,可以使用以下查询:
SELECT id, name FROM users ORDER BY FIELD(name, 'Bob', 'Alice', 'John');

4. 随机排序:
- 随机排序整个结果集:SELECT * FROM table_name ORDER BY RAND();
- 随机排序前N条记录:SELECT * FROM table_name ORDER BY RAND() LIMIT N;

FIELD()函数的语法如下:

FIELD(expr, value1, value2, …)

其中,expr是要进行排序的表达式,value1, value2, …是要进行比较的值。

FIELD()函数的工作原理是,它会按照参数列表中的顺序逐个比较expr和每个值,返回第一个匹配的值的位置。如果没有匹配的值,则返回0。

以下是一个示例,说明FIELD()函数的使用:

假设有一个名为users的表,包含id和name两列,数据如下:

±—±------+

| id | name |

±—±------+

| 1 | John |

| 2 | Alice |

| 3 | Bob |

±—±------+

现在,我们想按照指定的顺序对name进行排序,可以使用FIELD()函数来实现。例如,我们希望按照"Bob, Alice, John"的顺序进行排序,可以使用以下查询:

SELECT id, name

FROM users

ORDER BY FIELD(name, ‘Bob’, ‘Alice’, ‘John’);

执行上述查询后,结果将按照指定的顺序进行排序:

±—±------+

| id | name |

±—±------+

| 3 | Bob |

| 2 | Alice |

| 1 | John |

±—±------+

在上述示例中,FIELD(name, ‘Bob’, ‘Alice’, ‘John’)将按照指定的顺序比较name列的值,并返回相应的位置,然后根据该位置进行排序。

需要注意的是,FIELD()函数返回的位置是基于1的索引,如果没有匹配的值,则返回0

36.mysql 年月日时分秒与秒数互转

-- 将日期:年月日时分秒转为秒数
select UNIX_TIMESTAMP("1970-01-01 00:00:00");
-- 将秒数转为年月日时分秒
select FROM_UNIXTIME(0);

37.临时修改mysql为北京时间

select NOW();
SELECT @@session.time_zone;				

SET time_zone = '+8:00';
select NOW();	

38.mysql获取图片路径前缀

face=‘https://img.xx.com/file/1023182503734673408.jpg’

#获取图片前缀https://img.xx.com/file/
select SUBSTR(face,1,LOCATE(SUBSTRING_INDEX(face, '/', -1),face)-1) as prefix;
# 按照图片前缀分组
select  SUBSTR(face,1,LOCATE(SUBSTRING_INDEX(face, '/', -1),face)-1) as prefix FROM  `t_user_info` where face is not null and face !='' GROUP BY prefix  

39. mysql 传过来的是个日期范围,数据库也是个日期范围的查询

//传过来的是个日期范围,数据库也是个日期范围,可以使用逆向思维,判断是否有重叠来解决,  
如果 publish_date_end 小于 startDate 或者 publish_date_start 大于 endDate,则没有重叠;否则,有重叠
// 添加 NOT (publish_date_end < startDate OR publish_date_start > endDate) 条件,
在mysql 中取反可以使用not来解决,可以理解为两个圆相离,取反则为相切或部分重叠

sql 语句优化

0:mysql语句执行顺序

在MySQL中,SQL语句的执行顺序如下:

1. FROM:指定要查询的表或视图。
2. JOIN:将多个表连接在一起。
3. WHERE:对查询结果进行筛选。
4. GROUP BY:按照指定的列对结果进行分组。
5. HAVING:对分组后的结果进行筛选。
6. SELECT:选择要查询的列。
7. DISTINCT:去除重复的行。
8. ORDER BY:对结果进行排序。
9. LIMIT:限制返回的结果数量。

需要注意的是,这只是一般情况下的执行顺序,实际执行顺序可能会受到查询优化器的影响。优化器可能会对查询进行重写或重新排序,以提高查询性能。

此外,还有一些特殊情况下的执行顺序,例如子查询、联合查询等,它们的执行顺序可能会有所不同。

总结来说,MySQL中SQL语句的执行顺序是从FROM开始,依次经过JOINWHEREGROUP BYHAVINGSELECTDISTINCTORDER BYLIMIT。但实际执行顺序可能会受到查询优化器的影响 

【注意】
在mysql中,group by,having,order by 中可以使用别名;where中不能使用别名;其余像oracle,hive中别名的使用都是严格遵循sql执行顺序的,groupby后面不能用别名。【mysql特殊是因为mysql中对查询做了加强】

1:查询表中的信息

show full COLUMNS from task ;
[desc|DESCRIBE] task;

 -- 查看建表结构
 desc t_share_course
  -- 查看建表结构
 describe t_share_course
    -- 查看建表结构
 show columns from t_share_course;
 -- 查看建表语句
 SHOW CREATE TABLE t_share_course;

-- 正式环境
-- 查看建表结构 包含字段备注说明
-- table_schema 后面写数据库名,table_name后面写表名
SELECT column_name, column_comment, data_type, character_maximum_length, is_nullable
FROM information_schema.columns
WHERE table_schema = 'xx' AND table_name = 't_share_course';


-- 测试环境
-- 查看建表结构 包含字段备注说明
-- table_schema 后面写数据库名,table_name后面写表名
SELECT column_name, column_comment, data_type, character_maximum_length, is_nullable
FROM information_schema.columns
WHERE table_schema = 'xx_rec' AND table_name = 't_share_course';
  • Key

    列是否被索引:

    • 如果Key为空,则该列要么不被索引,要么仅作为多列非唯一索引中的第二列被索引。
    • 如果KeyPRIPRIMARY KEY则列为a或为多列中的列之一PRIMARY KEY
    • 如果KeyUNI,则该列为UNIQUE索引的第一列 。( UNIQUE索引允许多个 NULL值,但是您可以NULL通过检查Null字段来判断该列是否允许。)
    • 如果KeyMUL,则该列是非唯一索引的第一列,在该列中允许多次出现给定值。

    如果超过一个Key值适用于表中给定的列,Key 显示一个具有最高优先级,顺序 PRIUNIMUL

    UNIQUE索引可被显示为 PRI,如果它不能包含 NULL值并没有 PRIMARY KEY在表中。甲 UNIQUE索引可能会显示为 MUL如果若干列形成复合 UNIQUE索引; 尽管各列的组合是唯一的,但各列仍可以容纳多次出现的给定值。

  • Default

    列的默认值。这是 NULL如果列有一个明确的默认NULL,或者如果列定义不包括DEFAULT条款。

  • Extra

    有关给定列的任何其他可用信息。在以下情况下,该值是非空的:

    • auto_increment具有AUTO_INCREMENT属性的列。
    • on update CURRENT_TIMESTAMP对于 TIMESTAMPDATETIME有列ON UPDATE CURRENT_TIMESTAMP 属性。
    • VIRTUAL GENERATEDVIRTUAL STORED用于生成的列。
    • DEFAULT_GENERATED 对于具有表达式默认值的列。

表列信息也可从 INFORMATION_SCHEMA COLUMNS表中获得。请参见 第26.8节“ INFORMATION_SCHEMA列表”。有关隐藏列的扩展信息仅可使用 SHOW EXTENDED COLUMNS;它不能从COLUMNS表中获得。

2:in、exists、left join 小表驱动大表

类似于嵌套循环:
for(int i=5;.......){
     for(int j=1000;......){
     }
}
如果小的循环在外层,对于数据库连接来说就只连接5次,进行5000次操作,如果1000在外,则需要进行1000次数据库连接,从而浪费资源,增加消耗。这就是为什么要小表驱动大表。

in后面跟的是小表,exists后面跟的是大表。
简记:in小,exists大。

inexistsleft join 可以相互转换
IN/ALL/ANY/SOME 子查询不支持limit语句

2.1使用exists替代left join


explain SELECT count(DISTINCT a.id)
FROM task a
    # left JOIN date_task b ON a.id = b.task_id
WHERE a.os_type = 5
  AND a.status IN (2, 3, 4, 5, 6, 7)
 #and b.task_date >= '2021-05-01' and '2021-06-01'> b.task_date
 and EXISTS(select 1 from date_task b where a.id = b.task_id and b.id=575097)
 and EXISTS(select 1 from date_task b where a.id = b.task_id and b.task_date >= '2021-06-01' and '2021-07-01'> b.task_date)
create index status_sort_time_idex ON task (sort_time desc,status desc);
drop index  status_sort_time_idex on task;
show index from task;
#distinct 会造成创建临时表,using temporary
explain 
select distinct a.* from task a left join date_task b on a.id = b.task_id where a.os_type=1 and a.status in (2,3,4,5,6,7) order by sort_time desc limit 10

外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传

3:mysql对T-SQL标准的扩展

  1. 支持group by 引用别名,这在sql92和sql99中是非法的,

    这意义这mysql的select语句在在group by 之前完成

  2. 在禁用ONLY_FULL_GROUP_BY 的情况下,允许group by 非聚合函数列

  3. 如果你并不关心address的值,在only_full_group_by 开启的模式下,

    告诉mysql接受该查询可以使用any_value()

SELECT name, ANY_VALUE(address), MAX(age) FROM t GROUP BY name;

4:mysql 索引

4.1:创建索引

#MySQL 8.0.13和更高版本支持索引表达式,可以索引未直接存储在表中的值,表达式必须要用()括起来
#示例:
CREATE INDEX part_of_name ON customer (name(10));#列前缀索引长度不要超过786个字符
CREATE INDEX idx1 ON t1 ((col1 + col2));
CREATE INDEX idx2 ON t1 ((col1 + col2), (col1 - col2), col1);
CREATE TABLE t1 (col1 INT, col2 INT, INDEX func_index ((ABS(col1))));
ALTER TABLE t1 ADD INDEX ((col1 * 40) DESC);
#实际示例
ALTER TABLE `t_order`
ADD INDEX `user_id_idx` (`user_id`) USING BTREE,#暂时未用到
ADD INDEX `invitation_code_idx` (`invitation_code`) USING BTREE,#暂时未用到
ADD INDEX `osType_createTime_idx` (`os_type` asc,`create_time` desc) USING BTREE ,#create_time用于排序
ADD INDEX ((DATE_FORMAT(create_time, '%Y-%m-%d %H:%i:%s')) DESC)#方法索引

ALTER TABLE `t_wechat_user_info` 
ADD INDEX `idx_gzh_openid`(`gzh_open_id`) USING BTREE COMMENT '公众号openid索引';
#创建索引方式一:InnoDB默认的索引存储类型是BTREE
CREATE [UNIQUE | FULLTEXT | SPATIAL] INDEX index_name
    [index_type]
    ON tbl_name (key_part,...)
    [index_option]
    [algorithm_option | lock_option] ...
    
#语法解析
#索引类型
index_type: USING {BTREE | HASH}
#索引列:可以指定索引的长度和索引的排序方式    
key_part: {col_name [(length)] | (expr)} [ASC | DESC]

index_option: {
    KEY_BLOCK_SIZE [=] value
  | index_type
  | WITH PARSER parser_name
  | COMMENT 'string'
  | {VISIBLE | INVISIBLE}
  | ENGINE_ATTRIBUTE [=] 'string'
  | SECONDARY_ENGINE_ATTRIBUTE [=] 'string'
}

algorithm_option:
    ALGORITHM [=] {DEFAULT | INPLACE | COPY}

lock_option:
    LOCK [=] {DEFAULT | NONE | SHARED | EXCLUSIVE}
    
#创建索引方式二:InnoDB默认的索引存储类型是BTREE
#CREATE INDEX不能用于创建一个PRIMARY KEY; 使用 ALTER TABLE代替
 ALTER TABLE tb_name  ADD {INDEX | KEY} [index_name]
        [index_type] (key_part,...) [index_option] ...

4.2:查看索引和删除索引

show index from table_name
drop INDEX index_name on table_name
SHOW INDEX 返回以下字段:

Table
表的名称。
Non_unique
如果索引不能包含重复项,则为0;如果可以,则为1。
Key_name
索引名称。如果索引是主键,则名称始终为PRIMARY。
Seq_in_index
索引中的列序号,从1开始。
Column_name
列名。另请参见该Expression列的说明 。
Collation
列在索引中的排序方式。它可以具有值 A(升序),D (降序)或NULL(未排序)。
Cardinality
索引中唯一值数量的估计。要更新此数字,请运行ANALYZE TABLE或(对于MyISAM表) 运行myisamchk -a。
Cardinality
基于存储为整数的统计信息进行计数,因此即使对于小型表,该值也不一定精确。基数越高,MySQL在进行联接时使用索引的机会越大。
Sub_part
索引前缀。也就是说,NULL如果对整个列都进行了索引,则如果仅对该列进行部分索引,则为索引字符的数量 
注意
前缀限制以字节为单位。然而,前缀长度为索引规范CREATE TABLE,ALTER TABLE和CREATE INDEX语句解释为非二进制串类型的字符数(CHAR, VARCHAR, TEXT对于二进制串类型),并且字节数(BINARY, VARBINARY, BLOB)。为使用多字节字符集的非二进制字符串列指定前缀长度时,请考虑到这一点。

有关索引前缀的更多信息,请参见 第8.3.5节“列索引”和 第13.1.15节“ CREATE INDEX语句”。

Packed
指示密钥的包装方式。NULL如果不是这样。
Null
包含YES该列是否包含NULL值,''如果不包含 。
Index_type
使用的索引方法(BTREE, FULLTEXT,HASH, RTREE)。
Comment
关于索引的信息未在其自己的列中描述,例如disabled是否禁用了索引。
Index_comment
COMMENT创建索引时,为索引 提供的任何注释均带有 属性。
Visible
索引是否对优化器可见。请参见 第8.3.12节“不可见索引”。
Expression
MySQL 8.0.13和更高版本支持功能性关键部分(请参见 Functional Key Parts),这会影响Column_name和 Expression列:
对于非功能键部分, Column_name指示由键部分索引的列,并且 Expression为 NULL。
对于功能键部件, Column_name列为 NULL并 Expression指示键部件的表达式。

4.3示例:有where、有and or 、有like、有order by 有limit

#索引的创建要进行针对化设计
#删除索引 drop INDEX indexName on task
explain SELECT *
FROM task
WHERE user_id = 34723
	AND deleted = 0
	AND os_type = 5
	AND (shop_name LIKE '%戴浦(DAIPU)京东自营旗舰店%'
		OR live_name LIKE '%戴浦(DAIPU)京东自营旗舰店%')
 ORDER BY sort_time DESC
LIMIT 0, 10

#显示索引
show INDEX from task;
 
#添加多一个索引
ALTER TABLE `task` ADD INDEX `userId_sortTime_idx` (`user_id` asc, `sort_time` desc) USING BTREE ;

5:like优化

#第一种方式加入指定前缀,并且为该字段添加索引:yushu-,使之成为单值匹配:String.format("yushu-%s",search);
SELECT * FROM user_auth WHERE user_name LIKE 'yushu-%word%'
#第二种方式:使用各种函数,但有时候会更慢
SELECT `column` FROM `table` WHERE 0<LOCATE('keyword', `column`)
SELECT `column` FROM `table` WHERE POSITION('keyword' IN `column`)
SELECT `column` FROM `table` WHERE 0<INSTR(`column`, 'keyword' )
#第三种方式
   使用覆盖索引

6.优化过程optimizer trace

# 1. 打开optimizer trace功能 (默认情况下它是关闭的):
SET optimizer_trace="enabled=on";
SELECT ...; # 这里输入你自己的查询语句
SELECT * FROM INFORMATION_SCHEMA.OPTIMIZER_TRACE;
# 当你停止查看语句的优化过程时,把optimizer trace功能关闭
SET optimizer_trace="enabled=off";

7.性能瓶颈定位

#我们可以通过show命令查看MySQL状态及变量,找到系统的瓶颈:
 show status —— 显示状态信息(扩展show status like ‘XXX’)
 show variables —— 显示系统变量(扩展show variables like ‘XXX’)
 show innodb status —— 显示InnoDB存储引擎的状态
 show  full processlist ——查看当前SQL执行,包括执行状态、是否锁表等
mysqladmin variables -u username -p password——显示系统变量
mysqladmin extended-status -u username -p password——显示状态信息
#我常用的主要有show status和show processlist。
#链接:https://www.jianshu.com/p/3c79039e82aa

8.sql mode

#查看你mysql 版本 #mysql 不区分大小写
SELECT VERSION();
#展示变量 show [global] variables like '%mode';
show  variables like '%mode';
#查看全局sql mode 
select @@global.sql_mode;
#ONLY_FULL_GROUP_BY,STRICT_TRANS_TABLES,NO_ZERO_IN_DATE,NO_ZERO_DATE,ERROR_FOR_DIVISION_BY_ZERO,NO_ENGINE_SUBSTITUTION
#
# 将only_full_group_by 去掉,全局有效,不要设置成'',设置成'' 可能继续生效, 但该方法在重启mysql服务后会失效,重启服务后会失效
set global sql_mode='strict_trans_tables,no_zero_in_date,no_zero_date,error_for_division_by_zero,no_engine_substitution';

#关闭连接工具,重新打开运行,ok

9.mysql 变量、状态 、连接数

SHOW full  PROCESSLIST;

kill 99601

show status like 'innodb_row_lock%';

SHOW GLOBAL VARIABLES LIKE 'wait_timeout';

SHOW GLOBAL VARIABLES LIKE '%Thread%';

-- thread_cache_size=64

show status like '%Thread%';
show status like '%Connection%';
SHOW full  PROCESSLIST;

show global status like 'max_used_connections';

show variables like '%proces%';

show variables like '%max_connections%'

-- 单用户最大连接数
set global max_user_connections=2550
-- 全局最大连接数
set global max_connections=3000

set global wait_timeout=3600;
set global interactive_timeout=3600;



show variables like '%timeout%';

show tables from performance_schema;


select * from performance_schema.events_statements_current where sql_text='SET character_set_results = NULL';

desc performance_schema.events_statements_current;

select * from performance_schema.events_statements_history;

SELECT * FROM performance_schema.threads where thread_id=421713;
SELECT * FROM
SHOW full  PROCESSLIST;
select * from information_schema.processlist where id=105231 ;

select id ,`Command`, `Time`, `State`, `Info` from information_schema.processlist order by id  ;


SHOW VARIABLES LIKE 'performance_schema';
SHOW VARIABLES LIKE '%max_connections%'; 
SHOW VARIABLES LIKE '%wait_timeout%'; 
SHOW VARIABLES LIKE '%connect_timeout%';

-- kill '23367';
-- 批量删除连接
SELECT concat('KILL ',PROCESSLIST_id,';') as command  from performance_schema.events_statements_current as c ,  performance_schema.threads t where c.THREAD_ID=t.THREAD_ID and  t.`name`='thread/sql/one_connection'  and t.processList_host='119.130.228.100';
-- 124.71.63.169
-- 119.130.228.100

select concat("kill ", ID, ";") as command
from information_schema.PROCESSLIST
where `HOST` regexp "119.130.228.100";



-- 查看当前character_set_results的值
SHOW VARIABLES LIKE '%character%';

-- 单用户最大连接数
set global max_user_connections=2550
-- 全局最大连接数
set global max_connections=3000


show tables from performance_schema;
-- 性能参数表
select * from performance_schema.events_statements_current;

/**
在MySQL中,当使用SELECT语句时,如果查询的数据量较大,MySQL会自动将表锁住,以防止其他用户对表进行修改。这是因为MySQL使用了两种锁定机制:行级锁和表级锁。行级锁只锁定需要修改的行,而表级锁锁定整个表。如果使用SELECT语句时,MySQL自动将表锁住,可能是因为查询的数据量较大,或者查询语句中使用了不恰当的JOIN语句或WHERE条件。为了避免表锁,可以尝试优化查询语句,减少查询的数据量,或者使用行级锁定机制 */


Table_locks_immediate
对表锁的请求可能被立即授予的次数。

Table_locks_waited
对表锁的请求不能被立即授予,需要等待的次数。如果这个数字很高,并且你有性能问题,你应该首先优化你的查询,然后要么拆分你的表,要么使用复制。

Table_open_cache_hits
打开表缓存查询的命中数。

Table_open_cache_misses
开放式表缓存查询的失误数量。

Table_open_cache_overflow
开放表缓存的溢出数量。这是在一个表被打开或关闭后,缓存实例有一个未使用的条目,
并且实例的大小大于table_open_cache / table_open_cache_instances的次数。


/**
性能:
  1、查看mysql事务表,得到thread_id :  select * from information_schema.INNODB_TRX ;  从结果中的:trx_mysql_thread_id 得到线程id
  2、select * from performance_schema.threads where THREAD_ID = 第一步得到的线程id (这一步可以不执行,目的只是为了演示mysql的线程管理,以及相应的查看方法。)
  3、select * from performance_schema.events_statements_current where THREAD_ID =  第一步得到的线程id ;  根据 SQL_TEXT 得到相应的sql语句。
  以上就是mysql通过阻塞的线程,找到代码中相应的sql的方法。

在分析innodb中锁阻塞时,几种方法的对比情况:

(1)使用show processlist查看不靠谱;
(2)直接使用show engine innodb status查看,无法判断到问题的根因;
(3)使用mysqladmin debug查看,能看到所有产生锁的线程,但无法判断哪个才是根因;
(4)开启innodb_lock_monitor后,再使用show engine innodb status查看,能够找到锁阻塞的根因。
*/

10.HikariCP数据库连接池常用参数

connectionTimeout连接超时这个属性控制客户端,从池中等待连接的最大毫秒数。
如果超过这个时间而没有连接可用,将抛出一个SQLException。
可接受的最低连接超时为250ms。默认值:30000 (30秒)
连接超时时间(connectionTimeout):指定从连接池中获取连接的最大等待时间。如果在超时时间内无法获取连接,则会抛出异常。建议将其设置为较短的时间,以避免连接池中的连接长时间占用。
spring.datasource.hikari.connection-timeout=60000
idleTimeout此属性控制了允许一个连接在池中闲置的最大时间。这个设置只适用于minimumIdle被定义为小于maximumPoolSize的情况。一旦池中的连接达到最小闲置时间,闲置的连接将不会被清退。一个连接是否被清退为空闲连接,最大变化是+30秒,平均变化是+15秒。在这个超时之前,一个连接永远不会被作为空闲退役。值为0意味着空闲的连接永远不会从池中移除。允许的最小值是10000ms(10秒)。默认值:600000(10分钟)
空闲连接超时时间(idleTimeout):指定连接在池中保持空闲状态的最大时间,单位毫秒。如果连接在空闲时间超过此设置,则会被连接池关闭和删除。建议将其设置为适当的值,以避免长时间空闲的连接占用资源
spring.datasource.hikari.idle-timeout=600000
keepaliveTime保持连接时间
此属性控制HikariCP尝试保持一个连接的频率,以防止它被数据库或网络基础设施超时。这个值必须小于maxLifetime值。Keepalive "只会发生在空闲的连接上。当对一个给定的连接进行 "keepalive "的时间到了,该连接将从池中移除,“ping”,然后返回到池中。ping "是以下两种情况之一:调用JDBC4 isValid()方法,或执行connectionTestQuery。通常情况下,池外的持续时间应该以个位数毫秒甚至亚毫秒来衡量,因此应该很少或没有明显的性能影响。允许的最小值是30000ms(30秒),但最理想的值是在分钟范围内。默认值:0(禁用)
maxLifetime此属性控制池中连接的最大寿命。一个正在使用的连接将永远不会被淘汰,只有当它被关闭时才会被删除。在每个连接的基础上,轻微的负衰减【即:当达到最大生存时间时,不会立即就被清除,会慢慢清除】会被应用,以避免池中的大规模灭绝。我们强烈建议设置这个值,它应该比任何数据库或基础设施施加的连接时间限制短几秒。值为0表示没有最大的寿命(无限的寿命),当然要根据idleTimeout的设置。允许的最小值是30000ms(30秒)。默认值:1800000(30分钟)
spring.datasource.hikari.max-life-time=900000
connectionTestQuery如果你的驱动程序支持JDBC4,我们强烈建议不要设置这个属性。这是为不支持JDBC4 Connection.isValid() API的 "传统 "驱动程序准备的。这是一个查询,将在一个连接从池中给你之前执行,以验证与数据库的连接仍然有效。同样,尝试在没有这个属性的情况下运行数据库池,如果你的驱动不符合JDBC4标准,HikariCP会记录一个错误,让你知道。默认:无
spring.datasource.hikari.connection-test-query=select 1
connectionInitSqlconnectionInitSql
此属性设置了一个SQL语句,在每个新的连接创建后,在将其添加到池中之前,都会执行该语句。如果该SQL语句无效或抛出异常,将被视为连接失败,并遵循标准的重试逻辑。默认值:无
minimumIdle最小空闲连接数
此属性控制HikariCP试图在池中保持的最小空闲连接数。如果空闲连接数低于这个值,并且池中的总连接数少于最大池容量,HikariCP将尽最大努力快速有效地添加额外的连接。然而,为了获得最大的性能和对尖峰需求的响应,我们建议不要设置这个值,而是让HikariCP作为一个固定大小的连接池。默认值:与maximumPoolSize相同
连接最小空闲数量(minimumIdle):指定连接池中保持的最小空闲连接数。连接池将保持至少这么多数量的连接处于可用状态,以减少获取连接的开销。建议将其设置为一个较小但合理的值
spring.datasource.hikari.minimum-idle=10
maximumPoolSize最大连接数(maximumPoolSize)
此属性控制数据库池允许达到的最大连接数,包括空闲和使用中的连接。基本上这个值将决定到数据库后端的实际连接的最大数量。一个合理的值最好由你的执行环境决定。当数据库池达到这个大小,并且没有空闲连接可用时,对getConnection()的调用将在超时前阻塞多达connectionTimeout毫秒。请阅读关于池的大小。默认值:10
根据您的应用程序负载和数据库服务器的性能,设置一个合理的值。通常,建议将其设置为应用程序的预期最大并发连接数
spring.datasource.hikari.maximum-pool-size=100
validationTimeout验证超时
此属性控制了对连接进行有效性测试的最大时间量。此值必须小于connectionTimeout。
可接受的最低验证超时为250毫秒。默认值:5000
spring.datasource.hikari.validation-timeout=30000
leakDetectionThreshold泄漏检测阈值(leakDetectionThreshold)
此属性控制在记录表明可能存在连接泄漏的消息之前,连接可以离开池子的时间量。值为0意味着泄漏检测被禁用。启用泄漏检测的最低可接受值是2000(2秒)。默认值:0
spring.datasource.hikari.leak-detection-threshold=30000
https://github.com/brettwooldridge/HikariCP
https://github.com/brettwooldridge/HikariCP
spring.datasource.hikari.allow-pool-suspension
spring.datasource.hikari.auto-commit
spring.datasource.hikari.catalog
spring.datasource.hikari.connection-init-sql
spring.datasource.hikari.connection-test-query
spring.datasource.hikari.connection-timeout
spring.datasource.hikari.data-source-class-name
spring.datasource.hikari.data-source-j-n-d-i
spring.datasource.hikari.data-source-properties
spring.datasource.hikari.driver-class-name
spring.datasource.hikari.exception-override-class-name
spring.datasource.hikari.health-check-properties
spring.datasource.hikari.idle-timeout
spring.datasource.hikari.initialization-fail-timeout
spring.datasource.hikari.isolate-internal-queries
spring.datasource.hikari.jdbc-url
spring.datasource.hikari.keepalive-time
spring.datasource.hikari.leak-detection-threshold
spring.datasource.hikari.login-timeout
spring.datasource.hikari.max-lifetime
spring.datasource.hikari.maximum-pool-size
spring.datasource.hikari.metrics-tracker-factory
spring.datasource.hikari.minimum-idle
spring.datasource.hikari.password
spring.datasource.hikari.pool-name
spring.datasource.hikari.read-only
spring.datasource.hikari.register-mbeans
spring.datasource.hikari.scheduled-executor
spring.datasource.hikari.schema
spring.datasource.hikari.transaction-isolation
spring.datasource.hikari.username
spring.datasource.hikari.validation-timeout

max-lifetime的作用

spring.datasource.hikari.max-lifetime属性用于设置连接在连接池中的最大生存时间,

即连接在连接池中允许保持的最长时间。一旦连接的生存时间超过了max-lifetime的设定值,连接将被关闭和丢弃。

该属性的作用是防止连接在连接池中过长时间的停留,避免连接因为长时间未使用而出现问题,比如数据库服务器超时或连接失效。通过设置适当的max-lifetime值,可以确保连接在一段时间后被重新创建,以保持连接的健康状态。

一般来说,设置max-lifetime是为了应对以下情况:

  1. 数据库服务器超时设置:如果数据库服务器配置了连接的最大生存时间(如MySQL的wait_timeout参数),那么连接在超过这个时间后将被服务器主动关闭。通过设置连接池的max-lifetime,可以确保连接在数据库服务器超时之前被连接池关闭和重新创建,避免因服务器超时而导致连接失效。
  2. 连接状态变化:连接在长时间运行过程中,可能会由于网络故障、数据库服务器重启或其他原因导致连接状态发生变化。通过设置适当的max-lifetime,可以确保连接在一段时间后重新创建,以避免使用失效的连接。

请注意,max-lifetime的设置应根据您的应用程序需求和数据库服务器配置来决定。如果数据库服务器没有明确的连接超时限制,您可以根据经验和实际情况设置一个合理的值,以确保连接的健康和可靠性。

需要注意的是,过小的max-lifetime值可能导致频繁的连接创建和销毁,增加系统开销和连接池的负担。而过大的max-lifetime值可能导致连接长时间保持在连接池中,无法及时检测到连接的变化。因此,建议根据实际需求和负载情况进行调整和优化。

max-lifetime和idle-timeout有什么区别?

max-lifetimeidle-timeout是连接池中两个不同的属性,它们在连接的生命周期中扮演不同的角色:

  1. max-lifetime(最大生存时间):它定义了连接在连接池中允许保持的最长时间。一旦连接在连接池中的生存时间超过了max-lifetime的设定值,连接将被关闭和丢弃。该属性的作用是确保连接在一段时间后重新创建,以避免连接因为长时间未使用而出现问题,比如数据库服务器超时或连接失效。
  2. idle-timeout(闲置超时时间):它定义了连接在连接池中允许保持空闲的最大时间。如果连接在连接池中空闲时间超过了idle-timeout的设定值,连接将被关闭和丢弃。该属性的作用是控制连接在空闲状态下的存活时间,避免连接长时间闲置而占用连接池资源。

区别如下:

  • max-lifetime是连接在连接池中的最大生存时间,与连接的使用与否无关。它确保连接在一段时间后被重新创建,无论连接是否处于活动状态。
  • idle-timeout是连接在连接池中允许保持空闲的最大时间。它关注的是连接的空闲时间,即连接在没有被使用时的时间。

在实际应用中,两个属性通常配合使用以优化连接的管理和资源利用。idle-timeout可以控制连接在空闲状态下的回收,以避免过多的空闲连接占用资源。而max-lifetime可以确保连接在一段时间后被重新创建,以避免连接过久而导致连接失效或数据库服务器超时。

需要根据实际需求和负载情况合理配置这两个属性,确保连接的健康和可靠性,并平衡连接的利用和回收。

max-lifetime和idle-timeout分别和数据库的什么配置参数有关?

max-lifetimeidle-timeout 这两个属性与数据库服务器的配置参数有以下关联:

  1. max-lifetime数据库服务器的连接超时参数:数据库服务器通常会有一个连接超时参数,用于指定连接在服务器上保持的最长时间。在 MySQL 中,该参数是 wait_timeoutmax-lifetime 的设置应考虑到数据库服务器的连接超时时间,以确保连接在连接池中的生存时间不会超过数据库服务器的超时设置。如果连接池中的连接保持的时间超过了数据库服务器的超时时间,连接可能会因为服务器主动关闭而导致失效。
  2. idle-timeout数据库服务器的闲置连接超时参数:数据库服务器也可能有一个闲置连接超时参数,用于指定连接在闲置状态下保持的最长时间。在 MySQL 中,该参数是 interactive_timeoutwait_timeout 的较小值。idle-timeout 的设置应考虑到数据库服务器的闲置连接超时时间,以确保连接在连接池中的空闲时间不会超过数据库服务器的闲置连接超时时间。如果连接池中的连接保持的空闲时间超过了数据库服务器的闲置连接超时时间,连接可能会因为服务器主动关闭而导致失效。

通过理解数据库服务器的连接超时和闲置连接超时参数,可以更好地设置连接池中的 max-lifetimeidle-timeout 属性,

以与数据库服务器的配置相协调,避免连接在连接池中的生存时间和空闲时间超过数据库服务器的限制而导致连接失效。

请注意,具体的数据库服务器参数名称和含义可能会因数据库管理系统而异。

如果在程序中,max-lifetime配置了30分钟,数据库服务器wait_timeout配置了1小时,以哪个为准?

当配置了连接池的 max-lifetime 为30分钟,而数据库服务器的 wait_timeout 为1小时时,以较小的那个值为准。

在这种情况下,连接的最大生存时间由连接池的 max-lifetime 决定。即使数据库服务器的 wait_timeout 设置为1小时,连接在连接池中的最大生存时间仍然受到连接池配置的限制,即30分钟。一旦连接在连接池中保持超过了 max-lifetime 的时间,连接将被关闭和丢弃,并在需要时重新创建新的连接。

连接池的 max-lifetime 是作为连接管理的一部分,用于确保连接在一定时间后被重新创建,以防止连接过久而导致连接失效或数据库服务器超时。

因此,在这种情况下,无论数据库服务器的 wait_timeout 设置为多少,连接池的 max-lifetime 限制将起作用并控制连接在连接池中的最大生存时间。连接将在 max-lifetime 配置的时间后被关闭和重新创建,而不会依赖于数据库服务器的超时设置。

maximum-pool-size与数据库中的什么配置有关?

maximum-pool-size 属性是连接池中的一个配置参数,用于设置连接池的最大连接数。它与数据库中的任何特定配置参数没有直接的关联。

在数据库中,有一个与连接数相关的配置参数,即数据库服务器的最大连接数参数。在 MySQL 中,该参数是 max_connections。这个参数指定了数据库服务器所允许的最大并发连接数。

尽管 maximum-pool-size 和数据库服务器的 max_connections 都涉及到连接数的配置,但它们是独立的。

连接池中的 maximum-pool-size 确定了连接池管理的连接数的上限,即连接池中允许存在的最大连接数。

连接池会根据应用程序的需求和负载情况,动态地维护和分配连接。

而数据库服务器的 max_connections 则是指定了数据库服务器所允许的最大并发连接数,即整个数据库服务器上可以同时存在的最大连接数。

在使用连接池时,应该合理设置连接池的 maximum-pool-size 属性,以满足应用程序的并发连接需求,同时也要考虑到数据库服务器的 max_connections 参数的限制。这样可以确保连接池不会超过数据库服务器所允许的最大连接数,并提供适当的并发性能和连接资源管理。

spring.datasource.jdbc-url连接字符串的连接参数

https://dev.mysql.com/doc/connector-j/8.0/en/connector-j-reference-configuration-properties.html

以下是一些常见的连接参数以及它们的说明:

参数名说明示例
url数据库连接地址jdbc:mysql://localhost:3306/test
user数据库用户名root
password数据库密码123456
autoReconnect是否开启自动重连true/false
characterEncoding字符编码UTF-8
useSSL是否使用SSLtrue/false
serverTimezone服务器时区Asia/Shanghai
allowPublicKeyRetrieval是否允许从服务器获取公钥true/false
verifyServerCertificate是否验证服务器端证书true/false
useInformationSchema是否使用information_schema查询元数据true/false
cachePrepStmts是否缓存预处理语句true/false
prepStmtCacheSize预处理语句缓存大小250
prepStmtCacheSqlLimit预处理语句缓存SQL限制2048
useServerPrepStmts是否使用服务器端预处理语句true/false
rewriteBatchedStatements是否开启批量更新优化true/false
maxReconnects最大重试次数3
connectTimeout连接超时时间(单位:毫秒)5000
socketTimeoutSocket超时时间(单位:毫秒)300000
maxIdleTime最大空闲时间(单位:秒)7200

以上是一些常见的连接参数,可以根据自己的实际情况进行设置。

11.mybatis中文等值判断

/*双引号包裹单引号*/
<if test="qryStr=='Y'.toString()">
  and counts=1
</if>

/*单引号包裹双引号,双引号要转义*/
<if test='qryStr==\"Y\"'>
   and counts=2
 </if>
 
 /*如果是数字等值判断,直接写*/
<if test='status==1'>
   and counts=2
 </if>

12.mybatis批量更新

在 MyBatis 中进行批量更新可以使用 <foreach> 元素来循环处理要更新的数据列表。以下是一个示例代码,演示了如何使用 MyBatis 执行批量更新操作:

import org.apache.ibatis.annotations.Param;
import java.util.List;
@Mapper
public interface YourMapper {
    void batchUpdate(@Param("list") List<YourEntity> list);
}
<!-- yourMapper.xml -->
<update id="batchUpdate" parameterType="java.util.List">
    <foreach collection="list" item="item" separator=";">
        UPDATE your_table SET column1 = #{item.column1}, column2 = #{item.column2}
        WHERE id = #{item.id}
    </foreach>
</update>

在 MySQL 中,没有直接提供批量更新语句的特定语法。然而,你可以使用以下两种方法来实现批量更新操作:

  1. 使用多个单独的 UPDATE 语句:你可以使用多个独立的 UPDATE 语句来更新多行数据。这些语句可以在一个事务中执行,
  2. 以确保原子性。以下是一个示例代码片段:
START TRANSACTION;

UPDATE your_table SET column1 = 'value1' WHERE id = 1;
UPDATE your_table SET column1 = 'value2' WHERE id = 2;
UPDATE your_table SET column1 = 'value3' WHERE id = 3;

COMMIT;

在上面的示例中,我们通过多个独立的 UPDATE 语句逐行更新数据。通过将这些语句包含在一个事务中,可以确保在整个更新过程中的原子性。

  1. 使用 CASE 语句进行条件更新:你可以使用 CASE 语句来根据条件进行批量更新。这种方法适用于在一个 UPDATE 语句中同时更新多行数据的情况。以下是一个示例:

    UPDATE your_table
    SET column1 = 
      CASE
        WHEN id = 1 THEN 'value1'
        WHEN id = 2 THEN 'value2'
        WHEN id = 3 THEN 'value3'
      END
    WHERE id IN (1, 2, 3);
    

MySQL 批量更新的方式有以下几种:

  1. 使用多个 update 语句,每个语句更新一行数据。
  2. 使用单个 update 语句,使用 CASE WHEN 语句进行条件判断,然后更新符合条件的行。
  3. 使用单个 update 语句,使用 JOIN 语句将需要更新的数据与更新条件进行关联,然后更新符合条件的行。
  4. 使用 LOAD DATA INFILE 语句将需要更新的数据导入到一个临时表中,然后使用 UPDATE JOIN 语句更新原表中符合条件的数据。

在 update 语句中可以使用 IN 子句,它可以将一个条件与一个列表进行匹配。例如:

执行计划EXPLAIN

explain 
select id ,sku_id from task where id in (select task_id from date_task where task_date >= '2020-12-01' and task_date < '2020-12-11') limit 1000

EXPLAIN输出列

列名JSON名称含义
idselect_idSELECT标识符,该值越大越先执行
select_type没有SELECT类型
tabletable_name表名
partitionspartitions匹配的分区
typeaccess_type联接类型
possible_keyspossible_keys可能的索引选择
keykey实际选择的索引
key_lenkey_length所选键的长度
refref与索引比较的列
rowsrows估计要检查的行
filteredfiltered表示未过滤的百分比,该值越小越好
Extra没有附加信息

select_type类型

select_type的值JSON名称含义
SIMPLE没有简单SELECT(不使用 UNION或子查询)
PRIMARY没有最外层 SELECT
UNION没有第二个或之后的SELECT陈述 UNION
DEPENDENT UNIONdependenttrue中的第二个或更高版本的SELECT语句 UNION,取决于外部查询
UNION RESULTunion_result的结果UNION
SUBQUERY没有首先SELECT在子查询
DEPENDENT SUBQUERYdependenttrue首先SELECT在子查询中,取决于外部查询
DERIVED没有派生表
DEPENDENT DERIVEDdependenttrue派生表依赖于另一个表
MATERIALIZEDmaterialized_from_subquery物化子查询
UNCACHEABLE SUBQUERYcacheablefalse子查询,其结果无法缓存,必须针对外部查询的每一行重新进行评估
UNCACHEABLE UNIONcacheablefalseUNION 属于不可缓存子查询的中的第二个或更高版本的选择(请参阅参考资料UNCACHEABLE SUBQUERY

表名也可以是以下值之一:

  • <unionM,N>:该行是指具有和id值的行 的 M并集 N
  • <derivedN>:该行是指用于与该行的派生表结果id的值 N。派生表可能来自(例如)FROM子句中的子查询
  • <subqueryN>:该行是指该行的物化子查询的结果,其id 值为N。请参见 第8.2.2.2节“通过物化来优化子查询”。

type类型

typeEXPLAIN输出介绍如何联接表。在JSON格式的输出中,这些作为access_type属性的值找到。以下列表描述了连接类型,从最佳类型到最差类型:

  • system

    该表只有一行(=系统表)。这是const联接类型的特例 。

  • const

    该表最多具有一个匹配行,该行在查询开始时读取。因为只有一行,所以优化器的其余部分可以将这一行中列的值视为常量。 const表非常快,因为它们只能读取一次。

    const在将aPRIMARY KEYUNIQUEindex的所有部分与常数值进行比较时使用。在以下查询中,tbl_name可以用作const 表:

    SELECT * FROM tbl_name WHERE primary_key=1;
    
    SELECT * FROM tbl_name
      WHERE primary_key_part1=1 AND primary_key_part2=2;
    
  • eq_ref

    对于先前表中的行的每种组合,从此表中读取一行。除了 systemconst类型,这是最好的联接类型。当联接使用索引的所有部分并且索引为a PRIMARY KEYUNIQUE NOT NULLindex时使用。

    eq_ref可以用于使用=运算符进行比较的索引列 。比较值可以是常量,也可以是使用在此表之前读取的表中列的表达式。在以下示例中,MySQL可以使用 eq_ref联接进行处理 ref_table

    SELECT * FROM ref_table,other_table
      WHERE ref_table.key_column=other_table.column;
    
    SELECT * FROM ref_table,other_table
      WHERE ref_table.key_column_part1=other_table.column
      AND ref_table.key_column_part2=1;
    
  • ref

    对于先前表中的行的每种组合,将从该表中读取具有匹配索引值的所有行。ref如果联接仅使用键的最左前缀,或者如果键不是aPRIMARY KEYUNIQUE索引(换句话说,如果联接无法根据键值选择单个行),则使用。如果使用的键仅匹配几行,则这是一种很好的联接类型。

    ref可以用于使用=<=> 运算符进行比较的索引列 。在以下示例中,MySQL可以使用 ref联接进行处理 ref_table

    SELECT * FROM ref_table WHERE key_column=expr;
    
    SELECT * FROM ref_table,other_table
      WHERE ref_table.key_column=other_table.column;
    
    SELECT * FROM ref_table,other_table
      WHERE ref_table.key_column_part1=other_table.column
      AND ref_table.key_column_part2=1;
    
  • fulltext

    使用FULLTEXT 索引执行联接。

  • ref_or_null

    这种连接类型类似于 ref,但是除了MySQL对包含NULL值的行进行了额外的搜索之外。此联接类型优化最常用于解析子查询。在以下示例中,MySQL可以使用 ref_or_null联接进行处理ref_table

    SELECT * FROM ref_table
      WHERE key_column=expr OR key_column IS NULL;
    

    请参见第8.2.1.15节“ IS NULL优化”。

  • index_merge

    此联接类型指示使用索引合并优化。在这种情况下,key输出行中的列包含所用索引的列表,并key_len包含所用索引 的最长键部分的列表。有关更多信息,请参见 第8.2.1.3节“索引合并优化”。

  • unique_subquery

    此类型替换 以下形式的eq_ref某些 IN子查询:

    value IN (SELECT primary_key FROM single_table WHERE some_expr)
    

    unique_subquery 只是一个索引查找函数,它完全替代了子查询以提高效率。

  • index_subquery

    此连接类型类似于 unique_subquery。它替代IN子查询,但适用于以下形式的子查询中的非唯一索引:

    value IN (SELECT key_column FROM single_table WHERE some_expr)
    
  • range

    使用索引选择行,仅检索给定范围内的行。的key 输出行中的列指示使用哪个索引。将key_len包含已使用的时间最长的关键部分。该refNULL适用于此类型。

    range当一个键列使用任何的相比于恒定可使用 =<>>>=<<=IS NULL<=>BETWEENLIKE,或 IN()运营商:

    SELECT * FROM tbl_name
      WHERE key_column = 10;
    
    SELECT * FROM tbl_name
      WHERE key_column BETWEEN 10 and 20;
    
    SELECT * FROM tbl_name
      WHERE key_column IN (10,20,30);
    
    SELECT * FROM tbl_name
      WHERE key_part1 = 10 AND key_part2 IN (10,20,30);
    
  • index

    index联接类型是一样的 ALL,只是索引树被扫描。发生这种情况有两种方式:

    • 如果索引是查询的覆盖索引,并且可用于满足表中所需的所有数据,则仅扫描索引树。在这种情况下,Extra列显示为 Using index。仅索引扫描通常比索引扫描更快, ALL因为索引的大小通常小于表数据。
    • 使用对索引的读取来执行全表扫描,以按索引顺序查找数据行。 Uses index没有出现在 Extra列中。

    当查询仅使用属于单个索引一部分的列时,MySQL可以使用此联接类型。

  • ALL

    对来自先前表的行的每个组合进行全表扫描。如果该表是未标记的第一个表 const,则通常不好,并且在所有其他情况下通常 非常糟糕。通常,您可以ALL通过添加索引来避免这种情况,这些 索引允许基于早期表中的常量值或列值从表中检索行。

Extra类型

ExtraEXPLAIN输出包含MySQL解决查询的额外信息。以下列表说明了可以在此列中显示的值。每个项目还针对JSON格式的输出指示哪个属性显示Extra值。对于其中一些,有一个特定的属性。其他显示为message 属性的文本。

如果你想使你的查询尽可能快,看出来Extra的列值Using filesortUsing temporary,或在JSON格式的EXPLAIN输出,用于 using_filesortusing_temporary_table性能等于 true

  • Backward index scan(JSON: backward_index_scan

    优化器能够在InnoDB表上使用降序索引 。与一起显示 Using index。有关更多信息,请参见 第8.3.13节“降序索引”。

  • Child of '*table*' pushed join@1(JSON:message 文本)

    将该表引用为 table可以下推到NDB内核的联接中的子级。启用下推联接时,仅适用于NDB群集。有关ndb_join_pushdown更多信息和示例,请参见服务器系统变量的描述 。

  • const row not found(JSON属性: const_row_not_found

    对于诸如之类的查询,该表为空。 SELECT ... FROM *tbl_name*

  • Deleting all rows(JSON属性: message

    对于DELETE,某些存储引擎(例如MyISAM)支持一种处理程序方法,该方法以一种简单而快速的方式删除所有表行。Extra如果引擎使用此优化,则显示此值。

  • Distinct(JSON属性: distinct

    MySQL正在寻找不同的值,因此在找到第一个匹配的行后,它将停止为当前行组合搜索更多的行。

  • FirstMatch(*tbl_name*) (JSON属性:first_match

    半联接FirstMatch联接快捷方式策略用于tbl_name

  • Full scan on NULL key(JSON属性: message

    当优化器无法使用索引查找访问方法时,这会作为子查询优化的后备策略而发生。

  • Impossible HAVING(JSON属性: message

    HAVING子句始终为false,不能选择任何行。

  • Impossible WHERE(JSON属性: message

    WHERE子句始终为false,不能选择任何行。

  • Impossible WHERE noticed after reading const tables(JSON属性: message

    MySQL已经读取了所有 const(和 system)表,并注意到该WHERE子句始终为false。

  • LooseScan(*m*..*n*) (JSON属性:message

    使用半连接的LooseScan策略。 mn是关键部件号。

  • No matching min/max row(JSON属性: message

    没有行满足查询的条件,例如 。 SELECT MIN(...) FROM ... WHERE *condition*

  • no matching row in const table(JSON属性:message

    对于具有联接的查询,存在一个空表或没有满足唯一索引条件的行的表。

  • No matching rows after partition pruning(JSON属性: message

    对于DELETEUPDATE,在分区修剪后,优化器未发现任何要删除或更新的内容。它的含义类似于Impossible WHERE forSELECT语句。

  • No tables used(JSON属性: message

    该查询没有FROM子句,或者有 FROM DUAL子句。

    对于INSERTREPLACE语句, EXPLAIN在没有任何SELECT 部分时显示此值。例如,出现的EXPLAIN INSERT INTO t VALUES(10)原因是,它等同于 EXPLAIN INSERT INTO t SELECT 10 FROM DUAL

  • Not exists(JSON属性: message

    MySQL能够对LEFT JOIN 查询进行优化,并且在找到符合LEFT JOIN条件的一行后,不检查该表中的更多行是否为上一行组合。这是可以通过这种方式优化的查询类型的示例:

    SELECT * FROM t1 LEFT JOIN t2 ON t1.id=t2.id
      WHERE t2.id IS NULL;
    

    假设t2.id定义为 NOT NULL。在这种情况下,MySQL使用的值 扫描 t1并查找行 。如果MySQL在中找到匹配的行 ,它将知道它 永远不会是 ,并且不会扫描具有相同值的其余行。换句话说,对于in中的每一行,MySQL只需进行一次查找,无论in中实际匹配多少行。 t2``t1.id``t2``t2.id``NULL``t2``id``t1``t2``t2

    在MySQL 8.0.17和更高版本中,这还可以指示WHERE形式为的 条件 或 已在内部转换为反联接的条件。这将删除子查询,并将其表放入最顶层查询的计划中,从而提供了改进的成本计划。通过合并半联接和反联接,优化器可以更自由地对执行计划中的表进行重新排序,在某些情况下,可以使计划更快。 NOT IN (*subquery*)``NOT EXISTS (*subquery*)

    通过检查或 执行的结果后 的Message列,您可以查看何时对给定查询执行反联接转换 。 SHOW WARNINGS``EXPLAIN``EXPLAIN FORMAT=TREE

    笔记

    反连接是半连接的补充 。该反连接返回的所有行 对此有 没有行 相匹配 。 *table_a* JOIN *table_b* ON *condition*table_atable_bcondition

  • Plan isn't ready yet (JSON属性:无)

    EXPLAIN FOR CONNECTION当优化器尚未完成为在命名连接中执行的语句创建执行计划时, 就会出现此值。如果执行计划输出包括多行,则Extra取决于优化程序确定完整执行计划的进度,其中的任何一行或所有行都可以具有此 值。

  • Range checked for each record (index map: *N*)(JSON属性: message

    MySQL没有找到合适的索引来使用,但是发现一些索引可以在已知先前表中的列值之后使用。对于上表中的每个行组合,MySQL检查是否可以使用rangeindex_merge访问方法来检索行。这不是很快,但是比完全没有索引的联接要快。适用标准如 第8.2.1.2节“范围优化”和 第8.2.1.3节“索引合并优化”中所述,除了上表的所有列值都是已知的并且被视为常量。

    索引从1开始编号,其顺序SHOW INDEX与表中显示的顺序相同。索引图值 N是指示哪些索引为候选的位掩码值。例如,值0x19(二进制11001)表示考虑了索引1、4和5。

  • Recursive(JSON属性: recursive

    这表明该行适用于SELECT递归公用表表达式的递归 部分。请参见第13.2.15节“ WITH(公用表表达式)”。

  • Rematerialize(JSON属性: rematerialize

    Rematerialize (X,...)EXPLAINtable的行中 显示T,其中X是任何横向派生的表,当T读取新行时会触发其重新实现。例如:

    SELECT
      ...
    FROM
      t,
      LATERAL (derived table that refers to t) AS dt
    ...
    

    每当t顶级查询处理新的一行时,都会重新实现派生表的内容,以使它保持最新 。

  • Scanned *N* databases(JSON属性: message

    这表示在处理INFORMATION_SCHEMA表查询时服务器执行了多少目录扫描 ,如第8.2.3节“优化INFORMATION_SCHEMA查询”中所述。的值N可以是0、1或 all

  • Select tables optimized away(JSON属性:message

    优化器确定1)最多应返回一行,以及2)要生成这一行,必须读取确定的一组行。当在优化阶段可以读取要读取的行时(例如,通过读取索引行),则在查询执行期间无需读取任何表。

    当查询被隐式分组(包含聚合函数但没有GROUP BY子句)时,满足第一个条件 。当每个使用的索引执行一次行查找时,满足第二个条件。读取的索引数决定了要读取的行数。

    考虑以下隐式分组查询:

    SELECT MIN(c1), MIN(c2) FROM t1;
    

    假设MIN(c1)可以通过读取一个索引行MIN(c2) 来检索,并且可以通过从另一索引中读取一行来进行检索。即,对于每一列c1c2,存在其中列是索引的第一列的索引。在这种情况下,将返回一行,这是通过读取两个确定性行产生的。

    Extra如果要读取的行不是确定性的,则不会出现 此值。考虑以下查询:

    SELECT MIN(c2) FROM t1 WHERE c1 <= 10;
    

    假设这(c1, c2)是一个覆盖指数。使用此索引,c1 <= 10必须扫描所有具有的行以找到最小值 c2。相比之下,请考虑以下查询:

    SELECT MIN(c2) FROM t1 WHERE c1 = 10;
    

    在这种情况下,第一个索引行c1 = 10包含最小值c2 。只读取一行就可以产生返回的行。

    对于维护每个表的行数准确的存储引擎(例如MyISAM,但不是 InnoDB),对于缺少该子句或始终为true且没有 子句的查询,Extra 可能会出现此值。(这是一个隐式分组查询的实例,其中存储引擎影响是否可以读取确定数量的行。) COUNT(*)``WHERE``GROUP BY

  • Skip_open_tableOpen_frm_onlyOpen_full_table(JSON属性: message

    这些值指示适用于INFORMATION_SCHEMA 表查询的文件打开优化。

    • Skip_open_table:不需要打开表文件。该信息已经可以从数据字典中获得。
    • Open_frm_only:仅数据字典需要读取以获取表信息。
    • Open_full_table:未优化的信息查找。表信息必须从数据字典中读取并通过读取表文件来读取。
  • Start temporaryEnd temporary(JSON属性: message

    这表明临时表用于半联接重复淘汰策略。

  • unique row not found(JSON属性: message

    对于诸如之类的查询,没有行满足 索引或表中的条件。 SELECT ... FROM *tbl_name*``UNIQUE``PRIMARY KEY

  • Using filesort(JSON属性: using_filesort

    MySQL必须额外进行一遍,以找出如何按排序顺序检索行。排序是通过根据联接类型遍历所有行并存储与该WHERE子句匹配的所有行的排序键和指向该行的指针来完成的。然后对键进行排序,并按排序顺序检索行。请参见 第8.2.1.16节“按优化排序”。

  • Using index(JSON属性: using_index

    仅使用索引树中的信息从表中检索列信息,而不必进行其他查找以读取实际行。当查询仅使用属于单个索引的列时,可以使用此策略。

    对于InnoDB具有用户定义的聚集索引的表,即使列中Using index不存在 该索引也可以使用Extra。如果typeis indexkeyis就是这种情况 PRIMARY

    显示有关EXPLAIN FORMAT=TRADITIONAL和的 有关所使用的任何覆盖索引的信息 EXPLAIN FORMAT=JSON。从MySQL 8.0.27开始,它也显示为EXPLAIN FORMAT=TREE

  • Using index condition(JSON属性: using_index_condition

    通过访问索引元组并首先对其进行测试以确定是否读取完整的表行来读取表。这样,除非有必要,否则索引信息将用于延迟(“下推”)整个表行的读取。请参见 第8.2.1.6节“索引条件下推优化”。

  • Using index for group-by(JSON属性:using_index_for_group_by

    Using index表访问方法类似,Using index for group-by 表示MySQL找到了一个索引,该索引可用于检索aGROUP BYDISTINCT查询的所有列,而无需对实际表进行任何额外的磁盘访问。此外,以最有效的方式使用索引,因此对于每个组,仅读取少数索引条目。有关详细信息,请参见 第8.2.1.17节“优化组”。

  • Using index for skip scan(JSON属性:using_index_for_skip_scan

    指示使用“跳过扫描”访问方法。请参阅 跳过扫描范围访问方法。

  • Using join buffer (Block Nested Loop)Using join buffer (Batched Key Access)Using join buffer (hash join)(JSON属性:using_join_buffer

    来自较早联接的表被部分读取到联接缓冲区中,然后从缓冲区中使用它们的行来执行与当前表的联接。 (Block Nested Loop)表示使用块嵌套循环算法,(Batched Key Access)表示使用批处理密钥访问算法,并(hash join)表示使用哈希联接。即,EXPLAIN缓冲输出的前一行中的表中的键 ,并从出现行所在的表中批量提取匹配的行 Using join buffer

    在JSON格式的输出,值 using_join_buffer总是之一 Block Nested LoopBatched Key Accesshash join

    从MySQL 8.0.18开始,可以使用哈希联接。MySQL 8.0.20或更高版本的MySQL中未使用“块嵌套循环”算法。有关这些优化的更多信息,请参见第8.2.1.4节“哈希联接优化”和“ 块嵌套循环联接算法”。

    有关批量密钥访问算法的信息,请参阅批量密钥访问联接。

  • Using MRR(JSON属性: message

    使用多范围读取优化策略读取表。请参见第8.2.1.11节“多范围读取优化”。

  • Using sort_union(...)Using union(...)Using intersect(...)(JSON属性: message

    这些指示了特定算法,该算法显示了如何针对index_merge联接类型合并索引扫描 。请参见第8.2.1.3节“索引合并优化”。

  • Using temporary(JSON属性: using_temporary_table

    为了解决查询,MySQL需要创建一个临时表来保存结果。如果查询包含GROUP BYORDER BY子句以不同的方式列出列,通常会发生这种情况。

  • Using where(JSON属性: attached_condition

    WHERE子句用于限制来匹配下一个表或发送到客户端的行。除非您专门打算从表中获取或检查所有行,否则如果查询中的Extra值不是 Using where且表联接类型为ALL或 ,则 查询中可能会出错index

    Using where在JSON格式的输出中没有直接对应的内容;该 attached_condition属性包含使用的任何WHERE条件。

  • Using where with pushed condition(JSON属性:message

    此产品适用于NDB。这意味着NDB Cluster正在使用条件下推优化来提高在非索引列和常量之间进行直接比较的效率。在这种情况下,条件被“下推”到群集的数据节点,并同时在所有数据节点上进行评估。这样就无需通过网络发送不匹配的行,并且在可以但不使用条件下推的情况下,可以将此类查询的速度提高5到10倍。有关更多信息,请参见 第8.2.1.5节“发动机状况下推优化”。

  • Zero limit(JSON属性: message

    该查询有一个LIMIT 0子句,不能选择任何行。

口诀

建表SQL

索引情况

一、全值匹配我最爱



二、最佳左前缀法则 
1where条件列的,从索引的最左前列开始,且不跳过索引中的列

2、违背原则:未以最左前列开始,索引失效

3、违背原则:跳过索引中间列(age),只引用了部分索引

结论: where条件要满足 最佳左前缀法则。 口诀:带头大哥不能死,中间兄弟不能断


三、不在索引列上做任何操作(计算、函数、自动or手动类型转换),会导致索引失效而转向全表扫描 

即使满足最左前缀原则,但where条件中使用了函数后,索引失效


四、存储引擎不能使用索引中范围条件后边的列 


结论:组合索引中的列name用来检索,age只且到索引排序(没用到索引检索),而age后边的列pos就会失效了。
疑问:若使用最左边列做范围查找,那么后边的几个条件还会用到索引吗?


五、尽量使用覆盖索引(只访问索引的查询(索引列和查询列一致)),减少select *
1select*,从表中检索数据 

2、按需要查字段,从索引中检索数据

3、按需要查字段(查询列可只用索引列中的一部分),范围查询、未使用范围查询、查询部分索引列分析对比

结论:满足原则五时,用范围查找时,查询类型type:ref,而不是range,参考原则四的说明

六、mysql在使用不等于(!=或者<>)的时候无法使用索引会导致全表扫描
案例如下:

七、is null,is not null也无法使用索引

八、like以通配符开头('%abc')mysql索引失效,变成全表扫描的操作(注:以通配符结尾('abc%')不会失效)
1、以通配符开头,全表扫描,索引失效
2、未以通配符开头,只是通配符结尾,查询类型:range范围查找(未全表扫描),索引可用


3、问题:如何解决like '%字符串%'时索引失效的方法?? 答:使用用覆盖索引来解决
案例分析如下
3.1 表结构:

3.2 未建索引(只设置了主键为id)
结论:全表扫描,未使用索引


3.2.2 只查一个id字段的情况,也未用到索引

3.2.3 以下几种情况都未使用到索引,执行后都是全表扫描


3.3 创建索引

3.3.1 重新分析

用覆盖索引 ,只查name,age

只查主键id,覆盖索引查询,索引有效

只查name或只查age,覆盖索引查询,索引有效


查询列的个数超过索引列的个数时(id不考虑在内),索引失效


总结:用覆盖索引来解决 '%字符串%'查询的问题

九、字符串 不加号索引失效
不加引号时,mysql会给name字段做隐式的类型转换(将整数类型转换成字符串型),违反了第三条原则:索引列上无计算,所以索引会失效


十、少用or,用它来连接时会索引失效


小结:不要因为以上各种情况的索引失效而感到束手束脚,在实际应用中,也难免会出现索引失效的情况,sql最优并不代表满足以上所有原则 

口诀:
全值匹配我最爱,最左前缀要遵守;
带头大哥不能死,中间兄弟不能断;
索引列上无计算,范围之后全失效;
like百分加右边,覆盖索引不写星*
不等空值还有or,索引失效要少用;
字符串里有引号, 高级SQL也不难

二、面试题讲解
题目SQL


1、顺序问题
1.1where条件有带头大哥,中间兄弟顺序乱 ,索引有效

1.2where条件带头大哥往后站,中间兄弟顺序乱,索引有效


1.3 原因:mysql有自带的sql优化器,where查询条件顺序问题并不会导致索引失效

2、范围之后全失效(C3只用来做排序)

3where条件中C4列做范围查找,C1,C2,C3,C4索引列都生效


4where条件有带头大哥,但中间兄弟断,sql用了order by,索引部分失效

4.1 条件中有C4
用到了索引中的c1,c2用作查找,只用到c3做排序,未用到索引中的C4

4.2 条件中没有c4,分析结果跟4.1相同

4.3 中间兄弟C3断,索引在排序中失效,出现了filesort

5 、filesort的 问题
5.1 只用到c1一个索引,排序用了c2,c3(按序),未出现filesort
5.2 只用到c1一个索引,排序用了c3,c2(乱序),出现了filesort
小结:查找和排序列的顺序最好按照索引列的顺序来

5.3 用到c1,c2两个索引,排序用了c2,c3(按序),未出现filesort

5.4 用到c1,c2两个索引,排序用了c3,c2(乱序),未出现filesort
原因 :因为在查找条件中用了c2,所以order by排序时,c2已经是一个常量了,换句话说只要按[order by c3,常量]来排序,并不会产生filesort

6group by分组前必排序,所以索引使用原则和索引优化法则基本跟order by 相同,违反法则时会产生临时表和filesort

三、一般性建议

优化指南

怎么加快查询速度,优化查询效率,主要原则就是应尽量避免全表扫描,应该考虑在whereorder by 涉及的列上建立索引。

  建立索引不是建的越多越好,原则是:

  第一:一个表的索引不是越多越好,也没有一个具体的数字,根据以往的经验,一个表的索引最多不能超过6个,因为索引越多,对updateinsert操作也会有性能的影响,涉及到索引的新建和重建操作。

  第二:建立索引的方法论为:

多数查询经常使用的列;
很少进行修改操作的列;
索引需要建立在数据差异化大的列上
  利用以上的基础我们讨论一下如何优化sql.

  1sql语句模型结构优化指导

    a. ORDER BY + LIMIT组合的索引优化

      如果一个SQL语句形如:SELECT [column1],[column2],. FROM [TABLE] ORDER BY [sort] LIMIT [offset],[LIMIT];

      这个SQL语句优化比较简单,在[sort]这个栏位上建立索引即可。

    b. WHERE + ORDER BY + LIMIT组合的索引优化

      如果一个SQL语句形如:SELECT [column1],[column2],. FROM [TABLE] WHERE [columnX] = [VALUE] ORDER BY [sort] LIMIT [offset],[LIMIT];

      这个语句,如果你仍然采用第一个例子中建立索引的方法,虽然可以用到索引,但是效率不高。更高效的方法是建立一个联合索引(columnX,sort)

    c. WHERE+ORDER BY多个栏位+LIMIT

       如果一个SQL语句形如:SELECT * FROM [table] WHERE uid=1 ORDER x,y LIMIT 0,10;

       对于这个语句,大家可能是加一个这样的索引:(x,y,uid)。但实际上更好的效果是(uid,x,y)。这是由MySQL处理排序的机制造成的。

  2、复合索引(形如(x,y,uid)索引的索引)

    先看这样一条语句这样的:select * from users where area =’beijing’ and age=22;

     如果我们是在area和age上分别创建索引的话,由于mysql查询每次只能使用一个索引,所以虽然这样已经相对不做索引时全表扫描提高了很多效率,但是如果area,age两列上创建复合索引的话将带来更高的效率。

        在使用索引字段作为条件时,如果该索引是复合索引,那么必须使用到该索引中的第一个字段作为条件时才能保证系统使用该索引,否则该索引将不会被使用,并且应尽可能的让字段顺序与索引顺序相一致。

    例如我们建立了一个这样的索引(area,age,salary),那么其实相当于创建了(area,age,salary),(area,age),(area)三个索引,这样称为最佳左前缀特性。

  3like语句优化

SELECT id FROM A WHERE name like '%abc%'
   由于abc前面用了“%”,因此该查询必然走全表查询,除非必要,否则不要在关键词前加%,优化成如下

SELECT id FROM A WHERE name like 'abc%'
   4where子句使用 !=<> 操作符优化

  在where子句中使用 !=<>操作符,索引将被放弃使用,会进行全表查询。

   如SQL:SELECT id FROM A WHERE ID != 5 优化成:SELECT id FROM A WHERE ID>5 OR ID<5

  5where子句中使用 IS NULLIS NOT NULL 的优化

  在where子句中使用 IS NULLIS NOT NULL 判断,索引将被放弃使用,会进行全表查询。

   如SQL:SELECT id FROM A WHERE num IS NULL 优化成num上设置默认值0,确保表中num没有null值,然后SQL为:SELECT id FROM A WHERE num=0

  6where子句使用or的优化

  很多时候使用union all 或 nuin(必要的时候)的方式替换“or”会得到更好的效果。where子句中使用了or,索引将被放弃使用。

   如SQL:SELECT id FROM A WHERE num =10 or num = 20 优化成:SELECT id FROM A WHERE num = 10 union all SELECT id FROM A WHERE num=20

  7where子句使用INNOT IN的优化

  innot in 也要慎用,否则也会导致全表扫描。

   方案一:between替换in

   如SQL:SELECT id FROM A WHERE num in(1,2,3) 优化成:SELECT id FROM A WHERE num between 1 and 3

   方案二:exist替换in

   如SQL:SELECT id FROM A WHERE num in(select num from b ) 优化成:SELECT num FROM A WHERE num exists(select 1 from B where B.num = A.num)

   方案三:left join替换in

   如SQL:SELECT id FROM A WHERE num in(select num from B) 优化成:SELECT id FROM A LEFT JOIN B ON A.num = B.num

  8where子句中对字段进行表达式操作的优化

  不要在where子句中的“=”左边进行函数、算数运算或其他表达式运算,否则系统将可能无法正确使用索引。

   如SQL:SELECT id FROM A WHERE num/2 = 100 优化成:SELECT id FROM A WHERE num = 100*2

   如SQL:SELECT id FROM A WHERE substring(name,1,3) = 'abc' 优化成:SELECT id FROM A WHERE LIKE 'abc%'

   如SQL:SELECT id FROM A WHERE datediff(day,createdate,'2016-11-30')=0 优化成:SELECT id FROM A WHERE createdate>='2016-11-30' and createdate<'2016-12-1'

   如SQL:SELECT id FROM A WHERE year(addate) <2016 优化成:SELECT id FROM A where addate<'2016-01-01'

  9、任何地方都不要用 select * from table ,用具体的字段列表替换"*",不要返回用不到的字段  

  10、使用“临时表”暂存中间结果

    采用临时表暂存中间结果好处:

      (1)避免程序中多次扫描主表,减少程序执行“共享锁”阻塞“更新锁”,减少了阻塞,提高了并发性能。

      (2)尽量使用表变量来代替临时表。如果表变量包含大量数据,请注意索引非常有限(只有主键索引)。

      (3)避免频繁创建和删除临时表,以减少系统资源的浪费。

      (4)尽量避免向客户端返回大数据量,若数据量过大,应考虑相应需求是否合理。

  11limit分页优化

    当偏移量特别时,limit效率会非常低

    SELECT id FROM A LIMIT 1000,10   很快

    SELECT id FROM A LIMIT 90000,10 很慢

    优化方法:

    方法一:select id from A order by id limit 90000,10; 很快,0.04秒就OK。 因为用了id主键做索引当然快

    方法二:select id,title from A where id>=(select id from collect order by id limit 90000,1) limit 10;

        方法三:select id from A order by id  between 10000000 and 10000010;

  12、批量插入优化

INSERT into person(name,age) values('A',14) INSERT into person(name,age) values('B',14) INSERT into person(name,age) values('C',14)
    可优化为:

INSERT into person(name,age) values('A',14),('B',14),('C',14),
  13、利用limit 1top 1 取得一行

    有时要查询一张表时,你知道只需要看一条记录,你可能去查询一条特殊的记录。可以使用limit 1 或者 top 1 来终止数据库索引继续扫描整个表或索引。

    如SQLSELECT id FROM A LIKE 'abc%' 优化为:SELECT id FROM A LIKE 'abc%' limit 1

  14、尽量不要使用 BY RAND()命令

    BY RAND()是随机显示结果,这个函数可能会为表中每一个独立的行执行BY RAND()命令,这个会消耗处理器的处理能力。

    如SQLSELECT * FROM A order by rand() limit 10 优化为:SELECT * FROM A WHERE id >= ((SELECT MAX(id) FROM A)-(SELECT MIN(id) FROM A)) * RAND() + (SELECT MIN(id) FROM A) LIMIT 10

  15、排序的索引问题 

    Mysql查询只是用一个索引,因此如果where子句中已经使用了索引的话,那么order by中的列是不会使用索引的。因此数据库默认排序可以符合要求情况下不要使用排序操作;

   尽量不要包含多个列的排序,如果需要最好给这些列创建复合索引。

  16、尽量用 union all 替换 union

   unionunion all的差异主要是前者需要将两个(或者多个)结果集合并后再进行唯一性过滤操作,这就会涉及到排序,增加大量的cpu运算,加大资源消耗及延迟。所以当我们可以确认不可能出现重复结果集或者不在乎重复结果集的时候,尽量使用union all而不是union

  17、避免类型转换

   这里所说的“类型转换”是指where子句中出现column字段的类型和传入的参数类型不一致的时候发生的类型转换。人为的上通过转换函数进行转换,直接导致mysql无法使用索引。如果非要转型,应该在传入参数上进行转换。

        例如utime 是datetime类型,传入的参数是“2016-07-23”,在比较大小时通常是 date(utime)>"2016-07-23",可以优化为utime>"2016-07-23 00:00:00"

  18、尽可能使用更小的字段         

    MySQL从磁盘读取数据后是存储到内存中的,然后使用cpu周期和磁盘I/O读取它,这意味着越小的数据类型占用的空间越小,从磁盘读或打包到内存的效率都更好,但也不要太过执着减小数据类型,要是以后应用程序发生什么变化就没有空间了。

    修改表将需要重构,间接地可能引起代码的改变,这是很头疼的问题,因此需要找到一个平衡点。

  19Inner joinleft joinright join、子查询

  第一:inner join内连接也叫等值连接是,left/rightjoin是外连接。

     SELECT A.id,A.name,B.id,B.name FROM A LEFT JOIN B ON A.id =B.id;

     SELECT A.id,A.name,B.id,B.name FROM A RIGHT JOIN ON B A.id= B.id;

     SELECT A.id,A.name,B.id,B.name FROM A INNER JOIN ON A.id =B.id;

     经过来之多方面的证实inner join性能比较快,因为inner join是等值连接,或许返回的行数比较少。但是我们要记得有些语句隐形的用到了等值连接,如:

    SELECT A.id,A.name,B.id,B.name FROM A,B WHERE A.id = B.id;

    推荐:能用inner join连接尽量使用inner join连接

   第二:子查询的性能又比外连接性能慢,尽量用外连接来替换子查询。

    Select* from A where exists (select * from B where id>=3000 and A.uuid=B.uuid);

    A表的数据为十万级表,B表为百万级表,在本机执行差不多用2秒左右,我们可以通过explain可以查看到子查询是一个相关子查询(DEPENDENCE SUBQUERY);Mysql是先对外表A执行全表查询,然后根据uuid逐次执行子查询,如果外层表是一个很大的表,我们可以想象查询性能会表现比这个更加糟糕。

        一种简单的优化就是用innerjoin的方法来代替子查询,查询语句改为:

     Select* from A inner join B ON A.uuid=B.uuid using(uuid) where b.uuid>=3000;  这个语句执行测试不到一秒;

  第三:使用JOIN时候,应该用小的结果驱动打的结果(left join 左边表结果尽量小,如果有条件应该放到左边先处理,right join同理反向),同时尽量把牵涉到多表联合的查询拆分多个query (多个表查询效率低,容易锁表和阻塞)。如:

  Select * from A left join B A.id=B.ref_id where  A.id>10;可以优化为:select * from (select * from A wehre id >10) T1 left join B on T1.id=B.ref_id;

  20、exist 代替 in

SELECT * from A WHERE id in (SELECT id from B)
SELECT * from A WHERE id EXISTS(SELECT 1 from A.id= B.id)
  in 是在内存中遍历比较

  exist 需要查询数据库,所以当B的数据量比较大时,exists效率优于in.

  in()只执行一次,把B表中的所有id字段缓存起来,之后检查A表的id是否与B表中的id相等,如果id相等则将A表的记录加入到结果集中,直到遍历完A表的所有记录。

  In 操作的流程原理如同一下代码

    List resultSet={};

    Array A=(select * from A);
    Array B=(select id from B);

    for(int i=0;i<A.length;i++) { for(int j=0;j<B.length;j++) { if(A[i].id==B[j].id) { resultSet.add(A[i]); break; } } } return resultSet; 
  可以看出,当B表数据较大时不适合使用in(),因为它会B表数据全部遍历一次

  如:A表有10000条记录,B表有1000000条记录,那么最多有可能遍历10000*1000000次,效率很差。

  再如:A表有10000条记录,B表有100条记录,那么最多有可能遍历10000*100次,遍历次数大大减少,效率大大提升。

  结论:in()适合B表比A表数据小的情况

  exist()会执行A.length()次,执行过程代码如下

    List resultSet={};
    Array A=(select * from A);
    for(int i=0;i<A.length;i++) {
       if(exists(A[i].id) { //执行select 1 from B where B.id=A.id是否有记录返回  resultSet.add(A[i]); } } return resultSet;
  当B表比A表数据大时适合使用exists(),因为它没有那么多遍历操作,只需要再执行一次查询就行。

  如:A表有10000条记录,B表有1000000条记录,那么exists()会执行10000次去判断A表中的id是否与B表中的id相等。

  如:A表有10000条记录,B表有100000000条记录,那么exists()还是执行10000次,因为它只执行A.length次,可见B表数据越多,越适合exists()发挥效果。

  再如:A表有10000条记录,B表有100条记录,那么exists()还是执行10000次,还不如使用in()遍历10000*100次,因为in()是在内存里遍历比较,而exists()需要查询数据库,

  我们都知道查询数据库所消耗的性能更高,而内存比较很快。

  结论:exists()适合B表比A表数据大的情况

   当A表数据与B表数据一样大时,inexists效率差不多,可任选一个使用。
DELIMITER //
CREATE PROCEDURE insert_school_info()
BEGIN
  INSERT INTO t_school_info2 (school_id, school_name)
  SELECT school_id, school_name FROM t_school_info;
END //
DELIMITER ;

CALL insert_school_info();
DELIMITER //
CREATE PROCEDURE insert_school_info()
BEGIN
  DECLARE i INT DEFAULT 0;
  DECLARE max_i INT;
  SELECT COUNT(*) INTO max_i FROM t_school_info;
  WHILE i < max_i DO
    INSERT INTO t_school_info2 (school_id, school_name)
    SELECT school_id, school_name FROM t_school_info LIMIT i, 1;
    INSERT INTO t_school_info2 (school_id, school_name)
    SELECT school_id, school_name FROM t_school_info LIMIT i, 1;
    INSERT INTO t_school_info2 (school_id, school_name)
    SELECT school_id, school_name FROM t_school_info LIMIT i, 1;
    SET i = i + 1;
  END WHILE;
END //
DELIMITER ;

CALL insert_school_info();
DELIMITER //
CREATE PROCEDURE insert_school_info()
BEGIN
  DECLARE i INT DEFAULT 0;
  DECLARE max_i INT;
  SELECT COUNT(*) INTO max_i FROM t_school_info;
  WHILE i < max_i DO
    INSERT INTO t_school_info2 (ids, school_id, school_name)
    VALUES (1, (SELECT school_id FROM t_school_info LIMIT i, 1), (SELECT school_name FROM t_school_info LIMIT i, 1));
    INSERT INTO t_school_info2 (ids, school_id, school_name)
    VALUES (2, (SELECT school_id FROM t_school_info LIMIT i, 1), (SELECT school_name FROM t_school_info LIMIT i, 1));
    INSERT INTO t_school_info2 (ids, school_id, school_name)
    VALUES (3, (SELECT school_id FROM t_school_info LIMIT i, 1), (SELECT school_name FROM t_school_info LIMIT i, 1));
    SET i = i + 1;
  END WHILE;
END //
DELIMITER ;

CALL insert_school_info();

使用performance_schema

show tables from performance_schema;
use performance_schema;
--查看当前数据库下的所有表,会看到有很多表存储着相关的信息
show tables;

accounts
binary_log_transaction_compression_stats
cond_instances
data_lock_waits
data_locks
error_log
events_errors_summary_by_account_by_error
events_errors_summary_by_host_by_error
events_errors_summary_by_thread_by_error
events_errors_summary_by_user_by_error
events_errors_summary_global_by_error
events_stages_current
events_stages_history
events_stages_history_long
events_stages_summary_by_account_by_event_name
events_stages_summary_by_host_by_event_name
events_stages_summary_by_thread_by_event_name
events_stages_summary_by_user_by_event_name
events_stages_summary_global_by_event_name
events_statements_current
events_statements_histogram_by_digest
events_statements_histogram_global
events_statements_history
events_statements_history_long
events_statements_summary_by_account_by_event_name
events_statements_summary_by_digest
events_statements_summary_by_host_by_event_name
events_statements_summary_by_program
events_statements_summary_by_thread_by_event_name
events_statements_summary_by_user_by_event_name
events_statements_summary_global_by_event_name
events_transactions_current
events_transactions_history
events_transactions_history_long
events_transactions_summary_by_account_by_event_name
events_transactions_summary_by_host_by_event_name
events_transactions_summary_by_thread_by_event_name
events_transactions_summary_by_user_by_event_name
events_transactions_summary_global_by_event_name
events_waits_current
events_waits_history
events_waits_history_long
events_waits_summary_by_account_by_event_name
events_waits_summary_by_host_by_event_name
events_waits_summary_by_instance
events_waits_summary_by_thread_by_event_name
events_waits_summary_by_user_by_event_name
events_waits_summary_global_by_event_name
file_instances
file_summary_by_event_name
file_summary_by_instance
global_status
global_variables
host_cache
hosts
keyring_component_status
keyring_keys
log_status
memory_summary_by_account_by_event_name
memory_summary_by_host_by_event_name
memory_summary_by_thread_by_event_name
memory_summary_by_user_by_event_name
memory_summary_global_by_event_name
metadata_locks
mutex_instances
objects_summary_global_by_type
performance_timers
persisted_variables
prepared_statements_instances
processlist
replication_applier_configuration
replication_applier_filters
replication_applier_global_filters
replication_applier_status
replication_applier_status_by_coordinator
replication_applier_status_by_worker
replication_asynchronous_connection_failover
replication_asynchronous_connection_failover_managed
replication_connection_configuration
replication_connection_status
replication_group_member_stats
replication_group_members
rwlock_instances
session_account_connect_attrs
session_connect_attrs
session_status
session_variables
setup_actors
setup_consumers
setup_instruments
setup_objects
setup_threads
socket_instances
socket_summary_by_event_name
socket_summary_by_instance
status_by_account
status_by_host
status_by_thread
status_by_user
table_handles
table_io_waits_summary_by_index_usage
table_io_waits_summary_by_table
table_lock_waits_summary_by_table
threads
tls_channel_status
user_defined_functions
user_variables_by_thread
users
variables_by_thread
variables_info

使用information_schema


ADMINISTRABLE_ROLE_AUTHORIZATIONS
APPLICABLE_ROLES
CHARACTER_SETS
CHECK_CONSTRAINTS
COLLATIONS
COLLATION_CHARACTER_SET_APPLICABILITY
COLUMNS
COLUMNS_EXTENSIONS
COLUMN_PRIVILEGES
COLUMN_STATISTICS
ENABLED_ROLES
ENGINES
EVENTS
FILES
INNODB_BUFFER_PAGE
INNODB_BUFFER_PAGE_LRU
INNODB_BUFFER_POOL_STATS
INNODB_CACHED_INDEXES
INNODB_CMP
INNODB_CMPMEM
INNODB_CMPMEM_RESET
INNODB_CMP_PER_INDEX
INNODB_CMP_PER_INDEX_RESET
INNODB_CMP_RESET
INNODB_COLUMNS
INNODB_DATAFILES
INNODB_FIELDS
INNODB_FOREIGN
INNODB_FOREIGN_COLS
INNODB_FT_BEING_DELETED
INNODB_FT_CONFIG
INNODB_FT_DEFAULT_STOPWORD
INNODB_FT_DELETED
INNODB_FT_INDEX_CACHE
INNODB_FT_INDEX_TABLE
INNODB_INDEXES
INNODB_METRICS
INNODB_SESSION_TEMP_TABLESPACES
INNODB_TABLES
INNODB_TABLESPACES
INNODB_TABLESPACES_BRIEF
INNODB_TABLESTATS
INNODB_TEMP_TABLE_INFO
INNODB_TRX
INNODB_VIRTUAL
KEYWORDS
KEY_COLUMN_USAGE
OPTIMIZER_TRACE
PARAMETERS
PARTITIONS
PLUGINS
PROCESSLIST
PROFILING
REFERENTIAL_CONSTRAINTS
RESOURCE_GROUPS
ROLE_COLUMN_GRANTS
ROLE_ROUTINE_GRANTS
ROLE_TABLE_GRANTS
ROUTINES
SCHEMATA
SCHEMATA_EXTENSIONS
SCHEMA_PRIVILEGES
STATISTICS
ST_GEOMETRY_COLUMNS
ST_SPATIAL_REFERENCE_SYSTEMS
ST_UNITS_OF_MEASURE
TABLES
TABLESPACES
TABLESPACES_EXTENSIONS
TABLES_EXTENSIONS
TABLE_CONSTRAINTS
TABLE_CONSTRAINTS_EXTENSIONS
TABLE_PRIVILEGES
TRIGGERS
USER_ATTRIBUTES
USER_PRIVILEGES
VIEWS
VIEW_ROUTINE_USAGE
VIEW_TABLE_USAGE

使用 show full processlist

MySQL进程列表显示了当前正在由服务器内执行的线程组执行的操作。processlist表是进程信息的一个来源。关于此表与其他来源的比较,请参见进程信息的来源。

可以直接查询processlist表。如果你有PROCESS权限,你可以看到所有的线程,甚至属于其他用户的线程。否则(没有PROCESS权限),非匿名用户可以看到自己的线程信息,但不能看到其他用户的线程,而匿名用户不能看到线程信息。

SELECT * FROM performance_schema.processlist;
SHOW full  PROCESSLIST;

当在MySQL中使用SHOW FULL PROCESSLIST命令时,如果您看到大量的连接处于"Sleep"状态,这通常是表示这些连接当前没有活动,它们正在等待新的请求或事件。

"Sleep"状态的连接是指已经建立的连接,但在某个特定时间点没有正在执行任何操作或查询。这些连接保持在"Sleep"状态是正常的,因为在应用程序处理期间,不是所有连接都需要一直保持活跃状态。在应用程序中,当连接完成查询或事务后,可能会将其释放回连接池,并保持在"Sleep"状态等待下一个请求。

一:有几个常见的原因会导致大量的连接处于"Sleep"状态:

  1. 应用程序闲置或低负载:当应用程序处于闲置或低负载状态时,连接可能会处于"Sleep"状态。这是正常的,因为在这些时候连接不需要一直保持活跃。
  2. 连接池配置:连接池的配置可能会影响连接的闲置时间和回收策略。如果连接池设置的闲置超时时间(idleTimeout)较长,连接可能会在一段时间后进入"Sleep"状态。您可以根据应用程序的需求和负载情况来调整连接池的配置,以平衡连接的使用和回收。
  3. 长时间运行的事务:如果某些连接正在执行长时间运行的事务,它们可能会保持在"Sleep"状态直到事务完成,长时间运行的事务可能导致连接保持的时间较长

尽管处于"Sleep"状态的连接是正常的,但如果您注意到大量的连接处于"Sleep"状态并且这对性能或资源使用产生了负面影响,可以考虑以下措施:

  • 调整连接池配置:根据应用程序的需求和负载情况,调整连接池的最大连接数、最小空闲连接数、闲置超时时间等参数
  • 定期检查和优化长时间运行的事务:确保事务逻辑合理,并尽量减少长时间运行的事务。
  • 监控和调整系统资源:确保服务器具有足够的内存和CPU资源来处理连接数,避免资源瓶颈。

总的来说,大量处于"Sleep"状态的连接通常是正常的,只要它们不会导致性能问题或资源浪费。通过合理的连接池配置和定期监控,可以优化连接的使用和回收,提高数据库的性能和资源利用率。

二:如果在执行 SHOW FULL PROCESSLIST 命令时发现大量的空闲连接,同时其他用户却无法获取数据库连接并出现连接获取失败的提示

可能存在以下几个原因和解决方法:

  1. 连接池配置不当:检查连接池的配置参数,包括最大连接数(maximumPoolSize)、最小空闲连接数(minimumIdle)和连接超时时间(connectionTimeout)。确保这些参数的配置合理且与实际需求相符。可能存在连接池中空闲连接数过多,而最大连接数已达到或接近上限,导致其他用户无法获取连接。适当增加最大连接数或减少空闲连接数可以缓解这个问题。
  2. 连接泄漏:连接泄漏是指应用程序在使用完连接后没有正确地释放它们。这可能导致连接池中的连接资源被长时间占用,而其他用户无法获取到连接。确保在使用完连接后,及时关闭连接并将其返回到连接池。同时,检查应用程序代码,尽量避免连接在异常情况下未能被正确释放的情况。
  3. 数据库连接处理逻辑问题:检查应用程序中的数据库连接处理逻辑。确保连接的获取和释放在合适的位置进行,并遵循良好的连接使用和管理实践。例如,确保连接在使用后及时关闭、使用连接池进行连接管理等。
  4. 数据库服务器资源限制:如果数据库服务器的最大连接数配置(例如 MySQL 的 max_connections 参数)过低,可能会限制连接的数量,导致无法满足所有用户的连接请求。检查数据库服务器的最大连接数配置,确保它足够满足应用程序的连接需求。如果需要增加最大连接数,可以修改数据库服务器的配置文件并重启数据库服务。
  5. 数据库服务器性能问题:如果数据库服务器的资源(如CPU、内存、磁盘IO)受限,可能无法及时处理连接请求,导致其他用户无法获取连接。在这种情况下,优化数据库服务器的性能,增加硬件资源或调整数据库的配置参数,以提高数据库的处理能力。

综上所述,排查并解决这些潜在问题可以帮助确保数据库连接的正常获取和释放,避免大量的空闲连接占用资源导致其他用户无法获取连接的情况。同时,根据具体情况,对连接池配置、连接管理逻辑、数据库服务器资源和性能等进行综合优化,以确保数据库连接的稳定和可靠性。

performance_schema.events_statements_current的每个字段的含义

  1. THREAD_ID: 执行语句的线程 ID。
  2. EVENT_ID: 语句事件的唯一标识符。
  3. END_EVENT_ID: 语句结束事件的唯一标识符。
  4. EVENT_NAME: 语句事件的名称,表示语句的类型,例如 SQL_QUERY、QUERY_RESPONSE、STATEMENT、STAGE等。
  5. SOURCE: 语句的源,指示语句是来自客户端还是服务器内部。
  6. TIMER_START: 语句执行的开始时间。【单位:纳秒】与秒之间的的进率是10亿
  7. TIMER_END: 语句执行的结束时间。
  8. TIMER_WAIT: 语句执行期间的等待时间。
  9. LOCK_TIME: 语句执行期间的锁定时间。【单位:微秒】与秒之间的
  10. SQL_TEXT: 语句的文本内容,即实际执行的 SQL 语句。
  11. DIGEST: 语句的哈希摘要,用于标识相同语句的执行。
  12. CURRENT_SCHEMA: 当前执行语句的数据库模式。
  13. OBJECT_SCHEMA: 语句涉及的对象所在的数据库模式。
  14. OBJECT_NAME: 语句涉及的对象名称,例如表名、函数名等。
  15. OBJECT_TYPE: 语句涉及的对象类型,例如表、函数、存储过程等。
  16. PROCESSLIST_ID: 执行语句的进程 ID。
  17. CURRENT_THREAD_ID: 当前执行语句的线程 ID。
  18. HOST: 执行语句的主机名。
  19. USER: 执行语句的用户。
  20. OS_THREAD_ID: 执行语句的操作系统线程 ID。
  21. INDEX_NAMES: 使用的索引名称。
  22. DIGEST_TEXT: 语句的摘要文本,可用于识别相同的语句。

这些字段提供了关于当前执行的语句的详细信息,包括执行时间、语句内容、涉及的对象等。通过分析这些字段,可以获得有关数据库性能和语句执行的有用指标和统计信息。

批量查询连接进程id

-- 批量查询连接进程id
SELECT concat('KILL ',PROCESSLIST_id,';') as command  from performance_schema.events_statements_current as c ,  performance_schema.threads t where c.THREAD_ID=t.THREAD_ID and  t.`name`='thread/sql/one_connection'  and t.processList_host='119.130.228.100';
-- 124.71.63.169
-- 119.130.228.100

select concat("kill ", ID, ";") as command
from information_schema.PROCESSLIST
where `HOST` regexp "119.130.228.100";

打开一个新的查询窗口
复制刚刚查询的列表,执行

锁等待超时

这个错误是 MySQL 的一个标准错误信息,指的是你的事务尝试获取一个锁但被阻塞了,因为其他事务持有了这个锁并且没有释放

这个错误通常发生在并发环境中,当两个或更多的事务试图同时修改相同的数据时,会发生锁竞争如果事务A获取了某个数据的锁并正在对其进行操作,而事务B也尝试获取这个数据的锁,那么事务B会被阻塞,直到事务A释放锁或者超时

这个问题可以通过以下几种方式解决:

  1. 检查并优化你的数据库操作:确保你的数据库操作尽可能快地完成,以减少持有锁的时间这可能涉及优化你的 SQL 查询、增加数据库的性能,或者重新设计你的数据库架构以减少需要锁定的数据量
  2. 增加锁等待超时时间:你可以增加 MySQL 的 innodb_lock_wait_timeout 值,以允许事务持有锁更长时间这个值默认是 50秒,你可以设置为更大的值例如,设置为 120 秒:

mysql事务等待超时

SET GLOBAL innodb_lock_wait_timeout = 120;

-- 查找锁等待超时的事务
select * from information_schema.INNODB_TRX
-- 杀掉事务线程
kill 4450423

SELECT * FROM information_schema.INNODB_TRX
					
kill trx_mysql_thread_id
MySQL数据库中出现了锁等待超时的问题时,可以尝试以下几种解决方案:

优化数据库结构:

减少表的连接:通过优化数据库设计,减少表之间的连接操作,可以减少锁的竞争。
避免冗余字段:删除不必要的字段,可以减少锁的范围,提高并发性能。
调整MySQL缓存:

缓存大小调整:根据服务器的硬件配置和数据库访问频率,适当调整MySQL缓存的大小。
缓存时间调整:合理设置缓存的生存时间(TTL),以提高缓存的命中率,减少对数据库的访问次数。
优化查询语句:

使用索引:在查询中尽量使用索引,可以减少全表扫描和锁的竞争。
避免大表扫描:尽量避免对大表的扫描操作,可以通过分页、过滤条件等方式优化查询。
减少子查询:子查询会增加查询复杂度和执行时间,尽量使用连接(JOIN)来替代。
分离读写操作:

设置读写分离:将读操作和写操作分别分配到不同的MySQL实例或服务器上,以实现负载均衡和减少锁的竞争。
读锁优化:对于读操作,可以设置适当的读锁粒度,以减少锁的竞争和阻塞。
增加服务器资源:

增加CPU资源:增加服务器的CPU数量或核心数,可以提高数据库的处理能力和并发性能。
增加内存资源:增加服务器的内存容量,可以减少磁盘I/O操作和锁的竞争。
锁等待超时处理:

设置锁等待超时时间:通过调整MySQL配置参数(如innodb_lock_wait_timeout),设置锁等待超时的时间限制。
监控与调整:定期监控数据库的性能指标和锁等待情况,及时发现并解决潜在问题。
事务设计优化:

减少事务中的锁资源:一个事务中尽量减少对共享资源的访问,这样在同一时间需要等待锁的事务就会减少。
设计事务的粒度:将一个大事务分解为多个小事务,降低事务间的相互影响。
使用并发控制和隔离级别:
使用合适的隔离级别:比如使用READ COMMITTEDREPEATABLE READ级别,减少事务间的相互影响。
使用乐观锁:如果数据更新冲突不太可能发生,考虑使用乐观锁,避免长时间持有锁。
检查SQL语句和索引:
检查SQL语句的执行效率:使用EXPLAIN分析SQL语句,查看索引是否被正确使用,减少全表扫描和锁定。
添加必要的索引:如果查询性能较差,可能需要添加必要的索引来提高查询性能,减少锁的使用。
使用分区表:
如果表非常大,可以考虑使用分区表,将大表分解为多个小表,降低锁的竞争和事务的处理时间。
使用并行查询和事务:
如果可能的话,使用并行查询和事务来加快处理速度,减少锁等待时间。
考虑使用NoSQL解决方案:
如果需要处理大量并发请求和共享访问的数据结构,可以考虑使用NoSQL解决方案(如MongoDB、Cassandra等),避免MySQL的锁机制带来的问题。
定期优化数据库性能:
定期进行数据库性能优化,包括数据库重组、索引优化、数据清理等,提高数据库的整体性能和稳定性

ID

连接标识符。这是显示在SHOW PROCESSLIST语句的Id列中的相同值,显示在Performance Schema线程表的PROCESSLIST_ID列中,并由线程中的CONNECTION_ID()函数返回。

USER

发布该语句的MySQL用户。系统用户的值指的是由服务器生成的处理内部任务的非客户线程,例如,延迟行处理程序线程或复制主机上使用的I/O或SQL线程。对于系统用户来说,在Host列中没有指定主机。 未认证的用户指的是与客户连接相关的线程,但是客户用户的认证还没有发生。 event_scheduler指的是监视计划事件的线程(见第25.4节,“使用事件调度器”)。

thread_cache_size

服务器应该缓存多少个线程以便重复使用。当客户端断开连接时,如果客户端的线程少于thread_cache_size,则将其放入缓存。
如果可能的话,对线程的请求会通过重复使用从缓存中取出的线程来满足,只有当缓存为空时才会创建一个新的线程。
如果你有大量的新连接,这个变量可以增加以提高性能。
通常情况下,如果你有一个好的线程实现,这不会提供一个明显的性能改进。
然而,如果你的服务器每秒看到数百个连接,你通常应该把thread_cache_size设置得足够高,以便大多数新连接使用缓存的线程。



通过检查Connections和Threads_created状态变量之间的差异,你可以看到线程缓存的效率如何。详情请见第5.1.10"服务器状态变量"

闲连接占用资源导致其他用户无法获取连接的情况。同时,根据具体情况,对连接池配置、连接管理逻辑、数据库服务器资源和性能等进行综合优化,以确保数据库连接的稳定和可靠性。

performance_schema.events_statements_current的每个字段的含义

  1. THREAD_ID: 执行语句的线程 ID。
  2. EVENT_ID: 语句事件的唯一标识符。
  3. END_EVENT_ID: 语句结束事件的唯一标识符。
  4. EVENT_NAME: 语句事件的名称,表示语句的类型,例如 SQL_QUERY、QUERY_RESPONSE、STATEMENT、STAGE等。
  5. SOURCE: 语句的源,指示语句是来自客户端还是服务器内部。
  6. TIMER_START: 语句执行的开始时间。【单位:纳秒】与秒之间的的进率是10亿
  7. TIMER_END: 语句执行的结束时间。
  8. TIMER_WAIT: 语句执行期间的等待时间。
  9. LOCK_TIME: 语句执行期间的锁定时间。【单位:微秒】与秒之间的
  10. SQL_TEXT: 语句的文本内容,即实际执行的 SQL 语句。
  11. DIGEST: 语句的哈希摘要,用于标识相同语句的执行。
  12. CURRENT_SCHEMA: 当前执行语句的数据库模式。
  13. OBJECT_SCHEMA: 语句涉及的对象所在的数据库模式。
  14. OBJECT_NAME: 语句涉及的对象名称,例如表名、函数名等。
  15. OBJECT_TYPE: 语句涉及的对象类型,例如表、函数、存储过程等。
  16. PROCESSLIST_ID: 执行语句的进程 ID。
  17. CURRENT_THREAD_ID: 当前执行语句的线程 ID。
  18. HOST: 执行语句的主机名。
  19. USER: 执行语句的用户。
  20. OS_THREAD_ID: 执行语句的操作系统线程 ID。
  21. INDEX_NAMES: 使用的索引名称。
  22. DIGEST_TEXT: 语句的摘要文本,可用于识别相同的语句。

这些字段提供了关于当前执行的语句的详细信息,包括执行时间、语句内容、涉及的对象等。通过分析这些字段,可以获得有关数据库性能和语句执行的有用指标和统计信息。

批量查询连接进程id

-- 批量查询连接进程id
SELECT concat('KILL ',PROCESSLIST_id,';') as command  from performance_schema.events_statements_current as c ,  performance_schema.threads t where c.THREAD_ID=t.THREAD_ID and  t.`name`='thread/sql/one_connection'  and t.processList_host='119.130.228.100';
-- 124.71.63.169
-- 119.130.228.100

select concat("kill ", ID, ";") as command
from information_schema.PROCESSLIST
where `HOST` regexp "119.130.228.100";

打开一个新的查询窗口
复制刚刚查询的列表,执行

锁等待超时

这个错误是 MySQL 的一个标准错误信息,指的是你的事务尝试获取一个锁但被阻塞了,因为其他事务持有了这个锁并且没有释放

这个错误通常发生在并发环境中,当两个或更多的事务试图同时修改相同的数据时,会发生锁竞争如果事务A获取了某个数据的锁并正在对其进行操作,而事务B也尝试获取这个数据的锁,那么事务B会被阻塞,直到事务A释放锁或者超时

这个问题可以通过以下几种方式解决:

  1. 检查并优化你的数据库操作:确保你的数据库操作尽可能快地完成,以减少持有锁的时间这可能涉及优化你的 SQL 查询、增加数据库的性能,或者重新设计你的数据库架构以减少需要锁定的数据量
  2. 增加锁等待超时时间:你可以增加 MySQL 的 innodb_lock_wait_timeout 值,以允许事务持有锁更长时间这个值默认是 50秒,你可以设置为更大的值例如,设置为 120 秒:

mysql事务等待超时

SET GLOBAL innodb_lock_wait_timeout = 120;

-- 查找锁等待超时的事务
select * from information_schema.INNODB_TRX
-- 杀掉事务线程
kill 4450423

SELECT * FROM information_schema.INNODB_TRX
					
kill trx_mysql_thread_id
MySQL数据库中出现了锁等待超时的问题时,可以尝试以下几种解决方案:

优化数据库结构:

减少表的连接:通过优化数据库设计,减少表之间的连接操作,可以减少锁的竞争。
避免冗余字段:删除不必要的字段,可以减少锁的范围,提高并发性能。
调整MySQL缓存:

缓存大小调整:根据服务器的硬件配置和数据库访问频率,适当调整MySQL缓存的大小。
缓存时间调整:合理设置缓存的生存时间(TTL),以提高缓存的命中率,减少对数据库的访问次数。
优化查询语句:

使用索引:在查询中尽量使用索引,可以减少全表扫描和锁的竞争。
避免大表扫描:尽量避免对大表的扫描操作,可以通过分页、过滤条件等方式优化查询。
减少子查询:子查询会增加查询复杂度和执行时间,尽量使用连接(JOIN)来替代。
分离读写操作:

设置读写分离:将读操作和写操作分别分配到不同的MySQL实例或服务器上,以实现负载均衡和减少锁的竞争。
读锁优化:对于读操作,可以设置适当的读锁粒度,以减少锁的竞争和阻塞。
增加服务器资源:

增加CPU资源:增加服务器的CPU数量或核心数,可以提高数据库的处理能力和并发性能。
增加内存资源:增加服务器的内存容量,可以减少磁盘I/O操作和锁的竞争。
锁等待超时处理:

设置锁等待超时时间:通过调整MySQL配置参数(如innodb_lock_wait_timeout),设置锁等待超时的时间限制。
监控与调整:定期监控数据库的性能指标和锁等待情况,及时发现并解决潜在问题。
事务设计优化:

减少事务中的锁资源:一个事务中尽量减少对共享资源的访问,这样在同一时间需要等待锁的事务就会减少。
设计事务的粒度:将一个大事务分解为多个小事务,降低事务间的相互影响。
使用并发控制和隔离级别:
使用合适的隔离级别:比如使用READ COMMITTEDREPEATABLE READ级别,减少事务间的相互影响。
使用乐观锁:如果数据更新冲突不太可能发生,考虑使用乐观锁,避免长时间持有锁。
检查SQL语句和索引:
检查SQL语句的执行效率:使用EXPLAIN分析SQL语句,查看索引是否被正确使用,减少全表扫描和锁定。
添加必要的索引:如果查询性能较差,可能需要添加必要的索引来提高查询性能,减少锁的使用。
使用分区表:
如果表非常大,可以考虑使用分区表,将大表分解为多个小表,降低锁的竞争和事务的处理时间。
使用并行查询和事务:
如果可能的话,使用并行查询和事务来加快处理速度,减少锁等待时间。
考虑使用NoSQL解决方案:
如果需要处理大量并发请求和共享访问的数据结构,可以考虑使用NoSQL解决方案(如MongoDB、Cassandra等),避免MySQL的锁机制带来的问题。
定期优化数据库性能:
定期进行数据库性能优化,包括数据库重组、索引优化、数据清理等,提高数据库的整体性能和稳定性

ID

连接标识符。这是显示在SHOW PROCESSLIST语句的Id列中的相同值,显示在Performance Schema线程表的PROCESSLIST_ID列中,并由线程中的CONNECTION_ID()函数返回。

USER

发布该语句的MySQL用户。系统用户的值指的是由服务器生成的处理内部任务的非客户线程,例如,延迟行处理程序线程或复制主机上使用的I/O或SQL线程。对于系统用户来说,在Host列中没有指定主机。 未认证的用户指的是与客户连接相关的线程,但是客户用户的认证还没有发生。 event_scheduler指的是监视计划事件的线程(见第25.4节,“使用事件调度器”)。

thread_cache_size

服务器应该缓存多少个线程以便重复使用。当客户端断开连接时,如果客户端的线程少于thread_cache_size,则将其放入缓存。
如果可能的话,对线程的请求会通过重复使用从缓存中取出的线程来满足,只有当缓存为空时才会创建一个新的线程。
如果你有大量的新连接,这个变量可以增加以提高性能。
通常情况下,如果你有一个好的线程实现,这不会提供一个明显的性能改进。
然而,如果你的服务器每秒看到数百个连接,你通常应该把thread_cache_size设置得足够高,以便大多数新连接使用缓存的线程。



通过检查Connections和Threads_created状态变量之间的差异,你可以看到线程缓存的效率如何。详情请见第5.1.10"服务器状态变量"

http://www.kler.cn/a/537589.html

相关文章:

  • 优惠券平台(一):基于责任链模式创建优惠券模板
  • git SourceTree 使用
  • 离线安装Appium Server
  • 【Kubernetes Pod间通信-第2篇】使用BGP实现Pod到Pod的通信
  • 游戏引擎 Unity - Unity 打开项目、Unity Editor 添加简体中文语言包模块、Unity 项目设置为简体中文
  • Kafka 使用说明(kafka官方文档中文)
  • java Jvm 双亲委派模型
  • 【Ubuntu】本地部署Deep Seek(深度求索)大模型的保姆级教程 | 详细教程
  • ubuntu文件同步
  • MyCAT 2实现mysql8主从同步读写分离
  • 通讯录管理小程序
  • 【AI大模型】Cherry Studio和Deepseek模型搭建本地知识库+硅基流动API调用+本地ollama模型调用
  • 政采云业务网关实践:使用 Higress 统一替代 APISIX/Kong/Istio Ingress
  • 【AI日记】25.02.08
  • 【梦想终会实现】Linux驱动学习6
  • 部署open webui 调用ollama启动的deepseek
  • android设置添加设备QR码信息
  • 【Prometheus】如何通过prometheus监控springboot程序运行状态,并实时告警通知
  • Git仓库托管基本使用03——远程仓库
  • 使用Vue开发可复用的Web Components:跨框架组件封装指南
  • 【学术投稿-第五届消费电子与计算机工程国际学术会议】HTML核心元素详解:超链接、列表、表格与实用技巧
  • 【10.7】队列-解预算内的最多机器人数目
  • 一键操作,完美解决办公问题!
  • layui组件库的年份选择器怎么设置区间超过区间不可点击
  • 基于Docker搭建ES集群,并设置冷热数据节点
  • 【Flink实战】Flink -C实现类路径配置与实现UDF Jar