查看、校驗、歸檔…帶你掌握openGauss賬本資料庫

来源:https://www.cnblogs.com/huaweiyun/archive/2022/11/16/16895337.html
-Advertisement-
Play Games

​摘要:賬本資料庫融合了區塊鏈思想,將用戶操作記錄至兩種歷史表中:用戶歷史表和全局區塊表。 本文分享自華為雲社區《openGauss賬本資料庫,你不知道的那些事兒》,作者:Gauss松鼠會。 賬本資料庫融合了區塊鏈思想,將用戶操作記錄至兩種歷史表中:用戶歷史表和全局區塊表。當用戶創建防篡改用戶表時, ...


摘要:賬本資料庫融合了區塊鏈思想,將用戶操作記錄至兩種歷史表中:用戶歷史表和全局區塊表。

本文分享自華為雲社區《openGauss賬本資料庫,你不知道的那些事兒》,作者:Gauss松鼠會。

賬本資料庫融合了區塊鏈思想,將用戶操作記錄至兩種歷史表中:用戶歷史表和全局區塊表。當用戶創建防篡改用戶表時,系統將自動為該表添加一個hash列來保存每行數據的hash摘要信息,同時在blockchain模式下會創建一張用戶歷史表來記錄對應用戶表中每條數據的變更行為;而用戶對防篡改用戶表的一次修改行為將記錄至全局區塊表中。由於歷史表具有隻可追加不可修改的特點,因此歷史表記錄串聯起來便形成了用戶對防篡改用戶表的修改歷史。

操作步驟

1.創建防篡改模式。

openGauss=# CREATE SCHEMA ledgernsp WITH BLOCKCHAIN;

首先在這個SQL中我們可以看到WITH BLOCKCHAIN ,這裡說明創建出來的SCHEMA與普通的SCHEMA不同,但就行不同在哪裡我們後面會提到。

  • 從語法解析看,增加了對BLOCKCHAIN的處理,標記了是否為賬本模式。
 CreateSchema ::= CREATE SCHEMA schema_name
     [ AUTHORIZATION user_name ] [WITH BLOCKCHAIN] [ schema_element [ ... ] ];

  • CreateSchemaStmt 結構中增加了bool類型欄位hasBlockChain
typedef struct CreateSchemaStmt {
   NodeTag type;
 char *schemaname;  /* the name of the schema to create */
 char *authid;      /* the owner of the created schema */
 bool hasBlockChain;  /* whether this schema has blockchain */
   List *schemaElts;  /* schema components (list of parsenodes) */
   TempType temptype; /* if the schema is temp table's schema */
   List *uuids;       /* the list of uuid(only create sequence or table with serial type need) */
} CreateSchemaStmt;

你不知道的限制

賬本資料庫對於ALTER SCHEMA的幾個限制

1)dbe_perf和snapshot兩個模式不能ALTER為blockchain模式。

 if (withBlockchain && ((strncmp(nspName, "dbe_perf", STR_SCHEMA_NAME_LENGTH) == 0) ||
       (strncmp(nspName, "snapshot", STR_SNAPSHOT_LENGTH) == 0))) {
       ereport(ERROR, (errcode(ERRCODE_OPERATE_FAILED),
                       errmsg("The schema '%s' doesn't allow to alter to blockchain schema", nspName)));
   }

2)系統模式不能 ALTER 為blockchain模式。

 if (withBlockchain && !g_instance.attr.attr_common.allowSystemTableMods &&
       !u_sess->attr.attr_common.IsInplaceUpgrade && IsReservedName(nspName))
       ereport(ERROR,
           (errcode(ERRCODE_RESERVED_NAME),
               errmsg("The system schema \"%s\" doesn't allow to alter to blockchain schema", nspName)));

3)包含了表的SCHEMA不能ALTER為blockchain模式。

 /*
    * If the any table exists in the schema, do not change to ledger schema.
    */
   StringInfo existTbl = TableExistInSchema(HeapTupleGetOid(tup), TABLE_TYPE_ANY);
 if (existTbl->len != 0) {
 if (withBlockchain) {
           ereport(ERROR,
               (errcode(ERRCODE_RESERVED_NAME),
                   errmsg("It is not supported to change \"%s\" to blockchain schema which includes tables.",
                       nspName)));
       } else {
           ereport(ERROR,
               (errcode(ERRCODE_RESERVED_NAME),
                   errmsg("It is not supported to change \"%s\" to normal schema which includes tables.",
                       nspName)));
       }
   }

查看模式

2.在防篡改模式下創建防篡改用戶表。

openGauss=# CREATE TABLE ledgernsp.usertable(id int, name text);

你不知道的限制

  • 創建賬本表的同時會自動創建一個“歷史表”和“歷史表的索引”。

在建表時CreateCommand會調用AlterCreateChainTables,如果是賬本表再去調用create_hist_relation來創建歷史表

CreateCommand -> AlterCreateChainTables -> create_hist_relation

/*
* AlterCreateChainTables
*    If it is a ledger usertable, that should invoking this function.
*    then create a history table.
*/
void AlterCreateChainTables(Oid relOid, Datum reloptions, CreateStmt *mainTblStmt)
{
   Relation rel = NULL;
   rel = heap_open(relOid, AccessExclusiveLock);
 /* Ledger user table only support for the regular relation. */
 if (!rel->rd_isblockchain) {
       heap_close(rel, NoLock);
 return;
   }
   create_hist_relation(rel, reloptions, mainTblStmt);
   heap_close(rel, NoLock);
}
  • 歷史表命名規則,參見函數get_hist_name
bool get_hist_name(Oid relid, const char *rel_name, char *hist_name, Oid nsp_oid, const char *nsp_name)
{
   errno_t rc;
 if (!OidIsValid(relid) || rel_name == NULL) {
 return false;
   }
   nsp_oid = OidIsValid(nsp_oid) ? nsp_oid : get_rel_namespace(relid);
   nsp_name = (nsp_name == NULL) ? get_namespace_name(nsp_oid) : nsp_name;
 int part_hist_name_len = strlen(rel_name) + strlen(nsp_name) + 1;
 if (part_hist_name_len + strlen("_hist") >= NAMEDATALEN) {
       rc = snprintf_s(hist_name, NAMEDATALEN, NAMEDATALEN - 1, "%d_%d_hist", nsp_oid, relid);
       securec_check_ss(rc, "", "");
   } else {
       rc = snprintf_s(hist_name, NAMEDATALEN, NAMEDATALEN - 1, "%s_%s_hist", nsp_name, rel_name);
       securec_check_ss(rc, "", "");
   }
 return true;
}
  • 表名最大長度 #define NAMEDATALEN 64
  • 如果沒有超過長度限制:schema_table_hist
  • 如果超過長度限制:schema(oid)_talbe(oid)_hist,因為oid是unsigned int 類型最大值為4294967295為10位,所以這種命名規則的最大長度為10+1+10+1+4+\0=27,因此永遠不會超過最大長度64。
omm=# create schema aaaaaaaaaaaaaaaaaaaaaaaaaaaaaa with blockchain;
CREATE SCHEMA
omm=# create table aaaaaaaaaaaaaaaaaaaaaaaaaaaaaa.bbbbbbbbbbbbbbbbbbbbbbbbbbbbbb(id int);
CREATE TABLE

  • 歷史表索引命名規則,參見函數get_hist_name
 /* now create index for this new history table */
 char hist_index_name[NAMEDATALEN];
   rc = snprintf_s(hist_index_name, NAMEDATALEN, NAMEDATALEN - 1, "gs_hist_%u_index", relid);
  • 命名規則:gs_hist_$(賬本表oid)_index。

3、修改防篡改用戶表數據

對防篡改用戶表執行INSERT/UPDATE/DELETE。

openGauss=# INSERT INTO ledgernsp.usertable VALUES(1, 'alex'), (2, 'bob'), (3, 'peter');
INSERT 0 3
openGauss=# SELECT *, hash FROM ledgernsp.usertable ORDER BY id;
id | name  |       hash
----+-------+------------------
 1 | alex  | 1f2e543c580cb8c5
 2 | bob   | 8fcd74a8a6a4b484
 3 | peter | f51b4b1b12d0354b
(3 rows)
openGauss=# UPDATE ledgernsp.usertable SET name = 'bob2' WHERE id = 2;
UPDATE 1
openGauss=# SELECT *, hash FROM ledgernsp.usertable ORDER BY id;
id | name  |       hash
----+-------+------------------
 1 | alex  | 1f2e543c580cb8c5
 2 | bob2  | 437761affbb7c605
 3 | peter | f51b4b1b12d0354b
(3 rows)
openGauss=# DELETE FROM ledgernsp.usertable WHERE id = 3;
DELETE 1
openGauss=# SELECT *, hash FROM ledgernsp.usertable ORDER BY id;
id | name |       hash
----+------+------------------
 1 | alex | 1f2e543c580cb8c5
 2 | bob2 | 437761affbb7c605
(2 rows)

查看賬本歷史操作記錄

官方文檔

前提條件

  • 系統中需要有審計管理員或者具有審計管理員許可權的角色。
  • 資料庫正常運行,並且對防篡改資料庫執行了一系列增、刪、改等操作,保證在查詢時段內有賬本操作記錄結果產生。

基本操作

1、查詢全局區塊表記錄。

omm=# SELECT * FROM gs_global_chain;
blocknum | dbname | username |           starttime           | relid |  relnsp   |  relname  |     relhash      |            globalhash            |
                 txcommand
----------+--------+----------+-------------------------------+-------+-----------+-----------+------------------+----------------------------------+----------------
--------------------------------------------------------------
       1 | omm    | omm      | 2022-09-17 13:59:37.84824+00  | 16404 | ledgernsp | usertable | a41714001181a294 | 83927d11ba1fd678e8f4b0723a9cd5f2 | INSERT INTO led
gernsp.usertable VALUES(1, 'alex'), (2, 'bob'), (3, 'peter');
       2 | omm    | omm      | 2022-09-17 13:59:51.723068+00 | 16404 | ledgernsp | usertable | b3a9ed0755131181 | b5ee73b6c20c817230182f6373c78e20 | UPDATE ledgerns
p.usertable SET name = 'bob2' WHERE id = 2;
       3 | omm    | omm      | 2022-09-17 13:59:58.159596+00 | 16404 | ledgernsp | usertable | 0ae4b4e4ed2fcab5 | 0cc9938cf7f1ed7f7f1a03c29954380a | DELETE FROM led
gernsp.usertable WHERE id = 3;
(3 rows)
  • 註冊鉤子,在對賬本做修改操作的時候註冊的鉤子函數ledger_ExecutorEnd被回調。
/*
* ledger_hook_init -- install of gchain block record hook.
*/
void ledger_hook_init(void)
{
   t_thrd.security_ledger_cxt.prev_ExecutorEnd = (void *)ExecutorEnd_hook;
   ExecutorEnd_hook = ledger_ExecutorEnd;
}
  • 生成globalhash規則
全局區塊表記錄主要是生成globalhash.

調用過程:

ledger_ExecutorEnd --> ledger_gchain_append --> set_gchain_comb_string

--> get_next_g_blocknum

--> gen_global_hash

  • set_gchain_comb_string,是一組字元串拼接成的:rel_name + nsp_name + query_string + rel_hash
  • get_next_g_blocknum,用全局變數g_blocknum保存
  • gen_global_hash,是的set_gchain_comb_string拼出來的串+上一條的hash值拼串然後再去hash——區塊鏈的基本原理
bool gen_global_hash(hash32_t *hash_buffer, const char *info_string, bool exist, const hash32_t *prev_hash)
{
   errno_t rc = EOK;
 int comb_strlen;
 char *comb_string = NULL;
 /*
    * Previous block not exists means current insertion block is genesis,
    * then we use global systable as origin combine string for globalhash
    * generation. If previous block exists, we will use previous global
    * hash as combine string to calculate globalhash.
    */
 if (!exist) {
 /* generate genesis block globalhash */
       comb_strlen = strlen(GCHAIN_NAME) + strlen(info_string) + 1;
       comb_string = (char *)palloc0(comb_strlen);
       rc = snprintf_s(comb_string, comb_strlen, comb_strlen - 1, "%s%s", GCHAIN_NAME, info_string);
       securec_check_ss(rc, "", "");
   } else {
 /* use previous globalhash and current block info to calculate globalhash. */
 char *pre_hash_str = DatumGetCString(DirectFunctionCall1(hash32out, HASH32GetDatum(prev_hash)));
       comb_strlen = strlen(pre_hash_str) + strlen(info_string) + 1;
       comb_string = (char *)palloc0(comb_strlen);
       rc = snprintf_s(comb_string, comb_strlen, comb_strlen - 1, "%s%s", info_string, pre_hash_str);
       securec_check_ss(rc, "", "");
       pfree_ext(pre_hash_str);
   }
 if (!pg_md5_binary(comb_string, comb_strlen - 1, hash_buffer->data)) {
       pfree(comb_string);
       ereport(ERROR, (errcode(ERRCODE_OUT_OF_MEMORY), errmsg("Failed to generate globalhash, out of memory")));
 return false;
   }
   pfree(comb_string);
 return true;
}
  • 在src/gausskernel/runtime/executor/nodeModifyTable.cpp中更新_hist表的hash值。

  • 通過set_user_tuple_hash得到賬本表hash列的值。
/*
* set_user_tuple_hash -- calculate and fill the hash attribute of user table's tuple.
*
* tup: row data of user table
* rel: user table
* hash_exists: whether tuple comes with tuplehash.
*
* Note: if hash_exists is true, we should recompute
* tuple hash and compare with tuplehash of itself.
*/
HeapTuple set_user_tuple_hash(HeapTuple tup, Relation rel, bool hash_exists)
{
   uint64 row_hash = gen_user_tuple_hash(rel, tup);
 int hash_attrno = user_hash_attrno(rel->rd_att);
 if (hash_exists) {
 bool is_null;
       Datum hash = heap_getattr(tup, hash_attrno + 1, rel->rd_att, &is_null);
 if (is_null || row_hash != DatumGetUInt64(hash)) {
           ereport(ERROR, (errcode(ERRCODE_OPERATE_INVALID_PARAM), errmsg("Invalid tuple hash.")));
       }
 return tup;
   }
   Datum *values = NULL;
 bool *nulls = NULL;
 bool *replaces = NULL;
 /* Build modified tuple */
   int2 nattrs = RelationGetNumberOfAttributes(rel);
   values = (Datum*)palloc0(nattrs * sizeof(Datum));
   nulls = (bool*)palloc0(nattrs * sizeof(bool));
   replaces = (bool*)palloc0(nattrs * sizeof(bool));
   values[hash_attrno] = UInt64GetDatum(row_hash);
   replaces[hash_attrno] = true;
   HeapTuple newtup = heap_modify_tuple(tup, RelationGetDescr(rel), values, nulls, replaces);
   pfree_ext(values);
   pfree_ext(nulls);
   pfree_ext(replaces);
 return newtup;
}

校驗賬本數據一致性

官方文檔

資料庫正常運行,並且對防篡改資料庫執行了一系列增、刪、改等操作,保證在查詢時段內有賬本操作記錄結果產生。

基本操作

1、校驗防篡改用戶表ledgernsp.usertable與其對應的歷史表是否一致。

omm=# SELECT pg_catalog.ledger_hist_check('ledgernsp', 'usertable');
ledger_hist_check
-------------------
t
(1 row)
  • 校驗用戶許可權 Only super user or audit admin have access right to blockchain nsp
 /* Only super user or audit admin have access right to blockchain nsp */
 if (nsp_oid == PG_BLOCKCHAIN_NAMESPACE) {
 return gs_blockchain_aclmask(roleid, mask);
   }
  • 校驗歷史表hash值

is_hist_hash_identity --> get_usertable_hash_sum

--> get_histtable_hash_sum

/*
* is_hist_hash_identity -- check whether user table hash and history table hash are equal
*
* relid: user table oid
* res_hash: hash sum of history table
*/
bool is_hist_hash_identity(Oid relid, uint64 *res_hash)
{
   uint64 user_hash_sum;
   uint64 hist_hash_sum;
 char hist_name[NAMEDATALEN];
 char *rel_name = get_rel_name(relid);
 if (!get_hist_name(relid, rel_name, hist_name)) {
       ereport(ERROR, (errcode(ERRCODE_UNDEFINED_OBJECT), errmsg("get hist table name failed.")));
   }
   Oid histoid = get_relname_relid(hist_name, PG_BLOCKCHAIN_NAMESPACE);
 if (!OidIsValid(histoid)) {
       ereport(ERROR, (errcode(ERRCODE_UNDEFINED_OBJECT), errmsg("could not find hist table of \"%s\".", rel_name)));
   }
   user_hash_sum = get_usertable_hash_sum(relid);
   hist_hash_sum = get_histtable_hash_sum(histoid);
   *res_hash = hist_hash_sum;
 return user_hash_sum == hist_hash_sum;
}

2、查詢防篡改用戶表ledgernsp.usertable與其對應的歷史表以及全局區塊表中關於該表的記錄是否一致。

omm=# SELECT pg_catalog.ledger_gchain_check('ledgernsp', 'usertable');
ledger_gchain_check
---------------------
t
(1 row)
  • 校驗是否為賬本表ledger_usertable_check
  • 校驗用戶許可權has_ledger_consistent_privilege
  • 校驗歷史表hash值is_hist_hash_identity
  • 計算/校驗全局表hash get_gchain_relhash_sum
/*
* get_gchain_relhash_sum -- calculate relhash from gs_global_chain
*
* relid: user table oid
*/
static uint64 get_gchain_relhash_sum(Oid relid)
{
   uint64 relhash = 0;
   HeapTuple tuple = NULL;
 /* scan the gs_global_chain catalog by relid */
   Relation gchain_rel = heap_open(GsGlobalChainRelationId, AccessShareLock);
   Form_gs_global_chain rdata = NULL;
   TableScanDesc scan = heap_beginscan(gchain_rel, SnapshotNow, 0, NULL);
 while ((tuple = heap_getnext(scan, ForwardScanDirection)) != NULL) {
       rdata = (Form_gs_global_chain)GETSTRUCT(tuple);
 if (rdata == NULL || rdata->relid != relid) {
 continue;
       }
       relhash += rdata->relhash;
   }
   heap_endscan(scan);
   heap_close(gchain_rel, AccessShareLock);
 return relhash;
}

歸檔賬本資料庫

官方文檔

前提條件:

  • 系統中需要有審計管理員或者具有審計管理員許可權的角色。
  • 資料庫正常運行,並且對防篡改資料庫執行了一系列增、刪、改等操作,保證在查詢時段內有賬本操作記錄結果產生。
  • 資料庫已經正確配置審計文件的存儲路徑audit_directory。

基本操作

1、對指定用戶歷史表進行歸檔操作。

omm=# SELECT pg_catalog.ledger_hist_archive('ledgernsp', 'usertable');
ledger_hist_archive
---------------------
t
(1 row)
omm=# SELECT * FROM blockchain.ledgernsp_usertable_hist;
rec_num |     hash_ins     |     hash_del     |             pre_hash
---------+------------------+------------------+----------------------------------
 4 | e78e75b00d396899 | 84e8bfc3b974e9cf | 6475a497b7a272a92bab012d7f3d615b
(1 row)

主要步驟如下:

  1. Copy user history table.
  2. Do unify and truncate.
  3. sum all hash_ins and hash_del for unification.
  4. Do real truncate.heap_truncate_one_rel
  5. Do insertion for unified row.simple_heap_insert
  6. Flush history hash table cache.

2、執行全局區塊表導出操作

omm=# SELECT * FROM gs_global_chain;
blocknum | dbname | username |           starttime           | relid |  relnsp   |  relname  |     relhash      |            globalhash            |
                 txcommand
----------+--------+----------+-------------------------------+-------+-----------+-----------+------------------+----------------------------------+----------------
--------------------------------------------------------------
 1 | omm    | omm      | 2022-09-17 13:59:37.84824+00  | 16404 | ledgernsp | usertable | a41714001181a294 | 83927d11ba1fd678e8f4b0723a9cd5f2 | INSERT INTO led
gernsp.usertable VALUES(1, 'alex'), (2, 'bob'), (3, 'peter');
 2 | omm    | omm      | 2022-09-17 13:59:51.723068+00 | 16404 | ledgernsp | usertable | b3a9ed0755131181 | b5ee73b6c20c817230182f6373c78e20 | UPDATE ledgerns
p.usertable SET name = 'bob2' WHERE id = 2;
 3 | omm    | omm      | 2022-09-17 13:59:58.159596+00 | 16404 | ledgernsp | usertable | 0ae4b4e4ed2fcab5 | 0cc9938cf7f1ed7f7f1a03c29954380a | DELETE FROM led
gernsp.usertable WHERE id = 3;
(3 rows)
omm=# SELECT pg_catalog.ledger_gchain_archive();
ledger_gchain_archive
-----------------------
t
(1 row)
omm=# SELECT * FROM gs_global_chain;
blocknum | dbname | username |          starttime           | relid |  relnsp   |  relname  |     relhash      |            globalhash            | txcommand
----------+--------+----------+------------------------------+-------+-----------+-----------+------------------+----------------------------------+-----------
 2 | omm    | omm      | 2022-09-17 13:59:37.84824+00 | 16404 | ledgernsp | usertable | 62a5b5ec53c47eca | 7252d09679b0b3836a2e63da17284ad5 | Archived.
(1 row)

gs_global_chain主要處理流程:

  1. Init and prepare bak dictionary.
  2. Using CopyStmt to copy global chain.
  3. Do unify and truncate.
  4. Using hash table to do unify, each hash_entry refers to one relid informations.
  5. Split gs_global_chain by relid, and accumulate rel_hash to a new record for each rel.
  6. Do rel truncate.
  7. Insert newest record to gchain order by relid.
  8. Flush global_hash cache.

修複賬本資料庫

官方文檔

前提條件:

  • 系統中需要有審計管理員或者具有審計管理員許可權的角色。
  • 資料庫正常運行,並且對防篡改資料庫執行了一系列增、刪、改等操作,保證在查詢時段內有賬本操作記錄結果產生。

基本操作

1、執行歷史表修複操作

omm=# select * from blockchain.ledgernsp_usertable_hist;
rec_num |     hash_ins     |     hash_del     |             pre_hash
---------+------------------+------------------+----------------------------------
      4 | e78e75b00d396899 | 84e8bfc3b974e9cf | 6475a497b7a272a92bab012d7f3d615b
(1 row)
omm=# SELECT pg_catalog.ledger_hist_repair('ledgernsp', 'usertable');
ledger_hist_repair
--------------------
0000000000000000
(1 row)

[drawio] (rHmeQ8HWKS_RFXgP-oTUZINZguxBYqh2IV64Y0j5TAA.svg)

2、執行全局區塊表修複操作

omm=# select * from gs_global_chain ;
blocknum | dbname | username |          starttime           | relid |  relnsp   |  relname  |     relhash      |            globalhash            | txcommand
----------+--------+----------+------------------------------+-------+-----------+-----------+------------------+----------------------------------+-----------
       2 | omm    | omm      | 2022-09-17 13:59:37.84824+00 | 16404 | ledgernsp | usertable | 62a5b5ec53c47eca | 7252d09679b0b3836a2e63da17284ad5 | Archived.
(1 row)
omm=# SELECT pg_catalog.ledger_gchain_repair('ledgernsp', 'usertable');
ledger_gchain_repair
----------------------
62a5b5ec53c47eca
(1 row)

首先判斷用戶許可權,之後通過get_gchain_relhash_sum函數計算relhash欄位

/*
* get_gchain_relhash_sum -- calculate relhash from gs_global_chain
*
* relid: user table oid
*/
static uint64 get_gchain_relhash_sum(Oid relid)
{
   uint64 relhash = 0;
   HeapTuple tuple = NULL;
 /* scan the gs_global_chain catalog by relid */
   Relation gchain_rel = heap_open(GsGlobalChainRelationId, AccessShareLock);
   Form_gs_global_chain rdata = NULL;
   TableScanDesc scan = heap_beginscan(gchain_rel, SnapshotNow, 0, NULL);
 while ((tuple = heap_getnext(scan, ForwardScanDirection)) != NULL) {
       rdata = (Form_gs_global_chain)GETSTRUCT(tuple);
 if (rdata == NULL || rdata->relid != relid) {
 continue;
       }
       relhash += rdata->relhash;
   }
   heap_endscan(scan);
   heap_close(gchain_rel, AccessShareLock);
 return relhash;
}

主要是計算並修複gs_global_chain中的relhash欄位。

總結

賬本資料庫其實並不像我們想象的那麼複製,實際上就是利用了區塊鏈的最基本的原理,即當前記錄的特征值 + 上一條記錄特征值的hash值,再進行hash。下一條與上一條記錄具有數據關聯性,形成“鏈”的結構,如果篡改了其中的數據,則會導致“鏈”斷開,導致不能與後面數據記錄形成hash關聯。_hist表記錄了用戶表每一步數據變化的過程,gs_global_chain表記錄了所有防篡改模式下對用戶表的操作記錄。用戶表結合_hist和global表就能完整記錄和校驗。

 

點擊關註,第一時間瞭解華為雲新鮮技術~


您的分享是我們最大的動力!

-Advertisement-
Play Games
更多相關文章
  • 一:背景 1.講故事 今天給大家帶來一個入門級的 CPU 爆高案例,前段時間有位朋友找到我,說他的程式間歇性的 CPU 爆高,不知道是啥情況,讓我幫忙看下,既然找到我,那就用 WinDbg 看一下。 二:WinDbg 分析 1. CPU 真的爆高嗎 其實我一直都在強調,要相信數據,口說無憑,一定要親 ...
  • 一、前言 本文章彙總c#中常見的鎖,基本都列出了該鎖在微軟官網的文章,一些不常用的鎖也可以參考微軟文章左側的列表,方便溫習回顧。 二、鎖的分類 2.1、用戶模式鎖 1、volatile 關鍵字 volatile 並沒有實現真正的線程同步,操作級別停留在變數級別並非原子級別,對於單系統處理器中,變數存 ...
  • 前言 拋開死鎖不談,只聊性能問題,儘管鎖總能粗暴的滿足同步需求,但一旦存在競爭關係,意味著一定會有線程被阻塞,競爭越激烈,被阻塞的線程越多,上下文切換次數越多,調度成本越大,顯然在高併發的場景下會損害性能。在高併發高性能且要求線程安全的述求下,無鎖構造(非阻塞構造)閃亮登場。 參考文檔: C# - ...
  • 在 Windows 環境下如果採用 IIS 作為 網站伺服器時,常規的網站綁定 HTTPS 需要一個一個站點手動選擇對應的證書綁定,而且證書過期之後更換證書時也是需要一個個重新綁定操作,無法便捷的做到像 Nginx 或者 Apache 等程式一樣,直接在配置文件中指定所需要使用的證書文件的路徑,像 ...
  • //源文件 void RccPhyConfig() { uint16_t retry = 0; RCC->APB1ENR|=1<<28; //電源介面時鐘使能 PWR->CR1|=3<<14; //高性能模式,時鐘可到 180Mhz PWR->CR1|=1<<16; //使能過驅動,頻率可到 216 ...
  • Babelfish是在PostgreSQL的基礎之上實現了類似Microsoft SQL Server部分功能。為了實現這個目標,PostgreSQL核心內部的幾個部分必須被修改。PostgreSQL 沒有提供這種開箱即用的功能,因此必須提供附加組件才能實現這一點。 Babelfish... ...
  • 摘要:數據加密作為有效防止未授權訪問和防護數據泄露的技術,在各種信息系統中廣泛使用。作為信息系統的核心,GaussDB(DWS)數倉也提供數據加密功能,包括透明加密和使用SQL函數加密。 本文分享自華為雲社區《看GaussDB(DWS)如何使用SQL加密函數實現數據列加解密》,作者:Hello EI ...
  • 近年來,憑藉高可擴展、高可用等技術特性,分散式資料庫正在成為金融行業數字化轉型的重要支撐。分散式資料庫如何在不同的金融級應用場景下,在確保數據一致性的前提下,同時保障系統的高性能和高可擴展性,是分散式資料庫的一個核心技術挑戰。 針對以上分散式一致性的困境,中國人民大學-騰訊協同創新實驗室研究提出“多 ...
一周排行
    -Advertisement-
    Play Games
  • 移動開發(一):使用.NET MAUI開發第一個安卓APP 對於工作多年的C#程式員來說,近來想嘗試開發一款安卓APP,考慮了很久最終選擇使用.NET MAUI這個微軟官方的框架來嘗試體驗開發安卓APP,畢竟是使用Visual Studio開發工具,使用起來也比較的順手,結合微軟官方的教程進行了安卓 ...
  • 前言 QuestPDF 是一個開源 .NET 庫,用於生成 PDF 文檔。使用了C# Fluent API方式可簡化開發、減少錯誤並提高工作效率。利用它可以輕鬆生成 PDF 報告、發票、導出文件等。 項目介紹 QuestPDF 是一個革命性的開源 .NET 庫,它徹底改變了我們生成 PDF 文檔的方 ...
  • 項目地址 項目後端地址: https://github.com/ZyPLJ/ZYTteeHole 項目前端頁面地址: ZyPLJ/TreeHoleVue (github.com) https://github.com/ZyPLJ/TreeHoleVue 目前項目測試訪問地址: http://tree ...
  • 話不多說,直接開乾 一.下載 1.官方鏈接下載: https://www.microsoft.com/zh-cn/sql-server/sql-server-downloads 2.在下載目錄中找到下麵這個小的安裝包 SQL2022-SSEI-Dev.exe,運行開始下載SQL server; 二. ...
  • 前言 隨著物聯網(IoT)技術的迅猛發展,MQTT(消息隊列遙測傳輸)協議憑藉其輕量級和高效性,已成為眾多物聯網應用的首選通信標準。 MQTTnet 作為一個高性能的 .NET 開源庫,為 .NET 平臺上的 MQTT 客戶端與伺服器開發提供了強大的支持。 本文將全面介紹 MQTTnet 的核心功能 ...
  • Serilog支持多種接收器用於日誌存儲,增強器用於添加屬性,LogContext管理動態屬性,支持多種輸出格式包括純文本、JSON及ExpressionTemplate。還提供了自定義格式化選項,適用於不同需求。 ...
  • 目錄簡介獲取 HTML 文檔解析 HTML 文檔測試參考文章 簡介 動態內容網站使用 JavaScript 腳本動態檢索和渲染數據,爬取信息時需要模擬瀏覽器行為,否則獲取到的源碼基本是空的。 本文使用的爬取步驟如下: 使用 Selenium 獲取渲染後的 HTML 文檔 使用 HtmlAgility ...
  • 1.前言 什麼是熱更新 游戲或者軟體更新時,無需重新下載客戶端進行安裝,而是在應用程式啟動的情況下,在內部進行資源或者代碼更新 Unity目前常用熱更新解決方案 HybridCLR,Xlua,ILRuntime等 Unity目前常用資源管理解決方案 AssetBundles,Addressable, ...
  • 本文章主要是在C# ASP.NET Core Web API框架實現向手機發送驗證碼簡訊功能。這裡我選擇是一個互億無線簡訊驗證碼平臺,其實像阿裡雲,騰訊雲上面也可以。 首先我們先去 互億無線 https://www.ihuyi.com/api/sms.html 去註冊一個賬號 註冊完成賬號後,它會送 ...
  • 通過以下方式可以高效,並保證數據同步的可靠性 1.API設計 使用RESTful設計,確保API端點明確,並使用適當的HTTP方法(如POST用於創建,PUT用於更新)。 設計清晰的請求和響應模型,以確保客戶端能夠理解預期格式。 2.數據驗證 在伺服器端進行嚴格的數據驗證,確保接收到的數據符合預期格 ...