Leon Ding Blog - 数据库
type: channel
link: https://blog.leonding.me/category/database.html
description: 收录关于数据库技术文章
items: 计算复杂度与并发冲突对事务处理性能的耦合影响分析
items: 基于 LogStructuredFS 的 NoSQL 设计与实现
items: 在 Ubuntu 上安装 MySQL8.x 版本
items: 一个合格的站长应该具备的能力
items: 在 RHEL9 上安装 MySQL 社区版本
items: 免费体验 Oracle Database 新特性
items: PostgreSQL 简易教程
items: 日志结构在 InnoDB 应用
items: 数据库中的触发器
items: InnoDB 数据页的设计

计算复杂度与并发冲突对事务处理性能的耦合影响分析
type: item
date: 2026-04-19T18:02:00+08:00
description: 本文所讨论的方案，来源于笔者在 UrnaDB 数据库设计过程中的问题抽象与实践总结。事务抽象事务 Transaction 是数据库系统中的基础机制，Transaction 用于保证数据操作时的正确性与可靠性而提出的一组核心约束。事务必须具备的 ACID 四个核心特性，ACID 的缩写缩写源自于：原子性 Atomicity、一致性 Consistency、隔离性 Isolation 和持久性 Durability。ACID 约束确保了一组数据库操作，即使在运算故障时也能保持数据的一致性和可靠性，实现数据从一个合法状态向另一个合法状态的转换，使其执行运算过程中没有异常状态的产生。目前工业级主流数据库产品 MySQL、PostgreSQL、Redis、MongoDB、DynamoDB 都有对 Transaction 特性的支持，但具体实现细节有着天壤之别。同样按照数据库类型分类：关系型数据库如 PostgreSQL 的事务实现通常基于 SQL 标准并完整支持 ACID 特性；而 NoSQL 数据库如 Redis、DynamoDB 的事务机制则没有统一标准，通常根据系统设计在原子性、隔离性等方面提供不同级别的支持，部分系统甚至仅支持单操作原子性。关系型数据库的 SQL 语言表达能力强大，SQL 可以通过复杂的 JOIN、子查询、聚合函数、窗口函数等表达复杂的业务逻辑，一条 SQL 语句就能完成多表关联、分组统计、排序分页等操作。使用者就需要花费大量的时间来学习和掌握使用 SQL 语法包括复杂的子查询、窗口函数、索引原理、执行计划分析、事务隔离级别、锁机制、MVCC 原理、查询优化技巧、性能调优、高可用架构等。[scode type="blue"]关系型 SQL 数据库在技术实现层面同样存在较高门槛，其核心组件如 SQL 解析器、查询优化器和执行引擎的设计与实现复杂度极高，不仅需要长期的工程投入，还伴随着持续的维护成本，这种复杂性需要多年经验和多学科知识才能胜任，对于小规模团队或独立开发者而言，通常是不现实的选择。[/scode]NoSQL 相较于关系型 SQL 数据库在实现层面通常更加简化，通过放弃通用 SQL 标准，转而采用自定义 API 来定义数据访问与事务语义。NoSQL 系统无需实现 SQL 解析器、查询优化器以及执行引擎等复杂核心组件，从而显著降低了系统实现的复杂度，这使得 NoSQL 数据库对于小规模团队或独立开发者而言更加可行。[scode type="blue"]NoSQL 也有相应的学习成本，每换一款新的 NoSQL 数据库，使用者就得重新学习对应的新 API 来操作数据库。例如 Redis 和 MongoDB 数据库产品的设计还需要使用者去学习 Lua、JavaScript 语言，引入特定语言的设计会为使用者增加额外负担和学习成本。[/scode]🛡️ 安全性本节将以数据库 “安全性” 为切入点，对比 UrnaDB 与其他竞品数据库在相同安全问题下的设计思路与解决方案。传统 SQL 关系型数据库在生产环境中，安全层面的风险是不可忽略的要素，其中 SQL 注入作为一种典型的应用层安全漏洞，长期以来是开发者需要重点防范的问题之一，由于缺乏内建防护机制，使用者需要在应用层额外处理，例如在 Java 中通过 Prepared Statement 对 SQL 语句进行预编译。同样 NoSQL 数据库也存在安全层面的风险，例如 Redis 通过内置 Lua VM 虚拟机来实现事务逻辑或扩展能力如脚本执行、存储过程等，虽然增强了灵活性，但也带来了额外的复杂性和潜在的安全风险。[scode type="blue"]Redis 在运行期间支持通过 SDK 或 API 动态加载并执行用户自定义的 Lua 脚本，这依赖于内置 Lua VM 提供脚本执行能力。该设计在增强可编程性的同时，也对执行环境的隔离性与资源控制提出了更高的要求，如果宿主机（这里是指 Redis 数据库）缺乏严格的沙箱机制与执行限制，可能引发诸如沙箱逃逸、长时间阻塞、如死循环，以及内存滥用等问题。[/scode]下文将通过具体恶意代码示例，分析这些潜在的安全风险，如下：-- 死循环攻击 while true do redis.call('GET', 'key') end -- 内存炸弹 local t = {} for i=1,10000000 do t[i] = string.rep("x", 1000000) end -- DoS 攻击 for i=1,1000000 do redis.call('KEYS', '*') end上述任意代码，如果被恶意用户提交到 Redis 的 Lua VM 中执行，一定会导致系统行为不可预测，从而影响整体稳定性与调试难度，例如脚…

基于 LogStructuredFS 的 NoSQL 设计与实现
type: item
date: 2024-12-22T23:45:00+08:00
description: NoSQL 的抽象数据库系统的核心作用在于高效地存储和管理数据，并支持从海量信息中快速检索所需记录。传统的 SQL 关系型数据库在数据规模持续增长的情况下，容易遭遇单机性能瓶颈。目前提升性能的主流做法多依赖垂直扩展增强单机硬件能力，而非水平扩展增加节点以线性扩容。由于其架构限制，关系型数据库在弹性扩展性方面存在障碍，难以充分适应多节点分布式环境。同时为满足 ACID 事务特性，这类系统在支持分布式事务处理时也面临一定的技术挑战。相比之下，NoSQL 数据库更适用于大规模数据存储和高吞吐、低延迟的应用场景。由于 NoSQL 不受固定数据结构和关系的限制，具有更灵活的扩展能力，并且支持节点的动态扩展，从而更好地适应分布式环境。无论是关系型 SQL 数据库还是 NoSQL 数据库，它们都有一个共同的核心组件存储引擎，这是数据库设计与实现的关键核心部分。目前数据库存储引擎可以分为两大类，一类是基于内存的 （In-Memory）实现，例如 Redis 和 Memcached 这类的将数据全部存储在内存中；另外一种则是类似于 MySQL 的 InnoDB 和 LevelDB 这种将数据存储在磁盘中。两者都有各自的优势和劣势，基于内存的存储引擎通常存储的数据量较少，但其访问速度往往比基于磁盘的存储引擎快好几个数量级，而基于磁盘的存储引擎访问速度较慢，但存储数据量要比内存引擎多几个数量级。通常内存的价格要比磁盘的贵很多，这也是在选择存储硬件时要考虑的成本问题。存储引擎的开发者需要权衡读写性能和硬件成本的同时，重点在于如何高效存储数据和检索数据。数据库的存储引擎相当于在操作系统文件系统之上，建立的一套用于数据组织、存储与查询的逻辑管理层，最终实现是依赖于操作系统的文件系统 API 接口的，最终数据是被持久化到磁盘文件中保存，这个就对应着 ACID 中的 Durability 数据持久性。持久性（Durability）是存储引擎最为至关重要的功能实现，目前很多基于内存版本的 NoSQL 数据库例如 Memcached 和 Redis 在这方面做的就比较差，在 NoSQL 服务器运行过程中突然崩溃断电就会导致数据没有被持久化存储的情况，从而导致数据丢失影响到上层的业务程序。[scode type="yellow"]所谓的持久性就是如何将计算机运算的结果持久化存储在一些物理介质中；持久性是指系统能够将状态变更的结果可靠保存，使其在系统重启或故障后仍然能够恢复。[/scode]存储是现代计算机最核心的功能之一，计算机中的程序和文件以数据块的形式保存在各种存储介质中，主要可分为持久化存储和非持久化存储两类。非持久化存储通常指内存（如 RAM），其特点是在断电或关机后，数据会被清除。尽管容量通常小于持久化存储，但其高速读写性能使其广泛应用于程序运行过程中的数据处理和临时缓存。相比之下，持久化存储如硬盘、固态硬盘、DVD、磁带等，能够在断电后保留数据，适用于长期保存程序和文件信息。鉴于数据可能存储在多种物理介质（如磁盘、SSD、光盘等）上，主流的操作系统需要提供一致的抽象接口，以屏蔽底层硬件差异，使用户能够高效、统一地进行文件的读写、创建与删除等操作。当前主流操作系统均实现了完整的文件系统，开发者可以通过如 POSIX 这样的标准 API 接口来访问和管理存储设备，而无需关心具体设备的物理实现方式。尽管操作系统的文件子系统通过标准如 POSIX 提供了对存储设备的访问能力但仍存在一些限制；例如存储性能受限于设备的物理读写速度，同时文件系统通常采用页缓存 Page Cache 或写缓冲机制，这意味着数据写入操作并不会立即持久化到磁盘，一旦系统发生故障或意外断电，可能会导致数据丢失，影响系统的可靠性。基础架构综上所述，针对这些场景的问题 UrnaDB 采用了基于 Log-Structured Megre Tree 日志结构化文件系统的存储引擎实现，存储引擎会以 Append-Only Log 的方式将所有的数据操作写入到数据文件中。同时 UrnaDB 为了高速查询检索数据记录，存储引擎会将数据记录索引信息全部保存在内存中，从而实现高效快速的查询目标数据记录。这样的设计的好处是能以磁盘最大写入性能进行写入数据，并且还能减少读取磁盘索引所需要的时间，通过一次索引定位来读取数据记录，写入和查询流程图：其核心持久化机制基于预写日志 Write-Ahead Logging 简称 WAL ，在对数据执行任何操作前，都会先将操作记录写入 WAL 日志文件。WAL 文件不仅承担持久化的角色，也作为主要的数据存储载体。在数据库进程崩溃后，只需从 WAL 日志中顺序读取各条 Segment 记录，即可高效恢复内存中的索引结构。在 UrnaDB 中对这些 WAL 数据文件有一个统称为…

在 Ubuntu 上安装 MySQL8.x 版本
type: item
date: 2024-06-23T19:29:00+08:00
description: 在 MySQL 8.x 带来了许多新特性和改进包括 JSON 数据类型和 Java Script 作为 JavaScript Shell 支持，可以直接使用 JavaScript（JS）作为数据库查询语言或存储过程的语言，更好的性能和安全性，例如连接过程中出现的 Public Key Retrieval is not allowed 错误提示，这些都是新版本特性。本文将记录完整的基于 Ubuntu 最新版本中安装 MySQL8.x 版本的过程。安装和配置在安装 MySQL8.x 之前可以尝试先更新一下操作系统和软件包，执行下面命令可以更新：sudo apt update && apt upgrade -y更新系统和软件包版本需要一点时间，这个过程取决于你网络和所使用的 apt 软件包数据源地址，数据源配置文件路径 /etc/apt/sources.list。[scode type="yellow"]如果不确定系统中是否安装其他版本的 MySQL 版本，可以先执行清理命令。清空完成之后，提供 apt search mysql 包管理工具搜索最新的 MySQL8.x 软件安装包，最后通过 apt install 执行进行安装。[/scode]sudo apt remove mysql* sudo apt autoremove sudo apt autoclean清空完成之后提供 apt 所提供的 MySQL 软件包进行安装：sudo apt search mysql sudo apt install mysql-*安装完成之后，可以使用 sudo mysql_secure_installation 来初始化 root 用户的密码，这是 MySQL 新版本所提供的初始化数据库程序：sudo mysql_secure_installation[scode type="yellow"]如果需要让 MySQL 服务器能被远程的用户提供客户端访问，需要配置修改服务器所绑定监听的 ipv4 地址，默认是本地环路地址，在这个 /etc/mysql/mysql.conf.d/mysqld.cnf 中找到 bind-address 的值修改它为 0.0.0.0 即可。[/scode]# changed. Ref https://dev.mysql.com/doc/refman/8.0/en/server-system-variables.html#sysvar_tmpdir # tmpdir = /tmp # # Instead of skip-networking the default is now to listen only on # localhost which is more compatible and is not less secure. bind-address = 0.0.0.0 #这一行 ✅ mysqlx-bind-address = 127.0.0.1通过上面的配置目前 root 还是只能通过本机的客户端进行连接，有远程登录需求的可以配置一个远程用户并且允许远程登录，并且设置初始化密码，我这里开放的是 root 用户，执行下面命令：GRANT ALL PRIVILEGES ON *.* TO 'root'@'%' IDENTIFIED WITH mysql_native_password BY '新密码' WITH GRANT OPTION;解决安全连接问题在 MySQL8.x 版本中如果要使用正常的密码连接到 Server 中连接过程中出现的 Public Key Retrieval is not allowed 错误提示，这个是新特别特意到安全功能增强，如果要使用这种方式连接必须要配置 SSL/TLS 密钥对进行连接。[scode type="yellow"]指定用户使用的密码验证方法 mysql_native_password ，这让 MySQL 使用的默认身份验证插件，它支持基于用户名和密码的常规身份验证机制，执行下面命令使用默认密码验证方式进行连接：[/scode]ALTER USER 'root'@'%' IDENTIFIED WITH mysql_native_password BY 'new password';执行上面的 SQL 之后，刷新一下 MySQL 授权表：flush privileges;最后记得在服务器上允许开放 3306 端口数据包的连接。

一个合格的站长应该具备的能力
type: item
date: 2024-01-01T05:10:00+08:00
description: 现在注册一个域名和购买一台服务器部署一个 Web 网站非常容易，现在流行的 Serverless 架构更是连服务器都不需要购买，直接在各个云厂商的后台点点鼠标就可以部署一个在线的 Web 程序，虽然相比传统建站的方式简单很多，但是也有考虑一些很基础因素和配置，这篇博文我讲以博客系统为例子讲解如何维护一个 Web 服务程序。现在有很多流行的博客系统，例如静态的 Jekyll、Hugo、Hexo，动态以 PHP 为首的 WordPress 系统，WordPress 有着完善的生态系统是世界上目前二次开发使用最多的 Web 系统，静态的博客可以部署在 Github Pages 和 Vercel 等，直接将编写好的网站源代码上传到免费的空间即可。而对于动态博客系统还说，需要 LNMP 环境的支持才能正常运行，现在也流行直接购买已经部署好的数据库环境和运行环境来跑博客，对于动态博客系统还是数据才是最重要的，这篇博客将介绍一些方法一名合格的站长该如何运维自己博客。基础防御[scode type="yellow"]博客系统存放在主机之上通过互联网提供世界各地访问，这些访客里面有普通人也有一些黑客，黑客可以利用博客程序的漏洞例如 XSS 和 SQL 注入漏洞来破坏程序，这对于博客系统安全有了新的要求。普通的静态博客完全可以不用管这些，但是对于 PHP 这类的动态脚本语言来说更需要关注，因为 PHP 是一门动态语言如果被黑客注入了恶意代码会导致程序数据泄露，严重的会破坏程序的运行，目前场景的作为就是在博客核心代码里面对用户输入的数据进行过滤或者做合法性校验，或者在 PHP 使用 disable_functions 配置项来禁用一些敏感函数，来保证程序安全性。[/scode]如果考虑不是在应用程序嵌入代码来做防御，可以使用一些带有代码注入攻击检查防御的网关产品，博客作为一种 Web 程序提供的服务是要经过 HTTP 网络请求的，可以考虑采用带有防御型的网关产品。例如 Cloudflare 它可以提供基于 SASE 和 SSE 平台，将网站的域名的 DNS 接入到 Cloudflare 并且使用它来解析服务网站，可以改善安全和韧性减少攻击参数传入到主程序中，国内也有长亭科技的 SafeLine 的 WAF 产品。Cloudflare 并且提供 CDN 服务，可以隐藏真实的主机 IP 地址，也能确保域名和 DNS 设置的安全，防止域名劫持和 DNS 污染攻击。对于 HTTP 协议来说才有基于 TLS 安全层的 HTTP 协议，启用 SSL/TLS 证书，确保所有数据在传输过程中都是加密的，防止 Cookie 和 Session 信息被截取或篡改。对于基于使用 LNMP 作为基础设施运行的系统，要考虑是不是使用的长期支持版本的软件，如果使用的不是长期支持的版本的软件，如果出现了漏洞，对于这些已经不维护的基础软件会很麻烦，不能得到相应的补丁更新。操作系统环境还是建议使用基于 RHEL 系列的 Linux 发行版本，现在 CentOS 已经是最优的，我推荐使用 Rocky Linux 这个系统是 CentOS 创始人创建的一个克隆 RHEL 的开源操作系统，是在 RHEL 的基础上进行重新编译的，该系统在很大程度上兼容 RHEL 版本的，并且可以通过相同的方式安装 RHEL 软件包，还能得到长期的更新支持。当整个基础环境搭建完成之后，强烈不建议使用 root 账号去运行网站的基础设施，更应该创建一个专用的用户组和用户来运行对于的软件，这样即使黑客绕过前面的基层防护，也没有更高的权限去访问其他的数据和程序。服务器防火墙软件还是推荐使用目前主流的 firewalld ，这是 REHL 企业级的服务器管理软件，相比一些老的 iptables 来管理出入站流量复杂的操作更容易操作。不要开放不必要的端口，一个 Web 服务器最多开发基础的端口 80 、 443 、22 端口，其他端口可以一律禁止外部访问使用，最好的方式使用白名单 IP 的方式进行访问控制，通过跳板机进行管理。对于 root 用户来说应该不使用 root 密码进登录，而使用基于 SSH 的非对称密钥进行登录，可以将登录电脑和被管理的服务器进行绑定，将公钥上传到远程服务器来进行登录管理。此外此种方式还要考虑私钥的安全性，不要将私钥公开，避免在页面或日志中输出敏感信息，如数据库凭据、API 密钥等。数据备份在线系统的核心是数据，这些数据一般会被存储在 MySQL 这些关系型数据库中，目前主流的动态博客系统数据都是存储在关系数据库中，前面提到黑客通过一些特殊的手段获取到了服务器执行权限，从而可能会导致数据库被拖库导致数据泄漏。对于数据库系统来说管理也是不推荐开放 root 权限给远程用户登陆，对于 Web …

在 RHEL9 上安装 MySQL 社区版本
type: item
date: 2023-03-22T16:47:00+08:00
description: 关系型数据库目前有很多产品共同特点是分为商业版本和社区开源免费版本，目前社区开源版本的关系型数据库用的最多的数据库是 MySQL Community Server Edition ，最大特点为免费商业使用也不用顾虑任何法律相关的问题，MySQL 被 Oracle 公司收购之后也推出了商业服务，原有的 MySQL 开发人员为了能摆脱 Oracle 相关的控制也在 MySQL 原有代码的分支上开发出来了 MariaDB 开源社区驱动开发的版本。[scode type="lbule"]对于一个想要学习数据库技术的新手来说要解决的问题当然是如何拥有数据库服务器端，数据库也是一种 C/S 架构的程序，对于多数开发者来说用的最多的是客户端，服务器管理大部分由 dba 操作，这篇博客将介绍如何通过 tar 包的方式在 RHEL9 上安装 MySQL 服务端程序。[/scode]准备工作首先需要一台已经安装 RHEL9 的操作系统，目前 rhel 官方的仓库已经支持直接使用 dnf 命令安装 MySQL 服务器，这种针对是完全没有经验用户傻瓜安装方式。如果需要自定义安装路径和用户组还是推荐使用 tar 包的方式安装，首先安装一些依赖包和工具包：yum -y groupinstall "Development Tools" yum -y intsall vim wget libaio[scode type="yellow"]最后去 MySQL 官方网站找对应的 Linux Generic Archive 安装包下载到本地：[/scode]wget https://cdn.mysql.com//Downloads/MySQL-8.0/mysql-8.0.32-linux-glibc2.12-x86_64.tar.xz[scode type="yellow"]创建一个用于存储 MySQL 服务器程序的目录，通过命令 mkdir -p /usr/local/mysql ，将其解压到这个目录中：[/scode]# 解压到指定目录中 tar -xvf mysql-8.0.32-linux-glibc2.12-x86_64.tar.xz -C /usr/local # 重新命名目录 mv mysql-8.0.32-linux-glibc2.12-x86_64 mysql解压完成之后接下来需要创建两个目录，一个用来存放 mysql 运行时产生的日志文件，另外一个为存放 mysql 生成的数据文件目录，需要创建两个目录命令如下：# 创建 log 目录 和 创建数据目录 sudo mkdir -p /usr/local/mysql/data /usr/local/mysql/logs将 mysql 的支持文件拷贝到 /etc/systemd/system/ 目录中，该目录存放都是一些开机自动化启动程序，能够使用 systemctl 程序的子命令 start 、 stop 、restart 、reload 来管理某个具体的应用，例如这里的 mysql 程序。# 复制支持文件，并且重新命名为 mysqld sudo cp /usr/local/mysql/support-files/mysql.server /etc/systemd/system/ # 所有者应有执行权限 sudo chmod +x /etc/systemd/system/mysql.server[scode type="blue"]接下来要创建用来运行 mysqld 的用户，和将上面的文件夹所有者和所属组全部设置为新创建的 mysql 用户和组：[/scode]# 创建用户 useradd -M -s /sbin/nologin mysql # 更改文件所属组和所有者 chown -R mysql:mysql /usr/local/mysql[scode type="yellow"]最后将 mysql 的二进制命令工具目录加到环境变量中，以方便在任何地方都能使用 mysql 提供的命令工具：[/scode]# 在 /etc/profile 最后一行添加 echo "PATH=$PATH:/usr/local/mysql/bin" >> /etc/profile # 刷新环境变量 source /etc/profile到此整个安装前置条件已经满足，剩下就是对数据库进行安装和初始化了。初始化安装当准备工作完成之后就需要对数据库进行初始化，这里初始化使用 mysqld 命令，要提供 --initialize 参数，--user 指定运行程序用户，--basedir 指定数据库生成的数据文件存放的目录，--defaults-file 指定配置文件路径，执行命令会自动初始化数据库并且设置默认的 root 登…

免费体验 Oracle Database 新特性
type: item
date: 2023-03-19T16:35:00+08:00
description: Oracle 数据库目前是世界最为领先的数据库，也是很多有钱的企业使用的数据库，与开源数据库不一样的是 Oracle 数据库针对企业用户是收费，而且费用也不是一些小公司能承受的起的，和 SQL Server 一样针对是企业级应用市场。我第一次接触 Oracle 数据库是 2017 年的时候，培训使用过 Oracle 课程，但是目前我用的最多还是 MySQL 数据库，因为它是开源和免费的，商业场景使用没有法律顾虑，而 Oracle 针对教育使用是免费的，如果商业使用那么就要付费。最近在闲逛的时候发现 Oracle 有一款 Oracle Database Express Edition ，有核心数 2 颗和内存大小 2G 限制和磁盘数据大小 12G 限制，适用于小型应用程序和我这种想体验 Oracle 最新特性的开发人员。准备工作Oracle XE RPM 安装包去 Oracle 官方网站下载 Oracle DataBase Express Edition 选择对应版本，我使用的 Linux 版本和 AMD 架构 CPU ，也可以在通过 SFTP 上传下载好的 rpm 包到服务器中，如果服务器在没有连接到外网的情况下。我目前使用的 DigitalOcean 所提供的 IaaS 服务，他们家没有提供 rhel8 的 ISO 文件需要自己去 rhel 控制中创建 .vmdk 文件上传到 DO 中，目前 Oracle XE 是原生支持 rhel8 和 oel8 的系统，其他发行版本 Linux 安装过程中会提示 yum repo 仓库密钥错误或者一些莫名其妙的错误。[scode type="yellow"]由于 Oracle XE 要求硬件最低配置是 1G 内存和 12GB 磁盘，如果未达到要求安装过程中会提示安装失败，最好使用 2G 内存机器。[/scode][scode type="yellow"]如果使用的 RHEL 系统初始化完成之后，必须对系统进行注册和检查极好状态，可以运行下面的命令：[/scode]# 查看系统当其系统发行版本 cat /etc/redhat-release # 检查是否注册 subscription-manager identity # 注册系统 subscription-manager register # 检查订阅列表 subscription-manager list --available # 检查订阅状态 subscription-manager status没有特殊情况一般都会显示注册成功。安装 Oracle XE[scode type="lbule"]在安装 Oracle DataBase Express Edition 之前会需要检查依赖 libaio 软件包，因为 Oracle XE 版本采用了 AIO 模型，这个库 libaio 为异步 I/O 提供了 Linux 原生 API 被 Oracle XE 程序所采用。从这里就可以看出来 Oracle 数据库在这方面技术领先性，目前大部分网络和磁盘 IO 程序还是用的 NIO 或者 BIO 的方式。[/scode]# 检查依赖是否安装 rpm -qa | grep libaio # 如果没有就安装，并且安装一些依赖软件 yum install -y libaio vim wget # 安装一些开发工具依赖 yum -y groupinstall "Development Tools" 安装过程中可能会提示没有安装 compat-openssl10 依赖包，这是需要手动下载并且安装到系统中：# 下载依赖 wget https://repo.almalinux.org/almalinux/8/AppStream/x86_64/os/Packages/compat-openssl10-1.0.2o-4.el8_6.x86_64.rpm # 安装依赖 yum install -y compat-openssl10-1.0.2o-4.el8_6.x86_64.rpm[scode type="yellow"]Oracle XE 相比其他免费的数据库安装起来要复杂，其他开源数据库大部分不用考虑主机名和主机 IP 映射关系，在安装之前一定要保持主机名和 IP 地址是对应的，否则后面安装会提示 No valid IP Address returned for the host，需要自己手动更改 hostname 和 hosts 文件中的记录。[/scode]接下来下载对应的 Oracle EX 版本的软件安装包，分别为 oracle-database-preinstall-21c 和 oracle-database-xe-21c 软件包，这是 Oracle …

PostgreSQL 简易教程
type: item
date: 2022-12-30T15:00:00+08:00
description: PostgreSQL 是一个功能强大的开源对象关系数据库系统，经过超过 35 年的积极开发，在可靠性、功能稳健性和性能方面赢得了良好的声誉，该数据库产品在开源 BSD 协议下发行，这意味着如果你有能力在此基础之上进行二次开发的，可以基于它来构建你商业型产品，作为一名开源软件爱好者一个多多使用的开源软件。 MySQL 在被 Oracle 收购之后出现了 MariaDB 分支这些都是兼容 SQL 标准关系数据库，在数据库引擎排行榜上显示 PostgreSQL 正在慢慢上升趋势，作为一款开源数据库能和一些企业级专有数据库软件排名不分上下，已经表示出它的优势所在了。PostgreSQL 和传统的关系数据库相比，还有一个特别特性是对象关系型数据库，写过面向对象编程语言 Java 会熟悉多个对象继承关系，类似的 Postgres 它弥补了关系型编程和面向对象编码之间的差距，这使得对象的定义以及表的继承成为可能，从而导致创建更复杂的数据库系统；此外 PostgreSQL 除了传统关系数据库支持的数据类型以外还有一些特殊自定义数据类型，例如 Array 、Points 、 Inet 、JSON 、XML 等类型。安装 PostgreSQL这里我采用的通过 Homebrew 的方式进行安装 PostgreSQL ，通过 Homebrew 安装的软件包可以方便管理也是一种比较即可的方式；在使用 Homebrew 安装任何东西之前，你应该始终确保它是最新的并且是健康的：brew update & brew doctor这两条命令会更新你电脑上的 Homebrew 并且自动检测健康状态和自动修复一些常见的问题，并且也会报告一些无法自行修复的错误。接下来就可以使用 Homebrew 进行安装 PostgreSQL 使用命令如下：brew reinstall postgresql@14这个安装过程可能比较缓慢，取决于当前电脑所在地区的网络情况，可以切换到国内的软件源进行安装，使用下面命令就可以切换阿里云的软件源，如下命令：# 替换brew.git cd "$(brew --repo)" git remote set-url origin https://mirrors.aliyun.com/homebrew/brew.git # 替换homebrew-core.git cd "$(brew --repo)/Library/Taps/homebrew/homebrew-core" git remote set-url origin https://mirrors.aliyun.com/homebrew/homebrew-core.git # 刷新源 brew update安装完成之后可以使用 brew services list 查看当前支持管理的软件服务，启动 Postgres 也很简单，只需要使用下面的命令来启动 Postgres 服务：brew services start postgresql@14当启动成功之后，在最新的 MacOS 系统设置中的登录项可以查看到此数据库的进程信息，如下图所示：可以通过 psql --version 命令来查看是否成功安装，停止 Postgres 只需要使用下面命令停止进程：brew services stop postgresql@14如果是使用 Linux 环境可以在官网查看具体安装步骤文档，但是要注意的是在 Linux 系统环境下 Postgres 采用的系统用户的方式进行管理，默认超级用户是一个名为 postgres 用户，需要使用下面的命令切换：# 切换换默认超级管理员用户登录到数据库中 sudo -u postgres psql # 切换用户 sudo -i -u postgres默认使用端口 5432 ，默认的 Postgres 用户是 postgres ，不需要密码进行身份验证因此如果需要添加密码，我们必须首先以 postgres 用户身份登录并连接。修改密码需要注意的如果要修改默认用户的密码必须有足够的权限，如果这里出现了默认权限不够情况，可以在 /opt/homebrew/var/postgresql@14/pg_hba.conf 配置文件中进行修改，身份验证配置文件是身份验证规则列表：# "local" is for Unix domain socket connections only local all all md5 # IPv4 local connections: host all all 127.0.0.1/32 md5 # IPv6 local connections: host all all ::1/128 md5 # Allow replication connection…

日志结构在 InnoDB 应用
type: item
date: 2022-11-22T11:34:00+08:00
description: 在前面的分析 MySQL 的存储引擎相关的文章中，已经明确知道了在数据记录在以 16KB 的页进存储的，当操作 SQL 时数据库会针对数据所在的页面进行增删改查操作，读页面、写页面、创建新页面。在数据库执行事务的过程中会将所需要的数据页读取到内存中的 Buffer Pool 中进行操作，这样就使得内存中对事务进行隔离运算，当完成了数据更新操作就会进行刷盘持久化，这是一个完整的事务生命周期。当在内存中 Buffer Pool 的修改页面时，突发发生断电情况，那么内存中的数据就会丢失，如果数据未提交持久化就发生丢失情况，如何保证数据不丢失？最简单的办法记录日志，将在内存中对数据产生修改的页面信息记录到一个日志中，通过日志的方式好处是不需要对整个页面进行刷盘操作产生的随机 IO 性能问题，日志文件系统都是以 Append Only 方式进行的。REDO LOG为了防止在执行事务过程中程序突然崩溃导致内存数据丢失，MySQL 的设计者在引入 Redo 日志的设计，即使程序崩溃也能通过 Redo 日志里面信息恢复数据。Redo Log 相比传统数据页占用的内存空间非常小，只会存储表空间的 ID 和 页面号、和具体修改的数据偏移量，和被修改数据的新值，Redo Log 和之前博客中介绍的 Bitcask 存储引擎很相似。一条通用的 Redo Log 日志结构如下，Type 日志类型、Space ID 表空间、Page Number 页号、Data 真实的数据，至于 Len 和 offset 针对的是一些特殊类型的日志记录的附加信息：在 Redo Log 中的 Type 字段的类型有 53 种，日志数据的类型是根据页面中写入的数据类型进行划分的，整理一个常见的类型表格：类 型作用说明MLOG_1BYTE对应十进制数字为 1 表示在偏移量处写 1 字节MLOG_2BYTE对应十进制数字为 2 表示在偏移量处写 2 字节MLOG_4BYTE对应十进制数字为 4 表示在偏移量处写 4 字节MLOG_8BYTE对应十进制数字为 8 表示在偏移量处写 8 字节MLOG_WRITE_STRING对应十进制数字为 30 表示在偏移量处写入一个字节序列设计这么多的 Type 类型是为了节省空间，能劲量不要使用 offset 和 len 字段时就劲量不要使用，提高空间利用率积少成多，其 Type 字段类型远远不止本文中列出来的，能节省一个字节是一个字节。在 Redo Log 日志里面不仅仅有物理日志，还有一部分的逻辑日志，逻辑日志针对事务操作的类型日志，Type 类型是数据操作的动作，具体其他类型可以查看其他资料。在 MySQL 默认的存储引擎 Redo Log 大小可以通过命令进行查看，默认是 16MB 被存储引擎提前申请好存储在 /var/lib/mysql 目录中：SHOW VARIABLES LIKE 'innodb_log_buffer_size';当一个事务执行时 Redo Log 的工作原理图为下图，先是解析 SQL 语句，生成执行器然后去执行器里面执行加载需要的数据页，然后开始记录事务操作逻辑日志信息：正如上图 Redo Log 作为文件存储在磁盘上，那么就需要将内存中的日志操作记录刷入到磁盘文件里面，因为数据库是用户态下的程序所以他文件写入依赖于操作系统的文件缓存的刷入频率，Innodb 提供多种刷盘策略，默认 MySQL 的刷盘策略可以通过下面命令查看：SHOW VARIABLES LINK 'innodb_flush_log_at_trx_commit'分有 3 种策略，默认值为 1 下图：目前已 3 种值类型操作，分别为下面列表中：值类型作用说明0每次事务提交时不进行强行执行刷入磁盘，而是根据后台的线程进行的1每次事务提交时都会执行一次刷入磁盘的操作2每次事务提交时只把内存缓冲区的数据刷入到系统的 Page Cache 中依赖于系统在日志中还有一种特别的类型为 Mini-Transaction 类型，它是指一次事务对 Innodb 底层数据页的一次原子访问的过程。也就是说在一张有索引表中插入一条记录，那么聚族索引 B+ 树的数据页也会插入这条记录的索引信息，这个过程就称为对底层页的一次原子访问，如果这个过程中运行到一半就宕机了，如果 Redo Log 日志只记录一部分日志不是完整的，下次恢复数据就不能恢复完整的数据记录，所以这里的存储引擎设计者设计一组 Redo Log 要么把全部的日志都恢复掉，要么就一条也不恢复，结构如下图：在 Redo Log Buffer 中的多个 Mini-Transaction 可以组成一个 Log Block 块，多而个 Log Block 又可以组成 Redo Log Buffer，而负责存储 Mini…

数据库中的触发器
type: item
date: 2022-10-10T15:05:00+08:00
description: 在数据库事务中如何保证数据一致性这也是给开发者带来许多的挑战，我们将现实生活中的东西抽象成数据库的关系模型，存放在数据表中，但是现实生活中某些东西有一些约束的，例如付款不能出现负数，人民币最大的面值是 100 元不可能出现比 100 面值还要大的面值，那么这些如何在数据库中保证这些约束？在做某件事情之前查看是否满足某个条件，这就是本篇文章笔者要写的数据库触发器，如何使用 Trigger 来保证数据正确性。应用场景例如下面我用 DDL 写一段 SQL 语句创建表结构，在这段 DDL 语句中最后有一句 CHECK (balance >= 0)，如下：CREATE TBALE account_info { uid INT NOT NULL AUTO_INCREMENT, name VARCHAR(10), balance INT, PRIMARY KEY(id), CHECK (balance >= 0), }这是用SQL语句里面的 CHECK 关键字来规定 balance 列的值不能小于 0 的约束，这就是保证现实世界银行账户不可能出现负数情况，但是这个 CHECK 关键字在 MySQL 中是不起作用的插入数据值时不会检测条件是否成立，而在 Oracle 和 SQLServer 中是可以起到作用的，如果某个用 CHECK 语句来判断条件的列不满足条件就会出现错误。触发器像是上面的这种场景在日常业务需求经常遇到这些问题，例如一个用户在注册系统的时候会查看当前用户名是否占用？如果被使用了就不允许使用，这时大部分做法是通过业务层的代码进行检查的，通过业务层通过 SELECT 语句条件判断，但是这种情况会出现一个问题如果系统并发量很多如何保证多个用户输入一个相同的用户名情况下还能被正常满足数据正确性，也就是多个用户的条件查询如何保证？一般操作将几个语句包装成一个事务去操作。上面这种只是一个场景在某个操作之前先要查看是否能满足某个条件才能操作，这种场景很多，在数据库里面可以通过触发器去做，触发器是数据库中提供一个非常实用的功能，它可以在操作者对表进行「增删改」 之前（或之后）被触发，自动执行一段事先写好的 SQL 代码，目前触发器支持的模式有：名称作用域BEFORE INSERT在插入数据前检查某个条件，如不满足返回错误信息。AFTER INSERT在插入数据之后执行某个预定义好的SQL语句。BEFORE UPDATE在更新某个条记录之前检查某个条件，如果不满足返回错误信息。AFTER UDPATE在更新某条记录之后执行某个预定义SQL语句，例如记录日志信息。BEFORE DELETE在删除数据前，检查是否有关联数据，如有，停止删除操作。AFTER DLELTE删除表 A 信息后，自动删除表 B 中与表 A 相关联的信息。有了触发器在某些场景下就不需要再业务层做额外的处理了，有时候也不需要让业务方知道这些事情，例如每条更新操作都会被记录到日志表中，触发器也有自己的特定的语法的，类似于 Linux 下的 Shell 脚本，触发器的基本语法如下：DELIMITER // CREATE TRIGGER [触发器的名字] [触发器执行时机] [触发器监测的对象] ON [表名] FOR EACH ROW [触发器主体代码]// DELIMITER ;在头部和结尾的 // 符号表示触发器开始与结束，下面的中括号里面都已经其作用，触发器执行时机可以是 BEFORE 和 AFTER ，而触发器监测的对象则为具体对应操作动作类型可以是 INSERT 、 UPDATE 、 DELETE 这些，表名则为具体监测对象表，触发器主体代码则为具体触发器代码，下面是一个实例：DELIMITER // CREATE TRIGGER validate_sales_amount BEFORE INSERT ON account_info FOR EACH ROW IF NEW.balance<=0 THEN SIGNAL SQLSTATE '45000' SET MESSAGE_TEXT = "你输入Balance金额不合法！"; END IF// DELIMITER ;上面这段代码中，我们使用 IF...THEN...END IF 来创建一个监测 INSERT 语句写入的值是否在限定的范围内的触发器，这个触发器的作用就是为了检测在向 account_info 表添加新数据时会检测 balance 列的值是否合法，从而达到前面类似于 CHECK 的效果。触发器缺点触发器不是没有缺点的，虽然能解决一部分业务场景下的问题，但是需要注意合理使用。例如一些变态的需求，假设一个系统有张用户表里面存储用户账户和余额信息，需求要让每个用户在完成每笔交易之后都会对其让整个所有用户余额减去用…

InnoDB 数据页的设计
type: item
date: 2022-10-06T15:09:00+08:00
description: 数据库系统核心就是存储引擎，前面的文章我写过日志类型的LSM-Tree存储引擎，本篇文章将介绍基于B+树的 InnoDB 存储引擎实现细节，存储引擎一般都是有不同专业领域人员开发的，目前在MySQL5.7之后使用的默认存储引擎就为 InnoDB ，下面为笔者整理的 InnoDB 引擎相关的实现细节笔记总结。配置设计MySQL 是如何对配置选项进行设计的？设置这个可能大部分软件都有手机上大部分是 GUI 图形化设置界面。而大部分的基础软件或者运行在 Linux 平台上的软件都是通过命令行或者指定的配置文件进行设置的，MySQL 作为一个数据库基础软件也提供很多种配置参数的方式，分别为命令行参数、环境变量、配置文件。权限最高的就是命令行方式，如果在启动数据库的时候再命令行上传入参数那么可以覆盖其他方式配置的环境参数，例如下面的：mysqld --skip-networking # 等价于 mysqld --skip_networking上面这种方式通过做参数标记，这种没有具体的参数值，而另外一种命令行的方式就是有默认值可以通过命令行修改其值的，方式如下：# 修改默认的存储引擎 mysqld --default-storage-engine=MyISAM如果是传入参数值时不容许有空格必须用等号划分，多个参数值可以通过空格分割传递起来；上面这些都为长参数配置选项，还有一种就是 MySQL 客户端所使用的默认短参数，主要短参数是区分大小写的，例如 -p 代表密码，而 -P 表示的是端口号，如下图：mysql -h127.0.01 -uroot -p Enter password:上面的方式每次都要通过命令行参数传入其配置参数值，如果要配置很多的话需要写多个参数集合，这使得很多的参数需要一起写到命令行中并且每次都要这么传入，非常麻烦，但是命令行在所有配置方式中命令行上传入的参数优先级最高。另外一种方式是通过配置文件的方式设置其参数值，采用的是 ini 的配置文件格式，例如上面的命令行方式配置的参数可以编写配置文件的格式：[client] port=3306 [mysql] default-character-set=utf-8 [mysqld] # mysql服务端默认监听(listen on)的TCP/IP端口 port=3306 # mysql数据库文件所在目录 datadir="xxxx/Data" # 服务端使用的字符集,默认为Latin，安装时一般选择utf-8 character-set-server=utf-8 # 创建新表时将使用的默认存储引擎 default-storage-engine=INNODB 像上面这种方式称之为配置组的方式，每个组的都有自己对应的配置值，而组名大部分是对应的bin下面的可执行二进制名称，同一个配置文件中的配置出现多次，将以出现在最后一组的参数值为准；如果是命令行传入参数那么最终使用的是命令行参数，命令行参数级别最高；当然部分配置可以在使用 DDL 语言进行修改操作，例如数据库的字符集和表使用的默认存储引擎。MySQL的可配置信息远不止这么多，这些配置参数一部分是系统变量的成员，可以使用 SHOW VARIABLES 来查看相关的系统变量信息，设置系统环境变量的方式就是上面我所整理的命令行的方式和配置文件的方式，当数据库启动的时候这些被设置的参数就会被设置为其对应的值，如果没有配置则有默认的值。对应某些环境变量在程序运行的是无法动态修改的，且部分变量例如存储引擎都有各自作用域，例如字符串和字符串排序规则有数据级别、表级别、行级别，这时设计是因为可以合理的使用存储空间提高数据库性能。另外一些变量分为 GLOBAL 和 SESSION 级别的变量，服务器启动的时候会为分配初始化一些全局的变量，而会话级别则是针对于当前连接到服务器的某个客户端的环境变量设置，例如 max_connections 为全局变量，而 insert_id 客户端表示自增 ID 的初始化启始值，还有 default_storage_engine 则为服务器端和会话端共有的值。存储单位在 InnoDB 中数据表中的每条数据记录采用页存储，以页作为存储磁盘上的基本单位，默认的页大小为 16KB ，如果需要处理数据记录也就是要把页从磁盘中加载到内存中再操作，一次性批量操作 16KB 大小的页。当然这个页面大小页可以通过 innodb_page_size 系统环境来设置，当数据库启动之后这个值就不能被再次修改，运行过程中是不可变的系统变量。InnoDB 一些设计细节运用一些巧妙的设计来提高空间利用率，官方将存储数据的记录的页称之为 INDEX PAGE ，下面就为页布局图：这里主要关注的是 User Records 部分，数据库里面的记…