MariaDB
 sql >> Cơ Sở Dữ Liệu >  >> RDS >> MariaDB

ProxySQL Native Clustering với Kubernetes

ProxySQL đã hỗ trợ phân cụm gốc kể từ v1.4.2. Điều này có nghĩa là nhiều phiên bản ProxySQL nhận biết cụm; họ nhận thức được trạng thái của nhau và có thể tự động xử lý các thay đổi cấu hình bằng cách đồng bộ hóa cấu hình cập nhật nhất dựa trên phiên bản cấu hình, dấu thời gian và giá trị tổng kiểm tra. Hãy xem bài đăng trên blog này trình bày cách định cấu hình hỗ trợ phân cụm cho ProxySQL và cách bạn có thể mong đợi nó hoạt động.

ProxySQL là một proxy phi tập trung, được khuyến nghị triển khai gần ứng dụng hơn. Cách tiếp cận này có quy mô khá tốt, thậm chí lên đến hàng trăm nút, vì nó được thiết kế để có thể dễ dàng cấu hình lại trong thời gian chạy. Để quản lý hiệu quả nhiều nút ProxySQL, người ta phải đảm bảo rằng bất kỳ thay đổi nào được thực hiện trên một trong các nút đều phải được áp dụng trên tất cả các nút trong trang trại. Nếu không có phân cụm gốc, người ta phải xuất cấu hình theo cách thủ công và nhập chúng vào các nút khác (mặc dù bạn có thể tự động hóa việc này).

Trong bài đăng trên blog trước, chúng tôi đã đề cập đến phân cụm ProxySQL thông qua Kubernetes ConfigMap. Cách tiếp cận này ít nhiều khá hiệu quả với cách tiếp cận cấu hình tập trung trong ConfigMap. Bất cứ thứ gì được tải vào ConfigMap sẽ được gắn vào các nhóm. Cập nhật cấu hình có thể được thực hiện thông qua lập phiên bản (sửa đổi nội dung proxysql.cnf và tải nó vào ConfigMap với một tên khác) và sau đó đẩy đến các nhóm tùy thuộc vào chiến lược cập nhật và lập lịch của phương pháp Triển khai.

Tuy nhiên, trong một môi trường thay đổi nhanh chóng, cách tiếp cận ConfigMap này có lẽ không phải là phương pháp tốt nhất vì để tải cấu hình mới, cần lập lại lịch nhóm để gắn lại khối lượng ConfigMap và điều này có thể gây nguy hiểm cho toàn bộ dịch vụ ProxySQL. Ví dụ:giả sử trong môi trường của chúng tôi, chính sách mật khẩu nghiêm ngặt của chúng tôi yêu cầu buộc phải hết hạn mật khẩu người dùng MySQL cứ sau 7 ngày, chúng tôi sẽ phải tiếp tục cập nhật Bản đồ cấu hình ProxySQL cho mật khẩu mới hàng tuần. Lưu ý thêm, người dùng MySQL bên trong ProxySQL yêu cầu người dùng và mật khẩu phải khớp với mật khẩu trên các máy chủ MySQL phụ trợ. Đó là nơi chúng ta nên bắt đầu sử dụng hỗ trợ phân cụm gốc ProxySQL trong Kubernetes, để tự động áp dụng các thay đổi cấu hình mà không gặp rắc rối trong việc lập lịch lại phiên bản ConfigMap và nhóm.

Trong bài đăng trên blog này, chúng tôi sẽ chỉ cho bạn cách chạy phân cụm gốc ProxySQL với dịch vụ không đầu trên Kubernetes. Kiến trúc cấp cao của chúng tôi có thể được minh họa như sau:

Chúng tôi có 3 nút Galera đang chạy trên cơ sở hạ tầng kim loại trần do ClusterControl triển khai và quản lý:

  • 192.168.0.21
  • 192.168.0.22
  • 192.168.0.23

Các ứng dụng của chúng tôi đều đang chạy dưới dạng nhóm trong Kubernetes. Ý tưởng là giới thiệu hai phiên bản ProxySQL giữa ứng dụng và cụm cơ sở dữ liệu của chúng tôi để phục vụ như một proxy ngược. Sau đó, các ứng dụng sẽ kết nối với các nhóm ProxySQL thông qua dịch vụ Kubernetes, dịch vụ này sẽ được cân bằng tải và chuyển đổi dự phòng trên một số bản sao ProxySQL.

Sau đây là tóm tắt về thiết lập Kubernetes của chúng tôi:

[email protected]:~# kubectl get nodes -o wide
NAME    STATUS   ROLES    AGE     VERSION   INTERNAL-IP       EXTERNAL-IP   OS-IMAGE             KERNEL-VERSION      CONTAINER-RUNTIME
kube1   Ready    master   5m      v1.15.1   192.168.100.201   <none>        Ubuntu 18.04.1 LTS   4.15.0-39-generic   docker://18.9.7
kube2   Ready    <none>   4m1s    v1.15.1   192.168.100.202   <none>        Ubuntu 18.04.1 LTS   4.15.0-39-generic   docker://18.9.7
kube3   Ready    <none>   3m42s   v1.15.1   192.168.100.203   <none>        Ubuntu 18.04.1 LTS   4.15.0-39-generic   docker://18.9.7

Cấu hình ProxySQL qua Bản đồ cấu hình

Đầu tiên chúng ta hãy chuẩn bị cấu hình cơ sở của chúng ta sẽ được tải vào ConfigMap. Tạo một tệp có tên proxysql.cnf và thêm các dòng sau:

datadir="/var/lib/proxysql"

admin_variables=
{
    admin_credentials="proxysql-admin:adminpassw0rd;cluster1:secret1pass"
    mysql_ifaces="0.0.0.0:6032"
    refresh_interval=2000
    cluster_username="cluster1"
    cluster_password="secret1pass"
    cluster_check_interval_ms=200
    cluster_check_status_frequency=100
    cluster_mysql_query_rules_save_to_disk=true
    cluster_mysql_servers_save_to_disk=true
    cluster_mysql_users_save_to_disk=true
    cluster_proxysql_servers_save_to_disk=true
    cluster_mysql_query_rules_diffs_before_sync=3
    cluster_mysql_servers_diffs_before_sync=3
    cluster_mysql_users_diffs_before_sync=3
    cluster_proxysql_servers_diffs_before_sync=3
}

mysql_variables=
{
    threads=4
    max_connections=2048
    default_query_delay=0
    default_query_timeout=36000000
    have_compress=true
    poll_timeout=2000
    interfaces="0.0.0.0:6033;/tmp/proxysql.sock"
    default_schema="information_schema"
    stacksize=1048576
    server_version="5.1.30"
    connect_timeout_server=10000
    monitor_history=60000
    monitor_connect_interval=200000
    monitor_ping_interval=200000
    ping_interval_server_msec=10000
    ping_timeout_server=200
    commands_stats=true
    sessions_sort=true
    monitor_username="proxysql"
    monitor_password="proxysqlpassw0rd"
    monitor_galera_healthcheck_interval=2000
    monitor_galera_healthcheck_timeout=800
}

mysql_galera_hostgroups =
(
    {
        writer_hostgroup=10
        backup_writer_hostgroup=20
        reader_hostgroup=30
        offline_hostgroup=9999
        max_writers=1
        writer_is_also_reader=1
        max_transactions_behind=30
        active=1
    }
)

mysql_servers =
(
    { address="192.168.0.21" , port=3306 , hostgroup=10, max_connections=100 },
    { address="192.168.0.22" , port=3306 , hostgroup=10, max_connections=100 },
    { address="192.168.0.23" , port=3306 , hostgroup=10, max_connections=100 }
)

mysql_query_rules =
(
    {
        rule_id=100
        active=1
        match_pattern="^SELECT .* FOR UPDATE"
        destination_hostgroup=10
        apply=1
    },
    {
        rule_id=200
        active=1
        match_pattern="^SELECT .*"
        destination_hostgroup=20
        apply=1
    },
    {
        rule_id=300
        active=1
        match_pattern=".*"
        destination_hostgroup=10
        apply=1
    }
)

mysql_users =
(
    { username = "wordpress", password = "passw0rd", default_hostgroup = 10, transaction_persistent = 0, active = 1 },
    { username = "sbtest", password = "passw0rd", default_hostgroup = 10, transaction_persistent = 0, active = 1 }
)

proxysql_servers =
(
    { hostname = "proxysql-0.proxysqlcluster", port = 6032, weight = 1 },
    { hostname = "proxysql-1.proxysqlcluster", port = 6032, weight = 1 }
)

Một số dòng cấu hình trên được giải thích theo từng phần bên dưới:

admin_variables

Chú ý đến admin_credentials biến mà chúng tôi đã sử dụng người dùng không phải mặc định là "proxysql-admin". ProxySQL chỉ dành riêng người dùng "quản trị viên" mặc định cho kết nối cục bộ qua máy chủ cục bộ. Do đó, chúng tôi phải sử dụng những người dùng khác để truy cập phiên bản ProxySQL từ xa. Nếu không, bạn sẽ gặp lỗi sau:

ERROR 1040 (42000): User 'admin' can only connect locally

Chúng tôi cũng đã thêm cluster_username cluster_password giá trị trong admin_credentials , được phân tách bằng dấu chấm phẩy để cho phép đồng bộ hóa tự động diễn ra. Tất cả các biến có tiền tố là cluster_ * có liên quan đến phân cụm gốc ProxySQL và có thể tự giải thích.

mysql_galera_hostgroups

Đây là một chỉ thị mới được giới thiệu cho ProxySQL 2.x (hình ảnh ProxySQL của chúng tôi đang chạy trên 2.0.5). Nếu bạn muốn chạy trên ProxySQL 1.x, hãy xóa phần này và sử dụng bảng lập lịch để thay thế. Chúng tôi đã giải thích chi tiết cấu hình trong bài đăng blog này, Cách chạy và định cấu hình ProxySQL 2.0 cho MySQL Galera Cluster trên Docker trong "Hỗ trợ ProxySQL 2.x cho Galera Cluster".

mysql_servers

Tất cả các dòng đều tự giải thích dựa trên ba máy chủ cơ sở dữ liệu đang chạy trong MySQL Galera Cluster như được tóm tắt trong ảnh chụp màn hình Topology sau được lấy từ ClusterControl:

proxysql_servers

Ở đây chúng tôi xác định danh sách các đồng nghiệp ProxySQL:

  • tên máy chủ - tên máy chủ / địa chỉ IP của ngang hàng
  • port - Cổng quản trị viên ngang hàng
  • trọng lượng - Hiện chưa sử dụng, nhưng đang trong lộ trình cải tiến trong tương lai
  • bình luận - Trường bình luận biểu mẫu miễn phí

Trong môi trường Docker / Kubernetes, có nhiều cách để khám phá và liên kết tên máy chủ vùng chứa hoặc địa chỉ IP và chèn chúng vào bảng này, bằng cách sử dụng Bản đồ cấu hình, chèn thủ công, thông qua tập lệnh entrypoint.sh, biến môi trường hoặc một số phương tiện khác. Trong Kubernetes, tùy thuộc vào phương pháp ReplicationController hoặc Deployment được sử dụng, việc đoán trước tên máy chủ có thể phân giải của pod hơi khó, trừ khi bạn đang chạy trên StatefulSet.

Hãy xem hướng dẫn này về chỉ mục thứ tự của nhóm StatefulState cung cấp tên máy chủ có thể phân giải ổn định cho các nhóm đã tạo. Kết hợp điều này với dịch vụ không đầu (giải thích thêm), định dạng tên máy chủ có thể phân giải sẽ là:

{app_name} - {index_number}. {service}

Trong đó {service} là một dịch vụ không có đầu, giải thích "proxysql-0.proxysqlcluster" và "proxysql-1.proxysqlcluster" đến từ đâu. Nếu bạn muốn có nhiều hơn 2 bản sao, hãy thêm nhiều mục nhập tương ứng bằng cách thêm số chỉ mục tăng dần liên quan đến tên ứng dụng StatefulSet.

Bây giờ chúng tôi đã sẵn sàng đẩy tệp cấu hình vào ConfigMap, tệp này sẽ được gắn vào mọi pod ProxySQL trong quá trình triển khai:

$ kubectl create configmap proxysql-configmap --from-file=proxysql.cnf

Xác minh xem Bản đồ cấu hình của chúng tôi có được tải đúng không:

$ kubectl get configmap
NAME                 DATA   AGE
proxysql-configmap   1      7h57m

Tạo người dùng giám sát ProxySQL

Bước tiếp theo trước khi chúng tôi bắt đầu triển khai là tạo một người dùng giám sát ProxySQL trong cụm cơ sở dữ liệu của chúng tôi. Vì chúng tôi đang chạy trên cụm Galera, hãy chạy các câu lệnh sau trên một trong các nút Galera:

mysql> CREATE USER 'proxysql'@'%' IDENTIFIED BY 'proxysqlpassw0rd';
mysql> GRANT USAGE ON *.* TO 'proxysql'@'%';

Nếu bạn chưa tạo người dùng MySQL (như được chỉ định trong phần mysql_users ở trên), chúng tôi cũng phải tạo họ:

mysql> CREATE USER 'wordpress'@'%' IDENTIFIED BY 'passw0rd';
mysql> GRANT ALL PRIVILEGES ON wordpress.* TO 'wordpress'@'%';
mysql> CREATE USER 'sbtest'@'%' IDENTIFIED BY 'passw0rd';
mysql> GRANT ALL PRIVILEGES ON sbtest.* TO 'proxysql'@'%';

Đó là nó. Hiện chúng tôi đã sẵn sàng để bắt đầu triển khai.

Triển khai StatefulSet

Chúng tôi sẽ bắt đầu bằng cách tạo hai phiên bản ProxySQL hoặc bản sao cho mục đích dự phòng bằng cách sử dụng StatefulSet.

Hãy bắt đầu bằng cách tạo một tệp văn bản có tên là proxysql-ss-svc.yml và thêm các dòng sau:

apiVersion: apps/v1
kind: StatefulSet
metadata:
  name: proxysql
  labels:
    app: proxysql
spec:
  replicas: 2
  serviceName: proxysqlcluster
  selector:
    matchLabels:
      app: proxysql
      tier: frontend
  updateStrategy:
    type: RollingUpdate
  template:
    metadata:
      labels:
        app: proxysql
        tier: frontend
    spec:
      restartPolicy: Always
      containers:
      - image: severalnines/proxysql:2.0.4
        name: proxysql
        volumeMounts:
        - name: proxysql-config
          mountPath: /etc/proxysql.cnf
          subPath: proxysql.cnf
        ports:
        - containerPort: 6033
          name: proxysql-mysql
        - containerPort: 6032
          name: proxysql-admin
      volumes:
      - name: proxysql-config
        configMap:
          name: proxysql-configmap
---
apiVersion: v1
kind: Service
metadata:
  annotations:
  labels:
    app: proxysql
    tier: frontend
  name: proxysql
spec:
  ports:
  - name: proxysql-mysql
    nodePort: 30033
    port: 6033
    protocol: TCP
    targetPort: 6033
  - name: proxysql-admin
    nodePort: 30032
    port: 6032
    protocol: TCP
    targetPort: 6032
  selector:
    app: proxysql
    tier: frontend
  type: NodePort

Có hai phần của định nghĩa trên - StatefulSet và Service. StatefulSet là định nghĩa về nhóm hoặc bản sao của chúng tôi và là điểm gắn kết cho khối lượng Bản đồ cấu hình của chúng tôi, được tải từ proxysql-configmap. Phần tiếp theo là định nghĩa dịch vụ, nơi chúng tôi xác định cách các nhóm sẽ được hiển thị và định tuyến cho mạng nội bộ hoặc mạng bên ngoài.

Tạo dịch vụ và tập hợp trạng thái ProxySQL:

$ kubectl create -f proxysql-ss-svc.yml

Xác minh nhóm và trạng thái dịch vụ:

$ kubectl get pods,svc
NAME             READY   STATUS    RESTARTS   AGE
pod/proxysql-0   1/1     Running   0          4m46s
pod/proxysql-1   1/1     Running   0          2m59s

NAME                      TYPE        CLUSTER-IP       EXTERNAL-IP   PORT(S)                         AGE
service/kubernetes        ClusterIP   10.96.0.1        <none>        443/TCP                         10h
service/proxysql          NodePort    10.111.240.193   <none>        6033:30033/TCP,6032:30032/TCP   5m28s

Nếu bạn nhìn vào nhật ký của nhóm, bạn sẽ nhận thấy rằng chúng tôi đã tràn ngập cảnh báo này:

$ kubectl logs -f proxysql-0
...
2019-08-01 19:06:18 ProxySQL_Cluster.cpp:215:ProxySQL_Cluster_Monitor_thread(): [WARNING] Cluster: unable to connect to peer proxysql-1.proxysqlcluster:6032 . Error: Unknown MySQL server host 'proxysql-1.proxysqlcluster' (0)

Điều trên chỉ đơn giản có nghĩa là proxysql-0 không thể phân giải "proxysql-1.proxysqlcluster" và kết nối với nó, điều này được mong đợi vì chúng tôi chưa tạo dịch vụ headless cho các bản ghi DNS cần thiết cho giao tiếp giữa các ProxySQL.

Dịch vụ không đầu của Kubernetes

Để các nhóm ProxySQL có thể phân giải FQDN được mong đợi và kết nối trực tiếp với nó, quá trình giải quyết phải có khả năng tra cứu địa chỉ IP nhóm mục tiêu được chỉ định chứ không phải địa chỉ IP ảo. Đây là lúc mà dịch vụ không đầu xuất hiện trong bức tranh. Khi tạo dịch vụ không có đầu bằng cách đặt "clusterIP =None", không có tính năng cân bằng tải nào được định cấu hình và không có IP cụm (IP ảo) nào được cấp cho dịch vụ này. Chỉ DNS được cấu hình tự động. Khi bạn chạy truy vấn DNS cho dịch vụ không có đầu, bạn sẽ nhận được danh sách các địa chỉ IP của nhóm.

Đây là giao diện nếu chúng tôi tra cứu các bản ghi DNS dịch vụ không đầu cho "proxysqlcluster" (trong ví dụ này, chúng tôi có 3 phiên bản ProxySQL):

$ host proxysqlcluster
proxysqlcluster.default.svc.cluster.local has address 10.40.0.2
proxysqlcluster.default.svc.cluster.local has address 10.40.0.3
proxysqlcluster.default.svc.cluster.local has address 10.32.0.2

Trong khi đầu ra sau đây hiển thị bản ghi DNS cho dịch vụ tiêu chuẩn có tên "proxysql" phân giải thành clusterIP:

$ host proxysql
proxysql.default.svc.cluster.local has address 10.110.38.154

Để tạo một dịch vụ không có đầu và đính kèm nó vào các nhóm, người ta phải xác định ServiceName bên trong khai báo StatefulSet và định nghĩa Dịch vụ phải có "clusterIP =None" như được hiển thị bên dưới. Tạo một tệp văn bản có tên proxysql-headless-svc.yml và thêm các dòng sau:

apiVersion: v1
kind: Service
metadata:
  name: proxysqlcluster
  labels:
    app: proxysql
spec:
  clusterIP: None
  ports:
  - port: 6032
    name: proxysql-admin
  selector:
    app: proxysql

Tạo dịch vụ không đầu:

$ kubectl create -f proxysql-headless-svc.yml

Chỉ để xác minh, tại thời điểm này, chúng tôi có các dịch vụ sau đang chạy:

$ kubectl get svc
NAME              TYPE        CLUSTER-IP      EXTERNAL-IP   PORT(S)                         AGE
kubernetes        ClusterIP   10.96.0.1       <none>        443/TCP                         8h
proxysql          NodePort    10.110.38.154   <none>        6033:30033/TCP,6032:30032/TCP   23m
proxysqlcluster   ClusterIP   None            <none>        6032/TCP                        4s

Bây giờ, hãy xem một trong nhật ký nhóm của chúng tôi:

$ kubectl logs -f proxysql-0
...
2019-08-01 19:06:19 ProxySQL_Cluster.cpp:215:ProxySQL_Cluster_Monitor_thread(): [WARNING] Cluster: unable to connect to peer proxysql-1.proxysqlcluster:6032 . Error: Unknown MySQL server host 'proxysql-1.proxysqlcluster' (0)
2019-08-01 19:06:19 [INFO] Cluster: detected a new checksum for mysql_query_rules from peer proxysql-1.proxysqlcluster:6032, version 1, epoch 1564686376, checksum 0x3FEC69A5C9D96848 . Not syncing yet ...
2019-08-01 19:06:19 [INFO] Cluster: checksum for mysql_query_rules from peer proxysql-1.proxysqlcluster:6032 matches with local checksum 0x3FEC69A5C9D96848 , we won't sync.

Bạn sẽ nhận thấy thành phần Cluster có thể phân giải, kết nối và phát hiện một tổng kiểm tra mới từ một đồng đẳng khác, proxysql-1.proxysqlcluster trên cổng 6032 thông qua dịch vụ không đầu có tên "proxysqlcluster". Lưu ý rằng dịch vụ này chỉ hiển thị cổng 6032 trong mạng Kubernetes, do đó không thể truy cập bên ngoài.

Tại thời điểm này, việc triển khai của chúng tôi đã hoàn tất.

Kết nối với ProxySQL

Có một số cách để kết nối với các dịch vụ ProxySQL. Các kết nối MySQL cân bằng tải phải được gửi đến cổng 6033 từ bên trong mạng Kubernetes và sử dụng cổng 30033 nếu máy khách đang kết nối từ mạng bên ngoài.

Để kết nối với giao diện quản trị ProxySQL từ mạng bên ngoài, chúng ta có thể kết nối với cổng được xác định trong phần NodePort, 30032 (192.168.100.203 là địa chỉ IP chính của máy chủ kube3.local):

$ mysql -uproxysql-admin -padminpassw0rd -h192.168.100.203 -P30032

Sử dụng clusterIP 10.110.38.154 (được định nghĩa trong dịch vụ "proxysql") trên cổng 6032 nếu bạn muốn truy cập nó từ các nhóm khác trong mạng Kubernetes.

Sau đó, thực hiện các thay đổi cấu hình ProxySQL như bạn muốn và tải chúng vào thời gian chạy:

mysql> INSERT INTO mysql_users (username,password,default_hostgroup) VALUES ('newuser','passw0rd',10);
mysql> LOAD MYSQL USERS TO RUNTIME;

Bạn sẽ nhận thấy các dòng sau ở một trong các nhóm cho biết quá trình đồng bộ hóa cấu hình đã hoàn tất:

$ kubectl logs -f proxysql-0
...
2019-08-02 03:53:48 [INFO] Cluster: detected a peer proxysql-1.proxysqlcluster:6032 with mysql_users version 2, epoch 1564718027, diff_check 4. Own version: 1, epoch: 1564714803. Proceeding with remote sync
2019-08-02 03:53:48 [INFO] Cluster: detected peer proxysql-1.proxysqlcluster:6032 with mysql_users version 2, epoch 1564718027
2019-08-02 03:53:48 [INFO] Cluster: Fetching MySQL Users from peer proxysql-1.proxysqlcluster:6032 started
2019-08-02 03:53:48 [INFO] Cluster: Fetching MySQL Users from peer proxysql-1.proxysqlcluster:6032 completed

Hãy nhớ rằng đồng bộ hóa tự động chỉ xảy ra nếu có thay đổi cấu hình trong thời gian chạy ProxySQL. Do đó, điều quan trọng là phải chạy câu lệnh "LOAD ... TO RUNTIME" trước khi bạn có thể thấy hành động. Đừng quên lưu các thay đổi ProxySQL vào đĩa để duy trì ổn định:

mysql> SAVE MYSQL USERS TO DISK;

Giới hạn

Lưu ý rằng có một hạn chế đối với thiết lập này do ProxySQL không hỗ trợ lưu / xuất cấu hình đang hoạt động thành tệp cấu hình văn bản mà chúng ta có thể sử dụng sau này để tải vào Bản đồ cấu hình cho bền vững. Có một yêu cầu tính năng cho điều này. Trong khi đó, bạn có thể đẩy các sửa đổi lên ConfigMap theo cách thủ công. Nếu không, nếu các nhóm vô tình bị xóa, bạn sẽ mất cấu hình hiện tại của mình vì các nhóm mới sẽ được khởi động bởi bất kỳ thứ gì được xác định trong Bản đồ cấu hình.

Đặc biệt cảm ơn Sampath Kamineni, người đã khơi dậy ý tưởng về bài đăng trên blog này và cung cấp thông tin chi tiết về các trường hợp sử dụng và cách triển khai.


  1. Database
  2.   
  3. Mysql
  4.   
  5. Oracle
  6.   
  7. Sqlserver
  8.   
  9. PostgreSQL
  10.   
  11. Access
  12.   
  13. SQLite
  14.   
  15. MariaDB
  1. MariaDB CONNECTION_ID () Giải thích

  2. Bảo mật sao lưu MySQL:Hướng dẫn

  3. Cách REPEAT () hoạt động trong MariaDB

  4. Cách ABS () hoạt động trong MariaDB

  5. 2 cách để có được bộ ký tự có sẵn trong MariaDB