Update 操作 Where 字段未加索引引发的服务不可用

开发环境一个微服务模块所有接口不可用,对应页面无法访问。之前一直没有问题,早上打开页面就出现了500异常信息,并且其它微服务模块接口正常,页面也能访问。

错误排查

1、连接服务器,查看日志发现如下错误信息

org.springframework.jdbc.CannotGetJdbcConnectionException: Failed to obtain JDBC Connection; nested exception is java.sql.SQLTransientConnectionException: HikariPool-1 - Connection is not available, request timed out after 30000ms.

报错信息显示数据库连接超时,初步判断是请求量太高导致数据库连接数不够。进一步验证,查看 yml 配置文件中 Hikari 数据库连接池的配置,发现并没有配置最大连接数(maximum-pool-size),那默认最大连接数为10。至此想到的一个解决方案就是增加数据库连接池的最大连接数,但是还需要进一步查看数据库的运行情况。

2、连接数据库,查看 MySQL 运行是否正常

// 查看当前运行的所有事务
select * from information_schema.INNODB_TRX;

// 当前出现的锁
select * from infromation_schema.INNODB_LOCKS;

运行第一条SQL语句,查询到 10 条记录(正好对应上线程池的 10 条连接数),其中 9 个事务状态为 LOCK WAITtrx_state列),1 个事务状态为 RUNNING 。这 10 个事务都是对同一个表执行更新操作(trx_query列展示事务正在执行的SQL语句)。再运行第二条SQL语句,也是查询到 10 条记录,并且所有记录的锁模式都是X(排它锁,lock_mode列)。

INNODB_TRX表信息

image-20221222142135836.png

INNODB_LOCKS表信息

ba585b0c760dc1b474202c3cae27dd6c.png

到这里就明确了数据库中只有一个事务在执行,大量事务占用连接资源并处于等待状态,因为当前所有事务都是对同一个表进行更新操作,可能的情况就是对表加了表锁。加上了表锁,说明更新操作进行了全表扫描,没有走索引。之后查看执行的SQL语句,再查看建表信息,发现where 后的字段果然没有加索引。

UPDATE table SET a = ?,b = ? WHERE (id = ?)

解决

为该字段加上索引

alter table 表名 add index 索引名(列名)

问题

1、为什么服务运行了一段时间后,出现了这个问题?

服务刚启动的时候,连接池资源是够用的,业务也能正常使用。业务中这条更新语句会被频繁调用。当第一个事务执行更新操作的时候,此时有新的事务也要执行更新操作,后面的事务就要被阻塞(表锁)。调用次数多,处理速度慢,阻塞的事务越来越多,事务又占有数据库连接,可用的数据库连接数越来越少,当未来的某一天所有的数据库连接都在执行这条更新语句的时候,就出现了这种问题。

2、update不走索引,为什么会锁表?

数据库的事务隔离级别是“可重复读”。这个隔离级别下,多个事务并发的时候,会出现幻读的问题,因此 InnoDB 通过 next-key 锁(记录锁和间隙锁)来解决幻读现象。当我们执行 update 语句时,实际上是会对记录加排它锁(X锁)的,其他事务对持有排它锁的记录进行修改时会被阻塞,而且这个锁并不是执行完 update 语句就会释放,而是会等事务结束时才释放。

在 InnoDB 事务中,对记录加锁的基本单位是 next-key 锁,但是会因为一些条件退化成间隙锁,或者记录锁。加锁的位置准确的说是加在索引上的而不是记录行上。

在 update 语句的 where 条件使用了索引,那么 next-key 锁会退化成记录锁,也就是只会给一行记录加锁。

在 update 语句的 where 条件没有使用索引,就会全表扫描,于是就会对所有记录加上 next-key 锁(记录锁 + 间隙锁),相当于把整个表锁住了。那么锁就会持续很长一段时间,直到事务结束。

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
MySQl语句 show databases; 打开数据库 create database 数据库名; 新建数据库 use 数据库名; 使用数据库 show tables; 打开所有表 desc 表名 查看表结构 常用Mysql语句 1.插入字段 insert into 表名(表里字段,不加括号表示插入所有字段) values (对应的字段值); 2.查看表记录 select *(或字段名) from 表名; 3.更新记录 update 表名 set 字段名 = 字段值 where 要判断的字段名 = 要判断的字段值; 如果你要进行判断的字段值为null 要写 update 表名 set 字段名 = 字段值 where 要判断的字段名 is null; 4.删除记录 delete from 表名 where 要删除的字段名 = 要删除的字段值; 1.加载驱动 Class.ForName(com.mysql.jdbc.Driver); 2.建立连接 /* String url = "jdbc:mysql://localhost:3306/student"; //3306指你当前数据库的端口号,student指你指定的哪个数据库名字 String user = "root"; //root是你的用户名 String password = "esoft"; //esoft你的数据库密码 */ connection conn = DriverManager.getconnection(url,user,password); 3.创建对象 //create object Statement ste = conn.createStatement(); 4.执行语句 // 对数据库进行插入,更新,删除等操作时用executeUpdate()返回int型。 int i = ste.executeUpdate(); // 对数据库进行查看操作时用executeQuery()返回ResultSet结果集。 ResultSet res = ste.executeQuery(); 5.处理结果 当是返回int型时 if(i > 0){ System.out.println("操作成功!"); } 当返回ResultSet型结果集时 while(res.next()){ Student stu = new Student(); stu.setSno(res.getString("Sno")); stu.setSname(res.getString("Sname")); stu.setId(res.getInt("id")); stu.setSage(res.getInt("Sage")); list.add(stu); } return list; 6.关闭连接 当是返回int型时只需关闭 ste.close(); conn.close(); 当是返回ResultSet型结果集时需关闭 res.close(); ste.close(); conn.close(); Oracle数据库 数据库4种语言 1. DCL:数据控制语言。授权 2. DDL:数据定义语言。创建、修改数据库对象 3. DML:数据操纵语言。对数据库的增、删、改、查。 4.数据库事物处理语言 常用sql语句 1.插入字段 insert into 表名(表里字段,不加括号表示插入所有字段) values (对应的字段值); 2.查看表记录 select *(或字段名) from 表名; 3.更新记录 update 表名 set 字段名 = 字段值 where 要判断的字段名 = 要判断的字段值; 如果你要进行判断的字段值为null 要写 update 表名 set 字段名 = 字段值 where 要判断的字段名 is null; 4.删除记录 delete from 表名 where 要删除的字段名 = 要删除的字段值; 5.连接运算符 (||) select 字段名||'名字' from 表名; 6.去除重复行 (distinct) select distinct 字段名 from 表名; 7.升序排序 select 字段名 from 表名 order by ; 8.降序排序 select 字段名 from 表名 order by desc; 9.在……和……之间 (between and) select 字段名 from 表名 where 要判断的字段名 between 数值 and 数值; 10.in (相当于or) not in(相当于and) select 字段名 from 表名 where 字段名 in(数值,数值); select 字段名 from 表名 where 字段名 not in(数值,数值); 11.模糊查询 like %:0个或多个任意字符 _:1个任意字符 select 字段名 from 表名 where 要判断的字段 like '%字符%'; 12.null的处理 select 字段名 from 表名 where 要判断的字段名 is null(is not null); 13.dual表是用来计算的表 可以用来round:四舍五入,trunc:截断小数位,mod取余,ceil:求大于等于某数的最小整数。例:ceil(2.35) 3,floor:求小于等于某数的最大整数。例:floor(2.35) 2。 select round(123.123,要保留的位数) from dual; select trunc(213.231,要保留的位数) from dual; 14.字符函数 substr:截取字符串 Oracle当中substrate()函数的截取的索引从0或从1开始都可以。 Initcap:字符的首字母大写 Lpad:向字符串的左侧添加字符 Rpad:向字符串的右侧添加字符 Instr:是否包含某字符,从第几个字符开始,第几次出现的位置。 Replace:用一个字符串替换另一个字符串中的字符 15.日期函数 sysdate:当前日期 months_between(日期值1,日期值2):求出给定日期范围的月数 add_months(日期值,给定的月数):在制定日期上加上指定的月数,求出之后的日期 next_day(日期值,星期):指定日期后的星期对应的新日期 last_day(日期值):返回指定日期所在月份的最后一天 日期进行加减的一些规律: 日期-数字 = 日期 日期+数字 = 日期 日期-日期 = 数字(天数) 16.转换函数 (1).to_char:转换成字符串 a. 日期转换..............................后面文档自己下载

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值