PHP面试基础

1. mysql 事务的四个特征(ACID)

原子性:事务是数据库的逻辑工作单位,事务中包含的各操作要么都做,要么都不做

一致性:事务执行的结果必须是使数据库从一个一致性状态变到另一个一致性状态.因此当数据库只包含成功事务提交的结果时,

就说数据库处于一致性状态.如果数据库系统运行中发生故障,有些事务尚未完成就被迫中断,这些未完成事务对数据库所做的修改有一部分已写入物理数据库,

这时数据库就处于一种不正确的状态,或者说是不一致的状态

隔离线:一个事务的执行不能其它事务干扰,即一个事务内部的操作及使用的数据对其它并发事务是隔离的,并发执行的各个事务之间不能互相干扰

持续性:指一个事务一旦提交,它对数据库中的数据的改变就应该是永久性的,接下来的其它操作或故障不应该对其执行结果有任何影响


2. Mysql的四种隔离级别

Read Uncommitted (读取未提交内容)

        在该隔离级别,所有事务都可以看到其它未提交事务的执行结果,本隔离级别很少用于实际应用,因为它的性能也不比其它级别好多少.读取未提交的数据,也称为脏读(Dirty Read)

Read Committed (读取提交内容)

        这是大多数数据库系统的默认隔离级别(但不是Mysql默认的),它满足了隔离的简单定义:一个事务只能看见已经提交事务所做的改变,这种隔离级别也支持所谓的不可重复读(Nonrepeatable Read),因为同一事务的其它实例在该实例处理其间可能会有新的commit,所以同一select可能返回不同结果

Repeatable Read (可重读)

        Mysql默认事务隔离级别,它确保同一事务的多个实例在并发读取数据时,会看到同样的数据行,不过理论上,这会导致另一个棘手的问题: 幻读 (Phantom Read) .简单的说,幻读指当用户读取某一范围的数据行时,另一个事务又在该范围内插入了新行,当用户在读取该范围的数据行时,会发现新的 幻影 行,InnoDB和Falcon存储引擎通过多版本并发控制(MVCC)机制解决了该问题

Serializable (可串行化)

        这是最高的隔离级别,它通过强制事务排序,使之不可能互相冲突,从而解决幻读问题,简言之,它是在每个读的数据行上加上共享锁,在这个级别,可能导致大量的超时现象和锁竞争


3. for循环跟 foreach 的原理

a. for循环的时候是按照数字递增的,因此for只能访问键为数字的数组,列如循环 按照$i=0到$i=4去递增访问$arr中键为0到4的单元

但该数组中的键依次为:1,3,5,4,8. 而键值超过4的数组单元(5=>'b',8=>'d')不会被访问到,因为count($arr1)=5,故$i<5; 

因此最后输出结果只有: a 22 c;对于$arr2中所有的键都是字符,并非数字,所以循环2中没有输出.

$arr1=array(1=>'a', 3=>22, 5=>'b', 4=>'c', 8=>'d');
$count1=count($arr1)
for($i=0,$i<$count1$i++){
    echo $arr1[$i].' ';  // a 22 c
}

b. foreach循环结构是按照数组内部的指针去循环的,当foreach开始执行时,数组内部的指针会自动指向第一个单元.

因此下一次循环中将会得到下一个单元,不需要按照数组的键来遍历整个数组,这也是foreach与for的不同之处.当然foreach只能用于数组和对象,并且由于foreach依赖内部数组指针,再循环中修改其值将可能导致意外的行为.

$arr2=array('a'=>'aaa', 'b'=>'bbb', 'c'=>'ccc', 'd'=>'ddd', 'e'=>'eee');
foreach($arr2 as $key=>$value){
    echo $key.'=>'.$value.' ';
}

注: for每次循环都操作对应索引下的值,对于每个值的改变也都会反映到被遍历的对象中,而foreach每次操作一个单元,都是将其索引和值分别取到变量中,

或者只取出值到一个变量中,然后单独操作放有索引和值的变量,不会影响到被遍历的对象本身.如果要在遍历过程中修改对象中的值,需要在声明是在变量前加&符号. 例如:foreach($array as &$value).

c. foreach原理: 实现foreach的核心就是如下3个函数

  • zend_do_foreach_begin

  • zend_do_foreach_cont

  • zend_do_foreach_end

其中,zend_do_foreach_begin (代码太长,直接写伪码) 主要做了:

记录当前的opline行数(为以后跳转而记录)

对数组进行RESET(讲内部指针指向第一个元素)

获取临时变量 ($val)

设置获取变量的OPCODE FE_FETCH,结果存第3步的临时变量

记录获取变量的OPCODES的行数

而对于 zend_do_foreach_cont来说:

根据foreach_variable的u.EA.type来判断是否引用

根据是否引用来调整zend_do_foreach_begin中生成的FE_FETCH方式

根据zend_do_foreach_begin中记录的取变量的OPCODES的行数,来初始化循环(主要处理在循环内部的循环:do_begin_loop)

最后zend_do_foreach_end:

根据zend_do_foreach_begin中记录的行数信息,设置ZEND_JMP OPCODES

根据当前行数,设置循环体下一条opline, 用以跳出循环

结束循环(处理循环内循环:do_end_loop)

清理临时变量

当然, 在zend_do_foreach_cont 和 zend_do_foreach_end之间 会在语法分析阶段被填充foreach_satement的语句代码。


4. PHP的垃圾回收机制 

原理 : php5和ph p7的垃圾回收机制都是利用引用计数


变量在zval的变量容器中结构: 类型,值,is_ref,refcount

zval中,除了存储变量的类型和值之外,还有is_ref字段和refcount字段

  • is_ref:是个bool值,用来区分变量是否属于引用集合。

  • refcount:计数器,表示指向这个zval变量容器的变量个数。 

注意: php5.3中将一个变量 = 赋值给另一个变量时,不会立即为新变量分配内存空间,而是在原变量的zval中给refcount加1。 只有当原变量或者发生改变时,才会为新变量分配内存空间,同时原变量的refcount减 1 。当然,如果unset原变量,新变量直接就使用原变量的zval而不是重新分配。&引用赋值时,原变量的is_ref  加1.  如果给一个变量&赋值,之前 = 赋值的变量会分配空间。

<?php
$a = 1;
xdebug_debug_zval('a');
echo PHP_EOL;
$b = $a;
xdebug_debug_zval('a');
echo PHP_EOL;
$c = &$a;
xdebug_debug_zval('a');
echo PHP_EOL;
xdebug_debug_zval('b');
echo PHP_EOL;
# 结果如下:
a:(refcount=1, is_ref=0),int 1
a:(refcount=2, is_ref=0),int 1
a:(refcount=2, is_ref=1),int 1
b:(refcount=1, is_ref=0),int 1

标量在zval容器例子

<?php
$a = 1;
xdebug_debug_zval('a');
echo PHP_EOL;
$b = $a;
xdebug_debug_zval('a');
# 结果如下:可以看到标量(布尔,字符串,整形,浮点型)不再计数了
a:(refcount=0, is_ref=0),int 1
a:(refcount=0, is_ref=0),int 1

复合类型数组和对象在zval容器例子

<?php
$a = array( 'meaning' => 'life', 'number' => 42 );
xdebug_debug_zval( 'a' );
echo PHP_EOL;
class Test{
    public $a = 1;
    public $b = 2;
    function handle(){
        echo 'hehe';
    }
}
$test = new Test();
xdebug_debug_zval('test');
# 结果如下:可以看出,数组用了比数组长度多1个zval存储。数组分配了三个zval容器:a   meaning  number
a:(refcount=1, is_ref=0),
array
  'meaning' => (refcount=1, is_ref=0),
string
'life' (length=4)
  'number' => (refcount=1, is_ref=0),
int 42
test:(refcount=1, is_ref=0),
object(Test)[1]
  public 'a' => (refcount=2, is_ref=0),
int 1
  public 'b' => (refcount=2, is_ref=0),

复合类型数组和对象在zval容器例子

<?php
$a = array( 'meaning' => 'life', 'number' => 42 );
xdebug_debug_zval( 'a' );
echo PHP_EOL;
class Test{
    public $a = 1;
    public $b = 2;
    function handle(){
        echo 'hehe';
    }
}
$test = new Test();
xdebug_debug_zval('test');
# 结果如下:可以明显的看到数组a的refcount=2,后经测试发现数组的refcount都是从2开始的
a: (refcount=2, is_ref=0)
array(size=2)
  'meaning'=>(refcount=1, is_ref=0) string 'life' (length=4)
  'number'=>(refcount=0,is_ref=0) int 42
test: (refcount=1, is_ref=0)
object(Test)[1]
  public 'a'=>(refcount=0, is_ref=0) int 1
  public 'b'=>(refcount=0, is_ref=0) int 2

说明: 在5.2及更早版本的PHP中,没有专门的垃圾回收器GC(Garbage Collection),引擎在判断一个变量空间是否能够被释放的时候是依据这个变量的zval的refcount的值,如果refcount为0,那么变量的空间可以被释放,否则就不释放,这是一种非常简单的GC实现.现在unset ($a),那么array的refcount减1变为1.现在无任何变量指向这个zval, 而且这个zval的计数器为1,不会回收.

结果:尽管不再有某个作用域中的任何符号指向这个结构(就是变量容器),由于子元素"1"仍然指向数组本身,所以这个容器不能被清除.因为没有另外的符号指向它,用户没有办法清除这个结构,结果就会导致内存泄漏.


在php5.3的GC中,针对的垃圾做了如下说明:

  • 如果一个zval的refcount增加,那么此zval还在使用,肯定不是垃圾,不会进入缓冲区

  • 如果一个zval的refcount减少到0,那么zval会被立即释放掉,不属于GC要处理的垃圾对象,不会进入缓冲区.

  • 如果一个zval的refcount减少之后大于0,那么此zval还不能被释放,此zval可能成为一个垃圾,将其放入缓冲区.PHP5.3中的GC针对的就是这种zval进行的处理.


垃圾回收算法:

  • 对每个根缓冲区中的根zval按照深度优先遍历算法遍历所有能遍历到的zval,并将每个zval的refcount减1,同时为了避免对同一zval多次减1(因为可能不同的根能遍历到同一个zval),每次对某个zval减1后就对其标记为“已减”.

  • 再次对每个缓冲区中的根zval深度优先遍历,如果某个zval的refcount不为0,则对其加1,否则保持其为0.

  • 清空根缓冲区中的所有根(注意是把这些zval从缓冲区中清除而不是销毁它们),然后销毁所有refcount为0的zval,并收回其内存.


如果不能完全理解也没有关系,只需记住PHP5.3的垃圾回收算法有以下几点特性:

  • 并不是每次refcount减少时都进入回收周期,只有根缓冲区满额后在开始垃圾回收

  • 可以解决循环引用问题

  • 可以总将内存泄露保持在一个阈值以下

u=1674233793,3300999133&fm=26&gp=0.jpg

发表评论:

◎欢迎参与讨论,请在这里发表您的看法、交流您的观点。

Powered By Z-BlogPHP 1.5.2 Zero

WX:xcs345525801 QQ:345525801 Tel:19521445850 Email:xcssh868@163.com

Copyright © 2020 许承胜个人博客 版权所有 备案号:皖ICP备18014705号-1