数据库MySQL 之 索引原理与慢查询优化
浏览目录
-
索引介绍方法类型
-
聚合索引辅助索引
-
测试索引
-
正确使用索引
-
组合索引
-
注意事项
-
查询计划
-
慢查询日志
-
大数据量分页优化
一、索引介绍方法类型
1、介绍
可以帮助用户快速的找到需要的内容;在MySQL中也叫做“键”,是存储引擎用于快速找到记录的一种数据结构。能够大大提高查询效率。特别是当数据量非常大,查询涉及多个表时,使用索引往往能使查询速度加快成千上万倍。
总结:
索引的目的在于提高查询效率,与我们查阅图书所用的目录是一个道理:先定位到章,然后定位到该章下的一个小节,然后找到页数。相似的例子还有:查字典,查地图等等
本质:
通过不断地缩小想要获取数据的范围来筛选出最终想要的结果,同时把随机的事件变成顺序的事件,也就是说,有了这种索引机制,我们可以总是用同一种查找方式来锁定数据。
2、索引方法-MySQL的BTREE 改进索引
- 就是一种将索引值按一定的算法,存入一个树形的数据结构中
- 概念铺垫——B-Tree数据结构
-
- 结构
B-TREE 每个节点都是一个二元数组: [key, data],所有节点都可以存储数据。key为索引key,data为除key之外的数据。 - 结构图
- 结构
- 检索原理
首先从根节点进行二分查找,如果找到则返回对应节点的data,否则对相应区间的指针指向的节点递归进行查找,直到找到节点或未找到节点返回null指针。 - 缺点
-
-
- 插入删除新的数据记录会破坏B-Tree的性质,因此在插入删除时,需要对树进行一个分裂、合并、转移等操作以保持B-Tree性质。造成IO操作频繁。
- 区间查找可能需要返回上层节点重复遍历,IO操作繁琐。
-
- 概念铺垫——B+Tree(B-Tree的变种)
- 结构
与B-Tree相比,B+Tree有以下不同点:非叶子节点不存储data,只存储索引key;只有叶子节点才存储data。 - 结构图
- 结构
- Mysql选择B+TREE索引的原因
索引本身也很大,不可能全部存储在内存中,因此索引往往以索引文件的形式存储的磁盘上。这样的话,索引查找过程中就要产生磁盘I/O消耗,相对于内存存取,I/O存取的消耗要高几个数量级,所以索引的结构组织要尽量减少查找过程中磁盘I/O的存取次数,提升索引效率。 - MyISAM & InnoDB 都使用B+Tree索引结构。但是底层索引存储不同,MyISAM 采用非聚集索引,而InnoDB采用聚集索引。
3、索引方法-HASH索引
hash就是一种(key=>value)形式的键值对,允许多个key对应相同的value,但不允许一个key对应多个value,为某一列或几列建立hash索引,就会利用这一列或几列的值通过一定的算法计算出一个hash值,对应一行或几行数据。hash索引可以一次定位,不需要像树形索引那样逐层查找,因此具有极高的效率
4、HASH与BTREE比较
hash类型的索引:查询单条快,范围查询慢
btree类型的索引:b+树,层数越多,数据量越大,范围查询和随机查询快(innodb默认索引类型)
不同的存储引擎支持的索引类型也不一样
InnoDB支持事务,支持行级别锁定,支持 Btree、Hash 等索引,不支持Full-text 索引;
MyISAM不支持事务,支持表级别锁定,支持 Btree、Full-text 等索引,不支持 Hash 索引;
Memory不支持事务,支持表级别锁定,支持 Btree、Hash 等索引,不支持 Full-text 索引;
NDB支持事务,支持行级别锁定,支持 Hash 索引,不支持 Btree、Full-text 等索引;
Archive不支持事务,支持表级别锁定,不支持 Btree、Hash、Full-text 等索引;
5、索引类型
- 普通索引:加速查询
- 创建表+索引
1
2
3
4
5
6
|
创建表同时添加name字段为普通索引
create table tb1(
id
int
not
null auto_increment primary key,
name varchar(
100
)
not
null,
index idx_name(name)
);
|
-
- 创建索引
1
2
|
单独为表指定普通索引
create index idx_name on tb1(name);
|
-
- 删除索引
1
|
drop index idx_name on tb1;
|
-
- 查看索引
1
|
show index
from
tb1;
|
-
- 查看索引、列介绍
1
2
3
4
5
6
7
8
9
10
11
12
|
·Table 表的名称。
·Non_unique 如果索引为唯一索引,则为
0
,如果可以则为
1
。
·Key_name 索引的名称
·Seq_in_index 索引中的列序列号,从
1
开始。
·Column_name 列名称。
·Collation 列以什么方式存储在索引中。在MySQL中,有值‘A’(升序)或NULL(无分类)。
·Cardinality 索引中唯一值的数目的估计值。
·Sub_part 如果列只是被部分地编入索引,则为被编入索引的字符的数目。如果整列被编入索引,则为NULL。
·Packed 指示关键字如何被压缩。如果没有被压缩,则为NULL。
·Null 如果列含有NULL,则含有YES。如果没有,则该列含有NO。
·Index_type 用过的索引方法(BTREE, FULLTEXT,
HASH
, RTREE)。
·Comment 多种评注
|
- 唯一索引:加速查询 和 唯一约束(可含一个null值)
- 创建表+唯一(unique)索引
1
2
3
4
5
6
|
create table tb2(
id
int
not
null auto_increment primary key,
name varchar(
50
)
not
null,
age
int
not
null,
unique index idx_age (age)
);
|
-
- 创建unique索引
1
|
create unique index idx_age on tb2(age);
|
- 主键索引:加速查询 和 唯一约束(不可含null)、一个表中最多只能有一个主键索引
- 创建表 + 主键
1
2
3
4
5
6
7
8
9
10
11
12
13
|
方式一:
create table tb3(
id
int
not
null auto_increment primary key,
name varchar(
50
)
not
null,
age
int
default
0
);
方式二:
create table tb3(
id
int
not
null auto_increment,
name varchar(
50
)
not
null,
age
int
default
0
,
primary key(
id
)
);
|
-
- 创建主键
1
|
alter table tb3 add primary key(
id
);
|
-
- 删除主键
1
2
3
4
5
6
|
方式一
alter table tb3 drop primary key;
方式二:
如果当前主键为自增主键,则不能直接删除.需要先修改自增属性,再删除
alter table tb3 modify
id
int
,drop primary key;
|
- 组合索引:组合索引是将n个列组合成一个索引
- 创建表+组合索引
1
2
3
4
5
6
|
create table tb4(
id
int
not
null ,
name varchar(
50
)
not
null,
age
int
not
null,
index idx_name_age (name,age)
);
|
-
- 创建组合索引
1
|
create index idx_name_age on tb4(name,age);
|
-
- 索引应用场景
1
2
3
4
5
6
|
比如你在为某商场做一个会员卡的系统。这个系统有一个会员表包含下列字段:
会员编号
INT
、会员姓名 VARCHAR(
10
)、会员身份证号码 VARCHAR(
18
)、会员电话 VARCHAR(
10
)
会员住址 VARCHAR(
50
)、会员备注信息 TEXT
那么这个会员编号,作为主键,使用 PRIMARY
会员姓名如果要建索引的话,那么就是普通的 INDEX
会员身份证号码如果要建索引的话,那么可以选择 UNIQUE(唯一的,不允许重复)
|
二、聚合索引、辅助索引
1、聚集索引
InnoDB表索引组织表,即表中数据按主键B+树存放,叶子节点直接存放整条数据,每张表只能有一个聚集索引。
- 当你定义一个主键时,InnnodDB存储引擎则把它当做聚集索引
- 如果你没有定义一个主键,则InnoDB定位到第一个唯一索引,且该索引的所有列值均飞空的,则将其当做聚集索引
- 如果表没有主键或合适的唯一索引INNODB会产生一个隐藏的行ID值6字节的行ID聚集索引
- 补充:由于实际的数据页只能按照一颗B+树进行排序,因此每张表只能有一个聚集索引,聚集索引对于主键的排序和范围查找非常有利。
- 例子: 比如图书馆新进了一批书。那么这些书需要放到图书馆内。书如何放一般都有一个规则,杂志类的放到101货架,文学类的放到102货架,理工类的放到103货架等等。这些存储的规则决定了每本书应该放到哪里,找到对应的货架就相当于找到了所有的书。而这个例子中聚集索引为书的类别。
2、辅助索引
(也称为非聚集索引)是指叶节点不包含行的全部数据,叶节点除了包含键值之外,还包含一个书签连接,通过该书签再去找相应的行数据。
- 对于这种方式来说,你需要两个步骤:
- 查询该记录所在的位置。
- 通过该位置去取要找的记录。
3、二者区别以及使用场景
-
二者区别:
相同的是:不管是聚集索引还是辅助索引,其内部都是B+树的形式,即高度是平衡的,叶子结点存放着所有的数据。
不同的是:聚集索引叶子结点存放的是一整行的信息,而辅助索引叶子结点存放的是单个索引列信息。 -
何时使用聚集索引或非聚集索引
动作描述 | 使用聚集索引 | 使用非聚集索引 |
列经常被分组排列 | 应 | 应 |
返回某范围内的数据 | 应 | 不应 |
一个或极少不同值 | 不应 | 不应 |
频繁更新的列 | 不应 | 应 |
外键列 | 应 | 应 |
主键列 | 应 | 应 |
频繁修改索引列 | 不应 | 应 |
三、测试索引
1、创建数据
1
2
3
4
5
6
7
8
9
|
-
-
1.
创建表
CREATE TABLE userInfo(
id
int
NOT NULL,
name VARCHAR(
16
) DEFAULT NULL,
age
int
,
sex char(
1
)
not
null,
email varchar(
64
) default null
)ENGINE
=
MYISAM DEFAULT CHARSET
=
utf8;
注意:MYISAM存储引擎不产生引擎事务,数据插入速度极快,为方便快速插入测试数据,等我们插完数据,再把存储类型修改为InnoDB
|
2、创建存储过程,插入数据
1
2
3
4
5
6
7
8
9
10
11
12
13
14
|
-
-
2.
创建存储过程
delimiter$$
CREATE PROCEDURE insert_user_info(IN num
INT
)
BEGIN
DECLARE val
INT
DEFAULT
0
;
DECLARE n
INT
DEFAULT
1
;
-
-
循环进行数据插入
WHILE n <
=
num DO
set
val
=
rand()
*
50
;
INSERT INTO userInfo(
id
,name,age,sex,email)values(n,concat(
'alex'
,val),rand()
*
50
,
if
(val
%
2
=
0
,
'女'
,
'男'
),concat(
'alex'
,n,
'@qq.com'
));
set
n
=
n
+
1
;
end
while
;
END $$
delimiter;
|
3、调用存储过程插入500万条数据
1
|
call insert_user_info(
5000000
);
|
4、此步骤可以忽略修改引擎为INNODB
1
|
ALTER TABLE userinfo ENGINE
=
INNODB;
|
5、测试索引
1
2
3
4
5
6
7
8
9
10
|
·在没有索引的前提下测试查询速度
SELECT
*
FROM userinfo WHERE
id
=
4567890
;
注意:无索引情况,mysql根本就不知道
id
等于
4567890
的记录在哪里,只能把数据表从头到尾扫描一遍,此时有多少
个磁盘块就需要进行多少IO操作,所以查询速度很慢。
·在表中已经存在大量数据的前提下,为某个字段段建立索引,建立速度会很慢
CREATE INDEX idx_id on userinfo(
id
);
·在索引建立完毕后,以该字段为查询条件时,查询速度提升明显
select
*
from
userinfo where
id
=
4567890
;
|
6、注意
- mysql先去索引表里根据b+树的搜索原理很快搜索到id为4567890的数据,IO大大降低,因而速度明显提升
- 我们可以去mysql的data目录下找到该表,可以看到添加索引后该表占用的硬盘空间多了
- 如果使用没有添加索引的字段进行条件查询,速度依旧会很慢
四、正确使用索引
数据库表中添加索引后确实会让查询速度起飞,但前提必须是正确的使用索引来查询,如果以错误的方式使用,则即使建立索引也会不奏效。即使建立索引,索引也不会生效
五、组合索引
1、定义:是指对表上的多个列组合起来做一个索引
2、好处:
- “一个顶三个”。建了一个(a,b,c)的组合索引,那么实际等于建了(a),(a,b),(a,b,c)三个索引,因为每多一个索引,都会增加写操作的开销和磁盘空间的开销。对于大量数据的表,这可是不小的开销!
- 索引列越多,通过索引筛选出的数据越少。有1000W条数据的表,有如下sql:select * from table where a = 1 and b =2 and c = 3,假设假设每个条件可以筛选出10%的数据,如果只有单值索引,那么通过该索引能筛选出1000W*10%=100w 条数据,然后再回表从100w条数据中找到符合b=2 and c= 3的数据,然后再排序,再分页;如果是组合索引,通过索引筛选出1000w *10% *10% *10%=1w,然后再排序、分页,哪个更高效,一眼便知
- 最左匹配原则: 从左往右依次使用生效,如果中间某个索引没有使用,那么断点前面的索引部分起作用,断点后面的索引没有起作用;
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
|
select
*
from
mytable where a
=
3
and
b
=
5
and
c
=
4
;
#abc三个索引都在where条件里面用到了,而且都发挥了作用
select
*
from
mytable where c
=
4
and
b
=
6
and
a
=
3
;
#这条语句列出来只想说明 mysql没有那么笨,where里面的条件顺序在查询之前会被mysql自动优化,效果跟上一句一样
select
*
from
mytable where a
=
3
and
c
=
7
;
#a用到索引,b没有用,所以c是没有用到索引效果的
select
*
from
mytable where a
=
3
and
b>
7
and
c
=
3
;
#a用到了,b也用到了,c没有用到,这个地方b是范围值,也算断点,只不过自身用到了索引
select
*
from
mytable where b
=
3
and
c
=
4
;
#因为a索引没有使用,所以这里 bc都没有用上索引效果
select
*
from
mytable where a>
4
and
b
=
7
and
c
=
9
;
#a用到了 b没有使用,c没有使用
select
*
from
mytable where a
=
3
order by b;
#a用到了索引,b在结果排序中也用到了索引的效果
select
*
from
mytable where a
=
3
order by c;
#a用到了索引,但是这个地方c没有发挥排序效果,因为中间断点了
select
*
from
mytable where b
=
3
order by a;
#b没有用到索引,排序中a也没有发挥索引效果
|
六、注意事项
- 避免使用select *
- 其他数据库中使用count(1)或count(列) 代替 count(*),而mysql数据库中count(*)经过优化后,效率与前两种基本一样.
- 创建表时尽量时 char 代替 varchar
- 表的字段顺序固定长度的字段优先
- 组合索引代替多个单列索引(经常使用多个条件查询时)
- 使用连接(JOIN)来代替子查询(Sub-Queries)
- 不要有超过4个以上的表连接(JOIN)
- 优先执行那些能够大量减少结果的连接。
- 连表时注意条件类型需一致
- 索引散列值不适合建索引,例:性别不适合
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
|
-
like
'%xx'
select
*
from
tb1 where name like
'%cn'
;
-
使用函数
select
*
from
tb1 where reverse(name)
=
'wupeiqi'
;
-
or
特别的:当
or
条件中有未建立索引的列才失效,以下会走索引
select
*
from
tb1 where nid
=
1
or
name
=
'seven'
;
-
类型不一致
如果列是字符串类型,传入条件是必须用引号引起来,不然...
select
*
from
tb1 where name
=
999
;
-
!
=
select
*
from
tb1 where name !
=
'alex'
特别的:如果是主键,则还是会走索引
select
*
from
tb1 where nid !
=
123
-
>
select
*
from
tb1 where name >
'alex'
特别的:如果是主键或索引是整数类型,则还是会走索引
select
*
from
tb1 where nid >
123
select
*
from
tb1 where num >
123
-
order by
select email
from
tb1 order by name desc;
当根据索引排序时候,选择的映射如果不是索引,则不走索引
特别的:如果对主键排序,则还是走索引:
select
*
from
tb1 order by nid desc;
-
组合索引最左前缀
如果组合索引为:(name,email)
name
and
email
-
-
使用索引
name
-
-
使用索引
email
-
-
不使用索引
|
七、查询计划
1、语法格式
1
|
explain
+
查询SQL
-
用于显示SQL执行信息参数,根据参考信息可以进行SQL优化
|
2、执行计划让mysql预估执行操作一般正确
1
2
3
4
5
6
7
|
mysql> explain select
*
from
tb2;
+
-
-
-
-
+
-
-
-
-
-
-
-
-
-
-
-
-
-
+
-
-
-
-
-
-
-
+
-
-
-
-
-
-
+
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
+
-
-
-
-
-
-
+
-
-
-
-
-
-
-
-
-
+
-
-
-
-
-
-
+
-
-
-
-
-
-
+
-
-
-
-
-
-
-
+
|
id
| select_type | table |
type
| possible_keys | key | key_len | ref | rows | Extra |
+
-
-
-
-
+
-
-
-
-
-
-
-
-
-
-
-
-
-
+
-
-
-
-
-
-
-
+
-
-
-
-
-
-
+
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
+
-
-
-
-
-
-
+
-
-
-
-
-
-
-
-
-
+
-
-
-
-
-
-
+
-
-
-
-
-
-
+
-
-
-
-
-
-
-
+
|
1
| SIMPLE | tb2 |
ALL
| NULL | NULL | NULL | NULL |
2
| NULL |
+
-
-
-
-
+
-
-
-
-
-
-
-
-
-
-
-
-
-
+
-
-
-
-
-
-
-
+
-
-
-
-
-
-
+
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
+
-
-
-
-
-
-
+
-
-
-
-
-
-
-
-
-
+
-
-
-
-
-
-
+
-
-
-
-
-
-
+
-
-
-
-
-
-
-
+
1
row
in
set
(
0.00
sec)
|
1
2
3
4
5
6
7
8
9
10
11
12
13
|
type
: 查询计划的连接类型, 有多个参数,先从最佳类型到最差类型介绍
性能: null > system
/
const > eq_ref > ref > ref_or_null > index_merge >
range
> index >
all
慢:
explain select
*
from
userinfo where email
=
'alex'
;
type
:
ALL
(全表扫描)
特别的: select
*
from
userinfo limit
1
;
快:
explain select
*
from
userinfo where name
=
'alex'
;
type
: ref(走索引)
③EXPLAIN 参数详解:http:
/
/
www.cnblogs.com
/
wangfengming
/
articles
/
8275448.html
|
id 查询顺序标识 如:mysql> explain select * from (select nid,name from tb1 where nid < 10) as B; +----+-------------+------------+-------+---------------+---------+---------+------+------+-------------+ | id | select_type | table | type | possible_keys | key | key_len | ref | rows | Extra | +----+-------------+------------+-------+---------------+---------+---------+------+------+-------------+ | 1 | PRIMARY | <derived2> | ALL | NULL | NULL | NULL | NULL | 9 | NULL | | 2 | DERIVED | tb1 | range | PRIMARY | PRIMARY | 8 | NULL | 9 | Using where | +----+-------------+------------+-------+---------------+---------+---------+------+------+-------------+ 特别的:如果使用union连接气值可能为null select_type 查询类型 SIMPLE 简单查询 PRIMARY 最外层查询 SUBQUERY 映射为子查询 DERIVED 子查询 UNION 联合 UNION RESULT 使用联合的结果 ... table 正在访问的表名 type 查询时的访问方式,性能:all < index < range < index_merge < ref_or_null < ref < eq_ref < system/const ALL 全表扫描,对于数据表从头到尾找一遍 select * from tb1; 特别的:如果有limit限制,则找到之后就不在继续向下扫描 select * from tb1 where email = '[email protected]' select * from tb1 where email = '[email protected]' limit 1; 虽然上述两个语句都会进行全表扫描,第二句使用了limit,则找到一个后就不再继续扫描。 INDEX 全索引扫描,对索引从头到尾找一遍 select nid from tb1; RANGE 对索引列进行范围查找 select * from tb1 where name < 'alex'; PS: between and in > >= < <= 操作 注意:!= 和 > 符号 INDEX_MERGE 合并索引,使用多个单列索引搜索 select * from tb1 where name = 'alex' or nid in (11,22,33); REF 根据索引查找一个或多个值 select * from tb1 where name = 'seven'; EQ_REF 连接时使用primary key 或 unique类型 select tb2.nid,tb1.name from tb2 left join tb1 on tb2.nid = tb1.nid; CONST 常量 表最多有一个匹配行,因为仅有一行,在这行的列值可被优化器剩余部分认为是常数,const表很快,因为它们只读取一次。 select nid from tb1 where nid = 2 ; SYSTEM 系统 表仅有一行(=系统表)。这是const联接类型的一个特例。 select * from (select nid from tb1 where nid = 1) as A; possible_keys 可能使用的索引 key 真实使用的 key_len MySQL中使用索引字节长度 rows mysql估计为了找到所需的行而要读取的行数 ------ 只是预估值 extra 该列包含MySQL解决查询的详细信息 “Using index” 此值表示mysql将使用覆盖索引,以避免访问表。不要把覆盖索引和index访问类型弄混了。 “Using where” 这意味着mysql服务器将在存储引擎检索行后再进行过滤,许多where条件里涉及索引中的列,当(并且如果)它读取索引时,就能被存储引擎检验,因此不是所有带where子句的查询都会显示“Using where”。有时“Using where”的出现就是一个暗示:查询可受益于不同的索引。 “Using temporary” 这意味着mysql在对查询结果排序时会使用一个临时表。 “Using filesort” 这意味着mysql会对结果使用一个外部索引排序,而不是按索引次序从表里读取行。mysql有两种文件排序算法,这两种排序方式都可以在内存或者磁盘上完成,explain不会告诉你mysql将使用哪一种文件排序,也不会告诉你排序会在内存里还是磁盘上完成。 “Range checked for each record(index map: N)” 这个意味着没有好用的索引,新的索引将在联接的每一行上重新估算,N是显示在possible_keys列中索引的位图,并且是冗余的。
八、慢查询日志
1、概念
将mysql服务器中影响数据库性能的相关SQL语句记录到日志文件,通过对这些特殊的SQL语句分析,改进以达到提高数据库性能的目的。
2、慢查询日志参数
1
2
3
4
5
|
long_query_time : 设定慢查询的阀值,超出设定值的SQL即被记录到慢查询日志,缺省值为
10s
slow_query_log : 指定是否开启慢查询日志
log_slow_queries : 指定是否开启慢查询日志(该参数已经被slow_query_log取代,做兼容性保留)
slow_query_log_file : 指定慢日志文件存放位置,可以为空,系统会给一个缺省的文件host_name
-
slow.log
log_queries_not_using_indexes: 如果值设置为ON,则会记录所有没有利用索引的查询.
|
3、查看MySQL慢日志信息
1
2
3
4
|
#.查询慢日志配置信息 :
show variables like
'%query%'
;
#.修改配置信息
set
global
slow_query_log
=
on;
|
4、查看不使用索引参数状态
1
2
3
4
|
# 显示参数
show variables like
'%log_queries_not_using_indexes'
;
# 开启状态
set
global
log_queries_not_using_indexes
=
on;
|
5、查看慢日志显示的方式
1
2
3
4
5
|
#查看慢日志记录的方式
show variables like
'%log_output%'
;
#设置慢日志在文件和表中同时记录
set
global
log_output
=
'FILE,TABLE'
;
|
6、测试慢查询日志
1
2
3
4
5
|
#查询时间超过10秒就会记录到慢查询日志中
select sleep(
3
) FROM user ;
#查看表中的日志
select
*
from
mysql.slow_log;
|
九、大数据量分页优化
1、优化方案一
简单粗暴,就是不允许查看这么靠后的数据
2、优化方案二
在查询下一页时把上一页的行id作为参数传递给客户端程序,即select * from tb1 where id>3000000 limit 10;
还有一种方式,比如100页的10条数据 select * from tb1 where id>100*10 limit 10;
3、优化方案三延迟关联
我们在来分析一下这条语句为什么慢,机就处在这个 * 里面,这个表除了id主键肯定还有其他字段,
因为select * 所以mysql在沿着id主键走的时候要回行拿数据,走一下拿一下数据,如果把语句改成
select id from tb1 limit 3000000,10;你会发现时间缩短了一半;然后我们在拿id分别去取10条数据就行了;
语句就改成这样了:
select table.* from tb1 inner join ( select id from tb1 limit 3000000,10 ) as tmp on tmp.id=table.id;
4、三种方法优先选择
这三种方法最先考虑第一种,其次第二种,第三种是别无选择
浏览目录
-
索引介绍方法类型
-
聚合索引辅助索引
-
测试索引
-
正确使用索引
-
组合索引
-
注意事项
-
查询计划
-
慢查询日志
-
大数据量分页优化
一、索引介绍方法类型
1、介绍
可以帮助用户快速的找到需要的内容;在MySQL中也叫做“键”,是存储引擎用于快速找到记录的一种数据结构。能够大大提高查询效率。特别是当数据量非常大,查询涉及多个表时,使用索引往往能使查询速度加快成千上万倍。
总结:
索引的目的在于提高查询效率,与我们查阅图书所用的目录是一个道理:先定位到章,然后定位到该章下的一个小节,然后找到页数。相似的例子还有:查字典,查地图等等
本质:
通过不断地缩小想要获取数据的范围来筛选出最终想要的结果,同时把随机的事件变成顺序的事件,也就是说,有了这种索引机制,我们可以总是用同一种查找方式来锁定数据。
2、索引方法-MySQL的BTREE 改进索引
- 就是一种将索引值按一定的算法,存入一个树形的数据结构中
- 概念铺垫——B-Tree数据结构
-
- 结构
B-TREE 每个节点都是一个二元数组: [key, data],所有节点都可以存储数据。key为索引key,data为除key之外的数据。 - 结构图
- 结构
- 检索原理
首先从根节点进行二分查找,如果找到则返回对应节点的data,否则对相应区间的指针指向的节点递归进行查找,直到找到节点或未找到节点返回null指针。 - 缺点
-
-
- 插入删除新的数据记录会破坏B-Tree的性质,因此在插入删除时,需要对树进行一个分裂、合并、转移等操作以保持B-Tree性质。造成IO操作频繁。
- 区间查找可能需要返回上层节点重复遍历,IO操作繁琐。
-
- 概念铺垫——B+Tree(B-Tree的变种)
- 结构
与B-Tree相比,B+Tree有以下不同点:非叶子节点不存储data,只存储索引key;只有叶子节点才存储data。 - 结构图
- 结构
- Mysql选择B+TREE索引的原因
索引本身也很大,不可能全部存储在内存中,因此索引往往以索引文件的形式存储的磁盘上。这样的话,索引查找过程中就要产生磁盘I/O消耗,相对于内存存取,I/O存取的消耗要高几个数量级,所以索引的结构组织要尽量减少查找过程中磁盘I/O的存取次数,提升索引效率。 - MyISAM & InnoDB 都使用B+Tree索引结构。但是底层索引存储不同,MyISAM 采用非聚集索引,而InnoDB采用聚集索引。
3、索引方法-HASH索引
hash就是一种(key=>value)形式的键值对,允许多个key对应相同的value,但不允许一个key对应多个value,为某一列或几列建立hash索引,就会利用这一列或几列的值通过一定的算法计算出一个hash值,对应一行或几行数据。hash索引可以一次定位,不需要像树形索引那样逐层查找,因此具有极高的效率
4、HASH与BTREE比较
hash类型的索引:查询单条快,范围查询慢
btree类型的索引:b+树,层数越多,数据量越大,范围查询和随机查询快(innodb默认索引类型)
不同的存储引擎支持的索引类型也不一样
InnoDB支持事务,支持行级别锁定,支持 Btree、Hash 等索引,不支持Full-text 索引;
MyISAM不支持事务,支持表级别锁定,支持 Btree、Full-text 等索引,不支持 Hash 索引;
Memory不支持事务,支持表级别锁定,支持 Btree、Hash 等索引,不支持 Full-text 索引;
NDB支持事务,支持行级别锁定,支持 Hash 索引,不支持 Btree、Full-text 等索引;
Archive不支持事务,支持表级别锁定,不支持 Btree、Hash、Full-text 等索引;
5、索引类型
- 普通索引:加速查询
- 创建表+索引
1
2
3
4
5
6
|
创建表同时添加name字段为普通索引
create table tb1(
id
int
not
null auto_increment primary key,
name varchar(
100
)
not
null,
index idx_name(name)
);
|
-
- 创建索引
1
2
|
单独为表指定普通索引
create index idx_name on tb1(name);
|
-
- 删除索引
1
|
drop index idx_name on tb1;
|
-
- 查看索引
1
|
show index
from
tb1;
|
-
- 查看索引、列介绍
1
2
3
4
5
6
7
8
9
10
11
12
|
·Table 表的名称。
·Non_unique 如果索引为唯一索引,则为
0
,如果可以则为
1
。
·Key_name 索引的名称
·Seq_in_index 索引中的列序列号,从
1
开始。
·Column_name 列名称。
·Collation 列以什么方式存储在索引中。在MySQL中,有值‘A’(升序)或NULL(无分类)。
·Cardinality 索引中唯一值的数目的估计值。
·Sub_part 如果列只是被部分地编入索引,则为被编入索引的字符的数目。如果整列被编入索引,则为NULL。
·Packed 指示关键字如何被压缩。如果没有被压缩,则为NULL。
·Null 如果列含有NULL,则含有YES。如果没有,则该列含有NO。
·Index_type 用过的索引方法(BTREE, FULLTEXT,
HASH
, RTREE)。
·Comment 多种评注
|
- 唯一索引:加速查询 和 唯一约束(可含一个null值)
- 创建表+唯一(unique)索引
1
2
3
4
5
6
|
create table tb2(
id
int
not
null auto_increment primary key,
name varchar(
50
)
not
null,
age
int
not
null,
unique index idx_age (age)
);
|
-
- 创建unique索引
1
|
create unique index idx_age on tb2(age);
|
- 主键索引:加速查询 和 唯一约束(不可含null)、一个表中最多只能有一个主键索引
- 创建表 + 主键
1
2
3
4
5
6
7
8
9
10
11
12
13
|
方式一:
create table tb3(
id
int
not
null auto_increment primary key,
name varchar(
50
)
not
null,
age
int
default
0
);
方式二:
create table tb3(
id
int
not
null auto_increment,
name varchar(
50
)
not
null,
age
int
default
0
,
primary key(
id
)
);
|
-
- 创建主键
1
|
alter table tb3 add primary key(
id
);
|
-
- 删除主键
1
2
3
4
5
6
|
方式一
alter table tb3 drop primary key;
方式二:
如果当前主键为自增主键,则不能直接删除.需要先修改自增属性,再删除
alter table tb3 modify
id
int
,drop primary key;
|
- 组合索引:组合索引是将n个列组合成一个索引
- 创建表+组合索引
1
2
3
4
5
6
|
create table tb4(
id
int
not
null ,
name varchar(
50
)
not
null,
age
int
not
null,
index idx_name_age (name,age)
);
|
-
- 创建组合索引
1
|
create index idx_name_age on tb4(name,age);
|
-
- 索引应用场景
1
2
3
4
5
6
|
比如你在为某商场做一个会员卡的系统。这个系统有一个会员表包含下列字段:
会员编号
INT
、会员姓名 VARCHAR(
10
)、会员身份证号码 VARCHAR(
18
)、会员电话 VARCHAR(
10
)
会员住址 VARCHAR(
50
)、会员备注信息 TEXT
那么这个会员编号,作为主键,使用 PRIMARY
会员姓名如果要建索引的话,那么就是普通的 INDEX
会员身份证号码如果要建索引的话,那么可以选择 UNIQUE(唯一的,不允许重复)
|
二、聚合索引、辅助索引
1、聚集索引
InnoDB表索引组织表,即表中数据按主键B+树存放,叶子节点直接存放整条数据,每张表只能有一个聚集索引。
- 当你定义一个主键时,InnnodDB存储引擎则把它当做聚集索引
- 如果你没有定义一个主键,则InnoDB定位到第一个唯一索引,且该索引的所有列值均飞空的,则将其当做聚集索引
- 如果表没有主键或合适的唯一索引INNODB会产生一个隐藏的行ID值6字节的行ID聚集索引
- 补充:由于实际的数据页只能按照一颗B+树进行排序,因此每张表只能有一个聚集索引,聚集索引对于主键的排序和范围查找非常有利。
- 例子: 比如图书馆新进了一批书。那么这些书需要放到图书馆内。书如何放一般都有一个规则,杂志类的放到101货架,文学类的放到102货架,理工类的放到103货架等等。这些存储的规则决定了每本书应该放到哪里,找到对应的货架就相当于找到了所有的书。而这个例子中聚集索引为书的类别。
2、辅助索引
(也称为非聚集索引)是指叶节点不包含行的全部数据,叶节点除了包含键值之外,还包含一个书签连接,通过该书签再去找相应的行数据。
- 对于这种方式来说,你需要两个步骤:
- 查询该记录所在的位置。
- 通过该位置去取要找的记录。
3、二者区别以及使用场景
-
二者区别:
相同的是:不管是聚集索引还是辅助索引,其内部都是B+树的形式,即高度是平衡的,叶子结点存放着所有的数据。
不同的是:聚集索引叶子结点存放的是一整行的信息,而辅助索引叶子结点存放的是单个索引列信息。 -
何时使用聚集索引或非聚集索引
动作描述 | 使用聚集索引 | 使用非聚集索引 |
列经常被分组排列 | 应 | 应 |
返回某范围内的数据 | 应 | 不应 |
一个或极少不同值 | 不应 | 不应 |
频繁更新的列 | 不应 | 应 |
外键列 | 应 | 应 |
主键列 | 应 | 应 |
频繁修改索引列 | 不应 | 应 |
三、测试索引
1、创建数据
1
2
3
4
5
6
7
8
9
|
-
-
1.
创建表
CREATE TABLE userInfo(
id
int
NOT NULL,
name VARCHAR(
16
) DEFAULT NULL,
age
int
,
sex char(
1
)
not
null,
email varchar(
64
) default null
)ENGINE
=
MYISAM DEFAULT CHARSET
=
utf8;
注意:MYISAM存储引擎不产生引擎事务,数据插入速度极快,为方便快速插入测试数据,等我们插完数据,再把存储类型修改为InnoDB
|
2、创建存储过程,插入数据
1
2
3
4
5
6
7
8
9
10
11
12
13
14
|
-
-
2.
创建存储过程
delimiter$$
CREATE PROCEDURE insert_user_info(IN num
INT
)
BEGIN
DECLARE val
INT
DEFAULT
0
;
DECLARE n
INT
DEFAULT
1
;
-
-
循环进行数据插入
WHILE n <
=
num DO
set
val
=
rand()
*
50
;
INSERT INTO userInfo(
id
,name,age,sex,email)values(n,concat(
'alex'
,val),rand()
*
50
,
if
(val
%
2
=
0
,
'女'
,
'男'
),concat(
'alex'
,n,
'@qq.com'
));
set
n
=
n
+
1
;
end
while
;
END $$
delimiter;
|
3、调用存储过程插入500万条数据
1
|
call insert_user_info(
5000000
);
|
4、此步骤可以忽略修改引擎为INNODB
1
|
ALTER TABLE userinfo ENGINE
=
INNODB;
|
5、测试索引
1
2
3
4
5
6
7
8
9
10
|
·在没有索引的前提下测试查询速度
SELECT
*
FROM userinfo WHERE
id
=
4567890
;
注意:无索引情况,mysql根本就不知道
id
等于
4567890
的记录在哪里,只能把数据表从头到尾扫描一遍,此时有多少
个磁盘块就需要进行多少IO操作,所以查询速度很慢。
·在表中已经存在大量数据的前提下,为某个字段段建立索引,建立速度会很慢
CREATE INDEX idx_id on userinfo(
id
);
·在索引建立完毕后,以该字段为查询条件时,查询速度提升明显
select
*
from
userinfo where
id
=
4567890
;
|
6、注意
- mysql先去索引表里根据b+树的搜索原理很快搜索到id为4567890的数据,IO大大降低,因而速度明显提升
- 我们可以去mysql的data目录下找到该表,可以看到添加索引后该表占用的硬盘空间多了
- 如果使用没有添加索引的字段进行条件查询,速度依旧会很慢
四、正确使用索引
数据库表中添加索引后确实会让查询速度起飞,但前提必须是正确的使用索引来查询,如果以错误的方式使用,则即使建立索引也会不奏效。即使建立索引,索引也不会生效
五、组合索引
1、定义:是指对表上的多个列组合起来做一个索引
2、好处:
- “一个顶三个”。建了一个(a,b,c)的组合索引,那么实际等于建了(a),(a,b),(a,b,c)三个索引,因为每多一个索引,都会增加写操作的开销和磁盘空间的开销。对于大量数据的表,这可是不小的开销!
- 索引列越多,通过索引筛选出的数据越少。有1000W条数据的表,有如下sql:select * from table where a = 1 and b =2 and c = 3,假设假设每个条件可以筛选出10%的数据,如果只有单值索引,那么通过该索引能筛选出1000W*10%=100w 条数据,然后再回表从100w条数据中找到符合b=2 and c= 3的数据,然后再排序,再分页;如果是组合索引,通过索引筛选出1000w *10% *10% *10%=1w,然后再排序、分页,哪个更高效,一眼便知
- 最左匹配原则: 从左往右依次使用生效,如果中间某个索引没有使用,那么断点前面的索引部分起作用,断点后面的索引没有起作用;
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
|
select
*
from
mytable where a
=
3
and
b
=
5
and
c
=
4
;
#abc三个索引都在where条件里面用到了,而且都发挥了作用
select
*
from
mytable where c
=
4
and
b
=
6
and
a
=
3
;
#这条语句列出来只想说明 mysql没有那么笨,where里面的条件顺序在查询之前会被mysql自动优化,效果跟上一句一样
select
*
from
mytable where a
=
3
and
c
=
7
;
#a用到索引,b没有用,所以c是没有用到索引效果的
select
*
from
mytable where a
=
3
and
b>
7
and
c
=
3
;
#a用到了,b也用到了,c没有用到,这个地方b是范围值,也算断点,只不过自身用到了索引
select
*
from
mytable where b
=
3
and
c
=
4
;
#因为a索引没有使用,所以这里 bc都没有用上索引效果
select
*
from
mytable where a>
4
and
b
=
7
and
c
=
9
;
#a用到了 b没有使用,c没有使用
select
*
from
mytable where a
=
3
order by b;
#a用到了索引,b在结果排序中也用到了索引的效果
select
*
from
mytable where a
=
3
order by c;
#a用到了索引,但是这个地方c没有发挥排序效果,因为中间断点了
select
*
from
mytable where b
=
3
order by a;
#b没有用到索引,排序中a也没有发挥索引效果
|
六、注意事项
- 避免使用select *
- 其他数据库中使用count(1)或count(列) 代替 count(*),而mysql数据库中count(*)经过优化后,效率与前两种基本一样.
- 创建表时尽量时 char 代替 varchar
- 表的字段顺序固定长度的字段优先
- 组合索引代替多个单列索引(经常使用多个条件查询时)
- 使用连接(JOIN)来代替子查询(Sub-Queries)
- 不要有超过4个以上的表连接(JOIN)
- 优先执行那些能够大量减少结果的连接。
- 连表时注意条件类型需一致
- 索引散列值不适合建索引,例:性别不适合
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
|
-
like
'%xx'
select
*
from
tb1 where name like
'%cn'
;
-
使用函数
select
*
from
tb1 where reverse(name)
=
'wupeiqi'
;
-
or
特别的:当
or
条件中有未建立索引的列才失效,以下会走索引
select
*
from
tb1 where nid
=
1
or
name
=
'seven'
;
-
类型不一致
如果列是字符串类型,传入条件是必须用引号引起来,不然...
select
*
from
tb1 where name
=
999
;
-
!
=
select
*
from
tb1 where name !
=
'alex'
特别的:如果是主键,则还是会走索引
select
*
from
tb1 where nid !
=
123
-
>
select
*
from
tb1 where name >
'alex'
特别的:如果是主键或索引是整数类型,则还是会走索引
select
*
from
tb1 where nid >
123
select
*
from
tb1 where num >
123
-
order by
select email
from
tb1 order by name desc;
当根据索引排序时候,选择的映射如果不是索引,则不走索引
特别的:如果对主键排序,则还是走索引:
select
*
from
tb1 order by nid desc;
-
组合索引最左前缀
如果组合索引为:(name,email)
name
and
email
-
-
使用索引
name
-
-
使用索引
email
-
-
不使用索引
|
七、查询计划
1、语法格式
1
|
explain
+
查询SQL
-
用于显示SQL执行信息参数,根据参考信息可以进行SQL优化
|
2、执行计划让mysql预估执行操作一般正确
1
2
3
4
5
6
7
|
mysql> explain select
*
from
tb2;
+
-
-
-
-
+
-
-
-
-
-
-
-
-
-
-
-
-
-
+
-
-
-
-
-
-
-
+
-
-
-
-
-
-
+
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
+
-
-
-
-
-
-
+
-
-
-
-
-
-
-
-
-
+
-
-
-
-
-
-
+
-
-
-
-
-
-
+
-
-
-
-
-
-
-
+
|
id
| select_type | table |
type
| possible_keys | key | key_len | ref | rows | Extra |
+
-
-
-
-
+
-
-
-
-
-
-
-
-
-
-
-
-
-
+
-
-
-
-
-
-
-
+
-
-
-
-
-
-
+
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
+
-
-
-
-
-
-
+
-
-
-
-
-
-
-
-
-
+
-
-
-
-
-
-
+
-
-
-
-
-
-
+
-
-
-
-
-
-
-
+
|
1
| SIMPLE | tb2 |
ALL
| NULL | NULL | NULL | NULL |
2
| NULL |
+
-
-
-
-
+
-
-
-
-
-
-
-
-
-
-
-
-
-
+
-
-
-
-
-
-
-
+
-
-
-
-
-
-
+
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
+
-
-
-
-
-
-
+
-
-
-
-
-
-
-
-
-
+
-
-
-
-
-
-
+
-
-
-
-
-
-
+
-
-
-
-
-
-
-
+
1
row
in
set
(
0.00
sec)
|
1
2
3
4
5
6
7
8
9
10
11
12
13
|
type
: 查询计划的连接类型, 有多个参数,先从最佳类型到最差类型介绍
性能: null > system
/
const > eq_ref > ref > ref_or_null > index_merge >
range
> index >
all
慢:
explain select
*
from
userinfo where email
=
'alex'
;
type
:
ALL
(全表扫描)
特别的: select
*
from
userinfo limit
1
;
快:
explain select
*
from
userinfo where name
=
'alex'
;
type
: ref(走索引)
③EXPLAIN 参数详解:http:
/
/
www.cnblogs.com
/
wangfengming
/
articles
/
8275448.html
|
id 查询顺序标识 如:mysql> explain select * from (select nid,name from tb1 where nid < 10) as B; +----+-------------+------------+-------+---------------+---------+---------+------+------+-------------+ | id | select_type | table | type | possible_keys | key | key_len | ref | rows | Extra | +----+-------------+------------+-------+---------------+---------+---------+------+------+-------------+ | 1 | PRIMARY | <derived2> | ALL | NULL | NULL | NULL | NULL | 9 | NULL | | 2 | DERIVED | tb1 | range | PRIMARY | PRIMARY | 8 | NULL | 9 | Using where | +----+-------------+------------+-------+---------------+---------+---------+------+------+-------------+ 特别的:如果使用union连接气值可能为null select_type 查询类型 SIMPLE 简单查询 PRIMARY 最外层查询 SUBQUERY 映射为子查询 DERIVED 子查询 UNION 联合 UNION RESULT 使用联合的结果 ... table 正在访问的表名 type 查询时的访问方式,性能:all < index < range < index_merge < ref_or_null < ref < eq_ref < system/const ALL 全表扫描,对于数据表从头到尾找一遍 select * from tb1; 特别的:如果有limit限制,则找到之后就不在继续向下扫描 select * from tb1 where email = '[email protected]' select * from tb1 where email = '[email protected]' limit 1; 虽然上述两个语句都会进行全表扫描,第二句使用了limit,则找到一个后就不再继续扫描。 INDEX 全索引扫描,对索引从头到尾找一遍 select nid from tb1; RANGE 对索引列进行范围查找 select * from tb1 where name < 'alex'; PS: between and in > >= < <= 操作 注意:!= 和 > 符号 INDEX_MERGE 合并索引,使用多个单列索引搜索 select * from tb1 where name = 'alex' or nid in (11,22,33); REF 根据索引查找一个或多个值 select * from tb1 where name = 'seven'; EQ_REF 连接时使用primary key 或 unique类型 select tb2.nid,tb1.name from tb2 left join tb1 on tb2.nid = tb1.nid; CONST 常量 表最多有一个匹配行,因为仅有一行,在这行的列值可被优化器剩余部分认为是常数,const表很快,因为它们只读取一次。 select nid from tb1 where nid = 2 ; SYSTEM 系统 表仅有一行(=系统表)。这是const联接类型的一个特例。 select * from (select nid from tb1 where nid = 1) as A; possible_keys 可能使用的索引 key 真实使用的 key_len MySQL中使用索引字节长度 rows mysql估计为了找到所需的行而要读取的行数 ------ 只是预估值 extra 该列包含MySQL解决查询的详细信息 “Using index” 此值表示mysql将使用覆盖索引,以避免访问表。不要把覆盖索引和index访问类型弄混了。 “Using where” 这意味着mysql服务器将在存储引擎检索行后再进行过滤,许多where条件里涉及索引中的列,当(并且如果)它读取索引时,就能被存储引擎检验,因此不是所有带where子句的查询都会显示“Using where”。有时“Using where”的出现就是一个暗示:查询可受益于不同的索引。 “Using temporary” 这意味着mysql在对查询结果排序时会使用一个临时表。 “Using filesort” 这意味着mysql会对结果使用一个外部索引排序,而不是按索引次序从表里读取行。mysql有两种文件排序算法,这两种排序方式都可以在内存或者磁盘上完成,explain不会告诉你mysql将使用哪一种文件排序,也不会告诉你排序会在内存里还是磁盘上完成。 “Range checked for each record(index map: N)” 这个意味着没有好用的索引,新的索引将在联接的每一行上重新估算,N是显示在possible_keys列中索引的位图,并且是冗余的。
八、慢查询日志
1、概念
将mysql服务器中影响数据库性能的相关SQL语句记录到日志文件,通过对这些特殊的SQL语句分析,改进以达到提高数据库性能的目的。
2、慢查询日志参数
1
2
3
4
5
|
long_query_time : 设定慢查询的阀值,超出设定值的SQL即被记录到慢查询日志,缺省值为
10s
slow_query_log : 指定是否开启慢查询日志
log_slow_queries : 指定是否开启慢查询日志(该参数已经被slow_query_log取代,做兼容性保留)
slow_query_log_file : 指定慢日志文件存放位置,可以为空,系统会给一个缺省的文件host_name
-
slow.log
log_queries_not_using_indexes: 如果值设置为ON,则会记录所有没有利用索引的查询.
|
3、查看MySQL慢日志信息
1
2
3
4
|
#.查询慢日志配置信息 :
show variables like
'%query%'
;
#.修改配置信息
set
global
slow_query_log
=
on;
|
4、查看不使用索引参数状态
1
2
3
4
|
# 显示参数
show variables like
'%log_queries_not_using_indexes'
;
# 开启状态
set
global
log_queries_not_using_indexes
=
on;
|
5、查看慢日志显示的方式
1
2
3
4
5
|
#查看慢日志记录的方式
show variables like
'%log_output%'
;
#设置慢日志在文件和表中同时记录
set
global
log_output
=
'FILE,TABLE'
;
|
6、测试慢查询日志
1
2
3
4
5
|
#查询时间超过10秒就会记录到慢查询日志中
select sleep(
3
) FROM user ;
#查看表中的日志
select
*
from
mysql.slow_log;
|
九、大数据量分页优化
1、优化方案一
简单粗暴,就是不允许查看这么靠后的数据
2、优化方案二
在查询下一页时把上一页的行id作为参数传递给客户端程序,即select * from tb1 where id>3000000 limit 10;
还有一种方式,比如100页的10条数据 select * from tb1 where id>100*10 limit 10;
3、优化方案三延迟关联
我们在来分析一下这条语句为什么慢,机就处在这个 * 里面,这个表除了id主键肯定还有其他字段,
因为select * 所以mysql在沿着id主键走的时候要回行拿数据,走一下拿一下数据,如果把语句改成
select id from tb1 limit 3000000,10;你会发现时间缩短了一半;然后我们在拿id分别去取10条数据就行了;
语句就改成这样了:
select table.* from tb1 inner join ( select id from tb1 limit 3000000,10 ) as tmp on tmp.id=table.id;
4、三种方法优先选择
这三种方法最先考虑第一种,其次第二种,第三种是别无选择