从毕业到现在第一次接触到超过30万条数据导入MySQL的场景(有点low),就是在顺丰公司接入我司EMM产品时需要将AD中的员工数据导入MySQL中,因此楼主负责的模块connector就派上了用场。在楼主的努力下,线上数据同步代码经历了从最初的将近16个小时(并且还出现其他问题这些问题,等后面慢慢细说),到最终25分钟的性能优化。

打个广告,楼主自己造的轮子,感兴趣的请点https://github.com/haifeiWu/lightconf

代码直接Jenkins打包上线

楼主负责的connector模块之前经历过的最大的数据量也仅仅是几千条,当然面对几千条数据代码也是跑的及其的快,没有啥影响,然而当第一次在顺丰的正式环境上线时,由于数据量比较大,楼主的代码又是串行执行的,事务保持的时间就相当长,也就因此出现了下面的错误信息:

Lock wait timeout exceeded; try restarting transaction

这里来说一下报这个错的解决方案,查看MySQL是否有锁

show OPEN TABLES where In_use > 0;

另外,在information_schema下面有三张表:INNODB_TRX、INNODB_LOCKS、INNODB_LOCK_WAITS(解决问题方法),通过这三张表,可以更简单地监控当前的事务并分析可能存在的问题。

比较常用的列:

  • trx_id: InnoDB存储引擎内部唯一的事物ID
  • trx_status: 当前事务的状态
  • trx_status: 事务的开始时间
  • trx_requested_lock_id: 等待事务的锁ID
  • trx_wait_started: 事务等待的开始时间
  • trx_weight: 事务的权重,反应一个事务修改和锁定的行数,当发现死锁需要回滚时,权重越小的值被回滚
  • trx_mysql_thread_id: MySQL中的进程ID,与show processlist中的ID值相对应
  • trx_query: 事务运行的SQL语句

kill 进程ID,发生上面错误的根本原因在业务逻辑代码对数据库的操作无视了大数据量的情况,比如当数据量比较大时就会出现刚刚修改完这条记录,接着再次修改就会出现上述出现的问题。

代码优化过程

使用线程池,并发执行,提高效率

由于数据量比较大,首先想到的方法是拿到数据后将数据分拆成n份,由多个线程并发执行数导入的操作。由此引出多线程的问题,在处理多线程问题时共享的数据结构像Map,List应该采用jdk提供的current包下的数据结构,另外在涉及到操作数据库的地方应该加锁,楼主用的是jdk提供的ReentrantLock。使用jdk自带的 jvisualvm,进行代码的监控进而找到最佳线程数,下面是监控的数据,



下面是线程池的创建代码

ThreadFactory namedThreadFactory = new ThreadFactoryBuilder()
.setNameFormat("demo-pool-%d").build();
ExecutorService singleThreadPool = new ThreadPoolExecutor(1, 1,0L, TimeUnit.MILLISECONDS,
new LinkedBlockingQueue<Runnable>(1024), namedThreadFactory, new ThreadPoolExecutor.AbortPolicy()); singleThreadPool.execute(()-> System.out.println(Thread.currentThread().getName()));
singleThreadPool.shutdown();

果不其然,在使用多线程之后,数据插入效率由原来的十几个小时降到了三个小时,然后并没有达到我们的预期效果,我们继续。

使用druid监控发现问题的SQL

  • 配置druid监控,在应用的web.xml添加配置,并放开/druid的拦截
<!-- druid 数据库监控 -->
<filter>
<filter-name>DruidWebStatFilter</filter-name>
<filter-class>com.alibaba.druid.support.http.WebStatFilter</filter-class>
<init-param>
<param-name>exclusions</param-name>
<param-value>*.js,*.gif,*.jpg,*.png,*.css,*.ico,*.jsp,/druid/*,/download/*</param-value>
</init-param>
<init-param>
<param-name>sessionStatMaxCount</param-name>
<param-value>2000</param-value>
</init-param>
<init-param>
<param-name>sessionStatEnable</param-name>
<param-value>true</param-value>
</init-param>
<init-param>
<param-name>principalSessionName</param-name>
<param-value>session_user_key</param-value>
</init-param>
<init-param>
<param-name>profileEnable</param-name>
<param-value>true</param-value>
</init-param>
</filter>
<filter-mapping>
<filter-name>DruidWebStatFilter</filter-name>
<url-pattern>/*</url-pattern>
</filter-mapping> <servlet>
<servlet-name>DruidStatView</servlet-name>
<servlet-class>com.alibaba.druid.support.http.StatViewServlet</servlet-class>
<!--<init-param>
<param-name>allow</param-name>
<param-value>*.*.*.*</param-value>
</init-param>-->
<init-param>
<!-- 允许清空统计数据 -->
<param-name>resetEnable</param-name>
<param-value>true</param-value>
</init-param>
<init-param>
<!-- 用户名 -->
<param-name>loginUsername</param-name>
<param-value>druid</param-value>
</init-param>
<init-param>
<!-- 密码 -->
<param-name>loginPassword</param-name>
<param-value>druid</param-value>
</init-param>
</servlet>
<servlet-mapping>
<servlet-name>DruidStatView</servlet-name>
<url-pattern>/druid/*</url-pattern>
</servlet-mapping>
  • 配置数据库连接池
<!-- 数据源配置, 使用 BoneCP 数据库连接池 -->
<bean id="dataSource" class="com.alibaba.druid.pool.DruidDataSource" init-method="init" destroy-method="close">
<!-- 数据源驱动类可不写,Druid默认会自动根据URL识别DriverClass -->
<property name="driverClassName" value="${jdbc.driver}" /> <!-- 基本属性 url、user、password -->
<property name="url" value="${jdbc.url}" />
<property name="username" value="${jdbc.username}" />
<property name="password" value="${jdbc.password}" /> <!-- 配置初始化大小、最小、最大 -->
<property name="initialSize" value="${jdbc.pool.init}" />
<property name="minIdle" value="${jdbc.pool.minIdle}" />
<property name="maxActive" value="${jdbc.pool.maxActive}" /> <!-- 配置获取连接等待超时的时间 -->
<property name="maxWait" value="60000" /> <!-- 配置间隔多久才进行一次检测,检测需要关闭的空闲连接,单位是毫秒 -->
<property name="timeBetweenEvictionRunsMillis" value="60000" /> <!-- 配置一个连接在池中最小生存的时间,单位是毫秒 -->
<property name="minEvictableIdleTimeMillis" value="300000" /> <property name="validationQuery" value="${jdbc.testSql}" />
<property name="testWhileIdle" value="true" />
<property name="testOnBorrow" value="true" />
<property name="testOnReturn" value="false" /> <!-- 配置监控统计拦截的filters -->
<property name="filters" value="stat" />
</bean>
  • 访问http://ip/xxx/druid,输入用户名,密码登录就会看到下面的图

  • 使用druid的SQL监控发现问题SQL语句,优化SQL

通过命令查看程序的gc情况

通过命令jstat -gc pid 来查看程序的gc情况,下面是楼主程序数据同步完成之后的gc情况



简单说明:

这里写代码片S0C、S1C、S0U、S1U:Survivor 0/1区容量(Capacity)和使用量(Used)
EC、EU:Eden区容量和使用量
OC、OU:年老代容量和使用量
PC、PU:永久代容量和使用量
YGC、YGT:年轻代GC次数和GC耗时
FGC、FGCT:Full GC次数和Full GC耗时

小结

通过上面的优化过程,楼主的connector的数据同居效率也由小时级下降到了分钟级,同步30+万的时间可以在25分钟内完成。经此一役,楼主算是收获满满。抱着虔诚的心态学习,不浮不躁,快乐成长。

参考文章

复杂业务下向Mysql导入30万条数据代码优化的踩坑记录的更多相关文章

  1. mysql快速导入5000万条数据过程记录(LOAD DATA INFILE方式)

    mysql快速导入5000万条数据过程记录(LOAD DATA INFILE方式) 首先将要导入的数据文件top5000W.txt放入到数据库数据目录/var/local/mysql/data/${d ...

  2. 教你如何6秒钟往MySQL插入100万条数据!然后删库跑路!

    教你如何6秒钟往MySQL插入100万条数据!然后删库跑路! 由于我用的mysql 8版本,所以增加了Timezone,然后就可以了 前提是要自己建好库和表. 数据库test, 表user, 三个字段 ...

  3. Java 线程池 +生产者消费者+MySQL读取300 万条数据

    1.1需求 数据库300 万条用户数据 ,遍历获取所有用户, 各种组合关联, 获取到一个新的json ,存到redis 上. 1.2 难点 数据库比较多, 不可能单线程查询所有的数据到内存. 1.3解 ...

  4. PHP MySQL 快速导入10万条数据

    项目背景 数据来源:所有数据均为外部导入,最大数据量在10w+ 输出数据:导出经过业务处理之后的数据 使用框架:fastadmin 涉及的问题: 1.数据读取 2.数据保存 使用数据:10w+ 解决方 ...

  5. mysql生成20万条数据(连表插入)

    创建一个存储过程 DELIMITER $$ -- 设置定界符为$$,与';'意思相同,防止相同符号产生冲突 USE `yunkc_base1`$$ -- 使用数据库 DROP PROCEDURE IF ...

  6. mysql插入一万条数据

    定义一个存储过程 mysql> delimiter $$ mysql> create procedure ptest()    -> begin    -> declare p ...

  7. 用Python3生成30万条excel数据(xlsx格式)

    在B/S架构的系统测试中,有时需要通过导入excel文件来生成一些数据记录,当数据量小的时候,一般不会出现什么问题,而当导入的数据量巨大时,对系统的性能就是一个考验了.为了验证系统的性能,有时需要导入 ...

  8. 1000万条数据导入mysql

    今天需要将一个含有1000万条数据的文本内容插入到数据库表中,最初自然想到的是使用Insertinto '表名'values(),(),()...这种插入方式,但是发现这种方式对1000万条数据量的情 ...

  9. 极限挑战—C#100万条数据导入SQL SERVER数据库仅用4秒 (附源码)

    原文:极限挑战-C#100万条数据导入SQL SERVER数据库仅用4秒 (附源码) 实际工作中有时候需要把大量数据导入数据库,然后用于各种程序计算,本实验将使用5中方法完成这个过程,并详细记录各种方 ...

随机推荐

  1. 杂项-Java:JCP

    ylbtech-杂项-Java:JCP JCP(Java Community Process) 是一个开放的国际组织,主要由Java开发者以及被授权者组成,职能是发展和更新. 1. 中文名:jcp 外 ...

  2. android欢迎页

    在进入程序后,会在一个页面停留几秒然后自动跳转到程序主界面,这个页面就是欢迎页. 首先新建个java文件,给他起名叫做WelcomeActivity 在里面我们通过一个匿名类new Handler,在 ...

  3. PL/SQL 训练08--触发器

    --什么是触发器呢?--一触即发,某个事件发生时,执行的程序块?--数据库触发器是一个当数据库发生某种事件时作为对这个事件的响应而执行的一个被命名的程序单元 --适合场景--对表的修改做验证--数据库 ...

  4. GY89的使用

    GY89集成了三块不同的芯片,分别为:BMP180.L3GD20和LSM303DLH,作用分别是获取温度压强.三轴陀螺仪和加速度计的数据.以下通过把各个模块的数据输出到终端来测试GY89的功能. #i ...

  5. 循序渐进Python3(十三) --0-- django之form表单

    django为我们提供了form表单验证功能,下面来学习一下: 武sir博客:http://www.cnblogs.com/wupeiqi/articles/5246483.html  创建了djan ...

  6. PHP 乘法口诀表

    echo "乘法口诀表<br>"; for($i=1;$i<10;$i++) { for ($j = 1; $j <= $i; $j++) printf(& ...

  7. export LD_LIBRARY_PATH 的使用

    对linux不是很熟,之前只是听说过可以设置程序共享库位置也就是 使用 “export LD_LIBRARY_PATH” 今天用了用,感觉还挺不错,也很常用. 比如你编译了一个so 而这个so 同时又 ...

  8. Java多线程-新特征-阻塞栈LinkedBlockingDeque

    对于阻塞栈,与阻塞队列相似.不同点在于栈是“后入先出”的结构,每次操作的是栈顶,而队列是“先进先出”的结构,每次操作的是队列头. 这里要特别说明一点的是,阻塞栈是Java6的新特征.. Java为阻塞 ...

  9. codeforce 457DIV2 C题

    题意 你需要构造一个n个点m条边的无向有权图,要求这个图的MST中边权的和与从1到n的最短路长度都为素数 分析 可以想到这样一种贪心,在i到i+1直接连一条边,这样最短路和MST都会是同样的一些边.只 ...

  10. 高性能的城市定位API接口

    如果不需要精准的定位,还有一种通过IP地址获取当前城市的方法,采用新浪的api接口. <script src="http://int.dpool.sina.com.cn/iplooku ...