/* Copyright (C) 2008 Sun AB and Michael Widenius
This program is free software ; you can redistribute it and / or modify
it under the terms of the GNU General Public License as published by
the Free Software Foundation ; version 2 of the License .
This program is distributed in the hope that it will be useful ,
but WITHOUT ANY WARRANTY ; without even the implied warranty of
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE . See the
GNU General Public License for more details .
You should have received a copy of the GNU General Public License
along with this program ; if not , write to the Free Software
Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1335 USA */
/*
Functions to maintain live statistics for Maria transactional tables
and versioning for not transactional tables
See WL # 3138 ; Maria - fast " SELECT COUNT ( * ) FROM t ; " and " CHECKSUM TABLE t "
for details about live number of rows and live checksums
TODO
- Allocate MA_USED_TABLES and MA_HISTORY_STATE from a global pool ( to
avoid calls to malloc ( )
- In trnamn_end_trans_hook ( ) , don ' t call _ ma_remove_not_visible_states ( )
every time . One could for example call it if there has been more than
10 ended transactions since last time it was called .
*/
#include "maria_def.h"
#include "trnman.h"
#include "ma_trnman.h"
#include "ma_blockrec.h"
/**
@ brief Setup initial start - of - transaction state for a table
@ fn _ ma_setup_live_state
@ param info Maria handler
@ notes
This function ensures that trn - > used_tables contains a list of
start and live states for tables that are part of the transaction
and that info - > state points to the current live state for the table .
@ TODO
Change trn - > table_list to a hash and share - > state_history to a binary tree
@ return
@ retval 0 ok
@ retval 1 error ( out of memory )
*/
my_bool _ma_setup_live_state(MARIA_HA *info)
{
TRN *trn;
MARIA_SHARE *share= info->s;
MARIA_USED_TABLES *tables;
MARIA_STATE_HISTORY *history;
DBUG_ENTER("_ma_setup_live_state" );
DBUG_PRINT("enter" , ("info: %p" , info));
DBUG_ASSERT(share->lock_key_trees);
if (maria_create_trn_hook(info))
DBUG_RETURN(1 );
trn= info->trn;
for (tables= (MARIA_USED_TABLES*) trn->used_tables;
tables;
tables= tables->next)
{
if (tables->share == share)
{
/* Table is already used by transaction */
goto end;
}
}
/* Table was not used before, create new table state entry */
if (!(tables= (MARIA_USED_TABLES*) my_malloc(PSI_INSTRUMENT_ME,
sizeof (*tables), MYF(MY_WME | MY_ZEROFILL))))
DBUG_RETURN(1 );
tables->next= trn->used_tables;
trn->used_tables= tables;
tables->share= share;
mysql_mutex_lock(&share->intern_lock);
share->in_trans++;
DBUG_PRINT("info" , ("share: %p in_trans: %d" ,
share, share->in_trans));
history= share->state_history;
/*
We must keep share locked to ensure that we don ' t access a history
link that is deleted by concurrently running checkpoint .
It ' s enough to compare trids here ( instead of calling
tranman_can_read_from ) as history - > trid is a commit_trid
*/
while (trn->trid <= history->trid)
history= history->next;
mysql_mutex_unlock(&share->intern_lock);
/* The current item can't be deleted as it's the first one visible for us */
tables->state_start= tables->state_current= history->state;
tables->state_current.changed= tables->state_current.no_transid= 0 ;
DBUG_PRINT("info" , ("records: %ld" , (ulong) tables->state_start.records));
end:
info->state_start= &tables->state_start;
info->state= &tables->state_current;
info->used_tables= tables;
tables->use_count++;
/*
Mark in transaction state if we are not using transid ( versioning )
on rows . If not , then we will in _ ma_trnman_end_trans_hook ( )
ensure that the state is visible for all at end of transaction
*/
tables->state_current.no_transid|= !(info->row_flag & ROW_FLAG_TRANSID);
DBUG_PRINT("exit" , ("tables: %p info->state: %p" , tables, info->state));
DBUG_RETURN(0 );
}
/**
@ brief Remove states that are not visible by anyone
@ fn _ ma_remove_not_visible_states ( )
@ param org_history List to history
@ param all 1 if we should delete the first state if it ' s
visible for all . For the moment this is only used
on close ( ) of table .
@ param trnman_is_locked Set to 1 if we have already a lock on trnman .
@ notes
The assumption is that items in the history list is ordered by
commit_trid .
A state is not visible anymore if there is no new transaction
that has been started between the commit_trid ' s of two states
As long as some states exists , we keep the newest = ( last commit )
state as first state in the history . This is to allow us to just move
the history from the global list to the share when we open the table .
Note that if ' all ' is set trnman_is_locked must be 0 , becasue
trnman_get_min_trid ( ) will take a lock on trnman .
@ return
@ retval Pointer to new history list
*/
MARIA_STATE_HISTORY
*_ma_remove_not_visible_states(MARIA_STATE_HISTORY *org_history,
my_bool all,
my_bool trnman_is_locked)
{
TrID last_trid;
MARIA_STATE_HISTORY *history, **parent, *next;
DBUG_ENTER("_ma_remove_not_visible_states" );
if (!org_history)
DBUG_RETURN(0 ); /* Not versioned table */
last_trid= org_history->trid;
parent= &org_history->next;
for (history= org_history->next; history; history= next)
{
next= history->next;
if (!trnman_exists_active_transactions(history->trid, last_trid,
trnman_is_locked))
{
DBUG_PRINT("info" , ("removing history->trid: %lu next: %lu" ,
(ulong) history->trid, (ulong) last_trid));
my_free(history);
continue ;
}
*parent= history;
parent= &history->next;
last_trid= history->trid;
}
*parent= 0 ;
if (all && parent == &org_history->next)
{
/* There is only one state left. Delete this if it's visible for all */
if (last_trid < trnman_get_min_trid())
{
my_free(org_history);
org_history= 0 ;
}
}
DBUG_RETURN(org_history);
}
/**
@ brief Remove not used state history
@ param share Maria table information
@ param all 1 if we should delete the first state if it ' s
visible for all . For the moment this is only used
on close ( ) of table .
@ notes
share and trnman are not locked .
We must first lock trnman and then share - > intern_lock . This is becasue
_ ma_trnman_end_trans_hook ( ) has a lock on trnman and then
takes share - > intern_lock .
*/
void _ma_remove_not_visible_states_with_lock(MARIA_SHARE *share,
my_bool all)
{
my_bool is_lock_trman;
if ((is_lock_trman= trman_is_inited()))
trnman_lock();
mysql_mutex_lock(&share->intern_lock);
share->state_history= _ma_remove_not_visible_states(share->state_history,
all, 1 );
mysql_mutex_unlock(&share->intern_lock);
if (is_lock_trman)
trnman_unlock();
}
/*
Free state history information from share - > history and reset information
to current state .
@ notes
Used after repair / rename / drop as then all rows are visible for everyone
*/
void _ma_reset_state(MARIA_HA *info)
{
MARIA_SHARE *share= info->s;
MARIA_STATE_HISTORY *history= share->state_history;
DBUG_ENTER("_ma_reset_state" );
/* Always true if share->now_transactional is set */
if (history && share->have_versioning)
{
MARIA_STATE_HISTORY *next;
DBUG_PRINT("info" , ("resetting history" ));
/* Set the current history to current state */
share->state_history->state= share->state.state;
/* Set current table handler to point to new history state */
info->state= info->state_start= &share->state_history->state;
for (history= history->next ; history ; history= next)
{
next= history->next;
my_free(history);
}
share->state_history->next= 0 ;
share->state_history->trid= 0 ; /* Visible for all */
}
DBUG_VOID_RETURN;
}
/****************************************************************************
The following functions are called by thr_lock ( ) in threaded applications
for not transactional tables
****************************************************************************/
/*
Create a copy of the current status for the table
SYNOPSIS
_ ma_get_status ( )
param Pointer to Myisam handler
concurrent_insert Set to 1 if we are going to do concurrent inserts
( THR_WRITE_CONCURRENT_INSERT was used )
*/
my_bool _ma_get_status(void * param, my_bool concurrent_insert)
{
MARIA_HA *info=(MARIA_HA*) param;
DBUG_ENTER("_ma_get_status" );
DBUG_PRINT("info" ,("key_file: %ld data_file: %ld concurrent_insert: %d" ,
(long ) info->s->state.state.key_file_length,
(long ) info->s->state.state.data_file_length,
concurrent_insert));
#ifndef DBUG_OFF
if (info->state->key_file_length > info->s->state.state.key_file_length ||
info->state->data_file_length > info->s->state.state.data_file_length)
DBUG_PRINT("warning" ,("old info: key_file: %ld data_file: %ld" ,
(long ) info->state->key_file_length,
(long ) info->state->data_file_length));
#endif
info->state_save= info->s->state.state;
info->state= &info->state_save;
info->state->changed= 0 ;
info->append_insert_at_end= concurrent_insert;
DBUG_RETURN(0 );
}
void _ma_update_status(void * param)
{
MARIA_HA *info=(MARIA_HA*) param;
/*
Because someone may have closed the table we point at , we only
update the state if its our own state . This isn ' t a problem as
we are always pointing at our own lock or at a read lock .
( This is enforced by thr_multi_lock . c )
*/
if (info->state == &info->state_save)
{
MARIA_SHARE *share= info->s;
#ifndef DBUG_OFF
DBUG_PRINT("info" ,("updating status: key_file: %ld data_file: %ld" ,
(long ) info->state->key_file_length,
(long ) info->state->data_file_length));
if (info->state->key_file_length < share->state.state.key_file_length ||
info->state->data_file_length < share->state.state.data_file_length)
DBUG_PRINT("warning" ,("old info: key_file: %ld data_file: %ld" ,
(long ) share->state.state.key_file_length,
(long ) share->state.state.data_file_length));
#endif
/*
we are going to modify the state without lock ' s log , this would break
recovery if done with a transactional table .
*/
DBUG_ASSERT(!info->s->base.born_transactional);
share->state.state= *info->state;
info->state= &share->state.state;
DBUG_PRINT("info" , ("invalidator... '%s' (status update)" ,
info->s->data_file_name.str));
DBUG_ASSERT(info->s->chst_invalidator != NULL);
(*info->s->chst_invalidator)((const char *)info->s->data_file_name.str);
}
info->append_insert_at_end= 0 ;
}
/*
Same as ma_update_status ( ) but take a lock in the table lock , to protect
against someone calling ma_get_status ( ) from thr_lock ( ) at the same time .
*/
void _ma_update_status_with_lock(MARIA_HA *info)
{
my_bool locked= 0 ;
if (info->state == &info->state_save)
{
locked= 1 ;
mysql_mutex_lock(&info->s->lock.mutex);
}
(*info->s->lock.update_status)(info->lock.status_param);
if (locked)
mysql_mutex_unlock(&info->s->lock.mutex);
}
void _ma_restore_status(void *param)
{
MARIA_HA *info= (MARIA_HA*) param;
info->state= &info->s->state.state;
info->append_insert_at_end= 0 ;
}
void _ma_copy_status(void * to, void *from)
{
((MARIA_HA*) to)->state= &((MARIA_HA*) from)->state_save;
}
my_bool _ma_reset_update_flag(void *param,
my_bool concurrent_insert __attribute__((unused)))
{
MARIA_HA *info=(MARIA_HA*) param;
info->state->changed= 0 ;
return 0 ;
}
my_bool _ma_start_trans(void * param)
{
MARIA_HA *info=(MARIA_HA*) param;
if (!info->s->lock_key_trees)
{
info->state= info->state_start;
*info->state= info->s->state.state;
}
return 0 ;
}
/**
@ brief Check if should allow concurrent inserts
@ implementation
Allow concurrent inserts if we don ' t have a hole in the table or
if there is no active write lock and there is active read locks and
maria_concurrent_insert = = 2 . In this last case the new
row ( ' s ) are inserted at end of file instead of filling up the hole .
The last case is to allow one to inserts into a heavily read - used table
even if there is holes .
@ notes
If there is a an rtree indexes in the table , concurrent inserts are
disabled in maria_open ( )
@ return
@ retval 0 ok to use concurrent inserts
@ retval 1 not ok
*/
my_bool _ma_check_status(void *param)
{
MARIA_HA *info=(MARIA_HA*) param;
/*
The test for w_locks = = 1 is here because this thread has already done an
external lock ( in other words : w_locks = = 1 means no other threads has
a write lock )
*/
DBUG_PRINT("info" ,("dellink: %ld r_locks: %u w_locks: %u" ,
(long ) info->s->state.dellink, (uint) info->s->r_locks,
(uint) info->s->w_locks));
return (my_bool) !(info->s->state.dellink == HA_OFFSET_ERROR ||
(maria_concurrent_insert == 2 && info->s->r_locks &&
info->s->w_locks == 1 ));
}
/**
@ brief write hook at end of trans to store status for all used table
@ Notes
This function must be called under trnman_lock in trnman_end_trn ( )
because of the following reasons :
- After trnman_end_trn ( ) is called , the current transaction will be
regarded as committed and all used tables state_history will be
visible to other transactions . To do this , we loop over all used
tables and create / update a history entries that contains the correct
state_history for them .
*/
my_bool _ma_trnman_end_trans_hook(TRN *trn, my_bool commit,
my_bool active_transactions)
{
my_bool error= 0 ;
MARIA_USED_TABLES *tables, *next;
DBUG_ENTER("_ma_trnman_end_trans_hook" );
DBUG_PRINT("enter" , ("trn: %p used_tables: %p" , trn, trn->used_tables));
for (tables= (MARIA_USED_TABLES*) trn->used_tables;
tables;
tables= next)
{
MARIA_SHARE *share= tables->share;
next= tables->next;
if (commit)
{
MARIA_STATE_HISTORY *history;
mysql_mutex_lock(&share->intern_lock);
/* We only have to update history state if something changed */
if (tables->state_current.changed)
{
if (tables->state_current.no_transid)
{
/*
The change was done without using transid on rows ( like in
bulk insert ) . In this case this thread is the only one
that is using the table and all rows will be visible
for all transactions .
*/
_ma_reset_history(share);
}
else
{
if (active_transactions && share->now_transactional &&
trnman_exists_active_transactions(share->state_history->trid,
trn->commit_trid, 1 ))
{
/*
There exist transactions that are still using the current
share - > state_history . Create a new history item for this
commit and add it first in the state_history list . This
ensures that all history items are stored in the list in
decreasing trid order .
*/
if (!(history= my_malloc(PSI_INSTRUMENT_ME, sizeof (*history),
MYF(MY_WME))))
{
/* purecov: begin inspected */
error= 1 ;
mysql_mutex_unlock(&share->intern_lock);
my_free(tables);
continue ;
/* purecov: end */
}
history->state= share->state_history->state;
history->next= share->state_history;
share->state_history= history;
}
else
{
/* Previous history can't be seen by anyone, reuse old memory */
history= share->state_history;
DBUG_PRINT("info" , ("removing history->trid: %lu new: %lu" ,
(ulong) history->trid,
(ulong) trn->commit_trid));
}
history->state.records+= (tables->state_current.records -
tables->state_start.records);
history->state.checksum+= (tables->state_current.checksum -
tables->state_start.checksum);
history->trid= trn->commit_trid;
share->state.last_change_trn= trn->commit_trid;
if (history->next)
{
/* Remove not visible states */
share->state_history= _ma_remove_not_visible_states(history, 0 , 1 );
}
DBUG_PRINT("info" , ("share: %p in_trans: %d" ,
share, share->in_trans));
}
}
/* The following calls frees &share->intern_lock */
decrement_share_in_trans(share);
}
else
{
/*
We need to keep share - > in_trans correct because of the check
in free_maria_share ( )
*/
mysql_mutex_lock(&share->intern_lock);
decrement_share_in_trans(share);
}
my_free(tables);
}
trn->used_tables= 0 ;
trn->used_instances= 0 ;
DBUG_RETURN(error);
}
/**
Remove table from trnman_list
@ notes
This is used when we unlock a table from a group of locked tables
just before doing a rename or drop table .
share - > internal_lock must be locked when function is called
*/
void _ma_remove_table_from_trnman(MARIA_HA *info)
{
MARIA_SHARE *share= info->s;
TRN *trn= info->trn;
MARIA_USED_TABLES *tables, **prev;
DBUG_ENTER("_ma_remove_table_from_trnman" );
DBUG_PRINT("enter" , ("trn: %p used_tables: %p share: %p in_trans: %d" ,
trn, trn->used_tables, share, share->in_trans));
mysql_mutex_assert_owner(&share->intern_lock);
if (trn == &dummy_transaction_object)
DBUG_VOID_RETURN;
/* First remove share from used_tables */
for (prev= (MARIA_USED_TABLES**) (char *) &trn->used_tables;
(tables= *prev);
prev= &tables->next)
{
if (tables->share == share)
{
*prev= tables->next;
/*
We don ' t have to and can ' t call decrement_share_in_trans ( share ) here
as we know there is an active MARIA_HA handler around .
*/
share->in_trans--;
my_free(tables);
break ;
}
}
if (!tables)
{
/*
This can only happens in case of rename of intermediate table as
part of alter table
*/
DBUG_PRINT("warning" , ("share: %p where not in used_tables_list" , share));
}
/* Reset trn and remove table from used_instances */
_ma_reset_trn_for_table(info);
DBUG_VOID_RETURN;
}
/****************************************************************************
The following functions are called by thr_lock ( ) in threaded applications
for transactional tables .
****************************************************************************/
/*
Create a copy of the current status for the table
SYNOPSIS
_ ma_get_status ( )
param Pointer to Aria handler
concurrent_insert Set to 1 if we are going to do concurrent inserts
( THR_WRITE_CONCURRENT_INSERT was used )
*/
my_bool _ma_block_get_status(void * param, my_bool concurrent_insert)
{
MARIA_HA *info=(MARIA_HA*) param;
DBUG_ENTER("_ma_block_get_status" );
DBUG_PRINT("enter" , ("concurrent_insert %d" , concurrent_insert));
info->row_base_length= info->s->base_length;
info->row_flag= info->s->base.default_row_flag;
DBUG_ASSERT(!concurrent_insert ||
info->lock.type == TL_WRITE_CONCURRENT_INSERT);
if (concurrent_insert || !info->autocommit)
{
info->row_flag|= ROW_FLAG_TRANSID;
info->row_base_length+= TRANSID_SIZE;
}
else
{
DBUG_ASSERT(info->lock.type != TL_WRITE_CONCURRENT_INSERT);
}
DBUG_RETURN(0 );
}
my_bool _ma_block_start_trans(void * param)
{
MARIA_HA *info=(MARIA_HA*) param;
DBUG_ENTER("_ma_block_start_trans" );
if (info->s->lock_key_trees)
{
/*
Assume for now that this doesn ' t fail ( It can only fail in
out of memory conditions )
TODO : Fix this by having one extra state pre - allocated
*/
DBUG_RETURN(_ma_setup_live_state(info));
}
else
{
/*
We come here in the following cases :
- The table is a temporary table
- It ' s a table which is crash safe but not yet versioned , for
example a table with fulltext or rtree keys
Set the current state to point to save_state so that the
block_format code don ' t count the same record twice .
Copy also the current state . This may have been wrong if the
same file was used several times in the last statement
*/
info->state= info->state_start;
*info->state= info->s->state.state;
}
/*
Info - > trn is set if this table is already handled and we are
called from maria_versioning ( )
*/
if (info->s->base.born_transactional && !info->trn)
{
/*
Assume for now that this doesn ' t fail ( It can only fail in
out of memory conditions )
*/
DBUG_RETURN(maria_create_trn_hook(info) != 0 );
}
DBUG_RETURN(0 );
}
void _ma_block_update_status(void *param __attribute__((unused)))
{
}
void _ma_block_restore_status(void *param __attribute__((unused)))
{
}
/**
Check if should allow concurrent inserts
@ return
@ retval 0 ok to use concurrent inserts
@ retval 1 not ok
*/
my_bool _ma_block_check_status(void *param __attribute__((unused)))
{
return (my_bool) 0 ;
}
/* Get status when transactional but not versioned */
my_bool _ma_block_start_trans_no_versioning(void * param)
{
MARIA_HA *info=(MARIA_HA*) param;
DBUG_ENTER("_ma_block_start_trans_no_versioning" );
DBUG_ASSERT(info->s->base.born_transactional && !info->s->lock_key_trees);
info->state->changed= 0 ; /* from _ma_reset_update_flag() */
info->state= info->state_start;
*info->state= info->s->state.state;
if (!info->trn)
{
/*
Assume for now that this doesn ' t fail ( It can only fail in
out of memory conditions )
*/
DBUG_RETURN(maria_create_trn_hook(info));
}
DBUG_RETURN(0 );
}
/**
Enable / disable versioning
*/
void maria_versioning(MARIA_HA *info, my_bool versioning)
{
MARIA_SHARE *share= info->s;
DBUG_ENTER("maria_versioning" );
/* For now, this is a hack */
if (share->have_versioning)
{
enum thr_lock_type save_lock_type;
share->lock_key_trees= versioning;
/* Set up info->lock.type temporary for _ma_block_get_status() */
save_lock_type= info->lock.type;
info->lock.type= versioning ? TL_WRITE_CONCURRENT_INSERT : TL_WRITE;
_ma_block_get_status((void *) info, versioning);
info->lock.type= save_lock_type;
if (versioning)
info->state= &share->state.common;
else
info->state= &share->state.state; /* Change global values by default */
info->state_start= info->state; /* Initial values */
}
DBUG_VOID_RETURN;
}
/**
Update data_file_length to new length
NOTES
Only used by block records
*/
int _ma_set_share_data_file_length(MARIA_HA *info, ulonglong new_length)
{
MARIA_SHARE *share= info->s;
my_bool updated= 0 ;
if (!share->internal_table)
mysql_mutex_lock(&share->intern_lock);
if (share->state.state.data_file_length < new_length)
{
updated= share->tracked;
share->state.state.data_file_length= new_length;
if (new_length >= share->base.max_data_file_length)
{
/* Give an error on next insert */
share->state.changed|= STATE_DATA_FILE_FULL;
}
}
if (!share->internal_table)
mysql_mutex_unlock(&share->intern_lock);
else
{
if (updated &&
_ma_update_tmp_file_size(&share->track_data,
share->state.state.data_file_length))
{
share->state.changed|= STATE_DATA_FILE_FULL;
return 1 ;
}
}
return 0 ;
}
/**
Copy state information that where updated while the table was used
in not transactional mode
*/
void _ma_copy_nontrans_state_information(MARIA_HA *info)
{
info->s->state.state.records= info->state->records;
info->s->state.state.checksum= info->state->checksum;
}
/**
Reset history
This is only called during repair when we are the only one using the table .
*/
void _ma_reset_history(MARIA_SHARE *share)
{
MARIA_STATE_HISTORY *history, *next;
DBUG_ENTER("_ma_reset_history" );
share->state_history->trid= 0 ; /* Visibly by all */
share->state_history->state= share->state.state;
history= share->state_history->next;
share->state_history->next= 0 ;
for (; history; history= next)
{
next= history->next;
my_free(history);
}
DBUG_VOID_RETURN;
}
/****************************************************************************
Virtual functions to check if row is visible
****************************************************************************/
/**
Row is always visible
This is for tables without concurrent insert
*/
my_bool _ma_row_visible_always(MARIA_HA *info __attribute__((unused)))
{
return 1 ;
}
/**
Row visibility for non transactional tables with concurrent insert
@ implementation
When we got our table lock , we saved the current
data_file_length . Concurrent inserts always go to the end of the
file . So we can test if the found key references a new record .
*/
my_bool _ma_row_visible_non_transactional_table(MARIA_HA *info)
{
return info->cur_row.lastpos < info->state->data_file_length;
}
/**
Row visibility for transactional tables with versioning
@ TODO
Add test if found key was marked deleted and it was deleted by
us . In that case we should return 0
*/
my_bool _ma_row_visible_transactional_table(MARIA_HA *info)
{
return trnman_can_read_from(info->trn, info->cur_row.trid);
}
Messung V0.5 in Prozent C=94 H=85 G=89
¤ Dauer der Verarbeitung: 0.23 Sekunden
(vorverarbeitet am 2026-10-08)
¤
*© Formatika GbR, Deutschland