惯性聚合 高效追踪和阅读你感兴趣的博客、新闻、科技资讯
阅读原文 在惯性聚合中打开

推荐订阅源

B
Blog RSS Feed
Spread Privacy
Spread Privacy
T
Threatpost
C
Cisco Blogs
P
Palo Alto Networks Blog
AI
AI
Cyberwarzone
Cyberwarzone
NISL@THU
NISL@THU
P
Privacy & Cybersecurity Law Blog
G
GRAHAM CLULEY
Simon Willison's Weblog
Simon Willison's Weblog
T
Tor Project blog
Latest news
Latest news
AWS News Blog
AWS News Blog
D
Docker
S
SegmentFault 最新的问题
博客园 - 聂微东
WordPress大学
WordPress大学
Vercel News
Vercel News
S
Securelist
爱范儿
爱范儿
J
Java Code Geeks
Know Your Adversary
Know Your Adversary
S
Schneier on Security
Hugging Face - Blog
Hugging Face - Blog
F
Fortinet All Blogs
Last Week in AI
Last Week in AI
D
DataBreaches.Net
宝玉的分享
宝玉的分享
D
Darknet – Hacking Tools, Hacker News & Cyber Security
MongoDB | Blog
MongoDB | Blog
Engineering at Meta
Engineering at Meta
K
Kaspersky official blog
美团技术团队
博客园 - 叶小钗
阮一峰的网络日志
阮一峰的网络日志
量子位
博客园_首页
Attack and Defense Labs
Attack and Defense Labs
S
Secure Thoughts
Google Online Security Blog
Google Online Security Blog
Application and Cybersecurity Blog
Application and Cybersecurity Blog
Threat Intelligence Blog | Flashpoint
Threat Intelligence Blog | Flashpoint
腾讯CDC
T
Threat Research - Cisco Blogs
雷峰网
雷峰网
有赞技术团队
有赞技术团队
www.infosecurity-magazine.com
www.infosecurity-magazine.com
P
Privacy International News Feed
S
Security Affairs

元视角

.NET 生态下的 Agent 框架选型:从 ReAct 到原生推理 - 元视角 从「能用」到「好用」:LLM 流式响应实现方式的探索之路 - 元视角 当我用 2000 条聊天记录,让 AI 为我画一幅自画像 - 元视角 基于 Supabase 的 AI 应用开发探索 - 元视角 微博 × MCP:社交媒体新玩法解锁 - 元视角 四点钟海棠花未眠 - 元视角 Semantic Kernel × MCP:智能体的上下文增强探索 - 元视角 基于 K-Means 聚类分析实现人脸照片的快速分类 - 元视角 容器技术驱动下的代码沙箱实践与思考 - 元视角 温故而知新:后端通用查询方案的再思考 - 元视角 浅议 CancellationToken 在前后端协同取消场景中的应用 - 元视角 Semantic Kernel 视角下的 Text2SQL 实践与思考 - 元视角 关于 ChatGPT 的流式传输,你需要知道的一切 - 元视角 RAG 的是与非、Rewrite 和 Rerank - 元视角 使用 EFCore 和 PostgreSQL 实现向量存储及检索 - 元视角 基于 LLaMA 和 LangChain 实践本地 AI 知识库 - 元视角 使用 llama.cpp 在本地部署 AI 大模型的一次尝试 - 元视角 如何为 Git 配置多个 SSH Key - 元视角 C# 使用 LibUsbDotNet 实现 USB 设备检测 - 元视角 基于 C# 实现样式与数据分离的打印方案 - 元视角 基于 SVG 的图形交互方案实践 - 元视角 前端视频播放技术概览 - 元视角 温故而知新,再话 Python 动态导入 - 元视角 后 GPT 时代,NLP 不存在了? - 元视角 视频是不能 P 的系列:使用 Milvus 实现海量人脸快速检索 - 元视角 GDI+下字体大小自适应方案初探 - 元视角 小爱音箱集成 ChatGPT 的不完全教程 - 元视角 程序员视角下的三体世界随想 - 元视角 关于 Docker 容器配置信息的渐进式思考 - 元视角 在 Docker 容器内集成 Crontab 定时任务 - 元视角 为你的服务器集成 LDAP 认证 - 元视角 似花还似非花 - 元视角 视频是不能 P 的系列:使用 Dlib 实现人脸识别 - 元视角 浅议分布式链路追踪与日志的整合 - 元视角 关于 Git 大文件上传这件小事 - 元视角 .NET 进程内队列 Channel 的入门与应用 - 元视角 使用 Fody 实现 .NET 的静态编织 - 元视角 .NET Core + ELK 搭建可视化日志分析平台(下) - 元视角 聊一聊前端图片懒加载背后的故事 - 元视角 支持外部链接跳转的 Vue Router 扩展实现 - 元视角 视频是不能 P 的系列:OpenCV 和 Dlib 实现表情包 - 元视角 不得不说的 ASP.NET Core 集成测试 - 元视角 再议 DDD 视角下的 EFCore 与 领域事件 - 元视角 Vue.js 前端项目容器化部署实践极简教程 - 元视角 再见,人间四月天 - 元视角 Python 图像风格化迁移助力画家梦想 - 元视角 利用 ASP.NET Core 中的标头传播实现分布式链路追踪 - 元视角 利用 gRPC 实现文件的上传与下载 - 元视角 七种武器:延迟队列的原理和实现总结 - 元视角 gRPC 流式传输极简入门指南 - 元视角 Envoy 集成 Jaeger 实现分布式链路追踪 - 元视角 浅议非典型 Web 应用场景下的身份认证 - 元视角 gRPC 借助 Any 类型实现接口的泛化调用 - 元视角 分布式丛林探险系列之 Redis 集群模式 - 元视角 分布式丛林探险系列之 Redis 主从复制模式 - 元视角 通过 Python 预测 2021 年双十一交易额 - 元视角 gRPC 搭配 Swagger 实现微服务文档化 - 元视角 SSL/TLS 加密传输与数字证书的前世今生 - 元视角 使用 Python 自动识别防疫健康码 - 元视角 你不可不知的容器编排进阶技巧 - 元视角 ASP.NET Core 搭载 Envoy 实现 gRPC 服务代理 - 元视角 再话 AOP,从简化缓存操作说起 - 元视角 ASP.NET Core 搭载 Envoy 实现微服务身份认证(JWT) - 元视角 ASP.NET Core 搭载 Envoy 实现微服务的监控预警 - 元视角 ASP.NET Core 搭载 Envoy 实现微服务的反向代理 - 元视角 ASP.NET Core gRPC 打通前端世界的尝试 - 元视角 EFCore 实体命名约定库:EFCore.NamingConventions - 元视角 ASP.NET Core gRPC 集成 Polly 实现优雅重试 - 元视角 ASP.NET Core gRPC 健康检查的探索与实现 - 元视角 ASP.NET Core gRPC 拦截器的使用技巧分享 - 元视角 SnowNLP 使用自定义语料进行模型训练 - 元视角 使用 HttpMessageHandler 实现 HttpClient 请求管道自定义 - 元视角 ABP vNext 的实体与服务扩展技巧分享 - 元视角 ABP vNext 对接 Ant Design Vue 实现分页查询 - 元视角 源代码探案系列之 .NET Core 跨域中间件 CORS - 元视角 源代码探案系列之 .NET Core 限流中间件 AspNetCoreRateLimit - 元视角 源代码探案系列之 .NET Core 并发限制中间件 ConcurrencyLimiter - 元视角 通过 EmbededFileProvider 实现 Blazor 的静态文件访问 - 元视角 低代码,想说爱你不容易 - 元视角 记一次失败的 ThoughtWorks 面试经历 - 元视角 从 C# 1.0 到 C# 9.0,历代 C# 语言特性一览 - 元视角 通过 Python 分析 2020 年全年微博热搜数据 - 元视角 基于 Python 和 Selenium 实现 CSDN 一键三连自动化 - 元视角 使用多线程为你的 Python 爬虫提速的 N 种姿势,你会几种? - 元视角 实现网页长截图的常见思路总结 - 元视角 温故而知新,由 ADO.NET 与 Dapper 所联想到的 - 元视角 视频是不能 P 的系列:OpenCV 人脸检测 - 元视角 作为技术宅的我,是这样追鬼滅の刃的 - 元视角 使用 Python 抽取《半泽直树》原著小说人物关系 - 元视角 厉害了!打工人用 Python 分析西安市职位信息 - 元视角 使用 dotTrace 对 .NET 应用进行性能分析与优化 - 元视角 一道 HashSet 面试题引发的蝴蝶效应 - 元视角 基于选项模式实现.NET Core 的配置热更新 - 元视角 Dapper.Contrib 在 Oracle 环境下引发 ORA-00928 异常问题的解决 - 元视角 .NET Core 中对象池(Object Pool)的使用 - 元视角 利用 MySQL 的 Binlog 实现数据同步与订阅(下):EventBus 篇 - 元视角 利用 MySQL 的 Binlog 实现数据同步与订阅(中):RabbitMQ 篇 - 元视角 利用 MySQL 的 Binlog 实现数据同步与订阅(上):基础篇 - 元视角 记一次从已损坏的 Git 仓库中找回代码的经历 - 元视角 .NET Core 原生 DI 扩展之属性注入实现 - 元视角
由 DBeaver 与 PL/SQL 引发的数据库吐槽 - 元视角
飞鸿踏雪 · 2019-04-19 · via 元视角

因为工作中需要同时面向 MySQL、Oracle 和 SQLServer 三种数据库进行开发,所以,大概从去年国庆节开始,我开始使用一个开源的数据库管理工具——DBeaver。

使用这个工具的初衷,是因为我不想在同一台电脑上安装三个客户端工具,尤其是 Oracle 和 SQLServer 这种令人恐惧的、动辄需要重装系统的应用程序。我不想再使用类似 Navicat 这样的软件,因为它的画风像是上个世纪 VB6.0 的产品一样,同理,我不喜欢用 PL/SQL,因为我每次都要瞪大眼睛,在它狭窄而拥挤的画面上找表、找视图,更有甚者,有时要去找触发器、找存储过程。直到我同事给我发了一个几十 M 的文档,我突然间意识到,这货居然还要安装 Oracle 的客户端,配置数据库连接要手动去改配置文件,我一点都不喜欢 PL/SQL。

除了这三种经典的关系型数据库,我们还会用 Memcache 和 Redis 这样的内存数据库,Mongodb 这样的非关系型数据库,所以,我希望有一个统一的入口来管理这些连接,毕竟我身边的同事会使用三种以上的工具,譬如 SqlyogPL/SQLSQLServer 等来处理这些工作,恰好 DBeaver 可以满足我 80% 的工作需要。目前,DBeaver 企业版支持关系型数据库和非关系型数据库,而社区版仅支持关系型数据库。

可最近在写 Oracle 环境的触发器(存储过程和触发器都是万恶之源)时,我发现 DBeaver 和 PL/SQL 在面对同一段 SQL 脚本时,居然因为一点点语法上的差异而不兼容,这让我内心深处不由得想对 Oracle 吐槽一番。这是一个什么样的 SQL 脚本呢?我们一起来看下面的例子:

CREATE OR REPLACE TRIGGER "TRI_SYNC_ITEM_VALUE"
  BEFORE DELETE ON "or_line"
  FOR EACH ROW
DECLARE
  v_item_value NUMBER(18,6);
BEGIN
  SELECT ITEM_VALUE INTO v_item_value FROM "order_info" WHERE ORDER_GID = :OLD.ORDER_GID;
  v_item_value := v_item_value - :OLD.PACKAGE_COUNT * NVL(to_number(:OLD.OL_UDF7),0);
  IF v_item_value < 0 THEN
      v_item_value:= 0;
  END IF;

  UPDATE "order_info" SET ITEM_VALUE = v_item_value WHERE ORDER_GID = :OLD.ORDER_GID;
END "TRI_SYNC_ITEM_VALUE";

INSERT INTO "sys_upgrade_history"(UPGRADE_TYPE,VERSION_NO,UPDATE_DATE,REMARK) VALUES('版本更新','10005',SYSDATE,'Normal');

这是实际业务中编写的一个简单触发器脚本,我们通常的编写习惯是,在写完触发器或者存储过程以及函数后,会在升级历史中插入一天新纪录,所以,这个脚本实际上由两部分组成。如果这段脚本分两次执行,那么在 DBeaver 和 PL/SQL 中效果是一样的。可如果我们希望一次执行整个脚本,根据 PL/SQL 的规范,一个 PL/SQL 脚本由如下结构组成:

DECLARE
    [声明部分]
BEGIN
    [过程部分]
END;
/

这个时候,我们就要在这两部分脚本间增加一个分隔符——/。可尴尬的是,这种写法在 DBeaver 中是无法编译执行的,因为它认为**/**是个无效的 SQL 关键字。我一直疑心这是个 Bug,因为 Github 上曾有人提过类似的 Issue,作者回复说,DBeaver 并没有完全实现 PL/SQL 语法的解析,而最近更新的 6.0 版本中提到:对 Oracle 环境的存储过程编译进行了强化。博主尝试升级到最新版本,发现这个问题依然存在,哪怕用 Ctrl+Enter 来执行一样会报错,于是我想从这件事吐槽下某数据库,从哪里说起呢,就从 PL/SQL 说起吧!

标准与私货

我想一开始学习 SQL 语法的时候,大家绝对不会想到,看起来和谐而统一的结构化查询语言,其实是貌合神离。为什么这样说呢?因为我真的不知道,一个时间函数居然可以有 SYSDATE、NOW()和 GETDATE()三种写法,我更不知道,有一天会因为不知道 ROWNUM 而被面试官鄙视,更不必说每种数据库都会定义一两种不一样的数据类型,这东西号称是有一个标准吗?比如 SQL92/99 这个标准定义了 DML(数据操作语言)、DDL(数据定义语言)、DCL(数据控制语言)和 TCL(事务控制语言)四种分类,所以,SQL 的定位其实更接近于交互式命令行,它是命令式的查询语言,而非过程式的声明语言。

可在标准化进程缓慢的大背景下,每一家数据库厂商都在往自家产品里夹藏私货,以甲骨文为首的 Oracle 发展出了 PL/SQL、以微软为首的 SQLServer 发展出了 T-SQL。其实,我很能理解这种标准跟不上时代发展需要的阵痛,就像我们的 Web 领域直到 10 年前后才提出了 HTML5 标准,在此之前,我们为不同的浏览器的兼容性煞费苦心,兼容 IE8 与否甚至成为了评价技术好坏的一个隐性标准,可说句实话,浏览器的 Bug 难道不应该让浏览器厂商来修复吗?关前端工程师什么事?同样的,数据库间的差异,让我们的脚本失去了可移植性,触发器、存储过程这种严重依赖数据库的东西,一旦更换了数据库,基本等于要重头再写一遍,如今的小程序让 Web 变成信息孤岛,甚至 Chrome 正在变成下一个 IE,这就是所谓“屠龙少年战胜恶龙,自身亦化为恶龙吗”?

这种不统一带来的弊端就是,我们永远写出可以完美“跨”数据库的 SQL,现在跨平台基本成为了大家的共识,因为操作系统间的差异越来越小,以我个人为例,我使用的大多数软件都可以找到对应的 Linux 版本,这样做的好处是,我可以在无差别地从 Windows 切换到 Linux。可现在,我们必须在 MySQL 里使用 VARCHAR、而在 Oracle 里使用 NVARCHAR,而在 SQLServer 里又要使用 NVARCHAR2,可明明它们都是表示一样的东西啊,类似的还有 MediumText 和 CLOB,是不是起一个不一样的名字会显得与众不同呢?更不必说在 DDL 中表约束相关的语法存在差别了。我被告知 Oracle 脚本中表名要用双引号括起来,理由是 Oracle 区分大小写,加上双引号就可以让它忽略大小写,忽略大小写不应该给 Oracle 一个设置吗?为什么要让我再写个多余的双引号呢?诸如此类,举不胜举。

SQL 是个好 DSL 吗

SQL 标准定义的 SQL,就是一个以集合论为基础的结构化查询语言,它天生适合的场景就是,你在命令行中输入 SQL 语句,然后它去执行你输入的 SQL 语句,它就像我们大多数情况下使用的交互式命令行,不然,为什么 MySQL 要提供命令行版本,主流的数据库管理工具都提供了输入 SQL 语句的窗口。可我们同样能意识到,SQL 的表达能力有限,它无法表达顺序、条件、循环这种基本的程序结构,所以,数据库厂商几乎都对 SQL 标准进行了扩展,像 PL/SQL 和 T-SQL 中都提供了这些语法,进而催生出函数、触发器、存储过程一系列“万恶之源”,可从编程语言的角度来看,SQL 算是个好 DSL 吗?

SQL 试图从编程语言中获得“灵感”的思路是正确的,但总给人一种买椟还珠的感觉,譬如使用大量的英文关键字来作为保留关键字,可你很难想象,像 GROUP BY 和 ORDER BY 这样的关键字,居然可以保留中间一个甚至多个空格,既然是关键字,为什么不选择一个单词,而选择一个组合词呢?这个世界上用 Begin 和 End 的编程语言,我使用过的有 Pascal 和 Basic,但现在我几乎不会再用它们,为什么呢?因为使用花括号({})更符合这个世界的发展趋势,你看 Python 居然用缩进代替花括号,是打算时刻用游标卡尺写代码吗?

全世界都默认用分号作为一个语句的结束,那么,当多个语句放在一起的时候,直接相互间用分号隔开,编译器或者解释器都能识别,就算不喜欢写分号的 JavaScript,最新的标准提案里不还是建议要写吗?可为什么到了 PL/SQL 这里,明明已经用分号作为结束符了,偏偏还要再用一个/作为分隔符。我们都知道/会被当做是注释的开始,那么如果我在 PL/SQL 里恰好在 End;后写上一句/,你告诉我,这到底代表什么意思?明明像&&、||、^等这样的运算符,都是有固定含义,并且大家所有编程需要都默认了这个原则,可偏偏有人用||来连接字符串,你告诉我,用+不好吗?就像从小到大,÷都会被认为表示一个除法运算,结果突然有一天,有人用这个符号来表示加法运算,你说你是不是有种被当做傻子的感觉。全世界都用=表示赋值运算,结果 PL/SQL 自作聪明地搞了个:=,我想说,你真的考虑过使用者的体验吗?

你甚至连分页、排序、分组这种事情,都无法在不同的数据库上获得一致的书写体验,读取指定数目的数据库记录,居然要纠结用到底用 Limit 还是 Top,像 Select Into 这样把指定列存储到指定变量中的操作,居然要求使用者来限制结果集的数目,从函数的角度来看,返回的必然是结果集中的一个元素,只有这样才可以赋值给指定的变量,可问题是存在多条记录的时候,你必须用游标去循环读取,而不能像大多数编程语言一样,直接 Map()到一个类型上然后 ToList(),可能是我对 SQL 的要求太高了吧,毕竟它就是个面向过程的语言,OO 不 OO 的没那么重要,可明明你可以抛出异常啊,可以对字符串做截取啊正则啊,可以在控制台里输出日志啊,可以调用各种有的没的的内部函数啊,elsif 可能是因为 e 不发音,就像 usr 绝对不是拼写错误……

Python 的缩进虽然为人所不齿,但它至少和大部分编程语言一样,单独一行的程序语句和由多行程序组成的程序块之间,并不需要明显的分割符号。可 MySQL 需要用 DELIMITER $$这种奇怪的符号,PL/SQL 需要用/这种奇怪的符号,SQLServer 需要用@这种奇怪的符号,还有大名鼎鼎的虚拟表 DUAL。也许这些东西写多了就可以记住,就像我现在可以分清 SYSDATE、NOW()和 GETDATE(),可它带来的问题是什么呢,大多数的触发器、存储过程、函数都是没有移植性可言的,很多年前,我们讲设计模式,最喜欢觉的例子就是,如果项目发生变动,需要更换数据库,我们要怎么设计能不改动代码,现在看起来,当时还是太天真了,真要换了数据库,估计就是重新做了,敢把全部业务写到数据库里,Web 就做一个展示层的项目,有生之年应该是不会换数据库啦!

多元与统一

这个世界的离奇之处在于,人们一边渴望在标准的庇护下幸福生活,又一边渴望可以超脱标准去发展独立的个性,如你我所见,多元与统一,构成了这个世界永恒的旋律,或许是因为那句名言——没有永远的敌人,只有永远的利益。可对比 Web 的标准化与 SQL 的标准化,我们却看到了截然不同的场景,虽然 Chrome 浏览器市场份额的不断提高,加上微软、Mozilla 等“浏览器巨头”一起推动,HTML5 和 CSS4,让大量的工作得到了简化,尤其像 WebSocket、Drag&drop、Canvas 等 API 的推出,这带来的好处是什么呢?大家不再去重点关注浏览器的兼容性问题,各种天花乱坠的炫酷特效不再通过 JavaScript 去控制。一个标准的 API + 一个支持降级的 profily,基本就可以覆盖到主流的浏览器,就算有小程序这种偏离标准的解决方案,回顾近几年整个前端领域的趋势,可以说,一切都在向着好的方向发展。

可数据库领域发生了什么,依稀记得甲骨文和 Google 因为 Android 使用了 Java 而官司连连,Google 不得不推出一种新的基于 JVM 的语言——Kotlin;依稀记得甲骨文在开源社区的强烈反对下收购了 MySQL,社区不得不继续维护 MySQL 的开源分支——MariaDB。从这两件事情,我完全提不起对甲骨文这家公司的好感,虽然大家都说 Oracle 品质卓越,可实际使用下来,经常出问题的 Oracle。从 LAMP 时代开始,MySQL 就以其免费、轻量的特点广泛应用在互联网产品中,直至今天有大量的云产品使用着 MySQL,而 Oracle 和 SQLServer 则被更多地使用在私有部署的场景中。虽然,我承认把数据掌握在自己手里会放心些,可当你没有能力去维护这些东西时,付出的时间和精力远远要比这多。甲骨文收购了那么多公司的产品,时至今日,对整个行业的标准化有什么推动呢?Oracle 数据库依然难装、难用,PL/SQL 同样难用得要命,可我们这世界一直都很奇怪,最流行的偏偏未必是最好的,据说 Oracle 的代码写得非常差,开发人员表示不会在为它继续开发新功能。

可能有时候,我们完全说不出来,一件东西是好还是坏,就像 JavaScript 能在前端开发流行,是因为没有其它的选择,你说这门语言没有缺点吗?当然有,JavaScript 里各种“骚操作”和“黑科技”,甚至吐槽三天三夜都说不完。同样,还有 Python 这门语言,大家都觉得它的解释器慢腾腾的,动态语言遇上大型项目简直就是火葬场,还有神来之笔—— 通过缩进来代替花括号。我最终还是在 PL/SQL 里执行了我的脚本,只要我在使用 DBeaver 的时候,人肉地区分/前后的 SQL 语句就可以了。果然,我骨子里还是一个不喜欢写 SQL 脚本的人,因为我认为这么别扭的东西简直不能称之为脚本,你看看 Lua,再看看 Python,有哪一门脚本语言有 SQL 脚本这样别扭呢?数据库对我而言,就是一个存取数据的“潘多拉魔盒”,索引啊,触发器啊,数据库任务啊,执行计划啊,存储过程啊,难道不属于暴露了太多细节给用户吗?我天天用这个数据库,我每天用哪些表,我每天用哪些字段,你作为一个成熟的数据库了,居然不能自己去解决这些问题,我对你很失望啊,请记住,程序员比任何人都喜欢偷懒。