<?xml version="1.0" encoding="utf-8" standalone="yes"?>
<rss version="2.0" xmlns:atom="http://www.w3.org/2005/Atom">
    <channel>
        <title>後端開發 on Dev TLDRLSS</title>
        <link>https://dev.tldrlss.com/categories/%E5%BE%8C%E7%AB%AF%E9%96%8B%E7%99%BC/</link>
        <description>Recent content in 後端開發 on Dev TLDRLSS</description>
        <generator>Hugo -- gohugo.io</generator>
        <language>zh-tw</language>
        <lastBuildDate>Tue, 19 May 2026 18:10:00 +0800</lastBuildDate><atom:link href="https://dev.tldrlss.com/categories/%E5%BE%8C%E7%AB%AF%E9%96%8B%E7%99%BC/index.xml" rel="self" type="application/rss+xml" /><item>
        <title>別被 SQLite 的隨性騙了！動態型別的陷阱是什麼？為什麼 ALTER TABLE 是半殘的？如何在 Node.js 中做好防禦性編程實現無痛資料表升級？</title>
        <link>https://dev.tldrlss.com/article/2026/05/sqlite-pitfall-intro/</link>
        <pubDate>Tue, 19 May 2026 18:10:00 +0800</pubDate>
        
        <guid>https://dev.tldrlss.com/article/2026/05/sqlite-pitfall-intro/</guid>
        <description>&lt;img src="https://dev.tldrlss.com/global-assets/images/database/sqlite-type-pitfall-1.jpg" alt="Featured image of post 別被 SQLite 的隨性騙了！動態型別的陷阱是什麼？為什麼 ALTER TABLE 是半殘的？如何在 Node.js 中做好防禦性編程實現無痛資料表升級？" /&gt;&lt;p&gt;想像一個資源回收桶，你明明在上面貼了「寶特瓶專用」的標籤，結果別人丟了一張紙片進去，它居然默默收下，連一聲抗議都沒有？&lt;/p&gt;
&lt;p&gt;這就是開發者第一次遇見 &lt;code&gt;SQLite&lt;/code&gt; 型別系統時的驚悚體驗。&lt;/p&gt;
&lt;p&gt;如果你習慣了 &lt;code&gt;PostgreSQL&lt;/code&gt; 那種嚴格的海關作風（型別不對直接拒絕入境），&lt;code&gt;SQLite&lt;/code&gt; 的隨性程度可能會讓你懷疑人生。&lt;/p&gt;
&lt;p&gt;更恐怖的是，當你想 &lt;strong&gt;修改資料表結構&lt;/strong&gt; 時，它會告訴你：&lt;/p&gt;
&lt;blockquote&gt;
&lt;p&gt;&lt;strong&gt;「資料表改不了，請蓋一棟新房子，把傢俱搬過去，然後把舊房子炸掉。」&lt;/strong&gt;&lt;/p&gt;
&lt;/blockquote&gt;
&lt;h2 id=&#34;sqlite-底層只有-5-種儲存類別&#34;&gt;SQLite 底層只有 5 種儲存類別
&lt;/h2&gt;&lt;p&gt;不管你在 &lt;code&gt;CREATE TABLE&lt;/code&gt; 時宣告了什麼華麗的型別名稱（&lt;code&gt;VARCHAR(255)&lt;/code&gt;、&lt;code&gt;BIGINT&lt;/code&gt;、&lt;code&gt;DECIMAL&lt;/code&gt;），&lt;code&gt;SQLite&lt;/code&gt; 底層只認得這 &lt;strong&gt;5 種儲存類別&lt;/strong&gt;：&lt;/p&gt;
&lt;table&gt;
  &lt;thead&gt;
      &lt;tr&gt;
          &lt;th&gt;儲存類別&lt;/th&gt;
          &lt;th&gt;說明&lt;/th&gt;
      &lt;/tr&gt;
  &lt;/thead&gt;
  &lt;tbody&gt;
      &lt;tr&gt;
          &lt;td&gt;&lt;strong&gt;NULL&lt;/strong&gt;&lt;/td&gt;
          &lt;td&gt;空值&lt;/td&gt;
      &lt;/tr&gt;
      &lt;tr&gt;
          &lt;td&gt;&lt;strong&gt;INTEGER&lt;/strong&gt;&lt;/td&gt;
          &lt;td&gt;整數（依數字大小自動佔用 1 到 8 bytes）&lt;/td&gt;
      &lt;/tr&gt;
      &lt;tr&gt;
          &lt;td&gt;&lt;strong&gt;REAL&lt;/strong&gt;&lt;/td&gt;
          &lt;td&gt;浮點數（固定 8 bytes）&lt;/td&gt;
      &lt;/tr&gt;
      &lt;tr&gt;
          &lt;td&gt;&lt;strong&gt;TEXT&lt;/strong&gt;&lt;/td&gt;
          &lt;td&gt;字串（預設 UTF-8 編碼）&lt;/td&gt;
      &lt;/tr&gt;
      &lt;tr&gt;
          &lt;td&gt;&lt;strong&gt;BLOB&lt;/strong&gt;&lt;/td&gt;
          &lt;td&gt;二進位資料（原封不動儲存）&lt;/td&gt;
      &lt;/tr&gt;
  &lt;/tbody&gt;
&lt;/table&gt;
&lt;p&gt;你在欄位上設定的型別，對 &lt;code&gt;SQLite&lt;/code&gt; 來說 &lt;strong&gt;只是一個「建議」，而不是「強制規定」&lt;/strong&gt;。&lt;/p&gt;
&lt;blockquote&gt;
&lt;p&gt;這叫做 &lt;strong&gt;「型別親和性（Type Affinity）」&lt;/strong&gt;，&lt;code&gt;SQLite&lt;/code&gt; 會嘗試把你的資料轉成建議的型別，但如果轉不了，它照樣把原始資料塞進去，完全不報錯。&lt;/p&gt;
&lt;/blockquote&gt;
&lt;p&gt;你可以在 &lt;code&gt;SQLite&lt;/code&gt; 宣告一個 &lt;code&gt;age INTEGER&lt;/code&gt; 欄位，然後塞入字串 &lt;code&gt;&#39;永遠的十八歲&#39;&lt;/code&gt;，它會欣然接受。&lt;/p&gt;
&lt;!--adsense--&gt;
&lt;h2 id=&#34;三個最容易踩坑的型別陷阱&#34;&gt;三個最容易踩坑的型別陷阱
&lt;/h2&gt;&lt;h3 id=&#34;陷阱-1沒有原生-boolean&#34;&gt;陷阱 1：沒有原生 Boolean
&lt;/h3&gt;&lt;p&gt;&lt;code&gt;SQLite&lt;/code&gt; &lt;strong&gt;沒有布林型別&lt;/strong&gt;。&lt;code&gt;True&lt;/code&gt; 和 &lt;code&gt;False&lt;/code&gt; 只能用整數 &lt;code&gt;1&lt;/code&gt; 和 &lt;code&gt;0&lt;/code&gt; 代替。&lt;/p&gt;
&lt;p&gt;當你用 &lt;code&gt;Node.js&lt;/code&gt; 從 &lt;code&gt;SQLite&lt;/code&gt; 撈出資料時，拿到的會是數字 &lt;code&gt;1&lt;/code&gt; 或 &lt;code&gt;0&lt;/code&gt;，不是 &lt;code&gt;true&lt;/code&gt; 或 &lt;code&gt;false&lt;/code&gt;。&lt;/p&gt;
&lt;p&gt;如果你直接拿去做 &lt;code&gt;if (user.is_admin === true)&lt;/code&gt; 的判斷，永遠不會成立。&lt;/p&gt;
&lt;h3 id=&#34;陷阱-2沒有-datetime-型別&#34;&gt;陷阱 2：沒有 Date/Time 型別
&lt;/h3&gt;&lt;p&gt;&lt;code&gt;SQLite&lt;/code&gt; &lt;strong&gt;沒有日期時間型別&lt;/strong&gt;。你只能把時間存成：&lt;/p&gt;
&lt;table&gt;
  &lt;thead&gt;
      &lt;tr&gt;
          &lt;th&gt;儲存方式&lt;/th&gt;
          &lt;th&gt;範例&lt;/th&gt;
          &lt;th&gt;優缺點&lt;/th&gt;
      &lt;/tr&gt;
  &lt;/thead&gt;
  &lt;tbody&gt;
      &lt;tr&gt;
          &lt;td&gt;TEXT（ISO-8601 字串）&lt;/td&gt;
          &lt;td&gt;&lt;code&gt;&#39;2026-05-19T18:00:00Z&#39;&lt;/code&gt;&lt;/td&gt;
          &lt;td&gt;&lt;strong&gt;最推薦&lt;/strong&gt;，可讀性高，未來搬到 PostgreSQL 可無縫轉換&lt;/td&gt;
      &lt;/tr&gt;
      &lt;tr&gt;
          &lt;td&gt;INTEGER（Unix Timestamp）&lt;/td&gt;
          &lt;td&gt;&lt;code&gt;1747656000&lt;/code&gt;&lt;/td&gt;
          &lt;td&gt;佔用空間小，但人類不可讀&lt;/td&gt;
      &lt;/tr&gt;
  &lt;/tbody&gt;
&lt;/table&gt;
&lt;p&gt;千萬不要存像 &lt;code&gt;2026/5/19 下午六點&lt;/code&gt; 這種自創格式，否則未來做資料遷移時會是一場災難。&lt;/p&gt;
&lt;h3 id=&#34;陷阱-3字串塞進整數欄位不會報錯&#34;&gt;陷阱 3：字串塞進整數欄位不會報錯
&lt;/h3&gt;&lt;p&gt;在 &lt;code&gt;PostgreSQL&lt;/code&gt;，你把字串塞進 &lt;code&gt;INTEGER&lt;/code&gt; 欄位會直接噴錯。但 &lt;code&gt;SQLite&lt;/code&gt; 只會默默嘗試轉換，轉不了就原樣收下。&lt;/p&gt;
&lt;blockquote&gt;
&lt;p&gt;這意味著你的資料庫裡 &lt;strong&gt;可能悄悄混進了髒資料&lt;/strong&gt;，直到某天你的程式因為拿到非預期的型別而崩潰，才會發現問題。&lt;/p&gt;
&lt;/blockquote&gt;
&lt;h2 id=&#34;防禦性編程用嚴格的態度對待隨性的資料庫&#34;&gt;防禦性編程：用嚴格的態度對待隨性的資料庫
&lt;/h2&gt;&lt;p&gt;面對 &lt;code&gt;SQLite&lt;/code&gt; 的隨性，在 &lt;code&gt;Node.js&lt;/code&gt; 開發中必須建立起 &lt;strong&gt;嚴格的防禦機制&lt;/strong&gt;：&lt;/p&gt;
&lt;table&gt;
  &lt;thead&gt;
      &lt;tr&gt;
          &lt;th&gt;防禦層級&lt;/th&gt;
          &lt;th&gt;工具&lt;/th&gt;
          &lt;th&gt;作用&lt;/th&gt;
      &lt;/tr&gt;
  &lt;/thead&gt;
  &lt;tbody&gt;
      &lt;tr&gt;
          &lt;td&gt;編譯期擋錯&lt;/td&gt;
          &lt;td&gt;&lt;strong&gt;TypeScript&lt;/strong&gt;&lt;/td&gt;
          &lt;td&gt;在程式碼階段就擋下錯誤的型別&lt;/td&gt;
      &lt;/tr&gt;
      &lt;tr&gt;
          &lt;td&gt;API 入口驗證&lt;/td&gt;
          &lt;td&gt;&lt;strong&gt;Zod&lt;/strong&gt;&lt;/td&gt;
          &lt;td&gt;接收資料時嚴格驗證（確認 age 一定是數字）&lt;/td&gt;
      &lt;/tr&gt;
      &lt;tr&gt;
          &lt;td&gt;底層型別轉換&lt;/td&gt;
          &lt;td&gt;&lt;strong&gt;Prisma / Drizzle ORM&lt;/strong&gt;&lt;/td&gt;
          &lt;td&gt;自動處理 SQLite 和 PostgreSQL 之間的型別差異&lt;/td&gt;
      &lt;/tr&gt;
  &lt;/tbody&gt;
&lt;/table&gt;
&lt;blockquote&gt;
&lt;p&gt;把 &lt;strong&gt;「資料驗證的守門員」&lt;/strong&gt; 從資料庫層移到 &lt;code&gt;Node.js&lt;/code&gt; 應用層，這是同時駕馭 SQLite 開發速度與未來擴展性的關鍵策略。&lt;/p&gt;
&lt;/blockquote&gt;
&lt;p&gt;使用 ORM 時，你只要在程式裡宣告 &lt;code&gt;type: &#39;boolean&#39;&lt;/code&gt;，ORM 存進 &lt;code&gt;SQLite&lt;/code&gt; 時自動轉成 &lt;code&gt;1/0&lt;/code&gt;，拿出來時自動轉回 &lt;code&gt;true/false&lt;/code&gt;，完美遮蔽底層的型別差異。&lt;/p&gt;
&lt;!--adsense--&gt;
&lt;h2 id=&#34;alter-table-是半殘的什麼能改什麼不能改&#34;&gt;ALTER TABLE 是半殘的：什麼能改、什麼不能改
&lt;/h2&gt;&lt;p&gt;&lt;code&gt;SQLite&lt;/code&gt; 對資料表結構的修改支援非常有限：&lt;/p&gt;
&lt;table&gt;
  &lt;thead&gt;
      &lt;tr&gt;
          &lt;th&gt;操作&lt;/th&gt;
          &lt;th&gt;是否支援&lt;/th&gt;
      &lt;/tr&gt;
  &lt;/thead&gt;
  &lt;tbody&gt;
      &lt;tr&gt;
          &lt;td&gt;新增欄位（&lt;code&gt;ADD COLUMN&lt;/code&gt;）&lt;/td&gt;
          &lt;td&gt;&lt;strong&gt;支援&lt;/strong&gt;&lt;/td&gt;
      &lt;/tr&gt;
      &lt;tr&gt;
          &lt;td&gt;重新命名欄位（&lt;code&gt;RENAME COLUMN&lt;/code&gt;）&lt;/td&gt;
          &lt;td&gt;&lt;strong&gt;支援&lt;/strong&gt;&lt;/td&gt;
      &lt;/tr&gt;
      &lt;tr&gt;
          &lt;td&gt;刪除欄位（&lt;code&gt;DROP COLUMN&lt;/code&gt;）&lt;/td&gt;
          &lt;td&gt;&lt;strong&gt;支援&lt;/strong&gt; （較新版本）&lt;/td&gt;
      &lt;/tr&gt;
      &lt;tr&gt;
          &lt;td&gt;重新命名資料表（&lt;code&gt;RENAME TO&lt;/code&gt;）&lt;/td&gt;
          &lt;td&gt;&lt;strong&gt;支援&lt;/strong&gt;&lt;/td&gt;
      &lt;/tr&gt;
      &lt;tr&gt;
          &lt;td&gt;更改欄位型態&lt;/td&gt;
          &lt;td&gt;&lt;strong&gt;不支援&lt;/strong&gt;&lt;/td&gt;
      &lt;/tr&gt;
      &lt;tr&gt;
          &lt;td&gt;增加/移除 &lt;code&gt;UNIQUE&lt;/code&gt;、&lt;code&gt;NOT NULL&lt;/code&gt; 限制&lt;/td&gt;
          &lt;td&gt;&lt;strong&gt;不支援&lt;/strong&gt;&lt;/td&gt;
      &lt;/tr&gt;
      &lt;tr&gt;
          &lt;td&gt;修改 Primary Key&lt;/td&gt;
          &lt;td&gt;&lt;strong&gt;不支援&lt;/strong&gt;&lt;/td&gt;
      &lt;/tr&gt;
      &lt;tr&gt;
          &lt;td&gt;修改 Foreign Key&lt;/td&gt;
          &lt;td&gt;&lt;strong&gt;不支援&lt;/strong&gt;&lt;/td&gt;
      &lt;/tr&gt;
  &lt;/tbody&gt;
&lt;/table&gt;
&lt;blockquote&gt;
&lt;p&gt;一旦你需要做「不支援」的那些修改，&lt;code&gt;SQLite&lt;/code&gt; 會要求你執行 &lt;strong&gt;「重建並搬移」&lt;/strong&gt; 的策略。&lt;/p&gt;
&lt;/blockquote&gt;
&lt;h2 id=&#34;重建搬移四步曲sqlite-的資料表升級之道&#34;&gt;重建搬移四步曲：SQLite 的資料表升級之道
&lt;/h2&gt;&lt;p&gt;既然無法直接改，官方建議的標準做法就是 &lt;strong&gt;蓋新房子、搬傢俱、炸舊房子、貼新門牌&lt;/strong&gt;：&lt;/p&gt;
&lt;table&gt;
  &lt;thead&gt;
      &lt;tr&gt;
          &lt;th&gt;步驟&lt;/th&gt;
          &lt;th&gt;動作&lt;/th&gt;
          &lt;th&gt;說明&lt;/th&gt;
      &lt;/tr&gt;
  &lt;/thead&gt;
  &lt;tbody&gt;
      &lt;tr&gt;
          &lt;td&gt;1&lt;/td&gt;
          &lt;td&gt;建立新表&lt;/td&gt;
          &lt;td&gt;&lt;code&gt;CREATE TABLE users_new (...)&lt;/code&gt; 使用正確的結構&lt;/td&gt;
      &lt;/tr&gt;
      &lt;tr&gt;
          &lt;td&gt;2&lt;/td&gt;
          &lt;td&gt;複製資料&lt;/td&gt;
          &lt;td&gt;&lt;code&gt;INSERT INTO users_new SELECT ... FROM users&lt;/code&gt;&lt;/td&gt;
      &lt;/tr&gt;
      &lt;tr&gt;
          &lt;td&gt;3&lt;/td&gt;
          &lt;td&gt;刪除舊表&lt;/td&gt;
          &lt;td&gt;&lt;code&gt;DROP TABLE users&lt;/code&gt;&lt;/td&gt;
      &lt;/tr&gt;
      &lt;tr&gt;
          &lt;td&gt;4&lt;/td&gt;
          &lt;td&gt;改名&lt;/td&gt;
          &lt;td&gt;&lt;code&gt;ALTER TABLE users_new RENAME TO users&lt;/code&gt;&lt;/td&gt;
      &lt;/tr&gt;
  &lt;/tbody&gt;
&lt;/table&gt;
&lt;p&gt;這四個步驟必須 &lt;strong&gt;一氣呵成&lt;/strong&gt;，中途斷電或程式出錯就會造成資料遺失。&lt;/p&gt;
&lt;!--adsense--&gt;
&lt;h2 id=&#34;確保升級不遺失資料兩道安全防線&#34;&gt;確保升級不遺失資料：兩道安全防線
&lt;/h2&gt;&lt;h3 id=&#34;防線-1物理防禦直接複製檔案&#34;&gt;防線 1：物理防禦，直接複製檔案
&lt;/h3&gt;&lt;p&gt;&lt;code&gt;SQLite&lt;/code&gt; 的本質就是一個檔案。在執行任何結構變更前，先把 &lt;code&gt;.db&lt;/code&gt; 檔案複製一份當備份。&lt;/p&gt;
&lt;div class=&#34;highlight&#34;&gt;&lt;pre tabindex=&#34;0&#34; class=&#34;chroma&#34;&gt;&lt;code class=&#34;language-javascript&#34; data-lang=&#34;javascript&#34;&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;cl&#34;&gt;&lt;span class=&#34;kr&#34;&gt;const&lt;/span&gt; &lt;span class=&#34;nx&#34;&gt;fs&lt;/span&gt; &lt;span class=&#34;o&#34;&gt;=&lt;/span&gt; &lt;span class=&#34;nx&#34;&gt;require&lt;/span&gt;&lt;span class=&#34;p&#34;&gt;(&lt;/span&gt;&lt;span class=&#34;s1&#34;&gt;&amp;#39;fs&amp;#39;&lt;/span&gt;&lt;span class=&#34;p&#34;&gt;);&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;cl&#34;&gt;&lt;span class=&#34;nx&#34;&gt;fs&lt;/span&gt;&lt;span class=&#34;p&#34;&gt;.&lt;/span&gt;&lt;span class=&#34;nx&#34;&gt;copyFileSync&lt;/span&gt;&lt;span class=&#34;p&#34;&gt;(&lt;/span&gt;&lt;span class=&#34;s1&#34;&gt;&amp;#39;my_project.db&amp;#39;&lt;/span&gt;&lt;span class=&#34;p&#34;&gt;,&lt;/span&gt; &lt;span class=&#34;s1&#34;&gt;&amp;#39;my_project_backup.db&amp;#39;&lt;/span&gt;&lt;span class=&#34;p&#34;&gt;);&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;blockquote&gt;
&lt;p&gt;萬一搞砸了，檔案蓋回去就瞬間復原。這是其他大型資料庫做不到的優勢。&lt;/p&gt;
&lt;/blockquote&gt;
&lt;h3 id=&#34;防線-2transaction-包裝資料庫的時光機&#34;&gt;防線 2：Transaction 包裝，資料庫的時光機
&lt;/h3&gt;&lt;p&gt;把搬家的步驟包在一個 &lt;code&gt;Transaction&lt;/code&gt; 裡面，只要其中一步失敗，整個過程就會 &lt;strong&gt;自動還原（Rollback）&lt;/strong&gt;，當作沒發生過。&lt;/p&gt;
&lt;div class=&#34;highlight&#34;&gt;&lt;pre tabindex=&#34;0&#34; class=&#34;chroma&#34;&gt;&lt;code class=&#34;language-javascript&#34; data-lang=&#34;javascript&#34;&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;cl&#34;&gt;&lt;span class=&#34;kr&#34;&gt;const&lt;/span&gt; &lt;span class=&#34;nx&#34;&gt;Database&lt;/span&gt; &lt;span class=&#34;o&#34;&gt;=&lt;/span&gt; &lt;span class=&#34;nx&#34;&gt;require&lt;/span&gt;&lt;span class=&#34;p&#34;&gt;(&lt;/span&gt;&lt;span class=&#34;s1&#34;&gt;&amp;#39;better-sqlite3&amp;#39;&lt;/span&gt;&lt;span class=&#34;p&#34;&gt;);&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;cl&#34;&gt;&lt;span class=&#34;kr&#34;&gt;const&lt;/span&gt; &lt;span class=&#34;nx&#34;&gt;db&lt;/span&gt; &lt;span class=&#34;o&#34;&gt;=&lt;/span&gt; &lt;span class=&#34;k&#34;&gt;new&lt;/span&gt; &lt;span class=&#34;nx&#34;&gt;Database&lt;/span&gt;&lt;span class=&#34;p&#34;&gt;(&lt;/span&gt;&lt;span class=&#34;s1&#34;&gt;&amp;#39;my_project.db&amp;#39;&lt;/span&gt;&lt;span class=&#34;p&#34;&gt;);&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;cl&#34;&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;cl&#34;&gt;&lt;span class=&#34;kr&#34;&gt;const&lt;/span&gt; &lt;span class=&#34;nx&#34;&gt;migrateData&lt;/span&gt; &lt;span class=&#34;o&#34;&gt;=&lt;/span&gt; &lt;span class=&#34;nx&#34;&gt;db&lt;/span&gt;&lt;span class=&#34;p&#34;&gt;.&lt;/span&gt;&lt;span class=&#34;nx&#34;&gt;transaction&lt;/span&gt;&lt;span class=&#34;p&#34;&gt;(()&lt;/span&gt; &lt;span class=&#34;p&#34;&gt;=&amp;gt;&lt;/span&gt; &lt;span class=&#34;p&#34;&gt;{&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;cl&#34;&gt;  &lt;span class=&#34;nx&#34;&gt;db&lt;/span&gt;&lt;span class=&#34;p&#34;&gt;.&lt;/span&gt;&lt;span class=&#34;nx&#34;&gt;prepare&lt;/span&gt;&lt;span class=&#34;p&#34;&gt;(&lt;/span&gt;&lt;span class=&#34;sb&#34;&gt;`
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;cl&#34;&gt;&lt;span class=&#34;sb&#34;&gt;    CREATE TABLE users_new (
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;cl&#34;&gt;&lt;span class=&#34;sb&#34;&gt;      id INTEGER PRIMARY KEY AUTOINCREMENT,
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;cl&#34;&gt;&lt;span class=&#34;sb&#34;&gt;      name TEXT NOT NULL,
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;cl&#34;&gt;&lt;span class=&#34;sb&#34;&gt;      age INTEGER NOT NULL DEFAULT 18
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;cl&#34;&gt;&lt;span class=&#34;sb&#34;&gt;    )
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;cl&#34;&gt;&lt;span class=&#34;sb&#34;&gt;  `&lt;/span&gt;&lt;span class=&#34;p&#34;&gt;).&lt;/span&gt;&lt;span class=&#34;nx&#34;&gt;run&lt;/span&gt;&lt;span class=&#34;p&#34;&gt;();&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;cl&#34;&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;cl&#34;&gt;  &lt;span class=&#34;nx&#34;&gt;db&lt;/span&gt;&lt;span class=&#34;p&#34;&gt;.&lt;/span&gt;&lt;span class=&#34;nx&#34;&gt;prepare&lt;/span&gt;&lt;span class=&#34;p&#34;&gt;(&lt;/span&gt;&lt;span class=&#34;sb&#34;&gt;`
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;cl&#34;&gt;&lt;span class=&#34;sb&#34;&gt;    INSERT INTO users_new (id, name, age)
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;cl&#34;&gt;&lt;span class=&#34;sb&#34;&gt;    SELECT id, name, COALESCE(age, 18) FROM users
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;cl&#34;&gt;&lt;span class=&#34;sb&#34;&gt;  `&lt;/span&gt;&lt;span class=&#34;p&#34;&gt;).&lt;/span&gt;&lt;span class=&#34;nx&#34;&gt;run&lt;/span&gt;&lt;span class=&#34;p&#34;&gt;();&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;cl&#34;&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;cl&#34;&gt;  &lt;span class=&#34;nx&#34;&gt;db&lt;/span&gt;&lt;span class=&#34;p&#34;&gt;.&lt;/span&gt;&lt;span class=&#34;nx&#34;&gt;prepare&lt;/span&gt;&lt;span class=&#34;p&#34;&gt;(&lt;/span&gt;&lt;span class=&#34;s1&#34;&gt;&amp;#39;DROP TABLE users&amp;#39;&lt;/span&gt;&lt;span class=&#34;p&#34;&gt;).&lt;/span&gt;&lt;span class=&#34;nx&#34;&gt;run&lt;/span&gt;&lt;span class=&#34;p&#34;&gt;();&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;cl&#34;&gt;  &lt;span class=&#34;nx&#34;&gt;db&lt;/span&gt;&lt;span class=&#34;p&#34;&gt;.&lt;/span&gt;&lt;span class=&#34;nx&#34;&gt;prepare&lt;/span&gt;&lt;span class=&#34;p&#34;&gt;(&lt;/span&gt;&lt;span class=&#34;s1&#34;&gt;&amp;#39;ALTER TABLE users_new RENAME TO users&amp;#39;&lt;/span&gt;&lt;span class=&#34;p&#34;&gt;).&lt;/span&gt;&lt;span class=&#34;nx&#34;&gt;run&lt;/span&gt;&lt;span class=&#34;p&#34;&gt;();&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;cl&#34;&gt;&lt;span class=&#34;p&#34;&gt;});&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;cl&#34;&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;cl&#34;&gt;&lt;span class=&#34;k&#34;&gt;try&lt;/span&gt; &lt;span class=&#34;p&#34;&gt;{&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;cl&#34;&gt;  &lt;span class=&#34;nx&#34;&gt;migrateData&lt;/span&gt;&lt;span class=&#34;p&#34;&gt;();&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;cl&#34;&gt;  &lt;span class=&#34;nx&#34;&gt;console&lt;/span&gt;&lt;span class=&#34;p&#34;&gt;.&lt;/span&gt;&lt;span class=&#34;nx&#34;&gt;log&lt;/span&gt;&lt;span class=&#34;p&#34;&gt;(&lt;/span&gt;&lt;span class=&#34;s1&#34;&gt;&amp;#39;資料表升級成功&amp;#39;&lt;/span&gt;&lt;span class=&#34;p&#34;&gt;);&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;cl&#34;&gt;&lt;span class=&#34;p&#34;&gt;}&lt;/span&gt; &lt;span class=&#34;k&#34;&gt;catch&lt;/span&gt; &lt;span class=&#34;p&#34;&gt;(&lt;/span&gt;&lt;span class=&#34;nx&#34;&gt;error&lt;/span&gt;&lt;span class=&#34;p&#34;&gt;)&lt;/span&gt; &lt;span class=&#34;p&#34;&gt;{&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;cl&#34;&gt;  &lt;span class=&#34;nx&#34;&gt;console&lt;/span&gt;&lt;span class=&#34;p&#34;&gt;.&lt;/span&gt;&lt;span class=&#34;nx&#34;&gt;error&lt;/span&gt;&lt;span class=&#34;p&#34;&gt;(&lt;/span&gt;&lt;span class=&#34;s1&#34;&gt;&amp;#39;升級失敗，資料已安全還原：&amp;#39;&lt;/span&gt;&lt;span class=&#34;p&#34;&gt;,&lt;/span&gt; &lt;span class=&#34;nx&#34;&gt;error&lt;/span&gt;&lt;span class=&#34;p&#34;&gt;.&lt;/span&gt;&lt;span class=&#34;nx&#34;&gt;message&lt;/span&gt;&lt;span class=&#34;p&#34;&gt;);&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;cl&#34;&gt;&lt;span class=&#34;p&#34;&gt;}&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;blockquote&gt;
&lt;p&gt;&lt;strong&gt;「備份檔案 + Transaction 綁定」&lt;/strong&gt; 就是你的資料庫遷移的安全氣囊。&lt;/p&gt;
&lt;/blockquote&gt;
&lt;h2 id=&#34;用嚴格的態度駕馭隨性的-sqlite&#34;&gt;用嚴格的態度駕馭隨性的 SQLite
&lt;/h2&gt;&lt;blockquote&gt;
&lt;p&gt;用 &lt;strong&gt;嚴格的應用層架構&lt;/strong&gt; 來駕馭隨性的 &lt;code&gt;SQLite&lt;/code&gt;，享受它極速開發的優勢，同時避開未來的技術債。&lt;/p&gt;
&lt;/blockquote&gt;
&lt;p&gt;&lt;code&gt;SQLite&lt;/code&gt; 的型別系統很隨性，&lt;code&gt;ALTER TABLE&lt;/code&gt; 也有諸多限制。&lt;/p&gt;
&lt;p&gt;但只要在 &lt;code&gt;Node.js&lt;/code&gt; 端做好 &lt;strong&gt;TypeScript 型別檢查 + Zod 驗證 + ORM 抽象層&lt;/strong&gt;，再搭配 &lt;strong&gt;物理備份 + Transaction&lt;/strong&gt; 的安全策略，你就能安心享受 &lt;code&gt;SQLite&lt;/code&gt; 帶來的開發效率，同時為未來可能搬移至 &lt;code&gt;PostgreSQL&lt;/code&gt; 鋪好後路。&lt;/p&gt;
&lt;!--adsense--&gt;
&lt;h2 id=&#34;reference&#34;&gt;Reference
&lt;/h2&gt;&lt;ul&gt;
&lt;li&gt;&lt;a class=&#34;link&#34; href=&#34;https://sqlite.org/datatype3.html&#34;  target=&#34;_blank&#34; rel=&#34;noopener&#34;
    &gt;Datatypes In SQLite&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a class=&#34;link&#34; href=&#34;https://www.runoob.com/sqlite/sqlite-data-types.html&#34;  target=&#34;_blank&#34; rel=&#34;noopener&#34;
    &gt;SQLite 数据类型 | 菜鸟教程&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a class=&#34;link&#34; href=&#34;https://www.sqlite.org/lang_transaction.html&#34;  target=&#34;_blank&#34; rel=&#34;noopener&#34;
    &gt;Transaction&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a class=&#34;link&#34; href=&#34;https://sqlite.org/lang.html&#34;  target=&#34;_blank&#34; rel=&#34;noopener&#34;
    &gt;Query Language Understood by SQLite&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a class=&#34;link&#34; href=&#34;https://en.wikipedia.org/wiki/SQLite&#34;  target=&#34;_blank&#34; rel=&#34;noopener&#34;
    &gt;SQLite - Wikipedia&lt;/a&gt;&lt;/li&gt;
&lt;/ul&gt;
</description>
        </item>
        <item>
        <title>不要什麼都用 PostgreSQL！SQLite 的嵌入式架構與零設定優勢是什麼？SQLite 的極限在哪？什麼時候該選 SQLite、什麼時候該選 PostgreSQL？</title>
        <link>https://dev.tldrlss.com/article/2026/05/sqlite-intro/</link>
        <pubDate>Tue, 19 May 2026 18:00:00 +0800</pubDate>
        
        <guid>https://dev.tldrlss.com/article/2026/05/sqlite-intro/</guid>
        <description>&lt;img src="https://dev.tldrlss.com/global-assets/images/database/sqlite-vs-postgresql-choice-2.jpg" alt="Featured image of post 不要什麼都用 PostgreSQL！SQLite 的嵌入式架構與零設定優勢是什麼？SQLite 的極限在哪？什麼時候該選 SQLite、什麼時候該選 PostgreSQL？" /&gt;&lt;p&gt;你有沒有想過，你每天打開的瀏覽器、手機裡的通訊軟體、甚至桌面上的筆記工具，裡面都藏著同一個輕巧的資料庫？&lt;/p&gt;
&lt;p&gt;它不需要你安裝任何伺服器軟體，不需要設定帳號密碼，甚至不需要網路連線。它就是一個 &lt;strong&gt;檔案&lt;/strong&gt;，靜靜地躺在你的硬碟裡，隨時準備好為你服務。&lt;/p&gt;
&lt;p&gt;這個低調的存在，就是 &lt;code&gt;SQLite&lt;/code&gt;。&lt;/p&gt;
&lt;h2 id=&#34;什麼是-sqlite世界上部署最廣泛的資料庫&#34;&gt;什麼是 SQLite？世界上部署最廣泛的資料庫
&lt;/h2&gt;&lt;p&gt;&lt;code&gt;SQLite&lt;/code&gt; 是一個用 C 語言撰寫的 &lt;strong&gt;嵌入式關聯式資料庫引擎&lt;/strong&gt;。&lt;/p&gt;
&lt;blockquote&gt;
&lt;p&gt;它&lt;strong&gt;沒有獨立的伺服器程式&lt;/strong&gt;，而是直接嵌入你的應用程式中運作。&lt;/p&gt;
&lt;/blockquote&gt;
&lt;p&gt;這跟你熟悉的 &lt;code&gt;PostgreSQL&lt;/code&gt; 或 &lt;code&gt;MySQL&lt;/code&gt; 完全不同。傳統資料庫是一台 &lt;strong&gt;獨立運作的伺服器&lt;/strong&gt;，你的程式必須透過 &lt;strong&gt;網路協定（TCP/IP）&lt;/strong&gt; 去跟它「通訊」。&lt;/p&gt;
&lt;p&gt;但 &lt;code&gt;SQLite&lt;/code&gt; 不一樣，它就是一段程式碼，直接跑在你的應用程式裡面，讀寫硬碟上的那個 &lt;code&gt;.db&lt;/code&gt; 檔案。&lt;/p&gt;
&lt;table&gt;
  &lt;thead&gt;
      &lt;tr&gt;
          &lt;th&gt;比較維度&lt;/th&gt;
          &lt;th&gt;SQLite（嵌入式）&lt;/th&gt;
          &lt;th&gt;PostgreSQL（Client-Server）&lt;/th&gt;
      &lt;/tr&gt;
  &lt;/thead&gt;
  &lt;tbody&gt;
      &lt;tr&gt;
          &lt;td&gt;運作方式&lt;/td&gt;
          &lt;td&gt;直接嵌入應用程式，無獨立伺服器&lt;/td&gt;
          &lt;td&gt;獨立伺服器程式，透過網路連線&lt;/td&gt;
      &lt;/tr&gt;
      &lt;tr&gt;
          &lt;td&gt;設定需求&lt;/td&gt;
          &lt;td&gt;&lt;strong&gt;零設定&lt;/strong&gt;，免安裝、免帳密&lt;/td&gt;
          &lt;td&gt;需要安裝、設定帳號密碼與防火牆&lt;/td&gt;
      &lt;/tr&gt;
      &lt;tr&gt;
          &lt;td&gt;資料儲存&lt;/td&gt;
          &lt;td&gt;&lt;strong&gt;單一跨平台檔案&lt;/strong&gt;&lt;/td&gt;
          &lt;td&gt;伺服器目錄下的多個檔案&lt;/td&gt;
      &lt;/tr&gt;
      &lt;tr&gt;
          &lt;td&gt;備份方式&lt;/td&gt;
          &lt;td&gt;直接複製那個檔案&lt;/td&gt;
          &lt;td&gt;需要使用 pg_dump 等專用工具&lt;/td&gt;
      &lt;/tr&gt;
  &lt;/tbody&gt;
&lt;/table&gt;
&lt;p&gt;正因為這種 &lt;strong&gt;「隨插即用」&lt;/strong&gt; 的特性，&lt;code&gt;SQLite&lt;/code&gt; 成為了世界上部署最廣泛的資料庫引擎。&lt;/p&gt;
&lt;p&gt;從 Android 和 iOS 作業系統、Chrome 和 Firefox 瀏覽器，到 Adobe Lightroom、WhatsApp，甚至是 Airbus A350 的飛行系統，處處都有它的身影。&lt;/p&gt;
&lt;!--adsense--&gt;
&lt;h2 id=&#34;在-nodejs-中使用-sqlite&#34;&gt;在 Node.js 中使用 SQLite
&lt;/h2&gt;&lt;p&gt;如果你是 Node.js 開發者，使用 &lt;code&gt;SQLite&lt;/code&gt; 非常簡單。你不需要在電腦上安裝任何資料庫伺服器軟體，只需要安裝一個 npm 套件就能開始。&lt;/p&gt;
&lt;p&gt;目前業界最常用的選擇：&lt;/p&gt;
&lt;table&gt;
  &lt;thead&gt;
      &lt;tr&gt;
          &lt;th&gt;套件名稱&lt;/th&gt;
          &lt;th&gt;特點&lt;/th&gt;
          &lt;th&gt;建議情境&lt;/th&gt;
      &lt;/tr&gt;
  &lt;/thead&gt;
  &lt;tbody&gt;
      &lt;tr&gt;
          &lt;td&gt;&lt;code&gt;sqlite3&lt;/code&gt;&lt;/td&gt;
          &lt;td&gt;最老牌，支援非同步 API&lt;/td&gt;
          &lt;td&gt;需要大量同時處理多個非同步任務&lt;/td&gt;
      &lt;/tr&gt;
      &lt;tr&gt;
          &lt;td&gt;&lt;code&gt;better-sqlite3&lt;/code&gt;&lt;/td&gt;
          &lt;td&gt;&lt;strong&gt;效能極佳&lt;/strong&gt;，API 設計直覺，速度極快&lt;/td&gt;
          &lt;td&gt;&lt;strong&gt;首選推薦&lt;/strong&gt;，追求開發效率與執行速度&lt;/td&gt;
      &lt;/tr&gt;
  &lt;/tbody&gt;
&lt;/table&gt;
&lt;p&gt;用 &lt;code&gt;better-sqlite3&lt;/code&gt; 建立資料庫並查詢，不用五分鐘就能完成：&lt;/p&gt;
&lt;div class=&#34;highlight&#34;&gt;&lt;pre tabindex=&#34;0&#34; class=&#34;chroma&#34;&gt;&lt;code class=&#34;language-javascript&#34; data-lang=&#34;javascript&#34;&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;cl&#34;&gt;&lt;span class=&#34;kr&#34;&gt;const&lt;/span&gt; &lt;span class=&#34;nx&#34;&gt;Database&lt;/span&gt; &lt;span class=&#34;o&#34;&gt;=&lt;/span&gt; &lt;span class=&#34;nx&#34;&gt;require&lt;/span&gt;&lt;span class=&#34;p&#34;&gt;(&lt;/span&gt;&lt;span class=&#34;s1&#34;&gt;&amp;#39;better-sqlite3&amp;#39;&lt;/span&gt;&lt;span class=&#34;p&#34;&gt;);&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;cl&#34;&gt;&lt;span class=&#34;kr&#34;&gt;const&lt;/span&gt; &lt;span class=&#34;nx&#34;&gt;db&lt;/span&gt; &lt;span class=&#34;o&#34;&gt;=&lt;/span&gt; &lt;span class=&#34;k&#34;&gt;new&lt;/span&gt; &lt;span class=&#34;nx&#34;&gt;Database&lt;/span&gt;&lt;span class=&#34;p&#34;&gt;(&lt;/span&gt;&lt;span class=&#34;s1&#34;&gt;&amp;#39;my_project.db&amp;#39;&lt;/span&gt;&lt;span class=&#34;p&#34;&gt;);&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;cl&#34;&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;cl&#34;&gt;&lt;span class=&#34;nx&#34;&gt;db&lt;/span&gt;&lt;span class=&#34;p&#34;&gt;.&lt;/span&gt;&lt;span class=&#34;nx&#34;&gt;prepare&lt;/span&gt;&lt;span class=&#34;p&#34;&gt;(&lt;/span&gt;&lt;span class=&#34;s1&#34;&gt;&amp;#39;CREATE TABLE IF NOT EXISTS users (name TEXT, age INTEGER)&amp;#39;&lt;/span&gt;&lt;span class=&#34;p&#34;&gt;).&lt;/span&gt;&lt;span class=&#34;nx&#34;&gt;run&lt;/span&gt;&lt;span class=&#34;p&#34;&gt;();&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;cl&#34;&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;cl&#34;&gt;&lt;span class=&#34;kr&#34;&gt;const&lt;/span&gt; &lt;span class=&#34;nx&#34;&gt;insert&lt;/span&gt; &lt;span class=&#34;o&#34;&gt;=&lt;/span&gt; &lt;span class=&#34;nx&#34;&gt;db&lt;/span&gt;&lt;span class=&#34;p&#34;&gt;.&lt;/span&gt;&lt;span class=&#34;nx&#34;&gt;prepare&lt;/span&gt;&lt;span class=&#34;p&#34;&gt;(&lt;/span&gt;&lt;span class=&#34;s1&#34;&gt;&amp;#39;INSERT INTO users (name, age) VALUES (?, ?)&amp;#39;&lt;/span&gt;&lt;span class=&#34;p&#34;&gt;);&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;cl&#34;&gt;&lt;span class=&#34;nx&#34;&gt;insert&lt;/span&gt;&lt;span class=&#34;p&#34;&gt;.&lt;/span&gt;&lt;span class=&#34;nx&#34;&gt;run&lt;/span&gt;&lt;span class=&#34;p&#34;&gt;(&lt;/span&gt;&lt;span class=&#34;s1&#34;&gt;&amp;#39;John&amp;#39;&lt;/span&gt;&lt;span class=&#34;p&#34;&gt;,&lt;/span&gt; &lt;span class=&#34;mi&#34;&gt;25&lt;/span&gt;&lt;span class=&#34;p&#34;&gt;);&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;cl&#34;&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;cl&#34;&gt;&lt;span class=&#34;kr&#34;&gt;const&lt;/span&gt; &lt;span class=&#34;nx&#34;&gt;user&lt;/span&gt; &lt;span class=&#34;o&#34;&gt;=&lt;/span&gt; &lt;span class=&#34;nx&#34;&gt;db&lt;/span&gt;&lt;span class=&#34;p&#34;&gt;.&lt;/span&gt;&lt;span class=&#34;nx&#34;&gt;prepare&lt;/span&gt;&lt;span class=&#34;p&#34;&gt;(&lt;/span&gt;&lt;span class=&#34;s1&#34;&gt;&amp;#39;SELECT * FROM users WHERE name = ?&amp;#39;&lt;/span&gt;&lt;span class=&#34;p&#34;&gt;).&lt;/span&gt;&lt;span class=&#34;nx&#34;&gt;get&lt;/span&gt;&lt;span class=&#34;p&#34;&gt;(&lt;/span&gt;&lt;span class=&#34;s1&#34;&gt;&amp;#39;John&amp;#39;&lt;/span&gt;&lt;span class=&#34;p&#34;&gt;);&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;cl&#34;&gt;&lt;span class=&#34;nx&#34;&gt;console&lt;/span&gt;&lt;span class=&#34;p&#34;&gt;.&lt;/span&gt;&lt;span class=&#34;nx&#34;&gt;log&lt;/span&gt;&lt;span class=&#34;p&#34;&gt;(&lt;/span&gt;&lt;span class=&#34;nx&#34;&gt;user&lt;/span&gt;&lt;span class=&#34;p&#34;&gt;);&lt;/span&gt; &lt;span class=&#34;c1&#34;&gt;// { name: &amp;#39;John&amp;#39;, age: 25 }
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;blockquote&gt;
&lt;p&gt;如果檔案不存在，&lt;code&gt;better-sqlite3&lt;/code&gt; 會 &lt;strong&gt;自動幫你建立&lt;/strong&gt;。&lt;/p&gt;
&lt;/blockquote&gt;
&lt;h2 id=&#34;sqlite-支援的-sql-語法比你想的強大&#34;&gt;SQLite 支援的 SQL 語法比你想的強大
&lt;/h2&gt;&lt;p&gt;很多人以為 &lt;code&gt;SQLite&lt;/code&gt; 很陽春，但它支援絕大多數的標準 SQL 語法，包含許多進階功能：&lt;/p&gt;
&lt;table&gt;
  &lt;thead&gt;
      &lt;tr&gt;
          &lt;th&gt;語法類別&lt;/th&gt;
          &lt;th&gt;支援項目&lt;/th&gt;
      &lt;/tr&gt;
  &lt;/thead&gt;
  &lt;tbody&gt;
      &lt;tr&gt;
          &lt;td&gt;基礎操作&lt;/td&gt;
          &lt;td&gt;&lt;code&gt;SELECT&lt;/code&gt;、&lt;code&gt;INSERT&lt;/code&gt;、&lt;code&gt;UPDATE&lt;/code&gt;、&lt;code&gt;DELETE&lt;/code&gt;&lt;/td&gt;
      &lt;/tr&gt;
      &lt;tr&gt;
          &lt;td&gt;資料定義&lt;/td&gt;
          &lt;td&gt;&lt;code&gt;CREATE TABLE&lt;/code&gt;、&lt;code&gt;CREATE INDEX&lt;/code&gt;、&lt;code&gt;CREATE VIEW&lt;/code&gt;&lt;/td&gt;
      &lt;/tr&gt;
      &lt;tr&gt;
          &lt;td&gt;進階查詢&lt;/td&gt;
          &lt;td&gt;&lt;code&gt;WITH&lt;/code&gt; （CTE 遞迴查詢）、視窗函數（Window Functions）&lt;/td&gt;
      &lt;/tr&gt;
      &lt;tr&gt;
          &lt;td&gt;衝突處理&lt;/td&gt;
          &lt;td&gt;&lt;code&gt;UPSERT&lt;/code&gt; （&lt;code&gt;INSERT ... ON CONFLICT DO UPDATE&lt;/code&gt;）&lt;/td&gt;
      &lt;/tr&gt;
      &lt;tr&gt;
          &lt;td&gt;JSON 處理&lt;/td&gt;
          &lt;td&gt;&lt;code&gt;json_extract&lt;/code&gt;、&lt;code&gt;json_array&lt;/code&gt;、&lt;code&gt;json_object&lt;/code&gt; 等內建函數&lt;/td&gt;
      &lt;/tr&gt;
      &lt;tr&gt;
          &lt;td&gt;交易控制&lt;/td&gt;
          &lt;td&gt;&lt;code&gt;BEGIN&lt;/code&gt;、&lt;code&gt;COMMIT&lt;/code&gt;、&lt;code&gt;ROLLBACK&lt;/code&gt;、&lt;code&gt;SAVEPOINT&lt;/code&gt;&lt;/td&gt;
      &lt;/tr&gt;
      &lt;tr&gt;
          &lt;td&gt;連接查詢&lt;/td&gt;
          &lt;td&gt;&lt;code&gt;INNER JOIN&lt;/code&gt;、&lt;code&gt;LEFT JOIN&lt;/code&gt;、&lt;code&gt;RIGHT JOIN&lt;/code&gt; 和 &lt;code&gt;FULL OUTER JOIN&lt;/code&gt;&lt;/td&gt;
      &lt;/tr&gt;
  &lt;/tbody&gt;
&lt;/table&gt;
&lt;blockquote&gt;
&lt;p&gt;&lt;strong&gt;SQLite 的核心哲學是「小而美」&lt;/strong&gt;，它支援多數你日常需要的 SQL 能力，同時保持極致的輕量。&lt;/p&gt;
&lt;/blockquote&gt;
&lt;!--adsense--&gt;
&lt;h2 id=&#34;sqlite-的極限在哪&#34;&gt;SQLite 的極限在哪？
&lt;/h2&gt;&lt;p&gt;輕巧是有代價的。如果把 &lt;code&gt;SQLite&lt;/code&gt; 比喻為一間 &lt;strong&gt;只有一個結帳櫃檯的文青雜貨店&lt;/strong&gt;，那 &lt;code&gt;PostgreSQL&lt;/code&gt; 就是 &lt;strong&gt;配備 50 個收銀台的好市多&lt;/strong&gt;。&lt;/p&gt;
&lt;h3 id=&#34;1-寫入大塞車&#34;&gt;1. 寫入大塞車
&lt;/h3&gt;&lt;p&gt;&lt;code&gt;SQLite&lt;/code&gt; 在寫入資料時，會把整個資料庫檔案 &lt;strong&gt;鎖起來&lt;/strong&gt;。&lt;/p&gt;
&lt;blockquote&gt;
&lt;p&gt;想像一間 &lt;strong&gt;只有一間廁所的餐廳&lt;/strong&gt;：100 個人可以同時在外面看菜單（讀取），但只要有 1 個人進去把門鎖上（寫入），其他人就只能排隊等。&lt;/p&gt;
&lt;/blockquote&gt;
&lt;p&gt;雖然開啟 &lt;strong&gt;WAL 模式 （Write-Ahead Logging）&lt;/strong&gt; 能改善讀寫並行的效能，但本質上仍然無法做到多執行緒同時寫入不同的資料行。&lt;/p&gt;
&lt;h3 id=&#34;2-無法跨多台伺服器&#34;&gt;2. 無法跨多台伺服器
&lt;/h3&gt;&lt;p&gt;&lt;code&gt;SQLite&lt;/code&gt; 的本質就是一個實體檔案。如果你的系統部署在多台伺服器上（水平擴展），這些伺服器 &lt;strong&gt;無法安全地共用同一個檔案&lt;/strong&gt;。&lt;/p&gt;
&lt;h3 id=&#34;3-缺乏精細的權限管理&#34;&gt;3. 缺乏精細的權限管理
&lt;/h3&gt;&lt;p&gt;&lt;code&gt;SQLite&lt;/code&gt; 沒有「使用者帳號」的概念。誰能在作業系統層面讀取那個 &lt;code&gt;.db&lt;/code&gt; 檔案，誰就能看光、修改所有資料。&lt;/p&gt;
&lt;p&gt;對於 &lt;strong&gt;需要嚴格個資審查的商務系統&lt;/strong&gt;，這是無法接受的。&lt;/p&gt;
&lt;h2 id=&#34;sqlite-vs-postgresql技術選型決策&#34;&gt;SQLite vs. PostgreSQL：技術選型決策
&lt;/h2&gt;&lt;p&gt;工具沒有好壞，只有適不適合。以下是一份幫助你做決策的終極檢核表：&lt;/p&gt;
&lt;table&gt;
  &lt;thead&gt;
      &lt;tr&gt;
          &lt;th&gt;提問&lt;/th&gt;
          &lt;th&gt;答「是」→ 選 SQLite&lt;/th&gt;
          &lt;th&gt;答「否」→ 選 PostgreSQL&lt;/th&gt;
      &lt;/tr&gt;
  &lt;/thead&gt;
  &lt;tbody&gt;
      &lt;tr&gt;
          &lt;td&gt;後端伺服器是不是只有一台，或完全跑在本機端？&lt;/td&gt;
          &lt;td&gt;是&lt;/td&gt;
          &lt;td&gt;否（有水平擴展、多台機器）&lt;/td&gt;
      &lt;/tr&gt;
      &lt;tr&gt;
          &lt;td&gt;系統行為是不是讀遠遠大於寫，且沒有高頻並行寫入？&lt;/td&gt;
          &lt;td&gt;是&lt;/td&gt;
          &lt;td&gt;否（大家會同時搶著寫入）&lt;/td&gt;
      &lt;/tr&gt;
      &lt;tr&gt;
          &lt;td&gt;不需要精細的資料庫帳號權限或進階索引？&lt;/td&gt;
          &lt;td&gt;是&lt;/td&gt;
          &lt;td&gt;否（極度依賴進階功能）&lt;/td&gt;
      &lt;/tr&gt;
  &lt;/tbody&gt;
&lt;/table&gt;
&lt;p&gt;更具體的情境對照：&lt;/p&gt;
&lt;table&gt;
  &lt;thead&gt;
      &lt;tr&gt;
          &lt;th&gt;情境&lt;/th&gt;
          &lt;th&gt;推薦選擇&lt;/th&gt;
          &lt;th&gt;理由&lt;/th&gt;
      &lt;/tr&gt;
  &lt;/thead&gt;
  &lt;tbody&gt;
      &lt;tr&gt;
          &lt;td&gt;桌面軟體、手機 App、IoT 裝置&lt;/td&gt;
          &lt;td&gt;&lt;strong&gt;SQLite&lt;/strong&gt;&lt;/td&gt;
          &lt;td&gt;資料跟著設備走，免安裝&lt;/td&gt;
      &lt;/tr&gt;
      &lt;tr&gt;
          &lt;td&gt;個人部落格、展示型官網&lt;/td&gt;
          &lt;td&gt;&lt;strong&gt;SQLite&lt;/strong&gt;&lt;/td&gt;
          &lt;td&gt;讀多寫少，省去維護伺服器成本&lt;/td&gt;
      &lt;/tr&gt;
      &lt;tr&gt;
          &lt;td&gt;快速原型開發、Demo&lt;/td&gt;
          &lt;td&gt;&lt;strong&gt;SQLite&lt;/strong&gt;&lt;/td&gt;
          &lt;td&gt;建個檔案就能開工&lt;/td&gt;
      &lt;/tr&gt;
      &lt;tr&gt;
          &lt;td&gt;社群論壇、電商平台&lt;/td&gt;
          &lt;td&gt;&lt;strong&gt;PostgreSQL&lt;/strong&gt;&lt;/td&gt;
          &lt;td&gt;高併發寫入，需要行級鎖&lt;/td&gt;
      &lt;/tr&gt;
      &lt;tr&gt;
          &lt;td&gt;多台伺服器分散部署&lt;/td&gt;
          &lt;td&gt;&lt;strong&gt;PostgreSQL&lt;/strong&gt;&lt;/td&gt;
          &lt;td&gt;需要跨機器共享資料源&lt;/td&gt;
      &lt;/tr&gt;
      &lt;tr&gt;
          &lt;td&gt;醫療、金融等敏感系統&lt;/td&gt;
          &lt;td&gt;&lt;strong&gt;PostgreSQL&lt;/strong&gt;&lt;/td&gt;
          &lt;td&gt;需要嚴格的角色權限控管&lt;/td&gt;
      &lt;/tr&gt;
  &lt;/tbody&gt;
&lt;/table&gt;
&lt;!--adsense--&gt;
&lt;h2 id=&#34;你的資料庫是隨身筆記還是中央總機&#34;&gt;你的資料庫是隨身筆記還是中央總機
&lt;/h2&gt;&lt;p&gt;如果資料是 &lt;strong&gt;「單機、靜態、單一擁有者」&lt;/strong&gt;，選 &lt;code&gt;SQLite&lt;/code&gt; 享受極致的輕量與自由；&lt;/p&gt;
&lt;p&gt;如果資料是 &lt;strong&gt;「雲端、動態、高度互動」&lt;/strong&gt;，讓 &lt;code&gt;PostgreSQL&lt;/code&gt; 接管。&lt;/p&gt;
&lt;p&gt;&lt;code&gt;SQLite&lt;/code&gt; 的核心哲學是 &lt;strong&gt;「應用程式的內部元件」&lt;/strong&gt;，而 &lt;code&gt;PostgreSQL&lt;/code&gt; 的定位是 &lt;strong&gt;「系統架構的獨立中樞」&lt;/strong&gt;。&lt;/p&gt;
&lt;p&gt;下次在架構決策時，別急著拿出 &lt;code&gt;PostgreSQL&lt;/code&gt;，先問問自己：&lt;/p&gt;
&lt;blockquote&gt;
&lt;p&gt;&lt;strong&gt;「我的資料庫，是拿來當隨身筆記，還是中央總機？」&lt;/strong&gt;&lt;/p&gt;
&lt;/blockquote&gt;
&lt;p&gt;答案清楚了，選擇自然就明確了。&lt;/p&gt;
&lt;h2 id=&#34;reference&#34;&gt;Reference
&lt;/h2&gt;&lt;ul&gt;
&lt;li&gt;&lt;a class=&#34;link&#34; href=&#34;https://sqlite.org/lang.html&#34;  target=&#34;_blank&#34; rel=&#34;noopener&#34;
    &gt;Query Language Understood by SQLite&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a class=&#34;link&#34; href=&#34;https://sqlite.org/docs.html&#34;  target=&#34;_blank&#34; rel=&#34;noopener&#34;
    &gt;SQLite Documentation&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a class=&#34;link&#34; href=&#34;https://www.sqlitetutorial.net/&#34;  target=&#34;_blank&#34; rel=&#34;noopener&#34;
    &gt;SQLite Tutorial - An Easy Way to Master SQLite Fast&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a class=&#34;link&#34; href=&#34;https://en.wikipedia.org/wiki/SQLite&#34;  target=&#34;_blank&#34; rel=&#34;noopener&#34;
    &gt;SQLite - Wikipedia&lt;/a&gt;&lt;/li&gt;
&lt;/ul&gt;
</description>
        </item>
        
    </channel>
</rss>
