MySql下大数据量级别(1000万+)优化查询和操作方法

一、【原则一】: insert into tb (...) values(...),(...)...; 要比 insert into tb (...) values (...);insert into tb (...) values (...);...方式批量插入效率高 【原因】:

这里第二种SQL执行效率高的主要原因是合并后日志量(MySQL的binlog和innodb的事务让日志) 减少了,降低日志刷盘的数据量和频率,从而提高效率。通过合并SQL语句,同时也能减少SQL语句解析的次数,减少网络传输的IO。

【例子】:  效率较高

public static void insert() {
	// 开时时间
	Long begin = new Date().getTime();
	// sql前缀
	String prefix = "INSERT INTO tb_big_data (count, create_time, random) VALUES ";
	try {
		// 保存sql后缀
		StringBuffer suffix = new StringBuffer();
		// 设置事务为非自动提交
		conn.setAutoCommit(false);
		// Statement st = conn.createStatement();
		// 比起st,pst会更好些
		PreparedStatement pst = conn.prepareStatement("");
		// 外层循环,总提交事务次数
		for (int i = 1; i <= 100; i++) {
			// 第次提交步长
			for (int j = 1; j <= 10000; j++) {
				// 构建sql后缀
				suffix.append("(" + j * i + ", SYSDATE(), " + i * j
						* Math.random() + "),");
			}
			// 构建完整sql
			String sql = prefix + suffix.substring(0, suffix.length() - 1);
			// 添加执行sql
			pst.addBatch(sql);
			// 执行操作
			pst.executeBatch();
			// 提交事务
			conn.commit();
			// 清空上一次添加的数据
			suffix = new StringBuffer();
		}
		// 头等连接
		pst.close();
		conn.close();
	} catch (SQLException e) {
		e.printStackTrace();
	}
	// 结束时间
	Long end = new Date().getTime();
	// 耗时
	System.out.println("cast : " + (end - begin) / 1000 + " ms");
}

二、【使用事务提交方式】 

【例子】:START TRANSACTION;  (INSERT INTO TABLE (....) VALUES(...));...COMMIT;

【原因】

使用事务可以提高数据的插入效率,这是因为进行一个INSERT操作时,MySQL内部会建立一个事务,在事务内才进行真正插入处理操作。通过使用事务可以减少创建事务的消耗,所有插入都在执行后才进行提交操作。

三、【数据有序的插入】

【例子】: 

INSERT INTO `insert_table` (`datetime`, `uid`, `content`, `type`) VALUES ('1', 'userid_1', 'content_1', 1); 
INSERT INTO `insert_table` (`datetime`, `uid`, `content`, `type`) VALUES ('0', 'userid_0', 'content_0', 0); 
INSERT INTO `insert_table` (`datetime`, `uid`, `content`, `type`) VALUES ('2', 'userid_2', 'content_2',2);
INSERT INTO `insert_table` (`datetime`, `uid`, `content`, `type`)  VALUES ('1', 'userid_1', 'content_1', 1);
INSERT INTO `insert_table` (`datetime`, `uid`, `content`, `type`)  VALUES ('0', 'userid_0', 'content_0', 0);
INSERT INTO `insert_table` (`datetime`, `uid`, `content`, `type`)  VALUES ('2', 'userid_2', 'content_2',2);

修改成:
INSERT INTO `insert_table` (`datetime`, `uid`, `content`, `type`) VALUES ('0', 'userid_0', 'content_0', 0); 
INSERT INTO `insert_table` (`datetime`, `uid`, `content`, `type`) VALUES ('1', 'userid_1', 'content_1', 1); 
INSERT INTO `insert_table` (`datetime`, `uid`, `content`, `type`) VALUES ('2', 'userid_2', 'content_2',2);
INSERT INTO `insert_table` (`datetime`, `uid`, `content`, `type`)  VALUES ('0', 'userid_0', 'content_0', 0);
INSERT INTO `insert_table` (`datetime`, `uid`, `content`, `type`)  VALUES ('1', 'userid_1', 'content_1', 1);
INSERT INTO `insert_table` (`datetime`, `uid`, `content`, `type`)  VALUES ('2', 'userid_2', 'content_2',2);

【原因】: 

由于数据库插入时,需要维护索引数据,无序的记录会增大维护索引的成本。我们可以参照innodb使用的B+tree索引,如果每次插入记录都在索引的最 后面,索引的定位效率很高,并且对索引调整较小;如果插入的记录在索引中间,需要B+tree进行分裂合并等处理,会消耗比较多计算资源,并且插入记录的 索引定位效率会下降,数据量较大时会有频繁的磁盘操作。、

 【注意事项】:
1. SQL语句是有长度限制,在进行数据合并在同一SQL中务必不能超过SQL长度限制,通过max_allowed_packet配置可以修改,默认是1M,测试时修改为8M。
2. 事务需要控制大小,事务太大可能会影响执行的效率。MySQL有innodb_log_buffer_size配置项,超过这个值会把innodb的数据刷到磁盘中,这时,效率会有所下降。所以比较好的做法是,在数据达到这个这个值前进行事务提交。

 

【千万级+以上数据量的查询优化方案】:

参考文献:mysql 如何提高批量导入的速度

一、【临时表的使用】:

建立两张测试表:

CREATE TABLE `department` (
  `dep_id` varchar(50) COLLATE utf8_unicode_ci DEFAULT NULL,
  `dep_name` varchar(50) COLLATE utf8_unicode_ci DEFAULT NULL
) ENGINE=InnoDB DEFAULT CHARSET=utf8 COLLATE=utf8_unicode_ci
CREATE TABLE `out_warehouse` (
  `dep_id` varchar(50) COLLATE utf8_unicode_ci DEFAULT NULL,
  `out_remark` varchar(50) COLLATE utf8_unicode_ci DEFAULT NULL,
  `out_total_amount` decimal(18,2) DEFAULT NULL,
  `out_date` date DEFAULT NULL
) ENGINE=InnoDB DEFAULT CHARSET=utf8 COLLATE=utf8_unicode_ci

这里只是模拟使用mysql中建立临时表的方法: 临时表data1    注:mysql(版本5.7)中不支持  WITH TABLE AS  语法建立临时表

DROP TEMPORARY TABLE IF EXISTS data1;
CREATE TEMPORARY TABLE data1 (dep_id BIGINT(20), dep_name VARCHAR(200));

INSERT INTO data1 SELECT t.dep_id,t.dep_name FROM department t;

SELECT * FROM out_warehouse t1 where t1.dep_id IN (SELECT dep_id FROM data1 );
DROP TEMPORARY TABLE IF EXISTS data1;

【使用临时表的前提条件】: 

【优化前】:table1 表数据100W+  table3表数据200W+ 

SELECT
  b.*,
  A.value1,
  A.value2,
  A.value3,
  A.value4,
  A.value5,
  A.value6,
FROM table1 b
  LEFT JOIN table2A
    ON b.order_no = A.order_no AND b.channel_no = A.channel_no
WHERE 1 = 1 AND EXISTS (SELECT
    1
  FROM table3 t
  WHERE b.order_no = t.order_no AND t.ticket_no LIKE '%1792903240%')
ORDER BY CREATE_TIME LIMIT 0, 20 

【优化后】: 

CREATE TEMPORARY TABLE tmp_order_no (ticket_order_no varchar(100));

INSERT INTO tmp_order_no SELECT tp.order_no FROM t_passenger tp WHERE tp.ticket_no LIKE CONCAT('%',2903240,'%');

SELECT
  b.*,
  A.value1,
  A.value2,
  A.value3,
  A.value4,
  A.value5,
  A.value6,
FROM table1 b
  LEFT JOIN table2A
    ON b.order_no = A.order_no AND b.channel_no = A.channel_no
WHERE 1 = 1 AND b.order_no IN (SELECT ticket_order_no FROM tmp_order_no)
ORDER BY CREATE_TIME LIMIT 0, 20 

DROP TEMPORARY TABLE IF EXISTS tmp_order_no;

主要原因在于 exists这个部分,因为 table3有200W的数据,并且循环式和外表扫描查询,并且这里的like是不会走索引的,只能全扫描,所以慢就很明显了,由于是动态语句,并在存储过程中,所以优化就是拆解EXISTS这部分

主要思路就是 先从200W+ 的table3中查出来order_no 然后把order_no插入临时表,然后再使用in 临时表查询,减少关联扫描次数就能极大的优化查询时间

前提: table3中的ticket_no 重复率非常低,200W+的数据 有200W的非重复,为什么强调这个,临时表在处理少量数据时性能很优异,(一般只在确定不能用索引的时候才使用临时表,或者在存储过程中某些固定数据使用次数非常多的时候使用临时表,其他时候我一般不建议使用)

 

【嵌套子句】: 有博主指出多union all不花费查询时间,重点是通过优化 子句 ,通过索引或者优化子句其他方式等

WITH TABLE AS  语法例子】: 

with data1 as (
select *,
case when 来源 like '%Touareg%' then 'Touareg'   
     when 来源 like '%X5%' then 'X5'
     when 来源 like '%Q7%' then 'Q7'
     when 来源 like '%GLE%' then 'GLE'       
end as 车型1
    from autohome),
 
data2
as
(
  
/*下面是想定义的临时表data2,且下面的临时表是引用了上面的data1的*/
select * from (
select *,'国产途观'as 车型2 
from data1
where (来源 like '%Tiguan%' or 来源 like '%途观%') and 用户id not in (
select 用户ID from koubeicontent
where 发布类型 like '%口碑%' and 来源 like '%Tiguan%')
)
)
 
select * from data2

【union all 语句优化意见】: 

(方式一 - 原来的方式:) :EXPLAIN SELECT t.dep_id, t.dep_name FROM `department` t 
INNER JOIN
(
SELECT dep_id FROM out_warehouse1 
UNION ALL
SELECT dep_id FROM out_warehouse2
) a
ON t.dep_id = a.dep_id LIMIT 0,30

(方式二 - 优化方式一 ):EXPLAIN (SELECT t1.dep_id,t1.dep_name FROM department t1 INNER JOIN out_warehouse1 t2 
ON t1.dep_id = t2.dep_id LIMIT 0,30)
union all
(SELECT t1.dep_id,t1.dep_name FROM department t1 INNER JOIN out_warehouse2 t3  
ON t1.dep_id = t3.dep_id  LIMIT 0,30)


(方式三 - 优化方式二):EXPLAIN (SELECT t1.dep_id,t1.dep_name FROM department t1 WHERE EXISTS (SELECT dep_id FROM out_warehouse1 t2 WHERE t2.dep_id = t1.dep_id )
LIMIT 0,30)
UNION ALL
(SELECT t1.dep_id,t1.dep_name FROM department t1 WHERE EXISTS (SELECT dep_id FROM out_warehouse1 t3 WHERE t3.dep_id = t1.dep_id )
LIMIT 0,30)

【mySql优化】:参考文献:MySQL的查询优化(一)

(1)在MySQL中如果or语句两边的字段不是都增加了索引,那么即使一方有索引,查询仍会成为全表扫描 ;如果是不同的两个字段,我们给他们都加上索引,这里type变成了index_merge,也就是索引合并(联合索引

(2)如果是相同的字段,使用union all。union all 就是执行两条SQL语句,然后组合在一起,它的执行计划实际上是两条SQL。但是总共扫描行数是少于全表的行数的。​​​​​​​

(3)谈到union all 我们也要说一下 union 。Union因为要进行重复值扫描,所以效率低。如果合并没有刻意要删除重复行,那么就使用Union All。

(4)有时候采用分组操作代替排序去重 

例子:

原SQL:
select distinct id_card 
 from (select t.id_card 
 from clspuser.crf_p2p_account_info t 
 where t.loan_amount <= 200000 and t.pay_date <= '2016-11-14' 
 union 
 select t.id_card 
 from clspuser.zh_crf_p2p_account_info t 
 where t.loan_amount <= 200000 and t.pay_date <= '2016-11-14');



改造后:
 select  id_card 
  from (select t.id_card 
  from clspuser.crf_p2p_account_info t 
  where t.loan_amount <= 200000 
  union all
  select t.id_card 
  from clspuser.zh_crf_p2p_account_info t 
  where t.loan_amount <= 200000 ) a group by a.id_card

 

 

 

 

 

 

 

 

Logo

更多推荐