據(jù)到連接真實數(shù)據(jù)庫:SQLite到MySQL的入門實操指南)
1. 為什么想聊這個話題一千個“寫死數(shù)據(jù)”的項目1.1 我見到太多“寫死數(shù)據(jù)”的現(xiàn)場真的替他們著急先說說我最近的一次經(jīng)歷有個做運營平臺的朋友拿項目給我看說他們小團隊費了好大勁做了一個“客戶管理頁面”前端界面、登錄判斷、角色區(qū)分全都有了結(jié)果一點開用戶列表——三個測試賬號、兩行假記錄所有數(shù)據(jù)都直接寫死在代碼里。改一個手機號得先找到那段數(shù)組改完還要重新打包發(fā)布。我當時就問他你沒有接數(shù)據(jù)庫嗎他愣了一下說“數(shù)據(jù)庫不是得學很多東西嗎我們幾個都是文科生轉(zhuǎn)過來的能把這個頁面跑起來已經(jīng)不容易了?!边@句話我太熟悉了。這些年我看過太多類似的項目寫死用戶、寫死訂單、寫死商品列表甚至有的連“今日新聞”都是早上手動替換的。不能說他們不認真只能說大家都把“連數(shù)據(jù)庫”這件事想得太重總覺得那是資深后端工程師才能碰的領(lǐng)域。但事實完全不是這樣。今天這個環(huán)境普通開發(fā)者、非科班轉(zhuǎn)行的人要邁過“從寫死數(shù)據(jù)到連接真實數(shù)據(jù)庫”這一步門檻已經(jīng)低到了非常友好的程度。甚至可以說一旦你掌握了這個切換你手里的項目就從“演示品”變成“真正能用起來的東西”。這不只是技術(shù)升級是整個項目性質(zhì)的改變。1.2 “寫死數(shù)據(jù)”到底死在哪我不反對在最早的原型階段用寫死數(shù)據(jù)。當年我自己做Demo的時候也這么干畢竟最快。但你要搞清楚它的問題是什么改動一次數(shù)據(jù)就要改代碼改完還要重新構(gòu)建、重新部署。你早上改了個價格客戶下午看到的還是舊版你說這難受不難受。多個人一起用的時候各看各的版本。每個人都有一份自己的“假數(shù)據(jù)”誰都不知道哪個是準的。沒有歷史記錄。數(shù)據(jù)被覆蓋就被覆蓋了回頭找都沒地方找。數(shù)據(jù)量一大前端頁面直接卡死。你試試把一萬條訂單寫死在JavaScript里瀏覽器直接吃內(nèi)存。寫死數(shù)據(jù)的本質(zhì)是什么是把“數(shù)據(jù)”和“程序”捆死在了一起。真正靠譜的架構(gòu)數(shù)據(jù)應該獨立于程序存活程序停了數(shù)據(jù)還在版本升級了數(shù)據(jù)還能遷移換一個終端數(shù)據(jù)依然能查。這才叫“數(shù)據(jù)資產(chǎn)”寫死在代碼里的那些只能叫“臨時變量”。2. 數(shù)據(jù)庫到底解決了什么給數(shù)據(jù)一個“正規(guī)編制”2.1 千萬別把數(shù)據(jù)庫想成高深的技術(shù)它就是個“公共儲物柜”很多人一聽“數(shù)據(jù)庫”三個字就自動聯(lián)想到高薪DBA、復雜SQL調(diào)優(yōu)、分布式架構(gòu)其實對絕大部分項目來說數(shù)據(jù)庫干的事情非常簡單就是提供一個統(tǒng)一的地方讓你把數(shù)據(jù)放進去、拿出來、改掉、刪掉并且讓別人也能跟你同步操作。我經(jīng)常用一個比喻寫死數(shù)據(jù)就像你自己兜里揣著所有物品別人要用你得掏出來遞給他數(shù)據(jù)庫就像一個小區(qū)里的公共儲物柜人人都有鑰匙你放進去別人按編號取誰也不會拿錯里面還有登記冊記錄誰動過。這個“儲物柜”解決了一個很關(guān)鍵的問題程序可以有多個實例服務可以重啟但是數(shù)據(jù)是持久的、共享的、一致的。而這恰恰是企業(yè)級應用和“作業(yè)級Demo”最本質(zhì)的區(qū)別。2.2 關(guān)系數(shù)據(jù)庫最核心的三個概念表、行、列以及它們的主鍵連接真實數(shù)據(jù)庫之前花十分鐘搞懂三個概念就夠了。表Table就是一類東西的集合比如用戶表、訂單表。行Row表里面的一條具體記錄比如某個具體用戶張三。列Column一條記錄的一個屬性比如用戶名、手機號、注冊時間。主鍵每行數(shù)據(jù)唯一的身份標識。就好比身份證號一張表里不能有兩行完全相同的主鍵。搞懂這些之后再看SQL語句就一點都不嚇人了。查詢數(shù)據(jù)的語句核心就一句SELECT * FROM users WHERE name 張三;意思就是從“用戶表”里把“姓名”這一列等于“張三”的所有行都拿出來。所有復雜的查詢本質(zhì)上都是在“哪張表”“取哪些列”“什么條件”這三個維度上做組合。2.3 什么時候該下定決心從寫死切到真庫我總結(jié)了一個“觸發(fā)信號”滿足任何一條你就別猶豫了數(shù)據(jù)要由多個用戶、多個入口維護和查看數(shù)據(jù)量會持續(xù)增長比如記錄每天都會新增同一個數(shù)據(jù)需要被多個功能模塊復用上線之后需要有人在不碰代碼的情況下修改內(nèi)容。只要你的項目碰了上面任意一條“寫死數(shù)據(jù)”就撐不住了。反過來講如果你只是做一個單機的小玩具自己演示完就丟那繼續(xù)寫死也沒問題。工具不丟人選錯工具才麻煩。3. 動手實操把寫死的數(shù)據(jù)搬到真實數(shù)據(jù)庫3.1 第一步先選一個門檻最低、又足夠真實用的數(shù)據(jù)庫數(shù)據(jù)庫選型這件事很多人一上來就被Oracle、SQL Server那些企業(yè)級名詞勸退。其實我的建議非常明確先從 SQLite 開始只要你跑通流程再切到 MySQL 或者 PostgreSQL 會非常順。SQLite 強在哪它是一個單文件數(shù)據(jù)庫不需要安裝服務端、不需要配置端口、不需要賬號密碼就是一個“.db”文件。你的程序讀寫這個文件數(shù)據(jù)就持久化了。對于學習“連接數(shù)據(jù)庫”這件事來說它把所有的外部噪音都剔除了。不過如果你要做的項目將來要真實部署給別人用、有并發(fā)訪問我更推薦直接上 PostgreSQL 或者 MySQL。這倆的區(qū)別對新手來說沒那么重要你只需要知道PostgreSQL 功能更豐富一些對復雜查詢和事務支持更嚴謹MySQL 使用人群廣教程多排障容易。我的習慣是個人項目用 PostgreSQL團隊協(xié)作時看大家更熟哪個。以MySQL為例建庫建表的常用命令你可以直接抄CREATE DATABASE shop CHARACTER SET utf8mb4 COLLATE utf8mb4_unicode_ci; USE shop; CREATE TABLE users ( id INT AUTO_INCREMENT PRIMARY KEY, name VARCHAR(50) NOT NULL, phone VARCHAR(20), created_at DATETIME DEFAULT CURRENT_TIMESTAMP );注意這里的utf8mb4和utf8mb4_unicode_ci這是給中文數(shù)據(jù)用的能避免絕大多數(shù)亂碼問題。后面我會專門再講這個坑。3.2 第二步寫一條穩(wěn)定的“連接通道”而不是每次現(xiàn)連連接數(shù)據(jù)庫最忌諱什么就是每要執(zhí)行一次查詢就現(xiàn)寫一段連接代碼、用完就斷開。連接本身是有開銷的頻繁開關(guān)連接在高并發(fā)場景下會把數(shù)據(jù)庫拖死。正確姿勢是把這個連接過程封裝成一個固定的“通道層”。我用Python舉例因為非科班轉(zhuǎn)行的朋友大多會選Python語法沒有壓迫感。import pymysql from dbutils.pooled_db import PooledDB DB_CONFIG { host: 127.0.0.1, port: 3306, user: shop_user, password: your_password, database: shop, charset: utf8mb4 } pool PooledDB(creatorpymysql, maxconnections10, **DB_CONFIG) def get_conn(): return pool.connection()用連接池之后每次要操作數(shù)據(jù)就調(diào)用get_conn()借一條連接用完歸還。這樣既不會把連接用完不還也不會每次都重復握手。學名叫做“數(shù)據(jù)庫連接池”聽起來很高級本質(zhì)就是一個幫你管理連接的工具箱。3.3 第三步把增刪改查封裝成服務業(yè)務代碼里別裸奔SQL很多新手容易犯的第二個錯是在頁面邏輯里直接拼SQL字符串。這樣不但代碼四處重復更危險的是容易出安全問題。你應該把對每張表的操作收斂到一個文件里起個名字叫“數(shù)據(jù)訪問層”。比如用戶模塊你就封裝四個函數(shù)def get_user(user_id): ... def create_user(name, phone): ... def update_user(user_id, name, phone): ... def delete_user(user_id): ...業(yè)務代碼里只調(diào)用這些函數(shù)不再關(guān)心SQL長什么樣。這不是什么高深的架構(gòu)思想純粹是為了讓你的代碼好維護、好測試、好替換。數(shù)據(jù)庫同步軟件、數(shù)據(jù)庫導出腳本這些工具將來要接進來的時候也都以這個層為邊界。另一個很重要的點所有涉及用戶輸入的地方絕對不要用拼接字符串的方式寫SQL。要使用參數(shù)化查詢。你只要記住一個鐵律外部來的內(nèi)容永遠只作為一個“值”傳進去永遠不要變成SQL語句的一部分。這是你項目上線之后不被搞垮的底線之一。4. 連接真實數(shù)據(jù)庫后的關(guān)鍵細節(jié)從能跑到能用的距離4.1 事務要么全成要么全不成企業(yè)級項目和Demo之間有一道分水嶺叫“事務”。舉個例子一個下單流程扣庫存、減余額、生成訂單、記錄日志。如果其中一步失敗了前面已經(jīng)執(zhí)行的操作必須全部回滾否則就會出現(xiàn)“錢扣了但訂單沒創(chuàng)建”的臟數(shù)據(jù)。SQLite、MySQL、PostgreSQL都支持事務。核心語法就是三條START TRANSACTION; UPDATE ...; INSERT ...; COMMIT;當遇到異常時執(zhí)行ROLLBACK;就回到事務開始前的狀態(tài)。你在封裝數(shù)據(jù)訪問函數(shù)時要把多個操作放進同一個事務里管理而不是每個SQL各自獨立提交。這個習慣是你未來寫任何帶金錢、庫存、狀態(tài)的業(yè)務都繞不開的。事務之外還要理解“并發(fā)”這個詞。你項目上線后同一時間可能有成百上千個人操作。兩個用戶同時搶最后一個庫存數(shù)據(jù)庫怎么保證不超賣這就靠事務隔離級別和行鎖來處理。對剛?cè)腴T的人來說你不需要一開始就精通但至少要知道MySQL默認的REPEATABLE READ隔離級別在絕大多數(shù)業(yè)務場景下是安全可用的別沒事去“優(yōu)化”成其他級別。4.2 權(quán)限管理別用root賬號跑業(yè)務該建就建專號這個坑我見過太多次開發(fā)的時候圖省事全流程都用root或者管理員賬號然后項目要上線了用戶名密碼還寫死在配置里。一旦泄露別人就能把你的用戶表刪得干干凈凈。正確做法是為每個項目創(chuàng)建專用賬號并授予最小權(quán)限。還是以MySQL為例CREATE USER shop_user% IDENTIFIED BY a_strong_password; GRANT SELECT, INSERT, UPDATE, DELETE ON shop.* TO shop_user%; FLUSH PRIVILEGES;你就給增刪改查四個權(quán)限就夠了絕對不要給DROP、ALTER這類危險權(quán)限給業(yè)務賬號。萬一將來真出了安全事故損失的也是一部分數(shù)據(jù)而不是整個庫。誰會負責修復這些說實話并不重要重要的是你自己別把大門鑰匙交出去。4.3 千萬別忽視備份數(shù)據(jù)是你項目唯一的“不動產(chǎn)”代碼丟失了可以從Git倉庫拉回來數(shù)據(jù)丟了就真沒了。企業(yè)級項目意識里備份不是可選項是必需項。我建議至少做到兩條每天定時自動備份一次備份文件保留至少7天每次做批量更新、刪除、表結(jié)構(gòu)變更之前手動手動備份一次。MySQL的備份命令很簡單mysqldump -u shop_user -p shop backup_$(date %F).sql恢復也不復雜mysql -u shop_user -p shop backup_2025-01-01.sqlPostgreSQL對應的是pg_dump和psql用法差不多。這件事前期花的心思極少但真到出問題時你會覺得它是你做過最值的一個決定。5. 文科生/非科班最容易栽的五個跟頭5.1 表結(jié)構(gòu)設(shè)計時的“拍腦袋”陷阱非科班同學做表結(jié)構(gòu)最常見的問題就是把Excel的習慣搬到數(shù)據(jù)庫。Excel里你可以一張表放所有信息數(shù)據(jù)庫不行或者說最好不要。比如你要做訂單系統(tǒng)列里塞“客戶姓名、客戶電話、客戶地址”看起來方便但從第二筆訂單開始你就會發(fā)現(xiàn)同一客戶在不同訂單里重復填了三遍改一個地址要改好幾行。這叫做“數(shù)據(jù)冗余”。解決辦法是拆表客戶一張表訂單一張表訂單里只放“客戶ID”。想要查訂單對應的客戶信息時用關(guān)聯(lián)查詢把兩張表連起來。這個過程叫“規(guī)范化設(shè)計”說人話就是數(shù)據(jù)盡量只存一處其他地方只存它的ID。一開始別追求高深的第三范式你就記住“同一件事不重復存”這一條原則就已經(jīng)能避開一大半設(shè)計錯誤。5.2 改數(shù)據(jù)前的備份與“先查后改”習慣我見過一個讓人哭笑不得的事故有人寫了個更新語句忘了加WHERE條件結(jié)果把全表所有訂單的金額都改成了一個數(shù)。這種事資深工程師也會遇到但老手和非新手的區(qū)別在于——敢不敢在沒備份的情況下執(zhí)行。所以給你一個我用了很多年的鐵律任何修改類語句先寫成SELECT跑一遍確認影響的行數(shù)是你預期的再把它改寫成UPDATE執(zhí)行。執(zhí)行前保留一句備份哪怕就是導出一份CSV也好。這多出來的兩分鐘能救你無數(shù)次。5.3 中文亂碼的罪魁禍首字符集不一致“為什么我存進去的是中文查出來是問號”這個問題我回答了無數(shù)次。絕大多數(shù)原因是創(chuàng)建數(shù)據(jù)庫時用了默認字符集通常在Linux環(huán)境下是latin1或utf8但有些老版本連utf8都不完整而程序連接時用的是utf8mb4。你只要從建庫開始就統(tǒng)一三處字符集就不會有亂碼問題數(shù)據(jù)庫本身CREATE DATABASE ... CHARACTER SET utf8mb4表本身CREATE TABLE ... DEFAULT CHARSETutf8mb4連接參數(shù)charsetutf8mb4三處一致中文隨便存。另外老項目要是已經(jīng)變成亂碼了不要指望“改個配置”能讓舊數(shù)據(jù)自己恢復亂碼往往是不可逆的你只能修復新數(shù)據(jù)舊數(shù)據(jù)得靠備份恢復。5.4 索引與慢查詢數(shù)據(jù)多了以后你們家系統(tǒng)變慢的原因項目剛上線數(shù)據(jù)幾十條查詢飛快。半年后數(shù)據(jù)量上萬、上十萬頁面開始轉(zhuǎn)圈SQL開始慢。這種情況90%是因為該加索引的地方?jīng)]加。索引是什么你可以把它理解成書后面的目錄。沒有目錄你要找“賣冰箱的訂單”只能從第一頁翻到最后一頁這叫做“全表掃描”。加了索引數(shù)據(jù)庫直接跳到對應的那一塊區(qū)域速度自然快。常見的加索引場景是經(jīng)常出現(xiàn)在WHERE后面的字段、經(jīng)常用于排序的字段、以及外鍵字段。例如上面說的訂單表里customer_id就應該加索引ALTER TABLE orders ADD INDEX idx_customer_id (customer_id);有一點要提醒你索引不是越多越好。每加一個索引寫入數(shù)據(jù)時都要額外維護一份“目錄”反而拖慢插入速度。只給真正查詢用的關(guān)鍵字段加索引就夠了。5.5 連接泄漏程序一會兒正常一會兒卡死八成是這個原因還有一種很常見的詭異故障程序剛啟動時一切正常跑一段時間后突然變慢甚至報“too many connections”。重啟之后又好了過一會兒又犯病。這通常不是數(shù)據(jù)庫的問題而是你代碼里拿連接后沒歸還。如果你用的是連接池每次用完一定要把連接放回池里。Python里用with語句可以保證這一點with get_conn() as conn: cursor conn.cursor() cursor.execute(SELECT * FROM users)這樣無論中途有沒有報錯連接都會自動歸還。一個小細節(jié)能讓你的項目穩(wěn)定性和專業(yè)感提升一個檔次。6. 我的真實體會連接真庫不是技術(shù)升級是思維升級做這行越久我越覺得從“寫死數(shù)據(jù)”到“連接真實數(shù)據(jù)庫”最難的從來不是SQL語法、不是安裝數(shù)據(jù)庫軟件而是思維方式的轉(zhuǎn)變。寫死數(shù)據(jù)的時候你的腦子里只有“程序”連接到真實數(shù)據(jù)庫之后你的世界里多了一個長線存在的“數(shù)據(jù)實體”。它會逼著你考慮數(shù)據(jù)怎么組織才合理、別人怎么訪問、權(quán)限怎么隔離、壞數(shù)據(jù)怎么防、丟了怎么恢復、并發(fā)沖突怎么處理。這些東西看起來很多但每解決一個你手里的項目就向“企業(yè)級”靠近一點。這么多年我?guī)н^的人里有些確實是文科生出身但他們現(xiàn)在維護的系統(tǒng)服務著上萬人。他們唯一的共同點就是敢在那個“看起來門檻很高”的地方邁出第一步。最后分享一個小習慣我給自己的學習項目設(shè)了一個規(guī)則凡是會重復看、需要跟別人同步的信息一律不寫死在代碼里。哪怕只是一個備忘錄列表我也寧愿丟進SQLite文件里。這樣每一次練習都是在提前熟悉真實項目的操作流。等哪天真的要上一個大系統(tǒng)你不會覺得“連接數(shù)據(jù)庫”是你需要準備的技能它早就是你習以為常的一部分了。