PHP面试中会考查你的MySQL题大概是哪些?
-- 选取表 将多个表数据通过笛卡尔积变成一个表
FROM
-- 指定join 用于添加数据到 on 之后的虚表中
JOIN
-- 对笛卡尔积的虚表进行筛选
ON
-- 对虚表数据进行过滤筛选
WHERE
-- 分组
GROUP BY
-- 对分组后的结果进行聚合筛选
HAVING
-- 返回数据列表 返回的单列必须在 group by 子句中 聚合函数除外
SELECT
-- 数据去重
DISTINCT
-- 排序
ORDER BY
-- 限制结果返回
LIMIT
从sql优化方面来讲:
从表优化方面来讲:
从库优化方面来讲:
可以将数据库分为主从库,主库用来写数据,多个从库用来读数据,主库与从库之间通过某种机制实现数据同步
MySQL优化原则:
总结下来最重要的也就三点:
对于低性能的SQL语句定位,MySQL提供了 explain 命令来查看语句的执行计划。
执行结果:
id | select_type | table | partitions | type | key | ke_len | ref | rows | filtered | Extra |
---|---|---|---|---|---|---|---|---|---|---|
1 | SIMPLE | table_name | (Null) | index | idx_table_name_indexName | 500 | (Null) | 2 | 75000 | Using where;Using index |
id:SELECT识别符(查询序号),表示一个查询中各个子查询的执行顺序。
select type:子查询的查询类型。
table:查询的数据表;partitions:使用的分区,需要结合表分区才可以看到。
type:访问类型。
possible_keys:可能使用到的索引(不一定会使用)。
查询涉及到的字段上若存在索引,则该索引将被列出来。当该列为 Null 时就要考虑当前的SQL是否需要优化了。
key:显示 MySQL 在查询中实际使用的索引,若没有使用索引,显示为 Null。
PS:查询中若使用了覆盖索引(索引的数据覆盖了需要查询的所有数据,即所查询数据在索引中即可取到无需回表操作),则该索引仅出现在 key 列表中。
key_length:索引长度。
ref:表示上述表的连接匹配条件,即哪些列或常量被用于查找索引列上的值。
rows:返回估算的结果集数目,并不是一个准确的值。
extra:
推荐:SQL性能优化的目标至少要达到 range 级别,要求是 ref 级别,若是可以达到 const 最好。
- const 单表中最多只有一个匹配行(主键或者唯一索引),在优化阶段即可读取到数据;
- ref 指的是使用普通索引(normal index);
- range 对索引进行范围检索 ;
SQL生命周期:
select * from table where age > 20 limit 1000000,10;
-- 使用覆盖索引优化
select * from table where id in (select id from table where age > 20 limit 1000000,10);
-- 若是 id 连续
select * from table where id > 1000000 limit 10
-- 数据提前缓存至redis 查询直接走缓存
-- LIMIT 偏移量大的时候,查询效率低下,可以记录上次查询的最大ID,下次查询直接根据该ID来查询。
-- 查看慢查询日志是否开启
SHOW VARIABLES LIKE 'slow_query_log';
-- 开启慢查询日志
SET GLOBAL slow_query_log = on;
-- 关闭慢查询日志
SET GLOBAL slow_query_log = off;
-- 查看临界时间(s) 一旦sql执行时间超过临界时间将被记录至慢查询日志
SHOW VARIABLES LIKE 'long_query_time';
-- 设置临界时间
SET long_query_time = 10;
慢查询的优化思路:
1.访问数据太多导致查询性能下降
2.避免执行如下SQL语句
查询不需要的数据(使用 limit 解决)
多表关联返回全部列(指定列名)
总是返回全部列(避免使用 SELECT *)
重复查询相同的数据(缓存数据,下次直接取缓存)
扫描额外的记录(使用 explain 进行分析,若发现查询需要扫描大佬的数 据,但只返回少数行:)
使用索引覆盖扫描,把所有列都放到索引中,这样存储引擎就不需要回表直接获取索引中的数据就可以返回结果
改变数据库和表的结构,修改数据表范式
重写SQL,让优化器可以以更优的方式执行查询
UNION ALL 的效率高于 UNION
UNION 会给临时表加上一个 DISTINCT 选项,这回导致对整个临时表的数据做唯一性校验,资源消耗相当高。
默认情况下,MySQL 会对 GROUP BY 分组的所有值进行排序。
若在SQL中显式的包括一个相同的列的 ORDER BY 语句,MySQL 就可以毫不犹豫的对其进行优化,尽管仍然进行了排序。
若不想对分组数据的值进行排序,可以指定 ORDER BY NULL 禁止排序。
MySQL 中可以通过子查询来使用 SELECT 语句创建一个单列的查询结果,然后把这个结果作为过滤条件用在另一个查询中。
使用子查询可以一次性的完成很多逻辑上需要多个步骤才能完成的SQL操作,同时也可以避免事务或者表锁死,并且写起来很容易。
但是有些情况下,子查询可以被更有效率的连接(JOIN)替代。JOIN 更优销量是因为MySQL不需要在内存中创建临时表来完成这个逻辑上的需要俩个步骤的查询工作。
-- 子查询
SELECT col1 FROM customerinfo WHERE CustomerID NOT in (SELECT CustomerID FROM salesinfo)
-- join 优化后
SELECT col1 FROM customerinfo
LEFT JOIN salesinfoON customerinfo.CustomerID=salesinfo.CustomerID
WHERE salesinfo.CustomerID IS NULL
当删除全表中记录时,使用 DELETE 语句的操作会被记录到 undo 块中,删除记录也记录到 binlog。
当确认需要删除全表时,会阐释大量的 binlog 并占用大量的 undo 数据块。
使用 TRUNCATE 替代不会记录可恢复的信息,即数据不可恢复。因此使用 TRUNCATE 操作仅会占用极少的资源和时间。
此外 TRUNCATE 可以回收表的水位,使自增字段值归零。
主从复制原理:
参考文章:深入解析Mysql 主从同步延迟原理及解决方案
PS:DML(数据操作语言,如insert/delete/update/select等);DDL(数据定义语言,如create、alter、drop等)
谈到MySQL数据库主从同步延迟原理,得从MySQL的数据库主从复制原理说起,MySQL的主从复制都是单线程的操作,主库对所有 DDL
和 DML
产生 binlog
,binlog
是顺序写,所以效率很高。slave
的 Slave_IO_Running
线程到主库取日志记录到自己的中继日志,效率也比较高。下一步, 问题来了,slave
的 Slave_SQL_Running
线程将主库的 DDL
和 DML
操作在 slave
实施。DML
和 DDL
的IO操作是随机的,不是顺序的,成本高很多,还可能有 slave
上的其他查询产生 lock
争用,由于 Slave_SQL_Running
也是单线程的,所以一个 DDL
卡主了,需要执行10分钟,那么所有之后的 DDL
会等待这个 DDL
执行完才会继续执行,这就导致了延时。有朋友会问:“主库上那个相同的DDL也需要执行10分,为什 么slave会延时?”,答案是master可以并发,Slave_SQL_Running线程却不可以。
当主库的TPS并发较高时,产生的 DDL数量超过 slave 一个 sql 线程所能承受的范围,那么延时就产生了,当然还有就是可能与 slave 的大型 query 语句产生了锁等待。
MySQL主从同步延迟解决方案:
relay log
里面的SQL效率自然就高了。sync_binlog=1
,innodb_flush_log_at_trx_commit=1
之类的设置,而 slave
则不需要这么高的数据安全,完全可以讲 sync_binlog
设置为 0
或者关闭 binlog
,innodb_flushlog
, innodb_flush_log_at_trx_commit
也可以设置为 0
来提高sql的执行效率,这个能很大程度上提高效率。另外就是使用比主库更好的硬件设备作为slave。参数介绍:
sync_binlog:
该参数对于MySQL来说至关重要,①当 sync_binlog=0
时,事务提交后,MySQL不会做 fsync
之类的磁盘同步指令来刷新 binlog_cache
中的信息到磁盘,而是让 filesystem
自行决定什么时候来同步指令,或者等到 binlog_cache
满了之后同步到磁盘;②当 sync_binlog=n
时,每提交 n
次事务,MySQL将会进行异常 fsync
之类的磁盘同步指令来将 binlog_cache
中的数据强制写入磁盘。③MySQL中sync_binlog
默认是0
,此时性能是最好的,但风险也是最大的,一旦系统crash,binlog_cache
中的所有 binlog
信息都将丢失。而当 sync_binlog=1
时,是最安全但性能损耗最大的。innodb_flush_log_at_trx_commit:
默认是 1
,即每一次事务提交或者事务外的指令都需要将日志写入(flush)磁盘;当其设置为 2
时;指令不写入硬盘而是写入系统缓存。如何判断主从间是否存在同步延迟?
show slave status
来进行查看,查看 Seconds_Behind_Master
参数的值判断:①当值为 NULL
时,表示 io_thread
或者是 sql_thread
Running
状态是 NO
而非 YES
;②当值为 0
时,表示主从复制正常。③当值为 正值
时,表示主从同步延迟已经出现,数字越大延迟越严重;mk-heartbeat
-- 主从复制的配置
-- 配置主服务器 master
-- 修改 my.ini/my.conf
[mysqld]
log-bin=mysql-bin -- 启用二进制日志
server-id=102 -- 指定服务器唯一ID
-- 配置从服务器 slave
log-bin=mysql-bin -- 启用二进制日志
server-id=226 -- 服务器唯一ID
-- 主服务器上授权从服务器
GRANT REPLICATION SLAVE ON *.* to 'slavename'@'IP' identified by 'root'
-- 服务器上使用
change master to
master_host="masterIP"
master_user="masterUser"
master_password="masterpassword"
-- 开始主从复制
start slave
PS:每次修改配置后都需要重启服务器,然后可以在主从服务器上用 show master/slave status
查看主从状态
在数据库中数据表的数据量非常庞大,无论是索引还是缓存的压力都很大,对数据库进行 sharding,使之分别以多个数据库服务器或多个表存储,以减轻查询压力。
MyISAM索引(非聚集索引)的实现:
何时使用聚簇索引或非聚簇索引?
操作 | 使用聚簇索引 | 使用非聚簇索引 |
---|---|---|
列经常被分组排序 | YES | YES |
返回某范围内的数据 | YES | NO |
一个或极少不同值 | NO | NO |
小数目的不同值 | YES | NO |
大数目的不同值 | NO | YES |
频繁更新的列 | NO | YES |
外键列 | YES | YES |
主键列 | YES | YES |
频繁修改索引列 | NO | YES |
PS:
int > date/time > enum/char > varchar > blob
,选择数据类型时,可以考虑替换。RANGE/LIST/HASH/KEY
分区类型,其中RANGE最常用;MyISAM和InnoDB的区别:
数据库三范式(层层递增):
1.为什么要尽量设定一个主键?
主键是数据库确保数据行在整张表唯一性的保障,设定了主键后,在后续的删改查操作时都可以更快速的确保操作的数据范围安全。
2.主键使用自增ID还是UUID?
推荐使用自增ID,不要使用UUID。
3.字段为什么要求定义为 NOT NULL?
null 值会占用更多的字节,且在程序中造成很多与预期不符的情况。
4.存储用户的密码散列,用什么字段存储?
密码散列,盐(salt),用户身份证号等固定长度的字符串都应该使用 char 而不是 varchar 来存储,这样可以节省空间而且提高检索效率。
5.SQL语句优化:
索引是一种特殊的文件,它们包含着对数据表中所有记录的引用指针,是需要占据物理空间的。
更通俗的说,索引就相当于目录。为了方便查找书中的内容,通过对内容建立索引形成目录。
从逻辑角度来讲:
从物理角度来讲:
优点:
缺点:
0/特殊值/空串
代替;-- 1.建表时创建索引
CREATE TABLE user_index (
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)
);
-- 2.使用 ALTER TABLE命令增加索引
ALTER TABLE user_index ADD INDEX name (first_name,last_name);
-- 3. 使用 CREATE INDEX 创建索引
CREATE INDEX index_name ON table_name (column_list);
-- 删除索引
ALTER TABLE user_index DROP KEY name;
ALTER TABLE user_index DROP KEY id_card;
ALTER TABLE user_index DROP KEY information
-- 建表 sys_user
CREATE TABLE `sys_user` (
`id` varchar(64) NOT NULL COMMENT '主键',
`name` varchar(64) DEFAULT NULL COMMENT '名字',
`age` int(64) DEFAULT NULL COMMENT '年龄',
`pos` varchar(64) DEFAULT NULL COMMENT '职位',
PRIMARY KEY (`id`),
KEY `idx_sys_user_nameAgePos` (`name`,`age`,`pos`)
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COMMENT='用户表';
-- 创建复合索引
ALTER TABLE sys_user ADD INDEX idx_sys_user_nameAgePos(name,age,pos);
-- 检索语句
SELECT * FROM sys_user WHERE name='小明' AND age = 22 AND pos ='java';
-- 测试A 满足索引规则
-- 不仅满足最左前缀法则,而且满足全值匹配,很显然是最优的查询方式
EXPLAIN SELECT * FROM sys_user WHERE name='小明' AND age = 22 AND pos ='java';
-- 满足俩个索引条件 name 和 age 字段
EXPLAIN SELECT * FROM sys_user WHERE name='小明' AND age = 22;
-- 仅满足一个索引条件 name 字段
EXPLAIN SELECT * FROM sys_user WHERE name='小明';
-- 测试B 不满足索引规则 因为没有name检索条件,索引失效了 变成了全表扫描
EXPLAIN SELECT * FROM sys_user WHERE age = 22;
EXPLAIN SELECT * FROM sys_user WHERE age = 22 AND pos ='java';
EXPLAIN SELECT * FROM sys_user WHERE pos ='java';
-- 满足最左前缀索引规则 但是不满足全值匹配
EXPLAIN SELECT * FROM sys_user WHERE age = 22 AND name ='小红';
-- 在索引上进行任何操作(计算、函数、类型转换)都会导致索引失效转为全表扫描
EXPLAIN SELECT * FROM sys_user WHERE LEFT(name,1)='小明';
-- 存储引擎不能使用索引中范围条件右边的列
-- age 字段为范围查询,则 name 字段索引有效, pos 字段索引无效
EXPLAIN SELECT * FROM sys_user WHERE name='小明' AND age < 22 AND pos ='java';
-- 尽量使用覆盖索引 而不是 select * 虽然不会导致索引无效,但查询效率会变低
EXPLAIN SELECT * FROM sys_user WHERE name='小明' AND age =22 AND pos ='java';
EXPLAIN SELECT name,age,pos FROM sys_user WHERE name='小明' AND age =22 AND pos ='java';
-- MySQL 在使用不等于 (!= / <>) 的时候会导致索引失效,从而全表扫描
EXPLAIN SELECT * FROM sys_user WHERE name !='小明';
-- is null / is not null 也无法使用索引
EXPLAIN SELECT * FROM sys_user WHERE name is not null;
-- like 以通配符('%abc...')开头时,索引会失效变成全表扫描
-- 某些条件下,左通配符也可以使用索引,但不会进行回表操作
EXPLAIN SELECT * FROM sys_user WHERE name like '%明%';
-- 通配符写在右边时,可以避免索引失效
EXPLAIN SELECT * FROM sys_user WHERE name like '明%';
-- 覆盖索引下可以避免索引失效
EXPLAIN SELECT name,age,pos FROM sys_user WHERE name like '%明%';
-- 字符串不加单引号,索引会失效,因为 int 转 字符串 会导致类型转换
EXPLAIN SELECT * FROM sys_user WHERE name=222;
-- 尽量少用 or 查询,会导致索引失效
EXPLAIN SELECT * FROM sys_user WHERE name='小明' or age = 22;
1. B+ 树相对于 B树的改进点
2. 为什么MySQL索引选择使用 B+树而不是 B树?
3. 百万级别或以上的数据如何删除?
满足ACID特性的一组操作(原子性/隔离性/一致性/持久性)。
MySQL在多线程并发场景下,可能会出现脏读(dirty read)、不可重复读(non-repeatable read)、幻读(phantom read)这类并发问题,
为了解决这些问题,引申出“隔离级别”的概念。需要知道的是,隔离级别越高,效率就会越低。
事务A修改了数据,但未提交,而事务B查询了事务A修改过但未提交的数据,这就是脏读,因为事务A可能会回滚。
不可重复读,通俗说就是同一个事务内,查到的结果不一致,失去了MySQl的一致性。
幻读是指在同一个事务中,存在前后俩次查询同一个范围的数据,但是第二次查询却看到了第一次查询没看到的行,一般情况下指新增。
事务A先修改某个表的所有记录的状态字段为已处理,未提交;事务B也在此时新增了一条未处理的记录,并提交了;
事务A随后查询记录,发现仍有一条记录是未处理的,这就是幻读。
脏读说的是事务查到了本不该查到的数据,强调的动作是查询;
不可重复读强调的是一个事务执行查询操作的时候,其他事务依然可以增删改数据,而查询事务并不知道数据已经改变;
幻读强调的是一个事务修改了数据后进行查询操作,却发现仍有未被修改的数据,因为修改后查询前有事务进行了插入操作;
针对上述并发场景下存在的数据问题,MySQL制定了四种不同的“隔离级别”:
事务中的提交,即使没有提交,对其他事务也都是可见的。
最高隔离级别,通过强制事务串行(单线路)执行,避免了幻读的问题。
简单来说就是,可串行化会在读取的每一行数据上都加上锁,所以可能导致大量的超时和锁争用的问题。
对于UPDATE/DELETE/INSERT
语句,InnoDB会自动给涉及数据集加排它锁
而MyISAM在执行查询语句SELECT前,会自动给涉及的所有表加读锁,在执行增删改操作前,会自动给涉及的表加写锁。
按使用方式分为:
按粒度分为:
表锁: 开销小,加锁快,不会出现死锁,锁定粒度大,但是锁冲突概率高,并发度低。
行锁: 开销大,加锁慢,会出现死锁;锁粒度小,发生锁冲突的概率低,并发度高。
InnoDB和MyISAM的俩个本质区别就是:InnoDB支持行锁和事务;
InnoDB实现了俩种类型的行锁:
-- 共享锁(S)
SELECT * FROM table_name WHERE ... LOCK IN SHARE MODE;
-- 排他锁(X)
SELECT * FROM table_name WHERE ... FOR UPDATE;
悲观锁(pessimistic lock):
乐观锁(optimistic lock):
悲观锁的实现:
乐观锁的实现:
悲观锁和乐观锁的合理使用:
PS:
间隙锁:
死锁:
避免死锁: