編碼的世界 / 優質文選 / 文明

圖片怎麼存儲到數據庫裏


2022年5月04日
-   

存儲圖片到數據庫裏一般有兩種方式
  • 將圖片保存的路徑存儲到數據庫(文件存放在服務器的路徑或者ftp服務器的路徑)
  • 將圖片以二進制數據流的形式直接寫入數據庫字段中(base64的形式),base64

圖片在數據庫的存儲用途一般為
  • 用戶上傳的頭像,文章插圖,文章首頁圖片等等
  • 其他方面的圖片

一般存儲圖片有兩種做法:
  • 把圖片直接以二進制形式存儲在數據庫中,一般數據庫提供一個二進制字段來存儲二進制數據。比如mysql中有個blob字段。oracle數據庫中是blob或bfile類型
  • 圖片存儲在磁盤上(服務器上),數據庫字段中保存的是圖片在服務器上存儲的路徑。

  • 將圖片轉換成二進制存儲:


    大體思路:
  • 將讀取到的圖片用自己的程序轉化成二進制形式。(一般會有內置函數,可以快速轉出為base64格式),Nodejs的話可以這樣轉化
  • 再結合insert into語句插入數據表中的blob類型字段中去。
  • 從數據庫取出圖片展示的時候。則是直接發送圖片內容
  • 然後前端接收到二進制,展示到需要的位置即可

  • 總結:處理代碼不是很麻煩,使用nodejs很容易就可以處理。但是,我們用得更多的是存儲圖片的路徑,實際圖片是在磁盤上保存的(圖片二進制放到數據庫,把數據庫的負擔弄重了)。需要代碼的話,可以看我nodejs裏面對圖片的處理。
    互聯網環境中,大訪問量,數據庫速度和性能方面很重要。一般在數據庫存儲圖片的做法比較少,更多的是將圖片路徑存儲在數據庫中,展示圖片的時候只需要連接磁盤路徑把圖片載入進來即可。因為圖片是屬於大字段。一張圖片要占用1M甚至幾十M,所以使用數據庫很浪費資源,但是如果圖片量很小的情況下可以嘗試,或者直接在後台開辟空間存儲文件(這樣也給服務器造成了不小的壓力),所以最好還是使用第三方文件上傳平台,像七牛雲,阿裏雲,騰訊雲等等(坐等打錢)。
    牽扯到一些基本的數據庫調優,比如這篇文章分為標題、作者、添加時間、更新時間、文章內容、文章關鍵字等等。
    文章內容一般是比較長的。經常使用text字段去存儲。文章的內容就屬於大字段。一般文章內容可以拆分到單獨一個表中去。不要與文章信息存儲在一張表裏面。
    個人的理解:mysql中一張表的數據是全部在一個數據文件中的。如果大字段的數據也存儲在裏面。程序展示列表,比如文章列表。這個時候根本不需要展示文章內容的。但是仍然會影響速度,數據庫查找數據其實就是掃描那個數據文件,文件容量越小,速度就會越快(為什麼單表的容量在1g-2g的時候基本上要分表了)。拆分出去到一張單獨的表,就是單獨的文件了。舉一反三,相互獨立,分離的思想不僅在系統開發中用到,在現實生活中經常存在的。
    總結:三種東西永遠不要放到數據庫裏,圖片,文件,二進制數據。
    原因
    • 對數據庫的讀/寫的速度永遠都趕不上文件系統處理的速度
    • 數據庫備份變的巨大,越來越耗時間
    • 對文件的訪問需要穿越你的應用層和數據庫層
    • 把圖片縮略圖存到數據庫裏?很好,那你就不能使用nginx或其它類型的輕量級服務器來處理它們了。

    關於mysql中的blob類型
    bolb(binary large object)二進制大對像就像int型那樣,分為blobMEDIUMBLOBLONGBLOB。其實就是從小到大
    • blob 容量為64KB
    • MEDIUMBLOB 容量為16M
    • LONGBLOB 容量為4G。

    說實話,圖片用這樣子存儲用得還真少。使用java的序列化函數進行序列化的值,有人存入這個字段中去。
    mysql中blob字段存儲圖片有個通信大小的設置:
    圖片要傳輸給mysql存儲起來,那麼需要涉及到數據通信。mysql中有個配置是限制通信數據大小的。
    my.conf配置文件中的max_allowed_packet,mysql默認的值是1M。
    好多圖片尤其是原始圖可能不止1m。傳輸的數據(也就是圖片)超過這個設置大小。結果就會出錯
    其實所謂的性能,最關鍵是數據庫性能。因為隨著數據庫數據量增大,大部分時間耗費是在php,java等語言等待數據庫返回數據的過程中耗費時間。
    網站訪問量大了後,具體的語言不是瓶頸,瓶頸都在數據庫。用c,python,php,java 都能操作mysql數據庫獲取數據。語言之間可能存在速度執行差異,但是其實這種差別已經很小了。至少我覺得,給予用戶感覺不到明顯。執行相差0.0001秒用戶感覺並沒有明顯的區別。可能說,大並發(很多用戶同時訪問)的時候,就會體現到差別了。其實我覺得,大並發訪問是數據庫瓶頸。等待數據庫給予數據。沒達到一定級別實在體現不了差別。數據庫數據量達到一定級別。語言相差0.001s會給予用戶體驗上的差別。所以,這也是為什麼php很適合做web開發了。解析頁面速度快(解釋型語言,不需要編譯)。可以用java來與數據庫打交道獲取數據。php不直接操作數據庫,而是調用java提供的數據接口,獲取數據,馬上展示在頁面中。這是利用了php的頁面執行速度快的一個優勢。

    二、數據庫中保存圖片路徑


    一般是這樣子的:
    按照年月日生成路徑。具體是按照年月日還是按照年月去生成路徑,根據自己需要(不一定是按照日期去生成)。
    理解為什麼要分散到多個文件夾中去才是關鍵,涉及到一個原理就明白了:
  • 操作系統對單個目錄的文件數量是有限制的。當文件數量很多的時候。從目錄中獲取文件的速度就會越來越慢。所以為了保持速度,才要按照固定規則去分散到多個目錄中去。
  • 圖片分散到磁盤路徑中去。數據庫字段中保存的是類似於這樣子的”images/2012/09/25/ 1343287394783.jpg”
  • 原來上傳的圖片文件名稱會重新命名保存,比如按照時間戳來生成,1343287394783. jpg。這樣子是為了避免文件名重複,多個人往同一個目錄上傳圖片的時候會出現。
  • 反正用什麼樣的規則命名圖片,只要做到圖片名稱的唯一性即可。
  • 比如網站的並發訪問量大,目錄的生成分得月細越好。比如精確到小時,一個小時都可以是一個文件夾。同時0.001秒有兩個用戶同時在上傳圖片(因為那麼就會往同一個小時文件夾裏面存圖片)。因為時間戳是精確到秒的。為了做到圖片名稱唯一性而不至於覆蓋,生成可以在在時間戳後面繼續加毫秒微秒等。總結的規律是,並發訪問量越大。就越精確就好了。

  • 有個方面總結一下:為什麼保存的磁盤路徑,是”images/2012/09/25/1343287394783.jpg”,而不是” /images/2012/09/25/ 1343287394783.jpg”(最前面帶有斜杠)?
  • 連那個斜杠都不要。這裏也是做到方便以後系統擴展。
  • 在頁面中需要取出圖片路徑展示圖片的時候,如果是相對路徑,則可以使用”./”+”images/2012/09/25/1343287394783.jpg”進行組裝。
  • 如果需要單獨的域名(比如做cdn加速的時候)域名,img1.xxx.com,img2.xxx.com這樣的域名
  • 直接組裝 “http://img1.xxx.com/”+”images/2012/09/25/1343287394783.jpg”
  • 當然數據庫是可以在前面加斜杠/保存起來,/images/2012/09/25/ 1343287394783.jpg
  • 其實不方便統一。比如相對路徑載入圖片的時候,則是”.”+” /images/2012/09/25/ 1343287394783.jpg”
  • 可能我還沒體會到壞處,以後會遇到問題的。不過,遵循慣例不加斜杠” images/2012/09/25/ 1343287394783.jpg”就對了。

  • 涉及到一個新問題:為什麼大部分系統都不會域名保存進去,像這樣子http://www.xxx.com/images/2012/09/25/1343287394783.jpg保存到數據庫中
  • 了解的知識越多,越有利於我們做決定。可能就是一個”感覺區別不是很大”的影響下,去做一個決定,反而對後面是比較大的影響的。至少是增加自己的工作量了。
  • 其實把域名保存進去,也不是什麼滔天大罪的事情。但凡是經驗豐富的開發人員都不會這樣子做。這是一個經驗積累出來的,所以上海那個網友也對此並沒有明顯的概念很正常,他說他不知道cdn方面的(當然覺得存個域名進去沒什麼大不了的)。需要了解cdn知識,什麼情況下會用到cdn知識。
  • 雖然是做開發人員,不需要關注運維和服務器之類的知識。不過了解一些就有利於理解了。
  • 這裏涉及到cdn加速。關於cdn原理(就是內容分發網絡),我理解其本質就是為了解決距離遠產生的速度問題,使用就近的服務。

  • CDN
  • 從中國請求美國一台服務器上的圖片。一般比較慢,因為距離這麼遠,網絡傳輸是存在損耗的,距離越遠,傳輸的時間就越長。一般會看到瀏覽器左下角顯示:“已響應,正在傳輸數據…”。這不是服務器本身問題了。實際上服務器早就響應請求,把數據發給客戶端,但是網絡問題,就一直在傳輸,沒傳完了。
  • 在中國,是南北距離遠的問題。南北還會涉及到跨網,南方用戶使用電信居多,北方用戶網通居多。兩個線路需要跨越,會有時間延遲。
  • cdn加速就是適應這個需求產生的:現在不請求美國的服務器。直接在中國安放節點(節點是比較籠統的詞語,可以理解成一台服務器,也可以理解成一個機房,就是一個點嘛),請求距離近的節點。這樣子就不需要那麼遠的距離了。

  • 以前在長沙的網站,團購以城市分站的形式。北京和長沙用的是同一套程序。服務器在長沙。北京用戶訪問北京站的時候,實際上需要遠距離訪問長沙的服務器。速度怎麼都快不起來。跟服務器性能完全沒關系。當時不懂這些。不清楚怎麼折騰。就想辦法去做js代碼壓縮,瀏覽器緩存之類的。實際上瞎折騰。不是說這些前端優化不重要,哲學上有主次矛盾之分,瓶頸在哪裏就去突破哪裏。沒解決主要矛盾,問題並不會迎刃而解。當時也不是數據庫瓶頸。如果去優化數據庫。也不會明顯改善。就那點數據量。根本就達不到瓶頸。哪裏談得上主要矛盾。隨著後來去其他公司工作,接觸一些東西,類似不找瓶頸的優化例子發生在身邊好幾次了,先沒找到瓶頸就瞎去優化。我的同事可能是抱著多多益善的心態去做的,但主要矛盾(技術上說是瓶頸)沒找到,也沒改善。
    當時如果沒想到是距離問題。也就不會想到cdn,當時其實我根本不知道cdn服務。我只知道,google這些網站肯定在中國部署的服務器,要不然,中國用戶還去訪問美國的服務器,那再好的服務器都會速度慢的。
    由於自己搭建cdn環境和機房的資金比較大(需要大量的服務器),也需要人力維護。反正一般的公司弄不起,其實根本不劃算。淘寶以前用商用的cdn服務,後來商用的扛不住了,就搭建了自己的cdn網。我不知道新浪有沒有自己搭建,但其實我覺得跟淘寶的特點有關,店鋪很多,無論是商品還是交易記錄總計起來商品很多的圖片,圖片都是靜態的部分,cdn本來就是用來做靜態的(圖片,css,js等)請求分發用的。
    之前在網上看到一句話,cdn網絡不是一般的公司玩得起的。
    一般的公司自己搭建cdn網絡成本高,所以就有商業的cdn提供付費租用服務,這是一項很成熟的業務,很多這樣的公司,大部分全國性的互聯網公司都會使用到cdn。
    總結:cdn服務。對於靜態內容是非常適合的。所以像商品圖片,隨著訪問量大了後,租用cdn服務,只需要把圖片上傳到他們的服務器上去。
    例子:北京訪問長沙服務器,距離太遠。我們完全可以把商品圖片,放到北京的雲服務(我們覺得現在提供給網站使用的雲存儲其實就是cdn,給網站提供分流和就近訪問)上去。這樣子北京用戶訪問的時候,實際上圖片就是就近獲取。不需要很長距離的傳輸。
    自己用一個域名img.xxx.com來載入圖片。這個域名解析到北京的雲服務上去。
    做法:數據庫中保存的是” images/2012/09/25/1343287394783.jpg”,
    這些圖片實際上不存儲在web服務器上。上傳到北京的cdn服務器上去。
    我從數據庫取出來,直接”img.xxx.com/”+” images/2012/09/25/1343287394783.jpg”
    比如如果還有多個,就命名img1.xx.com、img2.xx.com
    反正可以隨便。所以如果把域名直接保存進去。就顯得很麻煩了。遷移麻煩。
    像淘寶,凡客,亞馬遜這些電子商務網站,我們看到請求的時候,下面往往會有
    img1.xxx.cdn.com
    img2.xxx.cdn.com
    其實他們保存在數據庫中的是相對路徑。有些是不需要在數據庫保存的,縮略圖可以實時訪問的時候用程序生成(節省很多存儲空間)
    實際上,把域名保存在數據庫中,非常不利於系統遷移。一旦換個域名的話,原來保存在數據庫中的是“www.abc.om/images/xxxxxx“,因為路徑都在數據庫中寫死了。下回換個域名就用不了了。那個時候自己去寫sql語句批量更新字段吧。
    幾個術語:
    • ICP(Internet Content Provider),也就是網絡內容提供者。聯想到我們運營一個網站需要icp備案了嗎?你自己運營網站,你就是icp服務商
    • IDC(Internet Data Center),互聯網數據中心。IDC的概念,目前還沒有一個統一的標准。通俗點,就是提供機房托管(服務器租用和托管),域名注冊之類的。

    關於淘寶的圖片存儲
    了解到:淘寶以前使用了商用的存儲。但是沒法滿足需求。據說,到2010年,淘寶網後端保存著286億張圖片。商用的系統系統沒法滿足需求的時候。他們就自己開發了一個tfs(Taobao File System)。大規模的小文件在磁盤上讀取,需要磁盤磁頭頻繁的尋道和換道。大並發情況下和大量的操作確實很麻煩。其實借鑒了當時google公布的gfs(Google File System)設計論文。google有相冊服務。為每個用戶提供上傳圖片存儲。
    有個觀點比較好:對於老板們而言,往往覺得,用錢能解決的都不算問題。但問題在於,你遇到的問題,別人都沒遇到過。那這個時候你就沒有經驗可以參考或者直接拿來使用。只有自己參考一些思路去創造技術了。

    三、關於圖片進行雲存儲(cdn加速)


    曾經看過這個,這個是比較適合創業公司的。價格相對便宜https://www.upyun.com/
    其實,現在的雲存儲本質就是一個cdn服務商。你把靜態的圖片上傳到他提供的服務器上去(ftp方式上傳或者api形式編寫程序上傳)。他為你做就近節點訪問。
    計費方式:按照流量付費,99元購買100g。怎麼算流量。每次訪問文件的大小累加,比如一個1m的文件,訪問一次流量就加1m。
    我個人理解,對於圖片的量不大的情況下,使用這種雲服務,好處不是節省存儲空間。你自己的服務器100g的空間可能創業型公司都沒用完,不是什麼存儲空間不夠用,然後去用雲存儲。以前我對cdn比較模糊,有這麼點理解,或者以為是分散網站web服務器流壓力,服務器分流。這些好處是有的。但是,只要理解了cdn產生的背景和解決的關鍵問題後,就會明白雲存儲關鍵好處在於:給用戶就近節點訪問,加速。
    我覺得,如果不是出於這個考慮,或者達不到這樣的目的。用其他方案也完全可以替代。何必使用雲存儲呢?就是你無非有實力做到全國多個節點去部署服務,才需要租用cdn來幫你,畢竟他們是規模產生的效益,專注於解決這個領域。
    使用騰訊雲、阿裏雲、華為雲不香嗎?
    下面是具體存儲方法代碼:
      一、保存圖片的上傳路徑到數據庫:
      string uppath="";//用於保存圖片上傳路徑
      //獲取上傳圖片的文件名
      string fileFullname = this.FileUpload1.FileName;
      //獲取圖片上傳的時間,以時間作為圖片的名字可以防止圖片重名
      string dataName = DateTime.Now.ToString("yyyyMMddhhmmss");
      //獲取圖片的文件名(不含擴展名)
      string fileName = fileFullname.Substring(fileFullname.LastIndexOf("\") + 1);
      //獲取圖片擴展名
      string type = fileFullname.Substring(fileFullname.LastIndexOf(".") + 1);
      //判斷是否為要求的格式
      if (type == "bmp" || type == "jpg" || type == "jpeg" || type == "gif" || type == "JPG" || type == "JPEG" || type == "BMP" || type == "GIF")
      {
       //將圖片上傳到指定路徑的文件夾
       this.FileUpload1.SaveAs(Server.MapPath("~/upload") + "\" + dataName + "." + type);
       //將路徑保存到變量,將該變量的值保存到數據庫相應字段即可
       uppath = "~/upload/" + dataName + "." + type;
      }
      二、將圖片以二進制數據流直接保存到數據庫:
      引用如下命名空間:
      using System.Drawing;
      using System.IO;
      using System.Data.SqlClient;
      設計數據庫時,表中相應的字段類型為iamge
      保存:
      //圖片路徑
      string strPath = this.FileUpload1.PostedFile.FileName.ToString ();
      //讀取圖片
      FileStream fs = new System.IO.FileStream(strPath, FileMode.Open, FileAccess.Read);
      BinaryReader br = new BinaryReader(fs);
      byte[] photo = br.ReadBytes((int)fs.Length);
      br.Close();
      fs.Close();
      //存入
      SqlConnection myConn = new SqlConnection("Data Source=.;Initial Catalog=stumanage;User ID=sa;Password=123");
      string strComm = " INSERT INTO stuInfo(stuid,stuimage) VALUES(107,@photoBinary )";//操作數據庫語句根據需要修改
      SqlCommand myComm = new SqlCommand(strComm, myConn);
      myComm.Parameters.Add("@photoBinary", SqlDbType.Binary, photo.Length);
      myComm.Parameters["@photoBinary"].Value = photo;
      myConn.Open();
      if (myComm.ExecuteNonQuery() > 0)
      {
       this.Label1.Text = "ok";
      }
      myConn.Close();
      讀取:
      連接數據庫字符串省略
      mycon.Open();
      SqlCommand command = new
      SqlCommand("select stuimage from stuInfo where stuid=107", mycon);//查詢語句根據需要修改
      byte[] image = (byte[])command.ExecuteScalar ();
      //指定從數據庫讀取出來的圖片的保存路徑及名字
      string strPath = "~/Upload/zhangsan.JPG";
      string strPhotoPath = Server.MapPath(strPath);
      //按上面的路徑與名字保存圖片文件
      BinaryWriter bw = new BinaryWriter(File.Open(strPhotoPath,FileMode.OpenOrCreate));
      bw.Write(image);
      bw.Close();
      //顯示圖片
      this.Image1.ImageUrl = strPath;
      采用這兩種方式可以根據實際需求靈活選擇。

    熱門文章