strtotime()
函数通过名字就可以看出来是把字符串转为时间戳
的一个函数。
在实际项目开发中肯定用到的特别多,但是使用方法都比较单一,只会用它把2021/09/08 15:00:00
这样的时间格式转化为时间戳。
ps:不同的分隔符,比如 m/d/y 或 d-m-y 会影响到解析结果:若以反斜线 (/) 为分隔,将会做为美洲日期 m/d/y 来解析;而当分隔符为短横线 (-) 或点 (.) 时,则将做为欧洲日期 d-m-y 格式来解析。当年份只有两位数字,且分隔符为短横线 (-时,日期字符串将被解析为 y-m-d 格式。
1 | // 今天午夜 |
1 | // 四月一号0点的时间戳 |
1 | // 返回一周后的现在再加三天的时间戳,也就是10天后。 |
大多数php程序员调试都只使用echo、print_r、var_dump()等函数来代替断点调试,虽然对于大多数开发经验丰富的程序员来说已经足够了,但是如果我们要调试性能、定位错误的情况下,简单的断点调试已经完全没办法满足需求。
在我们平时的php开发中,一个大的项目经过长时间的积累以后你会发现性能越来越慢,而性能到底消耗在了什么地方,常常是一个令人头疼的问题,function a()调用了多少次,function b()又消耗了多少时间,我们到底怎么查找是哪个蛀虫拉慢了我们的程序运行速度呢?在这里给大家介绍一款工具xdebug,相信很多人已经听说过了,希望借助这个工具我们可以起到简单分析php程序性能瓶颈的问题。
XDebug是一个开放源代码的PHP程序调试器(即一个Debug工具),可以用来跟踪,调试和分析PHP程序的运行状况。
首先我们通过phpinfo()
来查看是否安装了xdebug
。
PS:这里我已经安装过了,windows需要去官网或者phpstudy下载和php版本对应的xdebug扩展。
1 | pecl install xdebug |
找到php的配置文件目录config.d
,新增一个ext-xdebug.ini
1
2
3
4
5
6
7[xdebug]
zend_extension="/usr/local/Cellar/php@7.4/7.4.23/pecl/20190902/xdebug.so"//xdebug安装路径,安装完成后会返回
xdebug.mode=debug
xdebug.idekey=PHPSTORM
xdebug.client_host=127.0.0.1
xdebug.client_port=9002
xdebug.remote_handler="dbgp"
重启php服务
把这个参数到你请求的url中
接下来就请享受这一切吧!!!
]]>相信有很多同学都遇到过小程序需要授权两次的问题,这个问题的原因是错误的授权流程导致的。
(1)错误的流程:引导用户点击授权按钮(getUserProfile)
=>调起授权(wx.getUserProfile)
=>获取code(wx.login)
=>请求后端、传输数据(code、iv等)
=>后端解密并登陆
。
(2)正确的流程:获取code(wx.show)
=>引导用户点击授权按钮(getUserProfile)
=>调起授权(wx.getUserProfile)
=>请求后端、传输数据(code、iv等)
=>后端解密并登陆
。
1 | getUserProfile: function (e) { |
1 | // 获取code |
正确的写法在微信开发者工具
和扫码预览
都无法调起微信授权框,一定要在真机调试
和发布体验版
中测试。
1 | /** |
2.处理信息、生成订单、返回支付信息1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163/**
* 生成订单
* @param $userId
* @param $mobile
* @param int $active
* @param string $payType
* @return array|mixed
* @throws ApiException
* @throws \Throwable
*/
public function createOrder($userId, $mobile, $active = 1, $payType = 'wechat')
{
DB::beginTransaction();
try {
if ($this->countWaitOrderByUserId($userId)) {
throw new ApiException('您有未支付的订单,请先处理完毕', 500);
}
$activity = $this->findActive($active);
if (!$activity) {
throw new ApiException('充值金额有误', 500);
}
$orderId = StringHelper::getOrderId('R');
$orderData = [
'order_id' => $orderId,
'user_id' => $userId,
'original_price' => $activity->original_price,
'price' => $activity->price,
'pay_type' => $payType,
'mobile' => $mobile,
'active' => $active
];
if (!$this->insertOrder($orderData)) {
throw new ApiException('订单创建失败', 500);
}
// 请求微信统一下单
if ($result = $this->payOrder($orderId, $userId)) {
DB::commit();
return $result;
}
throw new ApiException('统一下单失败', 500);
} catch (\Exception $exception) {
DB::rollBack();
throw new ApiException($exception->getMessage(), 500);
}
}
/**
* 微信统一下单
* @param $orderId
* @param $userId
* @return mixed
* @throws ApiException
* @throws GuzzleException
*/
public function payOrder($orderId, $userId)
{
$orderInfo = $this->findOrderByOrderId($orderId);
if (!$orderInfo || $orderInfo->user_id != $userId) {
throw new ApiException('订单不存在', 500);
}
$userInfo = (new UserService())->findUserById($userId, ['openid']);
if (!$userInfo) {
throw new ApiException('用户不存在', 500);
}
if (!$userInfo->openid) {
throw new ApiException('请先绑定微信', 500);
}
$payParam = config('wechat.config');
//统一下单参数
$payConfig = [
'appid' => $payParam['appId'],
'mchid' => $payParam['mchId'],
'description' => $orderInfo['price'] . '话费充值-话费慢充72小时内到账',
'out_trade_no' => $orderId,
'notify_url' => $payParam['notifyUrl'],
'amount' => [
'total' => $orderInfo['price'],
'currency' => 'CNY'
],
'payer' => [
'openid' => $userInfo['openid']
]
];
// 重点,微信apiv3必须在headers里设置四个参数
$client = new Client();
$httpClient = $client->post($payParam['payUrl'], [
'headers' => [
'Content-Type' => 'application/json',
'User-Agent' => 'Mozilla/4.0',
'Accept' => 'application/json',
'Authorization' => StringHelper::getAuth($payParam['payUrl'], json_encode($payConfig), 'POST')
],
'json' => $payConfig
]);
$result = $httpClient->getBody()
->getContents();
$payResult = json_decode($result, true);
if ($httpClient->getStatusCode() != 200) {
throw new ApiException('支付错误', 500);
}
if (isset($payResult['errcode'])) {
throw new ApiException($payResult['errmsg'], 500);
}
if (isset($payResult['code'])) {
throw new ApiException($payResult['code'], 500);
}
// 获取支付参数
$appId = $payParam['appId'];
$time = time();
$str = StringHelper::createNonce();
$prepayId = 'prepay_id=' . $payResult['prepay_id'];
$message = $appId . "\n" .
$time . "\n" .
$str . "\n" .
$prepayId . "\n";
return [
'appId' => $payParam['appId'],
'timeStamp' => $time,
'nonceStr' => $str,
'package' => 'prepay_id=' . $payResult['prepay_id'],
'signType' => 'RSA',
'paySign' => StringHelper::getSign($message)
];
}
// 生成字符串
public static function createNonce($length = 32)
{
$chars = "abcdefghijklmnopqrstuvwxyz0123456789";
$str = "";
for ($i = 0; $i < $length; $i++) {
$str .= substr($chars, mt_rand(0, strlen($chars) - 1), 1);
}
return $str;
}
// 生成auth
public static function getAuth($url, $body, $type = 'POST')
{
$config = Config::get('wechat.config');
$nonce = self::createNonce();
$timestamp = time();
$url_parts = parse_url($url);
$canonical_url = ($url_parts['path'] . (!empty($url_parts['query']) ? "?${url_parts['query']}" : ""));
$message = $type . "\n" .
$canonical_url . "\n" .
$timestamp . "\n" .
$nonce . "\n" .
$body . "\n";
$sign = self::getSign($message);
$schema = 'WECHATPAY2-SHA256-RSA2048';
$token = sprintf('mchid="%s",nonce_str="%s",timestamp="%d",serial_no="%s",signature="%s"',
$config['mchId'], $nonce, $timestamp, $config['serialNo'], $sign);
return $schema . ' ' . $token;
}
// 获取证书
public static function getSign($message)
{
$filePath = '../resources/cert/apiclient_key.pem';
openssl_sign($message, $raw_sign, openssl_get_privatekey(file_get_contents($filePath)), 'sha256WithRSAEncryption');
return base64_encode($raw_sign);
}
这篇文章没有使用微信的sdk,所以代码量还有有点大的,不过整体逻辑简单。
]]>1.IntelliJ IDEA 2020.2及以下版本
2.PhpStorm 2020.2及以下版本
3.WebStorm 2020.2及以下版本
(1).运行IDE(新安装的选择Evaluate
)
(2).随便打开或者创建一个项目,成功后保持不动
(3).把补丁解压后的jetbrains-agent.jar
拖入到IDE中,弹框后点击Restart
。
(4).重启后会弹出【JetbrainsAgent 配置助手】,选择Activation Code
,复制下面的安装参数,然后点击为XXX
安装就可以了!!!
1 | LFq51qqupnaiTNn39w6zATiOTxZI2JYuRJEBlzmUDv4zeeNlXhMgJZVb0q5QkLr+CIUrSuNB7ucifrGXawLB4qswPOXYG7+ItDNUR/9UkLTUWlnHLX07hnR1USOrWIjTmbytcIKEdaI6x0RskyotuItj84xxoSBP/iRBW2EHpOc |
(1).安装成功后还是跳转到激活页面,请尝试断网后再操作。
(2).断网后还是无法破解,请尝试删除hosts设置的映射后再操作。
好用的话就请评论一下再走吧!
]]>1 | JSON.parseObject(string) |
1 | JSON.toJSONString(javaClass) |
1 | // 先转字符 |
1 | // 例子 |
mac下的homebrew直接使用brew install node
安装的node版本是14.+
,这给很多程序带来了问题,如果直接安装低版本的node会造成‘npm’等无法使用。
1 | brew remove node |
卸载之后要先把之前node解除连接。1
brew unlink node
1 | brew search node |
1 | brew install node@10 |
1 | vi ~/.bash_profile |
经过此步骤还是无法在终端使用‘node -v’就增加1
2vi ~/.bash_profile
alias node='/usr/local/opt/node@10/bin/node'
无法使用npm
等情况执行1
brew link --overwrite --force node@10
近年小程序、微信发展迅速。微信、微博病毒式传播使用户从传统的APP上向即扫即用、无需安装
的小程序上转化。而微信公众号、小程序的迅速发展离不开转发、二维码分享。
但是二维码形式单一,对用户来说毫无吸引力。纯粹依靠‘吆喝、金钱、礼物吸引’方式已经变得愈来愈无价值。
对于’视觉动物’和大部分用户来说,精美的图片加上二维码的组合更具有吸引力。
1 | // 生成方法 |
1 | // 具体类 |
1 | // 获取微信二维码 |
从上面两个图很明显可以看出,第二张图下面被切掉了,而且电脑是打不开的。解决这个问题的关键在于ob_clean();ob_end_clean();
两个函数。
1 | // 清空缓冲区 |
现如今的小程序愈发重要,微信小程序开发也成为开发者必须掌握的重要的技能之一。
经过多年的发展,小程序开发更新换代非常快,在最近的开发中发现之前小程序支付的写法已经完全不适用了,害得我研究了很久,特写一篇文章记录一下。
(1)开通微信支付商户号。
(2)开通小程序。
(3)微信支付平台设置特约商户APPID,绑定小程序Appid。(这里要注意,小程序和支付商户必须是同一个主体)。
(4)小程序绑定微信支付商户号。
可以从很久之前的文章看出,现在的小程序支付多了一个二次签名的步骤,这点需要大家注意。
其他的步骤倒是没什么差别,具体可 官方文档。
1 | public function actionIndex() |
1 |
|
微信支付比较简单,只是文档写的不清晰导致开发难度增加。
]]>PHPSpreadsheet是由老外使用纯PHP编写的Excel文件处理扩展,他使用的是最新写法,相比PHPExcel(已不再维护)性能提升巨大,使用方法和函数基本相同,可以完全的替代PHPExcel。使用PhpSpreadsheet可以轻松读取和写入Excel文档,支持Excel的所有操作。
支持导入和导出.xls,.xlsx,.html,.csv,.pdf等格式文件。
提供丰富的API,提供单元格样式设置、Excel表格属性设置、图表设置等等诸多功能。使用PhpSpreadsheet完全可以生成一个外观结构都满足你的Excel表格文件。
卓越的性能,尤其在PHP7上表现优异,比PHPExcel强大很多。
1 | composer require phpoffice/phpspreadsheet |
下面咱们来做一个通用的导出Excel表格类。
1 |
|
1 |
|
负载均衡建立在现有网络结构之上,它提供了一种廉价有效透明的方法扩展网络设备和服务器的带宽、增加吞吐量、加强网络数据处理能力、提高网络的灵活性和可用性。
负载均衡(Load Balance)其意思就是分摊到多个操作单元上进行执行,例如Web服务器、FTP服务器、企业关键应用服务器和其它关键任务服务器等,从而共同完成工作任务。
有了这些问题,我相信你一定会用得到这个技术。最让人高兴的一点是:只需要你懂点nginx的配置就能完美的驾驭负载均衡的配置,至于配置之后可能出现的问题,再慢慢去研究也不迟。
从上图可以看出,我们利用负载均衡可以很好的应对并发请求过高的情况,使每一台服务器发挥最大的效用。
轮询(rr) :按时间顺序逐一分配到不同的后端服务器(默认);
权重(weight):加权轮询(wrr)。weight值越大,分配到的访问率越高;
源IP地址hash调度方法(ip_hash):将每个请求按访问IP的hash值来分配,来自同一个IP固定访问一个后端服务器;
最少连接数(least_conn):当server拥有不同的权重时为wlc,当所有后端主机;连接数相同时,则使用wrr,适用于长连接(keepalive_timeout不为0);
准备三台服务器:(1)192.168.133.128(调度器)(2)192.168.133.129(服务器1)(3)192.168.133.130(服务器2)
在调度服务器中配置:1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21upstream 192.168.133.128{
server 192.168.133.129:80 weight=1;
server 192.168.133.130:80 weight=1;
}
server {
listen 80;
server_name localhost;
#charset koi8-r;
#access_log /var/log/nginx/host.access.log main;
client_max_body_size 50m;
client_body_buffer_size 256k;
location / {
proxy_pass http://192.168.133.128;
proxy_set_header X-Forwarded-For $remote_addr;
proxy_set_header Host $host;
}
其他服务器server_name都配置成自己的IP就行。在各自的服务Html里命名,加以区分。
说起排行榜上一篇已经介绍过了用Redis实现的方法,而这篇完全是用Sql来实现,不使用任何缓存。
现有十个同学,年龄分布如下图所示,假设你是戴七
,请根据年龄获得你在这十人中排第几?同时请获得你的前一名和后一名各是哪一位同学?
这个题是我去年在一家公司被问到的面试题。根据上图分析,获取到自己的排名并不是难事,那前一名和后一名也非常简单了。
1 | // 原生写法 |
1 | // Laravel框架写法 |
这里要注意的是:如果两个人的年龄相同,那么就要比较时间。最终得到的是年龄大于自己人有多少个,+1就是自己的排名。
1 | // 通过上面的Sql得知年龄大于自己的人数是6人 |
1 | select * from `rank` where `age` < 22 or (`age` = 22 and `created_at` < '2020-01-21 09:05:34') order by age desc,created_at desc limit 1 |
这里和原始Sql不同的是:年龄是<22
,时间也是<自己的出生时间
。
还是这十名同学,假设戴七
同学就是本人,现需要五名同学的年龄作为参考,要求这分别得知你本人的前两名同学和后两名同学都是谁(五名同学中必须包含你自己)?
(1)如果你是第一名,那么就获得你后面的四位同学。
(2)如果你是第二名,那么就获得你前面的一名+后面的三名同学。
(3)如果你是倒数第一名,那么就获得你前面的四位同学。
(4)如果你是倒数第二名,那么就获得你前面的三名+后面的一位同学。
(5)可使用其他语言辅助解决问题,但尽可能的简单清晰。
1 | $myRank = $rank + 1; |
说起排行榜大家肯定不陌生,在项目开发中排行榜的运用非常的多。如王者农药的天梯排行榜、商城的销量排行榜、热卖榜、用户积分排行榜等等。不管他是什么类型的排行榜,其业务逻辑、实现方法几乎是完全相同的。
如这样的一个手机和笔记本的销量排行榜是如何实现的呢?
假设做一个如上图一样的排行榜,规定榜单必须是实时更新的,你会使用什么技术来实现呢?
在数据库新建一张数据表,通过下单事件钩子来更新这张表的数据,记录销量情况。1
select id,name,image from goods left join goods_top on goods.id=goods_top.goods_id where sales > 0 order by sales desc limit 10;
最常见、最简单的写法莫过于此,但这个写法有一个非常严重的问题。假设goods
中数据规模极大,记录表goods_top
表中数据规模也不小,在每次查询的性能消耗无疑是很大的,在极端的情况下甚至可能会出现请求超时的情况。
这个方案在实际的项目运用中也很少出现,除非你能确定数据表规模一定会非常的小,或者你根本不考虑什么性能的情况下才会使用。
使用redis
的有序集合(zset)
来实现实时排行榜。关于redis有序集合的相关介绍、原理什么的大家自行百度
(1)Redis
有序集合和集合一样也是string
类型元素的集合,且不允许重复的成员。
(2)不同的是每个元素都会关联一个double类型的分数。redis正是通过分数来为集合中的成员进行从小到大的排序。
(3)有序集合的成员是唯一的,但分数(score)却可以重复。
(4)集合是通过哈希表实现的,所以添加,删除,查找的复杂度都是O(1)。 集合中最大的成员数为 232 - 1 (4294967295, 每个集合可存储40多亿个成员)。
也就是说,我们可以通过下单事件钩子创建一个zset集合
,我们通过value
来区分是哪个商品,通过score
来排名。
1 |
|
1 | Rank::add(1008,10); // 商品ID为1008的销量增加10 |
实现排行榜的方式有很多,大家根据具体需求和现有技术来确定实现方案。不要过分的追求性能或者是开发速度,欲速则不达。
]]>php7.4在2019/11/28日发布!
其中RFC: Preloading(预加载)最引人瞩目。这一新特性的诞生标志着php作为解释型语言在性能瓶颈上再次突破。
根据各大网友的测试统计,php7.4与php7.0相比提升将近10%。
每次请求php都会从0到1重复上图所示的操作,每次带来的内存、IO方面的消耗无疑是很大的。
在预加载的特性下,php会当预加载完成后的请求直接会命中缓存,省下了编译、语法解析、语法分析、类库引入等等操作。
这里你会发现怎么和swoole的常驻内存有点像?
不过说实话,在没有真正使用的情况下还真没法判断两者的区别。
关于更详细的预加载使用大家可以看这里:php7.4 preload(预加载)
欢迎大家来讨论有关于预加载和常驻内存的区别到底是什么?
]]>和数据直传到 OSS 相比,以上方法有三个缺点:
使用Oss web直传,能有效的解决上传速度过慢的问题。
1 | /** |
1 | <template> |
1 | .avatar-uploader .el-upload { |
1 | <template> |
在callbackurl为空或者callbackurl文件内容错误的情况下,这里是没有返回值的。
关于消息队列大家肯定都不陌生,基本上每个项目中都会使用到的技术,在我前几期的博文中也有介绍。
消息队列本质就是逻辑执行顺序,队列就如同一摞碗堆放在一起,你每放一个碗都会放在这螺碗的最上面,当你拿碗的时候也是只能从最上面拿。这个如同队列的消费特性(队列的特性是先进先出,先进后出,道理是一样的)。
1 | # 假设你有一个这样的接口 |
假设注册接口的并发在1秒100,在使用队列的情况下发送邮件这个操作是异步的,也就是说当第100名用户点击获取验证码后立刻就能获得响应,无需等待前99名所有邮件发送成功后才能得到相应,这就是不阻塞。
当SendReminderEmail::dispatch($mail)
执行之后立刻就会执行 return '验证码发送成功,请注意查收!'
,控制器不需要知道邮件是否发送成功,减少了阻塞的等待时间。
Laravel中的消息队列就非常简单了,他可以设置驱动和执行方式。
1 | php artisan make:job SendReminderEmail |
1 |
|
1 | use SendReminderEmail; |
(1)当我未开启消息队列服务的情况下,直接请求接口,会返回1。
(2)开启消息队列
一般情况下大部分业务都使用的是redis的消息队列。加入你们公司业务量非常大,几千万甚至几亿就必须考虑使用其他的MQ工具。如ActiveMQ
、RabbitMQ
、RocketMQ
、Kafka
。
Laravel的消息队列应用比较简单且非常的好用,建议大家可以尝试一下。具体可以看官方文档。
]]>上一篇介绍了主从复制的搭建的基础流程,为这篇的配置做了铺垫。便于理解是这篇最大的亮点,说实话经过亲手尝试了主从复制之后,感觉这个东西确实不难,就简单的改几个配置就可以搞定,但是作为高级程序优化的一部分,学习还是很有必要的。
1.服务器1号:192.168.22.130(主库服务器)
2.服务器2号:192.168.22.128(从库服务器)
1 | vi /etc/my.cnf |
1 | # 登陆mysql |
查看主库状态show master status
这里的File
就是主库的binlog
文件。
1 | vi /etc/my.cnf |
再主库创建一个数据表,然后刷新从库看看是否同步成功。
总体流程下来很简单、流程也不复杂、出现奇怪问题的几率小。
如果在配置中遇到问题,可在评论区提问,我会第一时间回复。
预告:下一篇是读写分离的配置
本文介绍的是在虚拟机上搭建,实际情况其实和真正的线上环境一毛一样,使用的系统是Centos 7。
答:简单点理解,主从复制其实就是两个数据库数据的项目备份过程。其原理就是主库产生的操作都会生成binlog
传入从库,从库通过I/O
线程把binlog
写入relaylog
,然后从库创建I/O
线程执行relaylog
进行数据备份。
答:一方面是为了备份数据,防止数据丢失和破坏,另一方面是为读写分离做铺垫。
首先在本地安装虚拟机,我个人比较推荐 VMware Workstation Pro
,当然也可以是使用其他的。
虚拟机的安装就不做介绍了,无非都是下一步下一步。安装好之后在需要下载对用的系统镜像,因为本文主要是在Centos
上做演示,所以我就下载了Centos
的镜像,但是无法分享给大家,因为太大了,足足4G
。
安装好之后新建两个虚拟机(怎么新建虚拟机就不做介绍了):
注意:尽量两个虚拟机上的mysql版本保持一直,避免出现不可预知的错误。
我安装的版本是5.7.1
2
3
4
5
6
7
8
9
10
11
12
13
14 进入
cd /usr/local/src
下载rpm包
wget https://dev.mysql.com/get/mysql57-community-release-el7-11.noarch.rpm
安装rpm
rpm -ivh mysql57-community-release-el7-11.noarch.rpm
安装 mysql-server
yum install -y mysql-server
启动
systemctl start mysqld
设置开机自启
systemctl enable mysqld
登陆mysql
mysql -uroot -p
因为5.7之后的版本都是禁止匿名登陆的,你可以通过查看密码并登陆,然后修改密码,或者直接修改my.cnf
来禁止登陆验证。1
cat /var/log/mysqld.log|grep 'A temporary password'
因为系统给我的默认密码含有一些特殊符号,我没法输入,我只能使用第二种方法:1
2
3
4
5
6
7
8
9
10vi \etc\my.cnf
在最后增加一行
skip-grant-tables=1
登陆mysql,刷新权限
flush privileges;
修改密码
use mysql;
update user set password='password' where user='root';
修改成功后再次刷新权限
最后重启一下mysql服务
1 | 查看IP |
1 | telnet 192.168.22.130 3360 |
1 | 关闭防火墙 |
再次使用telnet 192.168.22.130 3306
还是报错上边图片所示时。
1 | 创建一个用户 |
出现这个说明已经成功了
到这里基础的安装已经完成了,如果遇到问题可在评论中提问,下一篇介绍主从复制如何配置。
]]>最近接手一个新项目,整个项目完全看不到left join
这样的写法,我打印了模型关联查询后的sql
后发现跟常有的查询写法完全不同。
原谅我一直是个码农,几乎没见过这样的写法,一直想对比一下这样的写法和常用的写法性能方面到底有什么差别。
一个小栗子
模型关联生成的sql1
select * from `comment` where exists (select * from `post_comment` where `comment`.`id` = `post_comment`.`comment_id` and `post_id` = ?)
常用查询1
SELECT `comment`.id,`comment`.info,`comment`.user_id from `comment` left join post_comment on `comment`.id = `post_comment`.comment_id where post_comment.post_id = ?
单从sql
上看区别还是挺大的,再做码农的期间,最常听到的就是链表查询性能不好。但是还真不能确定这两个sql
到底哪个性能好一点,没办法只能测试一下。
id | title | time |
---|---|---|
1 | 模型关联查询 | 2019-11-08 |
2 | left join 查询 | 2019-11-08 |
id | info | time |
---|---|---|
1 | 模型关联查询的性能好 | 2019-11-08 |
2 | left join 查询的性能好 | 2019-11-08 |
3 | 两个都挺好 | 2019-11-08 |
id | post_id | comment_id |
---|---|---|
1 | 1 | 1 |
2 | 2 | 2 |
3 | 3 | 2 |
为了避免争议,我添加了一些比较合理的数据。
posts | comment | post_comment |
---|---|---|
29999条 | 29999条 | 29999条 |
1 | EXPLAIN select * from `comment` where exists (select * from `post_comment` where `comment`.`id` = `post_comment`.`comment_id` and `post_id` = 2) |
select_type | table | partitions | type |
---|---|---|---|
PRIMARY | comment | NULL | ALL |
DEPENDENT SUBQUERY | post_comment | NULL | ref |
possible_keys | key | key_len | ref |
---|---|---|---|
NULL | NULL | NULL | NULL |
post_id,comment_id | post_id | 5 | const |
rows | filtered | Extra |
---|---|---|
29999 | 100.00 | Using where |
1 | 10.00 | Using where |
1 | EXPLAIN SELECT `comment`.id,`comment`.info,`comment`.user_id from `comment` left join post_comment on `comment`.id = `post_comment`.comment_id where post_comment.post_id = 2 |
select_type | table | partitions |
---|---|---|
SIMPLE | post_comment | NULL |
SIMPLE | comment | NULL |
type | possible_keys | key | key_len |
---|---|---|---|
ref | post_id,comment_id | post_id | 5 |
eq_ref | PRIMARY | PRIMARY | 4 |
ref | rows | filtered | Extra |
---|---|---|---|
const | 1 | 100.00 | Using where |
test.post_comment.comment_id | 1 | 100.00 | NULL |
从上边的EXPLAIN
结果来看,left join
比模型关联的写法效率高太多了,之后我在laravel
社区也发布了相关的提问,得到的结论是数据量大的时候用JOIN,数据量小的时候用模型
。
从得的的结论来看,模型并适合大数据查询,但是对一些新增、修改、删除操作提供了很好的效果!
]]>什么是一对多?
在程序角度讲:一对多基本上都是用来描述两个数据表之间的关系。
假设现有两个数据表:(1)文章表posts (2)评论表comment。一个文章可以有多个评论,但一个评论只能属于一个文章,相对于评论来说它与文章的关系是多对一,相对与文章来说它与评论的关系是一对多,这也就是laravel里反向关联的基础。
文章表posts
id | title | time |
---|---|---|
1 | 什么是一对一? | 2019-11-07 |
2 | 什么是一对多? | 2019-11-08 |
3 | 什么是多对多? | 2019-11-06 |
id | info | user_id |
---|---|---|
1 | 我知道什么是一对一。 | 10086 |
2 | 我知道什么是一对多。 | 10085 |
3 | 我知道什么是多对多。 | 10084 | 4 | 这谁不知道啊! | 10084 |
中间表posts_comment(也可以叫做关系表)
id | post_id | comment_id |
---|---|---|
1 | 1 | 1 |
2 | 2 | 2 |
3 | 3 | 3 |
4 | 3 | 4 |
从表的结构可以看出来,ID=1、2的文章只有一条评论,ID=3的文章有两条评论。
那么这是要有个业务,已知文章的ID=3,请使用laravel的模型关联找出该文章下的所有评论。
首先确认两个表之间的关系,一个评论在中间表只有一条数据,中间表里的comment_id肯定是唯一的,不可能出现一个评论同时属于两个文章,所以它们之间的关系是一对一。但是,你会发现使用hasMany(多对多)关联方法获取的数据居然是一样的。
1 | # 一对多写法 |
1 | public function list() |
1 | # with('comments); |
1 | # 两次sql是一样的 |
我们分析一下laravel底层的处理逻辑,从上边的sql可以看出使用模型关联第一步会使用一个嵌套查询找出comment在post_comment里存在的结果集,最终的得到的结果是3、4。随后会做一个IN查询得到最终comment.id=3、4的数据最后合并成结果集返回。
1 | public function list() |
1 | # 两次执行sql也是一样的 |
1 | # 得到的结果也是一样的 |
两种写法的端异想必大家都能看得出来,就不再介绍了。
那么问题来了,为什么要使用模型关联呢?
假设某一个文章被评论了,在不使用模型关联的情况下需要这样写:
1 | $mode = new \App\Models\Comment(); |
1 | $mode = new \App\Models\Comment(); |
而且我的公司有规定不能使用left join写法,而且模型中不允许写有关业务的代码,这样大大限制了随心所欲开发,降低了项目的维护成本。并且在使用模型关联后,一切的代码变得简单了,可读性提高,倒是性能这块我没有真正的测试过,之后肯定会研究一下。
]]>