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

推荐订阅源

WordPress大学
WordPress大学
Microsoft Azure Blog
Microsoft Azure Blog
MongoDB | Blog
MongoDB | Blog
小众软件
小众软件
Apple Machine Learning Research
Apple Machine Learning Research
O
OpenAI News
酷 壳 – CoolShell
酷 壳 – CoolShell
The GitHub Blog
The GitHub Blog
CTFtime.org: upcoming CTF events
CTFtime.org: upcoming CTF events
Exploit-DB.com RSS Feed
Exploit-DB.com RSS Feed
博客园 - 聂微东
Engineering at Meta
Engineering at Meta
W
WeLiveSecurity
Hacker News: Ask HN
Hacker News: Ask HN
大猫的无限游戏
大猫的无限游戏
Vercel News
Vercel News
D
Docker
F
Full Disclosure
AI
AI
罗磊的独立博客
博客园 - 【当耐特】
U
Unit 42
S
SegmentFault 最新的问题
Stack Overflow Blog
Stack Overflow Blog
cs.AI updates on arXiv.org
cs.AI updates on arXiv.org
P
Palo Alto Networks Blog
博客园_首页
H
Help Net Security
量子位
月光博客
月光博客
Threat Intelligence Blog | Flashpoint
Threat Intelligence Blog | Flashpoint
K
KPMG report finds enterprise disconnect between AI and its ROI | CIO
博客园 - 司徒正美
F
Fortinet All Blogs
D
DataBreaches.Net
B
Blog RSS Feed
Webroot Blog
Webroot Blog
TaoSecurity Blog
TaoSecurity Blog
S
Secure Thoughts
爱范儿
爱范儿
I
InfoQ
钛媒体:引领未来商业与生活新知
钛媒体:引领未来商业与生活新知
www.infosecurity-magazine.com
www.infosecurity-magazine.com
Attack and Defense Labs
Attack and Defense Labs
Application and Cybersecurity Blog
Application and Cybersecurity Blog
C
CERT Recently Published Vulnerability Notes
Martin Fowler
Martin Fowler
Blog — PlanetScale
Blog — PlanetScale
让小产品的独立变现更简单 - ezindie.com
让小产品的独立变现更简单 - ezindie.com
S
Securelist

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

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

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

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