自拍偷在线精品自拍偷,亚洲欧美中文日韩v在线观看不卡

TiKV 源碼分析之 PointGet

數(shù)據(jù)庫
本文介紹了TiDB中最基本的PointGet算子在存儲層TiKV中的執(zhí)行流程。

一、背景介紹

TiDB是一款具有HTAP能力(同時支持在線事務處理與在線分析處理 )的融合型分布式數(shù)據(jù)庫產品,具備水平擴容或者縮容等重要特性。TiDB 采用多副本+Multi-Raft 算法的方式將數(shù)據(jù)調度到不同的機器節(jié)點上,具備較高的可靠性和容災能力。TiDB中的存儲層TiKV組件,能夠獨立于TiDB作為一款分布式KV數(shù)據(jù)庫使用,目前已經捐贈給CNCF并于2020年正式畢業(yè)。目前vivo公司內部的磁盤KV產品采用了開源的TiKV作為存儲層實現(xiàn), 目前已經在公司的不同業(yè)務產品中有深度實踐。

TiKV作為一款KV數(shù)據(jù)庫產品,同時提供了RawAPI和TxnAPI兩套接口:

  • RawAPI僅支持最基本的針對單Key操作的Set/Get/Del及Scan語義
  • TxnAPI提供了基于ACID事務標準的接口,支持多Key寫入的原子性

TxnAPI采用了分布式事務來保證多Key寫入的原子性,其適用的業(yè)務場景與RawAPI相比來說更為廣泛。本文后續(xù)內容將重點對PointGet在TiKV側的執(zhí)行流程進行分析,其內容涉及到storage和txn模塊。閱讀本文后,讀者將會深入了解TiKV源碼中Get流程的實現(xiàn)細節(jié),包括如何處理讀請求、如何進行數(shù)據(jù)定位和讀取、如何實現(xiàn)事務隔離級別等方面,并且能夠更好地理解TiKV的內部工作原理和性能優(yōu)化。

二、PointGet介紹

 2.1 TiDB視角中的PointGet

PointGet顧名思義即"點查", 它是TiDB中最為基本的幾種算子之一,以下列舉了兩個常見的PointGet算子的使用場景:

根據(jù)主鍵Id查詢

MySQL [test]> explain select * from user where id = 1024;
+-------------+---------+------+-------------------------------+---------------+
| id          | estRows | task | access object                 | operator info |
+-------------+---------+------+-------------------------------+---------------+
| Point_Get_1 | 1.00    | root | table:user, index:PRIMARY(id) |               |
+-------------+---------+------+-------------------------------+---------------+

根據(jù)唯一索引查詢

MySQL [test]> explain select * from users where name = "test";
+-------------+---------+------+-------------------------------+---------------+
| id          | estRows | task | access object                 | operator info |
+-------------+---------+------+-------------------------------+---------------+
| Point_Get_1 | 1.00    | root | table:users, index:name(name) |               |
+-------------+---------+------+-------------------------------+---------------+

2.2 純KV用戶視角中的PointGet

部分業(yè)務沒有完整地使用TiDB組件,而是使用官方提供的client-go/client-rust直接訪問PD和TiKV。

func testGet(k []byte) (error) {
    txn, err := client.Begin()
    if err != nil {
        return err
    }
    v, err := txn.Get(context.TODO(), k)
    if err != nil {
        return err
    }
    fmt.Printf("value of key is: %+v", v)
    return nil
}

三、PointGet在TiDB中的實現(xiàn)

TiDB層為計算層,其主要職能為MySQL協(xié)議的實現(xiàn)以及SQL優(yōu)化器和執(zhí)行器的構建。客戶端發(fā)起的所有SQL, 都會經過以下生命周期流程:

  1. Lexer/Parser解析后得到AST,并轉換為執(zhí)行計劃。
  2. 執(zhí)行計劃經過RBO/CBO后得到優(yōu)化過后的執(zhí)行計劃。
  3. 基于執(zhí)行計劃構建執(zhí)行器,其本質是不同的算子"套娃",整體構成一個樹型結構。

TiDB的執(zhí)行器基于"火山模型"構建,不同的操作算子具有不同的Executor實現(xiàn):

type Executor interface {   
    base() *baseExecutor
    Open(context.Context) error
    Next(ctx context.Context, req *chunk.Chunk) error
    Close() error
    Schema() *expression.Schema
}

Executor中最為核心的是三個函數(shù)分別是Open/Next/Close,分別對應算子的初始化、迭代以及收尾邏輯。本文涉及的PointGet算子由PointGetExector實現(xiàn),其核心的查詢邏輯位于PointGetExector::Next()函數(shù)中。由于相關邏輯耦合了悲觀事務,以及tikv/client-go中部分Percolator的實現(xiàn),且不屬于本文重點分析的主要內容,這里不展開描述,感興趣的讀者可以自行閱讀。

四、PointGet在TiKV中的實現(xiàn)

4.1 PointGet接口定義

TiKV和TiDB使用gRPC進行通信,其接口契約定義采用了protobuf,我們可以在pingcap/kvproto項目中找到與PointGet相關的接口定義KvGet如下:

// Key/value store API for TiKV.
service Tikv {
    // Commands using a transactional interface.
    rpc KvGet(kvrpcpb.GetRequest) returns (kvrpcpb.GetResponse) {}
    // ... other api definations ...
}

其中入?yún)etRequest定義如下代碼片段,我們可以看到,TiKV的點查接口除了key之外,還額外需要一個名為version的參數(shù),即當前事務的start_ts(事務開始時間戳),這個時間戳是由TiDB在啟動事務時從Pd組件申請而來。與很多數(shù)據(jù)庫類似,TiKV也采用了MVCC機制,即同一個key在底層的存儲中在不同時刻擁有不同的值,因此要想進行查詢,除了key之外,還需要帶上版本。

// A transactional get command. Lookup a value for `key` in the transaction with
// starting timestamp = `version`.
message GetRequest {
    Context context = 1;
    bytes key = 2;
    uint64 version = 3;
}

4.2 TiKV側調用堆棧

TiKV作為gRPC的Server端,提供了KvGet接口的實現(xiàn),相關調用堆棧為:

+TiKV::kv_get (grpc-poll-thread)
 +future_get
  +Storage::get
   +Storage::snapshot (readpool-thread)
   +SnapshotStore::get
     +PointGetterBuilder::build
     +PointGetter::get

在一次KvGet調用中,函數(shù)執(zhí)行流程會在grpc-poll-thread和readpool-thread中切換,其中前者為gRPC的poll thread,請求在被路由到Storage層后,會根據(jù)讀寫屬性路由到不同的線程池中,只讀語義的Get/Scan請求都會被路由到ReadPool中執(zhí)行,這是一個特定用于處理只讀請求的線程池。

4.3 Read through locks介紹

在分析后續(xù)邏輯之前,我們需要對Read through locks機制先做個簡單介紹。TiKV使用Percoaltor模型來實現(xiàn)分布式事務,同時也引入了MVCC機制。然而其實現(xiàn)和傳統(tǒng)的MVCC實現(xiàn)略有差異:TiKV的讀取過程中若遇到其他事務提交時寫入的Lock, 則需要等待或者嘗試解鎖,這會阻塞讀取直到事務狀態(tài)確定,一定程度上會損失并發(fā)性能。

然而在一些場景(如SecondaryLocks),在Key對應的鎖仍然存在的情況下,我們已經知道相關事務的最終狀態(tài)(提交或回滾)。如果我們將這些事務的最終狀態(tài)與查詢請求一起發(fā)送給TiKV, 那么TiKV可以根據(jù)這些事務狀態(tài)來確定能否在有Lock的情況下安全讀取,避免不必要的等待, 即本小節(jié)提到的Read through lock機制。

Context是所有的TiKV請求都會攜帶的上下文信息,為了實現(xiàn)Read through lock, 

https://github.com/pingcap/kvproto/pull/833 這個PR在Context中添加了如下字段:

message Context {
    // Read requests can ignore locks belonging to these transactions because either
    // these transactions are rolled back or theirs commit_ts > read request's start_ts.
    repeated uint64 resolved_locks = 13;
  
    // Read request should read through locks belonging to these transactions because these
    // transactions are committed and theirs commit_ts <= read request's start_ts.
    repeated uint64 committed_locks = 22;
}

其中resolved_locks用于記錄讀取時可以忽略的鎖,這些鎖對應的事務可能已被回滾,或者已成功提交但CommitTS大于當前的讀StartTS,直接忽略這些鎖也不影響快照一致性。

其中committed_locks則用于記錄邏輯上已被正確提交但物理上Lock還未被清理的、且CommitTS小于當前讀取使用的StartTS的事務。由于事務本質上已經被提交,因此讀取時可以不需要返回等待,只需要通過Lock查詢DefaultCF中的數(shù)據(jù)即可。

通過Read through lock機制,TiKV可以在一些Lock尚未被清理的情況下直接返回正確的結果,避免了客戶端層面的Wait和ResolveLock,其具體實現(xiàn)在后續(xù)小節(jié)會涉及到。

4.4 Storage::get流程分析

下方代碼塊是經過精簡過后的偽代碼,主要標注了get流程中一些比較關鍵的步驟。

pub fn get(&self, mut ctx: Context, key: Key, start_ts: TimeStamp) -> impl Future<Output = ... >> {
  self.read_pool.spawn_handle(async move {
    
     // 1. 創(chuàng)建創(chuàng)建快照需要的上下文
     let snap_ctx = prepare_snap_ctx(...);
  
     // 2. 申請一個快照
     let snapshot = Self::with_tls_engine(|engine| Self::snapshot(engine, snap_ctx)).await?;
       
     // 3. 創(chuàng)建SnapshotStore對象并執(zhí)行查詢
     let snap_store = SnapshotStore::new(...);
     let result = snap_store.get(key);
  
     // 4. 更新Metrics和Stats統(tǒng)計信息
  });
}

4.4.1 準備快照上下文

prepare_snap_ctx顧名思義即準備用于創(chuàng)建快照所需要的上下文對象,即SnapContext對象,其完整定義如下:

pub struct SnapContext<'a> {
    pub pb_ctx: &'a Context,
    pub read_id: Option<ThreadReadId>,
    // When start_ts is None and `stale_read` is true, it means acquire a snapshot without any
    // consistency guarantee.
    pub start_ts: Option<TimeStamp>,
    // `key_ranges` is used in replica read. It will send to
    // the leader via raft "read index" to check memory locks.
    pub key_ranges: Vec<KeyRange>,
    // Marks that this snapshot request is allowed in the flashback state.
    pub allowed_in_flashback: bool,
}
 
fn prepare_snap_ctx<'a>(...) -> Result<SnapContext<'a>> {
    if !pb_ctx.get_stale_read() {
        concurrency_manager.update_max_ts(start_ts);
    }
    if need_check_locks(isolation_level) {
       concurrency_manager.read_key_check(...)
    }
    let mut snap_ctx = SnapContext {...};
    if need_check_locks_in_replica_read(pb_ctx) {
       snap_ctx.key_ranges = ...
    }
}

prepare_snap_ctx只需要創(chuàng)建一個SnapContext對象,但目前實現(xiàn)中多出了如下判斷或操作,絕大部分都源于TiKV5.0中的AsyncCommit特性所需。

1.當本次讀取非StaleRead時,需要將當前讀取請求的start_ts與CurrencyManager中的max_ts進行比較,并將二者中的最大值更新為全局max_ts。這一操作用于保證異步提交事務計算出來的MinCommitTs不會破壞快照一致性。

2. 若當前的隔離級別是SnapshotIsolation或者RcCheckTs時, 則需要額外檢查CurrencyManager中的內存鎖。如果存在鎖且當前start_ts大于鎖中的MinCommitTs,TiKV會直接拒絕本次讀取請求。其原因在于AsyncCommit事務Prewrite結束之前需要暫時阻止使用更新的start_ts發(fā)起的快照讀,否則會導致正在異步提交的事務計算出的MinCommitTS無法滿足快照一致性。

4.4.2 向Engine申請Snapshot

Engine是TiKV中對上層存儲組件的一次抽象,所有實現(xiàn)了Engine Trait的具體實現(xiàn)都可以作為TiKV中的存儲層組件。目前TiKV中已經實現(xiàn)了BTreeEngine/MockEngine/RocksEngine/RaftKV等多個實現(xiàn)。

pub trait Engine: Send + Clone + 'static {
   // 獲取用于查詢的快照
   fn async_snapshot(&mut self, ctx: SnapContext<'_>) -> Self::SnapshotRes;
     
   // 提交寫入的Mutation
   fn async_write(&self,ctx: &Context,batch: WriteData,subscribed: u8, on_applied: Option<OnAppliedCb>) -> Self::WriteRes;
 
   // 其他接口...
}

Engine的接口定義中與讀寫相關的接口分別是async_snapshot和async_write。目前TiKV中的默認Engine實現(xiàn)為RaftKV,即一個基于Raftstore的實現(xiàn)。在RaftKV中,所有的寫入都會通過Raft狀態(tài)機進行propose/commit/apply流程,用戶可以基于訂閱機制獲得這3個事件的通知從而做出不同處理,默認情況下,TiKV會在一次寫入請求被RaftLeader apply成功后返回用戶。而讀取操作則需要遵循先行一致性讀取,在早期版本中,一次讀取需要通過Raft狀態(tài)機進行一次ReadIndex才能進行,在新版中TiKV實現(xiàn)了基于租約的LeaseRead, 簡化了讀取流程。本次介紹的PointGet讀取流程中,會涉及到使用async_snapshot獲取一個Engine在當前時刻的快照,并基于快照進行讀取。

TiKV按照KeyRange將Key拆分為不同的Region, 每個Region都是一個RaftGroup,且擁有獨立的狀態(tài)機推進運轉。因此,RaftKV-Engine中async_snapshot返回的是一個名為RegionSnapshot的對象,其定義如下:

pub struct RegionSnapshot<S: Snapshot> {
    snap: Arc<S>,
    region: Arc<Region>,
    apply_index: Arc<AtomicU64>,
    pub term: Option<NonZeroU64>,
    pub txn_extra_op: TxnExtraOp,
    // `None` means the snapshot does not provide peer related transaction extensions.
    pub txn_ext: Option<Arc<TxnExt>>,
    pub bucket_meta: Option<Arc<BucketMeta>>,
}

RegionSnapshot本質是對底層的KV引擎RocksDB層面的快照的封裝,其邏輯視圖如下:

圖片

4.4.3 MVCC實現(xiàn)和快照隔離級別實現(xiàn)

前文提到的Engine::async_snapshot接口返回的快照本質是Engine在當下時刻的快照,并不等于事務層面的MVCC快照,因此在具體查詢時,需要配合StartTS進行使用。TiKV中封裝了一個SnapshotStore用于輔助MVCC層面的查詢。其定義如下:

pub struct SnapshotStore<S: Snapshot> {
    snapshot: S,
    start_ts: TimeStamp,
    isolation_level: IsolationLevel,
    fill_cache: bool,
    bypass_locks: TsSet,
    access_locks: TsSet,
    check_has_newer_ts_data: bool,
    point_getter_cache: Option<PointGetter<S>>,
}

SnapshotStore中集合了從Engine獲取的快照和客戶端請求附帶的StartTS, 因此可以被認為是一個MVCC層面的快照。用戶對SnapshotStore發(fā)起的點查會被委托給內部的PointGetter。

// PointGetter::get
pub fn get(&mut self, user_key: &Key) -> Result<Option<Value>> {
        fail_point!("point_getter_get");
          
        // 根據(jù)當前請求使用的隔離級別判定是否需要檢查鎖
        if need_check_locks(self.isolation_level) {
            // 如果需要檢查鎖且鎖存在,則需要根據(jù)判定鎖
            if let Some(lock) = self.load_and_check_lock(user_key)? {
                return self.load_data_from_lock(user_key, lock);
            }
        }
          
        // Percoaltor正常讀取流程:從WriteCF中找到<=start_ts中最大的commit_ts,并基于其存儲的start_ts到DefaultCF中讀取        
        self.load_data(user_key)
}

在執(zhí)行查詢前,TiKV需要根據(jù)當前請求的隔離級別判定是否需要檢查鎖。

pub fn need_check_locks(iso_level: IsolationLevel) -> bool {
    matches!(iso_level, IsolationLevel::Si | IsolationLevel::RcCheckTs)
}

TiKV支持SnapshotIsolation/ReadCommitted/ReadCommittedCheckTs三種隔離級別,其中前兩種需要檢查鎖。其原因在于LockCf中的鎖是由于事務在2PC的第一階段提交階段寫入的,事務的最終狀態(tài)無法確定,如果不檢查鎖直接讀取,那么可能導致快照讀取被破壞。

fn load_and_check_lock(&mut self, user_key: &Key) -> Result<Option<Lock>> {
        // 從LockCf查詢該Key的鎖信息
        let lock_value = self.snapshot.get_cf(CF_LOCK, user_key)?;
  
        if let Some(ref lock_value) = lock_value {
            let lock = Lock::parse(lock_value)?;
            // 如果存在鎖則檢查鎖是否沖突
            if let Err(e) = Lock::check_ts_conflict(
                Cow::Borrowed(&lock),
                user_key,
                self.ts,
                &self.bypass_locks,
                self.isolation_level,
            )
        // ...
}

其中Lock::check_ts_conflict的實現(xiàn)中會根據(jù)當前的事務隔離級別進行判定,不同的隔離級別的判定邏輯略有差異。由于本文篇幅有限,這里只分析我們常用的快照隔離級別的實現(xiàn)。

fn check_ts_conflict_si(lock: Cow<'_, Self>, key: &Key, ts: TimeStamp, bypass_locks: &TsSet ) -> Result<()> {
        if lock.ts > ts
            || lock.lock_type == LockType::Lock
            || lock.lock_type == LockType::Pessimistic
        {
            return Ok(());
        }
  
        if lock.min_commit_ts > ts {
            // Ignore lock when min_commit_ts > ts
            return Ok(());
        }
  
        if bypass_locks.contains(lock.ts) {
            return Ok(());
        }
  
        let raw_key = key.to_raw()?;
  
        if ts == TimeStamp::max() && raw_key == lock.primary && !lock.use_async_commit {
            // When `ts == TimeStamp::max()` (which means to get latest committed version
            // for primary key), and current key is the primary key, we ignore
            // this lock.
            return Ok(());
        }
  
        // There is a pending lock. Client should wait or clean it.
        Err(Error::from(ErrorInner::KeyIsLocked(
            lock.into_owned().into_lock_info(raw_key),
        )))
}
  • 當lock.ts > ts時,當前查詢請求可以直接忽略這個鎖。其原因在于當前的lock是由具有更高start_ts的事務寫入,因此即便這個事務后續(xù)被提交,其commit_ts一定大于當前的start_ts,其新寫入的數(shù)據(jù)是不可見的,不會破壞快照一致性。
  • 當lock_type==Lock時,也可以直接忽略這個鎖突, 其原因在于LockType::Lock是由于創(chuàng)建索引產生,它只用于指示被鎖定但不會修改數(shù)據(jù),因此也可以直接被忽略。
  • 當lock_type==Pessistics時,也可以直接忽略這個鎖突,LockType::Pessistics是由于悲觀事務執(zhí)行DML時寫入,并未進行到事務提交階段,即使這個事務很快被提交,由于其commit_ts也一定大于當前讀取的start_ts, 直接忽略并不會影響快照一致性。
  • 當lock.min_commit_ts > ts時,也可以直接忽略這個鎖,其原因在于它能保證這個AsyncCommit事務的最終計算出的commit_ts一定大于ts,即使這個事務會被提交,也不會破壞快照一致性。
  • 當bypass_locks中包含了當前鎖的start_ts時, 也可以直接忽略這個鎖。bypass_locks即前面Read through locks小節(jié)中提到了resloved_locks,這些鎖雖然存在,但它們對應事務要么已經被回滾,要么使用了大于當前讀取start_ts的commit_ts進行提交,無論是哪種情況都不會破壞快照一致性。
  • 其他情況則需要返回KeyIsLocked錯誤給客戶端,客戶端收到這個錯誤后則會檢查這個鎖的過期時間,如果鎖尚未過期則需要做wait,否則會嘗試進行解鎖恢復這個事務的狀態(tài)。

若check_ts_conflict_si返回KeyIsLocked或其他錯誤后,TiKV會額外檢查access_locks里是否包含該鎖,如果該鎖存在,則KeyIsLocked錯誤則會被忽略,同時鎖會被直接返回,外層函數(shù)可以通過鎖找到start_ts從而直接讀取DefaultCF中的數(shù)據(jù)。這里的access_locks即Read through locks中的committed_locks,即已經知曉被提交的且commit_ts小于當前快照讀start_ts的事務,在這種情況下,直接讀取DefaultCF是一個超前但安全的操作,原因在在于一旦這個Lock被Resolve,用戶通過新的commit_ts可以定位到同一個start_ts。

if let Err(e) = Lock::check_ts_conflict(Cow::Borrowed(&lock),user_key,self.ts,&self.bypass_locks,self.isolation_level) {
    if self.access_locks.contains(lock.ts) {
        return Ok(Some(lock));
   }
    Err(e.into())
}

在不存在Key被鎖定或沖突,且沒有使用Read through locks讀取后,TiKV則會進行正常的Percolator讀取流程,即從WriteCF中找到<=start_ts中最大的commit_ts,并基于其存儲的start_ts到DefaultCF中讀取。

4.4.4 RegionSnapshot的Get實現(xiàn)

RegionSnapshot::get的實現(xiàn)相對比較簡單,邏輯如下:

fn get_value_cf_opt(&self, opts: &ReadOptions, cf: &str, key: &[u8]) -> EngineResult<Option<Self::DbVector>> {
    // 1. 檢查查詢的key是否在Region的范圍內, 如果不在則直接返回錯誤。
    check_key_in_range(key,self.region.get_id(),self.region.get_start_key(),self.region.get_end_key()).map_err(|e| EngineError::Other(box_err!(e)))?;
      
    // 2. 基于查詢的key拼接出raftstore層面的DataKey (raftstore在寫入時會給用戶key前添加一個前綴'z')。
    let data_key = keys::data_key(key);
      
    // 3. 使用內部的RocksSnapshot查詢RocksDB獲取key對應的值。
    self.snap.get_value_cf_opt(opts, cf, &data_key).map_err(|e| self.handle_get_value_error(e, cf, key))
}

4.4.5 RocksDB/Titan的Get實現(xiàn)

TiKV使用rust-rocksdb庫使用FFI實現(xiàn)與RocksDB C-API的交互,RocksSnapshot::get會通過crocksdb_get_pinned_cf將查詢接口委托給底層的RocksDB。值得注意的是,TiKV使用的并不是官方的RocksDB,而是自行維護的一個整合了Titan插件的版本。Titan是一個受WiscKey論文啟發(fā)而創(chuàng)建的項目,其主要目的是將存入RocksDB的大Value從LSM-Tree中分離出來,存儲到額外的Blob文件中,從而達到減小寫放大的目的。

本小節(jié)我們著重分析一下TitanDB中一次查詢的實現(xiàn)過程(做過大量精簡):

Status TitanDBImpl::GetImpl(const ReadOptions& options,
                            ColumnFamilyHandle* handle, const Slice& key,
                            PinnableSlice* value) {
    
  // 先查詢RocksDB
  s = db_impl_->GetImpl(options, key, gopts);
  
  // 如果Key的Value不存在或者不是BlobIndex, 則直接返回
  if (!s.ok() || !is_blob_index) return s;
    
  // Value是BlobIndex,說明這是一個索引,還需要額外查詢BlobStorage
  BlobIndex index;
  s = index.DecodeFrom(value);
  assert(s.ok());
  if (!s.ok()) return s;
  
  BlobRecord record;
  PinnableSlice buffer;
  
  mutex_.Lock();
  // 根據(jù)索引查詢BlobStorage
  auto storage = blob_file_set_->GetBlobStorage(handle->GetID()).lock();
  mutex_.Unlock();
  
  if (s.ok()) {
    value->Reset();
    value->PinSelf(record.value);
  }
  return s;
}

五、總結

  1. TiKV對數(shù)據(jù)存儲層的職能進行了非常合理的抽象,通過Engine/Snapshot/Iterator等trait定義實現(xiàn)了存儲層與上層的解耦。
  2. TiKV在RocksDB提供的多列族原子性寫入能力之上實現(xiàn)了Percolator模型,提供了分布式事務和MVCC等能力,并實現(xiàn)了AsyncCommit和1PC等改善了事務提交延遲。
  3. TiKV實現(xiàn)了一個基于RocksDB的KV分離插件titan, 借鑒了Wisckey的思想將大Value從LSM-Tree中分離,在大Value的業(yè)務場景下能夠通過降低寫放大改善性能。
  4. 從PointGet的實現(xiàn)我們可以看到在使用了MVCC的情況下,查詢時遇到前一事務Prewrite產生的Lock仍然需要等待Resolve,因此在AsyncCommit開啟的前提下,業(yè)務開發(fā)需要盡量避免設計事務提交后即刻發(fā)起查詢的場景,此外也要盡量避免由于大事務提交延遲高影響相關的查詢。

參考資料:

責任編輯:龐桂玉 來源: vivo互聯(lián)網(wǎng)技術
相關推薦

2011-05-26 10:05:48

MongoDB

2023-02-26 08:42:10

源碼demouseEffect

2012-09-20 10:07:29

Nginx源碼分析Web服務器

2021-07-06 09:29:38

Cobar源碼AST

2021-03-23 09:17:58

SpringMVCHttpServletJavaEE

2011-05-26 16:18:51

Mongodb

2020-07-28 08:54:39

內核通信Netlink

2012-09-06 10:07:26

jQuery

2021-09-05 07:35:58

lifecycleAndroid組件原理

2009-07-08 13:22:30

JDK源碼分析Set

2017-01-12 14:52:03

JVMFinalRefere源碼

2022-08-27 08:02:09

SQL函數(shù)語法

2022-05-30 07:36:54

vmstoragevmselect

2022-01-06 07:06:52

KubernetesResourceAPI

2019-09-09 06:30:06

Springboot程序員開發(fā)

2023-03-17 07:53:20

K8sAPIServerKubernetes

2017-02-27 11:48:58

JVM源碼分析Java

2021-02-16 10:55:02

Nodejs模塊

2020-05-26 18:50:46

JVMAttachJava

2019-09-20 08:54:38

KafkaBroker消息
點贊
收藏

51CTO技術棧公眾號