limit优化实测

首先是表

CREATE TABLE `page_test` (
    `id` INT(11) NOT NULL AUTO_INCREMENT,
    `name` VARCHAR(20) NOT NULL,
    `email` VARCHAR(40) NOT NULL,
    `solved_number` INT(11) NOT NULL,
    PRIMARY KEY (`id`)
)
COLLATE='utf8_general_ci'
ENGINE=InnoDB
AUTO_INCREMENT=1
;

搞1000086个数

delimiter $$
create procedure pre()
BEGIN
declare i INT;
SET i = 1;
while i < 1000086 DO
    INSERT INTO page_test
    VALUES
    (i,substring(MD5(RAND()),1,20),substring(MD5(RAND()),1,20),i);
    SET i = i+1;
END while;
END;
$$
CALL pre();

win10+i3(存储介质:eMLC寨盘)插入速度1.8MB/s

改用sql直接导入

首先要调整大小,不然会gone away

set global max_allowed_packet=1068435456;

(一个不准确但够大的数)

C++生成

#include
using namespace std;
const int MAXN = 5e6+11;
char rnd[13];
char rnd2[13];
int main() {
    freopen("insert.txt","w",stdout);
    int cur = 0;
    printf("INSERT INTO training.page_test\nVALUES");
    
    while(cur++ < MAXN) {
        for(int i = 0; i < 12; i++) {
            rnd[i] = (rand()%26)+'a';
            rnd2[i] = (rand()%10)+'0';
        }
        printf("(%d,'%s','%s',%d)",cur,rnd,rnd2,cur);
        if(cur < MAXN) printf(",\n");
    }
    return 0;
}
mysql -uroot -p123456 < D:\Code\cpp\insert.txt

IO大概在60-130MB/s(内存占了2-3G)


SELECT COUNT(*) FROM page_test;
/* Affected rows: 0  Found rows: 1  Warnings: 0  Duration for 1 query:  1.016 sec. */
SELECT * FROM page_test LIMIT 5000002,1;
/* Affected rows: 0  Found rows: 1  Warnings: 0  Duration for 1 query:  3.031 sec. */
SELECT * FROM page_test LIMIT 5000002,3;
/* Affected rows: 0  Found rows: 3  Warnings: 0  Duration for 1 query:  3.110 sec. */

走索引

SELECT * FROM page_test WHERE
id = (
    SELECT id FROM page_test LIMIT 5000003,1
);
/* Affected rows: 0  Found rows: 1  Warnings: 0  Duration for 1 query:  2.219 sec. */

多个id

SELECT a.* FROM page_test a
JOIN
(SELECT id FROM  page_test LIMIT 5000001,5) b
ON a.id = b.id;
/* Affected rows: 0  Found rows: 5  Warnings: 0  Duration for 1 query:  2.219 sec. */

如果已知id必然在某个范围可以这样

SELECT * FROM page_test a WHERE a.id >= 5000002 AND a.id <= 5000006;
/* Affected rows: 0  Found rows: 5  Warnings: 0  Duration for 1 query:  0.000 sec. */

EXPALIN分析

索引下的LIMIT

EXPLAIN
SELECT a.id FROM page_test a LIMIT 5000001,5;

1157717-20190818163336604-1578465517.png

大概需要2.2s

非索引下的LIMIT

EXPLAIN
SELECT a.email FROM page_test a LIMIT 5000001,5;

limit优化实测_第1张图片

差不多时间2.3s,虽然ALL看着比index要差点,但实际跑起来没差

事实上有无索引在Limit下跑起来速度几乎一样,个人推测与Innodb的索引文件和数据文件合并有关
也就是说,LIMIT的噩梦靠索引救不了

至于前面的先拿出索引id再join的做法,explain相对没那么难看且可灵活应对修改,但事实上跑起来也。。。没差拉

真正的索引与虚伪的索引

EXPLAIN
SELECT * FROM page_test a WHERE a.id >= 5000002 AND a.id <= 5000006;

1157717-20190818163406887-420517271.png

只需5行,0s

所以如果没有删除的需求且保证ID连续的话,用WHERE来代替分页是最好的选择
(各大OJ都使用VOL来表示分页,看来不是没有道理)

后记

1.尝试开启query_cache,发现灵活性不够好,算了

2.看到有人用索引+order by来优化后面的limit查询,感觉有点意思
试了一下

SELECT * FROM page_test ORDER BY id DESC LIMIT 5;

时间不用看了,秒出结果(DESC不需要真正的排序 (Backward index scan))
加上这种优化可以保证最坏情况只在n/2出现,非常实用(大多数人都是要么看前面要么看后面)
当然order by要保证索引

3.用派生的表/列来维护页数
要是愿意用O(n)的时间来维护每一次增删,倒是个不错的方法
但是这样表的数量直接X2
并且对于百/千万级以上的数据,O(n)需要花费秒级的代价来维护

4.子查询在我的mySQL版本不允许使用in (select....),我感觉即使能用也差不多

5.理论上limit m,n可以通过B+树上维护子树大小来logm查找,可能考虑到SQL的复杂性,事实上根本没有这种操作

6.考虑需求
要是确实要删除,直接屏蔽访问而不彻底删除也是一种策略,这样依然可以保证ID连续意义下分页查询的高性能

保证上面前提下的修改,某种情况下可以用交换来代替实现(ID唯一保证),这样分页依然可以快速查询

转载于:https://www.cnblogs.com/caturra/p/11372879.html

你可能感兴趣的:(limit优化实测)