1.表约束
在数据库中为了保证数据库插入时按照指定规则插入,引入表约束,常见表约束有
1.主键约束 primary key [ˈpraɪməri](不允许为空,不允许重复)如果是整数类型一般定义主键自动增长 auto_increment [ˈɔːtəʊ] [ˈɪŋkrəmənt],一般通过可视化连接工具添加。
CREATE TABLE `user` (
`id` int(11) NOT NULL AUTO_INCREMENT,
`name` char(10),
`password` char(10),
PRIMARY KEY (`id`)
)
2.唯一约束,也叫唯一索引,用于设置该字段不可重复。
ALTER TABLE `user`.`user` ADD UNIQUE INDEX index_name (`name`);
3.非空约束 not null 用于指定该字段不能为空一般在创表时指定或可视化连接工具添加。也可以和 DEFAULT 配合使用,表示不输入时该字段的默认值。
CREATE TABLE `user` (
`id` int(11) NOT NULL AUTO_INCREMENT,
`name` char(10) NOT NULL,
`password` char(10) NOT NULL DEFAULT 123456,
PRIMARY KEY (`id`)
)
4.外键约束,在创建外键约束时一定要保证两个表的数据库引擎相同且不能为 MYISAM,一般通过可视化连接工具添加。
ALTER TABLE `client` ADD CONSTRAINT `emp_id_fk` FOREIGN KEY (`emp_id`) REFERENCES `emp`(`id`);
一般创表命令如下
CREATE TABLE mytb(
id INT NOT NULL AUTO_INCREMENT,
NAME VARCHAR(100) NOT NULL,
pwd VARCHAR(40) NOT NULL,
birth DATE,
PRIMARY KEY ( id )
)ENGINE=INNODB DEFAULT CHARSET=utf8;
- 如果你不想字段为 NULL 可以设置字段的属性为 NOT NULL, 在操作数据库时如果输入该字段的数据为 NULL ,就会报错。
- AUTO_INCREMENT 定义列为自增的属性,一般用于主键,每次插入数据后数值会自动加 1。
- PRIMARY KEY 关键字用于定义列为主键。 可以使用多列来定义主键称为复合主键,列间以逗号分隔。
- ENGINE 设置存储引擎,使用 show engines 命令可以查看 MySQL 支持的数据库引擎,CHARSET 设置编码。
注意:utf8 与 utf8mb4 的区别在于 utf8mb4 兼容部分图标字体如:☺◐❀♬▯ 。如果引擎和字符编码在安装时设置好的则在创表时可以不指定。
2.数据类型
MySQL 中为了满足各种业务需要,提供很多数据类型,常用类型如下:
- INT 整数 4 字节
- BIGINT 长整数 8 字节
- FLOAT 单精度小数 单精度浮点数值
- DOUBLE 双精度小数 双精度浮点数值
- DECIMAL 小数值 对DECIMAL(P,D)
- DATE 日期值 YYYY-MM-DD
- TIME 时间值 HH:MM:SS
- DATETIME 混合日期和时间值 YYYY-MM-DD HH:MM:SS
- CHAR 定长字符串 0-255字节
- VARCHAR 变长字符串 0-65535 字节
- TEXT 长文本数据 0-65535 字节
- BLOB 二进制形式的数据 0-65535 字节
注意:
1.在 DECIMAL(P,D) 的语法中: P 是表示有效数字数的精度。 P 范围为 1〜65。 D 是表示小数点后的位数。 D 的范围是 0~30。MySQL 要求 D 小于或等于(<=)P。如:DECIMAL(6,2)中最多可以存储 6 位数字,小数位数为 2 位;因此,列的范围是从-9999.99 到 9999.99。
2.char(n) 和 varchar(n) 中括号中 n 代表字符的个数,并不代表字节个数,比如 CHAR(30) 就可以存储 30 个字符。char 是一种定长字符串。它一旦声明后无论存储的值是否达到该长度,都占有相应的字节长度。varchar 是一种变长字符串,它的长度由存储的内容决定,为存满的时以实际存储内容为主。
3.BLOB 是一个二进制大对象,可以容纳可变数量的二进制数据。有 4 种 BLOB 类型:TINYBLOB、BLOB、MEDIUMBLOB 和 LONGBLOB。它们区别在于可容纳存储范围不同。
4.TEXT 同样也有 4 种类型:TINYTEXT、TEXT、MEDIUMTEXT 和 LONGTEXT。它们的区别也是可存储的最大长度不同。具体使用哪种类型可根据实际情况选择。
3.视图
视图其实就是一张虚拟表,一张从别的表中导出的一张不完整或经过运算的虚拟表。
视图有什么用,当我们经常查询一个表中的某几个字段的时候,我们可以把这几个字段单独导出一个视图,以后查询的时候直接可以 select *,在数据库中只存放了视图的定义,没有存放视图的数据,视图是依赖原来表中的数据的,原表中的数据一旦发生变化,视图查询出的结果也会跟着变化。下面进行创建一个视图。
案例:给语文老师创建一个视图
CREATE VIEW vi_chinese_user
AS SELECT id,`name`,`chinese` FROM `student`;
视图在查询时和普通表查询语句完全相同。删除视图使用命令如下:
DROP VIEW `vi_chinese_user`;
4.触发器
触发器是由事件来触发某个操作,这些事件包括 insert、delete、update 语句。当数据库执行这些事件时,就会激活触发器执行相应的操作。
我们需要一个表,这里用 user 表,用来对该表进行增删改操作,另外需要一个 user_log 表,当对 user 表操作的时候,会激活我们的触发器,对 user_log 表做出相应的操作。
CREATE TRIGGER auto_save_time BEFORE INSERT
ON `user` FOR EACH ROW
INSERT INTO user_log(log_time) VALUES(NOW());
这条语句的意思是,创建一个触发器名字是 auto_save_time,在 user 表的任意行执行 insert 语句之前会激活触发器,并执行 insert into user_log(log_time) values(now()); 语句。下面我们对 user 表进行一个 insert 操作,可以看出,激活了触发器,并执行了那条语句。now() 表示当前时间括号前不能有空格。
查看触发器使用命令 show triggers;删除触发器使用命令 drop trigger 触发器名;触发器有 6 个 分别是插入前后,删除前后,修改前后六个,与前面案例中的 before 对应的还有 after。
例:创建一张表 student(id,name) 和 student_log (id,log_time,method),创建 3 个触发器,完成 student 表的所有修改操作的记录。
CREATE TABLE `student_log` (
`id` INT NOT NULL AUTO_INCREMENT,
`log_time` DATETIME NOT NULL,
`method` VARCHAR (50) NOT NULL,
PRIMARY KEY (`id`)
) ENGINE = INNODB CHARSET = utf8mb4 ;
CREATE TABLE `student` (
`id` INT NOT NULL AUTO_INCREMENT,
`name` VARCHAR (50) NOT NULL,
PRIMARY KEY (`id`)
) ENGINE = INNODB CHARSET = utf8mb4 ;
CREATE TRIGGER student_insert BEFORE INSERT
ON `student` FOR EACH ROW
INSERT INTO `student_log`(`log_time`,`method`) VALUES(NOW(),'添加');
CREATE TRIGGER student_update BEFORE UPDATE
ON `student` FOR EACH ROW
INSERT INTO `student_log`(`log_time`,`method`) VALUES(NOW(),'修改');
CREATE TRIGGER student_delete BEFORE DELETE
ON `student` FOR EACH ROW
INSERT INTO `student_log`(`log_time`,`method`) VALUES(NOW(),'删除');
5.存储过程
存储过程 (Stored Procedure) 是在大型数据库系统中,一组为了完成特定功能的 SQL 语句集 , 存储在数据库中,经过第一次编译后再次调用不需要再次编译,用户通过指定存储过程的名字并给出参数(如果该存储过程带有参数)来执行它,存储过程是数据库中的一个重要对象;存储过程中可以包含逻辑控制语句和 数据操纵语句 , 它可以接受参数,输出参数,返回单个或多个结果集以及返回值;
create table p_user(
id int primary key auto_increment,
name varchar(10),
sex char(2)
)ENGINE=InnoDB DEFAULT CHARSET=utf8;
insert into p_user(name,sex) values('A',"男");
insert into p_user(name,sex) values('B',"女");
insert into p_user(name,sex) values('C',"男");
创建存储过程(查询得到男性或女性的数量,如果传入的是 0 就获取女性的数量,否则获取男性的数量)
CREATE PROCEDURE ges_user_count(IN sex_id INT, OUT user_count INT)
BEGIN
IF sex_id = 0 THEN
SELECT COUNT(*) FROM p_user WHERE p_user.sex = '女' INTO user_count;
ELSE
SELECT COUNT(*) FROM p_user WHERE p_user.sex = '男' INTO user_count;
END IF;
END
调用存储过程
SET @user_count = 0;
CALL ges_user_count(1, @user_count);
SELECT @user_count;
查询得到男性或女性的数量, 如果传入的是 0 就女性否则是男性。
6.数据处理函数
在查询数据时有事需要对查出的字段进行处理,如小写转大写,切割等。这时就需要使用数据库的字符串处理函数,如下是 mysql 常用字符串处理函数。
CHARSET(字段) 返回字段的字符集
CONCAT(string2 [,... ]) 连接字串
INSTR(string ,substring ) 返回substring在string中出现的位置,没有返回0
UCASE(string2 ) 转换成大写
LCASE(string2 ) 转换成小写
LEFT(string2 ,length ) 从string2中的左边起取length个字符
LENGTH(string ) 返回string字节数量
REPLACE(str ,search_str ,replace_str ) 在str中用replace_str替换search_str
STRCMP(string1 ,string2 ) 逐字符比较两字串大小,返回1、0和-1
SUBSTRING(str , position [,length ]) 从str的position开始,取length个字符
LTRIM(string2 ) RTRIM (string2 ) trim 去除前端空格或后端空格
eg:
SELECT CHARSET(`name`) FROM `emp`;
SELECT CONCAT(`name`,`pwd`) FROM `emp`;
SELECT INSTR(`pwd`,'2') FROM `emp`;
SELECT UCASE(`name`) FROM `emp`;
SELECT LCASE(`name`) FROM `emp`;
SELECT LEFT(`pwd`,2) FROM `emp`;
SELECT LENGTH(`pwd`) FROM `emp`;
SELECT STRCMP('b' ,'b' ) FROM `emp`;
SELECT SUBSTRING(`pwd`,2,2) FROM `emp`;
SELECT LTRIM(`name`) FROM `emp`;
SELECT RTRIM(LTRIM(`name`)) FROM `emp`;
数字处理函数
以下是 mysql 常用的数字处理函数
ABS(number2 ) 绝对值
CEILING (number2 ) 向上取整
FLOOR (number2 ) 向下取整
FORMAT (number,小数位数) 保留小数位数
HEX (DecimalNumber ) 转十六进制
LEAST (number , number2 [,..]) 求最小值
MOD (numerator ,denominator ) 求余
RAND([seed]) 随机数
eg:
SELECT `name`,ABS(`english`) FROM `student`;
SELECT `name`,CEILING(100.00000001) FROM `student`;
SELECT `name`,FLOOR (100.00000001) FROM `student`;
SELECT `name`,`english`,FORMAT (`english`,2) FROM `student`;
SELECT `name`,`english`,HEX (`english`) FROM `student`;
SELECT `name`,RAND() FROM `student`;
SELECT `name`,CEILING(RAND()*10) FROM `student`;
日期时间处理函数
获取当前的日期(包含具体的时分秒)
SELECT NOW(),LOCALTIME(),SYSDATE()
获取当前的日期(不包含具体的时分秒)
SELECT CURRENT_DATE(),CURDATE()
日期与时间戳之间的转化
SELECT UNIX_TIMESTAMP(NOW()),FROM_UNIXTIME(1629882598)
获取当前的时间(时分秒)
SELECT CURRENT_TIME(),CURTIME()
获取月份
MONTH(date)返回月份的数值; MONTHNAME(date)返回月份的英文名称
SELECT MONTH(NOW()),MONTHNAME(NOW())
获取天的函数
DAYOFMONTH(date)返回日期属于当前月的第几天; DAYOFYEAR(date)返回日期属于当前年的第几天
SELECT DAYOFMONTH(NOW()),DAYOFYEAR(NOW())
获取星期的函数
DAYNAME(date)返回星期几的英文名字;DAYOFWEEK(date)返回星期几的数值,返回值的范围是1-7,1表示星期日,以此类推; WEEKDAY(date)返回星期几的数值,返回值的范围是0-6,0表示星期一,以此类推
SELECT DAYNAME(NOW()),DAYOFWEEK(NOW()),WEEKDAY(NOW())
两个日期间的间隔天数
TO_DAYS(date)日期date与默认日期(000年1月1日)之间间隔的天数;FROM_DAYS(number)默认日期经过 number 天后的日期;DATEDIFF(date1,date2)获取指定两个日期间的相隔天数,date1-date2
SELECT TO_DAYS(NOW()),FROM_DAYS(738166),DATEDIFF(NOW(),'2020-01-01')
日期的加减法
DATE_ADD(date,INTERVAL expr type),在 date 的基础上添加上指定的时间间隔后的日期。
DATE_SUB(date,INTERVAL expr type)在 date 的基础上减去上指定的时间间隔后的日期。
type 值常用的有:year、month、day、hour、minute、second、microsecond(毫秒)、week、quarter
SELECT DATE_ADD(NOW(),INTERVAL 1 DAY),DATE_SUB(NOW(),INTERVAL 1 DAY)
7.索引
MySQL 索引的建立对于 MySQL 的高效运行是很重要的,索引可以大大提高 MySQL 的检索速度。 拿汉语字典的目录页(索引)打比方,我们可以按拼音、笔画、偏旁部首等排序的目录(索引)快速查找到需要的字。
索引分单列索引和组合索引。单列索引,即一个索引只包含单个列,一个表可以有多个单列索引,但这不是组合索引。
组合索引,即一个索引包含多个列。 创建索引时,你需要确保该索引是应用在 SQL 查询语句的条件(一般作为 WHERE 子句的条件)。
实际上,索引也是一张表,该表保存了主键与索引字段,并指向实体表的记录。
注意:如果过多的使用索引将会降低更新表的速度,如对表进行 INSERT、UPDATE 和 DELETE。因为更新表时,MySQL 不仅要保存数据,还要保存一下索引文件。 建立索引会占用磁盘空间的索引文件。
索引在 mysql 中分为普通索引,唯一索引和全文索引
1.普通索引
CREATE INDEX indexName ON `users` (username(length))
如果是 CHAR,VARCHAR 类型,length 可以小于字段实际长度;如果是 BLOB 和 TEXT 类型,必须指定 length。
2.唯一索引:它与前面的普通索引类似,不同的就是:索引列的值必须唯一,但允许有空值。
CREATE UNIQUE INDEX indexName ON mytable(username(length))
3.全文索引:针对大文本 TEXT 的内容查找
CREATE FULLTEXT INDEX indexName ON `users` (username)
由于全文索引需要对该字段进行分词,对中文的支持不是很好,所以全文索引在实际开发中不建议使用。在一些大文本的内容管理系统中一般使用 elasticsearch [ɪˈlæstɪk] [sɜːtʃ] 完成全文检索。
使用全文索引的格式: MATCH (columnName) AGAINST ('string')
eg:
SELECT * FROM `student` WHERE MATCH(`name`) AGAINST('聪')
删除索引:
ALTER TABLE `users` DROP INDEX `indexName`
8.连接查询
两表连接分为内连接和外连接:
内连接(inner join [ˈɪnə(r)] [dʒɔɪn]):返回连接表中符合连接条件及查询条件的数据行。
外连接 outer [ˈaʊtə(r)]: 分为
- 左外连接(left outer join)可以简写 left join
- 右外连接(right outer join)可以简写为 right join
与内连接不同的是,外连接不仅返回连接表中符合连接条件及查询条件的数据行,也返回左表(左外连接时)或右表(右外连接时)中仅符合查询条件但不符合连接条件的数据行。
外连接查询(重点)
from 表1 连接类型 表2 [on (连接条件)] [where (查询条件)]
左外连接查询
使用 left outer join 关键字,在 on 子句中设定连接条件不仅包含符合连接条件的数据行,还包含左表全部数据,右表没连上的不显示。左连接时左表优先全部显示。
内连接查询 (重点)
SELECT e.id '员工编号',e.`name` '员工名字',c.`name` '客户名',c.`tel` '客户电话' FROM `emp` e ,`client` c WHERE e.`id`=c.`emp_id`;
SELECT e.id '员工编号',e.`name` '员工名字',c.`name` '客户名',c.`tel` '客户电话' FROM `emp` e INNER JOIN `client` c ON e.`id`=c.`emp_id`;
SELECT e.id '员工编号',e.`name` '员工名字',c.`name` '客户名',c.`tel` '客户电话' FROM `emp` e LEFT OUTER JOIN `client` c ON e.`id`=c.`emp_id`;
9.子查询与联合查询
子查询
子查询也叫嵌套查询,是指在 select 子句或者 where 子句中又嵌入 select 查询语句。
SELECT * FROM `emp` WHERE id IN(SELECT emp_id FROM `client` WHERE id IN(2,4,5))
联合查询
合并两条查询语句的查询结果,去掉其中的重复数据行,然后返回没有重复数据行的查询结果。联合查询使用 union 关键字
SELECT * FROM `client` WHERE emp_id = 2 UNION SELECT * FROM `client` WHERE emp_id = 1;
SELECT `id`,`name` FROM `client` WHERE emp_id = 2 UNION SELECT `id`,`name` FROM `emp`;
注意:两条查询语句返回的结构和字段的数量必须相同,否则无法合并。
10.报表函数
报表函数也叫聚合函数,一般用于统计汇总数据库表信息。通常和分组函数配合使用,如果不使用分组函数则认为该表的全部数据为一个组。常用报表函数如下:
①计数函数-COUNT,如果参数是字段名,则统计该字段不为空的记录数。
Select count(*)|count(列名) from tablename [WHERE where_definition]
eg:
SELECT COUNT(*) FROM `client`
②求和函数-SUM
Select sum(列名){,sum(列名)…} from tablename [WHERE where_definition]
eg:
SELECT SUM(`chinese`),SUM(`english`),SUM(`math`) FROM `student` WHERE `name` LIKE '%李%'
③平均函数-AVG
Select avg(列名){,avg(列名)…} from tablename [WHERE where_definition]
eg:
SELECT AVG(`chinese`),AVG(`english`),AVG(`math`) FROM `student` WHERE `name` LIKE '%李%'
④边角函数-MAX/MIN
Select max(列名) from tablename [WHERE where_definition]
eg:
SELECT MAX(`english`) FROM `client`;
SELECT MIN(`english`) FROM `client`;
11.分组查询
分组查询是按一个或多个字段进行分组,字段值相同的为一组进行分组统计,其语法格式为:
[select …] from … [where…] [ group by … [having… ]] [ order by … ][limit n]
其中 group by 子句指定按照哪些字段分组。having 子句设定分组查询条件。
例:
数据准备:
CREATE TABLE `staff` (
`id` int(11) NOT NULL AUTO_INCREMENT,
`name` varchar(20) NOT NULL,
`dept` varchar(10) DEFAULT NULL,
`salary` double DEFAULT NULL,
`edlevel` int(11) DEFAULT NULL,
`hiredate` datetime DEFAULT NULL,
PRIMARY KEY (`id`)
) ENGINE=InnoDB AUTO_INCREMENT=9 DEFAULT CHARSET=utf8mb4;
insert into `staff`(`id`,`name`,`dept`,`salary`,`edlevel`,`hiredate`) values (1,'张三','开发部',2000,3,'2020-02-10 15:19:59');
insert into `staff`(`id`,`name`,`dept`,`salary`,`edlevel`,`hiredate`) values (2,'李四','开发部',2500,3,'2019-07-16 15:20:01');
insert into `staff`(`id`,`name`,`dept`,`salary`,`edlevel`,`hiredate`) values (3,'王五','设计部',2600,5,'2019-09-25 15:20:03');
insert into `staff`(`id`,`name`,`dept`,`salary`,`edlevel`,`hiredate`) values (4,'王六','设计部',2300,4,'2020-06-05 15:20:05');
insert into `staff`(`id`,`name`,`dept`,`salary`,`edlevel`,`hiredate`) values (5,'马七','设计部',2100,4,'2019-11-11 15:20:07');
insert into `staff`(`id`,`name`,`dept`,`salary`,`edlevel`,`hiredate`) values (6,'赵八','销售部',3000,5,'2020-06-05 15:20:19');
insert into `staff`(`id`,`name`,`dept`,`salary`,`edlevel`,`hiredate`) values (7,'钱九','销售部',3000,7,'2020-06-05 15:20:21');
insert into `staff`(`id`,`name`,`dept`,`salary`,`edlevel`,`hiredate`) values (8,'孙十','销售部',3000,7,'2020-06-05 15:20:26');
查询:
1.列出每个部门最高薪水的结果
SELECT `dept`, MAX(`salary`) AS '最高薪资' FROM `staff` GROUP BY `dept`;
2.查询每个部门的总的薪水数
SELECT `dept`, SUM( `salary` ) AS '总薪水' FROM `staff` GROUP BY `dept`;
3.查询公司 2020 年入职的各个部门每个级别里的最高薪水
SELECT `dept`, `edlevel`, MAX( `salary` ) AS maximum FROM staff WHERE `hiredate` > '2020-01-01' GROUP BY `dept`, `edlevel`;
4.查询雇员数超过2个的部门的最高和最低薪水并按照部门升序查询
SELECT `dept`, MAX( `salary` ) AS maximum, MIN( `salary` ) AS minimum FROM `staff` GROUP BY `dept` HAVING COUNT(*) > 2 ORDER BY DEPT
5.查询雇员平均工资大于或等于 2300 的部门的最高和最低薪水并按照最高薪水排序
SELECT `dept`, MAX( `salary` ) AS maximum, MIN( `salary` ) AS minimum FROM `staff` GROUP BY `dept` HAVING AVG( `salary` ) >= 2300 ORDER BY MAX(`salary`)
分组连接函数:在 MySQL 中将分组相同的行数据合并起来使用 group_concat 函数,该函数语法如下:
group_concat([DISTINCT] 要连接的字段 [Order BY ASC/DESC 排序字段] [Separator '分隔符'])
案例:以 id 分组,把 name 字段的值打印在一行,逗号分隔(默认)
select id,group_concat(name) from aa group by id;
案例:以id分组,把name字段的值打印在一行,分号分隔
select id,group_concat(name separator ';') from aa group by id;
案例:以id分组,把去冗余的name字段的值打印在一行,并用逗号分隔
select id,group_concat(distinct name) from aa group by id;
案例:以id分组,把name字段的值打印在一行,逗号分隔,以name排倒序
select id,group_concat(name order by name desc) from aa group by id;
12.索引最左匹配原则
创建测试表并建立了复合(联合)索引(a,b,c)
create table test(
a int ,
b int,
c int,
d int,
key index_abc(a,b,c)
)engine=InnoDB default charset=utf8;
插入 10000 条数据
DROP PROCEDURE IF EXISTS proc_initData;
DELIMITER $
CREATE PROCEDURE proc_initData()
BEGIN
DECLARE i INT DEFAULT 1;
WHILE i<=10000 DO
INSERT INTO test(a,b,c,d) VALUES(i,i,i,i);
SET i = i+1;
END WHILE;
END $
CALL proc_initData();
explain 指令查看下面SQL
explain select * from test where a<10 ;
explain select * from test where a<10 and b <10;
explain select * from test where a<10 and b <10 and c<10;
将 a,b出现顺序换一下,a,b,c 出现顺序换一下
explain select * from test where b<10 and a <10;
explain select * from test where b<10 and a <10 and c<10;
查了下资料发现:mysql 查询优化器会判断纠正这条sql语句该以什么样的顺序执行效率最高,最后才生成真正的执行计划。所以,当然是我们能尽量的利用到索引时的查询顺序效率最高咯,所以 mysql 查询优化器会最终以这种顺序进行查询执行。
mysql> explain select * from test where b<10 and a <10;
+----+-------------+-------+------------+-------+---------------+-----------+---------+------+------+----------+-----------------------+
| id | select_type | table | partitions | type | possible_keys | key | key_len | ref | rows | filtered | Extra |
+----+-------------+-------+------------+-------+---------------+-----------+---------+------+------+----------+-----------------------+
| 1 | SIMPLE | test | NULL | range | index_abc | index_abc | 5 | NULL | 9 | 33.33 | Using index condition |
+----+-------------+-------+------------+-------+---------------+-----------+---------+------+------+----------+-----------------------+
1 row in set, 1 warning (0.00 sec)
为什么 b<10 and c <10,没有用到索引?而 a<10 and c <10用到了?
mysql> explain select * from test where b<10 and c <10;
+----+-------------+-------+------------+------+---------------+------+---------+------+-------+----------+-------------+
| id | select_type | table | partitions | type | possible_keys | key | key_len | ref | rows | filtered | Extra |
+----+-------------+-------+------------+------+---------------+------+---------+------+-------+----------+-------------+
| 1 | SIMPLE | test | NULL | ALL | NULL | NULL | NULL | NULL | 10005 | 11.11 | Using where |
+----+-------------+-------+------------+------+---------------+------+---------+------+-------+----------+-------------+
1 row in set, 1 warning (0.00 sec)
当 b+ 树的数据项是复合的数据结构,比如(name,age,sex)的时候,b+ 数是按照从左到右的顺序来建立搜索树的,比如当(张三,20,F)这样的数据来检索的时候,b+ 树会优先比较 name 来确定下一步的所搜方向,如果 name 相同再依次比较 age 和 sex,最后得到检索的数据;但当(20,F)这样的没有 name 的数据来的时候,b+ 树就不知道下一步该查哪个节点,因为建立搜索树的时候 name 就是第一个比较因子,必须要先根据 name 来搜索才能知道下一步去哪里查询。比如当(张三,F)这样的数据来检索时,b+ 树可以用 name 来指定搜索方向,但下一个字段 age 的缺失,所以只能把名字等于张三的数据都找到,然后再匹配性别是 F 的数据了, 这个是非常重要的性质,即索引的最左匹配特性。