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

推荐订阅源

T
Tenable Blog
K
Kaspersky official blog
cs.AI updates on arXiv.org
cs.AI updates on arXiv.org
T
The Exploit Database - CXSecurity.com
Cisco Talos Blog
Cisco Talos Blog
P
Palo Alto Networks Blog
Latest news
Latest news
cs.CL updates on arXiv.org
cs.CL updates on arXiv.org
C
CXSECURITY Database RSS Feed - CXSecurity.com
P
Privacy International News Feed
The Hacker News
The Hacker News
T
Tor Project blog
www.infosecurity-magazine.com
www.infosecurity-magazine.com
C
Cisco Blogs
阮一峰的网络日志
阮一峰的网络日志
Recent Commits to openclaw:main
Recent Commits to openclaw:main
博客园_首页
N
News and Events Feed by Topic
W
WeLiveSecurity
罗磊的独立博客
GbyAI
GbyAI
T
Troy Hunt's Blog
Y
Y Combinator Blog
Recorded Future
Recorded Future
The Cloudflare Blog
TaoSecurity Blog
TaoSecurity Blog
爱范儿
爱范儿
美团技术团队
Attack and Defense Labs
Attack and Defense Labs
C
Check Point Blog
Engineering at Meta
Engineering at Meta
Cyberwarzone
Cyberwarzone
Cyber Security Advisories - MS-ISAC
Cyber Security Advisories - MS-ISAC
F
Fortinet All Blogs
The GitHub Blog
The GitHub Blog
奇客Solidot–传递最新科技情报
奇客Solidot–传递最新科技情报
Apple Machine Learning Research
Apple Machine Learning Research
Know Your Adversary
Know Your Adversary
AWS News Blog
AWS News Blog
D
DataBreaches.Net
Recent Announcements
Recent Announcements
K
KPMG report finds enterprise disconnect between AI and its ROI | CIO
M
MIT News - Artificial intelligence
Webroot Blog
Webroot Blog
Security Latest
Security Latest
T
Tailwind CSS Blog
V2EX - 技术
V2EX - 技术
aimingoo的专栏
aimingoo的专栏
S
Security @ Cisco Blogs
Exploit-DB.com RSS Feed
Exploit-DB.com RSS Feed

元视角

.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 脚本这样别扭呢?数据库对我而言,就是一个存取数据的“潘多拉魔盒”,索引啊,触发器啊,数据库任务啊,执行计划啊,存储过程啊,难道不属于暴露了太多细节给用户吗?我天天用这个数据库,我每天用哪些表,我每天用哪些字段,你作为一个成熟的数据库了,居然不能自己去解决这些问题,我对你很失望啊,请记住,程序员比任何人都喜欢偷懒。