千万条据下的分页

1.1. 背景

对于开发来说,分页功能碰到的频率还是算蛮高的,基本上在每个模块中都需要都会遇到列表分页的功能。他们实现的都很快,因为基本上只要把之前的代码改改就OK了。他们的实现基本是是如下语句:

SELECT * FROM goods WHERE user_id = 4 LIMIT 1000, 20;

... omit ...

20 rows in set (0.11 sec)

像这样的语句对数据量小或偏移量小的时候是十分快的。但是当数据量大并且偏移量大的时候就会有问题了。如下:

SELECT * FROM goods WHERE user_id = 4 LIMIT 500000, 20;

... omit ...

20 rows in set (7.84 sec)

为什么会这样就不说了。下面给出优化的过程。

1.2. 构建数据

-- 创建商品表

DROP TABLE IF EXISTS goods;

CREATE TABLE goods(

  id bigint(20) unsigned NOT NULL AUTO_INCREMENT,

  good_name VARCHAR(50) NOT NULL,

  user_id TINYINT unsigned NOT NULL,

  PRIMARY KEY (`id`)

);

-- 创建批量添加数据存储过程

-- 下面创建数据可能需要一点时间

DROP PROCEDURE IF EXISTS insert_batch;

DELIMITER //

CREATE PROCEDURE insert_batch()

BEGIN

  DECLARE num INT;

  DECLARE user_id TINYINT;

  SET num=1;

  WHILE num <= 100000 DO

    SELECT FLOOR(RAND() * 10 + 1) INTO user_id;

    INSERT INTO goods VALUES(NULL, REPEAT('X', 50), user_id);

    SET num=num+1;

  END WHILE;

  SET num=1;

  WHILE num <= 7 DO

    INSERT INTO goods SELECT NULL, good_name, user_id FROM goods;

    SET num=num+1;

  END WHILE;

END //

DELIMITER ;

-- 调用存储过程

CALL insert_batch();

-- 添加索引

ALTER TABLE goods

ADD INDEX idx$goods$user_id(user_id);

SELECT user_id, COUNT(*) FROM goods GROUP BY user_id;

+---------+----------+

| user_id | COUNT(*) |

+---------+----------+

|       1 |    10089 |

|       2 |    10077 |

|       3 |     9944 |

|       4 | 12710074 |

|       5 |    10011 |

|       6 |     9925 |

|       7 |     9950 |

|       8 |    10149 |

|       9 |     9949 |

|      10 |     9832 |

+---------+----------+

这边我们以数据最多的user_id=4的记录来模拟

1.3. 优化规则

让所有结果集数据最小化。如果是临时表,还是行数据还是列数据都让结果最小化,还有就是临时结果集尽量不走主键索引,走二级索引。

1.4. 模拟

现在我们需要查询用户4在10000000后20条数据

1、通过user_id找到主键ID(让列结果最小化)

SELECT id FROM goods WHERE user_id = 4 LIMIT 10000000, 20;

10343427

... omit ...

10343446

20 rows in set (1.83 sec)

2、通过获得的主键ID寻找需要的数据,这边我就不使用python来演示了。在程序里面就需要拼出IN里面的条件。

SELECT *

FROM goods

WHERE id IN(

  10343427, 10343428, 10343429, 10343430, 10343431,

  10343432, 10343433, 10343434, 10343435, 10343436,

  10343437, 10343438, 10343439, 10343440, 10343441,

  10343442, 10343443, 10343444, 10343445, 10343446

);

+----------+----------------------------------------------------+---------+

| id       | good_name                                          | user_id |

+----------+----------------------------------------------------+---------+

| 10343427 | XXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXX |       4 |

... omit ...

20 rows in set (0.01 sec)

1.5. 进一步优化

其实上面我们还能让结构级变少。来看下面列表简图:

mysql

在网页的分页按钮基本省都是一个连接,或者通过jquery时间分页。我们可以在按钮上添加两个属性参数为max_id和min_id。分别记录的是当前页数据的最小ID和最大ID(如:min_max=10343427、max_id=10343446)。

查找数据如下:

1、通过user_id找到主键ID(让列结果最小化)

如果是点击下一页

SELECT id FROM goods WHERE id > 10343446 AND user_id = 4 LIMIT 0, 20;

+----------+

| id       |

+----------+

| 10343447 |

... omit ...

20 rows in set (0.02 sec)

如果是点击上一页(上一页会比下一页性能来的差一点,因为有用到排序)

SELECT id FROM goods WHERE id < 10343427 AND user_id = 4

ORDER BY id DESC

LIMIT 0, 20;

2、通过获得的主键ID寻找需要的数据

SELECT *

FROM goods

WHERE id IN(

  10343447, 10343448, 10343449, 10343450, 10343451,

  10343452, 10343453, 10343454, 10343455, 10343456,

  10343457, 10343458, 10343459, 10343460, 10343461,

  10343462, 10343463, 10343464, 10343465, 10343466

);

+----------+----------------------------------------------------+---------+

| id       | good_name                                          | user_id |

+----------+----------------------------------------------------+---------+

| 10343447 | XXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXX |       4 |

... omit ...

20 rows in set (0.01 sec)

1.6. 总结

这种优化可能在一些使用到聚合函数的排序的情况下没法使用。

在这边鼓励使用MySQL的尽量使用比较简单的语句,不使用JOIN。因为优化器对简单的语句解析的很快,而且在维护的角度来说越白痴的语句越让人容易明白。

当然,强烈反对在程序中 for 循环取数据库。

转载于:https://my.oschina.net/lemonwater/blog/1546327

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值