开发者博客 – IT技术 尽在开发者博客

开发者博客 – 科技是第一生产力


  • 首页

  • 归档

  • 搜索

一次StackOverflowError排查,原因竟然和Du

发表于 2019-03-21

前言

某天业务方的同事和我反馈,说系统出现了StackOverflowError.坦白说,Exception见得过了,但是Error倒是很少出现,此时他的心情是这样的

一波猛如虎的操作

我们先来看血淋淋的案发现场

从异常栈中很明显发现出现了死循环,其实StackOverflowError绝大多数都是死循环、递归引起的.那么,这为什么会出现循环调用呢?我们根据这个溢出的案发现场查看源码

如果你是用idea的话,他还会用图标提示你,这里会出现递归调用.

那么问题来了,这里究竟为什么会循环递归调用?由于这个问题,是必然重现的,对于之前查看过我源码解析的老粉丝来说简直是so easy.我们触发场景,然后把断点打在案发现场,不断把断点放过,反复几次之后,如下图

经过上面的数据,我们已经粗略看出死循环的迹象.我们把目标锁定在了currency和availableCurrencies. 为什么这里会出现反复的循环呢?我们查看一下Currency类的源码

1
2
3
4
复制代码public static Set<Currency> getAvailableCurrencies() {

//省略...
}

结合断点的序列化情况可以看出,currency进行json序列化的时候,需要去序列化availableCurrencies.然后getAvailableCurrencies()又包含currency,从而陷入了死循环.

本地重现

为了保证每个粉丝都能参与其中,肥朝抽取了一个必然重现的最简模型.希望肥朝公众号的粉丝都能一起参与进来,而不是每次看完分析过后,还有人一脸懵逼喊着666!

1
2
3
4
5
6
7
8
9
10
11
12
复制代码public class FeiChaoDTO {

private Currency currency;

public Currency getCurrency() {
return currency;
}

public void setCurrency(Currency currency) {
this.currency = currency;
}
}
1
2
3
4
5
6
7
复制代码@Test
public void test() throws Exception {
FeiChaoDTO feiChaoDTO = new FeiChaoDTO();
feiChaoDTO.setCurrency(Currency.getInstance("CNY"));
String json = JSON.json(feiChaoDTO);
System.out.println(json);
}

如何解决

很明显,出现这个问题的原因是因为该同学用了Dubbo里面的json序列化工具类,因为这个工具并非是Dubbo主流功能,所以关注度不足,自然存在一些不完善的功能,并且在Dubbo2.6.x以后版本,已经标注为过期.另外我们再看一下阿里开发手册

要解决这个问题方式很简单,既然要json序列化,那么就用主流的json序列化工具,无论是用fastjson、gson、jackson都有对这些循环的情况做处理,还是那句话,本公众号已经和各大搜索引擎长期合作,按照我的描述搜索,比如fastjson 循环引用,你想要的都有.当然细心的你可能发现,这些主流工具都不会去序列化availableCurrencies字段,Dubbo的这个序列化工具处理逻辑和主流的JSON工具处理还是有些不同,当然不用纠结,Dubbo本身定位就是RPC框架,就像肥朝公众号定位就是源码解析、真实场景源码实战,JSON解析我们可以选用上面的三大神器中的一个.

写在最后

近日有粉丝和肥朝反馈在面试过程遇到了如下的问题

我只想说,人家不问你也可以主动嘛.如果你是肥朝的粉丝,在工作、学习上遇到了什么问题,欢迎来撩,只要你主动,我们的故事就开始了!

肥朝 是一个专注于 原理、源码、开发技巧的技术公众号,号内原创专题式源码解析、真实场景源码原理实战(重点)。扫描下面二维码关注肥朝,让本该造火箭的你,不再拧螺丝!

本文转载自: 掘金

开发者博客 – 和开发相关的 这里全都有

【肥朝】面试官问我,使用Dubbo有没有遇到一些坑?我笑了。

发表于 2019-03-20

前言

17年的时候,因为一时冲动没把持住(当然最近也有粉丝叫我再冲动一把再更新一波),结合面试题写了一个系列的Dubbo源码解析.目前公众号大部分粉丝都是之前的粉丝,这里不过多介绍.

根据我的面试经验而言,能在简历上写上原理、源码等关键词的,是非常具备核心竞争力的.上周和一个公众号粉丝交流面试情况如下

面试的时候,把源码一波分析,令面试官虎躯一震!在一阵前戏过后,以为接下来无非就是身体的一顿抽搐一切变得索然无味,不料面试官来了句令剧情发生了反转

“你对Dubbo源码这么熟悉,那请问你使用的时候,有没有遇到什么坑”

我擦,毫无准备的他,菊花顿时一紧!此时就面临唬住了50K,唬不住就只能5K的局面,慌了!

论如何反杀

相信大家面试都遇到过类似问题,因为源码解析网上很多,很多人”考前突击”一下,但是遇到喜欢问细节的面试官,终究难逃法眼,无处遁形.遇到这个问题,我们如何反杀一波?那么我就从一次聊天记录说起,毕竟只有关注肥朝公众号,拥有 真实场景的源码实战(非常重要),遇到这类问题,才不至于出现猛虎落泪的情形

真实场景描述

那么我们把业务相关去掉,抽取一个最简模型.我们在公司,一般都会有自己的自定义异常,然后这个自定义异常一般放在common.jar给其他模块依赖,比如我这里定义一个HelloException

1
2
3
4
5
6
7
8
9
10
复制代码public class HelloException extends RuntimeException {

public HelloException() {
}

public HelloException(String message) {
super(message);
}

}

然后我们写一个最简单的Dubbo的demo,如下

interface

1
2
3
4
5
复制代码public interface DemoService {

String sayHello(String name);

}

provider

1
2
3
4
5
6
7
复制代码public class DemoServiceImpl implements DemoService {

public String sayHello(String name) {
throw new HelloException("公众号:肥朝");
}

}

consumer

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
复制代码public class DemoAction {

private DemoService demoService;

public void setDemoService(DemoService demoService) {
this.demoService = demoService;
}

public void start() throws Exception {
try {
String hello = demoService.sayHello("公众号:肥朝");
} catch (HelloException helloException) {
System.out.println("这里捕获helloException异常");
}
}

}

按照聊天记录的描述,此时consumer调用provider,provider抛出HelloException.但是consumer捕获到的,却不是HelloException.

那么我们运行看看

果然如该同事所言.为什么会这样呢?之前没看过肥朝Dubbo源码解析系列的同学这种时候往往采用最低效的解决办法,把异常栈往微信群一丢,各种求助.但是往往毫无收获,然后感叹社会为何如此冷漠!

但是相信公众号的老粉丝们早已掌握阅读源码的技能,和肥朝一样坐怀不乱,九浅一深直入源码.出现异常我们首先看一下异常栈

除非撸多了看不清(建议戒撸),否则这行异常和肥朝一样,就像漆黑中的萤火虫一样,那么鲜明,那么出众

1
复制代码com.alibaba.dubbo.rpc.filter.ExceptionFilter.invoke(ExceptionFilter.java:108)

那么我们一探究竟

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
复制代码    public Result invoke(Invoker<?> invoker, Invocation invocation) throws RpcException {
try {
Result result = invoker.invoke(invocation);
if (result.hasException() && GenericService.class != invoker.getInterface()) {
try {
Throwable exception = result.getException();

// 如果是checked异常,直接抛出
if (! (exception instanceof RuntimeException) && (exception instanceof Exception)) {
return result;
}
// 在方法签名上有声明,直接抛出
try {
Method method = invoker.getInterface().getMethod(invocation.getMethodName(), invocation.getParameterTypes());
Class<?>[] exceptionClassses = method.getExceptionTypes();
for (Class<?> exceptionClass : exceptionClassses) {
if (exception.getClass().equals(exceptionClass)) {
return result;
}
}
} catch (NoSuchMethodException e) {
return result;
}

// 未在方法签名上定义的异常,在服务器端打印ERROR日志
logger.error("Got unchecked and undeclared exception which called by " + RpcContext.getContext().getRemoteHost()
+ ". service: " + invoker.getInterface().getName() + ", method: " + invocation.getMethodName()
+ ", exception: " + exception.getClass().getName() + ": " + exception.getMessage(), exception);

// 异常类和接口类在同一jar包里,直接抛出
String serviceFile = ReflectUtils.getCodeBase(invoker.getInterface());
String exceptionFile = ReflectUtils.getCodeBase(exception.getClass());
if (serviceFile == null || exceptionFile == null || serviceFile.equals(exceptionFile)){
return result;
}
// 是JDK自带的异常,直接抛出
String className = exception.getClass().getName();
if (className.startsWith("java.") || className.startsWith("javax.")) {
return result;
}
// 是Dubbo本身的异常,直接抛出
if (exception instanceof RpcException) {
return result;
}

// 否则,包装成RuntimeException抛给客户端
return new RpcResult(new RuntimeException(StringUtils.toString(exception)));
} catch (Throwable e) {
logger.warn("Fail to ExceptionFilter when called by " + RpcContext.getContext().getRemoteHost()
+ ". service: " + invoker.getInterface().getName() + ", method: " + invocation.getMethodName()
+ ", exception: " + e.getClass().getName() + ": " + e.getMessage(), e);
return result;
}
}
return result;
} catch (RuntimeException e) {
logger.error("Got unchecked and undeclared exception which called by " + RpcContext.getContext().getRemoteHost()
+ ". service: " + invoker.getInterface().getName() + ", method: " + invocation.getMethodName()
+ ", exception: " + e.getClass().getName() + ": " + e.getMessage(), e);
throw e;
}
}

手机上阅读源码或许并不友好,但是没关系,上面都有完善的中文注释,他想表达的意思如下:

1.如果是checked异常,直接抛出.很明显,我们的HelloException是RuntimeException,不符合

2.在方法签名上有声明,直接抛出.很明显,我们接口并未声明该异常,不符合

3.异常类和接口类在同一jar包里,直接抛出.很明显,我们的异常类是在common.jar的,接口是在api.jar的,不符合

4.是JDK自带的异常,直接抛出.很明显,这个HelloException是我们自定义的,不符合

5.是Dubbo本身的异常(RpcException),直接抛出.很明显,这个HelloException是我们自定义的,和RpcException几乎没有半毛钱关系.

6.否则,包装成RuntimeException抛给客户端.因为以上5点均不满足,所以该异常会被包装成RuntimeException异常抛出(重要)

这也就是为什么我们catchHelloException是catch不到的,因为他包装成RuntimeException了

Dubbo为什么这么设计

也许你看到这里会觉得这个判断好坑.Dubbo为什么要这么设计?我们看源码,最重要的是知道作者为什么这么设计,只有知道为什么这么设计才是经过了深度的思考,否则看时高潮,看后就忘.讲清楚为什么这么设计,也是大家关注肥朝公众号的一个重要原因.

其实Dubbo的这个考虑,是基于序列化来考虑的.你想想,如果provider抛出一个仅在provider自定义的一个异常,那么该异常到达consumer,明显是无法序列化的.所以你注意看Dubbo的判断.我们来看下他的判断

1.checked异常和RuntimeException是不同类型,强行包装可能会出现类型转换错误,因此不包,直接抛出

2.方法签名上有声明.方法签名上有声明,如果这个异常是provider.jar中定义的,因为consumer是依赖api.jar的,而不是依赖provider.jar.那么编译都编译不过,如果能编译得过,说明consumer是能依赖到这个异常的,因此序列化不会有问题,直接抛出

3.异常类和接口类在同一jar包里.provider和consumer都依赖api,如果异常在这个api,那序列化也不会有问题,直接抛出

4.是JDK自带的异常,直接抛出.provider和consumer都依赖jdk,序列化也不会有问题,直接抛出

5.是Dubbo本身的异常(RpcException),直接抛出.provider和consumer都依赖Dubbo,序列化也不会有问题,直接抛出

6.否则,包装成RuntimeException抛给客户端.此时,就有可能出现我说的那种,这个异常是provider.jar自定义的,那么provider抛出的时候进行序列化,因为consumer没有依赖provider.jar,所以异常到达consumer时,根本无法反序列化.但是包装成了RuntimeException异常则不同,此时异常就是JDK中的类了,到哪都能序列化.

如何解决

既然都知道了原理了,那么很好解决,我随便列举一下,比如从规范上要求业务方接口声明HelloException

写在最后

当然肥朝面试的时候,也曾经被问过类似问题,你用XXX有没有遇到过什么坑.在一波操作猛如虎的分析下,面试官说

“你真帅”.

肥朝会心一笑

结果他却说

“你笑起来更帅”!


肥朝 是一个专注于 原理、源码、开发技巧的技术公众号,号内原创专题式源码解析、真实场景源码原理实战(重点)。扫描下面二维码关注肥朝,让本该造火箭的你,不再拧螺丝!

本文转载自: 掘金

开发者博客 – 和开发相关的 这里全都有

基于Smali文件 Android Studio 动态调试

发表于 2019-03-18

最近在研究动态调试技术,网上关于基于 Smali 动态调试App的教程很多,很多细节都没有贴出来,走了很多弯路,将调研结果分享一下,如果有写的不好地方,欢迎指正

Github:https://github.com/hi-dhl/DebugApkSmali

准备工作

  • 一部 Root 过的 Android 手机,打开开发者模式
  • Xposed 的运行的环境
  • 安装 smalidea 插件,点击这里下载
  • 安装 BDOpener.apk 或者 XDebug.apk

反编译 Apk(apktool)

点击 这里下载 最新的 apktool , 并配置好环境(不同环境配置的方式不同,参考 官网)

下载你需要反编译的 Apk 文件,执行

1
复制代码apktool d 反编译Apk名字.apk

如果反编译出来多个smali文件夹,可以用ant写个脚本,多个文件夹合并成一个

smalidea

AndroidStudio 如果要调试smali代码,需要安装第三方插件: smalidea

AS中【Android Studio–>Preferences–>Plugins–>Install plugin from desk…】,安装插件

Android Studio 导入 smali

  • 新疆一个 Android 工程项目(ps: 一定要新建Android项目,否则可能会找不到 “Attach debuger to Android Process”)删掉自动创建的资源和代码文件
  • 复制反编译好的 smali 文件, 到新建 Android 项目 java 文件夹下

PS: 也可以使用我配置好的项目 “ApkSampleSmali” , 需要安装提供的 Sample.apk进行测试

Android Studio 动态调试配置

新建调试配置,【Run–>Edit Configurations–> + –>Remote】,name 随意,端口 8700

动态调试

  • 在 smali 代码中打入断点,如下图标记1
  • 然后点击 “Attach debuger to Android Process” ,如下图标记2

  • 选择你要调试的进程,手动进入对应功能, 即进入断点动态调试(ps: 如果你想要的进程没有展示出来,请重新勾选 xposed module(BDOpener), 重启手机)

运行环境信息

  • Xposed Version90-beta3
  • BDOpener 1.0
  • AndoridStudio 3.3.2
  • smalidea 0.0.5
  • apktool 2.4.0

结语

致力于分享一系列 Android 系统源码、逆向分析、算法、翻译、Jetpack 源码相关的文章,如果你喜欢这篇文章欢迎 Star 一起来学习,期待与你一起成长

文章列表

Android 10 源码系列

  • 0xA01 Android 10 源码分析:APK 是如何生成的
  • 0xA02 Android 10 源码分析:APK 的安装流程
  • 0xA03 Android 10 源码分析:APK 加载流程之资源加载
  • 0xA04 Android 10 源码分析:APK 加载流程之资源加载(二)
  • 0xA05 Android 10 源码分析:Dialog 加载绘制流程以及在 Kotlin、DataBinding 中的使用
  • 0xA06 Android 10 源码分析:WindowManager 视图绑定以及体系结构

Android 应用系列

  • 如何高效获取视频截图
  • 如何在项目中封装 Kotlin + Android Databinding
  • [译][Google工程师] 刚刚发布了 Fragment 的新特性 “Fragment 间传递数据的新方式” 以及源码分析
  • [译][2.4K Start] 放弃 Dagger 拥抱 Koin
  • [译][5k+] Kotlin 的性能优化那些事
  • [译][Google工程师] 详解 FragmentFactory 如何优雅使用 Koin 以及源码分析
  • [译] 解密 RxJava 的异常处理机制

工具系列

  • 为数不多的人知道的 AndroidStudio 快捷键(一)
  • 为数不多的人知道的 AndroidStudio 快捷键(二)
  • 关于 adb 命令你所需要知道的
  • 10分钟入门 Shell 脚本编程

逆向系列

  • 基于 Smali 文件 Android Studio 动态调试 APP
  • 解决在 Android Studio 3.2 找不到 Android Device Monitor 工具

本文转载自: 掘金

开发者博客 – 和开发相关的 这里全都有

设计模式 —— 原型模式

发表于 2019-03-14

简介

了解原型模式前,我们先来了解下Java提供两种克隆方式:

  • 浅克隆:被克隆对象的所有变量都含有与原来的对象相同的值,而它所有的对其他对象的引用都仍然指向原来的对象。换一种说法就是浅克隆仅仅克隆所考虑的对象,而不克隆它所引用的对象。
  • 深克隆:被克隆对象的所有变量都含有与原来的对象相同的值,但它所有的对其他对象的引用不再是原有的,而这是指向被复制过的新对象。换言之,深复制把要复制的对象的所有引用的对象都复制了一遍,这种叫做间接复制。

原型模式(Prototype Pattern)是用于创建重复的对象,同时又能保证性能。用原型实例指定创建对象的种类,并通过拷贝这些原型创建新的对象。

实例

原型模式包含如下角色:

  1. Prototype:原型(实现 Cloneable 接口)。
  2. Concrete Prototype:具体原型,被复制的对象。此角色需要实现抽象的原型角色所要求的接口。
  3. Client:客户类
1
2
3
4
5
6
复制代码public class Prototype implements Cloneable {
@Override
protected Prototype clone() throws CloneNotSupportedException {
return (Prototype) super.clone();
}
}
1
2
3
4
5
复制代码public class ConcretePrototype extends Prototype {
public void print() {
System.out.println("原型模式实现类,hashCode:" + this.hashCode());
}
}
1
2
3
4
5
6
7
8
9
10
复制代码public class Client {
@Test
public void test() throws CloneNotSupportedException {
ConcretePrototype prototype = new ConcretePrototype();
for (int i = 0; i < 10; i++) {
ConcretePrototype clonecp = (ConcretePrototype) prototype.clone();
clonecp.print();
}
}
}

输出结果:

1
2
3
4
5
6
7
8
9
10
复制代码原型模式实现类,hashCode:574568002
原型模式实现类,hashCode:952486988
原型模式实现类,hashCode:932285561
原型模式实现类,hashCode:2028555727
原型模式实现类,hashCode:591391158
原型模式实现类,hashCode:898557489
原型模式实现类,hashCode:247944893
原型模式实现类,hashCode:1014166943
原型模式实现类,hashCode:1625082366
原型模式实现类,hashCode:572593338

类图

原型模式

原型模式主要用于对象的复制,它的核心是就是类图中的原型类Prototype。Prototype类需要具备以下两个条件:

  • 在java语言有一个Cloneable接口,它的作用只有一个,就是在运行时通知虚拟机可以安全地在实现了此接口的类上使用clone方法。在java虚拟机中,只有实现了这个接口的类才可以被拷贝,否则在运行时会抛出CloneNotSupportedException异常。
  • 重写Object类中的clone方法。Java中,所有类的父类都是Object类,Object类中有一个clone方法,作用是返回对象的一个拷贝,但是其作用域protected类型的,一般的类无法调用,因此,Prototype类需要将clone方法的作用域修改为public类型。

优点

  1. 创建对象性能好。使用原型模式创建对象比直接new一个对象在性能上要好的多,因为Object类的clone方法是一个本地方法,它直接操作内存中的二进制流,特别是复制大对象时,性能的差别非常明显。
  2. 简化对象的创建。

缺点

  1. 需要为每一个类配备一个克隆方法,而且该克隆方法位于一个类的内部,当对已有的类进行改造时违背“开闭原则”。
  2. 在实现深克隆时需要编写较为复杂的代码,而且当对象之间存在多重的嵌套引用时,为了实现深克隆,每一层对象对应的类都必须支持深克隆比较麻烦。

适用场景

需要重复地创建相似对象时可以考虑使用原型模式。比如需要在一个循环体内创建对象,假如对象创建过程比较复杂或者循环次数很多的话,使用原型模式不但可以简化创建过程,而且可以使系统的整体性能提高很多。

总结

原型模式是一种比较简单的模式,也非常容易理解,实现一个接口,重写一个方法即完成了原型模式。在实际应用中,原型模式很少单独出现。而且需要注意因为对象的复制是通过调用Object类的clone方法来完成的,它直接在内存中复制数据,因此不会调用到类的构造方法。所以,单例模式与原型模式是冲突的,在使用时要特别注意。

深拷贝与浅拷贝问题中,会发生深拷贝的有java中的8中基本类型以及他们的封装类型,另外还有String类型。其余的都是浅拷贝。

本文转载自: 掘金

开发者博客 – 和开发相关的 这里全都有

Node开发命令行工具的经验总结

发表于 2019-03-12

背景

近半年接手了一个非web开发类的工作,一直跟数据、数据库和脚本打交道,原项目是windows服bat脚本和.NET命令行程序来跑各种任务的。之前我没有太接触过shell这块,碰到这些bat脚本确实有点把我难到了,各方面相比bash来说还是有很大差距,于是我着手开始做迁移工作

这里推荐一个简单的bash入门教程

bash-handbook

而复杂逻辑的exe部分,还是用我熟悉的node.js来重构,在此之前我还没有过命令行的开发经验,算是摸着石头过河

参考资料

Node.js 命令行程序开发教程

node.js 命令行工具(cli)

开发

链接为全局指令

大多数文档都会提到的一点,在package.json里的bin属性下写好指令名与代码路径后,执行npm link即可全局使用这个指令

注意当前目录

记得代码可能会在任意目录上执行,因此用相对定位来读自己的代码目录内的文件是不可取的,如let s = fs.readFileSync('./a.json')

而应该改成使用__dirname来获取代码目录的绝对路径再去读文件,不过require函数不需要考虑这些,它会自己处理

获取和解析参数

我参考了npm上较为主流的几款命令行模块,看到大多是与具体函数耦合较紧的,需要用代码来配置命令行解析参数,这样的好处是能实现很多高级功能,如默认值、参数验证、自动生成帮助文档等。

而我希望的是每次传不同的子命令时再去动态require代码,并且希望自定义帮助文档,因此只需要一套比较简单的解析库,拿到参数再自己处理,因此暂定使用了yargs

输出markdown文档到控制台

1
2
3
4
5
复制代码const marked = require('marked');
const TerminalRenderer = require('marked-terminal');

marked.setOptions({ renderer: new TerminalRenderer({ showSectionPrefix: false }) });
process.stdout.write(marked(fs.readFileSync(path.join(__dirname, './readme.md')).toString()));

marked-terminal模块将markdown文本转换为带有颜色信息的ANSI格式,然后输出到控制台,赏心悦目

marked-terminal输出截图

从输入流读入数据

想实现一个标准的UNIX类控制台程序,支持管道是必不可少的,得益于node的封装,我们有很简单的办法来获取输入流的数据

  • 从process.stdin流式读取数据,这个时候我之前写的Node对流的Promise包装和并发控制模块就可以派上用场了
  • 直接用fs.readFileSync(0)读取全部内容,0就是标准输入流的文件句柄,当然前提是数据量不大或者需要完整解析的情况(如json),否则我们还是流式处理比较好

调试信息定向到stderr

想保持标准输出,又想在控制台看到调试日志的输出,我们可以将调试的数据都发到stderr去,最终的运行结果才输出到stdout

在常用的日志模块log4js中,只要如此配置就好,这样所有日志输出都不会影响到标准输出流

1
2
3
4
复制代码log4js.configure({
appenders: { err: { type: 'stderr' } },
categories: { default: { appenders: ['err'], level: 'all' } }
});

log4js文档

注意等待输出流写完成再关闭进程

如果有输出流或者日志文件的情况,要注意不要轻易调用process.exit来主动结束进程,要等待流写入文件完成

1
2
3
4
5
6
7
8
复制代码//等待流写入完成
await new Promise(resolve => {
ws.on('finish', resolve);
ws.end();
});

//node4js的处理进程
log4js.shutdown(cb);

最好的情况是不写process.exit,只要记得ws.end()结束写入就行,当活动的流(包括标准输出流)全部完成,文件句柄释放后,进程自己会退出

使用更大的内存

node.js使用的v8引擎默认堆内存上限约1.7GB,而在环境变量中加上NODE_OPTIONS=--max-old-space-size=40960后可以扩大可使用的内存大小,当然用大内存不一定是好事,优化代码、精简数据结构、流式处理和避免闭包才是合理做法

使用烘焙/模板函数

在用一些模板或规则批量处理数据的时候,可以考虑使用预构建函数或烘焙模板来优化效率,前端的编译框架对这个就很有研究了,有兴趣可以了解下

对CSV做SQL查询

有些复杂的业务需求需要对文件做数据统计汇总,原先是用lodash的一套函数来处理,但未免还是有些繁复和难以阅读,后来引入了alasql,支持流式地对文件进行sql查询,甚至支持连表语法,我目前也还在初步尝试阶段,有相关需求的可以了解下

总结

得益于现在基本所有前端渲染/JS预编译框架都会用Node的CLI工具来提供服务,npm上会有很多方便的模块供我们使用,在这之上使用Node来开发命令行工具还是很方便快捷的,在shell上如果有复杂的实现需求,不妨试试用Node编写代码来处理

关于控制台程序的一些理解和指导,推荐阅读《UNIX编程艺术》

本文转载自: 掘金

开发者博客 – 和开发相关的 这里全都有

再见,Python!你好,Go语言

发表于 2019-03-11

Go 语言诞生于谷歌,由计算机领域的三位宗师级大牛 Rob Pike、Ken Thompson 和 Robert Griesemer 写成。由于出身名门,Go 在诞生之初就吸引了大批开发者的关注。诞生十年以来,已经涌出了很多基于 Go 的应用。就在不多久之前,知乎也舍弃了 Python,转 用Go 重构推荐系统 。

作者:华为云技术宅基地
链接:https://zhuanlan.zhihu.com/p/57895717

谷歌前员工 Jake Wilson 认为,比起大家熟悉的 Python,Go 语言其实有很多优良特性,很多时候都可以代替 Python,他已经在很多任务中使用 Go 语言替代了 Python。那么 Go 语言到底有着什么样的独特魅力?它有哪些胜于 Python 的地方呢?让我们一起来了解一下吧!

2019 年程序员最想学习的编程语言

image

出身名门的 Go 语言

对于一门只有十年历史的新语言,Go 的发展势头相当迅猛,容器界的扛把子 Docker 就是用 Go 写的,国内也有不少团队广泛使用 Go。近日,HackerRank 在社区发起了程序员技能调查,来自 100 多个国家、超过 70000 名开发者参与其中。调查结果显示,2019 年,程序员最想学习的编程语言 Top 3 分别是 Go、Kotlin 和 Python,其中 Go 以 37.2% 的比例排在首位。

image

但 Go 要想撼动编程界的常青树 Java 二十多年的地位无疑难度颇大。据 HackerRank 数据显示,2018 年,Java 在开发者最受欢迎的编程语言排行榜中仍然排名第 2,Python 排名第 4,Go 排名第 13,距离第一名 JavaScript 还有不小的差距。

但对本文作者 Jack Wilson 来说,Go 语言虽然“年纪尚小”,但已经拥有很多非常优秀的特性。

Go 语言到底好在哪?

在很多任务上,我已经用 Go 语言代替了 Python,举几个例子:

  • 处理储存在 S3 上的云端日志
  • 在 S3 上的 bucket 和 / 或 region 之间移动 TB 级别大小的文件
  • 匹配本地的数据库记录和 S3 上的文件,以保证文件处于同步状态

这些任务大多是一次性的,所以使用脚本语言操作就会比较合适。这些任务需要快速地编程,而代码用过一次一般就舍弃了。一般来说,这种任务的需求都比较新颖、比较专,其代码很少需要复用。下面就来介绍一下,针对这种任务为什么可以用 Go 语言来替代 Python。

拥有编译器很方便

我经常在写 Python 时犯很低级的错误。我会给变量或函数命错名,或向它们传递错误的参数。这样的错误用调试工具可以找出一部分,但这种工具一般需要专门设置。我从来没很方便地配置过 pylint,而且我也不喜欢用那些很重的、配置起来更麻烦的 IDE。最惨的情况是,你可能会不小心打错变量名,而这种错误又不太容易发现。你的脚本可能会跑好几个小时才遇上这个错误,然后一切都崩溃了,你又得从头开始跑这个脚本。这种错误大部分都可以靠单元测试检出,但单元测试很少能覆盖 100% 的代码,而且我并不想浪费时间去给一个只用一次的脚本写单元测试。而带编译器的语言就可以解决以上所有问题。**编译器可以检测出你犯的所有低级错误。**出于这点原因,我在写长达几百行的代码时,更倾向于使用 Go 这类语言。

开发速度

然而,需要编译的语言的一个缺点是,一般你的开发速度会下降。这点在 C/C++ 和 Java 等语言上体现得尤其明显。而 Go 是一个非常简单的语言,我发现它的开发速度并没有被拖慢多少。不要误会,我的意思并不是说它比 Python 还快,而是想说,**用 Go 语言没有比 Python 慢很多,一般达到使用 Python 开发速度的 85% 还是没问题的。**相对于拥有编译器能避免的那些低级错误来说,我认为牺牲 15% 的开发速度还是很值的。

更好的并行性

你可能已经知道,Go 语言就是为并行而生的。在我的团队里经常会需要并行程序,因为我们要在 S3 上操作我们数据库中大量的数据。如果该任务是 IO 密集型的(实际上很多任务都是),我们就可以很容易地部署 Python 线程。但如果任务是 CPU 密集型的,用 Python 就比较不方便了,因为有全局解释器锁的存在。我非常享受在 Go 语言中简单代码不用修改就能直接多线程运行的爽快感。不知你在 Python 中有没有遇到过这种问题:直接复制粘贴的多线程代码却完全不工作。在 Go 语言中就不会有此问题。

部署简单

我比较喜欢把所有依赖放在单个二进制文件里。我经常在 EC2 服务器上运行自己的脚本,好让环境更加接近我们在 S3 上的服务器。如果用 Python 的话,我需要保证所有需要的包都在服务器上装好,而且我的同事不能在服务器上装任何可能产生冲突的包。虚拟环境可以解决大部分问题,但我还是觉得用 Go 语言更方便。我一般是在 Mac 和 Linux 上交叉编译我的代码,将其拷贝到远程服务器上,然后就可以任其运行了。我的代码所需要的所有依赖都在一个二进制文件里。

风格一致

一开始,Go 语言的格式化工具 gofmt 实在是让我抓狂,尤其是它在代码缩进时要求使用 tab 键而不是空格键。我觉得这简直是疯了。但是我用了一段时间后,就开始“真香”了。写代码时,我在格式上可以天马行空,格式化工具会帮我完成一切。我的所有代码风格都是一致的,即使我是在写不同的项目。这是因为格式化是标准 Go 工具的一个特性。但我如果想在 Python 中实现这一点,就要费些劲儿了。我需要正确地配置 pylint 工具,并要保证在每一个项目中都使用它。

更便捷的工具

Gofmt 只是 Go 语言众多工具中的一个小例子。所有我喜欢用的编辑器——VSCode,vim 和 Sublime Text 中,都有 Go 语言相应的扩展,让我能够方便地享受到 Go 工具的优点。这样,我就能获得写 Java 时的那种智能体验,却不需要真正使用一个 IDE。我在用 Python 时从未获得过这种体验。

Go 语言当然也有缺点

我每次看到批评 Go 语言的文章时,里面讲的几乎都是 Go 语言对关键特性的缺失,比如泛型。我倒是认为没有泛型没什么影响——你会发现,使用map和切片(Slice)就能实现多得惊人的操作。但是我在使用 Go 语言的过程中遇到了很多其他问题。

缺乏灵活性

首先,Go 语言可能是我用过的语言里最“固执”的语言了。比如,它除了会强迫你使用 tab 而不是空格键缩进(假设你用了 gofmt 工具),会强迫你使用特定的文件组织结构,还会强迫你在 GOPATH 环境变量中编程,如此等等。这种语言有太多难以改变的特性了。Go 语言简单易学的原因之一恐怕就是你不能改变这些特性。如果你不愿意将所有首字母大写的变量名 export 一遍,那真是抱歉了。幸运的是,Go 的这些特性倒是没有触犯我的原则底线,但是如果有人认为里面的某些要求根本无法理喻,我也能够理解。相比之下 Python 就灵活多了。

库支持有点差劲

在这方面把 Go 语言和 Python 做比较有些不公平。Go 的出现比 Python 晚很多,但当我发现有些功能 Go 居然不支持的时候,还是觉得很困惑。我甚至发现 StackOverflow 上很多人 po 出了本应该作为内置功能的代码段,而且大家都需要该功能,纷纷将代码复制粘贴到自己的项目下。这类功能不应该嵌入到语言内部吗?说到这里,我想到了近几年的两个例子:

  • 给切片排序(幸运的是在 Go 1.8 版本中这点方便多了)
  • Math.round 只支持整数,不能进行浮点数的取整(比如你想找一个最接近 0.5 的整数,Go 语言就无法完成)。甚至在 Go 1.10 版本之前,根本没有 math.round 函数

当然,这些问题的原因有一部分是 Go 语言没有泛型,另一部分是因为 Go 的开发者们只给 Go 的标准库中添加最最必要的功能。

这两点我都理解,但是在遇到很小的问题却需要自己写代码解决时还是感到烦恼。希望随着 Go 语言的发展,它的问题会变得越来越少。

Go 和 Python 你更喜欢谁?欢迎留言交流!

在接触Golang以前,我用C/C++、Lua及Python作为主要开发语言。

C/C++的问题:

  • 开发效率低,对开发者要求高
  • libc只向后兼容,运维难度偏大

Lua/Python的问题:

  • 动态语言,缺少编译过程,低级错误频出
  • 缺少有效的性能分析及调试工具

** 场景**

当时刚完成了nginx WAF模块的开发工作,便开始着手搭建WAF的后台管理系统。 由于之前同事都用的fluentd作为日志收集组件,为保持基础组件的一致性,我也选择了fluentd。 即最终架构为fluentd->mongodb->mysql,再基于mysql做前端数据展示。 后来被坑了许多次,就决定用Go重写fluentd以解决下面几个问题:

  • fluentd在ubuntu 9.04偶尔会出现假死,导致数据丢失
  • fluentd难以接入公司现有的包发布系统,导致运维难度极大
  • mongodb采用mmap实现,数据量大时占用内存过高

方案

  • gofluent的代码 请点击这里
  • httpmq的代码 请点击这里

事实上,这两个项目都是为了解决上面提到的问题。 说起来也许你不信,这两个套代码是我在业余时间完成的,也就是说这根本不算在KPI之内。 其实一开始我也没想到能够这么快就写得七七八八,毕竟是现学现用啊。 但实际情况就是,我花了一周时间写完httpmq,一个月多时间就写好了gofluent……当然,这两个项目还有很多不完善的地方。目前就日志收集方案来说,我更推荐elastic/logstash-forwarder · GitHub和elastic/logstash · GitHub配合使用。

为什么选择Golang

那么,为什么我会选择Golang呢?其实我在做出这个选择之前已经花了大量时间做过详尽调研。 国外如Google、AWS、Cloudflare、CoreOS等,国内如七牛、阿里等都已经开始大规模使用Golang开发其云计算相关产品。 跟着世界级巨人的脚步应该不至于走错方向,而且在学习Golang的过程中,我也渐渐被其背后的设计哲学所折服。

另外,云风博客中曾说过这样一句话:

我发现我花了四年时间锤炼自己用 C 语言构建系统的能力,试图找到一个规范,可以更好的编写软件。结果发现只是对 Go 的模仿。缺乏语言层面的支持,只能是一个拙劣的模仿。

以下则是我对Golang的浅薄理解:

  • 有C基础,学Golang非常轻松
  • 同步方式轻松实现高并发
  • 代码简洁,格式统一,阅读方便
  • 性能强劲的同时,开发效率又不差于Python等动态语言

效果

最开始准备上线的时候其实心里挺忐忑,毕竟一旦出现故障,不仅黑锅得自己背,面子也上过不去啊。 还好结果蛮漂亮,自上线后没出现过一次突发性BUG,降低运维难度的同时还减少了机器的负载。

总而言之,从工程的角度上来看,对于大多数后台应用场景,选择Golang是极为明智的选择。 这样可以很轻松的兼顾运行性能、开发效率及维护难度这三大让诸多程序猿欲仙欲死的奇点。

阅读更多

资本寒冬下的android面经

编程时,照着别人的代码敲进去有效率吗?

开发了5年android,我开始了go学习之旅

2018 开发者生态报告:Java 最流行,Go 最有潜力,JavaScript最常用

最后, 我还是建议你在这个互联网时代里,不要刻意追求一种语言,每一种语言都是有他的优势,选择合适的才是最重要的。互联网没有过期的技术,也没有寒冷的冬天。我们选择尽可能多的储备粮食柴火。欢迎关注我微信技术号:终端研发部,id:codeGoogler。如果您遇到什么问题,也可以和我一块交流~

本文转载自: 掘金

开发者博客 – 和开发相关的 这里全都有

【译】 2019 为什么我们还会继续使用 PHP ?

发表于 2019-03-07

file

我们来开门见山地说。 ** PHP 是一门奇葩的语言。它既不快,语法又不漂亮。还没有遵守良好的软件开发实践。** 但我还是使用它开发了很多软件。 那么问题就很明显了 为什么今天还在使用 PHP ?

除了个人理想主义的偏好以外,还有很多理由。这才是我们要讨论的范围。为什么选 PHP 作为我的 Web 开发语言?

PHP 是什么?

「PHP 代表什么意思?」 或者这么问,「PHP 是什么?」 PHP 是一门编写 web 页面的语言,底层使用 C 语言实现,使用 HTML 的标签语法包裹代码。PHP 通常跑在服务端,与 web 服务器配合工作,负责把 HTML 加工完返回给访问者。

PHP 最初的意思是 「 Personal Home Page 」。因为这完全限制了使用该语言进行一般使用的意义和可取性,所以该语言现在代表「 PHP:Hypertext Preprocessor」。这种写法被称为递归缩写(一种全称中引用自己的缩写)。 极客们都喜欢用这种名字。

PHP 能做什么?

PHP 能用来做什么?通常来说 PHP 能做任何你想在 web 服务器上实现的东西。 比如说做一个博客。实现一个 SAAS 应用也完全不在话下。写一个临时处理数据的脚本也是得心应手。又或者写了一个复杂的脚本,某一天突然变成了一门成功的软件业务?过去也经常发生。

如果你不相信我,可以看下 PHP 官网列出使用场景:

  • 服务端脚本
  • 命令行脚本
  • 编写桌面应用

我不太鼓励最后一项,但它确实可行。但是前两项确实是很棒的理由 。

这就引出了一个重要且无法回避的事实…

PHP 无所不在

了解和喜爱 PHP 的理由有非常多,可能最给力和有效的理由是:它可以在网络的任何地方使用和运行。如果你仔细寻找,你买到每月最低3美元的托管账户可能可以运行 Python 或 Ruby 的 Web 应用程序。但它肯定能运行 PHP 。这意味着无论什么时候你都可以依赖 PHP 。

因为 PHP 可以运行在任何地方,并且它容易上手,很多非常受欢迎的软件都是用 PHP 写的。 WordPress 是对我影响最大和最熟悉的例子,除此之外,像 Joomla , Drupal , Magento , ExpressionEngine , vBulletin (是的,它仍然存在), MediaWiki 等这些工具,它们都在服务器上运行 PHP 。

不仅如此,PHP 应用框架更是多得数不胜数, 比如:Symfony , Zend , Laravel , Aura , CakePHP , Yii 甚至是古老的 CodeIgnitor 框架。当然你可以为其它任何一门语言制作一些长度相当的 Web 框架列表。比如常用的 Web 语言像 Python , Ruby ,或 Node/JavaScript ,你甚至可以积累一个数字竞争列表来跟 PHP 比较。但运行 PHP 的网站数量庞大到无法估计。

WordPress 自豪地声称在互联网上有 30% 的用户量。 你甚至不需要相信这个说法就会意识到许多的互联网应用必须使用 PHP 即使这个说法甚至可以想象成是真的。

PHP 的优点

动态化特点

file

PHP 和 HTML 可以非常融洽地工作在一起。将 file.html 修改为 file.php ,即可在 <?php 和 ?> 标签里编写 PHP 代码,并且大部分的 Web 服务器默认配置就是可以直接运行 PHP 脚本。正因为其门槛非常低,不需要懂太多编程知识的菜鸟程序员也可以直接上手操作,以至于产生了很多低品质的代码。

因为其易学性,大量的菜鸟程序员涌入,暴露出来另一个 PHP 一直被人诟病的问题:PHP 并没有对自己是最好的 Web 服务器端语言有清晰的远景,当然也没为此做过太好的设计。导致走向了另一个结果,来自全球各地松散的合作,大杂烩般地贡献代码和想法,难以避免地,有一些坏的想法渗入其中。

拥有强大的面向对象包管理是现在 PHP 中的一种标准

file

Composer 是 PHP 的加分项。

在 PHP 的开发中,经常会有一些奇葩的东西被发布出来。最著名的栗子当属 PHP 5.3 (广泛的被视为 PHP 的第一个现代化版本 )中引入 goto 声明。

类似的问题是在 PHP 成长过程中产生:面向对象最初是作为一个有缺陷和有限制的概念来实现的,标准库中充满了不一致的名称和参数排序,并且(举个近来备受关注的栗子)在 PHP 5.3 中 :: 操作符无法识别时,解析器会抛出可读性很差的报错信息:语法错误 (T_PAAMAYIM_NEKUDOTAYIM) ,如果不看文档,你能用英文来理解这个 T_PAAMAYIM_NEKUDOTAYIM 标示的意思吗?

但是现在,PHP 已经完全支持 OOP。很少语言具有像 PHP 这样类似 Java 的 OOP 实现。另外,与 Java不同的是,PHP 有一个单独的并拥有广泛支持的包管理器,叫作 Composer。它非常的好用,并且不容忽视的一点是它可以方便地引用优质且维护良好的库,具有很高的易用性。

PHP 的高速发展

file

通过这些事实发现, PHP 正在以有趣的方式发展着。它正逐渐成为像java一样功能齐全的面向对象语言(更好或更糟糕),正在为函数式编程提供简单的抽象,这可以说是当前的热点。并且,正在发展一套非常棒的工具, PHP 喜欢 Composer , 并有充分的理由 — 因为它在一些大型开源项目的协同工作上做出了值得赞扬的努力.

当然,我们不应该忘记当前的热点:PHP 在 PHP7 系列版本中的发展取得了速度上的提升。这被广泛认为是由 Facbook 出现的 HHVM 引起的,在短时间内,存在 HHVM 的发展速度会破坏 PHP 社区的风险,但事实并非如此,相反,PHP 的发展速度快得多,以至于人们几乎忘记了 HHVM 的存在。

PHP 有一个巨大的社区;它对新手友好。

如果你决定使用什么很酷的新技术,我认为经常得到很少关注的是与语言达成协议的方式。学习PHP是什么感觉? PHP教程很好找,质量通常也很好。

PHP流行的一个缺点是,你会从那些对工具不够了解的人那里找到一些不那么好的教学。或者从某人那里学到的“最佳实践”是十年前的。但总的来说,这很少见,我不认为它应该让你灰心。或者突然发现自己使用了几个月的最佳实践并不是最好的是很少见的,也不是一个大问题。

PHP 与其他语言的比较

接下来我们要对比下 PHP 与其他的语言,这些语言需满足以下要求:

  • 开源:意味着你可以自由、免费地使用或者查看底层语言或者程序;
  • 应用于 Web 开发领域:不一定是专注于 Web 开发,但是在 Web 开发中有比较大的应用;
  • 高阶动态语言:具备动态语言的特性,大部分面向 Web 应用的语言兼具此特性;
  • 足够大的社区:有很多满足以上条件,但是只是在小范围使用的语言,我们使用这最后一个条件将他们过滤掉。

为什么使用 PHP?而不是 JavaScript?

也许和 PHP 比较的最重要的语言是 JavaScript。现代开发要求每个项目都至少会一点 JavaScript 来进行客户端开发和交互。借助 Node,这使得在服务器上使用 JS 变得相对简单。在服务器上和客户端“同构”使用相同的语言非常吸引人。

如同 PHP,JavaScript 是一个兼容并蓄但有时丑陋的语言,有很多的瑕疵和“陷阱”,但 JavaScript 在过去十年变得非常快,所以他的情况是真实的。为什么选择 PHP 而不是 JavaScript?你已有专业知识或者在 PHP 使用库。否则,我认为 JavaScript 或许是个更好的选择。

PHP 对比动态服务器页面 (.Net Core)

动态服务器语言起源于一个用于网友编程的 Microsoft 语言。他和 PHP 非常直接相似。但他运行在 Windows 服务器环境。这被 ASP.net 取代了。现在已被 ASP.NET Core 取代。后两者现在是 PHP 一样的开源语言。而我个人从没有在任何变体中写过一点 ASP。

如果我使用且更喜欢 Microsoft 服务器的话,我会更喜欢 ASP。其他情况,请给我 PHP。两者社区的规模和大小没有可比性。

我应该使用 Ruby 还是 PHP?

Ruby,特别是 Ruby on Rails,在过去十年前 非常 受欢迎。Ruby 依然是一个备受喜爱的语言,这在我看来他比 PHP 优雅。也就是说,他的社区较小。同时我认识到 Ruby 已经不再是 “热门语言”(被 JavaScript 取代了此角色)。Ruby 的优雅,并且有足够多的人擅长使用,因此我不会避免他。但招聘已熟悉 Ruby 的人依旧比招聘 PHP 要难。(虽然我认为一般的 Ruby 开发者水平比相同的 PHP 开发者要高。)

Python vs PHP:谁更好一些?

最后一种与 PHP 一对一比较起来有意义的是 Python 。 Python 的使用场景要更多,相对于专注于 web 开发的 PHP 来说(尤其是在数据统计与分析上面)。而且人们普遍都觉得它是一种更为更稳定和优雅的语言。

就像 Ruby 和 JavaScript 一样, Python 在服务器上运行要比 PHP 稍微麻烦一点。但是它是一种非常完美的语言,而且比起 PHP 的使用更具多样化,我认为它是比 PHP 更流行的语言之一,而且在其他方面(例如:各种第三方的库、专业技术知识的获取、招聘求职)都是一样的。

PHP vs Go 语言?Scala语言?Java语言?等等

一开始曾提到过,许多语言在做比较时或多或少会有一些相似的地方。这里有很多的语言可以拿来做对比,因此简单的聊几个:

  • java 非常受欢迎,并且效率很高。通常被用来搭建安卓应用程序,桌面应用程序,和 web 端应用。不过,它不是动态类型的,它有更好的性能保证,但很适合Web 编程。
  • Go 是 Google 支持的正在发展的一门新语言。它专注 web 服务。但是这块相比 PHP 还是有些不足(更像 C 语言),它的执行速度很快,但是社区相对有限。
  • ** Scala** 是一种流行的运行在内存区(Java 兼容)的语言,也似乎越来越受欢迎。它比 PHP 设计的更优雅,但是除此之外,似乎没有更多了。

此外,我觉得这些分析足够可以做出选择了,但是你有更多的选项可以去考量,不过最后我的替代方案将会考虑 PHP。

需要按需选择编程语言

有了上面的比较,为什么还要使用 PHP ?上文我已经提过了一些 PHP 里坏的设计,有一些我经常性会经历,例如在『查找类型的函数』里,这些函数needle 和 haystack 顺序不一致,在没有代码补全的环境下,我还是会掉坑里。

如果你在构建一个全新的 Web 项目,并且此项目不需要与其他系统交互,并且只有你一个人在工作,你想试试看其他语言,类似 Python 什么的。但是,你需要知道的是,即使是这样的一个项目,还是有很多理由让你选择 PHP。

任何现代化的语言都可以让你构建一个 Web App。每一门语言都有他们不足之处,例如 PHP 就是这样的,PHP 要求你清晰地了解其不足之处,然后才能决策是否要在项目里使用它。但是对于我来说,我仍然会选择 PHP 来构建我的 Web 项目,就如 Keith Adams 在其 演讲里 提到的:

PHP 的开发效率真不是一般的高。

如果你有一个 Python 的团队,请使用 Python。如果你有个合伙人熟悉 Java,请使用 Java 来编写你的 SaaS 程序。语言,永远都不是最重要的。

等一下

如果你已经看到文章末尾了,你很可能对 PHP 感兴趣。 也许你也有兴趣阅读我的其他内容? 以下是我写的一些内容。

本人博客 :泽林博客

本人公众号 :PHP开源Hub,ID:php_open

本文转载自: 掘金

开发者博客 – 和开发相关的 这里全都有

SQL 语法速成手册

发表于 2019-03-05

本文针对关系型数据库的一般语法。限于篇幅,本文侧重说明用法,不会展开讲解特性、原理。

一、基本概念

数据库术语

  • 数据库(database) - 保存有组织的数据的容器(通常是一个文件或一组文件)。
  • 数据表(table) - 某种特定类型数据的结构化清单。
  • 模式(schema) - 关于数据库和表的布局及特性的信息。模式定义了数据在表中如何存储,包含存储什么样的数据,数据如何分解,各部分信息如何命名等信息。数据库和表都有模式。
  • 列(column) - 表中的一个字段。所有表都是由一个或多个列组成的。
  • 行(row) - 表中的一个记录。
  • 主键(primary key) - 一列(或一组列),其值能够唯一标识表中每一行。

SQL 语法

SQL(Structured Query Language),标准 SQL 由 ANSI 标准委员会管理,从而称为 ANSI SQL。各个 DBMS 都有自己的实现,如 PL/SQL、Transact-SQL 等。

SQL 语法结构

sql-syntax

SQL 语法结构包括:

  • 子句 - 是语句和查询的组成成分。(在某些情况下,这些都是可选的。)
  • 表达式 - 可以产生任何标量值,或由列和行的数据库表
  • 谓词 - 给需要评估的 SQL 三值逻辑(3VL)(true/false/unknown)或布尔真值指定条件,并限制语句和查询的效果,或改变程序流程。
  • 查询 - 基于特定条件检索数据。这是 SQL 的一个重要组成部分。
  • 语句 - 可以持久地影响纲要和数据,也可以控制数据库事务、程序流程、连接、会话或诊断。

SQL 语法要点

  • SQL 语句不区分大小写,但是数据库表名、列名和值是否区分,依赖于具体的 DBMS 以及配置。

例如:SELECT 与 select 、Select 是相同的。

  • 多条 SQL 语句必须以分号(;)分隔。
  • 处理 SQL 语句时,所有空格都被忽略。SQL 语句可以写成一行,也可以分写为多行。
1
2
3
4
5
6
7
复制代码-- 一行 SQL 语句
UPDATE user SET username='robot', password='robot' WHERE username = 'root';

-- 多行 SQL 语句
UPDATE user
SET username='robot', password='robot'
WHERE username = 'root';
  • SQL 支持三种注释
1
2
3
复制代码## 注释1
-- 注释2
/* 注释3 */

SQL 分类

数据定义语言(DDL)

数据定义语言(Data Definition Language,DDL)是 SQL 语言集中负责数据结构定义与数据库对象定义的语言。

DDL 的主要功能是定义数据库对象。

DDL 的核心指令是 CREATE、ALTER、DROP。

数据操纵语言(DML)

数据操纵语言(Data Manipulation Language, DML)是用于数据库操作,对数据库其中的对象和数据运行访问工作的编程语句。

DML 的主要功能是 访问数据,因此其语法都是以读写数据库为主。

DML 的核心指令是 INSERT、UPDATE、DELETE、SELECT。这四个指令合称 CRUD(Create, Read, Update, Delete),即增删改查。

事务控制语言(TCL)

事务控制语言 (Transaction Control Language, TCL) 用于管理数据库中的事务。这些用于管理由 DML 语句所做的更改。它还允许将语句分组为逻辑事务。

TCL 的核心指令是 COMMIT、ROLLBACK。

数据控制语言(DCL)

数据控制语言 (Data Control Language, DCL) 是一种可对数据访问权进行控制的指令,它可以控制特定用户账户对数据表、查看表、预存程序、用户自定义函数等数据库对象的控制权。

DCL 的核心指令是 GRANT、REVOKE。

DCL 以控制用户的访问权限为主,因此其指令作法并不复杂,可利用 DCL 控制的权限有:CONNECT、SELECT、INSERT、UPDATE、DELETE、EXECUTE、USAGE、REFERENCES。

根据不同的 DBMS 以及不同的安全性实体,其支持的权限控制也有所不同。


(以下为 DML 语句用法)

二、增删改查

增删改查,又称为 CRUD,数据库基本操作中的基本操作。

插入数据

  • INSERT INTO 语句用于向表中插入新记录。

插入完整的行

1
2
复制代码INSERT INTO user
VALUES (10, 'root', 'root', 'xxxx@163.com');

插入行的一部分

1
2
复制代码INSERT INTO user(username, password, email)
VALUES ('admin', 'admin', 'xxxx@163.com');

插入查询出来的数据

1
2
3
复制代码INSERT INTO user(username)
SELECT name
FROM account;

更新数据

  • UPDATE 语句用于更新表中的记录。
1
2
3
复制代码UPDATE user
SET username='robot', password='robot'
WHERE username = 'root';

删除数据

  • DELETE 语句用于删除表中的记录。
  • TRUNCATE TABLE 可以清空表,也就是删除所有行。

删除表中的指定数据

1
2
复制代码DELETE FROM user
WHERE username = 'robot';

清空表中的数据

1
复制代码TRUNCATE TABLE user;

查询数据

  • SELECT 语句用于从数据库中查询数据。
  • DISTINCT 用于返回唯一不同的值。它作用于所有列,也就是说所有列的值都相同才算相同。
  • LIMIT 限制返回的行数。可以有两个参数,第一个参数为起始行,从 0 开始;第二个参数为返回的总行数。
    • ASC :升序(默认)
    • DESC :降序

查询单列

1
2
复制代码SELECT prod_name
FROM products;

查询多列

1
2
复制代码SELECT prod_id, prod_name, prod_price
FROM products;

查询所有列

1
2
复制代码ELECT *
FROM products;

查询不同的值

1
2
复制代码SELECT DISTINCT
vend_id FROM products;

限制查询结果

1
2
3
4
5
复制代码-- 返回前 5 行
SELECT * FROM mytable LIMIT 5;
SELECT * FROM mytable LIMIT 0, 5;
-- 返回第 3 ~ 5 行
SELECT * FROM mytable LIMIT 2, 3;

三、子查询

子查询是嵌套在较大查询中的 SQL 查询。子查询也称为内部查询或内部选择,而包含子查询的语句也称为外部查询或外部选择。

  • 子查询可以嵌套在 SELECT,INSERT,UPDATE 或 DELETE 语句内或另一个子查询中。
  • 子查询通常会在另一个 SELECT 语句的 WHERE 子句中添加。
  • 您可以使用比较运算符,如 >,<,或 =。比较运算符也可以是多行运算符,如 IN,ANY 或 ALL。
  • 子查询必须被圆括号 () 括起来。
  • 内部查询首先在其父查询之前执行,以便可以将内部查询的结果传递给外部查询。执行过程可以参考下图:

sql-subqueries

子查询的子查询

1
2
3
4
5
6
7
复制代码SELECT cust_name, cust_contact
FROM customers
WHERE cust_id IN (SELECT cust_id
FROM orders
WHERE order_num IN (SELECT order_num
FROM orderitems
WHERE prod_id = 'RGAN01'));

WHERE

  • WHERE 子句用于过滤记录,即缩小访问数据的范围。
  • WHERE 后跟一个返回 true 或 false 的条件。
  • WHERE 可以与 SELECT,UPDATE 和 DELETE 一起使用。
  • 可以在 WHERE 子句中使用的操作符
运算符 描述
= 等于
<> 不等于。注释:在 SQL 的一些版本中,该操作符可被写成 !=
> 大于
< 小于
>= 大于等于
<= 小于等于
BETWEEN 在某个范围内
LIKE 搜索某种模式
IN 指定针对某个列的多个可能值

SELECT 语句中的 WHERE 子句

1
2
复制代码SELECT * FROM Customers
WHERE cust_name = 'Kids Place';

UPDATE 语句中的 WHERE 子句

1
2
3
复制代码UPDATE Customers
SET cust_name = 'Jack Jones'
WHERE cust_name = 'Kids Place';

DELETE 语句中的 WHERE 子句

1
2
复制代码DELETE FROM Customers
WHERE cust_name = 'Kids Place';

IN 和 BETWEEN

  • IN 操作符在 WHERE 子句中使用,作用是在指定的几个特定值中任选一个值。
  • BETWEEN 操作符在 WHERE 子句中使用,作用是选取介于某个范围内的值。

IN 示例

1
2
3
复制代码SELECT *
FROM products
WHERE vend_id IN ('DLL01', 'BRS01');

BETWEEN 示例

1
2
3
复制代码SELECT *
FROM products
WHERE prod_price BETWEEN 3 AND 5;

AND、OR、NOT

  • AND、OR、NOT 是用于对过滤条件的逻辑处理指令。
  • AND 优先级高于 OR,为了明确处理顺序,可以使用 ()。
  • AND 操作符表示左右条件都要满足。
  • OR 操作符表示左右条件满足任意一个即可。
  • NOT 操作符用于否定一个条件。

AND 示例

1
2
3
复制代码SELECT prod_id, prod_name, prod_price
FROM products
WHERE vend_id = 'DLL01' AND prod_price <= 4;

OR 示例

1
2
3
复制代码SELECT prod_id, prod_name, prod_price
FROM products
WHERE vend_id = 'DLL01' OR vend_id = 'BRS01';

NOT 示例

1
2
3
复制代码SELECT *
FROM products
WHERE prod_price NOT BETWEEN 3 AND 5;

LIKE

  • LIKE 操作符在 WHERE 子句中使用,作用是确定字符串是否匹配模式。
  • 只有字段是文本值时才使用 LIKE。
  • LIKE 支持两个通配符匹配选项:% 和 _。
  • 不要滥用通配符,通配符位于开头处匹配会非常慢。
  • % 表示任何字符出现任意次数。
  • _ 表示任何字符出现一次。

% 示例

1
2
3
复制代码SELECT prod_id, prod_name, prod_price
FROM products
WHERE prod_name LIKE '%bean bag%';

_ 示例

1
2
3
复制代码SELECT prod_id, prod_name, prod_price
FROM products
WHERE prod_name LIKE '__ inch teddy bear';

四、连接和组合

连接(JOIN)

  • 如果一个 JOIN 至少有一个公共字段并且它们之间存在关系,则该 JOIN 可以在两个或多个表上工作。
  • 连接用于连接多个表,使用 JOIN 关键字,并且条件语句使用 ON 而不是 WHERE。
  • JOIN 保持基表(结构和数据)不变。
  • JOIN 有两种连接类型:内连接和外连接。
  • 内连接又称等值连接,使用 INNER JOIN 关键字。在没有条件语句的情况下返回笛卡尔积。
    • 自连接可以看成内连接的一种,只是连接的表是自身而已。
  • 自然连接是把同名列通过 = 测试连接起来的,同名列可以有多个。
  • 内连接 vs 自然连接
    • 内连接提供连接的列,而自然连接自动连接所有同名列。
  • 外连接返回一个表中的所有行,并且仅返回来自次表中满足连接条件的那些行,即两个表中的列是相等的。外连接分为左外连接、右外连接、全外连接(Mysql 不支持)。
    • 左外连接就是保留左表没有关联的行。
    • 右外连接就是保留右表没有关联的行。
  • 连接 vs 子查询
    • 连接可以替换子查询,并且比子查询的效率一般会更快。

sql-join

内连接(INNER JOIN)

1
2
3
复制代码SELECT vend_name, prod_name, prod_price
FROM vendors INNER JOIN products
ON vendors.vend_id = products.vend_id;

自连接

1
2
3
4
复制代码SELECT c1.cust_id, c1.cust_name, c1.cust_contact
FROM customers c1, customers c2
WHERE c1.cust_name = c2.cust_name
AND c2.cust_contact = 'Jim Jones';

自然连接(NATURAL JOIN)

1
2
3
复制代码SELECT *
FROM Products
NATURAL JOIN Customers;

左连接(LEFT JOIN)

1
2
3
复制代码SELECT customers.cust_id, orders.order_num
FROM customers LEFT JOIN orders
ON customers.cust_id = orders.cust_id;

右连接(RIGHT JOIN)

1
2
3
复制代码SELECT customers.cust_id, orders.order_num
FROM customers RIGHT JOIN orders
ON customers.cust_id = orders.cust_id;

组合(UNION)

  • UNION 运算符将两个或更多查询的结果组合起来,并生成一个结果集,其中包含来自 UNION 中参与查询的提取行。
  • UNION 基本规则
    • 所有查询的列数和列顺序必须相同。
    • 每个查询中涉及表的列的数据类型必须相同或兼容。
    • 通常返回的列名取自第一个查询。
  • 默认会去除相同行,如果需要保留相同行,使用 UNION ALL。
  • 只能包含一个 ORDER BY 子句,并且必须位于语句的最后。
  • 应用场景
    • 在一个查询中从不同的表返回结构数据。
    • 对一个表执行多个查询,按一个查询返回数据。

组合查询

1
2
3
4
5
6
7
复制代码SELECT cust_name, cust_contact, cust_email
FROM customers
WHERE cust_state IN ('IL', 'IN', 'MI')
UNION
SELECT cust_name, cust_contact, cust_email
FROM customers
WHERE cust_name = 'Fun4All';

JOIN vs UNION

  • JOIN vs UNION
    • JOIN 中连接表的列可能不同,但在 UNION 中,所有查询的列数和列顺序必须相同。
    • UNION 将查询之后的行放在一起(垂直放置),但 JOIN 将查询之后的列放在一起(水平放置),即它构成一个笛卡尔积。

五、函数

🔔 注意:不同数据库的函数往往各不相同,因此不可移植。本节主要以 Mysql 的函数为例。

文本处理

函数 说明
LEFT()、RIGHT() 左边或者右边的字符
LOWER()、UPPER() 转换为小写或者大写
LTRIM()、RTIM() 去除左边或者右边的空格
LENGTH() 长度
SOUNDEX() 转换为语音值

其中, SOUNDEX() 可以将一个字符串转换为描述其语音表示的字母数字模式。

1
2
3
复制代码SELECT *
FROM mytable
WHERE SOUNDEX(col1) = SOUNDEX('apple')

日期和时间处理

  • 日期格式:YYYY-MM-DD
  • 时间格式:HH:MM:SS
函 数 说 明
AddDate() 增加一个日期(天、周等)
AddTime() 增加一个时间(时、分等)
CurDate() 返回当前日期
CurTime() 返回当前时间
Date() 返回日期时间的日期部分
DateDiff() 计算两个日期之差
Date_Add() 高度灵活的日期运算函数
Date_Format() 返回一个格式化的日期或时间串
Day() 返回一个日期的天数部分
DayOfWeek() 对于一个日期,返回对应的星期几
Hour() 返回一个时间的小时部分
Minute() 返回一个时间的分钟部分
Month() 返回一个日期的月份部分
Now() 返回当前日期和时间
Second() 返回一个时间的秒部分
Time() 返回一个日期时间的时间部分
Year() 返回一个日期的年份部分
1
复制代码mysql> SELECT NOW();
1
复制代码2018-4-14 20:25:11

数值处理

函数 说明
SIN() 正弦
COS() 余弦
TAN() 正切
ABS() 绝对值
SQRT() 平方根
MOD() 余数
EXP() 指数
PI() 圆周率
RAND() 随机数

汇总

函 数 说 明
AVG() 返回某列的平均值
COUNT() 返回某列的行数
MAX() 返回某列的最大值
MIN() 返回某列的最小值
SUM() 返回某列值之和

AVG() 会忽略 NULL 行。

使用 DISTINCT 可以让汇总函数值汇总不同的值。

1
2
复制代码SELECT AVG(DISTINCT col1) AS avg_col
FROM mytable

六、排序和分组

ORDER BY

  • ORDER BY 用于对结果集进行排序。
    • ASC :升序(默认)
    • DESC :降序
  • 可以按多个列进行排序,并且为每个列指定不同的排序方式

指定多个列的排序方向

1
2
复制代码SELECT * FROM products
ORDER BY prod_price DESC, prod_name ASC;

GROUP BY

  • GROUP BY 子句将记录分组到汇总行中。
  • GROUP BY 为每个组返回一个记录。
  • GROUP BY 通常还涉及聚合:COUNT,MAX,SUM,AVG 等。
  • GROUP BY 可以按一列或多列进行分组。
  • GROUP BY 按分组字段进行排序后,ORDER BY 可以以汇总字段来进行排序。

分组

1
2
复制代码SELECT cust_name, COUNT(cust_address) AS addr_num
FROM Customers GROUP BY cust_name;

分组后排序

1
2
3
复制代码SELECT cust_name, COUNT(cust_address) AS addr_num
FROM Customers GROUP BY cust_name
ORDER BY cust_name DESC;

HAVING

  • HAVING 用于对汇总的 GROUP BY 结果进行过滤。
  • HAVING 要求存在一个 GROUP BY 子句。
  • WHERE 和 HAVING 可以在相同的查询中。
  • HAVING vs WHERE
    • WHERE 和 HAVING 都是用于过滤。
    • HAVING 适用于汇总的组记录;而 WHERE 适用于单个记录。

使用 WHERE 和 HAVING 过滤数据

1
2
3
4
5
复制代码SELECT cust_name, COUNT(*) AS num
FROM Customers
WHERE cust_email IS NOT NULL
GROUP BY cust_name
HAVING COUNT(*) >= 1;

(以下为 DDL 语句用法)

七、数据定义

DDL 的主要功能是定义数据库对象(如:数据库、数据表、视图、索引等)。

数据库(DATABASE)

创建数据库

1
复制代码CREATE DATABASE test;

删除数据库

1
复制代码DROP DATABASE test;

选择数据库

1
复制代码USE test;

数据表(TABLE)

创建数据表

普通创建

1
2
3
4
5
6
复制代码CREATE TABLE user (
id int(10) unsigned NOT NULL COMMENT 'Id',
username varchar(64) NOT NULL DEFAULT 'default' COMMENT '用户名',
password varchar(64) NOT NULL DEFAULT 'default' COMMENT '密码',
email varchar(64) NOT NULL DEFAULT 'default' COMMENT '邮箱'
) COMMENT='用户表';

根据已有的表创建新表

1
2
复制代码CREATE TABLE vip_user AS
SELECT * FROM user;

删除数据表

1
复制代码DROP TABLE user;

修改数据表

添加列

1
2
复制代码ALTER TABLE user
ADD age int(3);

删除列

1
2
复制代码ALTER TABLE user
DROP COLUMN age;

修改列

1
2
复制代码ALTER TABLE `user`
MODIFY COLUMN age tinyint;

添加主键

1
2
复制代码ALTER TABLE user
ADD PRIMARY KEY (id);

删除主键

1
2
复制代码ALTER TABLE user
DROP PRIMARY KEY;

视图(VIEW)

  • 定义
    • 视图是基于 SQL 语句的结果集的可视化的表。
    • 视图是虚拟的表,本身不包含数据,也就不能对其进行索引操作。对视图的操作和对普通表的操作一样。
  • 作用
    • 简化复杂的 SQL 操作,比如复杂的联结;
    • 只使用实际表的一部分数据;
    • 通过只给用户访问视图的权限,保证数据的安全性;
    • 更改数据格式和表示。

创建视图

1
2
3
4
复制代码CREATE VIEW top_10_user_view AS
SELECT id, username
FROM user
WHERE id < 10;

删除视图

1
复制代码DROP VIEW top_10_user_view;

索引(INDEX)

  • 作用
    • 通过索引可以更加快速高效地查询数据。
    • 用户无法看到索引,它们只能被用来加速查询。
  • 注意
    • 更新一个包含索引的表需要比更新一个没有索引的表花费更多的时间,这是由于索引本身也需要更新。因此,理想的做法是仅仅在常常被搜索的列(以及表)上面创建索引。
  • 唯一索引
    • 唯一索引表明此索引的每一个索引值只对应唯一的数据记录。

创建索引

1
2
复制代码CREATE INDEX user_index
ON user (id);

创建唯一索引

1
2
复制代码CREATE UNIQUE INDEX user_index
ON user (id);

删除索引

1
2
复制代码ALTER TABLE user
DROP INDEX user_index;

约束

SQL 约束用于规定表中的数据规则。

  • 如果存在违反约束的数据行为,行为会被约束终止。
  • 约束可以在创建表时规定(通过 CREATE TABLE 语句),或者在表创建之后规定(通过 ALTER TABLE 语句)。
  • 约束类型
    • NOT NULL - 指示某列不能存储 NULL 值。
    • UNIQUE - 保证某列的每行必须有唯一的值。
    • PRIMARY KEY - NOT NULL 和 UNIQUE 的结合。确保某列(或两个列多个列的结合)有唯一标识,有助于更容易更快速地找到表中的一个特定的记录。
    • FOREIGN KEY - 保证一个表中的数据匹配另一个表中的值的参照完整性。
    • CHECK - 保证列中的值符合指定的条件。
    • DEFAULT - 规定没有给列赋值时的默认值。

创建表时使用约束条件:

1
2
3
4
5
6
7
8
复制代码CREATE TABLE Users (
Id INT(10) UNSIGNED NOT NULL AUTO_INCREMENT COMMENT '自增Id',
Username VARCHAR(64) NOT NULL UNIQUE DEFAULT 'default' COMMENT '用户名',
Password VARCHAR(64) NOT NULL DEFAULT 'default' COMMENT '密码',
Email VARCHAR(64) NOT NULL DEFAULT 'default' COMMENT '邮箱地址',
Enabled TINYINT(4) DEFAULT NULL COMMENT '是否有效',
PRIMARY KEY (Id)
) ENGINE=InnoDB AUTO_INCREMENT=2 DEFAULT CHARSET=utf8mb4 COMMENT='用户表';

(以下为 TCL 语句用法)

八、事务处理

  • 不能回退 SELECT 语句,回退 SELECT 语句也没意义;也不能回退 CREATE 和 DROP 语句。
  • MySQL 默认是隐式提交,每执行一条语句就把这条语句当成一个事务然后进行提交。当出现 START TRANSACTION 语句时,会关闭隐式提交;当 COMMIT 或 ROLLBACK 语句执行后,事务会自动关闭,重新恢复隐式提交。
  • 通过 set autocommit=0 可以取消自动提交,直到 set autocommit=1 才会提交;autocommit 标记是针对每个连接而不是针对服务器的。
  • 指令
    • START TRANSACTION - 指令用于标记事务的起始点。
    • SAVEPOINT - 指令用于创建保留点。
    • ROLLBACK TO - 指令用于回滚到指定的保留点;如果没有设置保留点,则回退到 START TRANSACTION 语句处。
    • COMMIT - 提交事务。
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
复制代码-- 开始事务
START TRANSACTION;

-- 插入操作 A
INSERT INTO `user`
VALUES (1, 'root1', 'root1', 'xxxx@163.com');

-- 创建保留点 updateA
SAVEPOINT updateA;

-- 插入操作 B
INSERT INTO `user`
VALUES (2, 'root2', 'root2', 'xxxx@163.com');

-- 回滚到保留点 updateA
ROLLBACK TO updateA;

-- 提交事务,只有操作 A 生效
COMMIT;

(以下为 DCL 语句用法)

九、权限控制

  • GRANT 和 REVOKE 可在几个层次上控制访问权限:
    • 整个服务器,使用 GRANT ALL 和 REVOKE ALL;
    • 整个数据库,使用 ON database.*;
    • 特定的表,使用 ON database.table;
    • 特定的列;
    • 特定的存储过程。
  • 新创建的账户没有任何权限。
  • 账户用 username@host 的形式定义,username@% 使用的是默认主机名。
  • MySQL 的账户信息保存在 mysql 这个数据库中。
    1
    2
    3
    4
    > 复制代码USE mysql;
    > SELECT user FROM user;
    >
    >

创建账户

1
复制代码CREATE USER myuser IDENTIFIED BY 'mypassword';

修改账户名

1
2
复制代码UPDATE user SET user='newuser' WHERE user='myuser';
FLUSH PRIVILEGES;

删除账户

1
复制代码DROP USER myuser;

查看权限

1
复制代码SHOW GRANTS FOR myuser;

授予权限

1
复制代码GRANT SELECT, INSERT ON *.* TO myuser;

删除权限

1
复制代码REVOKE SELECT, INSERT ON *.* FROM myuser;

更改密码

1
复制代码SET PASSWORD FOR myuser = 'mypass';

十、存储过程

  • 存储过程可以看成是对一系列 SQL 操作的批处理;
  • 使用存储过程的好处
    • 代码封装,保证了一定的安全性;
    • 代码复用;
    • 由于是预先编译,因此具有很高的性能。
  • 创建存储过程
    • 命令行中创建存储过程需要自定义分隔符,因为命令行是以 ; 为结束符,而存储过程中也包含了分号,因此会错误把这部分分号当成是结束符,造成语法错误。
    • 包含 in、out 和 inout 三种参数。
    • 给变量赋值都需要用 select into 语句。
    • 每次只能给一个变量赋值,不支持集合的操作。

创建存储过程

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
复制代码DROP PROCEDURE IF EXISTS `proc_adder`;
DELIMITER ;;
CREATE DEFINER=`root`@`localhost` PROCEDURE `proc_adder`(IN a int, IN b int, OUT sum int)
BEGIN
DECLARE c int;
if a is null then set a = 0;
end if;

if b is null then set b = 0;
end if;

set sum = a + b;
END
;;
DELIMITER ;

使用存储过程

1
2
3
复制代码set @b=5;
call proc_adder(2,@b,@s);
select @s as sum;

十一、游标

  • 游标(cursor)是一个存储在 DBMS 服务器上的数据库查询,它不是一条 SELECT 语句,而是被该语句检索出来的结果集。
  • 在存储过程中使用游标可以对一个结果集进行移动遍历。
  • 游标主要用于交互式应用,其中用户需要对数据集中的任意行进行浏览和修改。
  • 使用游标的四个步骤:
    • 声明游标,这个过程没有实际检索出数据;
    • 打开游标;
    • 取出数据;
    • 关闭游标;
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
复制代码DELIMITER $
CREATE PROCEDURE getTotal()
BEGIN
DECLARE total INT;
-- 创建接收游标数据的变量
DECLARE sid INT;
DECLARE sname VARCHAR(10);
-- 创建总数变量
DECLARE sage INT;
-- 创建结束标志变量
DECLARE done INT DEFAULT false;
-- 创建游标
DECLARE cur CURSOR FOR SELECT id,name,age from cursor_table where age>30;
-- 指定游标循环结束时的返回值
DECLARE CONTINUE HANDLER FOR NOT FOUND SET done = true;
SET total = 0;
OPEN cur;
FETCH cur INTO sid, sname, sage;
WHILE(NOT done)
DO
SET total = total + 1;
FETCH cur INTO sid, sname, sage;
END WHILE;

CLOSE cur;
SELECT total;
END $
DELIMITER ;

-- 调用存储过程
call getTotal();

十二、触发器

触发器是一种与表操作有关的数据库对象,当触发器所在表上出现指定事件时,将调用该对象,即表的操作事件触发表上的触发器的执行。

可以使用触发器来进行审计跟踪,把修改记录到另外一张表中。

MySQL 不允许在触发器中使用 CALL 语句 ,也就是不能调用存储过程。

BEGIN 和 END

当触发器的触发条件满足时,将会执行 BEGIN 和 END 之间的触发器执行动作。

🔔 注意:在 MySQL 中,分号 ; 是语句结束的标识符,遇到分号表示该段语句已经结束,MySQL 可以开始执行了。因此,解释器遇到触发器执行动作中的分号后就开始执行,然后会报错,因为没有找到和 BEGIN 匹配的 END。

这时就会用到 DELIMITER 命令(DELIMITER 是定界符,分隔符的意思)。它是一条命令,不需要语句结束标识,语法为:DELIMITER new_delemiter。new_delemiter 可以设为 1 个或多个长度的符号,默认的是分号 ;,我们可以把它修改为其他符号,如 $ - DELIMITER $ 。在这之后的语句,以分号结束,解释器不会有什么反应,只有遇到了 $,才认为是语句结束。注意,使用完之后,我们还应该记得把它给修改回来。

NEW 和 OLD

  • MySQL 中定义了 NEW 和 OLD 关键字,用来表示触发器的所在表中,触发了触发器的那一行数据。
  • 在 INSERT 型触发器中,NEW 用来表示将要(BEFORE)或已经(AFTER)插入的新数据;
  • 在 UPDATE 型触发器中,OLD 用来表示将要或已经被修改的原数据,NEW 用来表示将要或已经修改为的新数据;
  • 在 DELETE 型触发器中,OLD 用来表示将要或已经被删除的原数据;
  • 使用方法: NEW.columnName (columnName 为相应数据表某一列名)

创建触发器

提示:为了理解触发器的要点,有必要先了解一下创建触发器的指令。

CREATE TRIGGER 指令用于创建触发器。

语法:

1
2
3
4
5
6
7
8
复制代码CREATE TRIGGER trigger_name
trigger_time
trigger_event
ON table_name
FOR EACH ROW
BEGIN
trigger_statements
END;

说明:

  • trigger_name:触发器名
  • trigger_time: 触发器的触发时机。取值为 BEFORE 或 AFTER。
  • trigger_event: 触发器的监听事件。取值为 INSERT、UPDATE 或 DELETE。
  • table_name: 触发器的监听目标。指定在哪张表上建立触发器。
  • FOR EACH ROW: 行级监视,Mysql 固定写法,其他 DBMS 不同。
  • trigger_statements: 触发器执行动作。是一条或多条 SQL 语句的列表,列表内的每条语句都必须用分号 ; 来结尾。

示例:

1
2
3
4
5
6
7
8
9
复制代码DELIMITER $
CREATE TRIGGER `trigger_insert_user`
AFTER INSERT ON `user`
FOR EACH ROW
BEGIN
INSERT INTO `user_history`(user_id, operate_type, operate_time)
VALUES (NEW.id, 'add a user', now());
END $
DELIMITER ;

查看触发器

1
复制代码SHOW TRIGGERS;

删除触发器

1
复制代码DROP TRIGGER IF EXISTS trigger_insert_user;

(完)


参考资料

  • BenForta. SQL 必知必会 [M]. 人民邮电出版社, 2013.
  • 『浅入深出』MySQL 中事务的实现
  • MySQL 的学习–触发器
  • 维基百科词条 - SQL
  • www.sitesbay.com/sql/index
  • SQL Subqueries
  • Quick breakdown of the types of joins
  • SQL UNION
  • SQL database security
  • Mysql 中的存储过程

本文转载自: 掘金

开发者博客 – 和开发相关的 这里全都有

看图理解基于数组的队列

发表于 2019-03-04

前言

推出一个新系列,《看图轻松理解数据结构和算法》,主要使用图片来描述常见的数据结构和算法,轻松阅读并理解掌握。本系列包括各种堆、各种队列、各种列表、各种树、各种图、各种排序等等几十篇的样子。

队列

队列(queue)是一种运算受限的线性表,通过该线性表存储的元素具有顺序性。它的插入操作只被允许在表的后端,而删除操作只被允许在表的前端。进行插入操作的端称为队尾,而进行删除操作的端则称为队头。

队列中的数据以先进先出(First In First Out,即FIFO)的方式进出队列。

队列的实现

队列的实现方式有多种方式,主要是使用不同的结构来存储队列元素,比如使用数组、单向链表、双向列表等。这里看数组方式的实现。

实现的四要素分别是数组、队头(head)指针、队尾(tail)指针以及队列操作集。其中数组用于存放元素,队头指针用于指引队头位置,队尾指针用于指引队尾位置,队列核心操作为入队和出队。此外,队列存放的元素数量不能超过数组的长度。

image

enqueue

将 "the","monster","is","coming"四个字符串分别进行入队操作,

image

image

image

image

dequeue

对队列中进行两次出队操作,

image

image

————-推荐阅读————

我的开源项目汇总(机器&深度学习、NLP、网络IO、AIML、mysql协议、chatbot)

为什么写《Tomcat内核设计剖析》

2018汇总数据结构算法篇

2018汇总机器学习篇

2018汇总Java深度篇

2018汇总自然语言处理篇

2018汇总深度学习篇

2018汇总JDK源码篇

2018汇总Java并发核心篇

2018汇总读书篇


跟我交流,向我提问:

欢迎关注:

本文转载自: 掘金

开发者博客 – 和开发相关的 这里全都有

用最简单的话告诉你什么是ElasticSearch

发表于 2019-03-01

介绍

Elasticsearch 是一个分布式可扩展的实时搜索和分析引擎,一个建立在全文搜索引擎 Apache Lucene(TM) 基础上的搜索引擎.当然 Elasticsearch 并不仅仅是 Lucene 那么简单,下面就介绍ElasticSearch为什么是分布式的,可扩展,高性能,高可用。

什么是搜索

在我们想知道一些信息时,就会使用一些搜索引擎来获取我们想要的数据,比如搜索我们喜欢的一款游戏,或者喜欢的一本书等等,这就是提到搜索的的第一印象,说直白点就是在任何场景下找寻你想要知道的信息,这就是搜索。

  • 现在的搜索也称为垂直搜索
    垂直搜索引针对某一个行业的专业搜索引擎,比如说电商网站,新闻网站,各种app内部等等,他们都是搜索引擎的细分和延伸,在抽取出需要的数据进行处理后再以某种形式返回给用户。

如果用数据库来做搜索会怎么样

例如我们这里有一张商品表,现在我们要搜索”衣服”这个关键字,,执行了 select * from products where product_name like %衣服%,(假设这里没有其他任何提升效率的设置)来进行搜索,或者进行其他字段的匹配,可以分析一下这个方式的缺点。

  • 1,比如说,每条记录的指定字段的数据会很长,比如说**“商品介绍”**这个字段,可能会有几千或者几万个字符,那么搜索的时候就会去这些字符里面进行匹配是否包含要搜索的关键词。
  • 2,这种方式只能搜索到完全包含**“衣服”这个两个字符的记录,但是可能会有一些特殊的情况,某几条记录里面的“衣服”**关键词并不是连续的,可能衣服中间会插入某些字符,这个时候就搜索不出来这些记录了,但是这个商品又是我们希望搜索出来的,这个时候这种方式的弊端就十分明显了。

总的来说用数据库来实现搜索是不太靠谱的,性能会很差。

什么是全文检索

首先需要了解什么是倒排索引?我们这里先上一幅图,里面有4条记录。

现在将这4条记录的内容进行拆分成一些词条,这个过程叫做分词

现在我们得到了这4条记录拆分出来词语,然后将这写词语放到一个列表中,并记录他们的ID,这个分析出来的就是 倒排索引

现在我们输入 生化电影 这个关键词,这个时候搜素引擎将我们输入的内容分词为 生化 和 电影 这两个关键词,然后使用这个两个关键词去倒排索引里面匹配,发现包含 生化 这个关键词的记录有ID为 1,2,3,4这四条记录,包含 电影 这个关键词的有ID为1这条记录,由于ID为1这条记录已经被录入了,所以就被排除在外了, 这时候我们就得到了想要ID为1234这4条记录,同理,如果我们只输入 电影 这个关键词,那么符合条件的只有ID为1这条记录了。
全文检索就是从拆分词语,存入倒排索引,然后分析用户输入的内容,在倒排索引里面进行匹配,这个过程就是全文检索。

什么是ElasticSearch

首先需要知道什么是lucene,lucene它就是一个Java的jar包,里面实现了倒排索引的算法和其他的全文检索相关的东西,ElasticSearch就是对lucene进行了封装,为什么有lucene了还要ElasticSeaearch来干什么呢?首先,当数据量很大的时候,比如有1PB的数据,这个时候数据放在同一台机器上基本就不行了,那么把数据分开来放在多台机器上呢?那就变成分布式了,这个时候数据前端获取数据的时候到底去那一台机器上面去获取数据呢?这个时候就很麻烦了,如果某一台机器宕机了,那么这个机器上的数据就获取不到了,这也就无法保证高可用性了,还有数据存储的时候怎么到底存入那台机器等等,这些都需要人为的处理和维护。这个时候ElasticSearch就应运而生了,它就将lucene这些弊端给完全解决了。

举例一些优点

  1. 高性能,自动维护数据分布到多个节点进行索引的建立,还有搜索请求分布到多个节点的执行。
  2. 高可用,自动维护数据的冗余副本,保证说,一些机器宕机了,不会造成数据的丢失。
  3. 封装了更多的高级功能,以给我们提供更多的高级支持,让我们快速的开发应用,开发更加复杂的应用,复杂的搜索功能,聚合分析的功能,基于地理位置的搜索(比如周围一公里内有几家咖啡厅)等等。
  4. 动态扩容,当我们数据量急剧提升的时候,我们只需要增加机器就行了,比如两台机器存放1.2T数据,那么没台机器存放就是600G,但是如果600G对于服务器的压力太大了,这个时候就需要增加第三台机器,让他们每人负责400G的数据,这个过程不需要人为的去分配,只需要将汲取加入集群中就自动完成。

本文转载自: 掘金

开发者博客 – 和开发相关的 这里全都有

1…877878879…956

开发者博客

9558 日志
1953 标签
RSS
© 2025 开发者博客
本站总访问量次
由 Hexo 强力驱动
|
主题 — NexT.Muse v5.1.4
0%