Các doanh nghiệp và dịch vụ mang lại giá trị dựa trên dữ liệu. Tính sẵn có, trạng thái nhất quán và độ bền là những ưu tiên hàng đầu để giữ hài lòng khách hàng và người dùng cuối. Dữ liệu bị mất hoặc không truy cập được có thể tương đương với mất khách hàng.
Sao lưu cơ sở dữ liệu phải được đặt lên hàng đầu trong các hoạt động và tác vụ hàng ngày.
Chúng ta nên chuẩn bị cho trường hợp dữ liệu của chúng ta bị hỏng hoặc bị mất.
Tôi là một người tin chắc vào một câu nói cũ mà tôi đã nghe:" Tốt hơn là có nó và không cần nó hơn là cần nó và không có nó . "
Điều đó cũng áp dụng cho các bản sao lưu cơ sở dữ liệu. Hãy đối mặt với nó, nếu không có họ, bạn về cơ bản không có gì cả. Hoạt động dựa trên quan điểm rằng không có gì có thể xảy ra với dữ liệu của bạn là một sai lầm.
Hầu hết các DBMS đều cung cấp một số phương tiện của các tiện ích sao lưu tích hợp sẵn. PostgreSQL có pg_dump và pg_dumpall ngoài hộp.
Cả hai đều có nhiều tùy chọn tùy chỉnh và cấu trúc. Việc bao gồm tất cả chúng riêng lẻ trong một bài đăng blog sẽ là điều không thể. Thay vào đó, tôi sẽ xem xét những ví dụ mà tôi có thể áp dụng tốt nhất vào môi trường học tập / phát triển cá nhân của tôi.
Điều đó đang được nói, bài đăng trên blog này không nhắm mục tiêu đến môi trường sản xuất. Nhiều khả năng, một máy trạm / môi trường phát triển sẽ được hưởng lợi nhiều nhất.
pg_dump và pg_dumpall là gì?
Tài liệu mô tả pg_dump là:“pg_dump là một tiện ích để sao lưu cơ sở dữ liệu PostgreSQL”
Và tài liệu pg_dumpall:“pg_dumpall là một tiện ích để ghi (“ kết xuất ”) tất cả cơ sở dữ liệu PostgreSQL của một cụm vào một tệp script.”
Sao lưu Cơ sở dữ liệu và / hoặc (các) Bảng
Để bắt đầu, tôi sẽ tạo một cơ sở dữ liệu thực hành và một số bảng để làm việc bằng cách sử dụng SQL bên dưới:
postgres=# CREATE DATABASE example_backups;
CREATE DATABASE
example_backups=# CREATE TABLE students(id INTEGER,
example_backups(# f_name VARCHAR(20),
example_backups(# l_name VARCHAR(20));
CREATE TABLE
example_backups=# CREATE TABLE classes(id INTEGER,
example_backups(# subject VARCHAR(20));
CREATE TABLE
example_backups=# INSERT INTO students(id, f_name, l_name)
example_backups-# VALUES (1, 'John', 'Thorn'), (2, 'Phil', 'Hampt'),
example_backups-# (3, 'Sue', 'Dean'), (4, 'Johnny', 'Rames');
INSERT 0 4
example_backups=# INSERT INTO classes(id, subject)
example_backups-# VALUES (1, 'Math'), (2, 'Science'),
example_backups-# (3, 'Biology');
INSERT 0 3
example_backups=# \dt;
List of relations
Schema | Name | Type | Owner
--------+----------+-------+----------
public | classes | table | postgres
public | students | table | postgres
(2 rows)
example_backups=# SELECT * FROM students;
id | f_name | l_name
----+--------+--------
1 | John | Thorn
2 | Phil | Hampt
3 | Sue | Dean
4 | Johnny | Rames
(4 rows)
example_backups=# SELECT * FROM classes;
id | subject
----+---------
1 | Math
2 | Science
3 | Biology
(3 rows)
Cơ sở dữ liệu và bảng đều được thiết lập.
Cần lưu ý:
Trong nhiều ví dụ này, tôi sẽ tận dụng psql's \! meta-command, cho phép bạn thả vào shell (dòng lệnh) hoặc thực hiện bất kỳ lệnh shell nào theo sau.
Chỉ cần lưu ý rằng trong một phiên dòng lệnh hoặc đầu cuối (được biểu thị bằng '$' đứng đầu trong bài đăng blog này), \! lệnh meta không được bao gồm trong bất kỳ lệnh pg_dump hoặc pg_dumpall nào. Một lần nữa, nó là một lệnh meta tiện lợi trong psql.
Sao lưu một bảng duy nhất
Trong ví dụ đầu tiên này, tôi sẽ kết xuất bảng sinh viên duy nhất:
example_backups=# \! pg_dump -U postgres -t students example_backups > ~/Example_Dumps/students.sql.
Liệt kê nội dung của thư mục, chúng tôi thấy tệp ở đó:
example_backups=# \! ls -a ~/Example_Dumps
. .. students.sql
Các tùy chọn dòng lệnh cho lệnh riêng lẻ này là:
- -U postgres:tên người dùng được chỉ định
- -t học sinh:cái bàn để đổ
- example_backups:cơ sở dữ liệu
Có gì trong tệp student.sql?
$ cat students.sql
--
-- PostgreSQL database dump
--
-- Dumped from database version 10.4 (Ubuntu 10.4-2.pgdg16.04+1)
-- Dumped by pg_dump version 10.4 (Ubuntu 10.4-2.pgdg16.04+1)
SET statement_timeout = 0;
SET lock_timeout = 0;
SET idle_in_transaction_session_timeout = 0;
SET client_encoding = 'UTF8';
SET standard_conforming_strings = on;
SELECT pg_catalog.set_config('search_path', '', false);
SET check_function_bodies = false;
SET client_min_messages = warning;
SET row_security = off;
SET default_tablespace = '';
SET default_with_oids = false;
--
-- Name: students; Type: TABLE; Schema: public; Owner: postgres
--
CREATE TABLE public.students (
id integer,
f_name character varying(20),
l_name character varying(20)
);
ALTER TABLE public.students OWNER TO postgres;
--
-- Data for Name: students; Type: TABLE DATA; Schema: public; Owner: postgres
--
COPY public.students (id, f_name, l_name) FROM stdin;
1 John Thorn
2 Phil Hampt
3 Sue Dean
4 Johnny Rames
\.
--
-- PostgreSQL database dump complete
Chúng ta có thể thấy tệp có các lệnh SQL cần thiết để tạo lại và điền lại các sinh viên trong bảng.
Nhưng, bản sao lưu có tốt không? Đáng tin cậy và hiệu quả?
Chúng tôi sẽ kiểm tra nó ra và xem.
example_backups=# DROP TABLE students;
DROP TABLE
example_backups=# \dt;
List of relations
Schema | Name | Type | Owner
--------+---------+-------+----------
public | classes | table | postgres
(1 row)
Nó đã biến mất.
Sau đó, từ dòng lệnh chuyển bản sao lưu đã lưu vào psql:
$ psql -U postgres -W -d example_backups -f ~/Example_Dumps/students.sql
Password for user postgres:
SET
SET
SET
SET
SET
set_config
------------
(1 row)
SET
SET
SET
SET
SET
CREATE TABLE
ALTER TABLE
COPY 4
Hãy xác minh trong cơ sở dữ liệu:
example_backups=# \dt;
List of relations
Schema | Name | Type | Owner
--------+----------+-------+----------
public | classes | table | postgres
public | students | table | postgres
(2 rows)
example_backups=# SELECT * FROM students;
id | f_name | l_name
----+--------+--------
1 | John | Thorn
2 | Phil | Hampt
3 | Sue | Dean
4 | Johnny | Rames
(4 rows)
Bảng và dữ liệu đã được khôi phục.
Sao lưu nhiều bảng
Trong ví dụ tiếp theo này, chúng tôi sẽ sao lưu cả hai bảng bằng lệnh này:
example_backups=# \! pg_dump -U postgres -W -t classes -t students -d example_backups > ~/Example_Dumps/all_tables.sql
Password:
(Lưu ý rằng tôi cần chỉ định mật khẩu trong lệnh này do tùy chọn -W, nơi tôi không có trong ví dụ đầu tiên. Sẽ có thêm thông tin về điều này.)
Hãy xác minh lại tệp đã được tạo bằng cách liệt kê nội dung thư mục:
example_backups=# \! ls -a ~/Example_Dumps
. .. all_tables.sql students.sql
Sau đó, thả các bảng:
example_backups=# DROP TABLE classes;
DROP TABLE
example_backups=# DROP TABLE students;
DROP TABLE
example_backups=# \dt;
Did not find any relations.
Sau đó, khôi phục bằng tệp sao lưu all_tables.sql:
$ psql -U postgres -W -d example_backups -f ~/Example_Dumps/all_tables.sql
Password for user postgres:
SET
SET
SET
SET
SET
set_config
------------
(1 row)
SET
SET
SET
SET
SET
CREATE TABLE
ALTER TABLE
CREATE TABLE
ALTER TABLE
COPY 3
COPY 4
example_backups=# \dt;
List of relations
Schema | Name | Type | Owner
--------+----------+-------+----------
public | classes | table | postgres
public | students | table | postgres
(2 rows)
Cả hai bảng đã được khôi phục.
Như chúng ta thấy với pg_dump, bạn có thể sao lưu chỉ một hoặc nhiều bảng trong một cơ sở dữ liệu cụ thể.
Sao lưu cơ sở dữ liệu
Bây giờ chúng ta hãy xem cách sao lưu toàn bộ cơ sở dữ liệu example_backups với pg_dump.
example_backups=# \! pg_dump -U postgres -W -d example_backups > ~/Example_Dumps/ex_back_db.sql
Password:
example_backups=# \! ls -a ~/Example_Dumps
. .. all_tables.sql ex_back_db.sql students.sql
Tệp ex_back_db.sql ở đó.
Tôi sẽ kết nối với cơ sở dữ liệu postgres để bỏ cơ sở dữ liệu example_backups.
postgres=# DROP DATABASE example_backups;
DROP DATABASE
Sau đó, khôi phục từ dòng lệnh:
$ psql -U postgres -W -d example_backups -f ~/Example_Dumps/ex_back_db.sql
Password for user postgres:
psql: FATAL: database "example_backups" does not exist
Nó không có ở đó. Tại sao không? Và nó ở đâu?
Chúng ta phải tạo nó trước.
postgres=# CREATE DATABASE example_backups;
CREATE DATABASE
Sau đó khôi phục bằng lệnh tương tự:
$ psql -U postgres -W -d example_backups -f ~/Example_Dumps/ex_back_db.sql
Password for user postgres:
SET
SET
SET
SET
SET
set_config
------------
(1 row)
SET
SET
SET
CREATE EXTENSION
COMMENT
SET
SET
CREATE TABLE
ALTER TABLE
CREATE TABLE
ALTER TABLE
COPY 3
COPY 4
postgres=# \c example_backups;
You are now connected to database "example_backups" as user "postgres".
example_backups=# \dt;
List of relations
Schema | Name | Type | Owner
--------+----------+-------+----------
public | classes | table | postgres
public | students | table | postgres
(2 rows)
Cơ sở dữ liệu và tất cả các bảng hiện diện và được tính toán.
Chúng ta có thể tránh trường hợp này phải tạo cơ sở dữ liệu đích trước, bằng cách bao gồm tùy chọn -C khi thực hiện sao lưu.
example_backups=# \! pg_dump -U postgres -W -C -d example_backups > ~/Example_Dumps/ex_back2_db.sql
Password:
Tôi sẽ kết nối lại với cơ sở dữ liệu postgres và thả cơ sở dữ liệu example_backups để chúng tôi có thể xem cách khôi phục hoạt động ngay bây giờ (Lưu ý rằng các lệnh kết nối và DROP không được hiển thị cho ngắn gọn).
Sau đó, trên dòng lệnh (chú ý không có tùy chọn -d dbname bao gồm):
$ psql -U postgres -W -f ~/Example_Dumps/ex_back2_db.sql
Password for user postgres:
……………..
(And partway through the output...)
CREATE DATABASE
ALTER DATABASE
Password for user postgres:
You are now connected to database "example_backups" as user "postgres".
SET
SET
SET
SET
SET
set_config
------------
(1 row)
SET
SET
SET
CREATE EXTENSION
COMMENT
SET
SET
CREATE TABLE
ALTER TABLE
CREATE TABLE
ALTER TABLE
COPY 3
COPY 4
Sử dụng tùy chọn -C, chúng tôi được nhắc nhập mật khẩu để tạo kết nối như đã đề cập trong tài liệu liên quan đến cờ -C:
“Bắt đầu kết xuất bằng lệnh để tạo chính cơ sở dữ liệu và kết nối lại với cơ sở dữ liệu đã tạo.”
Sau đó, trong phiên psql:
postgres=# \c example_backups;
You are now connected to database "example_backups" as user "postgres".
Mọi thứ đều được khôi phục, hoạt động tốt và không cần tạo cơ sở dữ liệu đích trước khi khôi phục.
pg_dumpall cho toàn bộ cụm
Cho đến nay, chúng tôi đã sao lưu một bảng, nhiều bảng và một cơ sở dữ liệu.
Nhưng nếu chúng ta muốn nhiều hơn thế, chẳng hạn như sao lưu toàn bộ cụm PostgreSQL, thì đó là nơi chúng ta cần sử dụng pg_dumpall.
Vậy một số khác biệt đáng chú ý giữa pg_dump và pg_dumpall là gì?
Đối với người mới bắt đầu, đây là một điểm khác biệt quan trọng với tài liệu:
“Vì pg_dumpall đọc các bảng từ tất cả các cơ sở dữ liệu, bạn rất có thể sẽ phải kết nối như một siêu người dùng cơ sở dữ liệu để tạo ra một kết xuất hoàn chỉnh. Ngoài ra, bạn sẽ cần các đặc quyền của người dùng siêu cấp để thực thi tập lệnh đã lưu để được phép thêm người dùng và nhóm cũng như tạo cơ sở dữ liệu. ”
Sử dụng lệnh dưới đây, tôi sẽ sao lưu toàn bộ cụm PostgreSQL của mình và lưu nó trong tệp whole_cluster.sql:
$ pg_dumpall -U postgres -W -f ~/Example_Dumps/Cluster_Dumps/entire_cluster.sql
Password:
Password:
Password:
Password:
Password:
Password:
Password:
Password:
Password:
Password:
Password:
Password:
Password:
Password:
Password:
Password:
Password:
Password:
Password:
Password:
Password:
Password:
Password:
Password:
Những gì trên trái đất? Bạn có thắc mắc rằng tôi có phải nhập mật khẩu cho mỗi lời nhắc không?
Vâng, chắc chắn đã làm. 24 lần.
Đếm chúng. (Này, tôi thích khám phá và đi sâu vào các cơ sở dữ liệu khác nhau khi tôi học? Tôi có thể nói gì?)
Nhưng tại sao tất cả các lời nhắc?
Trước hết, sau tất cả những công việc khó khăn đó, pg_dumpall đã tạo tệp sao lưu chưa?
postgres=# \! ls -a ~/Example_Dumps/Cluster_Dumps
. .. entire_cluster.sql
Đúng, tệp sao lưu ở đó.
Hãy làm sáng tỏ tất cả những điều đó ' thực hành đánh máy 'Bằng cách xem đoạn văn này từ tài liệu:
“Pg_dumpall cần kết nối nhiều lần với máy chủ PostgreSQL (một lần cho mỗi cơ sở dữ liệu). Nếu bạn sử dụng xác thực mật khẩu, nó sẽ yêu cầu mật khẩu mỗi lần. ”
Tôi biết bạn đang nghĩ gì.
Điều này có thể không lý tưởng hoặc thậm chí không khả thi. Còn các quy trình, tập lệnh hoặc công việc cron chạy vào nửa đêm thì sao?
Có ai đó sẽ di chuột qua bàn phím, đợi nhập?
Có lẽ là không.
Một biện pháp hiệu quả để tránh phải đối mặt với những lời nhắc mật khẩu lặp đi lặp lại đó là tệp ~ / .pgpass.
Đây là cú pháp mà tệp ~ / .pgpass yêu cầu để hoạt động (ví dụ được cung cấp từ tài liệu, xem liên kết ở trên):
hostname:port:database:username:password
Với tệp ~ / .pgpass có trong môi trường phát triển của tôi, chứa thông tin đăng nhập cần thiết cho vai trò postgres, tôi có thể bỏ qua tùy chọn -W (also -w) và chạy pg_dumpall mà không cần xác thực thủ công bằng mật khẩu:
$ pg_dumpall -U postgres -f ~/Example_Dumps/Cluster_Dumps/entire_cluster2nd.sql
Liệt kê nội dung thư mục:
postgres=# \! ls -a ~/Example_Dumps/Cluster_Dumps
. .. entire_cluster2nd.sql entire_cluster.sql
Tệp được tạo và không có lời nhắc mật khẩu lặp lại.
Tệp đã lưu có thể được tải lại bằng psql tương tự như pg_dump.
Cơ sở dữ liệu kết nối cũng ít quan trọng hơn theo đoạn này từ tài liệu:"Bạn kết nối cơ sở dữ liệu nào ở đây không quan trọng vì tệp script được tạo bởi pg_dumpall sẽ chứa các lệnh thích hợp để tạo và kết nối với cơ sở dữ liệu đã lưu."
Tải xuống Báo cáo chính thức hôm nay Quản lý &Tự động hóa PostgreSQL với ClusterControlTìm hiểu về những điều bạn cần biết để triển khai, giám sát, quản lý và mở rộng PostgreSQLTải xuống Báo cáo chính thứcpg_dump, pg_dumpall và shell scripts - Một sự kết hợp tiện dụng
Trong phần này, chúng ta sẽ thấy một số ví dụ về việc kết hợp pg_dump và pg_dumpall vào các tập lệnh shell đơn giản.
Rõ ràng, đây không phải là một hướng dẫn tập lệnh shell. Tôi cũng không phải là một guru kịch bản shell. Tôi chủ yếu cung cấp một vài ví dụ mà tôi sử dụng trong môi trường học tập / phát triển tại địa phương của mình.
Đầu tiên, hãy xem một tập lệnh shell đơn giản mà bạn có thể sử dụng để sao lưu một cơ sở dữ liệu duy nhất:
#!/bin/bash
# This script performs a pg_dump, saving the file the specified dir.
# The first arg ($1) is the database user to connect with.
# The second arg ($2) is the database to backup and is included in the file name.
# $(date +"%Y_%m_%d") includes the current system date into the actual file name.
pg_dump -U $1 -W -C -d $2 > ~/PG_dumps/Dump_Scripts/$(date +"%Y_%m_%d")_$2.sql
Như bạn có thể thấy, tập lệnh này chấp nhận 2 đối số:đối số đầu tiên là người dùng (hoặc vai trò) để kết nối để sao lưu, trong khi đối số thứ hai là tên của cơ sở dữ liệu bạn muốn sao lưu.
Lưu ý tùy chọn -C trong lệnh để chúng tôi có thể khôi phục nếu cơ sở dữ liệu không tồn tại mà không cần phải tạo thủ công trước.
Hãy gọi tập lệnh với vai trò postgres cho cơ sở dữ liệu example_backups (Đừng quên đặt tập lệnh có thể thực thi với ít nhất chmod + x trước khi gọi lần đầu tiên):
$ ~/My_Scripts/pgd.sh postgres example_backups
Password:
Và xác minh nó ở đó:
$ ls -a ~/PG_dumps/Dump_Scripts/
. .. 2018_06_06_example_backups.sql
Quá trình khôi phục được thực hiện với tập lệnh sao lưu này như trong các ví dụ trước.
Một tập lệnh shell tương tự có thể được sử dụng với pg_dumpall để sao lưu toàn bộ cụm PostgreSQL.
Tập lệnh shell này sẽ chuyển (|) pg_dumpall vào gzip, sau đó được chuyển hướng đến vị trí tệp được chỉ định:
#!/bin/bash
# This shell script calls pg_dumpall and pipes into the gzip utility, then directs to
# a directory for storage.
# $(date +"%Y_%m_%d") incorporates the current system date into the file name.
pg_dumpall -U postgres | gzip > ~/PG_dumps/Cluster_Dumps/$(date +"%Y_%m_%d")_pg_bck.gz
Không giống như tập lệnh mẫu trước, tập lệnh này không chấp nhận bất kỳ đối số nào.
Tôi sẽ gọi tập lệnh này trên dòng lệnh, (không có dấu nhắc mật khẩu vì vai trò postgres sử dụng tệp ~ / .pgpass - Xem phần ở trên.)
$ ~/My_Scripts/pgalldmp.sh
Sau khi hoàn tất, tôi sẽ liệt kê nội dung thư mục cũng hiển thị kích thước tệp để so sánh giữa tệp .sql và gz:
postgres=# \! ls -sh ~/PG_dumps/Cluster_Dumps
total 957M
37M 2018_05_22_pg_bck.gz 32M 2018_06_06_pg_bck.gz 445M entire_cluster2nd.sql 445M entire_cluster.sql
Lưu ý cho định dạng lưu trữ gz từ tài liệu:
“Các định dạng tệp lưu trữ thay thế phải được sử dụng với pg_restore để xây dựng lại cơ sở dữ liệu.”
Tóm tắt
Tôi đã tập hợp các điểm chính từ tài liệu về pg_dump và pg_dumpall, cùng với những quan sát của tôi, để kết thúc bài đăng trên blog này:
Lưu ý:Các điểm được cung cấp từ tài liệu nằm trong dấu ngoặc kép.
- “pg_dump chỉ kết xuất một cơ sở dữ liệu duy nhất”
- Định dạng tệp SQL văn bản thuần túy là đầu ra mặc định cho pg_dump.
- Một vai trò cần đặc quyền SELECT để chạy pg_dump theo dòng này trong tài liệu:“pg_dump thực thi nội bộ các câu lệnh SELECT. Nếu bạn gặp sự cố khi chạy pg_dump, hãy đảm bảo rằng bạn có thể chọn thông tin từ cơ sở dữ liệu bằng cách sử dụng, ví dụ:psql ”
- Để bao gồm lệnh DDL CREATE DATABASE cần thiết và kết nối trong tệp sao lưu, hãy bao gồm tùy chọn -C.
- -W:Tùy chọn này buộc pg_dump phải nhắc nhập mật khẩu. Cờ này không cần thiết vì nếu máy chủ yêu cầu mật khẩu, bạn vẫn được nhắc. Tuy nhiên, đoạn văn này trong tài liệu đã thu hút sự chú ý của tôi nên tôi nghĩ phải đưa nó vào đây:“Tuy nhiên, pg_dump sẽ lãng phí một nỗ lực kết nối để tìm ra rằng máy chủ muốn có mật khẩu. Trong một số trường hợp, bạn nên nhập -W để tránh việc cố gắng kết nối thêm. ”
- -d:Chỉ định cơ sở dữ liệu để kết nối. Cũng trong tài liệu:"Điều này tương đương với việc chỉ định dbname làm đối số không phải tùy chọn đầu tiên trên dòng lệnh."
- Việc sử dụng các cờ như -t (table) cho phép người dùng sao lưu các phần của cơ sở dữ liệu, cụ thể là các bảng, họ có đặc quyền truy cập.
- Các định dạng tệp sao lưu có thể khác nhau. Tuy nhiên, các tệp .sql là một lựa chọn tuyệt vời trong số những tệp khác. Các tệp sao lưu được psql đọc lại để khôi phục.
- pg_dump có thể sao lưu cơ sở dữ liệu đang chạy, hoạt động mà không can thiệp vào các hoạt động khác (tức là người đọc và người viết khác).
- Một lưu ý:pg_dump không kết xuất các vai trò hoặc các đối tượng cơ sở dữ liệu khác bao gồm không gian bảng, chỉ một cơ sở dữ liệu duy nhất.
- Để sao lưu trên toàn bộ cụm PostgreSQL của bạn, pg_dumpall là lựa chọn tốt hơn.
- pg_dumpall có thể xử lý toàn bộ cụm, sao lưu thông tin về vai trò, không gian bảng, người dùng, quyền, v.v. trong đó pg_dump không thể.
- Rất có thể, một vai trò có đặc quyền SUPERUSER sẽ phải thực hiện kết xuất và khôi phục / tạo lại tệp khi nó được đọc lại thông qua psql vì trong quá trình khôi phục, cần có đặc quyền đọc tất cả các bảng trong tất cả cơ sở dữ liệu.
Hy vọng của tôi là thông qua bài đăng trên blog này, tôi đã cung cấp đầy đủ các ví dụ và chi tiết để có cái nhìn tổng quan ở cấp độ người mới bắt đầu về pg_dump và pg_dumpall cho một môi trường PostgreSQL phát triển / học tập duy nhất.
Mặc dù tất cả các tùy chọn có sẵn chưa được khám phá, nhưng tài liệu chính thức chứa nhiều thông tin với các ví dụ cho cả hai tiện ích, vì vậy hãy chắc chắn và tham khảo tài nguyên đó để nghiên cứu thêm, đặt câu hỏi và đọc.