PHP面试题整理

PHP面试题整理

用最简单的语言描述什么是PHP,以及它能做什么?

PHP是动态解释性脚本语言,主要适用于Web应用开发

如下代码

$a = true && false;
$b = true and false;

var_dump($a);
var_dump($b);

$a = false;
$b = true;

原因是=的优先级比&&低,但是比and高

用PHP代码实现官方var_dump函数

代码链接:https://github.com/nonfu/php_interviews/blob/master/var_dump.php

require、include、require_once、include_once它们各自的用途是什么?如果某个业务按需加载PHP文件,你会怎么设计和实现

我们把 require、include、require_once、include_once 分成两对,对比着来看:

1、require 和 include 都是用于将另一个php 文件的内容插入到当前 php 文件的引用位置,两者唯一的区别在于对错误的处理上,require 在遇到错误时会产生致命错误(E_COMPILE_ERROR)并退出脚本,而 include 在遇到错误时只发出警告(E_WARNING)并继续执行后续代码,使用哪一个取决于你对引入文件出错的容忍度,如果使用 include 的话即便引入文件不存在也能继续执行后续脚本,所以从稳健性上来讲,require 更好;

2、require_once/include_once 功能和 require/include 也相同,只不过前者会检查引入文件是否已经加载过,如果已经加载过则不再加载,在遇到错误时的处理机制也分别和 require/include 一一对应,由于存在额外的重复加载检查机制,所以 require/include 性能更好。

当然,如果需要引入多个文件,在 PHP 脚本中罗列长长的 require/include 语句显然很不优雅,好在从 PHP5 开始,我们可以通过 spl_autoload_register() 函数定义自动加载器,当代码中遇到未定义的类/接口/函数时,会尝试通过自动加载器去加载,我们通常使用这种机制来实现按需加载。

需要注意的是,require、include、require_once、include_once 和 if、else、for、break、return 一样都是流程控制语句,而不是函数。

我们都知道可以通过索引对数据库查询进行优化,MySQL支持哪些索引,不同索引之间的性能对比如何?索引越多越好吗?

MySQL 索引是为了帮助快速查询数据的数据结构,我们平常对数据库的操作大部分是读多写少,合理设置索引,可以有效提高查询效率,但索引需要额外处理和存储,因此在写入的时候性能要比不使用索引差。

MySQL 索引属于存储引擎级别的概念,我们比较熟悉的存储引擎是 MyISAM 和 InnoDB,后者支持事务、外键和行级锁,所以我们日常使用 InnoDB 更多。两者支持的索引和对索引的处理也不尽相同。

我们日常常见的索引有以下四个:主键索引(PRIMARY)、唯一索引(UNIQUE)、普通索引(INDEX)和全文索引(FULLTEXT)。我们可以在指定字段上设置索引,也可以组合几个字段设置索引。从查询性能上说,一般它们的优先级是这样的:主键索引>唯一索引>普通索引>全文索引。

在 MyISAM 中,索引和数据是分离的,所以允许数据表没有主键,而 InnoDB 中索引和数据是一体的,表数据本身就是主索引,并且按照主键聚集,所以必须包含主键,此外 InnoDB 不再支持 FULLTEXT 全文索引,因为全文索引通常用于实现全文搜索,如果不支持分词的话这个查询很鸡肋,我们现在一般借助搜索引擎来实现类似搜索功能。

在日常开发实践中,如何定位数据库慢查询语句?针对慢查询语句,你说如何进行优化的,说说你的思路。

大家回复的已经比较全面了,我简单总结下:

1、通过开启慢查询日志定位慢查询语句;

2、对应慢查询语句,首先通过 explain 语句进行分析,是否用到索引,如果索引不合理,需要重新设置。

具体细节,可以参考下美团团队的这篇技术分享:https://tech.meituan.com/2014/06/30/mysql-index.html#慢查询优化

请具体解释下什么是SQL注入攻击,通常发生在哪些场景,以及如何避免SQL注入攻击?

关于 SQL 注入的定义,SQL 注入是最常见的数据库攻击手段,也是 PHP Web 应用开发过程中最应该规避的安全问题,所谓 SQL 注入指的是将 SQL 命令通过表单提交或者 URL 查询字符串的方式注入到后台执行的 SQL 语句中,如果我们在后台没有对 SQL 语句动态传入的参数进行校验和转义,则会被恶意用户利用,达到获取指定数据或者对服务器攻击的目的。

在 PHP 中,如果是通过 PDO 或者 mysqli 扩展访问数据库的话,可以通过预处理语句避免 SQL 注入攻击,在此之前更早的 PHP 版本中,不支持 mysqli 或 PDO,那个时候通过原生的 mysql 扩展访问数据库,该扩展不支持预处理语句,要避免 SQL 注入攻击,只能通过 mysql_escape_string 函数手动对传入 SQL 语句的参数进行转义处理,这对新手程序员来说大大提供了 SQL 注入攻击风险,好在从 PHP7 开始,已经废弃了 mysql 扩展。

关于预处理语句为什么能够避免 SQL 注入攻击,因为预处理语句会将带占位符的 SQL 模板和对应参数值分开传递给数据库,数据库在接收到参数值时会进行校验,即使参数值中带有恶意 SQL 指令也会作为对应占位符字段值的一部分,而不是作为指令执行,从而避免了 SQL 注入攻击。

请简单介绍下数据库事务的定义及其作用,有哪些特性,以及在PHP或者你所使用的框架中如何实现,数据库事务能保证并发操作的原子性问题吗?

一般地,我们看到事务,脑袋蹦出的可能很直接“要么都成功,要么都失败”。
实际上,数据库事务指的是满足 ACID 特性(原子性、一致性、隔离性、持久性)的一组操作,可以通过 Commit 提交一个事务,也可以使用 Rollback 进行回滚。一个数据库事务通常是包含对数据库进行读或写的一个操作序列,目的在于保证操作序列的完整性。
在并发环境下,数据库上有多个事务同时执行的时候,就可能出现脏读(dirty read)、不可重复读 (non-repeatable read)、幻读(phantom read)的问题,为了解决这些问题,于是就有了“隔离级别”的概念。
Laravel 使用事务的基本实现原理应该是通过 savepoint。
面试考察数据库事务其实也是比较常见的,与此相关的锁也是热门。

什么是数据库主从分离?为什么要做主从分离?主从分离要怎么设计,主从之间的数据同步是怎么做的?针对主从分离网络延迟导致的数据不一致,你会怎么处理?

  1. 读写分离的定义:

读写分离通常指的是让主库进行事务性查询(插入、更新、删除)操作,而让从库进行 SELECT 查询操作,通俗来说,就是主库写、从库读。

  1. 读写分离的意义:

    1. 对大部分 web 应用而言,都是读多写少,我们设计一主多从,可以提高系统的并发处理能力;
    2. 增加冗余备份,一处写入(主),多处同步(从),可以提高系统的可用性;
    3. 读写分离,让主库专注于写,让读库专注于读,区分优化读库和写库,从而提升数据库性能。
  2. 读写分离的实现:

    通过读写分离的定义,我们知道,所谓读写分离,就是写入操作都在主库完成,查询操作都在从库完成。

    所以在代码层级,我们需要将查询和写入操作区分开,通过不同的数据库连接实现,对于简单的读写分离,通过配置文件完成即可,对于多个主库/从库,从配置数组中随机选取一个建立连接即可,对于更复杂的数据库集群,可以通过数据库中间件来建立连接。

    在数据库级别,我们要实现主库记录通过同步机制实时同步到从库,以便随时可以从从库查询到最新记录,关于这一块可以通过配置主库和从库通过 binlog 日志实现数据同步复制。

  3. 数据延迟

    binlog 同步机制简单方便,但是由于需要从库通过网络请求去主库拉取数据,对于高并发场景,可能会由于系统负载、网络延迟问题导致从库和主库短期内的数据不一致(毫秒级或者秒级),针对这种情况,对于简单的中小型系统,可以在写入操作完成后强制查询操作使用主库连接来解决,比如 Laravel 的数据库 sticky 配置项底层实现就是这么干的,此外我们还可以借助缓存层来解决这种不一致性,然后主从同步之后触发缓存更新。

    需要注意的是,在分布式系统中,绝对的数据一致性是无法保证的,我们必须在 CAP 中找到一个平衡。

对于大型应用系统,数据库 SQL 优化、读写分离、添加缓存之后,往往还是满足不了系统的并发处理要求,这个时候往往就要进行垂直拆分和水平拆分,以进一步提高数据库负载能力,说说你对数据库水平拆分和垂直拆分的理解,以及面对这样的数据库系统改造,从代码层面和数据库层面我们要注意哪些问题,如果需要你去做这样的系统改造,你会怎么设计?

  1. 数据库的垂直拆分和水平拆分

    所谓垂直拆分指的是按照业务类型对原来存放在一个数据库中的表进行分类,然后 按照分类将数据库拆分成多个子库,比如用户库、商品库、交易库等。

    随着业务增长,垂直分库还是会遇到单体瓶颈,比如库存表,每次交易、下单、秒杀都会涉及到频繁修改库存表,当业务到达一定级别,可能导致库存表单体性能瓶颈,这个时候,我们就需要对其进行水平拆分。

    相对于垂直拆分把不同表拆分到不同数据库,水平拆分指的是把同一张表进行拆分,这个「水平」可以理解为按照表的数据行进行切分,把一张表拆分成多个表,如果是水平分表,则拆分后的表可以存放到同一个库,如果是水平分库分表,还要把拆分后的表放到不同数据库。

  2. 垂直拆分和水平拆分需要注意的问题

    垂直拆分和水平拆分都是为了提高数据库负载和并发处理能力,但是引入它们也增加了系统的复杂度,垂直拆分还要好一些,尤其是水平拆分,如何拆分,拆分后如何查询,都是比较棘手的问题。

    它们引入的主要共同问题包括:

    1. 分布式事务一致性如何解决
    2. 跨库 join 查询如何处理
    3. 分库分表后数据的扩展和维护难度增加

    对于水平拆分,还有一些更加棘手的问题:

    1. 主键 ID 唯一性在分布式数据表中如何保证
    2. 数据拆分到不同表中聚合查询如何做
    3. 垂直拆分遇到瓶颈还可以考虑水平拆分,水平拆分再度遇到瓶颈如何对数据进行扩容?

    这些问题都是我们需要在拆分前考虑好对应解决方案的。这些问题处理起来都不简单,所以数据库拆分一定要慎重,而不是一拍脑门下决定,它应该放到 SQL 语句优化、表结构优化(索引、分表)、引入缓存系统、读写分离之后,并且再细分的话,把水平拆分放到垂直拆分之后,因为它最复杂。

  3. 垂直拆分和水平拆分的实现

    万不得已,必须要做垂直拆分和水平拆分的话,我们接下来看看如何做。

    垂直拆分相对简单,需要先对系统业务进行分类,一般系统模块很好划分,比如用 户、商品、交易、售后等,然后把相关表都拆分到对应子系统中,一般来说,垂直拆分可以伴随着服务拆分(微服务、服务化)改造同步进行,这样每一个服务子系统访问对应的子库即可,这样也有利于权责划分,商品系统没有访问用户系统数据库的权限,只能通过用户服务提供的接口对数据进行访问和修改。在代码层面我们需要做好数据库事务和跨库 join 代码的改造。

    水平拆分相对复杂,需要先规划好切分维度,比如范围、时间、取模、哈希等,然后一般规划拆分后单表数据在1000万以内,比如我们对一个预计容量在1亿的表进行水平拆分,按照对主键 ID 取模的维度分表,可以通过 ID % 10 去指定表查询数据,对于唯一主键不能再通过数据库自增的 ID 来解决,可以借助 UUID 之类的解决方案,对于聚合查询和分页查询,也要做限制,比如用户只能看多少页数据,或者必须按照切分维度进行查询,一定要排除全表扫描的可能,分库的话设计到数据库事务和跨库 join 的代码也要调整,比如我之前的公司不再使用数据库事务并在代码中杜绝使用 join 查询。

    在具体实践中,可以借助数据库中间件来做分库分表,比如 Cobar 等,但是一定建立在你对分库分表底层的东西非常熟悉,否则一旦出现问题,那是灾难性的。

假设我们有一个商城订单表,用来保存用户订单信息,现在这张表数据容量达到亿级,影响到交易并发量,需要进行拆分,你会从哪些维度对它进行拆分,并列举出你选择方案的优缺点。

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值