数据库实战案例—————记一次TempDB暴增的问题排查

前言

  很多时候数据库的TempDB、日志等文件的暴增可能导致磁盘空间被占满,如果日常配置不到位,往往会导致数据库故障,业务被迫中断。

  这种文件暴增很难排查,经验不足的一些运维人员可能更是无法排查具体原因,导致问题不能彻底解决。

场景描述

  客户系统比较稳定,用了5台机器做了AlwaysOn高可用组,完全实现了读写分离。磁盘也做了规划,主库日常操作TempDB需求在20G以下,所以TempDB所在的磁盘只配置了100个G的空间。

  本案例是客户突然接到监控报警,显示TempDB磁盘空间不足,可用空间不断减小直到耗尽。

  比较戏剧的是,这个客户早上刚刚做了巡检数据库情况稳定,没有什么异常。

  那么我初步判定,这必然是一次特殊操作或应用配置出错导致的问题。

深入指标分析

  文件看问题

  TempDB暴增必然伴随着文件的增长,首先我们看一下TempDB文件的增长情况。

  数据库实战案例—————记一次TempDB暴增的问题排查

 

  可见TempDB的分配空间在14点50几分的时候开始暴增,细心的朋友会发现这是1个G到6个G的增长,这是因为客户的TempDB配置了16个数据文件:

  

  数据库实战案例—————记一次TempDB暴增的问题排查

 

  注:为什么配置这么多TempDB文件请参见:Expert 诊断优化系列——————给TempDB 降温

什么造成了增长

  造成TempDB暴增原因很多语句使用临时表、语句排序、CheckDB等,但这些都是可以在语句中反应出来。所以下面我们分析一下语句。

  注:很多使用过SQL专家云平台或工具的朋友可能不会注意里面一些细节,其实很多细节(如上面的TempDB文件增长趋势和下面的语句分配空间)的设计都是解决一些疑难的问题。

  

  首先语句中的其中两个指标用户分配空间(MB)和内部对象分配空间(MB)指的就是对TempDB的使用消耗。

  注:用户分配空间可能是临时表使用的比较多,内部对象分配空间可能是排序或者hash join等操作(其他使用的消耗可以参见前面给出的链接文章)

  数据库实战案例—————记一次TempDB暴增的问题排查

  分配空间越大,也就说明语句越消耗TempDB资源。

  我们有两种方式找到到底是什么操作导致的TempDB暴增,可以直接找时间点的语句,也可以在TempDB资源消耗的高到低顺序中找!

  为了全面了解一下TempDB的问题,这里我们采用第二种方式。

  那么我们就分析一下语句对TempDB资源被消耗的情况:

  步骤1:首先我们按照用户对象分配空间排序:数据库实战案例—————记一次TempDB暴增的问题排查

 

 经过排查,这里面用户空间分配比较高的都是CDC的作业,服务器上确实运行这几个库的CDC作业。其他的一些操作的用户分配空间都比较小,所以这不是造成问题的原因!

 

 步骤2:接着我们按照内部对象分配空间排序:

 数据库实战案例—————记一次TempDB暴增的问题排查

 

 这里发现最消耗空间的是CheckDB的操作,但时间点是对应不上的,所以这也不是问题的原因。

 继续排查:

 在消耗排位在第三的这个语句中我们发现了等待资源FGCB_ADD_REMOVE(这个可以简单理解为有大量的文件自动生长发生,这里是16个TempDB文件),并且使用的内部对象空间也很高,并且我们还发现有多个会话同时执行这样的高消耗操作。数据库实战案例—————记一次TempDB暴增的问题排查

 

 继续深入:

 数据库实战案例—————记一次TempDB暴增的问题排查

  性能计数器的表象也与之前的种种迹象相吻合。

  数据库实战案例—————记一次TempDB暴增的问题排查

 

排查结论

  综合各项现象指标,可以分析出系统在下午14点57分左后开始执行TempDB高消耗操作,语句本身是一个近千行涉及大量表连接排序等操作的复杂存储过程,对TempDB造成暴增的问题负主要责任,而且雪上加霜的是从会话的标识可以看出,这不是一个语句只一次的执行,而是在特定的时间存在比较大的并发操作导致。

  与相关业务人员沟通,发现这是一个集团的类似报表的大消耗操作,因为对功能进行的调整,而程序人员的一次误操作而错误的指向了集群中的主库,而导致的问题。

 

————–博客地址—————————————————————————–

原文地址: http://www.cnblogs.com/double-K/

如有转载请保留原文地址! 

 

 —————————————————————————————————-

 总结

  问题的结果往往比较简单,也相对容易解决,但综合各项指标深入分析问题原因是值得和每一个技术人员探讨的,这也是为什么用一篇长案例来分析一个小点的原因。

  

  再思考:本案例中服务器的架构设计是比较完善的,已经做了读写分离可以轻松的把这样的大操作指向辅助服务器,并且可以做到负载均衡,那么如果你的单机服务器也有类似这样一个报表操作,你会怎么办呢?

 —————————————————————————————————-

注:此文章为原创,欢迎转载,请在文章页面明显位置给出此文链接!
若您觉得这篇文章还不错请点击下右下角的推荐,非常感谢!

  

java IO流 之 字节输出流 OutputString()

Java学习重点之一:OutputStream 字节输出流的使用

FileOutPutStream:子类,写出数据的通道

  步骤:

    1.获取目标文件

    2.创建通道(如果原来没有目标文件,则会自动创建一个)

    3.写入数据 write()

    4.释放资源

  注意:

    (1)如果目标文件不存在,那么会自己创建一个目标文件

    (2)如果目标文件存在,先将里面的数据清空,再写入数据

    (3)想在原有的数据上写入数据,则在创建通道的时候使用 构造方法:

       OutPutStreamFile fileBoolean append),boolean值为true则可以

    (4)用 writeint a)方法写入数据,虽然接收的是int,但实际上只有一个字节的数据

      (操作的是低八位的,其他的全部丢掉)

 

//会自动导入一些包

import java.io.File;
import java.io.FileOutputStream;
import java.io.IOException;
//方式一
1
public static void writeData() throws IOException{ 2 //1.找目标文件 3 File file = new File("C://Users//bigerf//Desktop//文件夹//writeTest.java"); 4 5 //2.创建一个通道 6 FileOutputStream outputStream = new FileOutputStream(file); 7 8 //3.开始写入数据, 9 int a = 10; // int 型 4个字节 10 outputStream.write(a); //注意这里每次只能输出一个字节 11 outputStream.write('b'); // char 类型 12 outputStream.write(5); 13 14 // 0000-0000 0000-0000 0000-0001 1111-1111 == 511 15 int b = 511 ; //大于八位(9位) 16 outputStream.write(b); //实际结果 255,但没有显示 17 18 int c = 63; //小于八位(6位) 19 outputStream.write(c); //乱码 20 21 //4.关闭资源 22 outputStream.close(); 23 }

 

 //方式二
1
public static void writeData2() throws IOException{ 2 //1.找目标文件 3 File file = new File("C://Users//bigerf//Desktop//文件夹//writeTest2.java"); 4 5 //2.创建一个通道,(如果不存在路径中的文件,则会在这一步创建的文件) 6 //new FileOutputStream(file,true); /true表示在原来文本的基础上写入文本(反之则会先清空再写入) 7 FileOutputStream outputStream = new FileOutputStream(file,true); 8 9 //3.创键一个字节数组 10 String str = "hello word"; 11 //将字符串变为字节数组 12 byte[] b = str.getBytes(); 13 14 //4.写入数据 15 outputStream.write(b); //hello word 16 17 //5.关闭资源 18 outputStream.close(); 19 20 }

陌陌说:

  输入流与输出流可以实现对文件的拷贝,不妨试着实现

  (先将路径文件的数据拷贝写入到字节数组,再从字节数组写出道路径文件)

java IO流 之 字节输入流 InputString()

学习java的重点之一:InputStream  字节输入流的使用

(1)FileInputstream: 子类,读取数据的通道

  使用步骤:

    1.获取目标文件:new File()

    2.建立通道:new FileInputString()

    3.读取数据:read()

    4.释放资源:close()

 

//一些默认要导入的包

import java.io.File;
import java.io.FileInputStream;
import java.io.IOException;

 

 

 

 1     public static void main(String[] args) throws IOException {
 2         // TODO Auto-generated method stub
 3               
 4         //分别调用方法查看效果
 5         test1();
 6         System.out.println("-------------------------------------------");
 7         test2();
 8         System.out.println("-------------------------------------------");
 9         test3();
10         System.out.println("-------------------------------------------");
11         test4();
12     }    

 

  (2)读取数据的三种方式

    1.直接读取 (一次只能一个字节)

      int date = fileInputStream.read();

      char date3 = (char)fileInputStream.read();

 

 1 //方式一    直接打印
 2     public static void test1() throws IOException{
 3         
 4         //(1)获取目标文件路径
 5         File file = new File("C://Users//joke//Desktop//Demo1.java");
 6     
 7         //(2)根据目标文件路径 建立通道: new FileInputStream(file)
 8         FileInputStream fileInputStream = new FileInputStream(file);
 9         
10         //(3)读取数据 :read();
11         int date = fileInputStream.read();//这里是int类型
12         int date2 = fileInputStream.read();//
13         char date3 = (char)fileInputStream.read(); //以char类型显示
14         System.out.println(date+"//"+date2+"//"+date3);
15         
16         //(4)释放资源
17         fileInputStream.close();
18     }

 

 

 

 

    2.单独使用for循环(效率低)

      for(int i = 0; i < file.length();i++){

        System.out.print((char)fileInputStream.read());

      }

 

 1 //方式二    循环遍历
 2     public static void test2() throws IOException{
 3         
 4         //通过时间测试效率
 5         long startTime = System.currentTimeMillis();
 6         
 7         File file = new File("C://Users//joke//Desktop//Demo1.java");
 8         FileInputStream fileInputStream = new FileInputStream(file);
 9         
10         //for循环
11         for(int i = 0; i < file.length();i++){
12             System.out.print((char)fileInputStream.read());
13         }
14         
15         fileInputStream.close();
16         
17         long endTime = System.currentTimeMillis();
18         System.out.println("读取文件所花时间:"+(endTime-startTime));
19     }

 

    3.Byte[ ] 缓冲区(只能读取指定的字节数不能读取一个完整的文件)

      byte[] bt = new byte[1024];

      int count = fileInputStream.read(bt);

      System.out.println(new String (bt,0,count));

 

 1 //方式三    创建缓冲区(只能读取制定的大小,不能读取一个完整的文件)
 2     public static void test3() throws IOException{
 3         
 4         
 5         File file = new File("C://Users//joke//Desktop//Demo1.java");
 6     
 7         FileInputStream fileInputStream = new FileInputStream(file);
 8         
 9         //创建缓冲区,加快读取数据,确定要读取的字节大小
10         byte[] bt = new byte[1024];
11         
12         //read() 读取字节
13         int count = fileInputStream.read(bt);
14         System.out.println(count); //显示读取到的字节数
15         System.out.println(new String (bt,0,count));//将字节转为字符串显示
16 
17         fileInputStream.close();
18     }

 

    4.缓冲区和循环结合。缓冲区一般设置为1024的倍数。理论上设置的缓冲区越大,读取效率越高

      byte[] bt = new byte[1024];

      int count = 0;

      while((count = fileInputStream.read(bt)) != -1){

        System.out.println(new String (bt,0,count));

      }

 1 //方式四    循环与缓冲区结合(效率高)
 2     public static void test4() throws IOException{
 3         
 4         //通过时间测试效率
 5         long startTime = System.currentTimeMillis();
 6         
 7         File file = new File("C://Users//joke//Desktop//Demo1.java");
 8     
 9         FileInputStream fileInputStream = new FileInputStream(file);
10         
11         //缓冲区一般设置为1024的倍数。理论上设置的缓冲区越大,读取效率越高
12         byte[] bt = new byte[1024];
13         
14         int count = 0;
15         //read返回 -1 时,证明已经遍历完
16         while((count = fileInputStream.read(bt)) != -1){
17             //字符串型显示(从bt中的第0个字节开始遍历count个长度)
18             System.out.println(new String (bt,0,count));
19         }
20         
21         fileInputStream.close();
22         
23         long endTime = System.currentTimeMillis();
24         System.out.println("读取文件所花时间:"+(endTime-startTime));
25     }    

 

陌陌说:

  在以上,对比第二个和第四个方法,会发现方法四的效率是比较高的,所以推荐使用的四个方法

  在这里我们是直接抛出异常,除了抛出之外我们还可以使用

    try{  }cater{  }finally{  }

  的方式来处理异常