某日,在讨论解决生产环境的问题时,一同事问说增加条件的Update语句效率是否更高?虽然我当时就有了自己的判断,但本着严谨的态度,我还是编写了测试代码,对MySQL中Update的执行效率进行测试及验证。
2、解决思路
1、根据生产环境的情况,模拟不同数据量的测试数据,分别模拟以下数据量的数据进行比较
1000 | 2000 | 5000 |
10000 | 20000 | 50000 |
100000 | 200000 | 500000 |
1000000 | 2000000 | 5000000 |
2、测试对比增加条件和不加条件两种的Update语句的执行时间
3、解决过程
3.1 数据表名
不同数据量存放在不同的数据表中,表名规则体现数据量,代码如下
// 根据数据量生成表名 private String getTableName(int count) { return "call_charge_" + count; }
3.2 删除数据表
// 删除数据表 private void dropTable(String tableName) { String dropTableSql = "DROP TABLE IF EXISTS `" + tableName + "` "; jdbcTemplate.execute(dropTableSql); }
3.3 创建数据表
// 创建数据表 private void createTable(String tableName) { String createTableSql = "CREATE TABLE `" + tableName + "` ("; createTableSql += "`caller_flag` int(11) NOT NULL,"; createTableSql += "`call_type` int(11) NOT NULL,"; createTableSql += "`e164no` char(64) NOT NULL,"; createTableSql += "`conf_begin_time` int(11) NOT NULL,"; createTableSql += "`begin_time` int(11) NOT NULL,"; createTableSql += "`end_time` int(11) NOT NULL,"; createTableSql += "`correct_begin_time` int(11) NOT NULL,"; createTableSql += "`correct_end_time` int(11) NOT NULL,"; createTableSql += "`correct_begin_time_string` varchar(32) NOT NULL,"; createTableSql += "`correct_end_time_string` varchar(32) NOT NULL,"; createTableSql += "`opposite_call_type` int(11) NOT NULL,"; createTableSql += "`opposite_e164no` char(64) NOT NULL,"; createTableSql += "`opposite_conf_begin_time` int(11) NOT NULL,"; createTableSql += "`company_guid` varchar(32) NOT NULL,"; createTableSql += "`band_width` int(11) NOT NULL,"; createTableSql += "`roam_flag` int(11) NOT NULL,"; createTableSql += "`out_flag` int(11) NOT NULL,"; createTableSql += "`incoming_flag` int(11) NOT NULL,"; createTableSql += "`csu_guid` varchar(32) NOT NULL,"; createTableSql += "KEY `correct_begin_time` (`correct_begin_time`),"; createTableSql += "KEY `company_guid` (`company_guid`)"; createTableSql += ") ENGINE=InnoDB DEFAULT CHARSET=utf8"; jdbcTemplate.execute(createTableSql); }
3.4 初始化数据
// 初始化数据 private void initData(String tableName, int count) { String insertSql = "insert into `" + tableName + "` "; insertSql += "(`caller_flag`, `call_type`, `e164no`, `conf_begin_time`, `begin_time`, `end_time`, "; insertSql += "`correct_begin_time`, `correct_end_time`, `correct_begin_time_string`, `correct_end_time_string`, "; insertSql += "`opposite_call_type`, `opposite_e164no`, `opposite_conf_begin_time`, `company_guid`, "; insertSql += "`band_width`, `roam_flag`, `out_flag`, `incoming_flag`, `csu_guid`) "; insertSql += "values('1','2','051211#999548','1420075106','1420075107','1420075188','1420066067','1420066148',"; insertSql += "'2015-01-01 06:47:47','2015-01-01 06:49:08','1','0512114880046','2147483647','','960','0',"; insertSql += "'0','0','%s')"; for (int i = 0; i < count; i++) { if (i % 3 == 0) { jdbcTemplate.execute(String.format(insertSql, CSU_GUID1)); } else { jdbcTemplate.execute(String.format(insertSql, CSU_GUID2)); } } }
3.5 复制数据
// 复制数据 private void copyData(String templateTableName, int count, int step) { String createSql = "CREATE TABLE `" + getTableName(count) + "` SELECT * FROM " + templateTableName; jdbcTemplate.execute(createSql); String copySql = "INSERT INTO `" + getTableName(count) + "` SELECT * FROM " + templateTableName; for (int i = step; i < count; i += step) { jdbcTemplate.update(copySql); } }
3.6 公用测试代码
private String CSU_GUID1 = "10103000000006000000000000000001"; private String CSU_GUID2 = "10103000000006000000000000000002"; private String CSU_GUID3 = "10103000000006000000000000000003"; // 公用单元测试方法 private void doTest(String templateTableName, int count, int step) { dropTable(getTableName(count)); copyData(templateTableName, count, step); doTest(count); } // 测试加条件和不加条件的update private void doTest(int count) { long startTime = System.currentTimeMillis(); String updateSql = "update " + getTableName(count) + " set csu_guid = '" + CSU_GUID3 + "' where csu_guid != '" + CSU_GUID1 + "'"; jdbcTemplate.update(updateSql); long endTime = System.currentTimeMillis(); System.out.println(String.format("测试数据[%d]过滤Update=%d", count, endTime - startTime)); startTime = System.currentTimeMillis(); updateSql = "update " + getTableName(count) + " set csu_guid = '" + CSU_GUID2 + "'"; jdbcTemplate.update(updateSql); endTime = System.currentTimeMillis(); System.out.println(String.format("测试数据[%d]直接Update=%d", count, endTime - startTime)); }
3.7 Junit测试
@Test public void prepareData() { dropTable("call_charge_data"); createTable("call_charge_data"); initData("call_charge_data", 100); } @Test public void create1000() { doTest("call_charge_data", 1000, 100); } @Test public void create2000() { doTest(getTableName(1000), 2000, 1000); } @Test public void create5000() { doTest(getTableName(1000), 5000, 1000); } @Test public void create10000() { doTest(getTableName(1000), 10000, 1000); } @Test public void create20000() { doTest(getTableName(10000), 20000, 10000); } @Test public void create50000() { doTest(getTableName(10000), 50000, 10000); } @Test public void create100000() { doTest(getTableName(10000), 100000, 10000); } @Test public void create200000() { doTest(getTableName(100000), 200000, 100000); } @Test public void create500000() { doTest(getTableName(100000), 500000, 100000); } @Test public void create1000000() { doTest(getTableName(100000), 1000000, 100000); } @Test public void create2000000() { doTest(getTableName(1000000), 2000000, 1000000); } @Test public void create5000000() { doTest(getTableName(1000000), 5000000, 1000000); }
3.8 测试结果
-------------------------- Test[com.hero.test.update.TestMysqlUpdate.create1000] start... 测试数据[1000]过滤Update=30 测试数据[1000]直接Update=135 Test[com.hero.test.update.TestMysqlUpdate.create1000] end... -------------------------- Test[com.hero.test.update.TestMysqlUpdate.create10000] start... 测试数据[10000]过滤Update=225 测试数据[10000]直接Update=852 Test[com.hero.test.update.TestMysqlUpdate.create10000] end... -------------------------- Test[com.hero.test.update.TestMysqlUpdate.create100000] start... 测试数据[100000]过滤Update=7883 测试数据[100000]直接Update=43920 Test[com.hero.test.update.TestMysqlUpdate.create100000] end... -------------------------- Test[com.hero.test.update.TestMysqlUpdate.create1000000] start... 测试数据[1000000]过滤Update=26170 测试数据[1000000]直接Update=460927 Test[com.hero.test.update.TestMysqlUpdate.create1000000] end... -------------------------- Test[com.hero.test.update.TestMysqlUpdate.create2000] start... 测试数据[2000]过滤Update=53 测试数据[2000]直接Update=657 Test[com.hero.test.update.TestMysqlUpdate.create2000] end... -------------------------- Test[com.hero.test.update.TestMysqlUpdate.create20000] start... 测试数据[20000]过滤Update=329 测试数据[20000]直接Update=2025 Test[com.hero.test.update.TestMysqlUpdate.create20000] end... -------------------------- Test[com.hero.test.update.TestMysqlUpdate.create200000] start... 测试数据[200000]过滤Update=10867 测试数据[200000]直接Update=95978 Test[com.hero.test.update.TestMysqlUpdate.create200000] end... -------------------------- Test[com.hero.test.update.TestMysqlUpdate.create2000000] start... 测试数据[2000000]过滤Update=42915 测试数据[2000000]直接Update=944865 Test[com.hero.test.update.TestMysqlUpdate.create2000000] end... -------------------------- Test[com.hero.test.update.TestMysqlUpdate.create5000] start... 测试数据[5000]过滤Update=122 测试数据[5000]直接Update=1449 Test[com.hero.test.update.TestMysqlUpdate.create5000] end... -------------------------- Test[com.hero.test.update.TestMysqlUpdate.create50000] start... 测试数据[50000]过滤Update=11848 测试数据[50000]直接Update=14608 Test[com.hero.test.update.TestMysqlUpdate.create50000] end... -------------------------- Test[com.hero.test.update.TestMysqlUpdate.create500000] start... 测试数据[500000]过滤Update=14463 测试数据[500000]直接Update=222890 Test[com.hero.test.update.TestMysqlUpdate.create500000] end... -------------------------- Test[com.hero.test.update.TestMysqlUpdate.create5000000] start... 测试数据[5000000]过滤Update=90726 测试数据[5000000]直接Update=2322410 Test[com.hero.test.update.TestMysqlUpdate.create5000000] end... -------------------------- Test[com.hero.test.update.TestMysqlUpdate.prepareData] start... Test[com.hero.test.update.TestMysqlUpdate.prepareData] end...
整理后的测试结果如下所示
数据量 | 加条件的执行时间(ms) | 不加条件的执行时间(ms) | 单元测试执行时间(s) |
1000 | 30 | 135 | 5.103 |
2000 | 53 | 657 | 7.096 |
5000 | 122 | 1449 | 13.244 |
10000 | 225 | 852 | 9.859 |
20000 | 329 | 2025 | 26.353 |
50000 | 11848 | 14608 | 68.743 |
100000 | 7883 | 43920 | 102.544 |
200000 | 10867 | 95978 | 232.576 |
500000 | 14463 | 222890 | 560.074 |
1000000 | 26170 | 460927 | 1018.023 |
2000000 | 42915 | 944865 | 2052.403 |
5000000 | 90726 | 2322410 | 5145.207 |
特别说明:
测试结果,因机器及机器的工作状态而不同
4、总结
从测试结果的对比来看,增加条件的Update的执行效率明显比不加条件的Update的执行更短,效率也就更好
5、附录
1、测试基类BaseTest
import org.junit.After; import org.junit.Before; import org.junit.Rule; import org.junit.rules.TestName; import org.junit.runner.RunWith; import org.springframework.test.context.ContextConfiguration; import org.springframework.test.context.junit4.SpringJUnit4ClassRunner; /** * Junit测试基类 * @author [email protected] * @date 2014-7-28 下午5:27:34 * @version 1.0.0 */ // 使用@RunWith(SpringJUnit4ClassRunner.class),才能使测试运行于Spring测试环境 @RunWith(SpringJUnit4ClassRunner.class) // @ContextConfiguration 注解有以下两个常用的属性: // locations:可以通过该属性手工指定 Spring 配置文件所在的位置,可以指定一个或多个 Spring 配置文件 // inheritLocations:是否要继承父测试类的 Spring 配置文件,默认为 true // 如果只有一个配置文件就直接写locations=“配置文件路径+名” @ContextConfiguration(locations = "classpath:applicationContext.xml") public class BaseTest { @Rule public TestName name = new TestName(); @Before public void before() { System.out.println("--------------------------"); System.out.println("Test[" + getClassName() + "." + getMethodName() + "] start..."); } @After public void after() { System.out.println("Test[" + getClassName() + "." + getMethodName() + "] end..."); } private String getClassName() { return getClass().getName(); } private String getMethodName() { return name.getMethodName(); } }
2、Spring配置文件applicationContext.xml
<?xml version="1.0" encoding="UTF-8"?> <beans xmlns="http://www.springframework.org/schema/beans" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xsi:schemaLocation="http://www.springframework.org/schema/beans http://www.springframework.org/schema/beans/spring-beans-3.0.xsd"> <!-- 配置数据源 --> <bean id="dataSource" class="org.springframework.jdbc.datasource.DriverManagerDataSource"> <property name="driverClassName" value="com.mysql.jdbc.Driver" /> <property name="url" value="jdbc:mysql://localhost:3306/test?useUnicode=true&characterEncoding=UTF-8" /> <property name="username" value="root" /> <property name="password" value="" /> </bean> <!-- 配置Spring JdbcTemplate --> <bean id="jdbcTemplate" class="org.springframework.jdbc.core.JdbcTemplate"> <property name="dataSource"> <ref bean="dataSource" /> </property> </bean> </beans>