批:PHP生成唯一订单号【徐老师对此点评:徐老师对此进行高并发检测,重复率还是非常的高,压根无法实现所谓的唯一性。】

在日常的网站开发中,我们经常需要生成唯一的订单号。订单号太短,在高迸发情况下,很容易造成订单号重复事件,虽然是小概率事件。

下面我们使用PHP多个函数生成一个现在最常用的订单号格式:

提示:以下code经过徐老师进行高并发检测,重复率还是非常的高,压根无法实现所谓的唯一性。不管用哪种方式生成订单号,建议把订单号存储到单个的表中,然后生成新的订单号前,你可能首先考虑:判断存储订单号表中是否存在,若存在则重新生成【这个时候必须要进行文件锁或者表锁定操作或者sql where dingdanhao<>值 条件计算记录三种方式,可参看我这篇文章:www.xuduowei.com/archives/500 ,因为高并发下还是有可能同时判断某一个订单不存在,但是生成的却是相同的情况的发生。】

徐老师推荐一种更简单,高效且不重复的方案!看此链接:http://www.xuduowei.com/archives/524

 

date('Ymd').substr(implode(NULL, array_map('ord', str_split(substr(uniqid(), 7, 13), 1))), 0, 8);

网上有很多地方推荐写的类似这样的订单写法,其实在高并发下,都是会重复的,这里只是分解下:

简单从内到外解析一下这个订单号生成过程:

  1. date("Ymd"):这个很容易理解,是在最前方拼接一个当前年月日组成的数字。2.uniqid():此函数获取一个带前缀、基于当前时间微秒数的唯一ID。
  2. substr(uniqid(), 7, 13):由于uniqid()函数生成的结果前面7位很久才会发生变化,所以有或者没有对于我们没有多少影响,所以我们截取后面经常发生变化的几位。
  3. str_split(substr(uniqid(), 7, 13),1):我们将刚刚生成的字符串进行分割放到数组里面,str_split()第二个参数是每个数组元素的长度。
  4. array_map('ord', str_split(substr(uniqid(), 7, 13),1))):其中array_map()函数作用为:函数返回用户自定义函数作用后的数组,意思就是ord是函数ord(),而后面第二个参数是ord()函数的参数。可以这么理解ord(str_split(substr(uniqid(),7, 13), 1)))。然后ord()是干啥的,ord()函数php内置函数:返回字符串的首个字符的 ASCII值,意思就是把第二个参数生成的数组每个元素全部转换为数字,因为刚刚我们截取的字符串中含有字母,不适合订单号。
  5. implode():很简单了,把刚刚那个转换成数字的数字在拼接成为一个数字。
  6. 由于刚刚生成的随机数可能会长短不一(原因就是,每个字符转换为ASCII值可能不一样,有些是2位,有些可能是一位),所以我们同意截取0-8
  7. 然后加上刚刚的日期数字,现在就凑成了一个等长的高大上的订单号了~

https://blog.csdn.net/leyuxinsi/article/details/47009177

=========================

订单命名的几种规则:

订单命名的几种规则:
1、不重复。
这点我相信大家都懂,订单的唯一性不用解释。
2、安全性。
你的订单编号不能透露你公司的真实运营信息,比如你的订单就是流水号的话,那么别人就可以从订单号推测出你公司的整体运营概括了。所以订单编码必须是除了你们公司少部分人外,其他人基本看不懂的。参考京东和淘宝的编码规则,基本别人是搞不清是什么意思的。
其实最好的防泄漏编码规则就是在编码中不要加入任何和公司运营的数据。
3、不能使用大规模随机码。
很多人分析订单编码规则的时候,第一个念头肯定是不重复唯一性,那么第二个念头可能就是安全性,那么同时满足前两者的第三个念头就是随机码了。因为大规模的随机码随机生成,因为本身就没有意义所以无所谓泄密了。但是事实上这种编码规则在实现上会有很大问题的。
随机码满足第二点安全性要求,为了满足第一点不重复特性,那就得在生成随机码的时候对比历史数据是否有重复,如果你的订单数量到达了十万次,你每次生成订单编码时就得对比十万条历史数据,你可想而知会造成什么巨大问题。
但是难道随机码就不能在编码中使用了吗?小规模的随机码是可以使用的,比如2~3位,这种随机码一般都是和流水号等结合使用,主要作用是为了隐藏流水号的真实数据而进行使用的。
PS:在这里感谢 @马驰@dad ni @bao xu(这个不知道为何@不到)同学的讨论,马驰同学实际测试估算了生成随机码并且检测重复所花费的时间在纳秒级别。但是我还是保持原来观点,觉得这种生成规则存在方向性问题,可能会造成检测时间过长的问题出现。
希望大家积极参与讨论。
4、防止并发。
这条规则主要针对编码中有时间的设定。
5、控制位数。
这点很好理解,订单号的作用就是便于查询。
一般正常使用场景应该是订单出异状或者退货的时候,用户将订单号报给客服,由客服进行查询。
所以一般在10~15位为好。

京东10位,淘宝15位。

推荐的几种编码规则:

年月日时分秒+用户ID(命名用户ID时也要注意,不要用流水号。可以采用区域ID+随机码+流水号+随机码方式)
1、唯一性:时间是单向的,确保唯一性。
2、安全性:确保用户ID安全即可。
3、随机码不参与判断,因为之前数据已确保无重复。
4、在同1秒钟,同一用户是不会产生2个订单编码的,所以可以防并发。
5、位数可能会在20位之内,位数比较多。
年月日时分秒微秒+随机码(2)+流水号+随机码(3)
1、唯一性:时间是单向的,确保唯一性。
2、安全性:确保流水号不会识别出即可。
3、随机码的位数和前后都是保密的,所以如果不清楚这一点的话,是很难判断出流水号的位数的。因为同时产生的订单数量很多,编码不具备线性对比功能。就算知道了流水号,可以在初始化时进行赋值。
4、在同1秒钟,同一用户是不会产生2个订单编码的,所以可以防并发。

5、位数可能会在20位之内,位数比较多。

(以上来自知乎@benben)

==============================================

订单号常见的几种方式:
1.利用数据库主键值产生一个自增长的订单号(订单号即数据表的主键)
2.日期+自增长数字的订单号(比如:2012040110235662)
3.产生随机的订单号(65865325365966)
4.字母+数字字符串式,字母有包含特别意义,C02356652

订单号设计原则: 按需设计
用来检索订单详细信息的唯一特征码,可以利用订单号检索到下单日期、产品类别、颜色、尺码(或款式)、仓位等信息,订单号包含过多的信息有点“画蛇添足”的意味!只要按需设计即可!

订单号设计用户体验规则:
1.订单号无重复性;
2.如果方便客服的话,最好是“日期+自增数”样式的订单号,客服一看便知道订单是否在退货保障期限内容;
3.订单号长度尽量保持短(10位以内),方便用户,尤其电话投诉时,长的号码报错几率高,影响客服效率;
4.订单号尽量保持数字型(纯整数),在数据库订单索引查询中,长整数字型的数据索引与检索效率,远远高于文本型,因此尽量避免“字母+数字字符串式”!

php商城高并发下订单模拟

https://blog.csdn.net/yxwb1253587469/article/details/50572927?locationNum=1

php 生成唯一订单号,文件锁

https://blog.csdn.net/wmsjlihuan/article/details/51537092

大数据量下高并发同步的讲解(不看,保证你后悔)

https://blog.csdn.net/xcw931924821/article/details/52475742

高并发环境下生成订单唯一流水号方法:SnowFlake

https://blog.csdn.net/qq_24267619/article/details/78624978

反序列化存入数据库里面的session数据

session数据存取的方法可通过session.serialize_handler方法来判断,反序列化可通过下面的unserialize方法,参考http://stackoverflow.com/questions/27813619/unserializing-php-session-data-from-db-table

public static function unserialize($session_data) {
    $method = ini_get("session.serialize_handler");
    switch ($method) {
        case "php":
            return self::unserialize_php($session_data);
            break;
        case "php_binary":
            return self::unserialize_phpbinary($session_data);
            break;
        default:
            throw new Exception("Unsupported session.serialize_handler: " . $method . ". Supported: php, php_binary");
    }
}

private static function unserialize_php($session_data) {
    $return_data = array();
    $offset = 0;
    while ($offset < strlen($session_data)) {
        if (!strstr(substr($session_data, $offset), "|")) {
            throw new Exception("invalid data, remaining: " . substr($session_data, $offset));
        }
        $pos = strpos($session_data, "|", $offset);
        $num = $pos - $offset;
        $varname = substr($session_data, $offset, $num);
        $offset += $num + 1;
        $data = unserialize(substr($session_data, $offset));
        $return_data[$varname] = $data;
        $offset += strlen(serialize($data));
    }
    return $return_data;
}

private static function unserialize_phpbinary($session_data) {
    $return_data = array();
    $offset = 0;
    while ($offset < strlen($session_data)) {
        $num = ord($session_data[$offset]);
        $offset += 1;
        $varname = substr($session_data, $offset, $num);
        $offset += $num;
        $data = unserialize(substr($session_data, $offset));
        $return_data[$varname] = $data;
        $offset += strlen(serialize($data));
    }
    return $return_data;
}

 

MySQL锁机制和PHP锁机制

模拟准备–如何模拟高并发访问一个脚本:apache安装文件的bin/ab.exe可以模拟并发量 -c 模拟多少并发量 -n 一共请求多少次 http://请求的脚本
例如:cmd: apache安装路径/bin/ab.exe -c 10 -n 10 http://web.test.com/test.php

【切入正题】
MYSQL中的锁:
语法 :
LOCK TABLE 表名1 READ|WRITE, 表名2 READ|WRITE ……………… 【锁表】
UNLOCK TABLES 【释放表】

Read:读锁|共享锁 : 所有的客户端只能读这个表不能写这个表
Write:写锁|排它锁: 所有当前锁定客户端可以操作这个表,其他客户端只能阻塞
注意:在锁表的过程中只能操作被锁定的表,如果要操作其他表,必须把所有要操作的表都锁定起来!

PHP中的文件锁 (锁的是文件,不是表)
文件锁的文件与表有什么关系?:一点关系也没有,与令牌相似,谁拿到谁操作。所以表根本没锁。
测试时,有个文件就行,叫什么名无所谓。

总结:
项目中应该只使用PHP中的文件锁,尽量避免锁表,因为如果表被锁定了,那么整个网站中所有和这个表相关的功能都被拖慢了(例如:前台很多用户一直下订单,商品表mysql锁表,其他与商品表相关的操作一直处于阻塞状态【读不出来商品表】,因为一个功能把整个网站速度拖慢)。

我的一个项目就是O2O外卖,中午12-2点,晚上6点都是订单高并发时,这种情况下,MySQL锁显然是不考虑的,用户体验太差。其实根据实际的需求,外卖可以不用设计库存量的,当然除了秒杀活动模块还是需要php文件锁的。

应用场景:
1. 高并发下单时,减库存量时要加锁
2. 高并发抢单、抢票时要使用

MySQL锁示例代码:

    <?php  
    /** 
    模拟秒杀活动-- 商品100件 
    CREATE TABLE a 
    ( 
        id int comment '模拟100件活动商品的数量' 
    ); 
    INSERT INTO a VALUES(100); 
    模仿:以10的并发量访问这个脚本!    使用apache自带的ab.exe软件 
     */  
    error_reporting(0);  
    mysql_connect('localhost','root','admin123');  
    mysql_select_db('test');  
      
    # mysql 锁  
    mysql_query('LOCK TABLE a WRITE');// 只有一个客户端可以锁定表,其他客户端阻塞在这  
    $rs = mysql_query('SELECT id FROM a');  
    $id = mysql_result($rs, 0, 0);  
    if($id > 0)  
    {  
        --$id;  
        mysql_query('UPDATE a SET id='.$id);  
    }  
      
    # mysql 解锁  
    mysql_query('UNLOCK TABLES');  

PHP文件锁示例代码:

    <?php  
    /** 
    模拟秒杀活动-- 商品100件 
    CREATE TABLE a 
    ( 
        id int comment '模拟100件活动商品的数量' 
    ); 
    INSERT INTO a VALUES(100); 
    模仿:以10的并发量访问这个脚本!    使用apache自带的ab.exe软件 
     */  
    error_reporting(0);  
    mysql_connect('localhost','root','admin123');  
    mysql_select_db('test');  
    # php中的文件锁  
    $fp = fopen('./a.lock', 'r'); // php的文件锁和表没关系,随便一个文件即可  
    flock($fp, LOCK_EX);// 排他锁  
      
      
    $rs = mysql_query('SELECT id FROM a');  
    $id = mysql_result($rs, 0, 0);  
    if($id > 0)  
    {  
        --$id;  
        mysql_query('UPDATE a SET id='.$id);  
    }  
    # php的文件锁,释放锁  
    flock($fp, LOCK_UN);  
    fclose($fp);  

其实还有一种最简单的方案,一个sql就可以解决这个事情。

@$mysql = mysql_connect('localhost','root','');
mysql_query('set names utf8');
mysql_select_db('test');
mysql_query('UPDATE warehouse SET `stock` = `stock` -1 WHERE `stock` > 0');  //可以避免库存为负数

测试的方法是,找到Apache下的ab.exe,拖入CMD终端,然后输入指定参数测试。

具体参数说明Google一下你就知道,比如耗时之类的…这里不做详细说明。

 

其他参考:

php mysql 锁表和解锁详细语句http://blog.sina.com.cn/s/blog_6e637ea701016r01.html

mysql锁(行锁,表锁)同一用户同一秒操作保持唯一性https://blog.csdn.net/webnoties/article/details/22874431

MySQL锁机制和PHP锁机制 http://phpkim.iteye.com/blog/2294464

Mysql的锁机制与PHP文件锁处理高并发简单思路https://www.cnblogs.com/yuandongdong/p/7560327.html

在不改变php.ini中 session.save_handler = files 的前提下,实现不生成文件session,后期可写入到数据库。

<?php
//原创:xuduowei,想学习php开发的可以找我。微信号:weilanweb
class session_mysql{
		//private static $db=null;
		private static $ip=null;
		private static $lifetime=null;
		private static $time=null;

		private static function init(){
			//self::$db=$db;
			self::$ip=!empty($_SERVER["REMOTE_ADDR"]) ? $_SERVER["REMOTE_ADDR"] : "unknown";
			self::$lifetime=ini_get('session.gc_maxlifetime');
			self::$time=time();
		}

		public static function start(){
			self::init();
			
			session_set_save_handler(
				array(__CLASS__, "open"),
				array(__CLASS__, "close"),
				array(__CLASS__, "read"),
				array(__CLASS__, "write"),
				array(__CLASS__, "destroy"),
				array(__CLASS__, "gc")
			);
			session_start();
		}

		public static function open($path, $name){
			//return true;
		}
	
		public static function close() {
			//return true;
		}
		
		public static function read($id){
			echo "read读取信息<br>";			
		}
		
		public static function write($id ,$data){
			echo "write写信息<br>";			
			//return true;
		}
		
		public static function destroy($id){
			echo "destroy清除信息<br>";
			
		}
		
		private static function gc($lifetime){
			echo "gc回收信息<br>";
		
		}

	
}

session_mysql::start();
echo session_name().'='.session_id()."<br>";
?>

输出结果:

read读取信息
PHPSESSID=2jo24tkjjqgu8jq1p6u2aijnd3
write写信息
=========================================
以phpstudy环境为例:默认的J:\phpStudy\tmp\tmp session存储路径下并不会有session文件的存储。php.ini配置文件中依然是默认的:session.save_handler = files
=========================================

这样做的目的就是方便后期把session写入数据库中。

以下为数据库融合版【xuduowei 原创】:

class db{
	var $conn;
	function __construct(){
		$conn=mysqli_connect("主机","用户名","密码");//为了方便,我这直接写。
		if(!$conn){
			echo "数据库连接失败";
			die;
		}
		mysqli_select_db($conn,"数据库");//为了方便,我这直接写。
		$this->conn=$conn;	
		//return $conn;
	}

	function select($sql){	
		$rst=mysqli_query($this->conn,$sql);
		$arr=array();
		while($rs=mysqli_fetch_assoc($rst)){
			$arr[]=$rs;
		}
		return $arr;
	}

	function query($sql){
		//用户数据库的删除操作	
		return mysqli_query($this->conn,$sql);		
	}
}//end db


class session_mysql{
		private static $db=null;
		private static $ip=null;
		private static $lifetime=null;
		private static $time=null;

		private static function init($db){
			self::$db=$db;
			self::$ip=!empty($_SERVER["REMOTE_ADDR"]) ? $_SERVER["REMOTE_ADDR"] : "unknown";
			self::$lifetime=ini_get('session.gc_maxlifetime');
			self::$time=time();
		}

		public static function start($db){
			self::init($db);
			
			session_set_save_handler(
				array(__CLASS__, "open"),
				array(__CLASS__, "close"),
				array(__CLASS__, "read"),
				array(__CLASS__, "write"),
				array(__CLASS__, "destroy"),
				array(__CLASS__, "gc")
			);
			session_start();
		}

		public static function open($path, $name){
			return true;
		}
	
		public static function close() {
			return true;
		}
		
		public static function read($id){
				echo "read读取信息<br>";
			
				$sql="select sessionid,updatetime,data from xdw_session where sessionid='".$id."'";	
				$result=self::$db->select($sql);
				if(!$result[0]){//若没有值,就返回空。
					return "";
				}

				if ((strtotime($result[0]["updatetime"])+self::$lifetime) < self::$time){
					//echo "时间过期";
					self::destroy($id);
					return '';
				}

				return $result[0]['data'];
		 
				
		}

	
		public static function write($id ,$data){//sessionid和session 值
			echo "write写信息<br>";	

			$sql="select sessionid,updatetime,data from xdw_session where sessionid='".$id."'";	
			
			$result=self::$db->select($sql);
			//若有值
			if($result[0]){
				//若session内容不一致,或者超出一段时间300秒了【还没有过期】。我们就重新更新一次。
				if($result[0]['data'] != $data || self::$time > (strtotime($result[0]['updatetime'])+300)){
					$sql="update xdw_session set updatetime = '".date("Y-m-d H:i:s")."', data ='".$data."' where sessionid ='".$id."'";
					self::$db->query($sql);
				}

			}else{//若没有值,就所以这个sessionid不存在。我们就重新写入一次。
				$sql="insert into xdw_session set updatetime = '".date("Y-m-d H:i:s")."', data ='".$data."' ,sessionid ='".$id."'";
				self::$db->query($sql);
			}

			return true;
		}
	

		public static function destroy($id){
			echo "destroy清除信息<br>";				
			$sql="delete from xdw_session where sessionid ='".$id."'";			
			self::$db->query($sql);

			return true;				
			
		}
		
		private static function gc($lifetime){
			echo "gc回收信息<br>";//也就是删除过期的

			$time=self::$time-$lifetime;
			$sql = "delete from xdw_session where unix_timestamp(updatetime) < ".$time;
			self::$db->query($sql);		
			return true;
		
		}

	
}

session_mysql::start(new db());
echo session_name().'='.session_id()."<br>";

$_SESSION['username']="xuduowei";
附上数据库表结构:
#有想学习php开发的,可以联系我。电话:15309695130  php著名老师:xuduowei

DROP TABLE IF EXISTS `xdw_session`;
CREATE TABLE `xdw_session` (
  `session_id` varchar(32) NOT NULL,
  `data` text NOT NULL,
  `expire` datetime NOT NULL,
  PRIMARY KEY (`session_id`)
) ENGINE=InnoDB DEFAULT CHARSET=utf8;

 

xuduowei 原创作品,请勿转载,谢谢合作。

php 控制同一个用户只能同时一个人在线

有时候项目要求:控制同一个用户只能同时一个人在线

网上有说到很多种方法。不管哪种方法,都要注意2个特殊情况:

1、关闭浏览器;2、电脑断电【计算机系统中断】;

徐多蔚,徐老师,合肥php老师 这里分享一个稳定,高效,安全的解决方案:

核心效果:类似QQ登录一样,以最后一次登录为准。

若你当前在A电脑登录中【login_session 表记录:session_id,登录名,状态=1】,然后又在B电脑登录【首先:删除login_session表中和当前用户名同名的记录,或者更新和当前用户名同名的记录登陆状态为0;然后login_session 表记录:session_id,登录名,状态=1】,当在A电脑操作的时候,会判断当前登录状态为1才可以操作。这个时候发现为0了,则提示你的账号在其他地方登陆。

徐多蔚,徐老师,合肥php老师

ajax中获得处理接口传过来的xml

$.ajax({
   type: "POST",
   url:"http://127.0.0.1/jk/xml.php",
 
   data: "",
   success: function(xml){
			obj=$(xml).find("teacher");
			alert(obj.text());//弹出内容 xuduowei
			


   }
});

xml.php 接口 code:

<?php
$xmlstring=<<<XML

 
<?xml version="1.0" encoding="UTF-8"?>
<persons>
<student>xue</student>
<teacher>xuduowei</teacher>
</persons>
 
XML;

echo $xmlstring;
?>

 

ajax中 json字符串或者json对象的处理。

若对应的json传递过来的是json对象【php中json_encode($arr) 就是json对象】。

ajax,js中,可以用 var json=JSON.parse(msg);处理转成数组。注意:JSON.parse兼容性不好,还是推荐用:eval(“(“+msg+”)”);转成对象。

 

但是若接口文件php中直接输出的是json字符串,则我们需要使用:

//我们需要将eval()方法将这个字符串包一下,就可以转成JSON对象了

var json = eval(‘(‘ + msg + ‘)’);

如果用错了,会报错的!

SyntaxError: JSON.parse: unexpected character at line 1 column 1 of the JSON

一定要区分开!

 

模拟JS代码如下:

<script src="jquery.min.js"></script>
<script>
	$.ajax({
   type: "POST",
   url:"http://127.0.0.1/jk/json.php",
  //url: "https://api.miaodiyun.com/20150822/industrySMS/sendSMS",
 //  data: "accountSid=c754405c64bd493188b56a5b781dc5ff&smsContent=【秒嘀科技】您的验证码是345678,30分钟输入有效。    &to=13896543210&timestamp=20150821100312&sig=96d3adac3d2f43b8a85b0aeb201ab2be&respDataType=JSON",
   success: function(msg){
    // alert( "Data Saved: " + msg );
			//var json=JSON.parse(msg);//这个不行,用下面的
		
			var json = eval('(' + msg + ')');
			// alert(json.respDesc);
			 var respDesc=json.respDesc;
				alert(respDesc);
			// if(respDesc=="成功"){
				//alert('接收成功');
			// }

			//alert(11);

   }
});
</script>

模拟接口文件代码:

<?php
$str=<<<xdw
{
"respCode":"00000",
"respDesc":"成功",
"failCount":"1",
"failList":
[
    {
        "phone":"13896543210",
        "respCode":"00111",
        "respDesc":"匹配到黑名单",
    }
],
"smsId":"913945fec0204b1e94baa75a5c013f59"
}
xdw;


echo $str;//这里输出的是json字符串,不是对象。


?>

 

2个自定义的PHP in_array 函数,解决大量数据判断in_array的效率问题

大家可能都用过in_array来判断一个数据是否在一个数组中,一般我们的数组可能数据都比较小,对性能没什么影响,所以也就不会太在意。

但是如果数组比较大的时候,性能就会下降,运行的就会久一点,那如果针对在大数组情况下做优化呢,下面说两种方法(都是通过自定义函数来实现):

1.数组key与value翻转,通过isset判断key是否存在于数组中

/**
 * in_array is too slow when array is large
 */
function inArray($item, $array) {
    $flipArray = array_flip($array);
    return isset($flipArray[$item]);
}

大家可能也会问为什么不用 array_key_exists 来做判断二用isset呢? 下面看下array_key_exists() 与 isset() 的对比:
isset()对于数组中为NULL的值不会返回TRUE,而array_key_exists()会。

<?php
$search_array = array('first' => null, 'second' => 4);
// returns false
isset($search_array['first']);
// returns true
array_key_exists('first', $search_array);
?>

2.用implode连接,直接用strpos判断

用implode函数+逗号连起来,直接用strpos判断。php里面字符串取位置速度非常快,尤其是在大数据量的情况下。不过需要注意的是首尾都要加”,” ,这样比较严谨。如: ,user1,user2,user3, 查找的时候,查,user1,。还有strpos要用!== false,因为第一个会返回0。示例如下:

return strpos($str,(string)$item)==false?false:true;**
 * in_array is too slow when array is large
 */
function inArray($item, $array) {
    $str = implode(',', $array);    
    return strpos($str,(string)$item)==false?false:true;
}

提示:以上2个函数均有bug!方法一数组翻转中,若原始数组值有null的,是会出问题的;方法二的只能验证字符串类型的。无法验证整形数字。总结:还是in_array($str,$arr,true) 验证方式最安全可靠。虽然性能相对底一点。

 

 

 

php自带的sort排序和用php实现排序算法的性能比较?

我今天特地试验了一下两者的性能
php自带的排序函数 100000的数据 排序 平均耗时0.068s
microtime() //php自带排序sort()耗时:0.12000608444214 
返回当前 Unix 时间戳的微秒数。
 for ($i = 0; $i<100000;$i++){

    $arr[] = rand(0,10000);
}

 $t1 = microtime(true);

sort($arr);

$t2 = microtime(true);
echo "php自带排序sort()耗时:".($t2-$t1); 

自己写的快速排序 平均耗时1.0s

//快速排序耗时:1.7631008625031

 for ($i = 0; $i<100000;$i++){

    $arr[] = rand(0,100000);
}

 $t1 = microtime(true);

$returnAr = quickSort($arr);

$t2 = microtime(true);
echo "快速排序耗时:".($t2-$t1); 
//快速排序
function quickSort($arr) {
    //先判断是否需要继续进行
    $length = count($arr);
    if($length <= 1) {
        return $arr;
    }
    //选择第一个元素作为基准
    $base_num = $arr[0];
    //遍历除了标尺外的所有元素,按照大小关系放入两个数组内
    //初始化两个数组
    $left_array = array();  //小于基准的
    $right_array = array();  //大于基准的
    for($i=1; $i<$length; $i++) {
        if($base_num > $arr[$i]) {
            //放入左边数组
            $left_array[] = $arr[$i];
        } else {
            //放入右边
            $right_array[] = $arr[$i];
        }
    }
    //再分别对左边和右边的数组进行相同的排序处理方式递归调用这个函数
    $left_array = quickSort($left_array);
    $right_array = quickSort($right_array);
    //合并
    return array_merge($left_array, array($base_num), $right_array);
}

明显是php自带的函数排序速度快很多。
但重点是,为什么还有那么多问题是问 如何用php实现快速排序等算法?