GreatSQL刪除分區慢的跟蹤

来源:https://www.cnblogs.com/greatsql/archive/2023/06/07/17462356.html
-Advertisement-
Play Games

# GreatSQL刪除分區慢的跟蹤 ## 背景 某業務系統,每天凌晨會刪除分區表的一個分區(按天分區),耗時較久,從最開始的30秒,慢慢變為1分鐘+,影響到交易業務的正常進行。 在測試環境進行了模擬,復現了刪除分區慢的情況,本次基於GreatSQL8.0.25-17進行測試,官方mysql版本也存 ...


GreatSQL刪除分區慢的跟蹤

背景

某業務系統,每天凌晨會刪除分區表的一個分區(按天分區),耗時較久,從最開始的30秒,慢慢變為1分鐘+,影響到交易業務的正常進行。 在測試環境進行了模擬,復現了刪除分區慢的情況,本次基於GreatSQL8.0.25-17進行測試,官方mysql版本也存在相同問題。

測試環境

$ mysql -h127.0.0.1 -P8025 -uroot -p

mysql: [Warning] Using a password on the command line interface can be insecure.
Welcome to the MySQL monitor.  Commands end with ; or \g.
Your MySQL connection id is 10
Server version: 8.0.25-17 GreatSQL, Release 17, Revision 4733775f703

Copyright (c) 2000, 2021, Oracle and/or its affiliates.

Oracle is a registered trademark of Oracle Corporation and/or its
affiliates. Other names may be trademarks of their respective
owners.

Type 'help;' or '\h' for help. Type '\c' to clear the current input statement.

greatsql> select version();
+-----------+
| version() |
+-----------+
| 8.0.25-17 |
+-----------+
1 row in set (0.00 sec)
greatsql> show variables like 'autocommit' ;
+---------------+-------+
| Variable_name | Value |
+---------------+-------+
| autocommit    | ON    |
+---------------+-------+
1 row in set (0.01 sec)

greatsql> show variables like 'innodb_buffer_pool_size';
+-------------------------+------------+
| Variable_name           | Value      |
+-------------------------+------------+
| innodb_buffer_pool_size | 4294967296 |
+-------------------------+------------+
1 row in set (0.01 sec)

greatsql> show variables like 'innodb_flush_log_at_trx_commit';
+--------------------------------+-------+
| Variable_name                  | Value |
+--------------------------------+-------+
| innodb_flush_log_at_trx_commit | 1     |
+--------------------------------+-------+
1 row in set (0.00 sec)

greatsql> show variables like 'sync_binlog';
+---------------+-------+
| Variable_name | Value |
+---------------+-------+
| sync_binlog   | 1     |
+---------------+-------+
1 row in set (0.00 sec)

建表

CREATE TABLE `t_partition` (
  `id` bigint NOT NULL AUTO_INCREMENT,
  `ua` varchar(100) DEFAULT NULL,
  `start_time` datetime NOT NULL,
  PRIMARY KEY (`id`,`start_time`)
)  PARTITION BY RANGE (to_days(`start_time`))
(PARTITION P20230129 VALUES LESS THAN (738915) ENGINE = InnoDB,
 PARTITION P20230130 VALUES LESS THAN (738916) ENGINE = InnoDB,
 PARTITION P20230131 VALUES LESS THAN (738917) ENGINE = InnoDB,
 PARTITION P20230201 VALUES LESS THAN (738918) ENGINE = InnoDB,
 PARTITION P20230202 VALUES LESS THAN (738919) ENGINE = InnoDB,
 PARTITION P20230203 VALUES LESS THAN (738920) ENGINE = InnoDB,
 PARTITION P20230204 VALUES LESS THAN (738921) ENGINE = InnoDB,
 PARTITION P20230302 VALUES LESS THAN (738947) ENGINE = InnoDB,
 PARTITION P20230303 VALUES LESS THAN (738948) ENGINE = InnoDB,
 PARTITION P20230304 VALUES LESS THAN (738949) ENGINE = InnoDB,
 PARTITION P20230305 VALUES LESS THAN (738950) ENGINE = InnoDB,
 PARTITION P20230306 VALUES LESS THAN (738951) ENGINE = InnoDB,
 PARTITION P20230307 VALUES LESS THAN (738952) ENGINE = InnoDB, 
 PARTITION P20230308 VALUES LESS THAN (738953) ENGINE = InnoDB,
 PARTITION P20230309 VALUES LESS THAN (738954) ENGINE = InnoDB,
 PARTITION P20230310 VALUES LESS THAN (738955) ENGINE = InnoDB, 
 PARTITION P20230311 VALUES LESS THAN (738956) ENGINE = InnoDB, 

 PARTITION P20230312 VALUES LESS THAN (738957) ENGINE = InnoDB,
 PARTITION P20230313 VALUES LESS THAN (738958) ENGINE = InnoDB,
 PARTITION P20230314 VALUES LESS THAN (738959) ENGINE = InnoDB,
 PARTITION P20230315 VALUES LESS THAN (738960) ENGINE = InnoDB,
 PARTITION P20230316 VALUES LESS THAN (738961) ENGINE = InnoDB,
 PARTITION P20230317 VALUES LESS THAN (738962) ENGINE = InnoDB, 
 PARTITION P20230318 VALUES LESS THAN (738963) ENGINE = InnoDB,
 PARTITION P20230319 VALUES LESS THAN (738964) ENGINE = InnoDB,
 PARTITION P20230320 VALUES LESS THAN (738965) ENGINE = InnoDB, 
 PARTITION P20230321 VALUES LESS THAN (738966) ENGINE = InnoDB, 
 
 PARTITION P20230322 VALUES LESS THAN (738967) ENGINE = InnoDB,
 PARTITION P20230323 VALUES LESS THAN (738968) ENGINE = InnoDB,
 PARTITION P20230324 VALUES LESS THAN (738969) ENGINE = InnoDB,
 PARTITION P20230325 VALUES LESS THAN (738970) ENGINE = InnoDB,
 PARTITION P20230326 VALUES LESS THAN (738971) ENGINE = InnoDB,
 PARTITION P20230327 VALUES LESS THAN (738972) ENGINE = InnoDB, 
 PARTITION P20230328 VALUES LESS THAN (738973) ENGINE = InnoDB,
 PARTITION p20230329 VALUES LESS THAN (738974) ENGINE = InnoDB,
 PARTITION p20230330 VALUES LESS THAN (738975) ENGINE = InnoDB,
 PARTITION p20230331 VALUES LESS THAN (738976) ENGINE = InnoDB,
 PARTITION p20230401 VALUES LESS THAN (738977) ENGINE = InnoDB,
 PARTITION p20230402 VALUES LESS THAN (738978) ENGINE = InnoDB,
 PARTITION p20230403 VALUES LESS THAN (738979) ENGINE = InnoDB);

插入數據

greatsql> insert into t_partition(ua,start_time) select substring(md5(rand()),1,20),'2023-03-10' ;
Query OK, 1 row affected (0.01 sec)
Records: 1  Duplicates: 0  Warnings: 0

greatsql> insert into t_partition(ua,start_time) select substring(md5(rand()),1,20),'2023-03-10' ;
Query OK, 1 row affected (0.00 sec)
Records: 1  Duplicates: 0  Warnings: 0

greatsql> insert into t_partition(ua,start_time) select substring(md5(rand()),1,20),'2023-03-10' ;
Query OK, 1 row affected (0.01 sec)
Records: 1  Duplicates: 0  Warnings: 0



greatsql> insert into t_partition(ua,start_time) select substring(md5(rand()),1,20),start_time from t_partition; 
Query OK, 3 rows affected (0.01 sec)
Records: 3  Duplicates: 0  Warnings: 0

greatsql> insert into t_partition(ua,start_time) select substring(md5(rand()),1,20),start_time from t_partition; 
Query OK, 6 rows affected (0.00 sec)
Records: 6  Duplicates: 0  Warnings: 0

............... 
greatsql> insert into t_partition(ua,start_time) select substring(md5(rand()),1,20),start_time from t_partition; 
Query OK, 3145728 rows affected (35.68 sec)
Records: 3145728  Duplicates: 0  Warnings: 0

greatsql> insert into t_partition(ua,start_time) select substring(md5(rand()),1,20),start_time from t_partition; 
Query OK, 6291456 rows affected (1 min 11.51 sec)
Records: 6291456  Duplicates: 0  Warnings: 0

greatsql> insert into t_partition(ua,start_time) select substring(md5(rand()),1,20),start_time from t_partition; 
Query OK, 12582912 rows affected (2 min 26.74 sec)
Records: 12582912  Duplicates: 0  Warnings: 0

greatsql> select count(*) from t_partition; 
+----------+
| count(*) |
+----------+
| 25165824 |
+----------+
1 row in set (0.50 sec)
greatsql> select count(*) from t_partition partition(P20230310); 
+----------+
| count(*) |
+----------+
| 25165824 |
+----------+ 

向分區插入數據

insert into t_partition(ua,start_time) select substring(md5(rand()),1,20),'2023-03-11' from t_partition partition(P20230310); 
 insert into t_partition(ua,start_time) select substring(md5(rand()),1,20),'2023-03-12' from t_partition partition(P20230310); 
 insert into t_partition(ua,start_time) select substring(md5(rand()),1,20),'2023-03-13' from t_partition partition(P20230310); 
 insert into t_partition(ua,start_time) select substring(md5(rand()),1,20),'2023-03-14' from t_partition partition(P20230310); 
 insert into t_partition(ua,start_time) select substring(md5(rand()),1,20),'2023-03-15' from t_partition partition(P20230310); 
 insert into t_partition(ua,start_time) select substring(md5(rand()),1,20),'2023-03-16' from t_partition partition(P20230310); 
 insert into t_partition(ua,start_time) select substring(md5(rand()),1,20),'2023-03-17' from t_partition partition(P20230310); 
 insert into t_partition(ua,start_time) select substring(md5(rand()),1,20),'2023-03-18' from t_partition partition(P20230310); 
 insert into t_partition(ua,start_time) select substring(md5(rand()),1,20),'2023-03-19' from t_partition partition(P20230310); 
 insert into t_partition(ua,start_time) select substring(md5(rand()),1,20),'2023-03-20' from t_partition partition(P20230310); 
 insert into t_partition(ua,start_time) select substring(md5(rand()),1,20),'2023-03-21' from t_partition partition(P20230310); 
 
 。。。。。。。。。。。
greatsql>  insert into t_partition(ua,start_time) select substring(md5(rand()),1,20),'2023-03-19' from t_partition partition(P20230310); 
Query OK, 25165824 rows affected (5 min 17.92 sec)
Records: 25165824  Duplicates: 0  Warnings: 0

greatsql>  insert into t_partition(ua,start_time) select substring(md5(rand()),1,20),'2023-03-20' from t_partition partition(P20230310); 
Query OK, 25165824 rows affected (5 min 19.56 sec)
Records: 25165824  Duplicates: 0  Warnings: 0

greatsql>  insert into t_partition(ua,start_time) select substring(md5(rand()),1,20),'2023-03-21' from t_partition partition(P20230310); 
Query OK, 25165824 rows affected (5 min 27.27 sec)
Records: 25165824  Duplicates: 0  Warnings: 0
 

更新數據

greatsql> Update t_partition set ua=concat(ua,'abc') where start_time='2023-03-19';
Query OK, 25165824 rows affected (12 min 55.53 sec)
Rows matched: 25165824  Changed: 25165824  Warnings: 0

刪除分區s

greatsql> alter table t_partition drop partition P20230311;

Query OK, 0 rows affected (13.68 sec)

Records: 0 Duplicates: 0 Warnings: 0

greatsql> alter table t_partition drop partition P20230312;

Query OK, 0 rows affected (0.07 sec)

Records: 0 Duplicates: 0 Warnings: 0

兩個分區數據量是一樣,但刪除第一個分區耗時較長。

$ perf record -ag -p  11222  -o /mysqldb/perf_drop_part_mysql2.data

Warning:
PID/TID switch overriding SYSTEM
^C[ perf record: Woken up 41 times to write data ]
[ perf record: Captured and wrote 10.610 MB /mysqldb/perf_drop_part_mysql2.data (54351 samples) ]
  Children      Self  Command  Shared Object        Symbol                                                   
+   99.17%     0.00%  mysqld   libpthread-2.17.so   [.] start_thread                                         
+   99.17%     0.00%  mysqld   mysqld               [.] pfs_spawn_thread                                     
+   99.17%     0.00%  mysqld   mysqld               [.] handle_connection                                    
+   99.17%     0.00%  mysqld   mysqld               [.] do_command                                           
+   99.17%     0.00%  mysqld   mysqld               [.] dispatch_command                                     
+   99.17%     0.00%  mysqld   mysqld               [.] dispatch_sql_command                                 
+   99.16%     0.00%  mysqld   mysqld               [.] mysql_execute_command                                
+   99.16%     0.00%  mysqld   mysqld               [.] Sql_cmd_alter_table::execute                         
+   99.16%     0.00%  mysqld   mysqld               [.] mysql_alter_table                                    
+   99.09%     0.00%  mysqld   mysqld               [.] mysql_inplace_alter_table                            
+   98.56%     0.00%  mysqld   mysqld               [.] ha_innopart::commit_inplace_alter_partition          
+   98.54%     0.00%  mysqld   mysqld               [.] alter_parts::prepare_or_commit_for_new               
+   98.54%     0.00%  mysqld   mysqld               [.] alter_part_normal::try_commit                        
+   98.53%     0.00%  mysqld   mysqld               [.] btr_drop_ahi_for_table                               
+   98.52%     1.30%  mysqld   mysqld               [.] btr_drop_next_batch                                  
+   97.20%     0.03%  mysqld   mysqld               [.] btr_search_drop_page_hash_when_freed                 
+   96.34%     2.03%  mysqld   mysqld               [.] btr_search_drop_page_hash_index                      
+   86.52%    52.25%  mysqld   mysqld               [.] ha_remove_all_nodes_to_page                          
+   34.21%    34.15%  mysqld   mysqld               [.] ha_delete_hash_node                                  
+    4.27%     2.68%  mysqld   mysqld               [.] rec_get_offsets_func                                 
+    2.11%     2.10%  mysqld   mysqld               [.] ut_fold_binary                                       
+    1.58%     1.58%  mysqld   mysqld               [.] rec_init_offsets                                     
+    1.30%     1.30%  mysqld   mysqld               [.] rec_fold                                             
+    0.57%     0.03%  mysqld   mysqld               [.] buf_page_get_gen                                     
+    0.57%     0.00%  mysqld   mysqld               [.] execute_native_thread_routine                        
+    0.56%     0.01%  mysqld   [kernel.kallsyms]    [k] system_call_fastpath

從系統調用上看有大量的自適應hash相關的調用

重啟關閉自適應hash

greatsql> show variables like '%hash%';
+----------------------------------+-------+
| Variable_name                    | Value |
+----------------------------------+-------+
| innodb_adaptive_hash_index       | OFF   |
| innodb_adaptive_hash_index_parts | 8     |
+----------------------------------+-------+

修改配置文件,關閉自適應hash,按照上面的流程從新執行

greatsql> alter table t_partition drop partition P20230311;

Query OK, 0 rows affected (0.08 sec)

Records: 0 Duplicates: 0 Warnings: 0

greatsql> alter table t_partition drop partition P20230312;

Query OK, 0 rows affected (0.08 sec)

Records: 0 Duplicates: 0 Warnings: 0

關閉自適應hash後,相同的操作過程,刪除第一個分區的時間明顯變短,刪除每個分區的時間基本上一致。

備註:innodb_adaptive_hash_index是全局變數,可以動態修改,不重啟資料庫。

測試結果彙總

自適應hash對比 第一次刪分區 第二次刪分區
innodb_buffer_pool_instances=8& innodb_adaptive_hash_index=on 13.68 0.07
innodb_buffer_pool_instances=8& innodb_adaptive_hash_index=off 0.08 0.08

源碼分析

// btr_drop_ahi_for_table
void btr_drop_ahi_for_table(dict_table_t *table) {
  const ulint len = UT_LIST_GET_LEN(table->indexes);

  if (len == 0) {
    return;
  }

  const dict_index_t *indexes[MAX_INDEXES];
  const page_size_t page_size(dict_table_page_size(table));

  for (;;) {
    ulint ref_count = 0;
    const dict_index_t **end = indexes;

    for (dict_index_t *index = table->first_index(); index != nullptr;
         index = index->next()) {
      if (ulint n_refs = index->search_info->ref_count) {
        ut_ad(!index->disable_ahi);
        ut_ad(index->is_committed());
        ref_count += n_refs;
        ut_ad(indexes + len > end);
        *end++ = index;
      }
    }

    ut_ad((indexes == end) == (ref_count == 0));

    if (ref_count == 0) {
      return;
    }

    btr_drop_next_batch(page_size, indexes, end);  // breakpoint  

    std::this_thread::yield();
  }
}



// btr_drop_next_batch
static void btr_drop_next_batch(const page_size_t &page_size,
                                const dict_index_t **first,
                                const dict_index_t **last) {
  static constexpr unsigned batch_size = 1024;
  std::vector<page_id_t> to_drop;
  to_drop.reserve(batch_size);

  for (ulint i = 0; i < srv_buf_pool_instances; ++i) {
    to_drop.clear();
    buf_pool_t *buf_pool = buf_pool_from_array(i);
    mutex_enter(&buf_pool->LRU_list_mutex);
    const buf_page_t *prev;

    for (const buf_page_t *bpage = UT_LIST_GET_LAST(buf_pool->LRU);
         bpage != nullptr; bpage = prev) {
      prev = UT_LIST_GET_PREV(LRU, bpage);

      ut_a(buf_page_in_file(bpage));
      if (buf_page_get_state(bpage) != BUF_BLOCK_FILE_PAGE ||
          bpage->buf_fix_count > 0) {
        continue;
      }

      const dict_index_t *block_index =
          reinterpret_cast<const buf_block_t *>(bpage)->ahi.index;

      /* index == nullptr means the page is no longer in AHI, so no need to
      attempt freeing it */
      if (block_index == nullptr) {
        continue;
      }
      /* pages IO fixed for read have index == nullptr */
      ut_ad(!bpage->was_io_fix_read());

      if (std::find(first, last, block_index) != last) {
        to_drop.emplace_back(bpage->id);
        if (to_drop.size() == batch_size) {   // batch_size = 1024
          break;
        }
      }
    }

    mutex_exit(&buf_pool->LRU_list_mutex);

    for (const page_id_t &page_id : to_drop) {
      btr_search_drop_page_hash_when_freed(page_id, page_size); // breakpoint
    }
  }
}

// btr_search_drop_page_hash_when_freed
void btr_search_drop_page_hash_when_freed(const page_id_t &page_id,
                                          const page_size_t &page_size) {
  buf_block_t *block;
  mtr_t mtr;

  ut_d(export_vars.innodb_ahi_drop_lookups++);

  mtr_start(&mtr);

  /* If the caller has a latch on the page, then the caller must
  have a x-latch on the page and it must have already dropped
  the hash index for the page. Because of the x-latch that we
  are possibly holding, we cannot s-latch the page, but must
  (recursively) x-latch it, even though we are only reading. */

  block = buf_page_get_gen(page_id, page_size, RW_X_LATCH, nullptr,
                           Page_fetch::PEEK_IF_IN_POOL, UT_LOCATION_HERE, &mtr);

  if (block) {
    /* If AHI is still valid, page can't be in free state.
    AHI is dropped when page is freed. */
    ut_ad(!block->page.file_page_was_freed);

    buf_block_dbg_add_level(block, SYNC_TREE_NODE_FROM_HASH);

    dict_index_t *index = block->ahi.index;
    if (index != nullptr) {
      /* In all our callers, the table handle should
      be open, or we should be in the process of
      dropping the table (preventing eviction). */
      ut_ad(index->table->n_ref_count > 0 || dict_sys_mutex_own());
      btr_search_drop_page_hash_index(block);
    }
  }

  mtr_commit(&mtr);
}

函數邏輯說明:

drop 分區和add分區都會清空所有分區的AHI信息,最耗時的如下
迴圈每個分區調用函數
->btr_drop_ahi_for_table
  迴圈表(或者分區)中的每個索引,如果索引都沒有用到AHI,
則退出
  迴圈innodb buffer中的每個實例,根據LRU鏈表迴圈每個page
如果page建立了AHI信息,且是要刪除表(或者分區)的相關索引
  則放入drop vector容器中
如果page沒有建立AHI信息 
則跳過
如果drop verctor容器中填滿1024個page
則清理一次,迴圈每個page,調用函數
->btr_search_drop_page_hash_index
  計算page所在AHI結構的slot信息,以便找到對應的hash_table_t結構
  迴圈page中所有的行
    迴圈行中訪問到的索引欄位(訪問模式),計算出fold信息填入到fold[]數組中
    本迴圈中會通過函數rec_get_offsets進行欄位偏移量的獲取,為耗用CPU的函數
      迴圈fold[]數組,一個fold代表一行數據,調用函數
       ->ha_remove_all_nodes_to_page,為耗用CPU的函數
         ->ha_chain_get_fist
           根據fold信息找到hash結構的cell
         迴圈本cell中的鏈表信息
           如果行的地址在本要刪除的page上,調用函數
           ->ha_delete_hash_node,為消耗CPU的函數
             進行鏈表和hash結構的維護
每次處理完1024個page後,yeild線程主動放棄CPU,避免長期占用CPU,醒來後繼續處理

drop 分區和add分區都會迴圈每個分區調用函數btr_drop_ahi_for_table 、btr_search_drop_page_hash_index清空所有分區及索引的的AHI信息, 刪除第1個分區的時ahi信息被清空, 刪第2個分區的時候buffer中已經沒有ahi信息了,所有刪除第2個分區就很快了。

避免方式

針對以上原因,線上執行 drop必須遵守以下原則:

1、關閉AHI功能,不使用AHI帶來的查詢加速功能,需要先在測試環境進行業務測試,確保業務性能不受影響。

2、刪除表的第一個分區時,內部會清理該表在每個buffer pool實例中對應的數據塊頁面,耗時較久,接著刪其他分區耗時很小,建議將每天一次的刪除分區的操作改為每周或者每月批量執行刪除分區的操作,並且需要在業務低峰期操作。


Enjoy GreatSQL

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

-Advertisement-
Play Games
更多相關文章
  • # 基本概念 ## 簡介 Kafka 最初是由 LinkedIn 即領英公司基於 Scala 和 Java 語言開發的分散式消息發佈-訂閱系統,現已捐獻給Apache 軟體基金會。其具有高吞吐、低延遲的特性,許多大數據實時流式處理系統比如 Storm、Spark、Flink等都能很好地與之集成。 總 ...
  • > 不要哀求,學會爭取。若是如此,終有所獲。 > > 原文:https://mp.weixin.qq.com/s/zbOqyAtsWsocarsFIGdGgw ## 前言 你是否還在煩惱 SQL 該從何學起,或者學了 SQL 想找個地方練練手?好巧不巧,最近在工作之餘登上牛客,發現了牛客不知道啥時候 ...
  • 摘要:GaussDB(DWS)查詢過濾器(黑名單)提供查詢過濾功能,支持自動隔離反覆被終止的查詢,防止爛SQL再次執行。 本文分享自華為雲社區《GaussDB(DWS)查詢過濾器原理與應用》,作者:門前一棵葡萄樹 。 一、概述 GaussDB(DWS)查詢過濾器(黑名單)提供查詢過濾功能,支持自動隔 ...
  • 啟動安裝程式 下載sqlserver2014,雙擊startup.exe進行安裝 系統配置檢查器 使用系統配置檢查器,看系統是否符合安裝sqlserver2014的所有要求 開始安裝 然後點擊安裝,全新sqlserver獨立安裝或向現有安裝添加功能 安裝規則 然後就是使用預設的設置,點開詳細信息,可 ...
  • 前段時間,[袋鼠雲離線開發產品](https://www.dtstack.com/dtinsight?src=szsm)接到改造數據同步表單的需求。 一方面,[數據同步模塊](https://www.dtstack.com/dtinsight?src=szsm)的代碼可讀性和可維護性較差,導致在數據 ...
  • 閑來無事,看到“短文學網”文章內容還算整潔,而且非常容易進行採集,於是也就手癢了弄了一下,速度非常快可能與網路沒有大量廣告啊、JS啊有關。 詳細的分類信息如下: qq日誌包含有:qq空間(2098)條、非主流日(180)條、搞笑日誌(132)條、個性日誌(204)條、經典日誌(260)條、空間文字( ...
  • 常識類的知識比較被大家接受,博客也好,微博也好,似乎常識類的都不會讓你成為冷門,主要看是的經營發展、營銷推廣的方式好壞。而一些流行的常識類一般只是少量的一兩句話,今天採集了“生活小知識”(http://www.xiaozhishi.com)網站的1萬四千多條內容: 內容沒有去除“ [來源:生活小知識 ...
  • 《1萬多中草藥中藥材大全ACCESS資料庫》採集的是全國中草藥的信息,內容包含別名,來源,性味,主治,使用方法和用量,外形,化學成分等眾多實用信息。 至於為什麼數據內容中包含HTML代碼,因為這些HTML代碼是調整顯示格式作用的,沒有了這些HTML代碼,內容排版方面就不層次分明瞭,效果其實可以看這裡 ...
一周排行
    -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.數據驗證 在伺服器端進行嚴格的數據驗證,確保接收到的數據符合預期格 ...