数据库基础知识
什么是SQL?
结构化查询语言(Structured Query Language):数据查询语言,用于存取数据、查询、更新和管理关系数据库系统。
数据库三大范式:
第一范式:每个列都不可再拆分;
第二范式:在第一范式的基础上,非主键列完全依赖于主键,而不能依赖于主键的一部分;
第三范式:在第二范式的基础上,非主键列只依赖于主键,不依赖于其他非主键;
2NF:非主键列是否完全依赖于主键,还是依赖于主键的一部分;3NF:非主键列是直接依赖于主键,还是直接依赖于非主键列。
数据类型
整数类型,包括TINYINT、SMALLINT、MEDIUMINT、INT、BIGINT,分别表示1字节、2字节、3字节、4字节、8字节整数。任何整数类型都可以加上UNSIGNED属性,表示数据是无符号的,即非负整数。
int(20)表示显示字符的长度为20,但是仍占4个字节存储。
char与varchar的区别
char的特点:
- char表示定长字符串,长度固定;
- 如果插入数据的长度小于char的固定长度时,则用空格填充;(存取快)
- 最多能存放的字符个数为255
varchar的特点:
- 可变长字符串;
- 插入的数据有多长,就按照多长来存储;(存取慢)
- 最多能存放的字符个数为65536
- varchar(50)表示最多存放50个字符
日期和时间类型,尽量使用timestamp,空间效率高于datetime,
用整数保存时间戳通常不方便处理。
如果需要存储微秒,可以使用bigint存储。
引擎
MySQL存储引擎MyISAM与InnoDB区别
存储引擎Storage engine:MySQL中的数据、索引以及其他对象是如何存储的,是一套文件系统的实现。
- InnoDB:InnoDB存储引擎提供了对数据库ACID的支持,具有提交、回滚和崩溃恢复能力的事务安全存储引擎,主索引是聚簇索引,在索引中保存了数据,从而避免直接读取磁盘,因此对查询性能有很大的提升。并且还提供了行级锁和外键的约束,支持哈希索引,不支持全文索引。它的设计目标就是处理大数据容量的数据库系统。
- MyISAM:不提供事务的支持,表级锁,不支持行级锁和外键,支持全文索引,不支持哈希索引,适合一些CMS(内容管理系统)作为后台数据库使用,但是使用大并发、重负荷生产系统上,显得力不从心。
比较
- 事务:InnoDB 是事务型的,可以使用 Commit 和 Rollback 语句。
- 并发:MyISAM 只支持表级锁,而 InnoDB 还支持行级锁。
- 外键:InnoDB 支持外键。
- 备份:InnoDB 支持在线热备份。
- 崩溃恢复:MyISAM 崩溃后发生损坏的概率比 InnoDB 高很多,而且恢复的速度也更慢。
- 其它特性:MyISAM 支持压缩表和空间数据索引。

ACID事务:
- 原子性(事务是一个不可分割的工作单位,事务中的操作要么都发生、要么都不发生);
- 一致性(事务前后数据的完整性必须保持一致);
- 隔离性(多个用户并发访问数据库时,数据库为每个用户开启的事务,不能被其他事务的操作数据所干扰,多个并发事务之间要相互隔离);
- 持久性(一个事务一旦被提交,它对数据库中的数据的改变是永久性的,接下来即使数据库发生故障,也不应该对其有任何影响)
InnoDB引擎的4大特性
- 插入缓存:每次插入先判断插入的非聚集索引页是否存在缓存池中,如果在则直接插入,如果不在,先放到Buffer中,再按照一定的频率进行合并操作,将多个插入合并到一个操作中,目的是为了减少随机IO带来性能损耗;
- 二次写(doublewrite):

doublewrite用来缓存InnoDB的数据页从InnoDB buffer pool中flush之后并写入到数据文件之前,所以当操作系统或者数据库进程在数据页写到磁盘的过程中崩溃,InnoDB可以从doublewrite缓存中找到数据页的备份而执行crash恢复。 - 自适应哈希索引:InnoDB存储引擎会监控二级索引的查找,如果发现某二级索引被频繁访问,二级索引会成为热数据,建立哈希索引来提高访问速度,自适应哈希索引通过缓存池的B+树构造。
- 预读:分为线性预读和随机预读,线性预读着眼于将下一个extent提前读取到buffer pool中,而随机预读着眼于将当前extent中的剩余的page提前读取到buffer pool中。
存储引擎选择
默认使用InnoDB
- MyISAM:以读写插入为主的应用程序,如:博客系统、新闻门户网站;
- InnoDB:更新(删除)操作频率高的场合,或者要保证数据的完整性,并发量高,要求支持事务和外键,比如OA(自动化办公系统)。
索引
什么是索引
索引是一种特殊的文件(相当于目录,不过占用物理空间),包含着对数据表中所有记录的引用指针,是数据库管理系统中一个排序的数据结构,以协调快速查询、更新数据库表中数据。索引的实现通常是B树或变种的B+树。
索引的使用场景(重点)
where
-- 增加一个没有建立索引的字段
alter table innodb1 add sex char(1);
-- 按sex检索时可选的索引为null
EXPLAIN SELECT * from innodb1 where sex='男';order by
对没有建立索引的字段使用order by排序时,会将查询到的所有数据使用外部排序,这个操作是很影响性能的,因为需要将查询涉及到所有数据从磁盘中读到内存。
如果该字段有建立索引,由于索引本身是有序的,因此直接按照索引的顺序和映射关系逐条取出数据即可。而且如果分页的,那么只用取出索引表某个范围内的索引对应的数据,而不用取出所有数据进行排序再返回某个范围内的数据。
join
对join语句匹配关于(on)涉及的字段建立索引能够提高效率。
索引类型
普通索引:基本的索引类型,没有唯一性的限制,允许为NULL
ALTER TABLE table_name ADD INDEX index_name (column);创建普通索引ALTER TABLE table_name ADD INDEX index_name(column1, column2, column3);创建组合索引
唯一索引:数据列不允许重复,允许为NULL,一个表允许多个列创建唯一索引;
ALTER TABLE table_name ADD UNIQUE (column);创建唯一索引ALTER TABLE table_name ADD UNIQUE (column1,column2);创建唯一组合索引
主键索引:数据列不允许重复,不允许为NULL,一个表只能有一个主键;
全文索引:(InnoDB不支持)
ALTER TABLE table_name ADD FULLTEXT (column);
索引的基本原理
把无序的数据变成有序的查询。
- 把创建了索引的列的内容进行排序
- 对排序结果生成倒排表
- 在倒排表内容上拼上数据地址链
- 在查询的时候,先拿到倒排表内容,再取出数据地址链,从而拿到具体数据
创建索引的原则(非常重要)
- 对于查询频率高的字段创建索引;
- 对排序、分组、联合查询频率高的字段创建索引;
- 索引的数目不宜太多(占用物理内存);
- 如果需要将多个列设置索引时,可以采用多列索引;
- 选择唯一性索引(id);
- 尽量使用数据量少的索引(char的类型);
- 尽量使用前缀索引;
- 删除不再使用或者很少使用的索引;
- 对于定义为text、image和bit的数据类型的列不要建立索引;
前缀索引
语法:index(field(10))
前提:前缀的标识度高
创建索引的三种方式
第一种方式:在执行CREATE TABLE时创建索引
CREATE TABLE user_index2 (
id INT auto_increment PRIMARY KEY,
first_name VARCHAR (16),
last_name VARCHAR (16),
id_card VARCHAR (18),
information text,
KEY name (first_name, last_name),
FULLTEXT KEY (information),
UNIQUE KEY (id_card)
);第二种方式: 使用ALTER TABLE命令去增加索引
ALTER TABLE table_name ADD INDEX index_name (column_list);ALTER TABLE用来创建普通索引、UNIQUE索引或PRIMARY KEY索引。
第三种方式:使用CREATE INDEX命令创建
CREATE INDEX index_name ON table_name (column_list);CREATE INDEX可对表增加普通索引或UNIQUE索引。(但是,不能创建PRIMARY KEY索引)
删除索引
根据索引名删除普通索引、唯一索引、全文索引:alter table 表名 drop KEY 索引名
alter table user_index drop KEY name;
alter table user_index drop KEY id_card;
alter table user_index drop KEY information;注意:如果主键自增长,那么需要先取消自增长再删除:(通常不会删除主键,因为设计主键一定要与业务逻辑无关)
alter table user_index
-- 重新定义字段
MODIFY id int,
drop PRIMARY KEYHash索引和B+树索引的区别
hash索引底层就是hash表,进行查找时,调用一次hash函数就能获得相应的键值,之后进行回表查询获得实际数据。
B+树底层实现是多路平衡查找树,对于每次查询都是从根节点出发,查找到叶子结点方可以获得所查键值,然后根据查询判断是否需要回表查询数据。
区别:
- hash索引进行等值查询更快,但是无法进行范围查询(hash函数的不可预测性)
- hash索引不支持使用索引进行排序
- hash索引不支持模糊查询以及多列索引的最左前缀匹配
- hash索引任何时候都避免不了回表查询
- hash索引虽然在等值查询上较快,但是不稳定,性能不可预测,当某个键值存在大量重复的时候,发生hash碰撞,此时效率可能极差。
数据库为什么使用B+树而不是B树
- B树只适合随机检索,而B+树同时支持随机检索和顺序检索;
- B+树空间利用率更高,可减少I/O次数,磁盘读写代价更低;
- B+树的查询效率更加稳定;
- B+树的叶子结点使用指针顺序连接在一起,只要遍历叶子结点就可以实现整棵树的遍历;
- 增删文件时,效率更高。
数据库为什么不使用红黑树实现索引?
红黑树等平衡树也可以用来实现索引,但是文件系统及数据库系统普遍采用 B+ Tree 作为索引结构,主要有以下两个原因:
(一)更少的查找次数
平衡树查找操作的时间复杂度和树高 h 相关,O(h)=O(logdN),其中 d 为每个节点的出度。
红黑树的出度为 2,而 B+ Tree 的出度一般都非常大,所以红黑树的树高 h 很明显比 B+ Tree 大非常多,查找的次数也就更多。
(二)利用磁盘预读特性
为了减少磁盘 I/O 操作,磁盘往往不是严格按需读取,而是每次都会预读。预读过程中,磁盘进行顺序读取,顺序读取不需要进行磁盘寻道,并且只需要很短的旋转时间,速度会非常快。
操作系统一般将内存和磁盘分割成固定大小的块,每一块称为一页,内存与磁盘以页为单位交换数据。数据库系统将索引的一个节点的大小设置为页的大小,使得一次 I/O 就能完全载入一个节点。并且可以利用预读特性,相邻的节点也能够被预先载入。什么是聚簇索引?
- 聚簇索引:将数据存储与索引放到一起,找到索引也就找到了数据;
- 非聚簇索引:将数据存储与索引分开。
事务
什么是数据库事务
事务是一个不可分割的数据库操作序列,也是数据库并发控制的基本单位,其执行结果必须使数据库从一种一致性状态变到另一种一致性状态,事务是逻辑上的一组操作,要么都执行,要么都不执行。
事务的四大特性
- 原子性:事务是最小的执行单元,不允许分割,事务的原子性确保动作要么全部完成,要么完全不起作用;
- 一致性:执行事务前后,数据的完整性保持一致,比如转账前后金额总数不变。
- 隔离性:并发访问数据库时,一个用户的事务不被其他事务所干扰,各并发事务之间数据库是独立的;
- 持久性:一个事务被提交之后,它对数据库中数据的改变是持久的;
什么是脏读?不可重复读?幻读?
- 脏读:一个事务读取了另一个事务未提交的数据操作结果;
- 不可重复读:事务 T1 读取某一数据后,事务 T2 对其做了修改,当事务 T1 再次读该数据时得到与前一次不同的值。这样就发生了在一个事务内两次读到的数据是不一样的,因此称为是不可重复读。
- 幻读:事务在操作过程中进行两次查询,第二次查询的结果包含了第一次查询中未出现的数据或者缺少了第一次查询中出现的数据。
不可重复读和幻读的区别:不可重复读的重点是修改,幻读的重点在于新增或是删除。
事务隔离级别
- READ-UNCOMMITTED(读取未提交): 最低的隔离级别,可以读到未提交的内容;
- READ-COMMITTED(读取已提交): 通过“快照读”的机制,保证只能读到已经提交的内容;
- REPEATABLE-READ(可重复读): 对同一字段的多次读取结果都是一致的,因为事务开启后,不允许进行“修改,删除”操作;
- SERIALIZABLE(可串行化): 最高的隔离级别,完全服从ACID的隔离级别。所有的事务依次逐个执行,这样事务之间就完全不可能产生干扰,但是效率太差,性能开销也大,几乎不使用。
锁
数据库中为什么会有锁
当数据库有并发访问事务的时候,可能会产生数据的不一致,这时候需要一些机制(锁)来保证访问的次序。
隔离级别和锁的关系
- READ-UNCOMMITTED(读取未提交): 读取数据不需要加共享锁;
- READ-COMMITTED(读取已提交): 读操作需要加共享锁,但是在语句执行完以后释放共享锁;
- REPEATABLE-READ(可重复读):读操作需要加共享锁,但是在事务提交之前并不释放共享锁,也就是必须等待事务执行完毕以后才释放共享锁;
- SERIALIZABLE(可串行化):锁定整个范围的键,并且一直持有锁,直到事务完成。
按照锁的粒度分数据库锁有哪些?
MyISAM和InnoDB存储引擎使用的锁:
- MyISAM采用表级锁(table-level locking)。
- InnoDB支持行级锁(row-level locking)和表级锁,默认为行级锁
行级锁:行级锁是MySQL中锁定粒度最细的一种锁,表示只针对当前操作的行进行加锁,行级锁能大大减少数据库操作的冲突,其加锁粒度最小,但加锁的开销也最大,行级锁分为共享锁(读锁)和排他锁(写锁)。
特点:开销大,加锁慢,会出现死锁;发生锁冲突的概率最低,并发度也最高;
表级锁:MySQL中锁定粒度最大的一种锁,表示对当前操作的整张表加锁,它实现简单,资源消耗较少,被大部分MySQL引擎支持,最常使用的MyISAM和InnoDB都支持表级锁定,表级锁定分为共享锁和排他锁。
特点:开销小,加锁快,不会出现死锁,锁定粒度大,出现锁冲突的概率最高,并发度最小。
页级锁:介于两者之间
行级锁的实现
InnoDB是基于索引来完成行级锁
什么是死锁,产生死锁的必要条件,如何解决死锁
死锁是指两个或两个以上的进程在执行过程中,由于竞争资源或者由于彼此通信而造成的一种阻塞的现象,若无外力作用,它们都将无法推进下去。
产生死锁的必要条件:
1、互斥使用:即当资源被一个线程使用时,其他线程无法使用;
2、不可抢占:资源请求者不能强制从资源占有者手中夺取资源,资源只能由资源占有者主动释放;
3、请求与保持:当资源请求者在请求其他的资源的同时保持对原有资源的占有。
4、循环等待:即存在一个等待队列,P1占有P2的资源,P2占有P3的资源,P3占有P1的资源,这样就形成了等待环路。
解决死锁的方法:
1、如果不同程序会并发存取多个表,尽量约定以相同的顺序访问表,可以大大降低死锁机会;
2、在同一个事务中,尽量做到一次锁定所需要的所有资源,减少死锁产生的概率;
3、对于非常容易产生死锁的业务部分,可以尝试使用升级锁定颗粒度,通过表级锁定来减少死锁产生的概率;
视图(View)
什么是视图?为什么要用视图?
视图本质上是一种虚拟表,在物理上是不存在的,其内容与真实的表相似,包含一系列带有名称的列和行数据。但是,视图并不在数据库中以存储的数据值形式存在。行和列数据来自定义视图的查询索引用基本表,并且在具体引用视图时动态生成。
视图使开发者只关心感性的某些特定数据和所负责的特定任务,只能看到视图中所定义的数据,而不是视图所引用表中的数据,从而提高了数据库中数据的安全性。
- 创建表
mysql> CREATE TABLE t (qty INT, price INT);
Query OK, 0 rows affected (0.09 sec)
mysql> INSERT INTO t VALUES(3, 50), (5, 60);
Query OK, 2 rows affected (0.01 sec)
Records: 2 Duplicates: 0 Warnings: 0
mysql> SELECT * FROM t;
+------+-------+
| qty | price |
+------+-------+
| 3 | 50 |
| 5 | 60 |
+------+-------+
2 rows in set (0.00 sec)- 创建视图
mysql> CREATE VIEW v AS SELECT qty, price, qty*price AS value FROM t;
Query OK, 0 rows affected (0.01 sec)- 查看视图
mysql> SELECT * FROM v;
+------+-------+-------+
| qty | price | value |
+------+-------+-------+
| 3 | 50 | 150 |
| 5 | 60 | 300 |
+------+-------+-------+
2 rows in set (0.00 sec)对于视图的查询也可以加上过滤条件:mysql> SELECT * FROM v WHERE qty = 5;+------+-------+-------+| qty | price | value |+------+-------+-------+| 5 | 60 | 300 |+------+-------+-------+1 row in set (0.00 sec)- 查看当前库下的所有视图
mysql> show full tables where table_type like 'VIEW';
+----------------+------------+
| Tables_in_test | Table_type |
+----------------+------------+
| v | VIEW |
| vw_01 | VIEW |
+----------------+------------+
2 rows in set (0.00 sec)- 基于其他的视图来创建视图
mysql> select * from vw_01; #vw_01是一个视图.
+---+------+
| a | b |
+---+------+
| 1 | NULL |
| 2 | NULL |
| 3 | NULL |
| 4 | NULL |
+---+------+
4 rows in set (0.00 sec)
mysql> create view v01 as select a,b from vw_01; #基于视图来创建视图,也就是说,视图定义中的SELECT查询的是另外的视图.
Query OK, 0 rows affected (0.02 sec)
mysql> select * from v01;
+---+------+
| a | b |
+---+------+
| 1 | NULL |
| 2 | NULL |
| 3 | NULL |
| 4 | NULL |
+---+------+
4 rows in set (0.00 sec)什么是游标
游标是系统为用户开设的一个数据缓冲区,存放SQL语句的执行结果,它可以定位到结果集中的某一行,对数据进行读写,也可以移动游标定位到你需要的行进行数据操作,是面向集合的数据库管系统和面向行的程序设计之间的桥梁。
存储过程与函数
什么是存储过程,有什么优缺点?
存储过程是在大型数据库系统中,一组为了完成特定功能的SQL 语句集,存储在数据库中,经过第一次编译后再次调用时不需要再次编译,用户通过指定存储过程的名字并给出参数(如果该存储过程带有参数)来执行它。
优点:
- 预编译过、执行效率高;
- 代码直接存放于数据库中,通过存储过程名直接调用,减少网络通讯;
- 安全性高,执行存储过程需要有一定权限的用户;
- 存储过程可以重复使用,减少数据库开发人员的工作量
缺点:
1、调试麻烦
2、移植问题
3、重新编译问题,因为后端代码是运行前编译的,如果带有引用关系的对象发生改变时,受影响的存储过程、包将需要重新编译
触发器
什么是触发器,触发器的使用场景有哪些?
触发器是用户定义在关系表上的一类由事件驱动的特殊的存储过程,触发器是指一段代码,当触发某个事件时,自动执行这些代码。
使用场景:
- 可以通过 数据库中的相关表实现级联更改;
- 实时监控某张表中的某个字段的更改而需要做出相应的处理;
- 生成某些业务的编号;
常用SQL语句
SQL语句主要分为哪几类
数据定义语言DDL:CREATE、DROP、ALTER(修改)
数据查询语言DQL:SELECT
数据操纵语言DML:INSERT、UPDATE、DELETE
数据控制功能DCL:GRANT、REVOKE、COMMIT、ROLLBACK
超键、候选键、主键、外键分别是什么
- 超键:在关系中能唯一标识元组的属性集称为关系模式的超键,一个属性可以作为一个超键,多个属性组合在一起可以作为一个超键,超键包含候选键和主键。
- 候选键:是最小超键,即没有冗余元素的超键
- 主键:数据库表中对存储数据对象予以唯一和完整标识的数据的数据列或属性的组合。一个数据列只能有一个主键,且主键的取值不能缺失,即不能为空值(Null);
- 外键:在一个表中存在的另一个表的主键称为此表的外键。
SQL约束有哪几种
- NOT NULL:非空
- UNIQUE:不能重复
- PRIMARY KEY:不能重复,且在表中只允许出现一次;
- FOREIGN KEY:预防破坏表之间连接的动作,也能防止非法数据插入外键列;
- CHECK:控制字段的值范围
五种关联查询
- 内连接(INNER JOIN)
- 外连接(LEFT JOIN/RIGHT JOIN)
- 联合查询(UNION与UNION ALL)
- 全连接(FULL JOIN)
- 交叉连接(CROSS JOIN)
内连接:多表中同时符合某种条件的数据记录的集合
①等值连接: on a.id = b.id
②不等值连接 : on a.id > b.id
③自连接: select * from a as a1 inner join a as a2 on a.id = a2.pid
[自连接就是自己连接自己 条件id和pid]INNER JOIN 可以缩写为 JOIN
外连接:
- 左外连接: 以左表为主,先查询出左表,按照ON之后关联条件匹配右表,没有匹配到的用NULL填充
- 右外连接: 以右表为主,先查询出右表,按照ON之后关联条件匹配左表,没有匹配到的用NULL填充
联合查询:
SELECT * FROM A UNION SELECT * FROM B UNION ...将多个结果集集中在一起,UNION前的结果为基准,注意的是联合查询的列数要相等, 相同的记录行会合并,如果使用UNION ALL,不会合并重复的记录行,效率方面UNION ALL高于UNION。
全连接
MySQL不支持全连接
交叉连接SELECT * FROM A,B(,C)或者SELECT * FROM A CROSS JOIN B (CROSS JOIN C)
没有任何关联条件,结果是笛卡尔积,结果集会很大,没有意义,很少使用交叉连接
什么是子查询
- 条件:一条SQL语句的查询结果作为另一条查询语句的条件或查询结果
- 嵌套:多条SQL语句嵌套使用,内部的SQL查询语句称为子查询
MySQL中in和exists区别
exists
exists关键字后面的参数是一个任意的子查询,系统对子查询进行运算以判断它是否返回行,如果至少返回一行,那么exists的结果为true,此时外层的查询语句将进行查询;如果子查询没有返回任何行,那么exists的结果为false,此时外层语句将不进行查询。
in
in关键字进行子查询时,内存查询语句仅仅返回一个数据列,这个数据列的值将提供给外层查询语句进行比较操作。
注意:外层查询表小于子查询表,用exists,外层循环表大于子循环表,用in,如果外层循环表跟子查询表差不多,无所谓。
UNION和UNION ALL的区别
- 使用UNION ALL,不会合并重复的记录行
- UNION ALL的效率要高于UNION
SQL优化
如何定位及优化SQL语句的性能问题?
对于低性能的SQL语句的定位,最重要也是最有效的方法就是使用执行计划,MySQL提供了explain命令来查看语句的执行计划,对于查询语句,最重要的优化方式就是使用索引,而执行计划,就是显示数据库引擎对SQL语句的执行情况,其中包括:是否使用索引,使用什么索引,使用的索引的相关信息等。
SQL的生命周期
- 应用服务器与数据库服务器建立一个连接
- 数据库进程拿到请求sql
- 解析并生成执行计划,执行
- 读取数据到内存并进行逻辑处理
- 通过步骤一的连接,发送结果到客户端
- 关闭连接,释放资源

超大分页如何处理
解决超大分页主要依靠缓存,可预测性的提前查到内容,缓存至Redis等k-v数据库中,直接返回即可。
LIMIT(分页)
LIMIT子句可以被用于强制SELECT语句返回指定的记录数,LIMIT接受一个或两个数字参数,第一个参数指定第一个返回记录行等待偏移量,第二个参数指定返回记录行的最大数目,初始记录行的偏移量是0。
mysql> SELECT * FROM table LIMIT 5,10; // 检索记录行 6-15为了检索从某一个偏移量到记录集的结束所有的记录行,可以指定第二个参数为 -1:
mysql> SELECT * FROM table LIMIT 95,-1; // 检索记录行 96-last.在需要排序的列后面加上DESC,表示是倒序排列。
关心过业务系统里面的sql耗时吗?统计过慢查询吗?对慢查询都怎么优化过?
慢查询日志:
用于记录执行时间超过某个临界值的SQL日志,用于快速定位慢查询,为我们的优化做参考。
开启慢查询日志:set GLOBAL slow_query_log = on
设置临界时间:set long_query_time=0.5
慢查询优化:
- 首先分析语句,看看是否load了额外的数据,可能是查询了多余的行并且抛弃掉了,可能是加载了需要结果并不需要的列,对语句进行分析以及重写;
- 分析语句的执行计划,获得其索引的使用情况,看能否通过修改语句或索引,来提高索引命中率;
- 如果对语句的优化已经无法进行,可以考虑表中的数据量是否太大,看能否进行分表。
主键使用自增ID还是UUID
推荐使用自增ID,性能上更好,如果使用UUID,可能会造成非常多的数据插入,数据移动,然后会产生很多内存碎片,造成插入性能的下降。
字段值为什么要求定义为not null
null值会占用更多的字节,且会在程序中造成很多与预期不符的情况
如果要存储用户的密码散列,应该使用什么字段进行存储
密码散列,盐,用户身份证号等固定长度的字符串应该使用char而不是varchar来存储,这样可以节省空间且提高检索效率。
数据库优化
为什么要优化
系统的吞吐量瓶颈往往出现在数据库的访问速度上;
随着应用程序的运行,数据库中的数据会越来越多,处理时间相应变慢;
数据是存放在磁盘上的,读写速度无法和内存比
面试题:给我说说平时是如何优化MySQL的?
- explain
在MySQL优化的时候,使用explain查看SQL的执行计划

- SQL语句中IN包含的值不应过多
MySQL对于IN做了相应的优化,即将IN中的常量全部存储在一个数组里面,而且这个数组是排好序的。但是如果数值较多,产生的消耗也是比较大的。建议对于连续的数值,能用between就不要用in,再或者使用连接来替换。 - SELECT语句务必指明字段名称
SELECT *增加很多不必要的消耗(cpu、io、内存、网络带宽);
增加了使用覆盖索引的可能性; - 当只需要一条数据的时候,使用limit1
这是为了使EXPLAIN中type列达到const类型 - 如果排序字段没有用到索引,就尽量少排序
- 如果限制条件中其他字段没有索引,尽量少用or
- 尽量使用union all代替union
union和union all的差异主要是前者需要将结果集合并后再进行唯一性过滤操作;
这就会涉及到排序,增加大量的CPU运算,加大资源消耗及延迟。
当然,union all的前提条件是两个结果集没有重复数据。 - 不使用order by rand()
- 分区in和exists
in 适合于外表大而内表小的情况。exists适合于外表小而内表大的情况。 - 区分not in 和 not exists
推荐使用not exists,不仅仅是效率问题,not in可能存在逻辑问题。 - 使用合理的分页以提高分页效率
- 分段查询
- 避免在where字句中对字段进行null值判断
- 不建议使用%前缀模糊查询
例如:LIKE “%name”或者LIKE “%name%”;
这种查询会导致索引失效而进行全表扫描;但是可以使用LIKE “name%”。 - 避免在where子句中对字段进行表达式操作
- 避免隐式类型转换
- 对于联合索引来说,要遵守最左前缀法则
- 必要时可以使用force index来强制查询某个索引
- 注意范围查询语句
- 关于JOIN优化
LEFT JOIN A表为驱动表
INNER JOIN MySQL会自动找出那个数据少的表作用驱动表
RIGHT JOIN B表为驱动表
尽量使用inner join,避免left join
参与联合查询的表至少为2张表,一般都存在大小之分。
如果连接方式是inner join,在没有其他过滤条件的情况下MySQL会自动选择小表作为驱动表;
但是left join在驱动表的选择上遵循的是左边驱动右边的原则,即left join左边的表名为驱动表。
合理利用索引:
被驱动表的索引字段作为on的限制字段。
利用小表去驱动大表
如果能够减少驱动表的话,减少嵌套循环中的循环次数,以减少 IO总量及CPU运算的次数。数据库结构优化
- explain
将字段很多的表分解为多个表
增加中间表来提高查询效率
合理的加入冗余字段来提高查询速度
大表如何优化
- 限制数据的查询范围
- 读写分离
- 使用缓存
- 分库分表
分库分表
分库分表的原因
单库的数据量越来越大,查询所需的时间越来越多;
单库发生意外时,需要修复的是所有数据,而多库中的一个库发生意外时,只需要修复一个库。
分库分表的常用策略
垂直切分:
根据业务的不同,将原有多个字段的表拆分为多个表。
水平切分:
将所有数据分散在多个表中
分库分表需要解决的问题
1、事务问题:
解决办法:可以使用分布式事务或由应用程序和数据库共同控制;
2、跨节点Join问题
解决办法:分两次查询,在第一次查询的结果集中找出关联数据的id,根据这些id发起第二次请求得到关联数据;
3、跨节点的count、order by、group by以及聚合函数问题
解决办法:分别在各个节点上得到结果后在应用程序端进行合并
4、数据迁移,容量规划,扩容等问题
解决办法:利用对2的倍数取余具有向前兼容的特性(如对4取余跟对2取余结果相同)来分配数据,避免了行级别的数据迁移,但是依然需要进行表级别的迁移,同时对扩展规模和分表数量都有限制。
5、事务
解决办法:基于Best Efforts 1PC模式,实现在一个时间周期内达到最终一致性。
6、ID问题
因为数据库一旦被切分到多个物理节点上,我们将不能再依赖数据库自身的主键生成机制,因为在某个分区数据库自生成的ID无法保证在全局上是唯一的。
解决办法:结合数据库维护一个Sequence表
MySQL的复制原理以及流程
主从复制:将主数据库中的DDL和DML操作通过二进制日志(binlog)传输到从数据库上,然后将这些日志重新执行,从而使得从数据库的数据跟主数据库的数据保持一致。
主从复制的作用
- 主数据库出现问题,可以切换到从数据库
- 可以进行数据库层面的读写分离
- 可以在从数据库上进行日常备份
MySQL主从复制解决的问题
- 数据分布:可以在不同地理位置进行数据备份
- 负载均衡:降低单个服务器的压力
- 高可用和故障切换:避免单点失败
- 升级测试:可以用更高版本的MySQL作为从库

本博客所有文章除特别声明外,均采用 CC BY-SA 4.0 协议 ,转载请注明出处!