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

推荐订阅源

The Hacker News
The Hacker News
F
Full Disclosure
Cloudbric
Cloudbric
Blog — PlanetScale
Blog — PlanetScale
W
WeLiveSecurity
N
News and Events Feed by Topic
T
Troy Hunt's Blog
V2EX - 技术
V2EX - 技术
CTFtime.org: upcoming CTF events
CTFtime.org: upcoming CTF events
B
Blog
GbyAI
GbyAI
C
Check Point Blog
B
Blog RSS Feed
Application and Cybersecurity Blog
Application and Cybersecurity Blog
Recorded Future
Recorded Future
The Last Watchdog
The Last Watchdog
N
News and Events Feed by Topic
T
The Blog of Author Tim Ferriss
O
OpenAI News
V
V2EX
人人都是产品经理
人人都是产品经理
cs.AI updates on arXiv.org
cs.AI updates on arXiv.org
IT之家
IT之家
WordPress大学
WordPress大学
www.infosecurity-magazine.com
www.infosecurity-magazine.com
S
Security @ Cisco Blogs
C
Cisco Blogs
Security Latest
Security Latest
S
Security Affairs
V
Visual Studio Blog
C
Cybersecurity and Infrastructure Security Agency CISA
Hacker News - Newest:
Hacker News - Newest: "LLM"
博客园 - 司徒正美
freeCodeCamp Programming Tutorials: Python, JavaScript, Git & More
Microsoft Azure Blog
Microsoft Azure Blog
Last Week in AI
Last Week in AI
AWS News Blog
AWS News Blog
雷峰网
雷峰网
Apple Machine Learning Research
Apple Machine Learning Research
PCI Perspectives
PCI Perspectives
博客园_首页
U
Unit 42
Google DeepMind News
Google DeepMind News
Hugging Face - Blog
Hugging Face - Blog
Project Zero
Project Zero
Cisco Talos Blog
Cisco Talos Blog
The Register - Security
The Register - Security
N
Netflix TechBlog - Medium
L
LINUX DO - 热门话题
H
Hacker News: Front Page

Razeen`s Blog

Let's Encrypt 推出 Gen Y 根证书架构:揭示 Web PKI 的五大未来趋势 通过 Wi-Fi 自动备份你的 iPhone 到 Nas 管理培训感悟:从技术视角看管理之路 UPS 一拖多:保护你的 NAS 和 Linux 服务器 从影音中心到 AI 助手:我的50款 Homelab 服务清单 从SSL证书有效期将缩短到47天聊开去 部署一个自己的 Running Page 谈谈特斯拉 Model Y 用车一年的感受和费用分析 弃用 Disqus 评论,使用自建 Waline 使用 Prometheus 和 Grafana 搭建你的证书监控面板 服务器迁移记:一次磁盘换板引发的Linux分区与挂载的学习 改善信息来源, 利用 RSS 高效获取资讯 (RSShub + Reeder5 + WeWe RSS) 多种 Docker 镜像拉取解决方案与实践 App分享 | AppCleaner - Mac上的卸载神器 Azure OpenAI API 申请和使用 如何开通 OneKey 虚拟信用卡,并充值消费 如何拥有一个可长期在国内使用的国外手机号码 分享开通 ChatGPT Plus 过程 如何开通 Depay 虚拟信用卡,并充值消费 如何开通欧易Web3钱包, 交易入账 如何5分钟内1块钱注册 ChatGPT 如何注册美区的 Apple ID (2023年/无需科学上网) NAS折腾记(11): NASTool3.0体验和降级 NAS折腾记(10): Docker版本的NASTool配置 NAS折腾记(9): NASTool与微信交互,微信发送消息远程下载电影【多图】 NAS折腾记(8):群晖安装 NASTool 实现影音半自动化【多图】 内网穿透(2):Tailscale 组网实现内网穿透,操作简单,无成本 内网穿透(1):总结了11中内网穿透的方式,总有一种适合你 NAS折腾记(7):从零开始设置(黑)群晖系统 NAS折腾记(6):黑群晖系统安装好后怎么洗白? NAS折腾记(5):群晖硬盘休眠设置与分析 NAS折腾记(4): 20分钟手把手带你完美安装 DS918+ 黑群晖7.1.1 NAS折腾记(3):NAS 装机 NAS折腾记(2):B360-ITX 双M.2 双2.5G网口 6 SATA主版 开箱 NAS折腾记(1):328元的4盘位Nas机箱开箱 Openwrt + Clash 全局科学上网 Newifi3 刷入 OpenWrt 固件 v21.02 利用 Markdown 画一些流程图、时序图、甘特图等 Homelab(8): 搭建自用 Gitlab 与 Docker 仓库 Nginx Tcp 转发保留客户端真实 IP (PROXY Protocol) Homelab (7):IPSec VPN(基于证书认证)客户端设置 Homelab (6): 基于自签发证书的 IPSec VPN 搭建 Homelab (5): DDNS 动态域名解析 Homelab (4): Linux 服务器基础环境准备 Homelab (3): 整体网络与基础硬件介绍 Go学习笔记(十)老项目迁移 go module 大型灾难记录 Github Actions 初体验之自动化部署 Hexo 博客 记一次 Nginx DNS 缓存导致转发问题 Homelab (2): 电信悦me网关修改桥接模式,路由器拨号 Homelab (1):5分钟上手黑群晖 NAS 终极 Bash 脚本指南 Typora 自动上传图片到七牛云 Ubuntu 20.04 LTS 有线网卡驱动安装 折腾 Ubuntu 20.04 LTS 开发环境 Go学习笔记(九) 计时器的生命周期[译] 利用 git hook 规范你的代码与 commit message 规范 git commit message 与自动化版本控制 超详细 vim 配置 (with MacVim) Golang 中的 RESTful API 最佳实践 折腾服务器(开篇) 我的第一台个人服务器 Newifi3 实现低成本家庭级科学上网 Go学习笔记(八) | 使用 os/exec 执行命令 如何用 Go 调用 Windows API Mac OS 自动根据 WI-FI 名字改变网络位置 关于 Docker 清理 MIME Types 速查表 Go学习笔记(七) | 理解OAuth 2.0并实现一个客户端 我又又又把博客迁移了 Go学习笔记(六) | 使用swaggo自动生成Restful API文档 Go学习笔记(五) | 使用代码片段(snippets)提高编码效率 书单 - 2018 IPFS 初体验,利用 IPFS 托管你的静态网站 记一次 PostgreSQL LIKE 索引优化,联合字段 LIKE 查询优化。 Disqus 添加有趣的 Reactions 的功能 TLS 1.3 详解 (RFC 8446解读) gRPC在Go中的使用(三)gRPC实现TLS加密通信与流模式 gRPC在Go中的使用(二)gRPC实现简单通讯 gRPC在Go中的使用(一)Protocol Buffers语法与相关使用 CentOS 安装 tshark 抓包工具 Go学习笔记(四) | win上使用VSCode搭建Go开发环境 日常 Postgres 数据库点滴记录 简单了解 PKCS 规范 美食篇 | DIY戚风蛋糕(烤箱做蛋糕) Go学习笔记(三) | 怎么写Go基准测试(性能测试) TLS1.3正式更新,为Nginx添加TLS1.3的支持 GitHub Pages自定义域名开启HTTPS 证书透明度是什么?它是怎么工作的? Go学习笔记(二) | 我对 recover 的一点误解 搭建证书透明度(certificate-transparency)日志服务之从入门到放弃 修复远程登陆 Centos 时,出现 UTF-8 Warning HTTPS篇之SSL握手过程详解 Go学习笔记(一) | postgres与golang点点滴滴 AWS 命令行界面(aws-cli)从安装到快速上手 数字证书分类及怎么区分各类数字证书 常用 linux 命令小结(一)文件目录操作 云服务器搭建 hexo 博客,git hooks自动更新 SSH 免密登陆, SSH Config 配置 Golang CGO Mac 交叉编译 Windows 使用 goose 让数据库迁移更加轻松 开始使用Ghost
一次诡异的数据库删除
2018-04-12 · via Razeen`s Blog

事情是这样的。

我们的项目使用的是sqlite3数据库。在一次常规模拟测试中,我们想了解,如果程序正在运行中,用户将sqlite3数据库文件删除,程序能否进入预设的重置流程。

结果,我们却让我们有些意外。

try

于是我写了一段go代码模拟一下该过程。

package main

import (
	"database/sql"
	"net/http"
	"time"

	_ "github.com/mattn/go-sqlite3"
)


func main() {
    // 首先,我们打开个数据库
	db, err := sql.Open("sqlite3", "file:test.sqlite")
	if err != nil {
		panic(err)
	}
	defer db.Close()

    // 建个表
	_, err = db.Exec(`CREATE TABLE IF NOT EXISTS test_t(
		a TEXT,
		create_at TIMESTAMP default CURRENT_TIMESTAMP
	);`)

	if err != nil {
		panic(err)
	}


    // 我们通过API来简单控制一些插入,查询
	http.HandleFunc("/test", func(w http.ResponseWriter, r *http.Request) {
		if r.FormValue("query") != "" {
			rows, err := db.Query("SELECT * FROM test_t")
			if err != nil {
				w.Write([]byte(err.Error()))
				return
			}

			for rows.Next() {
				var a string
				var t time.Time
				err := rows.Scan(&a, &t)
				if err != nil {
					w.Write([]byte(err.Error()))
					return
				}

				w.Write([]byte(a + " | " + t.Format("2006-01-02 15:04:05"+"\r\n")))
			}
			return
		}

		if v := r.FormValue("insert"); v != "" {
			_, err := db.Exec("INSERT INTO test_t (a) VALUES (?)", v)
			if err != nil {
				w.Write([]byte(err.Error()))
				return
			}
			w.Write([]byte("OK"))
			return
		}

		w.Write([]byte("NO"))
	})

	http.ListenAndServe(":10888", nil)
}

该段代码是简单的通过API来访问数据库内容。

我们的重点不是API部分,而是我们开启了一个数据库的连接,然后我们将数据库文件删除后会有什么现象发生呢?

首先,我们先向插入一些数据。

$ curl "http://127.0.0.1:10888/test?insert=ddd"
OK 

然后我们查询一下。

$ curl "http://127.0.0.1:10888/test?query=xxx"

ddd | 2018-09-10 16:24:32
ddd | 2018-09-10 16:24:33
ddd | 2018-09-10 16:24:34
ddd | 2018-09-10 16:24:34
ddd | 2018-09-10 16:24:35
ddd | 2018-09-10 16:24:35

我们看到已经能查到数据。

我们看到,当前目录已经生成了一个test.sqlite文件。

我们看看该进程在系统的状态。

jietu20180911-003048

我看看到,该进程打开了.../go/src/test.sqlite数据库文件。

现在,我们将文件删了。

$ rm -rf test.sqlite

$ ls
...

我们再次调取查询接口。

$ curl "http://127.0.0.1:10888/test?query=xxx"

ddd | 2018-09-10 16:24:32
ddd | 2018-09-10 16:24:33
ddd | 2018-09-10 16:24:34
ddd | 2018-09-10 16:24:34
ddd | 2018-09-10 16:24:35
ddd | 2018-09-10 16:24:35

我们发现数据还在。

我们再试试插入数据。

$ curl "http://127.0.0.1:10888/test?insert=ddd"
attempt to write a readonly database

此时,我们发现,没有写入成功,而是显示了一个错误。

我们在查看一下进程打开的文件。

jietu20180911-003846

我们发现居然是一样的。

ls: .../go/src/test.sqlite: No such file or directory

我们发现并没有该文件。

try & try

我们先不忙者下结论,我们再看看另外一种写法。

那就是每次使用完数据库就关闭连接,下次使用再打开。

代码如下。

package main

import (
	"database/sql"
	"net/http"
	"time"

	_ "github.com/mattn/go-sqlite3"
)

func getDB() *sql.DB {
	db, err := sql.Open("sqlite3", "file:test.sqlite")
	if err != nil {
		panic(err)
	}
	return db
}

func main() {
	db := getDB()
	_, err := db.Exec(`CREATE TABLE IF NOT EXISTS test_t(
		a TEXT,
		create_at TIMESTAMP default CURRENT_TIMESTAMP
	);`)

	if err != nil {
		panic(err)
	}

	db.Close()

	http.HandleFunc("/test", func(w http.ResponseWriter, r *http.Request) {
		db := getDB()
		defer db.Close()
		if r.FormValue("query") != "" {
			rows, err := db.Query("SELECT * FROM test_t")
			if err != nil {
				w.Write([]byte(err.Error()))
				return
			}

			for rows.Next() {
				var a string
				var t time.Time
				err := rows.Scan(&a, &t)
				if err != nil {
					w.Write([]byte(err.Error()))
					return
				}

				w.Write([]byte(a + " | " + t.Format("2006-01-02 15:04:05"+"\r\n")))
			}
			return
		}

		if v := r.FormValue("insert"); v != "" {
			_, err := db.Exec("INSERT INTO test_t (a) VALUES (?)", v)
			if err != nil {
				w.Write([]byte(err.Error()))
				return
			}
			w.Write([]byte("OK"))
			return
		}

		w.Write([]byte("NO"))
	})

	http.ListenAndServe(":10888", nil)
}

我们先跑起来。

向其中插入一些数据。

$ curl "http://127.0.0.1:10888/test?insert=ddd"
OK

查询一下。

$ curl "http://127.0.0.1:10888/test?query=xxx"
ddd | 2018-09-10 23:29:42
ddd | 2018-09-10 23:29:43
ddd | 2018-09-10 23:29:43
ddd | 2018-09-10 23:29:44

我们将数据库删除。

再次查询。

$ curl "http://127.0.0.1:10888/test?query=xxx"
no such table: test_t

此时,给我们表现是,该表已经不存在了。

我们再查看一下数据库,发现数据库居然还在。

$ sqlite3 test.sqlite
SQLite version 3.19.3 2017-06-27 16:48:08
Enter ".help" for usage hints.
sqlite > select count(*) from sqlite_master;
0
sqlite >

虽然库文件依然存在(应该是查询是sql.Open创建的),但其中的数据,表都不存在了。

so

通过两种方法的对比,我们比较容易想到, 在第一种操作中,由于我们打开一个数据库连接,始终没有关闭,即使数据库删除后,其引用仍然存在,文件还没留在内存中。后一种,我们每次操作完成后会关闭数据库,导致数据库直接被删除了。

但,如果数据库很大,内存放不了那么多怎么办?

所以,这个还有待验证,先记录一下,留个坑给自己。