php面试时经常会问一些理论性的问题,给大家整理了些,很多都是网上找的,难免有不严谨的地方,望请大家指正,共勉
mysql
innodb和myisam是存储格式:
-
myisam:
.frm: 存储表定义
.myd(MYData):存储数据
.MYI(MYindex):索引的数据树。
(索引和数据是分开存放的,索引数上只存了数据的物理地址) -
innodb:
.frm:存储表定义
.ibd:存储数据和索引,在同一个文件中
innodb和myisam引擎的区别:
- innodb是聚簇索引:索引和数据是放一起的
- mysaim非聚集(局促)索引:任何情况下都必须回表查询,因为MyISAM索引文件和数据文件是分离的。
- 详细介绍
b+树和b树的区别:
innodb和myisam都是b+树实现,只是底层innodb用的是主键,myisam用的是随机是生成的。b+树=聚簇索引+非聚簇索引
- b 所有层之间key不重复,所有层上的节点都放着数据,找到就跳出
- b+ 所有层之间key会重复,只有最底层的才带数据
b+树的优点:
- 单一节点存储更多的元素,使得查询的IO次数更少。
- 所有查询都要查找到叶子节点,查询性能稳定。
- 所有叶子节点形成有序链表,便于范围查询。
如何优化这句sql :
原:select uid from user limit 7000000,10:
新:select uid from user where id>700000000 limit 10;(第一个要扫描700000行)
hash索引的特点:
在等值的情况下比btree效率更高(重复的不多的情况下),不支持排序,需要回表查询
聚集索引和非聚集索引:
在非聚集索引通过name取得性别,就需要先拿到叶上的主键id,再拿着id去主键b+树的聚集索引里找
- 聚集索引:就是按照每张表的主键构造一棵B+ 树,它的叶子节点存放的是整行数据,叶上的数据如"id,name,age,sex"等全部数据
- 非聚集索引:比如给name建了一个普通索引,那么就生产一棵树B+树,它的叶子上只有"name,id"
什么是索引覆盖:
联合索引是一棵树上,数据"骷大人,29",两棵树的话肯定又要走一遍流程了,需遵从最左原则
- 比如我用户表有三个索引,一个id的聚簇索引,一个身份证的唯一索引,一个用户名的普通索引,那么会建立3棵索引树,
- 第一个树的叶上有id=1 idcard=123 name=kudaren 等全部数据·
(主键的聚集索引)
,第二棵树 idcard=123,id=1,第三棵树name=kudaren id=1·(两个普通的非聚集索引)
,第一个数可以拿到全部数据,而其他的需要拿着id再查一次(拿id去索引树查) - 如果解决普通索引二次查找的问题,建立联合索引,name,和age ,select name,age from tabe where name=‘xx’
如何实现索引覆盖:
- 尽量使用主键查询,因为inodb是聚簇索引(聚簇索引上存了行的全部内容),不需要回表,
- 退一步做普通索引的覆盖
1.联合索引a和b搜索a直接拿到b,
2.先拿到主键id,再去聚簇索引通过id搜索数据。
索引的分类:
按类型:unique(唯一),normal(普通)
按方法:btree(b+树),hash(哈希)
普通索引,全文索引,联合索引
主键索引,唯一索引(区别是后者允许为空)
如何合适的使用索引:
- 给经常用到的字段,增加索引,然后使用的时候也要避免比如is null,<>,like啊等操作,in的话尽量用between代替。
- 经常用到的组合搜索可以增加联合索引,秉持最左原则,越常用的字段越前面。比如你加的是a,b 搜索的时候却搜了b,a
- 用explain来看一下这条sql的索引应用效果。
- 当字段操作大于读取的情况也不建议加索引,主键建议设置成自增。
- 重复性较高的,比如性别1,2也不建议加索引。
索引会失效的情况:
网上说的between替换成in是错的,事实是如果between 1 and 100 是可以的,但 1 and 100000000000就不走了,优化器的聪明之处
- 联合索引没有秉持最左原则。
- 索引只用到了一部分,因为联合索引从左到右执行到< 或者>的区间查询的时候停止,那么建立了a,b,c,d,然后c做了一个区间查询,那么d就用不到,可以吧索引改成a,b,d,c
- sql中用了mysql的内置函数
- 用了%like,or等操作
以下情况索引是否使用:
现有user表,建立了联合索引a,b,c
问:select * from user where c=4 and b=6 and a=3;
答:mysql的优化器很聪明,会在内部转换sql,所以会用到索引
问:select * from user where a=3 order by c;
答:a索引用到了,c没用到,explain看到filesort,如c改成b不会有
字段的数据类型:
- 新增字段你的时候选择合适的数据类型,字段大小估计一个合适的不要选的很大,
- 用内置的日期和时间类型存储时间,用整型存储ip地址
- 应尽量将字段设置成not null,null对索引不友好,用0或者空字符来替代。
聚合函数执行顺序:
SQL:SELECT age,count(age) as enum FROM people
where age<>‘’ GROUP BY age HAVING enum>100 order by enum desc;
顺序: where group having order (先where运算,得到符合条件的数据,进行group操作,再having筛选group的值,最后排序)
数据库设计三大范式:
- 第一范式(1NF)(每一列都不可分割)是指数据库表的每一列都是不可分割的基本数据项,同一列中不能有多个值,即实体中的某个属性不能有多个值或者不能有重复的属性。
- 第二范式(2NF) (完全依赖于主键) 简而言之,第二范式就是属性完全依赖于主键。
- 第三范式(3NF) 任何字段不能由其他字段派生出来,它要求字段没有冗余
锁的分类:
- 通过锁定范围:行级锁(innodb)和表级锁(myisam)
- 通过锁定类型:排它锁和共享锁
排他锁:[for update] innodb除select的操作都会排它锁]锁定后,用select还是能读取,但是无法再给数据加共享锁和排它锁(一行不能同时存在两个锁)
select * from table where xxx for update
共享锁:[ lock in share mode] 锁定后,除了排它锁,其他都可以加上
select * from tb_test where id = 1 lock in share mode;
(InnoDB 引擎中的四种隔离级别就是用排他锁 + 共享锁
实现的)
mysql的执行过程:
server层
- 连接器(权限校验)
- 分析器(判断sql语法,有的话执行缓存)
- 优化器(判断何时的索引
- 执行器->返回结果
存储引擎层
- 存储数据,提供读写接口如innodb,mysaim,memory
垂直分表和水平分表:
垂直分表:
把不常用的字段单独放在一张表;
把text,blob等大字段拆分出来放在附表中;
经常组合查询的列放在一张表中;
实现方式:修改sql
水平分表:
比如原表有100万行数据,那么拆分成一行25万的四个表
实现方式:union all
sql中时间转换的函数:
- 从标准时间提取:EXTRACT(DAY FROM capTime) as day 提取2020-02-02 11:11:11 中的日
- 从时间戳提取:FROM_UNIXTIME(add_time,‘%Y-%m-%d’) as date 提取时间戳中的月日年
redis
redis过期策略:
- 定时删除(定期删除过期的)
- 惰性删除 使用的时候判断,如果过期则删除返回null
抢红包的大概思路:
类似使用队列防止超卖的思路。
比如一个10元,5个红包,那么push5次2到队列里(随机红包将2经过计算就好),后面抢的时候先pop就好。
你pop 了redis 列表里的数据之后进程挂了怎么办:
- 增加一个备用队列,主队列消息出队后加入备用队列,在消息被成功 消费后,从备用队列剔除。
- 通过使用专门的消息队列工具如rabbitmq,它有个消息确认机制(Acknowlege)。当消费者处理完消息后,会向RabbitMQ发送回执ACK,告知消息已经被接收。
自动ACK:消息一旦被接收,消费者自动发送ACK(不太重要)
手动ACK:消息接收后,不会发送ACK,需要手动调用(重要)
用 redis 实现的分布式锁不适合高并发情况,如何优化:
为什么redis是单线程:
因为cpu不是redis的瓶颈,有可能是内存,还可以避免不必要的上下文切换和竞争关系
memcached 与 redis 区别:
redis 支持持久化,字符类型更丰富,Memcached是多线程
redis使用场景:
- string:做缓存,分布式锁
- list :做对队列
- hash:适合存储对象key=>filed:value
- set和:微博关注,微信点赞。
- sortSet:热词排行,直播礼物排行(延时队列)
缓存失效的情况:
-
缓存击穿:
原由:缓存中没有但数据库中有的数据
解决办法:加锁或设置数据不过期 -
缓存穿透:
原由:查询一个一定不存在的数据,由于缓存是不命中时需要从数据库查询。
解决办法:布隆过滤或缓存空数据 -
缓存雪崩:
原由:同一时间大批缓存过期,导致大量的请求进入mysql
解决办法:缓存时间设置的长些的情况下,加一个随机值,防止同时大量过期
高并发抢购如何实现高可用,不会出现 502,不会出现超卖的情况:
使用锁的方式+mysql事务,如使用redis的sex配合nx ex来实现锁,然后走队列判断库存,事务减库存。
常用的使用场景:
简单的缓存
分布式锁
队列
发布/订阅
时间序列/排行榜
用户token
数据共享
接口限流
防超卖
布隆过滤
防超卖
redis过期和删除策略:
- 定期删除:redis默认每隔100ms就随机抽取一些设置了过期时间的key,检查其是否过期,如果有过期就删除。缺点:占资源
- 惰性删除:在你获取某个key的时候,redis会检查一下,这个key如果设置了过期时间并且过期了,是的话就删除。缺点:命中率差
redis各命令如何实现原子性:
pipeline或lua脚本
其他
数组和对象的区别:
- 数组是普通变量,拷贝副本,只有引用传递后才会改变原先的值,而对象就是引用类型。
- 单纯存数据用数组,如果要进行自定义的操作,则对象。
API请求如何保证数据不被篡改:
使用https,或在URL上加上一个sign秘钥
GIT常用的命令:
- git clone htt dev
- git status (红色工作区)(绿色暂存区)
- git diff 具体更新了哪里
- git add
- git commit
- git push
restful接口规范:
在接口名里取消add,get等动词,转换成请求类型来表示,比如 post 创建数据、put修改数据、get获取数据、delete删除数据。
公钥私钥的原理:
- 持有公钥的一方(服务器)在收到持有私钥的一方(客户端)的请求时,甲会在自己的公钥列表中查找是否有乙的公钥,如果有则使用一个随机字串使用公钥加密并发送给乙。
- 乙收到加密的字串使用自己的私钥进行解密,并将解密后的字串发送给甲。
- 甲接收到乙发送来的字串与自己的字串进行对比,如过通过则验证通过,否则验证失败。
微信支付的实现方式:
- 先创建一个订单,然后拿着单号,appid,支付价格,回调地址等参数请求腾讯的接口,获得sign等信息。
- 将sign提供给前端唤醒支付功能
- 设置对应的回调地址,写上订单状态变更的逻辑代码,请求成功后会自动请求回调地址
redis队列数据丢失怎么办:
消费者把数据从队列取出来的时候,先放入备用队列,等业务都处理完后再从备用队列删除(备用队列一个单独的脚本判断过期时间,如过期则放回主队列)
http的状态码
- 1消息系列 2消息成功系列 3消息重定向系列 4请求错误系列 5服务器错误系列
- 202 Accepted(服务器已接受请求,但尚未处理)204 No Content(服务器成功处理了请求,没有返回任何内容 )
- 301永久跳转 302临时跳转 304 Not Modified:(未更改,比如tp6开启了缓存)协商缓存(询问服务器改变了没没有则取本地的数据)
- 401访问未授权 403禁止访问
- 500服务器内部错误 502 Bad Gateway(很大可能是nginx服务的问题) 503服务器维护中 504 Gateway Timeout
错误级别:
- E_ERROR 这是一个严重错误,不可恢复,如位置异常,(内存不足)等
- E_WARNING 警告,最一般的错误,如函数的(参数错误)等
- E_PARSE 解析错误,在解析PHP文件时产生,(语法错误,没加封号)
- E_NOTICE 通告表示可能在操作一些未知的变量等。在开发时可开启通告,以保证程序是"安全通告"的,在正式系统中,应关闭通告
cgi 和 fastcgi 区别:
- cgi 每次请求创建一个进程 fastcgi 多个请求复用一个进程
- php-fpm 和 fastcgi 的关系 - fastcgi 是协议 php-fpm 是实现
跨域的解决方案:
- 接口repose时候的header(‘Access-Control-Allow-Origin: *’);
- 请求(JSONP)【原理就是通过src的方式】
- nginx转发
什么是布隆过滤器:
是一种数据结构,用来判断一个元素是否在另一个元素中,相比hash表,他的有点是相对开销小,缺点就是有一定的误差,它说有那可能有,它说没有,那一定没有。
使用场景:网络过滤判断黑名单,缓存穿透的问题
拍下减库存和付款减库存的使用场景:
- 对库存比较敏感的业务,比如秒杀,适合拍下减库存,不会有超卖的风险,虽然会有不按时付款订单被取消的人,但一般也会有人去捡漏,而且超卖明显比没卖完更麻烦
- 像团购这类可以用付钱减库存,这类周期比较长,瞬时流量没有那么大,最大程度规避恶拍不付款的情况
常见的加密:
单向全是通过hash,其特点不可逆,长度相等。password_hash函数默认使用使用bcrypt加密,password_verify函数比对是否一样
- 双向:base64decode,urlencode
- 单向:md5,sha1,bcrypt
- 对称加密:AES(同样一个秘钥加解密)
- 非对称加密:RSA(分公钥和私钥)
简述高并发网站解决方案:
A、前端优化(CND加速、建立独立图片服务器)
B、服务端优化(页面静态化、并发处理[异步|多线程]、队列处理)
C、数据库优化(数据库缓存[Memcachaed|Redis]、读写分离、分库分表、分区)
D、Web服务器优化(负载均衡、反向代理)
常见的提高服务安全性的方法:
- 错误日志直接打印出来了
- 某些页面比方说忘记密码被人恶意调用, 解决方法,增加验证码等机制
- 某些使用量特别高的功能高峰期拖垮服务器,增加缓存,或者拉出来放在单独的服务器
- 服务器弱密码,改成秘钥增加白名单
对于大并发业务的防护,有什么看法:
我觉得并发是防止单个时间大量流量涌入,导致服务器或者数据库挂掉,我觉得数据库比服务器更脆弱,(在服务端构建一个屏障)防止前端请求直接涌入数据库。
- 在web服务器优化端:做负载均衡,将图片放在cdn服务器。
- 在数据库端:做数据库缓存,读写分离。
- 服务端优化:页面静态化,数据做缓存(或参与的人太多了等,可直接用incr)
幂等性:
接口幂等性就是用户对于同一操作发起的一次请求或者多次请求的结果是一致的,不会因为多次点击而产生了副作用(不会出现两条)。
incluce和require的区别:
- incluce 在用到时加载
- require 在一开始就加载
- _once 后缀表示已加载的不加载
final和abstract:
关键字final:
用于类、方法前,不可被继承,不可被覆盖(如果不想某个类或方法不想被继承)
抽象类abstract:
用于类,方法前
一个类中至少有一个抽象方法,有一个抽象方法整个就是抽象类
抽象方法前面必须加abstract
抽象方法不允许有{}
子类中所有的抽象类都必须重写定义一遍
只允许被继承,不能实例化
(上面两个是反过来的)
接口interface(抽象类的一种):
整个类都是只有名字的,比如put() get()后面通过继承来写入具体的操作代码(如架构师不想我们瞎起名字,定义好了规范,我们继承)
只能能通过implements继承
接口中定义的所有方法都必须是公有的,这是接口的特性。
solr 没有降低 db 压力的作用,它不是缓存,你怎么不用 redis?redis 更好?
我感觉redis适合做缓存系统,而solr更适合做搜索引擎,它支持的搜索条件会更多
常见的psr规范有哪些:
psr2代码标准(psr1的升级),psr3日志标准,psr4自动加载标准(替换psr0)
消息中间件的用途:
解耦、异步、削峰
大屏数据显示,需要聚合统计的数据量太大,mysql 查询太慢。有什么解决方案:
- 分析sql,增加合理的索引
- 需要统计的数据每次增加或减少时都给他算出来存好,
- 取巧,刷新页面时实际上不显示这个数,而是显示一个“点击显示数据”的按钮。
session和cookie的关系:
session和cookie之间是通过 C O O K I E [′ P H P S E S S I D′ ] 来联系的,通过 _COOKIE['PHPSESSID']来联系的,通过 COOKIE[′PHPSESSID′]来联系的,通过_COOKIE[‘PHPSESSID’]可以知道session的id,从而获取到其他的信息。
什么是OOP编程:
降低对象之间的耦合,增加程序的可复用性、可扩展性和可维护性
ORM代表什么:
ORM代表对象关系映射
array_map与array_walk区别:
- array_map的用法是array_map(函数名,数组),而array_walk的用法是array_walk(数组,函数名);
- array_map不可以改变原函数的值,会获取到新的数组。array_walk是可以改变原函数的值的(加个引用)。
Trait类的使用:
- 介绍:PHP是单继承的语言,在当前类中使用两个或两个以上的其他类时,继承不能实现。
- 使用:Trait是一种不同于继承的语法,定义一个trait类,在其他类中使用它采用use引入,类似于命名空间,但含义不同。
- 区别:引入Trait类后,相当于require或include进来,Trait类与当前类是可以看做同一个类的,可以用$this关键字调用Trait类的方法,而常规use需要(new RedisTrait())->get()调用;
use App\Traits\Redis\RedisTrait;class FoolsDay2023Push{ use RedisTrait; public function test(){ $this->redis->get('name');//这个方法实际在RedisTrait中,但可以直接$this->关键词调用 }
来源地址:https://blog.csdn.net/xiantianga6883/article/details/129392517