DML對QUERY CACHE 處理過程之原始碼分析

Steven1981發表於2011-07-27
問題:
當一個大的SELECT查詢在執行時,會把UPDATE堵住;
SHOW INNODB STATUS看,UPDATE的狀態是:invalidating query cache entries

SELECT結束,現象消失。

版本號:mysql 5.1.40

首先說明一下,QUERY CACHE 只有一把全域性鎖。
private:
Cache_lock_status m_cache_lock_status;
下面說到的鎖,就是這個全域性獨佔鎖。
[@more@]
MYSQL在處理SELECT過程中,對QUERY CACHE會有兩個操作:
High.Performance.MySQL.Second.Edition.pdf -- P161
1) 在SQL PARSE 之前,就會先到QUERY CACHE裡去找,是否有這個SQL的CACHE;如果有,就把結果前直接發給使用者;
2) 第一步無果,進行查詢處理,查詢處理後,把結果集放到QUERY CACHE中;
(都會調這個函式, 是否真正需要放,在函式內判斷)


我們來看第1步:呼叫的是以下函式:
/*
Check if the query is in the cache. If it was cached, send it
to the user.

RESULTS
1 Query was not cached.
0 The query was cached and user was sent the result.
-1 The query was cached but we didn't have rights to use it.
No error is sent to the client yet.

NOTE
This method requires that sql points to allocated memory of size:
tot_length= query_length + thd->db_length + 1 + QUERY_CACHE_FLAGS_SIZE;
*/

int
Query_cache::send_result_to_client(THD *thd, char *sql, uint query_length)

在這個函式中, SERVER會先試著去拿鎖,如果拿不到鎖,就直接返回錯誤,表示找不到SQL;
拿不到鎖,有可能是剛好有其他任務在讀或者有更新;
/*
Try to obtain an exclusive lock on the query cache. If the cache is
disabled or if a full cache flush is in progress, the attempt to
get the lock is aborted. 去QUERY CACHE查詢時,
*/
if (try_lock())
goto err;

if (query_cache_size == 0)
goto err_unlock;

如果是這樣,有一點是不能解釋:
雖然從QUERY CACHE中透過HASH值拿到RESULT很快;
但畢竟是一個全域性鎖,他這樣設計,對SELECT併發大的環境,是不是會有很多查詢都拿不到RESULT,而去重新查詢;



第二步,是把查詢結果註冊到QUERY CACHE中; 調的是以下函式:
/* register query in cache */
void store_query(THD *thd, TABLE_LIST *used_tables);


/*
A table- or a full flush operation can potentially take a long time to
finish. We choose not to wait for them and skip caching statements
instead.
*/
if (try_lock())
DBUG_VOID_RETURN;

這裡也是,如果拿不到鎖就直接跳出。不再進行寫QUERY CACHE。
如果一直等待,那麼SELECT就HANG起

對以上兩個操作,小結了一下: MYSQL在處理SELECT時,對QUERY CACHE的態度時,能用就用,用不上拉到,(響應優先)


下面來看MYSQL在處理INSERT,UPDATE,DELETE過程,
每個事務提交完成前,會對被更新的所有表進行QUERY CACHE清理工作;
事務處理int ha_commit_one_phase(THD *thd, bool all) 呼叫以下函式:
void Query_cache::invalidate(CHANGED_TABLE_LIST *tables_used)
{
DBUG_ENTER("Query_cache::invalidate (changed table list)");
THD *thd= current_thd;
for (; tables_used; tables_used= tables_used->next)
{
thd_proc_info(thd, "invalidating query cache entries (table list)");
invalidate_table(thd, (uchar*) tables_used->key, tables_used->key_length);
DBUG_PRINT("qcache", ("db: %s table: %s", tables_used->key,
tables_used->key+
strlen(tables_used->key)+1));
}
DBUG_VOID_RETURN;
}

在這個函式里面,每個表的清理工作:
void Query_cache::invalidate_table(THD *thd, uchar * key, uint32 key_length)
/*
Lock the query cache and queue all invalidation attempts to avoid
the risk of a race between invalidation, cache inserts and flushes.
*/
lock();

DBUG_EXECUTE_IF("wait_in_query_cache_invalidate2",
debug_wait_for_kill("wait_in_query_cache_invalidate2"); );


if (query_cache_size > 0)
invalidate_table_internal(thd, key, key_length);

unlock();

在這裡我們發現,呼叫的是lock(),這個函式需要拿QUERY CACHE的獨佔鎖,如果拿不到,就等待,直到超時;
void Query_cache::lock(void)
{
DBUG_ENTER("Query_cache::lock");

pthread_mutex_lock(&structure_guard_mutex);
while (m_cache_lock_status != Query_cache::UNLOCKED)
pthread_cond_wait(&COND_cache_status_changed, &structure_guard_mutex);


到這裡為止,大查詢會阻塞UPDATE這一說法還是成立的;
如果你的QUERY CAHCE足夠大,以及你的查詢結果集夠大,Void store_query()工作時,會拿到鎖,
而UPDATE要清理,也要拿鎖,這時等待;

但我們有幾個引數要看一下:
| query_cache_limit | 2097152 ## 超過2M的的結果集不CACHE
| query_cache_size | 67108864 ## QUERY CACHE 大小

這裡,如果QUERY CAHCE超過2M,其實不會被CACHE; status variable:Qcache_not_cached+1;

但即使儲存2M的結果集,也不會需要很長時間;
要超時,實在是不理解;

到這裡,雖然分析了,DML時QUERY CACHE的相關處理過程,
但還是沒能解釋我們資料庫中,SELECT把我們資料庫UPDATE堵成超時的真正原因;
先作為知識儲備。

來自 “ ITPUB部落格 ” ,連結:http://blog.itpub.net/703656/viewspace-1053088/,如需轉載,請註明出處,否則將追究法律責任。

相關文章