什麼是嵌入式資料庫
不需另外安裝,就能和系統程式語言整合到應用程式中的輕量級資料庫。由於資料庫就儲存在應用程式的本地文件 中,無須網路連線,資料的存取可以更快速。嵌入式資料庫通常不需要複雜的配置過程,應用程式在運行時即可自 動創建和管理資料庫。沒有額外的伺服器,所以部署和安裝也相對簡單,通常只須將資料庫文件和應用程式一起打 包即可。而且大多數嵌入式資料庫都支持標準的 SQL 查詢語言,使得資料操作更便利,特別適合使用手機應用程 式、桌面應用程式、嵌入式系統和遊戲開發等。
常見的嵌入式資料庫
- SQLite:輕量級、零配置、支持標準 SQL 查詢語言和跨平台(包括移動設備和桌面應用程式),符合 ACID (Atomicity, Consistency, Isolation, Durability)。但是 SQLite 在多個使用者同時寫入操作時可能會受到 一些限制,因此不適用於高流量的場景。SQLite 提供了最簡單和高效的本地資料儲存解決方案,如果應用程 式為單用戶取向,未來也不會需要支援多用戶訪問,那麼 SQLite 會是最好的嵌入式資料庫選擇。
- Firebird:輕量級、支持跨平台和 ACID ,確保資料一致性。與 SQLite 不同的是,Firebird 能支援多個使用 者同時訪問,特別適用於多用戶、伺服器端的場景。另外,Firebird 是完全開源的,可以自由使用和修改。
- H2 Database:使用 Java 開發的輕量級嵌入式資料庫,支援 SQL 和 JDBC(Java Database Connectivity,是 Java 語言中用來規範客戶端程式如何來訪問資料庫的應用程式介面)。可以在本地快速啟動,並支援瀏覽器的 Console 介面。但由於其主要針對 Java,所以不太適用於非 Java 的應用程式。
- Realm:適用於移動應用程式,特別是需要離線儲存和同步功能的場景,支援 iOS 和 Android,並提供跨平台 的同步功能。Realm 還提供物件映射(ORM)API,避免讓使用者直接接觸 SQL 語法,簡化了程式語言的複雜度 。但缺點是其關聯式查詢需求可能受限,相對不如其他嵌入式資料庫靈活。
SQLite 效能檢測
測試內容如下,每項測試 5 次取平均值:
- Insert:插入多筆 trade 資料,內容為
id
、price
、amount
、timestamp
- Read:從 1000 筆/1 萬筆/10 萬筆/100 萬筆 的資料表中讀取任一筆 trade 10 萬次
- Insert:插入多筆 trade 資料,內容為
根據實驗結果,得到以下結論:
- SQLite 的 Insert TPS 約為
每秒 527.76 次
- SQLite 的 Read 10 萬筆 row 的 TPS 約為
每秒 31000 次
- SQLite 的 Insert TPS 約為
實驗結果
Insert
- Insert 1000 筆
第一次 | 第二次 | 第三次 | 第四次 | 第五次 | 平均值 |
---|---|---|---|---|---|
2.185s | 2.314s | 2.187s | 2.585s | 2.151s | 2.284s |
- Insert 1 萬筆
第一次 | 第二次 | 第三次 | 第四次 | 第五次 | 平均值 |
---|---|---|---|---|---|
16.684s | 15.433s | 15.617s | 20.116s | 17.027s | 17.375s |
- Insert 10 萬筆
第一次 | 第二次 | 第三次 | 第四次 | 第五次 | 平均值 |
---|---|---|---|---|---|
2:45.127 | 2:46.373 | 2:56.607 | 2:47.190 | 2:55.462 | 2:50.552(m:ss.mmm) |
Read
- Read 1000 筆資料表
第一次 | 第二次 | 第三次 | 第四次 | 第五次 | 平均值 |
---|---|---|---|---|---|
30.104s | 29.709s | 30.221s | 29.890s | 34.809s | 30.947s |
- Read 1 萬筆資料表
第一次 | 第二次 | 第三次 | 第四次 | 第五次 | 平均值 |
---|---|---|---|---|---|
31.299s | 33.073s | 31.359s | 30.215s | 33.944s | 31.978s |
- Read 10 萬筆資料表
第一次 | 第二次 | 第三次 | 第四次 | 第五次 | 平均值 |
---|---|---|---|---|---|
30.401s | 30.367s | 30.385s | 30.812s | 32.621s | 30.917s |
- Read 100 萬筆資料表
第一次 | 第二次 | 第三次 | 第四次 | 第五次 | 平均值 |
---|---|---|---|---|---|
34.572s | 35.603s | 34.607s | 29.824s | 39.734s | 34.868s |
連接 SQLite
以下是提供在 Next.js 中連接 SQLite 資料庫的工具:
- Prisma:一套可以透過 JavaScript 或 TypeScript 來操作資料庫套件,幫助開發人員更輕鬆地管理資料庫。
能夠串接
PostgreSQL
、MySQL
、SQLite
、MongoDB
等資料庫,並且提供 GUI 介面。Prisma 可以自動 從資料庫結構生成模型,而無需手動定義模型;還提供資料庫遷移功能,模型結構變化時也能輕鬆地同步資料 庫結構,不必手動更新資料庫。相較於其他工具,Prisma 擁有許多自動化功能,使開發者更高效地操作和管 理資料庫。 - TypeORM:一個支援多種資料庫的 ORM 框架,其中就包括 SQLite,能夠使用 TypeScript 定義模型並進行查詢 。
- Sequelize:另一個流行的 Node.js ORM,提供了強大的查詢和模型定義功能,支援多種包括 SQLite 的資料庫 。
- SQLite3 NPM Package:如果想以更原生的方式進行 SQLite 連接,可以直接使用 sqlite3 NPM 套件。該套件 提供一個能夠使用 SQL 語句直接與 SQLite 進行互動的簡單介面。
安裝 Prisma
- 透過 npm 安裝 Prisma
npm i -D prisma
- 安裝 Prisma cilent
npm i @prisma/client
設置 schema.prisma
- 初始化 Prisma
npx prisma init
- 完成後可以看到專案裡自動建立好的
prisma/schema.prisma
,這個文件將定義資料庫的結構和模型,並通過 Prisma CLI 來生成相關的程式碼和工具
如果使用的是 VS Code ,可以安裝
Prisma
套件讓文件自動標色,提升可讀性另外,在
.env
也會出現預設的 database url ,將 DATABASE_URL 改為file:./dev.db
撰寫 Prisma Schema
設定 Data Source
datasource db {
provider = "sqlite"
url = env("DATABASE_URL")
}
- 這裡定義資料庫種類是 SQLite ,並指定 URL 為
.env
中的 DATABASE_URL 參數
設定 Data Modal
model Post {
id String @id
title String
description String
categories String
picture String
author String
content String
date DateTime @default(now())
}
- 這裡定義資料表的欄位、屬性和關聯。以下為標籤的說明
@id
:主鍵@unique
:表示欄位不能重覆@default
:設定欄位預設值,例如@default(autoincrement())
自動遞增、@default(now())
預設為 目前時間等...@relation
:描述不同 model 的欄位關聯性,詳細的說明可參 考官方文件
生成 Migration
npx prisma migrate dev --name init
- 在
migration.sql
中記錄了要放入資料庫的 Table 。在本例中只有一個Post
Table
接下來執行 Prisma Studio:
npx prisma studio
- 執行後就可以在瀏覽器上透過 GUI 操作剛才生成的 Table
連接 Next.js
import {PrismaClient} from '@prisma/client';
const prisma = new PrismaClient();
完成資料庫的設定後,就可以在 Next.js 中進行 CRUD
Create
const saveContact = await prisma.post.create({
data: {
id: 'km-20230818001',
title: 'I am a title',
description: "I'm a description",
categories: 'category',
picture: '/picture_src',
author: 'julian',
content: 'I am content',
},
});
Read
// Filter by a single value
const post = await prisma.post.findMany({
where: {
id: {
endsWith: '001',
},
},
});
// Get all
const allPosts = await prisma.post.findMany();
Update
const updatePost = await prisma.post.update({
where: {
id: 'km-20230818001',
},
data: {
categories: 'newbie',
},
});
Delete
const deletePosts = await prisma.post.deleteMany({
where: {
author: {
contains: 'julian',
},
},
});
應用實例
- API 內容
// /api/addPosts.ts
import {NextApiRequest, NextApiResponse} from 'next';
import {PrismaClient} from '@prisma/client';
const prisma = new PrismaClient();
export default async function handler(req: NextApiRequest, res: NextApiResponse) {
// Info (20230818 - Julian) Check if the method is POST
if (req.method !== 'POST') {
return res.status(405).json({message: 'Method not allowed'});
}
const contactData = JSON.parse(req.body);
// Info (20230818 - Julian) Create a new contact in the database
const saveContact = await prisma.post.create({
data: contactData,
});
res.status(200).json(saveContact);
}
- Form component (節錄)
const FormComponent = () => {
{...}
// Info: (20230818 - Julian) Call API to add new post
const addPost = async (kmData: IKMData) => {
const response = await fetch('/api/addPost', {
method: 'POST',
body: JSON.stringify(kmData),
});
if (!response.ok) {
throw new Error(response.statusText);
}
return await response.json();
};
// Info: (20230818 - Julian) Click submit button
const submitHandler = async (event: React.FormEvent<HTMLFormElement>) => {
const newData: IKMData = {
id: kmId,
author: inputAuthorId,
title: inputTitle,
description: inputDescription,
picture: inputPicture,
categories: inputCategory,
content: inputContent,
};
try {
event.preventDefault();
await addPost(newData);
...
} catch (error) {
console.log(error);
}
};
return(...)
}