第一范式:每个列都不可以再拆分。
第二范式:在第一范式的基础上,非主键列完全依赖于主键,而不能是依赖于主键的一部分。
第三范式:在第二范式的基础上,非主键列只依赖于主键,不依赖于其他非主键。
MySQL 基本架构概览:Server 层 +存储引擎层:
连接器: 身份认证和权限相关(登录 MySQL 的时候)。
查询缓存: 执行查询语句的时候,会先查询缓存(MySQL 8.0 版本后移除,因为这个功能不太实用)
分析器: 没有命中缓存的话,SQL 语句就会经过分析器,分析器说白了就是要**先看你的 SQL 语句要干嘛,再检查你的 SQL 语句语法是否正确。**
优化器: 按照 MySQL 认为最优的方案去执行。
执行器: 执行语句,然后从存储引擎返回数据。
- **减少 I/O 操作,**I/O 是 DBMS 最容易出现瓶颈的地方,可以说数据库操作中有大量的时间都花在了 I/O 上。
- 降低 CPU 的计算量,在 SQL 语句中使用 GROUP BY、ORDER BY 等这些语句会消耗大量的 CPU 计算资源,因此我们需要从全局出发,不仅需要考虑数据库的 I/O 性能,还需要考虑 CPU 计算、内存使用情况等。
1、选择合适的数据库
2、优化表设计,三大范式、表字段的数据类型选择,关系到了查询效率的高低以及存储空间的大小。
3、优化查询逻辑,改变 SQL 语句的内容让 SQL 执行效率更高效
4、优化物理查询,高效地建立索引,并通过这些索引来做各种优化。
5、使用 Redis 或 Memcached 作为缓存
6、库级优化
- 读写分离的方式降低主数据库的负载,比如用主数据库(master)完成写操作,用从数据库(slave)完成读操作。
- 数据库分库分表。
drop:删除整张表
delete和truncate只删除表的数据不删除表的结构
- delete 删除可以撤销,删除某个内容,想删除部分数据行时候,用delete,并且带上where子句
DELETE FROM infor WHERE subjects = '小米'; ROLLBACK;
- truncate 删除不可撤销,删除所有内容,保留表而删除所有数据的时候用truncate
如果使用delete删除,是真的把这个记录删除了吗?
delete并没有删掉底层用户数据, 只是将 记录中的 记录头信息中的 deleted_flag=1,因此你使用 delete 删除表中的数据,表文件在磁盘上所占空间不会变小。如果下一次insert更大的记录,delete之后的空间不会被重用,如果插入的记录小于等于delete的记录空会被重用。 不删除的原因是,数据都是连续存储,如果是真的了,后续的数据记录都要移位。
对于这种情况,我们通常可以使用下面这两个命令就能解决数据空洞问题。
alter table t engine=InnoDB optimize table t
这个命令的原理就是重建表,就是建立一个临时表 B,然后把表 A(存在数据空洞的表) 中的所有数据查询出来,接着把数据全部重新插入到临时表 B 中,最后再用临时表 B 替换表 A 即可,这就是重建表的过程。
DROP TABLE IF EXISTS `player`;
CREATE TABLE `player` (
`player_id` int(11) NOT NULL AUTO_INCREMENT,
`team_id` int(11) NOT NULL,
#字符集utf8,排序规则是utf8_general_ci,代表对大小写不敏感
`player_name` varchar(255) CHARACTER SET utf8 COLLATE utf8_general_ci NOT NULL,
`height` float(3, 2) NULL DEFAULT 0.00,
PRIMARY KEY (`player_id`) USING BTREE,
UNIQUE INDEX `player_name`(`player_name`) USING BTREE
) ENGINE = InnoDB CHARACTER SET = utf8 COLLATE = utf8_general_ci ROW_FORMAT = Dynamic;
SELECT DISTINCT attack_range, name FROM heros #DISTINCT 需要放到所有列名的前面
SELECT name, hp_max FROM heros ORDER BY hp_max DESC #ORDER BY位于 SELECT 语句的最后一条子句 一或多个列名,按第一个列先进行排序
1、关键字的顺序是不能颠倒的:
SELECT ... FROM ... WHERE ... GROUP BY ... HAVING ... ORDER BY ...
2、SELECT 语句的执行顺序
FROM > WHERE > GROUP BY > HAVING > SELECT 的字段 > DISTINCT > ORDER BY > LIMIT
# 拿数据——分组——过滤得结果表——提取字段——处理字段——显示字段
# from先拿到原始数据,尤其对于多表需要进行聚合得到最终的虚拟表 vt1
# 在此基础上再进行 WHERE 阶段。在这个阶段中,会根据 vt1 表的结果进行筛选过滤,得到虚拟表 vt2。
# GROUP 和 HAVING 阶段,在虚拟表 vt2 的基础上进行分组和分组过滤,得到中间的虚拟表 vt3 和 vt4。
# 完成了条件筛选部分之后,就可以筛选表中提取的字段,也就是进入到 SELECT 提取想要的字段和 DISTINCT 阶段过滤掉重复的行
# 提取了想要的字段数据之后,就可以按照指定的字段进行排序,也就是 ORDER BY 阶段,得到虚拟表 vt6。
# 在 vt6 的基础上,取出指定行的记录,也就是 LIMIT 阶段,得到最终的结果,对应的是虚拟表 vt7。
3、SELECT *
增加了数据库的负担。生产时应写清列名,减少数据表查询的网络传输量
SELECT name, role_main, role_assist, hp_max, mp_max, birthdate
FROM heros
WHERE (role_main IN ('法师', '射手') OR role_assist IN ('法师', '射手'))
AND DATE(birthdate) NOT BETWEEN '2016-01-01' AND '2017-01-01'
ORDER BY (hp_max + mp_max) DESC
匹配任意字符串出现的任意次数,需要使用(%)通配符,(%)代表一个或多个字符,而(_
)只代表一个字符
SELECT name FROM heros WHERE name LIKE '% 太 %'
SELECT COUNT(*), role_main FROM heros GROUP BY role_main
HAVING 的作用和 WHERE 一样,都是起到过滤的作用,只不过 WHERE 是用于数据行,而 HAVING 则作用于分组。选择大于 5 的分组,即HAVING num > 5
SELECT COUNT(*) as num, role_main, role_assist FROM heros
GROUP BY role_main, role_assist
HAVING num > 5
ORDER BY num DESC
笛卡尔乘积是一个数学运算。假设我有两个集合 X 和 Y,那么 X 和 Y 的笛卡尔积就是 X 和 Y 的所有可能组合,也就是第一个对象来自于 X,第二个对象来自于 Y 的所有可能。
SELECT * FROM player, team
SELECT * FROM player LEFT JOIN team ON player.team_id = team.team_id #左外连接,左表主表
SELECT * FROM player RIGHT JOIN team ON player.team_id = team.team_id #右外连接
SELECT * FROM player FULL JOIN team ON player.team_id = team.team_id #全连接=左右表匹配的数据 + 左表没有匹配到的数据 + 右表没有匹配到的数据
SELECT ...FROM table1 JOIN table2 ON table1 和 table2 的连接条件
SELECT ...JOIN table3 ON table2 和 table3 的连接条件
例如:连表查询:查找选修了音乐的学生的数学成绩
select 查询列表 from 表1 别名 【连接类型】join 表2 别名 on 连接条件 【where 筛选条件】 【group by 分组】 【having 筛选条件】 【order by 排序列表】
SELECT `last_name`,e.job_id,`salary` FROM employees e JOIN jobs j ON e.job_id = j.job_id WHERE salary>10000; #外连接:查询结果 = 内连接结果 + 主表中有而从表没有的记录
#查询部门名为SAL或IT的员工信息
#主表是部门,从表是员工表,连接条件是部门号相等,筛选条件是name
SELECT d.department_name,last_name FROM departments d LEFT JOIN employees e ON d.department_id = e.department_id WHERE d.department_name IN('SAL','IT');
String sql = "select * from user_table where username=' "+userName+" ' and password=' "+password+" '";
--当输入了上面的用户名和密码,上面的SQL语句变成:
SELECT * FROM user_table WHERE username='’or 1 = 1 -- and password='’"""
--分析SQL语句:
--条件后面username=”or 1=1 用户名等于 ” 或1=1 那么这个条件一定会成功;
--然后后面加两个-,这意味着注释,它将后面的语句注释,让他们不起作用,这样语句永远都
--能正确执行,用户轻易骗过系统,获取合法身份。
--这还是比较温柔的,如果是执行
SELECT * FROM user_table WHEREusername='' ;DROP DATABASE (DB Name) --' and password=''-
-其后果可想而知…"""
解决办法:预编译
select*from tablename where username=? and password=? #变成这样
存储引擎说白了就是如何存储数据、如何为存储的数据建立索引和如何更新、查询数据等技术的实现方法。
MySQL 当前默认的存储引擎是 InnoDB,并且在 5.7 版本所有的存储引擎中只有 InnoDB 是事务性存储引擎,也就是说只有 InnoDB 支持事务
mysql> show engines;//一共9种
1.是否支持行级锁。MyISAM 只有表级锁,一锁就是锁住了整张表,而 InnoDB 支持行级锁和表级锁,默认为行级锁。
2.是否支持事务。MyISAM 不提供事务支持。InnoDB 提供事务支持,具有提交(commit)和回滚(rollback)事务的能力。
3.是否支持外键,MyISAM 不支持,而 InnoDB 支持。
4.是否支持数据库异常崩溃后的安全恢复,使用 InnoDB 的数据库在异常崩溃后,数据库重新启动的时候会保证数据库恢复到崩溃前的状态。
5.是否支持 MVCC,MyISAM 不支持,而 InnoDB 支持。
表级锁和行级锁对比:
表级锁: MySQL 中锁定 粒度最大 的一种锁,对当前操作的整张表加锁,实现简单,资源消耗也比较少,加锁快,不会出现死锁。其锁定粒度最大,触发锁冲突的概率最高,并发度最低,MyISAM 和 InnoDB 引擎都支持表级锁。
行级锁: MySQL 中锁定 粒度最小 的一种锁,只针对当前操作的行进行加锁。 行级锁能大大减少数据库操作的冲突。其加锁粒度最小,并发度高,但加锁的开销也最大,加锁慢,会出现死锁。
啥叫外键:
关系型数据库中的一条记录中有若干个属性,若其中某一个属性组能唯一标识一条记录,该属性组就是主键,学生表(学号,姓名,性别,班级) 中的学号,成绩表中的学号不是成绩表的主键,但它和学生表中的学号相对应,并且学生表中的学号是学生表的主键,则称成绩表中的学号是学生表的外键
比如,A表中的一个字段,是B表的主键,那他就可以是A表的外键。
InnoDB与MyISAM各自的优势是什么,能用在什么场合
大多数时候我们使用的都是 InnoDB 存储引擎,在某些读密集的情况下,使用 MyISAM 也是合适的。不过,前提是你的项目不介意 MyISAM 不支持事务、崩溃恢复等缺点
大量读,几乎没有写请求 和 小于5.6.4版本需要全文索引,选择 MyISAM,其他情况选择InnoDB。MyISAM 的性能还行,各种特性也还不错(比如全文索引、压缩、空间函数等
MySQL什么时候用表级锁,什么时候用行级锁?
对于InnoDB表,在绝大部分情况下都应该使用行级锁,因为事务和行锁往往是我们之所以选择InnoDB表的理由。但在个另特殊事务中,也可以考虑使用表级锁。
第一种情况是:事务需要更新大部分或全部数据,表又比较大,如果使用默认的行锁,不仅这个事务执行效率低,而且可能造成其他事务长时间锁等待和锁冲突,这种情况下可以考虑使用表锁来提高该事务的执行速度。
第二种情况是:事务涉及多个表,比较复杂,很可能引起死锁,造成大量事务回滚。这种情况也可以考虑一次性锁定事务涉及的表,从而避免死锁、减少数据库因事务回滚带来的开销。
范围查询的时候用的是行级锁还是表级锁?
用主键和其他索引查询时是行锁,当索引失效或者是不使用索引时:是表锁
原子性(Atomicity) : 事务是最小的执行单位,不允许分割。事务的原子性确保动作要么全部完成,要么完全不起作用;
一致性(Consistency): 数据库在进行事务操作后,会由原来的一致状态,变成另一种一致的状态。当事务提交后或回滚后,数据库的完整性约束不能被破坏。
隔离性(Isolation): 每个事务彼此独立,不会受到其他事务的执行影响。一个事务在提交之前,对其他事务都是不可见的。
持久性(Durabilily): 事务提交之后对数据的修改是持久性的,即使在系统出故障的情况下,比如系统崩溃或者存储介质发生故障,数据的修改依然是有效的原子性是基础,隔离性是手段,一致性是约束条件,而持久性是我们的目的
原子性和一致性有什么区别?
一致性是最基本的属性,其它的三个属性都为了保证一致性而存在的。
- 原子性,一个事务要么完全提交要么完全回滚。原子性不能保证一致性。从单个事务的角度看,不管是事务 1 还是事务 2,它们都保证的原子性,但最终,它们并没有保证数据库的一致性
- 一致性,一个查询发起后,不管数据发生了多少变化多少事务,查询结果应当为发起查询时间一致的数据。
事务的原子性和持久性是由事务日志(transaction log)保证的
undo logo
保证事务的原子性想要保证事务的原子性,就需要在异常发生时,对已经执行的操作进行回滚,而在 MySQL 中,恢复机制是通过回滚日志(undo log)实现的,所有事务进行的修改都会先记录到这个回滚日志中,然后在对数据库中的对应行进行写入。如果有多个相互依赖的事务,Transaction1 由于执行出现问题发生回滚时,为了保证事务的原子性,就会将 Transaction2 和 Transaction3 中的工作全部回滚,这种情况也叫做级联回滚,undoLog中记录的就是多版本数据,用于快照读和事务失败后的数据回滚
redo logo
保证事务的持久性一旦事务被提交,那么数据一定会被写入到数据库中并持久存储起来。MySQL 使用重做日志(redo log)实现事务的持久性,重做日志由两部分组成,一是内存中的重做日志缓冲区,因为重做日志缓冲区在内存中,所以它是易失的,另一个就是在磁盘上的重做日志文件,它是持久的。事务中尝试对数据进行修改时,它会先将数据从磁盘读入内存,并更新内存中缓存的数据,然后生成一条重做日志并写入重做日志缓存,当事务真正提交时,MySQL 会将重做日志缓存中的内容刷新到重做日志文件,再将内存中的数据更新到磁盘上
丢失修改(Lost to modify): 指在一个事务读取一个数据时,另外一个事务也访问了该数据,那么在第一个事务中修改了这个数据后,第二个事务也修改了这个数据。这样第一个事务内的修改结果就被丢失,因此称为丢失修改
脏读(Dirty read): 当一个事务正在访问数据并且对数据进行了修改,而这种修改还没有提交到数据库中,这时另外一个事务也访问了这个数据,然后使用了这个数据。因为这个数据是还没有提交的数据,依据“脏数据”所做的操作可能是不正确的。
不可重复读(Unrepeatableread): 指在一个事务内多次读同一数据。在这个事务还没有结束时,另一个事务也访问该数据。那么,在第一个事务中的两次读数据之间,由于第二个事务的修改导致第一个事务两次读取的数据可能不太一样。这就发生了在一个事务内两次读到的数据是不一样的情况,因此称为不可重复读。
幻读(Phantom read): 它发生在一个事务读取了几行数据,接着另一个并发事务插入了一些数据时。在随后的查询中,第一个事务就会发现多了一些原本不存在的记录,就好像发生了幻觉一样,所以称为幻读。
不可重复读和幻读区别:
不可重复读的重点是修改,比如多次读取一条记录发现其中某些列的值被修改。
幻读的重点在于新增或者删除比如多次读取一条记录发现记录增多或减少了。
RAED UNCOMMITED
读取未提交:使用查询语句不会加锁,可能会读到未提交的行,出现脏读、幻读、不可重复读;READ COMMITED
读取已提交:只对记录加锁,而不会在记录之间加锁,所以允许新的记录插入到被锁定记录的附近,多次使用查询语句时,可能得到不同的结果,可能会发生不可重复读,幻读;REPEATABLE READ
可重复读:多次读取同一范围的数据会返回第一次查询的快照,不会返回不同的数据行,但是可能发生幻读;SERIALIZABLE
可串行化:InnoDB 隐式地将全部的查询语句加上共享锁,解决了幻读的问题;
MySQL InnoDB 存储引擎的默认支持的隔离级别是 REPEATABLE-READ(可重读)。
隔离级别越低,事务请求的锁越少,大部分数据库系统的隔离级别都是 READ-COMMITTED(读取提交内容)
InnoDB 存储引擎在 分布式事务 的情况下一般会用到 SERIALIZABLE(可串行化) 隔离级别
MySQL如何解决脏读、不可重复读、幻读
读取已提交,加行锁,解决幻读。但是每个 select 语句都有自己的一份快照,而不是一个事务一份,所以在不同的时刻,查询出来的数据可能是不一致的。
可重复读,MySQL 采用了 MVVC (多版本并发控制) 的方式。读提交和可重复读的区别就是在快照的创建上,可重复读仅在事务开始是创建一次,而读提交每次执行语句的时候都要重新创建一次。
正是
Read View
生成时机的不同,从而造成读取已提交,可重复读级别下快照读的结果不同
- 可重复读,某个事务的对某条记录的第一次快照读会创建一个快照及Read View, 将当前系统活跃的其他事务记录起来,此后在调用快照读的时候,还是使用的是同一个Read View,所以只要当前事务在其他事务提交更新之前使用过快照读,那么之后的快照读使用的都是同一个Read View,所以对之后的修改不可见;
- 读取已提交,事务中,每次快照读都会新生成一个快照和Read View, 这就是我们在读取已提交级别下的事务中可以看到别的事务提交的更新的原因
- 解决幻读,用的也是锁,把行锁和间隙锁合并在一起,解决幻读的问题,这个锁叫做 Next-Key锁。
在数据库中会为索引维护一套B+树,用来快速定位行记录。B+索引树是有序的,所以会把这张表的索引分割成几个区间。
update user set name='风筝2号’ where age = 10;
的时候,由于条件 where age = 10 ,数据库不仅在 age =10 的行上添加了行锁,而且在这条记录的两边,也就是(负无穷,10]、(10,30]这两个区间加了间隙锁,从而导致事务B插入操作无法完成,只能等待事务A提交。不仅插入 age = 10 的记录需要等待事务A提交,age<10、10
MVCC
,全称Multi-Version Concurrency Control
,即多版本并发控制。MVCC是一种并发控制的方法,为事务分配单向增长的时间戳,为每个修改保存一个版本,版本与事务时间戳关联,读操作只读该事务开始前的数据库的快照,使得读写操作没有冲突
- 在并发读写数据库时,可以做到在读操作时不用阻塞写操作,写操作也不用阻塞读操作,提高了数据库并发读写的性能
- 同时还可以解决脏读,幻读,不可重复读等事务隔离问题,但不能解决更新丢失问题
当前读和快照读?
普通的
SELECT
就是快照读
UPDATE、DELETE、INSERT、SELECT ... LOCK IN SHARE MODE、SELECT ... FOR UPDATE
是当前读。
MVCC模型在MySQL中的具体实现则是由 3个隐式字段
,undo日志
,Read View
等去完成
每行记录除了我们自定义的字段外,还有数据库隐式定义的DB_TRX_ID
,DB_ROLL_PTR
,DB_ROW_ID
等字段
DB_TRX_ID
,记录创建/最后一次修改该记录的事务IDDB_ROLL_PTR
,指向这条记录的上一个版本(存储于rollback segment里)DB_ROW_ID
,隐含的自增ID(隐藏主键),如果数据表没有主键,InnoDB会自动以DB_ROW_ID
产生一个聚簇索引undo log主要分为两种:
insert
新记录时产生的undo log
, 只在事务回滚时需要,并且在事务提交后可以被立即丢弃update
或delete
时产生的undo log
; 不仅在事务回滚时需要,在快照读时也需要;所以不能随便删除,只有在快速读或事务回滚不涉及该日志时,对应的日志才会被purge
线程统一清除对MVCC有帮助的实质是update undo log
,undo log
实际上就是存在rollback segment
中旧记录链,它的执行流程如下:
1、一个事务插入persion表插入了一条新记录,记录如下,
name
为Jerry,age
为24岁,隐式主键
是1,事务ID
和回滚指针
,我们假设为NULL
2、现在来了一个
事务1
对该记录的name
做出了修改,改为Tom
- 在
事务1
修改该行(记录)数据时,数据库会先对该行加排他锁
- 然后把该行数据拷贝到
undo log
中,作为旧记录,既在undo log
中有当前行的拷贝副本- 拷贝完毕后,修改该行
name
为Tom,并且修改隐藏字段的事务ID为当前事务1
的ID, 我们默认从1
开始,之后递增,回滚指针指向拷贝到undo log
的副本记录,既表示我的上一个版本就是它- 事务提交后,释放锁
3、 又来了个
事务2
修改person表
的同一个记录,将age
修改为30岁
- 在
事务2
修改该行数据时,数据库也先为该行加锁- 然后把该行数据拷贝到
undo log
中,作为旧记录,发现该行记录已经有undo log
了,那么最新的旧数据作为链表的表头,插在该行记录的undo log
最前面- 修改该行
age
为30岁,并且修改隐藏字段的事务ID为当前事务2
的ID, 那就是2
,回滚指针指向刚刚拷贝到undo log
的副本记录- 事务提交,释放锁
从上面,我们就可以看出,不同事务或者相同事务的对同一记录的修改,会导致该记录的undo log
成为一条记录版本线性表,既链表,undo log
的链首就是最新的旧记录,链尾就是最早的旧记录
Read View就是事务进行快照读
操作的时候生产的读视图
(Read View),在该事务执行的快照读的那一刻,会生成数据库系统当前的一个快照,记录并维护系统当前活跃事务的ID
用来做可见性判断的, 即当我们某个事务执行快照读的时候,对该记录创建一个Read View
读视图,把它比作条件用来判断当前事务能够看到哪个版本的数据,既可能是当前最新的数据,也有可能是该行记录的undo log
里面的某个版本的数据。
① 将
要被修改的数据
的最新记录中的DB_TRX_ID
(即当前事务ID)取出来
② 与系统当前其他活跃事务的ID去对比(由Read View维护),如果DB_TRX_ID
跟Read View的属性做了某些比较
③ 不符合可见性,那就通过DB_ROLL_PTR
回滚指针去取出Undo Log
中的DB_TRX_ID
再比较
④ 即遍历链表的DB_TRX_ID
,直到找到满足特定条件的DB_TRX_ID
, 那么这个DB_TRX_ID所在的旧记录就是当前事务能看见的最新老版本
可以把Read View简单的理解成有三个全局属性
list
一个数值列表,用来维护Read View生成时刻系统正活跃的事务IDup_limit_id
记录trx_list列表中事务ID最小的IDlow_limit_id
ReadView生成时刻系统尚未分配的下一个事务ID,也就是目前已出现过的事务ID的最大值+1
整体流程
在数据表中的数据行数比较少的情况下,比如不到 1000 行,是不需要创建索引的。
当数据重复度大,比如高于 10% 的时候,也不需要对这个字段使用索引。
数据库有优化机制,判断全表扫描快还是走索引快
索引是一种用于快速查询和检索数据的数据结构。常见的索引结构有: B 树, B+树和 Hash。
相当于目录。为了方便查找书中的内容,通过对内容建立索引形成目录。索引是一个文件,它是要占据物理空间的。
每一个索引在InnoDB里面对应一棵B+树。
优点 :
大多数情况下,索引查询都是比全表扫描要快的。但是如果数据库的数据量不大,那么使用索引也不一定能够带来很大提升
缺点:
因为InnoDB的数据文件本身要按主键聚集,所以InnoDB要求表必须有主键,如果没有显式指定,则MySQL系统会自动选择一个可以唯一标识数据记录的列作为主键,如果不存在这种列,则MySQL自动为InnoDB表生成一个隐含字段作为主键,这个字段长度为6个字节,类型为长整形。
主键索引的叶子节点存放的是整行数据,非主键索引的叶子节点存放的是主键的值。
普通索引导致的回表
从age索引树再到id索引树的查询的过程叫做回表(回到主键索引树搜索的过程)
select * from user where age=3;
根据age这个普通索引在age索引树上搜索,得到主键id的值为300。
根据在age索引树上查询到的主键id的值300再到id索引树搜索一次,查询到数据
也就是说通过非主键索引的查询需要多扫描一棵索引树,因此需要尽量使用主键索引查询。
覆盖索引
为什么覆盖索引能够提升查询效率了,因为少了一次回表的过程。
即需要查询的字段正好是索引的字段,那么直接根据该索引,就可以查到数据了, 而无需回表查询。
例如:直接在age索引树中就能查询到id的值,不用再去id索引树中查找其他的数据,避免了回表。
select id from user where age=3;
聚集索引,叶节点包含了完整的数据记录,这种索引方式叫聚集索引
非聚集索引,不存数据
系统会进行两次查找,第一次先找到索引,第二次找到索引对应的位置取出数据行。非聚集索引不会把索引指向的内容像聚集索引一样直接放到索引的后面,而是维护单独的索引表(只维护索引,不维护索引指向的数据),为数据检索提供方便。
聚集索引的优点
聚集索引的查询速度非常的快,因为整个 B+树本身就是一颗多叉平衡树,叶子节点也都是有序的**,定位到索引的节点,就相当于定位到了数据**。
聚集索引的缺点
① 依赖于有序的数据 :因为 B+树是多路平衡树,如果索引的数据不是有序的,那么就需要在插入时排序,如果数据是整型还好,否则类似于字符串或 UUID 这种又长又难比较的数据,插入或查找的速度肯定比较慢。
② 更新代价大 : 如果对索引列的数据被修改时,那么对应的索引也将会被修改, 而且况聚集索引的叶子节点还存放着数据,修改代价肯定是较大的, 所以对于主键索引来说,主键一般都是不可被修改的。
非聚集索引的优点
更新代价比聚集索引要小 。非聚集索引的更新代价就没有聚集索引那么大了,非聚集索引的叶子节点是不存放数据的
非聚集索引的缺点
① 跟聚集索引一样,非聚集索引也依赖于有序的数据
② 可能会二次查询(回表) :这应该是非聚集索引最大的缺点了。 当查到索引对应的指针或主键后,可能还需要根据指针或主键再到数据文件或表中查询。
ALTER TABLE `table_name` ADD INDEX (`col1`,`col2`,`col3`);
最左匹配原则
按照最左优先的方式进行索引的匹配。遇到范围查询(>、<、between、like)就会停止匹配。
比如
a = 1 and b = 2 and c > 3 and d = 4
如果建立(a,b,c,d)
顺序的索引,d是用不到索引的,因为c字段是一个范围查询,它之后的字段会停止匹配。举例 (a,b,c),下列sql执行时都无法命中索引abc
select * from table where c = '1';select * from table where b ='1' and c ='2';
以下三种情况却会走索引:
select * from table where a = '1';select * from table where a = '1' and b = '2';select * from table where a = '1' and b = '2' and c='3';
从源码上讲为什么要最左匹配
联合索引底层还是一颗B+树,只不过联合索引的健值数量不是一个,而是多个。**构建一颗B+树只能根据一个值来构建,因此数据库依据联合索引最左的字段来构建B+树。**例子:假如创建一个(a,b)的联合索引,那么它的索引树是这样的
可以看到a的值是有顺序的,1,1,2,2,3,3,而b的值是没有顺序的1,2,1,4,1,2。所以b = 2这种查询条件没有办法利用索引,因为联合索引首先是按a排序的,b是无序的。
同时我们还可以发现在a值相等的情况下,b值又是按顺序排列的,但是这种顺序是相对的。所以最左匹配原则遇上范围查询就会停止,剩下的字段都无法使用索引。例如a = 1 and b = 2 a,b字段都可以使用索引,因为在a值确定的情况下b是相对有序的,而a>1and b=2,a字段可以匹配上索引,但b值不可以,因为a的值是一个范围,在这个范围中b是无序的。
什么情况下使用复合索引更好?
如果一个表中的数据在查询时有多个字段总是同时出现,则这些字段就可以作为复合索引,形成索引覆盖可以提高查询的效率
跟主键、外键、where、group by、order by这些量有关系的变量
1、表的主键、外键必须有索引;
2、数据量超过300的表应该有索引;
3、经常与其他表进行连接的表,在连接字段上应该建立索引;
4、经常出现在Where子句中的字段,特别是大表的字段,应该建立索引;
5、索引应该建在频繁用到的字段上;
6、索引应该建在小字段上,对于大的文本字段甚至超长字段,不要建索引;
7、频繁进行数据操作的表,不要建立太多的索引;
8、删除无用的索引,避免对执行计划造成负面影响;
9、复合索引的建立需要进行仔细分析;尽量考虑用单字段索引代替:过多的复合索引,在有单字段索引的情况下,一般都是没有存在价值的;相反,还会降低数据增加删除时的性能,特别是对频繁更新的表来说,负面影响更大。
- 正确选择复合索引中的主列字段,一般是选择性较好的字段;
- 复合索引的几个字段是否经常同时以AND方式出现在Where子句中?单字段查询是否极少甚至没有?如果是,则可以建立复合索引;否则考虑单字段索引;
- 如果复合索引中包含的字段经常单独出现在Where子句中,则分解为多个单字段索引;
- 如果既有单字段索引,又有这几个字段上的复合索引,一般可以删除复合索引;
1、in到底会不会使用索引?——会
explain select * from students where id_card IN ('320106194910290999','320106194910290998');
如果没有走索引:
(1) 字符串类型没有加单引号,
explain select * from students where id_card IN (320106194910290999,320106194910290998);
(2) mysql认为使用全表扫描要比使用索引快,则不使用索引
explain select * from students where major IN ('RO','CN');
2、OR到底会不会使用索引?——会
explain select * from students where id = 1 or stuno = 'A20170002';
如果没有走索引:
(1) 两个条件里面其中一个字段没有建立索引;
explain select * from students where id = 1 or name = 'AAB';
(2) 字符串类型的没有加单引号;
(3) mysql认为使用全表扫描要比使用索引快,则不使用索引
explain select * from students where id = 1 or major = 'CN';
3、LIKE会不会使用索引
答:LIKE ‘xxx%‘会;LIKE’%xxx’或者LIKE’%xxx%'不会
explain select * from students where stuno like 'A2017%';
explain select * from students where stuno like '%20170001';
(2)mysql认为使用全表扫描要比使用索引快,则不使用索引
explain select * from students where major like 'C%';
为什么使用模糊匹配会失效,底层原理吗?
字符串的排序方式
:B+树中,字符串先按照第一个字母排序,如果第一个字母相同,就按照第二个字母排序。。。以此类推
a%
,由于B+树的索引顺序,是按照首字母的大小进行排序,前缀匹配又是匹配首字母。所以可以在B+树上进行有序的查找,查找首字母符合要求的数据。所以有些时候可以用到索引。
%a
尾部的字母是没有顺序的,所以不能按照索引顺序查询,就用不到索引。
%a%
只有首字母是进行索引排序的,其他位置的字母都是相对无序的,所以查找任意位置的字母是用不上索引的。
1. 如果索引进行了表达式计算,则会失效
SELECT comment_id, user_id, comment_text FROM product_comment WHERE comment_id+1 = 900001
2. 如果对索引使用函数,也会造成失效
SELECT comment_id, user_id, comment_text FROM product_comment WHERE SUBSTRING(comment_text, 1,3)='abc'
3. 在 WHERE 子句中,如果在 OR 前的条件列进行了索引,而在 OR 后的条件列没有进行索引,那么索引会失效。
4. 当我们使用 LIKE 进行模糊查询的时候,后面不能是 %
SELECT comment_id, user_id, comment_text FROM product_comment WHERE comment_text LIKE '%abc'
5. 索引列与 NULL 或者 NOT NULL 进行判断的时候也会失效。
6. 我们在使用联合索引的时候要注意最左原则
MySQL
没有使用Hash
作为索引的数据结构呢?1.Hash 冲突问题 :我们上面也提到过Hash 冲突了,不过对于数据库来说这还不算最大的缺点。
2.Hash 索引不支持顺序和范围查询。是它最大的缺点: 假如我们要对表中的数据进行排序或者进行范围查询,那 Hash 索引可就不行了。
① 二叉搜索树:左子节点< 父节点< 右子节点
想二叉树的查询效率尽可能高,需要这棵二叉树是平衡的
② 平衡二叉树:左右两个子树的高度差的绝对值不超过1,并且左右两个子树都是一棵平衡二叉树
平衡二叉树每一层最多容纳的结点数量为2^(n-1),有限,当数据太多时不够用,层数多之后IO来回的次数多,所以无法满足实际需求,基于此又延伸出B树
③ B-Tree:平衡多路查找树,也叫B树。
用二叉树作为索引的实现结构,会让树变得很高,增加硬盘的 I/O 次数,影响数据查询的时间。因此一个节点就不能只有 2 个子节点,而应该允许有 M 个子节点 (M>2),每个结点都可能包含多个元素,并且非叶子结点在元素的左右都有指向子结点的指针。查询所经过的结点数量要少很多,也就意味着要少很多次的磁盘IO,这对性能的提升是很大的。
它的每一个节点最多可以包括 M 个子节点,M 称为 B 树的阶。同时你能看到,每个磁盘块中包括了关键字和子节点的指针。如果一个磁盘块中包括了 x 个关键字,那么指针数就是 x+1。
④ B+Tree中,非叶子节点上只存储key值信息,每个节点存储的key值数量增加,降低B+Tree的高度
① B 树的所有节点既存放键(key) 也存放 数据(data),而 B+树只有叶子节点存放 key 和 data,其他内节点只存放 key。
② B 树的叶子节点都是独立的;B+树的叶子节点有一条引用链指向与它相邻的叶子节点。
③ B+树的检索效率就很稳定,任何查找都是从根节点到叶子节点的过程,叶子节点的顺序检索很明显。
④ 非叶子结点的子树指针与关键字个数相同;
为什么要用链表把叶子结点连起来
B+树全盘扫描时可以直接通过叶子节点间的链表直接进行而不用遍历树结构,B树全盘扫描时需要从根节点开始遍历树结构。当我们在使用范围查找的时候 只要找到那个边界值就可以通过指针去查找其他所需要的数据就不用再从根结点开始遍历 减少了所消耗的时间 增加了效率
为什么所有数据都存在叶子结点?为什么说B+树比B树更适合数据库索引?
- B+树的磁盘读写代价更低:B+树的内部节点并没有指向关键字具体信息的指针,因此其内部节点相对B树更小**,如果把所有同一内部节点的关键字存放在同一盘块中,那么盘块所能容纳的关键字数量也越多,一次性读入内存的需要查找的关键字也就越多,相对IO读写次数就降低了**。
- B+树的查询效率更加稳定:由于非终结点并不是最终指向文件内容的结点,而只是叶子结点中关键字的索引。所以任何关键字的查找必须走一条从根结点到叶子结点的路。所有关键字查询的路径长度相同,导致每一个数据的查询效率相当。
- B+可以通过叶子结点之间的链表进行全盘扫描。但是B树因为其分支结点同样存储着数据,我们要找到具体的数据,需要进行一次中序遍历按序来扫,所以B+树更加适合在区间查询的情况,所以通常B+树用于数据库索引。
① B+树在MyISAM索引实现
叶节点的data域存放的是数据记录的地址,MyISAM的索引方式也叫做“非聚集”的,之所以这么称呼是为了与InnoDB的聚集索引区分
② B+树在InnoDB索引实现
区别:
第一个重大区别是InnoDB的数据文件本身就是索引文件。
MyISAM索引文件仅保存数据记录的地址。
而在InnoDB中data域保存了完整的数据记录。这个索引的key是数据表的主键,因此InnoDB表数据文件本身就是主索引。
第二个与MyISAM索引的不同是InnoDB的辅助索引data域存储相应记录主键的值而不是地址。换句话说,InnoDB的所有辅助索引都引用主键作为data域。例如用名字来作为辅助索引。data中存的是主键
聚集索引这种实现方式使得按主键的搜索十分高效,但是辅助索引搜索需要检索两遍索引:首先检索辅助索引获得主键,然后用主键到主索引中检索获得记录。
关于索引:由于索引需要额外的维护成本,因为索引文件是单独存在的文件,所以当我们对数据的增加,修改,删除,都会产生额外的对索引文件的操作,这些操作需要消耗额外的IO,会降低增/改/删的执行效率。所以,在我们删除数据库百万级别数据的时候,删除数据的速度和创建的索引数量是成正比的。
SQL 调优的三个步骤:慢查询、explain 和 show profile
通过性能分析工具可以让我们了解执行慢的 SQL 都有哪些,再针对性地用 EXPLAIN 查看对应 SQL 语句的执行计划,或者使用 show profile 查看 SQL 中每一个步骤的时间成本。
mysql > show variables like '%slow_query_log';mysql > set global slow_query_log='ON'; #打开慢查询mysql > set global long_query_time = 3; #设置慢查询阈值# 开启慢查询日志,并设置相应的慢查询时间阈值之后,只要大于这个阈值的 SQL 语句都会保存在慢查询日志中,然后我们就可以通过 mysqldumpslow 工具提取想要查找的 SQL 语句了。
EXPLAIN 可以帮助我们了解数据表的读取顺序、SELECT 子句的类型、数据表的访问类型、可使用的索引、实际使用的索引、使用的索引长度、上一个表的连接匹配条件、被优化器查询的行的数量以及额外的信息
例如,不走索引的分页:查第1页和查第10000页有什么区别。
直接用limit 分页语句
select * from product limit 10, 20 #0.016秒
select * from product limit 100, 20 #0.016秒
select * from product limit 1000, 20 #0.047秒
select * from product limit 10000, 20 #0.094秒
select * from product limit 400000, 20 #3.229秒
select * from product limit 866613, 20 #37.44秒
1)limit语句的查询时间与起始记录的位置成正比
2) mysql的limit语句是很方便,但是对记录很多的表并不适合直接使用。
解决办法:
加上索引排序select * from table order by xx,id(任意有索引的字段) limit 0,10
SHOW PROFILE 相比 EXPLAIN 能看到更进一步的执行解析,包括 SQL 都做了什么、所花费的时间等。默认情况下,profiling 是关闭的,我们可以在会话级别开启这个功能。
mysql > show variables like 'profiling';
mysql > set profiling = 'ON';
mysql > show profile;
mysql > show profile for query 2 #查询指定的query ID开销
数据切分根据其切分类型,可以分为两种方式:垂直(纵向)切分和水平(横向)切分
基于数据库中的"列"进行,某个表字段较多,可以新建一张扩展表,将不经常用或字段长度较大的字段拆分出去到扩展表中。
垂直切分的优点:
缺点:
难以再细粒度的垂直切分,或切分后数据量行数巨大,存在单库读写、存储性能瓶颈,这时候就需要进行水平切分了。
按照时间区间或ID区间来切分。例如:按日期将不同月甚至是日的数据分散到不同的库中;将userId为1~ 9999的记录分到第一个库,10000~20000的分到第二个库,以此类推。
优点:
缺点:
一般采用hash取模mod的切分方式,例如:将 Customer 表根据 cusno 字段切分到4个库中,余数为0的放到第一个库,余数为1的放到第二个库,以此类推。这样同一个用户的数据会分散到同一个库中,如果查询条件带有cusno字段,可明确定位到相应库查询
优点:
缺点:
一致性哈希的目的就是为了在节点数目发生改变时尽可能少的迁移数据,将所有的存储节点排列在收尾相接的Hash环上,每个key在计算Hash 后会顺时针找到临接的存储节点存放。而当有节点加入或退 时,仅影响该节点在Hash环上顺时针相邻的后续节点。
- 优点:加入和删除节点只影响哈希环中顺时针方向的相邻的节点,对其他节点无影响。
- 缺点 :数据的分布和节点的位置有关,因为这些节点不是均匀的分布在哈希环上的,所以数据在进行存储时达不到均匀分布的效果。
1、Master 数据库只要发生变化,立马记录到Binary log 日志文件中
2、Slave数据库启动一个I/O thread连接Master数据库,请求Master变化的二进制日志
3、Slave I/O获取到的二进制日志,保存到自己的Relay log 日志文件中。
4、Slave 有一个 SQL thread定时检查Realy log是否变化,变化那么就更新数据
1.实现服务器负载均衡
设置一台主服务器,专门用来数据的更新。同时设置多台从服务器,用来负责用户信息的查询
切分查询的作业。当主服务器比较忙时,部分查询请求会自动发送到从服务器重,以降低主服务器的工作负荷。
2.通过复制实现数据的异地备份, 可以定期的将数据从主服务器上复制到从服务器上,这无疑是先了数据的异地备份。
3.提高数据库系统的可用性,数据库复制功能实现了主服务器与从服务器之间数据的同步,增加了数据库系统的可用性。