SQL Antipatterns: Phần 1 - Tối ưu hóa thiết kế cơ sở dữ liệu

Thiết kế cơ sở dữ liệu



Về việc thiết kế cơ sở dữ liệu cần lưu ý các vấn đề sau

  • Tránh lưu trữ giá trị dạng đa giá trị
    • Không sử dụng các trường lưu trữ dạng 1 cột nhiều giá trị
      • VD: bảng USER không nên tạo 1 trường PHONG_BAN_IDS chứa giá trị dạng ID1, ID2 … kiểu 1,2,3
  • Tránh lưu trữ bảng dạng đệ quy phụ thuộc
    • Không sử dụng kiểu trong 1 bảng, khóa ngoại lại trỏ vào khóa chính của bảng đó
    • Ví dụ:
      • Bảng Category thường sẽ là CategoryID, ParentCategoryID, Name
      • Bạn có thể tạo ra 1 bảng
        • Bảng Category (CategoryID, Name)
        • Và bảng Category_Xref (CategoryID, ParentCategoryID) làm bảng quan hệ cha-con
  • Khóa chính không tồn tại
    • Bất kỳ bảng nào cũng nên có khóa chính
    • Mục tiêu là khi truy cập vào 1 bản ghi trong bảng rất dễ dàng theo khóa chính để thao tác như cập nhật, xóa.
    • Kiểm tra trùng lặp bản ghi (dựa vào khóa chính đã có)
  • Không đặt tên khóa chính tên dạng chung chung như (ID)
    • Thay vì đặt tên khóa chính cho một bảng tên là Id thì chúng ta nên đặt tên có ý nghĩa hơn như: CategoryID, UserID, DepartmentID
    • Mục đích là khi tạo các mối quan hệ giữa các bảng thì bạn có thể nhìn thấy luôn khóa ngoại là của bảng nào, tương ứng với khóa chính ở bảng nào
    • VD:
      • Không nên: Category (ID, Name, Note)
      • Nên: Category (CategoryID, CategoryName, Note), Product (ProductID, ProductName, CategoryID)
  • Xem xét thêm khóa ngoại cho bảng nếu cần thiết
    • Một số trường hợp bỏ khóa ngoại thì có vẻ sẽ giúp cho DB nhẹ hơn, linh hoạt hơn, tuy nhiên đánh đổi lại là chúng ta phải tự quản lý tính toàn vẹn dữ liệu. Đôi khi cái giá phải trả là rất đắt.
    • Thêm khóa ngoại sẽ giúp việc ràng buộc dữ liệu chặt chẽ, nên thêm đối với những chức năng cần tính ràng buộc dữ liệu chặt chẽ.
  • Không tạo nhiều cột để lưu các giá trị khác nhau của một thuộc tính
    • Ví dụ:
      • Không nên: USER (UserID, Active, InActive)
      • Nên USER (UserID, STATUS_ID), USER_STATUS (STATUS_ID, STATUS_NAME) với giá trị STATUS_ID là 1: Active, 2: InActive
  • Chia nhỏ dữ liệu của một bảng hoặc theo năm
    • Có thể đánh partition theo năm cho bảng dữ liệu

SQL Tips: Tối ưu hóa câu lệnh MySQL - Phần 2

Tiếp phần 1 đã viết được khá lâu. Nay chúng ta sẽ tiếp tục viết phần 2 trong loạt bài tối ưu hóa câu lệnh MySQL

Vấn đề dư thừa dữ liệu trong câu truy vấn

Một nguyên tắc đơn giản nhất khi truy vấn dữ liệu là tránh sử dụng SELECT * trong bất kỳ trường hợp nào. Vì khi gọi lệnh này thì hệ quản trị sẽ tốn thêm tài nguyên để định danh những cột sẽ được lấy ra. Hãy chỉ rõ cột nào mình muốn lấy ra, giảm tải cho hệ thống và câu lệnh trở nên sáng sủa hơn.

Trong câu lệnh JOIN nhiều bảng thì cũng có chuyện dư thừa dữ liệu trong câu join. Bạn tìm hiểu thêm về tích descartes trong SQL thì bạn sẽ hiểu rõ hơn phần này. ví dụ đơn giản dưới đây:

VD

Bảng đơn hàng tb_Order có 300 bản ghi.

Bảng người mua tb_User có 1.000.000 bản ghi.

Bảng công ty tb_Company có 100.000 bản ghi.

Bạn cần lấy thông tin bản ghi thông tin mã hóa đơn, tài khoản người mua, tên người mua, tuổi, tên công ty của những khách hàng lớn hơn 18 tuổi trong năm 2021. 

Giả sử người dùng có thể thuộc hoặc không thuộc công ty nào cả. Ta sẽ phải viết query sử dụng câu lệnh JOIN như sau:

Select  a.OrderNumber,

                a.OrderDate, 

b.UserName,

b.FullName,

b.Age, 

c.CompanyName

from  tb_Order as a 

inner join   tb_User as b

on  a.UserID = b.UserID

left join tb_Company as c

on  b.CompanyID = c.CompanyID

where  b.Age > 18   

and a.OrderDate >= '202-01-01' and  a.OrderDate <= '202-12-31'


Nhìn câu lệnh này thì sau khi Join bảng a, b lại, số bản ghi câu lệnh phải duyệt ở mệnh đề where là  

300 * 1.000.000 = 30.000.000 bản ghi. Để giảm bớt số bản ghi lọc bởi where thì chúng ta nên viết lại thế này sẽ tối ưu hơn:

Select  a.OrderNumber,

                a.OrderDate, 

b.UserName,

b.FullName,

b.Age, 

c.CompanyName

from  tb_Order as a 

inner join   tb_User as b

on  a.UserID = b.UserID

and b.Age > 18   

left join tb_Company as c

on  b.CompanyID = c.CompanyID

where   a.OrderDate >= '202-01-01' and  a.OrderDate <= '202-12-31'


Trong một số trường hợp mà Left join thêm 1 loạt bảng khác để lấy thông tin thêm trên SELECT mà trong điều kiện WHERE không chứa điều kiện của bảng đó thì ta có thể viết dạng subquery để tăng tốc độ cho câu query. Chẳng hạn câu query:

Select  top 50 a.OrderNumber,

               a.OrderDate, 

b.UserName,

b.FullName,

b.Age, 

c.CompanyName,

d.CityName as CompanyCityName

from  tb_Order as a 

inner join   tb_User as b

on  a.UserID = b.UserID

and b.Age > 18   

left join tb_Company as c

on  b.CompanyID = c.CompanyID

left join tb_City as d

on d.CityID  = c.CityID

where   a.OrderDate >= '202-01-01' and  a.OrderDate <= '202-12-31'


Câu lệnh này nếu rơi vào trường hợp bảng Company và bảng City có rất nhiều dữ liệu (từ triệu bản ghi trở lên) sẽ chạy chậm hơn câu lệnh sau:

 select  ab.OrderNumber,

               ab.OrderDate, 

ab.UserName,

ab.FullName,

ab.Age,

c.CompanyName,

d.CityName as CompanyCityName

from (

Select  a.OrderNumber,

a.OrderDate, 

b.UserName,

b.FullName,

b.Age, 

b.CompanyID

from  tb_Order as a 

inner join   tb_User as b

on  a.UserID = b.UserID

and b.Age > 18   

where  a.OrderDate >= '202-01-01' and  a.OrderDate <= '202-12-31' 

                  order by a.OrderDate desc

                  limit 50 

) as ab

left join tb_Company as c

on  c.CompanyID = ab.CompanyID

left join tb_City as d

on d.CityID  = c.CityID


Triển khai một hệ thống trên IIS Server với mô hình Web Garden

Đối với một lập trình viên dotnet, nếu bạn đã từng tham gia phát triển hệ thống web thì chắc bạn sẽ từng biết đến khái niệm máy chủ web IIS (Internet Information Service).

IIS là một máy chủ web do Microsoft phát triển và được sử dụng để triển khai và lưu trữ ứng dụng Web. IIS có Công cụ xử lý ASP.NET của riêng mình để xử lý yêu cầu. Vì vậy, khi một yêu cầu đến từ máy khách đến máy chủ, IIS sẽ nhận yêu cầu đó và xử lý nó và gửi phản hồi trở lại máy khách.

Mặc định khi tạo một ứng dụng chạy trên IIS sẽ tạo ra một application pool chạy với một worker process (W3Wp.exe). 



Ứng dụng web sau khi được tạo






















Và Application Pool tương ứng bên trong thư mục Application Pools
















Và mặc định sẽ chỉ có tối đa 1 Worker chạy cho ứng dụng này
































Chúng ta có thể kiểm tra số worker đang chạy cho ứng dụng theo cách sau:


















Danh sách worker đang chạy, và ở danh sách này thể hiện thông số như CPU % 












Khi để chế độ mặc định này thì mọi thao tác và yêu cầu (request) gửi vào ứng dụng được IIS xử lý đồng bộ và nhất quán. 

Nhưng nhược điểm là khi có nhiều request gửi vào thì tổng thời gian để trả về kết quả có thể bị chậm đi nhiều vì chỉ có một Worker chạy.
Ví dụ: 
+ 2 request thì 60 mili giây trả về
+ 10 request thì mất đến 1200 mili giây trả về
+ 100 request thì mất 12000 mili giây trả về
Tức là với lượng truy cập đồng thời tăng thì thời gian trả về càng lớn.

Bài toán này chắc khá nhiều bạn đã gặp phải rồi. CPU lúc nào cũng chiếm lượng % rất lớn, server thường xuyên bị đơ.

Để giải quyết bài toán này người ta đưa ra hai khái niệm là Web Garden và Web Farm. Vậy ta đi tìm hiểu từng loại khái niệm và ứng dụng trong thực tế của nó.


WEB GARDEN

Web Garden là khái niệm để chỉ những ứng dụng web được cài đặt sử dụng nhiều hơn một worker để chạy. Ta có thể mượn cái ảnh từ internet để minh họa cho dễ hiểu


Cách thiết lập:


























Ưu điểm của Web Garden: 

Do có nhiều worker được thiết lập có thể chia sẻ cùng nhau để xử lý các request, nên việc xử lý nhiều request cùng lúc được diễn ra nhanh chóng hơn và trả về kết quả trong thời gian nhanh hơn so với một worker.

Ví dụ: 
+ 2 request thì 60 mili giây trả về
+ 10 request thì mất đến 90 mili giây trả về
+ 100 request thì mất 3600 mili giây trả về

Như vậy so với Application Pool (Normal) thì Application Pool (Web Garden) thực thi nhanh có vẻ ổn hơn nhiều.

Hạn chế của web garden:

Do sử dụng nhiều worker khác nhau để xử lý request nên yêu cầu các phiên xử lý phải được các worker hiểu được. Vậy để triển khai được web garden chạy không bị lỗi thì yêu cầu ứng dụng của phải được thiết kế lưu trữ các thông tin như session ... ở một nơi chung chứ không dùng bộ nhớ của IIS server. Chẳng hạn như dùng SQL Session State hoặc dùng ứng dụng bên thứ 3 như các hệ thống cache: memcache, redis.

Nhưng với những cách triển khai những hệ thống nhỏ, đơn giản, ít lưu lượng truy cập thì có thể dùng cách này. Nhưng đối với hệ thống có lượng truy cập lớn thì cách này không đáp ứng được. 

Mình sẽ viết một series về thiết kế ứng dụng triển khai đáp ứng khả năng scale out.

Cách sử dụng patch-package để tạo ra một bản fix lỗi thư viện cho dự án ReactJs

Ai từng sử dụng ReactJs thì cũng hiểu rằng nó là một bộ thư viện tuyệt với cho công việc triển khai một dự án theo mô hình single page. 

Bởi sự tiện lợi và đặc biệt là cực kỳ nhiều thư viện. Khi bạn có chức năng, biết yêu cầu rồi thì vấn đề của bạn bây giờ là từ khóa tìm kiếm để có thể tìm ra đúng thư viện viết sẵn của người ta. Sau đó bạn chỉ cần install vào project UI là sử dụng thôi, quá tiện lợi.

Bên cạnh sự tiện lợi thì cũng có những vấn đề khá củ chuối, trong đó có một vấn đề khá nhức nhối là chủ sử hữu thư viện đã thêm quá nhiều package phụ thuộc vào bên trong thư viện. Tức là muốn chạy thư viện này phải bắt buộc phải cài một loạt thư viện khác, điều đó gây ra không ít rắc rối cho người phát triển. 

Chẳng hạn trường hợp của mình là sử dụng thư viện @aws-amplify để phát triển một Shopify app Embedded. Tức là App của mình sẽ được đưa vào Shopify Admin dưới dạng nhúng trong một IFRAME. 

"aws-amplify": "^3.0.22",

"aws-amplify-react": "^4.1.21",

"aws-appsync": "^4.0.0",

"aws-appsync-react": "^4.0.0",

Mọi thứ đều rất tốt đẹp cho đến khi cụ Chrome đưa ra cơ chế tự động block cookies và localStorage của những third-party khi chạy ở chế độ Private Mode (ẩn danh).

Thế là khi người dùng ở chế độ Private Mode thì app của mình lăn ra chết vì cái lỗi

Fails to load in an private/incognito browser session within an iframe

Mở chế độ debugger thì cụ thể là lỗi 

Failed to read the 'localStorage' property from 'Window': Access is denied for this document

Mình đã dò trong Project của mình xem có sử dựng cookies, localStorage không thì chẳng thấy sử dụng. Điều tra kỹ hơn thì do trong thư viện @aws-amplify bắt buộc cài đặt thêm package  paho-mqtt, thư viện này lại sử dụng đến localStorage. 

Thế là ăn cám, phải tìm phương án để xử lý cho vấn đề này, không thể bắt người sử dụng đi tắt chế độ block cookies và localStorage ở private mode được. 

Cách thứ nhất phải xóa cái localStorage đó khỏi thư viện paho-mqtt hoặc cách thứ 2 là bỏ hẳn cái thư viện này ra khỏi @aws-amplify.

Cách thứ 2 có vẻ không khả thi nên mình chọn cách thứ nhất, tìm và loại bỏ từ khóa localStorage khỏi các file js trong thư viện node_modules\paho-mqtt\paho-mqtt.js

Sau khi tìm và loại bỏ thì vấn đề đặt ra là làm thế nào để sử dụng được bản hot fix này. Và sau một ngày mò mẫm thì tìm ra được cách là sử dụng patch-package để tạo ra một version hot-fix cho riêng dự án của mình. 

Mình code trên môi trường window nên cách làm như sau:

Bước 1. Mở project bằng Visual Studio Code, sau đó mở file node_modules\paho-mqtt\paho-mqtt.js sửa và lưu lại.

Bước 2. Sử dụng terminal của Visual Studio Code chạy lệnh 

npx patch-package paho-mqtt

Trong thư mục project sẽ xuất hiện một thư mục patches, và trong thư mục sẽ là một version paho-mqtt+1.1.0.patch (máy mình nó hiện vậy)



Bước 3. Chạy tuần tự tiếp các lệnh trên terminal, để add và commit code bạn vừa sửa lên git

git add patches/paho-mqtt+1.1.0.patch

git commit -m "fix paho-mqtt.js in paho-mqtt" 

Bước 4. Thêm đoạn Script sau vào file package.json nếu trong file đó chưa có scripts

"scripts": {

     "postinstall": "patch-package"

 }

Nếu trong file package.json đã có sẵn "scripts", bạn chỉ cần thêm vào trong scripts đoạn sau

"postinstall": "patch-package"

Bước 5. Chạy lệnh patch-package tiếp trên terminal để hoàn thành công đoạn cuối

npm i patch-package

Thế là xong, debug thấy chạy ầm ầm, build code lên Shopify app, chạy mượt mà ở cả chế độ Normal và Private Mode (ẩn danh).

SQL Tips: Tối ưu hóa câu lệnh MySQL - Phần 1

Comeback  to write my blog

Qua quá trình làm việc liên tục với hệ quản trị cơ sở dữ liệu MySQL, mình note lại một số tips, câu lệnh SQL hữu ích. Tất nhiên ngoài những câu lênh riêng biệt thì những tips này có thể sử dụng cho cả SQL Server hay một hệ quản trị cơ sở dữ liệu quan hệ phổ biến khác như PosgreSQL



1. Vấn đề sử dụng JOIN, UNION, WHERE

Cố gắng sử dụng nhiều nhất có thể INNER JOIN, vì Inner join là câu lệnh tránh dư thừa dữ liệu nhất trong các loại JOIN. 

Và đặc biệt là nên sử dụng WHERE ở chỗ cần thiết:

Câu query A:

Select a.UserName,

a.FullName, 

a.Age, 

b.CompanyName

from tb_User as a

inner join tb_Company as b 

on b.CompanyID = a.CompanyID

where   a.Age > 18

 Câu query B:

Select a.UserName,

a.FullName, 

a.Age, 

b.CompanyName

from tb_User as a, tb_Company as b 

where b.CompanyID = a.CompanyID

and a.Age > 18

Trong thực tế thì nếu lượng dữ liệu nhỏ thì 2 câu query A, B mất thời gian như nhau. Nhưng câu lệnh viết kiểu query A sẽ tốt hơn kiểu viết query B.


2. UNION, UNION ALL

Tránh sử dụng Union đơn thuần, nếu bắt buộc sử dụng Union thì nên sử dụng UNION ALL.


3. EXPLAIN 

Sau khi viết câu lệnh query để lấy dữ liệu ra, có một bước khá quan trọng để tăng trải nghiệm người dùng là kiểm tra tốc độ nhanh hay chậm của tính năng vừa xây dựng. Bạn có thể sử dụng câu lệnh EXPLAIN để kiểm tra xem câu lệnh của mình tối ưu hay chưa

Ví dụ:

Bạn có câu lệnh lấy danh sách tên, tuổi và cơ quan của người dùng lớn hơn 18 tuổi:

 Select a.UserName,

a.FullName, 

a.Age, 

b.CompanyName

from tb_User as a

inner join tb_Company as b 

on b.CompanyID = a.CompanyID 

where     a.Age > 18

     

Sau khi hoàn thành câu lệnh nên thử kiểm tra lại xem câu lệnh tối ưu chưa bằng cách chạy lệnh EXPLAIN

EXPLAIN 

Select a.UserName,

a.FullName, 

a.Age, 

b.CompanyName

from tb_User as a

inner join tb_Company as b 

on b.CompanyID = a.CompanyID

where  a.Age > 18

 Để hiểu câu lệnh này sử dụng như thế nào thì bạn tham khảo trang chủ MySQL có nói. Hoặc mình sẽ nêu ra trong một bài khác.


4. DISTINCT, GROUP BY

Hạn chế sử dụng DISTINCT, thay vì dùng nó thì chuyển sang GROUP BY nếu có thể, vì câu lệnh Distinct chạy khá tốn bộ nhớ.


5. Vấn đề INDEX dữ liệu

Sử dụng Index có thể tăng tốc độ câu truy vấn, nhưng nó sẽ làm cho câu lệnh Update/Insert/Delete bị chậm hơn. 

Bản chất đánh Index là hệ quản trị sẽ lưu trữ thêm dữ liệu vào bộ nhớ theo một logic để dễ dàng truy xuất các cột có index. Nên dung lượng dữ liệu sẽ phình lên nhiều hơn, và ổ cứng nhanh đầy hơn. 

Một bảng không nên đánh quá nhiều index.


6. Đối với câu lệnh phức tạp, hay phân tích và tách nhỏ thành các câu lệnh đơn giản


...

Nghĩ ra sẽ viết tiếp phần 2 





Tôi đến với nghề IT như thế nào

Tôi đến với nghề IT như thế nào?


Tôi, đích thị là một tên từ trên núi xuống. Thời gian đầu đi học ở Hà Nội mấy bạn cứ tưởng tôi là người dân tộc thiểu số.

Tôi đến với nghề IT cũng chỉ là một sự tình cờ. Chọn trường, chọn ngành cho vui thôi chứ lớp 12 có biết cái gì là máy tính đâu (những năm 2000 - 2004 trong xã tôi chỉ có 1 hay 2 nhà gì đó có máy tính). 
Nhưng không hiểu trời xui ai dè đỗ thật, thế là tôi bỏ qua giấy gọi ngành bác sĩ đa khoa của Đại học Y Huế để đợi giấy gọi Bách Khoa. 
Rồi cũng đến ngày nhập trường, tiếp đến là chuỗi ngày đại cương vô cùng thú vị (Không hiểu sao nhiều bạn sợ học đại cương thế). Học xong không hiểu gì, tối về nằm gác tay lên trán nghĩ liệu mình đang học cái gì không biết. 

Nhưng có thú vui tý là được biết môn tin học là gì, là cái đĩa mềm (Maxcel) đút vào cái lỗ (à ổ đĩa), kết hợp với màn hình xanh chết chóc, thế mà cũng chạy được mấy bài ví dụ gõ y chang trong sách. 
Thú thực lúc đó tôi cũng hoang mang thật, không hiểu học cái này xong ra làm cái gì nữa. Rồi kỳ tin học cũng qua với số điểm tròn trịa 5/10. 
Lúc đó tôi thực sự phân vân là có nên vào cái khoa CNTT không nữa, tôi chuyển hướng qua ngành toán và đăng ký vào khoa Toán Tin Ứng Dụng. Lúc đó cũng chẳng biết cái ngành mình vào thì ra trường làm gì, lúc đó sao cái gì cũng mù mờ và công tác truyền thông của các khoa thực sự rất là yếu kém.
Hết năm 2 mọi thứ vẫn thế, vẫn chưa định hình được mình là ai và học làm gì, may cái được mẹ mua cho cái máy tính nên bắt đầu biết chơi game, nghịch một vài ngôn ngữ lập trinh như C, C++, tháo máy, lắp máy, cài win lậu. 
Vào chuyên ngành năm 3 thì bắt đầu được tiếp xúc một số môn liên quan đến tin học nhiều hơn, còn toán học thì chắc chắn là nhiều rồi. 
Tôi bắt đầu thấy ngành IT có gì đó thú vị, không phải vì mình thay đổi được nhận thức về công việc sau khi ra trường mà vì lúc đó đã có vài người nhờ cài win, chẳng lẽ công việc của một IT đây sao.
Năm thứ 3, 4 dành cả tuối thanh xuân để học toán, chơi AOE, ngủ. Nghĩ lại tôi thấy mình thật thiếu năng động, giá như tôi tập trung học một ngoại ngữ thì thật là tốt. Giờ nhìn lại thấy mình thật đáng trách.
Năm thứ 5 thì bắt đầu đi thực tập, học Internship, làm đồ án. Sau khi vượt qua vòng phỏng vấn Internship của Fsoft, tôi bắt đầu được đi học ở RAC của Fsoft. Mình cố gắng học tốt để có thể có một cơ hội được vào Fsoft. Và kết thúc khóa học tôi dành được món học bổng giá trị 400k. Nhưng năm đó Fsoft đang thừa người thì phải, không thấy nhận người từ Internship. 
Ra trường với cái giấy chứng nhận tốt nghiệp, đi rải CV khắp các hang cùng ngõ hẻm để tìm công việc, lúc đó bố mẹ không nuôi nữa. Không có việc làm thì nhịn đói, chỗ nào trúng và gọi đi làm sớm thì đi làm thôi.
Nhìn lại quãng đường đó mình nhận ra một điều là làm gì cũng nên phải có định hướng rõ ràng.
Nếu bạn có một mentor dẫn đường cho bạn đi thì thật tốt, có thể bạn sẽ đi đúng đường và đạt được nhiều thành quả sớm hơn những người tự mò mẫm.

Nghề IT đến với tôi như vậy thôi, cũng chẳng có gì đặc biệt. Nhưng không hiểu sao vẫn gắn bó được gần chục năm rồi.
Nghễ IT thực sự mang lại thu nhập khá cao so với đa số các nghề, nhưng thứ đánh đổi lại thì bạn mất khá nhiều thứ.

Rảnh thì tôi sẽ viết về những khoảnh khắc công việc tôi đã trải qua trong mấy năm, và tôi đã làm gì cho nghề IT và nghề đã cho tôi những gì.

... ..



Cách giảm dung lượng file transaction log trong SQL Server

Kiến trúc file CSDL SQL

Khi tạo một CSDL trong SQL Server, tự động sẽ sinh ra hai tệp là tệp đuôi .MDF.LDF
Thí dụ mình tạo một CSDL tên là Demo thì sẽ sinh ra: Demo.mdf và Demo_log.ldf


Khi để chế độ Recovery Model mặc định là Full hoặc Bulk-logged thì sau một thời gian sử dụng, dung lượng file log (.ldf) sẽ phình lên rất nhiều, có thể dung lượng sẽ file log sẽ gấp nhiều lần file data. Bạn có thể chuột phải vào CSDL và chọn properties để xem


Với dung lượng file log lớn như thế này vừa tốn dung lượng ổ cứng HDD, vừa làm chậm thao tác trên CSDL đó. Vì vậy chúng ta phải giảm dung lượng file log đi là điều tất yếu.

Cách giảm dung lượng file log
Cách 1. Sử dụng SQL management tool để xóa log




Cách 2. Xóa log bằng cách sử dụng câu lệnh SHRINKFILE
ALTER DATABASE [mydatabase] SET RECOVERY SIMPLE 
DBCC SHRINKFILE(<log_file_name_Log>)
ALTER DATABASE [mydatabase] SET RECOVERY FULL
Thứ tự thực hiện câu lệnh trên như sau:

  • B1. Chuyển CSDL về simple model, thì việc xóa log mới có hiệu quả.
  • B2. Chạy lệnh xóa log file
  • B3. Chuyển về Recovery Full Model để CSDL có thể thực hiện log các trạng thái


KẾT QUẢ
Sau khi thực hiện một trong hai cách trên thì dung lượng của file log của mình giảm xuống đang ngạc nhiên




PS: Các bạn nếu thấy hay thì share giúp nhé hoặc để lại comment.

Cách phân biệt giữa các loại Recovery Model trong MSSQL Server

Bài này sẽ nói về cách phân biệt giữa các loại Recovery Model trong SQL. Cách sử dụng các Mode này sao cho hiệu quả.

1. Các loại Recovery Model

Hệ quản trị cơ MSSQL Server có 3 loại Recovery model:

  1. Full, 
  2. Simple 
  3. Bulk-logged.

Khi chúng ta tạo một CSDL mới thì hệ thống tự động chọn model là: Full.

Chúng ta có thể sử dụng SQL management tool để xem như sau:
- Click chuột phải vào database, chọn Properties



- Trên cửa số thông tin về database, chọn tab Options



- Recovery Model: Full, Bulk-logged, Simple

Hoặc có thể dụng câu lệnh SQL để truy vấn
SELECT name,
       recovery_model_desc
FROM sys.databases GO

2. Cách sử dụng các loại Recovery Model

Full Recovery Model:
  • Sao lưu gần như tất cả những thay đổi trong cơ sở dữ liệu. Nên việc bạn có thể phục hồi được tất cả dữ liệu.
  • Nếu cơ sở dữ liệu có chứa nhiều nhóm tập tin, và bạn muốn khôi phục từng phần đọc / ghi filegroups thứ cấp và tùy chọn, chỉ đọc filegroups.
  • Người quản trị có thể phục hồi được cơ sở dữ liệu đến trước thời điểm db bị hỏng hóc.
  • Dung lượng file data và file log sẽ phình ra rất nhanh, và file log có thể lớn hơn rất nhiều so với file data. Dẫn đến chi phí cho phần cứng sẽ đội lên khủng khiếp.
  • Và tất nhiên khi file phình lên thì truy vấn SQL sẽ chậm đi, thao tác với CSDL cũng chậm đi. Bạn phải thường xuyên xóa bớt file log để hệ thống chạy nhanh hơn.

Bulk-Logged Recovery Model:
  • Gần như bạn có thể phục hồi mọi thứ nếu chọn Model này. Tương tự Full Recovery Model

Simple Recovery Model
  • Không tự động lưu lại những sự thay đổi của CSDL. Nên có thể mất toàn bộ dữ liệu nếu người quản trị không backup thường xuyên.
  • Do không lưu lại các thay đổi vào file log nên dung lượng file log sẽ rất nhỏ so với file data.
  • Đây là lựa chọn hợp lý cho admin có túi tiền không được rủng rỉnh.

3. Lời khuyên
Nếu dữ liệu của bạn có thể mất mát tại một số thời điểm thì nên sử dụng Simple Recovery Model, với ưu điểm là dung lượng file data và log sẽ nhỏ, nên hệ thống sẽ chạy mượt mà hơn. Tất nhiên chúng ta có thể tự tạo ra SQL Job Backup tự động cho CSDL theo thời gian để tránh mất mát dữ liệu (ngày backup 2 lần chẳng hạn) các bạn có thể tham khảo tại đây.

Chúng ta có thể thay đổi Recovery Model bằng cách sử dụng SQL Management Tool hoặc sử dụng câu lệnh sau:
USE master;


ALTER DATABASE DEMO
SET RECOVERY FULL ;

Chúc các bạn thành công khi làm việc với SQL Server.



Một ngày đi học ...


Hôm nay đi dự hội thảo Tech Insider Expo 2015 do Vietnamworks tổ chức. Qua những lời giới thiệu trong email mời hoành tráng của ban tổ chức. Sau khi tham gia hội thảo mình có một số cảm nhận như sau.

Thứ nhất, về cách thức tổ chức, lựa chọn khách mời có thể nói là tạm được. Cũng hơi buồn vì mình làm công nghệ, biết khá nhiều cao thủ nhưng khi tham dự hội thảo lại chẳng bắt gặp cao thủ nào.

Thứ hai, mình đến để tham gia hội thảo nhưng khi đến phòng hội thảo thì vừa đúng giờ chuẩn bị bắt đầu nhưng lại hết chỗ ngồi, thế là lại chưng hửng ngồi ngoài. Buồn vì với số khách mời là hơn 1.5k sao cái phòng hội thảo lại bé tý... Đành đợi buổi hội thảo vào 1h30 vậy.

Thứ ba, ghé qua các lán trại tuyển dụng, đọc thấy thông tin tuyển dụng hấp dẫn ghê (toàn công ty lớn theo như profile là những công ty top của châu á) mặc dù chưa có nhu cầu xin việc nhưng cũng thử ghé vào xem thế nào. Nhưng hơi bất ngờ vì không thấy người đến ứng tuyển mà chỉ thấy các HR đang ngồi chém gió là nhiều. Có một vài người ghé vào, đi ra với gương mặt thỏa mãn vì có quà xách về...

Phải nói ý tưởng về một Tech Insider Expo là rất hay. Nhưng cần quảng bá rộng hơn trong cộng đồng lập trình viên việt nam. Dù sao cũng rất cảm ơn Vietnamworks đã tổ chức 1 chương trình khá hay dành cho những người đang có nhu cầu tìm việc làm.

Thiết kế database theo hướng multi-tenancy, SaaS


Bài toán hướng multi-tenancy trong thực tế gặp rất nhiều, nhưng có rất nhiều developer chưa nắm được khái niệm và cách thức hoạt động của các hệ thống thiết kế theo hướng này. Qua một thời gian nghiên cứu và phát triển các hệ thống, mình đúc rút một số kinh nghiệm muốn chia sẻ cho mọi người.

Thực tế ta bắt gặp rất nhiều hệ thống sử dụng multi-tenacy
vd:
- Hệ thống quản lý cửa hàng cho phép nhiều đại lý có thể truy cập với những tài khoản độc lập, dữ liệu độc lập, nhưng cùng chung 1 hệ thống site.
- Hệ thống quản lý công văn sử dụng trong tổng công ty và nhiều công ty con, cùng site nhưng dữ liệu độc lập.
- Hệ thống quản lý dự án Jira
- Hệ thống CRM của zoho, saleforce...

Nhiều hệ thống sử dụng SQL server, Oracle ... thiết kế hệ thống multi-tenancy theo một trong các kiến trúc sau.


Phương án I. Cùng chung một cơ sở dữ liệu (database), chia sẻ bảng (table)
Ví dụ:
Một hệ thống quản lý cửa hàng, có bảng shop, bảng sản phẩm (product), bảng acccount

Bảng shop
Shop (
Id,
Name,
Notes)

Bảng user
User(
Id,
Name,
UserName,
Password,
ShopId
)
Bảng product
Product (
Id int,
Code varchar(50),
Name varchar(255),
ShopId)

Tất cả các bảng liên quan đều có 1 khóa ngoại là ShopId. Dữ liệu sản phẩm của từng shop đều được lưu chung trong bảng Product, nhưng được phân biệt nhau bởi trường ShopId.

Điểm mạnh:
- Thiết kế lưu trữ đơn giản.
- Dễ cho việc phát triển.
- Không gặp phải vấn đề đồng bộ cấu trúc bảng trong quá trình phát triền.

Nhược điểm:
- Không độc lập database nên việc một shop có thể xem dữ liệu của shop khác nếu có quyền truy cập SQL, phân quyền trên SQL thực sự là vấn đề lớn.
- Vấn đề backup, restore dữ liệu cho từng shop là gần như không thể, chỉ có thể backup cho tất cả.
- Vấn đề phát sinh thực sự phức tạp khi dữ liệu phình to, rất khó khăn trong việc backup, restore...
- Khó khăn khi scale hệ thống.

Lời khuyên: Phương án này chỉ dùng làm những hệ thống nhỏ, ít dữ liệu, phát sinh dữ liệu không lớn.


Phương án II. Cùng chung database, chia sẻ schema
Hướng thiết kế này sử dụng một cơ sở dữ liệu, mỗi tenant tương ứng 1 schema. Có một schema chung để quản lý những các dữ liệu chung, quản lý thông tin về tenants. Cấu trúc các bảng ở tất cả các tenant đều giống nhau.
Cần 1 schema chuẩn để dựa vào đó tạo ra tenant mới trong quá trình thêm mới tenant.

Điểm mạnh:
- Thiết kế theo hướng này thì có thê thay đổi các cấu trúc, hàm, thủ tục riêng rẽ giữa các tenant.
- Dễ phân quyền hơn phương án 1.
- Tiết kiệm được chi phí khi triển khai (do số lượng database chỉ là rất ít)

Nhược điểm:
- Phương án backup độc lập từng tenant là vấn đề nan giải, lập trình viên sẽ phải tự quản lý việc backup/restore cho từng tenant bằng code.
- Việc đồng bộ những thay đổi trong cấu schema là vấn đề cần phải quan tâm.
- Dữ liệu trong database sẽ phình ra nhanh chóng.
- Số lượng schema trong 1 database là có giới hạn.
- Khó khăn khi scale hệ thống.


Phương án III. Mỗi tenant một database.

Phương án này sẽ thực hiện như sau: hệ thống sẽ gồm 1 database chung (chuyên để quản lý các phần như danh sách tenant, user, role ...), 1 database tenant chuẩn (chứa dữ liệu chuẩn), và các tenant khác.
Mỗi tenant sẽ là 1 database, người dùng sẽ có quyền truy cập vào database chung và database tenant của user đó.

Mình sẽ đính kèm script sql server để tạo databases cho các phương án trên, phương án 3 giống như phương án 2, nhưng thay vì dùng schema thì chuyển sang dùng database.

Google Drive: SQL Script Mutil-tenancy












SQL Tips: Hướng dẫn sử dụng SQL Profiler


Microsoft SQL Server Profiler là một công cụ hỗ trợ DBA giám sát câu lệnh query thực thi (T-SQL Statements ) của Database Engine. DBA có thể lưu lại thông tin về các câu lệnh đã thực thi để sử dụng về sau.

System requirement: SQL Server Enterprise (2005/2008/2012...).

Step1. Mở SQL Server profiler 

Path: Start | All Programs | Microsoft SQL Server 2012| Performance Tools | SQL Server Profiler


Step 2. Tạo một SQL Server Profiler Trace


Step 3. Connect to SQL Server


Step 4. Điền thông tin Trace

General Tab
- Trace Name
- Save to file: Lưu thông tin trace vào một file.
- Save to table: Lưu thông tin trace vào 1 bảng trong SQL do người dùng chỉ định.



Events Selection Tab


DBA có thể sử dụng nhưng thông tin mặc định. Hoặc có thể chỉnh sửa filter theo nhu cầu.
Để lựa chọn filter phù hợp, hãy click chọn Column Filters..

- Nhập tên ứng dụng đang sử dụng SQL.


- Nhập text bạn muốn filter


Sau khi điền thông tin, chọn OK.

Bước 5. Tiếp  tục Click on Run button.


Khi một ứng dụng bất kỳ truy cập, thực thi câu lệnh SQL, trên giao diện trace sẽ hiển thị thông tin câu lệnh đó.


Chúc mọi người sớm thành thạo SQL Server Profiler

SQL Tips: Create SQL Job to backup database everyday


Today, I will make a backup manual database daily work by creating SQL Job.
First we have to install the SQL Server Agent SQL.
Set mode to run automatically when Windows startup.

Step 1.

- Setup SQL Server Agent
- Run Sql Server Agent services




Step 2.
- Create database: "Demo"
- Create store procedure jb_Backup_Database


-- =============================================
-- Author:  Phuong Nguyen
-- Create date: YYYY-MM-DD
-- Description: Backup database
-- =============================================
ALTER PROCEDURE [dbo].[jb_Backup_Database]
 
AS
BEGIN
 
 DECLARE @DBName  NVARCHAR(50) -- database name  
 DECLARE @BKPath  NVARCHAR(256) -- path for backup files  
 DECLARE @BKFileName NVARCHAR(256) -- filename for backup  
 DECLARE @BKFileDate NVARCHAR(20) -- used for file name

 -- Set dbname is current db
 SET @DBName  = DB_NAME()

 -- Database backup directory
 SET @BKPath  = 'C:\DB\Backup\'  
  
 -- Filename format
 SELECT @BKFileDate = CONVERT(NVARCHAR(20),getDate(),112) 
 SET @BKFileName = @BKPath + @DBName + '_' + @BKFileDate + '.BAK'  
 
 BACKUP DATABASE @DBName TO DISK = @BKFileName  

END

- Run proc to test result
exec jb_Backup_Database

Create backup file successful



Step 3. Create Job call store procedure

- Create Job with job name "jb_Backup_database_demo"


- Create Step for database "Demo" with command: exec jb_Backup_Database

- Schedule Job

After create job, enable job.

You can run job to test result.

SQL Script:
USE [msdb]
GO

/****** Object:  Job [jb_Backup_database_demo]    Script Date: 6/28/2015 4:18:14 PM ******/
BEGIN TRANSACTION
DECLARE @ReturnCode INT
SELECT @ReturnCode = 0
/****** Object:  JobCategory [[Uncategorized (Local)]]]    Script Date: 6/28/2015 4:18:14 PM ******/
IF NOT EXISTS (SELECT name FROM msdb.dbo.syscategories WHERE name=N'[Uncategorized (Local)]' AND category_class=1)
BEGIN
EXEC @ReturnCode = msdb.dbo.sp_add_category @class=N'JOB', @type=N'LOCAL', @name=N'[Uncategorized (Local)]'
IF (@@ERROR <> 0 OR @ReturnCode <> 0) GOTO QuitWithRollback

END

DECLARE @jobId BINARY(16)
EXEC @ReturnCode =  msdb.dbo.sp_add_job @job_name=N'jb_Backup_database_demo', 
  @enabled=1, 
  @notify_level_eventlog=0, 
  @notify_level_email=0, 
  @notify_level_netsend=0, 
  @notify_level_page=0, 
  @delete_level=0, 
  @description=N'No description available.', 
  @category_name=N'[Uncategorized (Local)]', 
  @owner_login_name=N'sa', @job_id = @jobId OUTPUT
IF (@@ERROR <> 0 OR @ReturnCode <> 0) GOTO QuitWithRollback
/****** Object:  Step [RunBackupDemo]    Script Date: 6/28/2015 4:18:15 PM ******/
EXEC @ReturnCode = msdb.dbo.sp_add_jobstep @job_id=@jobId, @step_name=N'RunBackupDemo', 
  @step_id=1, 
  @cmdexec_success_code=0, 
  @on_success_action=1, 
  @on_success_step_id=0, 
  @on_fail_action=2, 
  @on_fail_step_id=0, 
  @retry_attempts=0, 
  @retry_interval=0, 
  @os_run_priority=0, @subsystem=N'TSQL', 
  @command=N'exec jb_Backup_Database', 
  @database_name=N'Demo', 
  @flags=0
IF (@@ERROR <> 0 OR @ReturnCode <> 0) GOTO QuitWithRollback
EXEC @ReturnCode = msdb.dbo.sp_update_job @job_id = @jobId, @start_step_id = 1
IF (@@ERROR <> 0 OR @ReturnCode <> 0) GOTO QuitWithRollback
EXEC @ReturnCode = msdb.dbo.sp_add_jobschedule @job_id=@jobId, @name=N'Everyday', 
  @enabled=1, 
  @freq_type=4, 
  @freq_interval=1, 
  @freq_subday_type=1, 
  @freq_subday_interval=0, 
  @freq_relative_interval=0, 
  @freq_recurrence_factor=0, 
  @active_start_date=20150627, 
  @active_end_date=99991231, 
  @active_start_time=234700, 
  @active_end_time=235959, 
  @schedule_uid=N'624f7aee-128d-403b-8037-4bde1c1a792d'
IF (@@ERROR <> 0 OR @ReturnCode <> 0) GOTO QuitWithRollback
EXEC @ReturnCode = msdb.dbo.sp_add_jobserver @job_id = @jobId, @server_name = N'(local)'
IF (@@ERROR <> 0 OR @ReturnCode <> 0) GOTO QuitWithRollback
COMMIT TRANSACTION
GOTO EndSave
QuitWithRollback:
    IF (@@TRANCOUNT > 0) ROLLBACK TRANSACTION
EndSave:

GO

You are created Job to backup database everyday.

Good luck


Like