1. mysql示例表:dept_manager
SELECT * FROM dept_manager;
部分截图:
2. 子查询实现 - 数据量小:
SELECT dept_no,emp_no,( SELECT COUNT(*) FROM dept_manager AS s2 WHERE s1.`dept_no`=s2.dept_no AND s2.emp_no <= s1.`emp_no` ) AS rownum FROM dept_manager AS s1 ORDER BY dept_no,emp_no;
部分截图:
2. 子查询实现 - 数据1万条记录:
这里使用到了mysql官方示例数据库表:dept_emp
#dept_emp 查询10000条数据,耗时1分19秒 SELECT de1.`emp_no`, de1.`dept_no`,( SELECT COUNT(*) FROM dept_emp de2 WHERE de2.dept_no=de1.`dept_no` AND de2.`emp_no` <= de1.emp_no ) rownum FROM dept_emp de1 ORDER BY de1.`dept_no`,de1.`emp_no` LIMIT 0,10000;
对于分区问题,假设表中有m个分区,每个分区有n行数据,则扫描的总行数是m*n*(n-1)/2,而m*n=N为表中的所有数据。因此,当n的值较小,即每个分区中含有的数据较少时,扫描成本为O(N),而当n的值较大时,该子查询解决方案的扫描成本为O(N²)。
总结:对于表中数据较少的情况,这种方法非常快。 当表数据记录过大时,子查询不可取。
引用书上的:“当查询30万条数据时,电脑上运行了8个小时也没有得到结果”
3. 优化 - 游标解决方案
- 创建存储过程:
DROP PROCEDURE IF EXISTS pGetPartitionNumber; DELIMITER // CREATE PROCEDURE pGetPartitionNumber() BEGIN DECLARE done INT DEFAULT 0; DECLARE deptno CHAR(4) DEFAULT NULL; DECLARE deptno_prev CHAR(4) DEFAULT NULL; DECLARE empno INT; DECLARE rn INT DEFAULT 0; DECLARE cur1 CURSOR FOR SELECT dept_no,emp_no FROM dept_emp ORDER BY dept_no,emp_no; DECLARE CONTINUE HANDLER FOR NOT FOUND SET done = 1; -- 若游标没有数据返回时,则将done设置等于1 -- 创建临时表 CREATE TEMPORARY TABLE $ret( dept_no CHAR(4), emp_no INT, rownum INT )ENGINE=INNODB; OPEN cur1; FETCH cur1 INTO deptno,empno; -- 游标向下走一步 START TRANSACTION; -- 开启事物 WHILE done = 0 DO IF deptno_prev IS NULL THEN SET rn = 1; ELSEIF deptno = deptno_prev THEN SET rn = rn + 1; ELSE SET rn = 1; END IF; SET deptno_prev = deptno; INSERT INTO $ret SELECT deptno,empno,rn; FETCH cur1 INTO deptno,empno; END WHILE; COMMIT; -- 提交事物 CLOSE cur1; SELECT * FROM $ret; DROP TABLE $ret; END; //
- 调用存储过程
# 调用存储过程
CALL pGetPartitionNumber();
- 执行信息
总结:从上图可以看到查询30多万条记录只花了7秒,数据记录过大时可以采取游标的方法进行优化。
4. 优化 - 临时表
# 创建带有自增长列的临时表 DROP TABLE IF EXISTS $temp; CREATE TABLE $temp( id INT UNSIGNED AUTO_INCREMENT, dept_no CHAR(4), emp_no INT, PRIMARY KEY(id) ); # 根据分区要求插入数据 INSERT INTO $temp SELECT NULL, dept_no, emp_no FROM dept_manager ORDER BY dept_no, emp_no; # join运算,通过嵌套子查询得到结果 SELECT m.dept_no, m.emp_no, id-minid+1 AS rownum FROM $temp AS m INNER JOIN ( SELECT dept_no, MIN(id) AS minid FROM $temp GROUP BY dept_no ) AS n ON m.dept_no = n.dept_no; # 删除临时表 DROP TABLE $temp;
执行信息:
上图来看,执行效率也是非常高的。