[Commits] 6d20f95: MDEV-8931: (server part of) session state tracking

Oleksandr Byelkin sanja at mariadb.com
Mon May 30 22:22:50 EEST 2016


revision-id: 6d20f95d18339fc2c30cb36c252f8d8b1d99dd19 (mariadb-10.2.0-57-g6d20f95)
parent(s): a640bc6c01f2dc8de2d24e4ba0348d7c432009e2
committer: Oleksandr Byelkin
timestamp: 2016-05-30 21:22:50 +0200
message:

MDEV-8931: (server part of) session state tracking

Transaction tracker

---
 mysql-test/r/mysqld--help.result |  10 +
 sql/lock.cc                      |  37 +++
 sql/session_tracker.cc           | 528 ++++++++++++++++++++++++++++++++++++++-
 sql/session_tracker.h            | 122 +++++++++
 sql/sp_head.cc                   |   5 +
 sql/sql_base.cc                  |  19 ++
 sql/sql_cache.cc                 |  25 ++
 sql/sql_class.h                  |   5 +-
 sql/sql_parse.cc                 |  15 +-
 sql/sys_vars.cc                  |  38 +++
 sql/sys_vars.ic                  |  36 +++
 sql/transaction.cc               |  79 +++++-
 sql/transaction.h                |   2 +
 13 files changed, 902 insertions(+), 19 deletions(-)

diff --git a/mysql-test/r/mysqld--help.result b/mysql-test/r/mysqld--help.result
index 6a8a4e8..be6893c 100644
--- a/mysql-test/r/mysqld--help.result
+++ b/mysql-test/r/mysqld--help.result
@@ -910,6 +910,15 @@ The following options may be given as the first argument:
  Track changes to the 'session state'.
  --session-track-system-variables=name 
  Track changes in registered system variables.
+ --session-track-transaction-info=name 
+ Track changes to the transaction attributes. OFF to
+ disable; STATE to track just transaction state (Is there
+ an active transaction? Does it have any data? etc.);
+ CHARACTERISTICS to track transaction state and report all
+ statements needed to start a transaction with the same
+ characteristics (isolation level, read only/read write,
+ snapshot - but not any work done / data modified within
+ the transaction).
  --show-slave-auth-info 
  Show user and password in SHOW SLAVE HOSTS on this
  master.
@@ -1397,6 +1406,7 @@ server-id 0
 session-track-schema TRUE
 session-track-state-change FALSE
 session-track-system-variables autocommit,character_set_client,character_set_connection,character_set_results,time_zone
+session-track-transaction-info OFF
 show-slave-auth-info FALSE
 silent-startup FALSE
 skip-grant-tables TRUE
diff --git a/sql/lock.cc b/sql/lock.cc
index 2e44786..e2d2da0 100644
--- a/sql/lock.cc
+++ b/sql/lock.cc
@@ -89,6 +89,7 @@ extern HASH open_cache;
 
 static int lock_external(THD *thd, TABLE **table,uint count);
 static int unlock_external(THD *thd, TABLE **table,uint count);
+static void track_table_access(THD *thd, TABLE **tables, size_t count);
 
 /* Map the return value of thr_lock to an error from errmsg.txt */
 static int thr_lock_errno_to_mysql[]=
@@ -244,6 +245,39 @@ void reset_lock_data(MYSQL_LOCK *sql_lock, bool unlock)
 
 
 /**
+  Scan array of tables for access types; update transaction tracker
+  accordingly.
+
+   @param thd          The current thread.
+   @param tables       An array of pointers to the tables to lock.
+   @param count        The number of tables to lock.
+*/
+
+static void track_table_access(THD *thd, TABLE **tables, size_t count)
+{
+  if (thd->variables.session_track_transaction_info > TX_TRACK_NONE)
+  {
+    Transaction_state_tracker *tst= (Transaction_state_tracker *)
+      thd->session_tracker.get_tracker(TRANSACTION_INFO_TRACKER);
+    enum enum_tx_state         s;
+
+    while (count--)
+    {
+      TABLE *t= tables[count];
+
+      if (t)
+      {
+        s= tst->calc_trx_state(thd, t->reginfo.lock_type,
+                               t->file->has_transactions());
+        tst->add_trx_state(thd, s);
+      }
+    }
+  }
+}
+
+
+
+/**
    Lock tables.
 
    @param thd          The current thread.
@@ -280,6 +314,9 @@ MYSQL_LOCK *mysql_lock_tables(THD *thd, TABLE **tables, uint count, uint flags)
       my_free(sql_lock);
     sql_lock= 0;
   }
+
+  track_table_access(thd, tables, count);
+
   DBUG_RETURN(sql_lock);
 }
 
diff --git a/sql/session_tracker.cc b/sql/session_tracker.cc
index cfbb170..ae7be5f 100644
--- a/sql/session_tracker.cc
+++ b/sql/session_tracker.cc
@@ -907,6 +907,531 @@ void Current_schema_tracker::reset()
 
 ///////////////////////////////////////////////////////////////////////////////
 
+
+Transaction_state_tracker::Transaction_state_tracker()
+{
+  m_enabled        = false;
+  tx_changed       = TX_CHG_NONE;
+  tx_curr_state    =
+  tx_reported_state= TX_EMPTY;
+  tx_read_flags    = TX_READ_INHERIT;
+  tx_isol_level    = TX_ISOL_INHERIT;
+}
+
+/**
+  Enable/disable the tracker based on @@session_track_transaction_info.
+
+  @param thd [IN]           The thd handle.
+
+  @retval true if updating the tracking level failed
+  @retval false otherwise
+*/
+
+bool Transaction_state_tracker::update(THD *thd)
+{
+#ifdef EMBEDDED_LIBRARY
+  return true;
+
+#else
+  if (thd->variables.session_track_transaction_info != TX_TRACK_NONE)
+  {
+    /*
+      If we only just turned reporting on (rather than changing between
+      state and characteristics reporting), start from a defined state.
+    */
+    if (!m_enabled)
+    {
+      tx_curr_state     =
+      tx_reported_state = TX_EMPTY;
+      tx_changed       |= TX_CHG_STATE;
+      m_enabled= true;
+    }
+    if (thd->variables.session_track_transaction_info == TX_TRACK_CHISTICS)
+      tx_changed       |= TX_CHG_CHISTICS;
+    mark_as_changed(thd, NULL);
+  }
+  else
+    m_enabled= false;
+
+  return false;
+#endif
+}
+
+
+/**
+  Store the transaction state (and, optionally, characteristics)
+  as length-encoded string in the specified buffer.  Once the data
+  is stored, we reset the flags related to state-change (see reset()).
+
+
+  @param thd [IN]           The thd handle.
+  @paran buf [INOUT]        Buffer to store the information to.
+
+  @retval false Success
+  @retval true  Error
+*/
+
+static LEX_CSTRING isol[]= {
+  { STRING_WITH_LEN("READ UNCOMMITTED") },
+  { STRING_WITH_LEN("READ COMMITTED") },
+  { STRING_WITH_LEN("REPEATABLE READ") },
+  { STRING_WITH_LEN("SERIALIZABLE") }
+};
+
+bool Transaction_state_tracker::store(THD *thd, String *buf)
+{
+  /* STATE */
+  if (tx_changed & TX_CHG_STATE)
+  {
+    uchar *to= (uchar *) buf->prep_append(11, EXTRA_ALLOC);
+
+    to= net_store_length((uchar *) to,
+                         (ulonglong) SESSION_TRACK_TRANSACTION_STATE);
+
+    to= net_store_length((uchar *) to, (ulonglong) 9);
+    to= net_store_length((uchar *) to, (ulonglong) 8);
+
+    *(to++)=  (tx_curr_state & TX_EXPLICIT)       ? 'T' :
+             ((tx_curr_state & TX_IMPLICIT)       ? 'I' : '_');
+    *(to++)=  (tx_curr_state & TX_READ_UNSAFE)    ? 'r' : '_';
+    *(to++)= ((tx_curr_state & TX_READ_TRX) ||
+              (tx_curr_state & TX_WITH_SNAPSHOT)) ? 'R' : '_';
+    *(to++)=  (tx_curr_state & TX_WRITE_UNSAFE)   ? 'w' : '_';
+    *(to++)=  (tx_curr_state & TX_WRITE_TRX)      ? 'W' : '_';
+    *(to++)=  (tx_curr_state & TX_STMT_UNSAFE)    ? 's' : '_';
+    *(to++)=  (tx_curr_state & TX_RESULT_SET)     ? 'S' : '_';
+    *(to++)=  (tx_curr_state & TX_LOCKED_TABLES)  ? 'L' : '_';
+  }
+
+  /* CHARACTERISTICS -- How to restart the transaction */
+
+  if ((thd->variables.session_track_transaction_info == TX_TRACK_CHISTICS) &&
+      (tx_changed & TX_CHG_CHISTICS))
+  {
+    bool is_xa= (thd->transaction.xid_state.xa_state != XA_NOTR);
+    size_t start;
+
+    /* 2 length by 1 byte and code */
+    buf->prep_alloc(1 + 1 + 1, EXTRA_ALLOC);
+
+    /* Session state type (SESSION_TRACK_TRANSACTION_CHARACTERISTICS) */
+    buf->q_net_store_length((ulonglong)
+                            SESSION_TRACK_TRANSACTION_CHARACTERISTICS);
+    compile_time_assert(SESSION_TRACK_TRANSACTION_CHARACTERISTICS < 251);
+
+    /* Whole length: Track result will fit in 251 byte (in worst case 110) */
+    buf->append('\0');
+
+    /* String length: Track result will fit in 251 byte (in worst case 110) */
+    buf->append('\0');
+
+    start= buf->length();
+
+    {
+      /*
+        We have four basic replay scenarios:
+
+        a) SET TRANSACTION was used, but before an actual transaction
+           was started, the load balancer moves the connection elsewhere.
+           In that case, the same one-shots should be set up in the
+           target session.  (read-only/read-write; isolation-level)
+
+        b) The initial transaction has begun; the relevant characteristics
+           are the session defaults, possibly overridden by previous
+           SET TRANSACTION statements, possibly overridden or extended
+           by options passed to the START TRANSACTION statement.
+           If the load balancer wishes to move this transaction,
+           it needs to be replayed with the correct characteristics.
+           (read-only/read-write from SET or START;
+           isolation-level from SET only, snapshot from START only)
+
+        c) A subsequent transaction started with START TRANSACTION
+           (which is legal syntax in lieu of COMMIT AND CHAIN in MySQL)
+           may add/modify the current one-shots:
+
+           - It may set up a read-only/read-write one-shot.
+             This one-shot will override the value used in the previous
+             transaction (whether that came from the default or a one-shot),
+             and, like all one-shots currently do, it will carry over into
+             any subsequent transactions that don't explicitly override them
+             in turn. This behavior is not guaranteed in the docs and may
+             change in the future, but the tracker item should correctly
+             reflect whatever behavior a given version of mysqld implements.
+
+           - It may also set up a WITH CONSISTENT SNAPSHOT one-shot.
+             This one-shot does not currently carry over into subsequent
+             transactions (meaning that with "traditional syntax", WITH
+             CONSISTENT SNAPSHOT can only be requested for the first part
+             of a transaction chain). Again, the tracker item should reflect
+             mysqld behavior.
+
+        d) A subsequent transaction started using COMMIT AND CHAIN
+           (or, for that matter, BEGIN WORK, which is currently
+           legal and equivalent syntax in MySQL, or START TRANSACTION
+           sans options) will re-use any one-shots set up so far
+           (with SET before the first transaction started, and with
+           all subsequent STARTs), except for WITH CONSISTANT SNAPSHOT,
+           which will never be chained and only applies when explicitly
+           given.
+
+        It bears noting that if we switch sessions in a follow-up
+        transaction, SET TRANSACTION would be illegal in the old
+        session (as a transaction is active), whereas in the target
+        session which is being prepared, it should be legal, as no
+        transaction (chain) should have started yet.
+
+        Therefore, we are free to generate SET TRANSACTION as a replay
+        statement even for a transaction that isn't the first in an
+        ongoing chain. Consider
+
+          SET TRANSACTION ISOLATION LEVEL READ UNCOMMITED;
+          START TRANSACTION READ ONLY, WITH CONSISTENT SNAPSHOT;
+          # work
+          COMMIT AND CHAIN;
+
+        If we switch away at this point, the replay in the new session
+        needs to be
+
+          SET TRANSACTION ISOLATION LEVEL READ UNCOMMITED;
+          START TRANSACTION READ ONLY;
+
+        When a transaction ends (COMMIT/ROLLBACK sans CHAIN), all
+        per-transaction characteristics are reset to the session's
+        defaults.
+
+        This also holds for a transaction ended implicitly!  (transaction.cc)
+        Once again, the aim is to have the tracker item reflect on a
+        given mysqld's actual behavior.
+      */
+
+      /*
+        "ISOLATION LEVEL"
+        Only legal in SET TRANSACTION, so will always be replayed as such.
+      */
+      if (tx_isol_level != TX_ISOL_INHERIT)
+      {
+        /*
+          Unfortunately, we can't re-use tx_isolation_names /
+          tx_isolation_typelib as it hyphenates its items.
+        */
+        buf->append(STRING_WITH_LEN("SET TRANSACTION ISOLATION LEVEL "));
+        buf->append(isol[tx_isol_level - 1].str, isol[tx_isol_level - 1].length);
+        buf->append(STRING_WITH_LEN("; "));
+      }
+
+      /*
+        Start transaction will usually result in TX_EXPLICIT (transaction
+        started, but no data attached yet), except when WITH CONSISTENT
+        SNAPSHOT, in which case we may have data pending.
+        If it's an XA transaction, we don't go through here so we can
+        first print the trx access mode ("SET TRANSACTION READ ...")
+        separately before adding XA START (whereas with START TRANSACTION,
+        we can merge the access mode into the same statement).
+      */
+      if ((tx_curr_state & TX_EXPLICIT) && !is_xa)
+      {
+        buf->append(STRING_WITH_LEN("START TRANSACTION"));
+
+        /*
+          "WITH CONSISTENT SNAPSHOT"
+          Defaults to no, can only be enabled.
+          Only appears in START TRANSACTION.
+        */
+        if (tx_curr_state & TX_WITH_SNAPSHOT)
+        {
+          buf->append(STRING_WITH_LEN(" WITH CONSISTENT SNAPSHOT"));
+          if (tx_read_flags != TX_READ_INHERIT)
+            buf->append(STRING_WITH_LEN(","));
+        }
+
+        /*
+          "READ WRITE / READ ONLY" can be set globally, per-session,
+          or just for one transaction.
+
+          The latter case can take the form of
+          START TRANSACTION READ (WRITE|ONLY), or of
+          SET TRANSACTION READ (ONLY|WRITE).
+          (Both set thd->read_only for the upcoming transaction;
+          it will ultimately be re-set to the session default.)
+
+          As the regular session-variable tracker does not monitor the one-shot,
+          we'll have to do it here.
+
+          If READ is flagged as set explicitly (rather than just inherited
+          from the session's default), we'll get the actual bool from the THD.
+        */
+        if (tx_read_flags != TX_READ_INHERIT)
+        {
+          if (tx_read_flags == TX_READ_ONLY)
+            buf->append(STRING_WITH_LEN(" READ ONLY"));
+          else
+            buf->append(STRING_WITH_LEN(" READ WRITE"));
+        }
+        buf->append(STRING_WITH_LEN("; "));
+      }
+      else if (tx_read_flags != TX_READ_INHERIT)
+      {
+        /*
+          "READ ONLY" / "READ WRITE"
+          We could transform this to SET TRANSACTION even when it occurs
+          in START TRANSACTION, but for now, we'll resysynthesize the original
+          command as closely as possible.
+        */
+        buf->append(STRING_WITH_LEN("SET TRANSACTION "));
+        if (tx_read_flags == TX_READ_ONLY)
+          buf->append(STRING_WITH_LEN("READ ONLY; "));
+        else
+          buf->append(STRING_WITH_LEN("READ WRITE; "));
+      }
+
+      if ((tx_curr_state & TX_EXPLICIT) && is_xa)
+      {
+        XID *xid= &thd->transaction.xid_state.xid;
+        long glen, blen;
+
+        buf->append(STRING_WITH_LEN("XA START"));
+
+        if ((glen= xid->gtrid_length) > 0)
+        {
+          buf->append(STRING_WITH_LEN(" '"));
+          buf->append(xid->data, glen);
+
+          if ((blen= xid->bqual_length) > 0)
+          {
+            buf->append(STRING_WITH_LEN("','"));
+            buf->append(xid->data + glen, blen);
+          }
+          buf->append(STRING_WITH_LEN("'"));
+
+          if (xid->formatID != 1)
+          {
+            buf->append(STRING_WITH_LEN(","));
+            buf->append_ulonglong(xid->formatID);
+          }
+        }
+
+        buf->append(STRING_WITH_LEN("; "));
+      }
+
+      // discard trailing space
+      if (buf->length() > start)
+        buf->length(buf->length() - 1);
+    }
+
+    {
+      ulonglong length= buf->length() - start;
+      uchar *place= (uchar *)(buf->ptr() + (start - 2));
+      DBUG_ASSERT(length < 249); // in fact < 110
+      DBUG_ASSERT(start >= 3);
+
+      DBUG_ASSERT((place - 1)[0] == SESSION_TRACK_TRANSACTION_CHARACTERISTICS);
+      /* Length of the overall entity. */
+      place[0]= length + 1;
+      /* Transaction characteristics (length-encoded string). */
+      place[1]= length;
+    }
+  }
+
+  reset();
+
+  return false;
+}
+
+
+/**
+  Mark the tracker as changed.
+*/
+
+void Transaction_state_tracker::mark_as_changed(THD *thd,
+                                                LEX_CSTRING *tracked_item_nam)
+{
+  m_changed= true;
+  thd->lex->safe_to_cache_query= 0;
+  thd->server_status|= SERVER_SESSION_STATE_CHANGED;
+}
+
+
+/**
+  Reset the m_changed flag for next statement.
+*/
+
+void Transaction_state_tracker::reset()
+{
+  m_changed=  false;
+  tx_reported_state=  tx_curr_state;
+  tx_changed=  TX_CHG_NONE;
+}
+
+
+/**
+  Helper function: turn table info into table access flag.
+  Accepts table lock type and engine type flag (transactional/
+  non-transactional), and returns the corresponding access flag
+  out of TX_READ_TRX, TX_READ_UNSAFE, TX_WRITE_TRX, TX_WRITE_UNSAFE.
+
+  @param thd [IN]           The thd handle
+  @param set [IN]           The table's access/lock type
+  @param set [IN]           Whether the table's engine is transactional
+
+  @return                   The table access flag
+*/
+
+enum_tx_state Transaction_state_tracker::calc_trx_state(THD *thd,
+                                                        thr_lock_type l,
+                                                        bool has_trx)
+{
+  enum_tx_state      s;
+  bool               read= (l <= TL_READ_NO_INSERT);
+
+  if (read)
+    s= has_trx ? TX_READ_TRX  : TX_READ_UNSAFE;
+  else
+    s= has_trx ? TX_WRITE_TRX : TX_WRITE_UNSAFE;
+
+  return s;
+}
+
+
+/**
+  Register the end of an (implicit or explicit) transaction.
+
+  @param thd [IN]           The thd handle
+*/
+void Transaction_state_tracker::end_trx(THD *thd)
+{
+  DBUG_ASSERT(thd->variables.session_track_transaction_info > TX_TRACK_NONE);
+
+  if ((!m_enabled) || (thd->state_flags & Open_tables_state::BACKUPS_AVAIL))
+    return;
+
+  if (tx_curr_state != TX_EMPTY)
+  {
+    if (tx_curr_state & TX_EXPLICIT)
+      tx_changed  |= TX_CHG_CHISTICS;
+    tx_curr_state &= TX_LOCKED_TABLES;
+  }
+  update_change_flags(thd);
+}
+
+
+/**
+  Clear flags pertaining to the current statement or transaction.
+  May be called repeatedly within the same execution cycle.
+
+  @param thd [IN]           The thd handle.
+  @param set [IN]           The flags to clear
+*/
+
+void Transaction_state_tracker::clear_trx_state(THD *thd, uint clear)
+{
+  if ((!m_enabled) || (thd->state_flags & Open_tables_state::BACKUPS_AVAIL))
+    return;
+
+  tx_curr_state &= ~clear;
+  update_change_flags(thd);
+}
+
+
+/**
+  Add flags pertaining to the current statement or transaction.
+  May be called repeatedly within the same execution cycle,
+  e.g. to add access info for more tables.
+
+  @param thd [IN]           The thd handle.
+  @param set [IN]           The flags to add
+*/
+
+void Transaction_state_tracker::add_trx_state(THD *thd, uint add)
+{
+  if ((!m_enabled) || (thd->state_flags & Open_tables_state::BACKUPS_AVAIL))
+    return;
+
+  if (add == TX_EXPLICIT)
+  {
+    /* Always send characteristic item (if tracked), always replace state. */
+    tx_changed |= TX_CHG_CHISTICS;
+    tx_curr_state = TX_EXPLICIT;
+  }
+
+  /*
+    If we're not in an implicit or explicit transaction, but
+    autocommit==0 and tables are accessed, we flag "implicit transaction."
+  */
+  else if (!(tx_curr_state & (TX_EXPLICIT|TX_IMPLICIT)) &&
+           (thd->variables.option_bits & OPTION_NOT_AUTOCOMMIT) &&
+           (add &
+            (TX_READ_TRX | TX_READ_UNSAFE | TX_WRITE_TRX | TX_WRITE_UNSAFE)))
+    tx_curr_state |= TX_IMPLICIT;
+
+  /*
+    Only flag state when in transaction or LOCK TABLES is added.
+  */
+  if ((tx_curr_state & (TX_EXPLICIT | TX_IMPLICIT)) ||
+      (add & TX_LOCKED_TABLES))
+    tx_curr_state |= add;
+
+  update_change_flags(thd);
+}
+
+
+/**
+  Add "unsafe statement" flag if applicable.
+
+  @param thd [IN]           The thd handle.
+  @param set [IN]           The flags to add
+*/
+
+void Transaction_state_tracker::add_trx_state_from_thd(THD *thd)
+{
+  if (m_enabled)
+  {
+    if (thd->lex->is_stmt_unsafe())
+      add_trx_state(thd, TX_STMT_UNSAFE);
+  }
+}
+
+
+/**
+  Set read flags (read only/read write) pertaining to the next
+  transaction.
+
+  @param thd [IN]           The thd handle.
+  @param set [IN]           The flags to set
+*/
+
+void Transaction_state_tracker::set_read_flags(THD *thd,
+                                               enum enum_tx_read_flags flags)
+{
+  if (m_enabled && (tx_read_flags != flags))
+  {
+    tx_read_flags = flags;
+    tx_changed   |= TX_CHG_CHISTICS;
+    mark_as_changed(thd, NULL);
+  }
+}
+
+
+/**
+  Set isolation level pertaining to the next transaction.
+
+  @param thd [IN]           The thd handle.
+  @param set [IN]           The isolation level to set
+*/
+
+void Transaction_state_tracker::set_isol_level(THD *thd,
+                                               enum enum_tx_isol_level level)
+{
+  if (m_enabled && (tx_isol_level != level))
+  {
+    tx_isol_level = level;
+    tx_changed   |= TX_CHG_CHISTICS;
+    mark_as_changed(thd, NULL);
+  }
+}
+
+
+///////////////////////////////////////////////////////////////////////////////
+
 Session_state_change_tracker::Session_state_change_tracker()
 {
   m_changed= false;
@@ -1024,7 +1549,7 @@ void Session_tracker::enable(THD *thd)
   m_trackers[SESSION_GTIDS_TRACKER]=
     new (std::nothrow) Not_implemented_tracker;
   m_trackers[TRANSACTION_INFO_TRACKER]=
-    new (std::nothrow) Not_implemented_tracker;
+    new (std::nothrow) Transaction_state_tracker;
 
   for (int i= 0; i <= SESSION_TRACKER_END; i ++)
     m_trackers[i]->enable(thd);
@@ -1067,7 +1592,6 @@ bool Session_tracker::server_boot_verify(const CHARSET_INFO *char_set)
 
 void Session_tracker::store(THD *thd, String *buf)
 {
-  /* Temporary buffer to store all the changes. */
   size_t start;
 
   /*
diff --git a/sql/session_tracker.h b/sql/session_tracker.h
index 7025c34..5d529b8 100644
--- a/sql/session_tracker.h
+++ b/sql/session_tracker.h
@@ -174,4 +174,126 @@ class Session_tracker
   void store(THD *thd, String *main_buf);
 };
 
+
+/*
+  Transaction_state_tracker
+*/
+
+/**
+  Transaction state (no transaction, transaction active, work attached, etc.)
+*/
+enum enum_tx_state {
+  TX_EMPTY        =   0,  ///< "none of the below"
+  TX_EXPLICIT     =   1,  ///< an explicit transaction is active
+  TX_IMPLICIT     =   2,  ///< an implicit transaction is active
+  TX_READ_TRX     =   4,  ///<     transactional reads  were done
+  TX_READ_UNSAFE  =   8,  ///< non-transaction   reads  were done
+  TX_WRITE_TRX    =  16,  ///<     transactional writes were done
+  TX_WRITE_UNSAFE =  32,  ///< non-transactional writes were done
+  TX_STMT_UNSAFE  =  64,  ///< "unsafe" (non-deterministic like UUID()) stmts
+  TX_RESULT_SET   = 128,  ///< result-set was sent
+  TX_WITH_SNAPSHOT= 256,  ///< WITH CONSISTENT SNAPSHOT was used
+  TX_LOCKED_TABLES= 512   ///< LOCK TABLES is active
+};
+
+
+/**
+  Transaction access mode
+*/
+enum enum_tx_read_flags {
+  TX_READ_INHERIT =   0,  ///< not explicitly set, inherit session.tx_read_only
+  TX_READ_ONLY    =   1,  ///< START TRANSACTION READ ONLY,  or tx_read_only=1
+  TX_READ_WRITE   =   2,  ///< START TRANSACTION READ WRITE, or tx_read_only=0
+};
+
+
+/**
+  Transaction isolation level
+*/
+enum enum_tx_isol_level {
+  TX_ISOL_INHERIT     = 0, ///< not explicitly set, inherit session.tx_isolation
+  TX_ISOL_UNCOMMITTED = 1,
+  TX_ISOL_COMMITTED   = 2,
+  TX_ISOL_REPEATABLE  = 3,
+  TX_ISOL_SERIALIZABLE= 4
+};
+
+
+/**
+  Transaction tracking level
+*/
+enum enum_session_track_transaction_info {
+  TX_TRACK_NONE      = 0,  ///< do not send tracker items on transaction info
+  TX_TRACK_STATE     = 1,  ///< track transaction status
+  TX_TRACK_CHISTICS  = 2   ///< track status and characteristics
+};
+
+
+/**
+  This is a tracker class that enables & manages the tracking of
+  current transaction info for a particular connection.
+*/
+
+class Transaction_state_tracker : public State_tracker
+{
+public:
+  /** Constructor */
+  Transaction_state_tracker();
+  bool enable(THD *thd)
+  { return update(thd); }
+  bool check(THD *thd, set_var *var)
+  { return false; }
+  bool update(THD *thd);
+  bool store(THD *thd, String *buf);
+  void mark_as_changed(THD *thd, LEX_CSTRING *tracked_item_name);
+
+  /** Change transaction characteristics */
+  void set_read_flags(THD *thd, enum enum_tx_read_flags flags);
+  void set_isol_level(THD *thd, enum enum_tx_isol_level level);
+
+  /** Change transaction state */
+  void clear_trx_state(THD *thd, uint clear);
+  void add_trx_state(THD *thd, uint add);
+  void add_trx_state_from_thd(THD *thd);
+  void end_trx(THD *thd);
+
+  /** Helper function: turn table info into table access flag */
+  enum_tx_state calc_trx_state(THD *thd, thr_lock_type l, bool has_trx);
+
+private:
+  enum enum_tx_changed {
+    TX_CHG_NONE     = 0,  ///< no changes from previous stmt
+    TX_CHG_STATE    = 1,  ///< state has changed from previous stmt
+    TX_CHG_CHISTICS = 2   ///< characteristics have changed from previous stmt
+  };
+
+  /** any trackable changes caused by this statement? */
+  uint                     tx_changed;
+
+  /** transaction state */
+  uint                     tx_curr_state,  tx_reported_state;
+
+  /** r/w or r/o set? session default? */
+  enum enum_tx_read_flags  tx_read_flags;
+
+  /**  isolation level */
+  enum enum_tx_isol_level  tx_isol_level;
+
+  void reset();
+
+  inline void update_change_flags(THD *thd)
+  {
+    tx_changed &= ~TX_CHG_STATE;
+    tx_changed |= (tx_curr_state != tx_reported_state) ? TX_CHG_STATE : 0;
+    if (tx_changed != TX_CHG_NONE)
+      mark_as_changed(thd, NULL);
+  }
+};
+
+#define TRANSACT_TRACKER(X) \
+ do { if (thd->variables.session_track_transaction_info > TX_TRACK_NONE) \
+   {((Transaction_state_tracker *) \
+         thd->session_tracker.get_tracker(TRANSACTION_INFO_TRACKER)) \
+          ->X; } } while(0)
+
 #endif /* SESSION_TRACKER_INCLUDED */
diff --git a/sql/sp_head.cc b/sql/sp_head.cc
index 3f9075c..396f308 100644
--- a/sql/sp_head.cc
+++ b/sql/sp_head.cc
@@ -2046,6 +2046,8 @@ sp_head::execute_procedure(THD *thd, List<Item> *args)
           break;
         }
       }
+
+      TRANSACT_TRACKER(add_trx_state_from_thd(thd));
     }
 
     /*
@@ -3061,6 +3063,9 @@ sp_lex_keeper::reset_lex_and_exec_core(THD *thd, uint *nextp,
     what is needed from the substatement gained
   */
   thd->transaction.stmt.modified_non_trans_table |= parent_modified_non_trans_table;
+
+  TRANSACT_TRACKER(add_trx_state_from_thd(thd));
+
   /*
     Unlike for PS we should not call Item's destructors for newly created
     items after execution of each instruction in stored routine. This is
diff --git a/sql/sql_base.cc b/sql/sql_base.cc
index 08a9647..924e9d4 100644
--- a/sql/sql_base.cc
+++ b/sql/sql_base.cc
@@ -2755,6 +2755,9 @@ Locked_tables_list::init_locked_tables(THD *thd)
       return TRUE;
     }
   }
+
+  TRANSACT_TRACKER(add_trx_state(thd, TX_LOCKED_TABLES));
+
   thd->enter_locked_tables_mode(LTM_LOCK_TABLES);
 
   return FALSE;
@@ -2795,6 +2798,8 @@ Locked_tables_list::unlock_locked_tables(THD *thd)
   }
   thd->leave_locked_tables_mode();
 
+  TRANSACT_TRACKER(clear_trx_state(thd, TX_LOCKED_TABLES));
+
   DBUG_ASSERT(thd->transaction.stmt.is_empty());
   close_thread_tables(thd);
 
@@ -5013,6 +5018,20 @@ static bool check_lock_and_start_stmt(THD *thd,
     table_list->table->file->print_error(error, MYF(0));
     DBUG_RETURN(1);
   }
+
+  /*
+    Record in transaction state tracking
+  */
+  if (thd->variables.session_track_transaction_info > TX_TRACK_NONE)
+  {
+    Transaction_state_tracker *tst= (Transaction_state_tracker *)
+      thd->session_tracker.get_tracker(TRANSACTION_INFO_TRACKER);
+    enum enum_tx_state s=
+      tst->calc_trx_state(thd, lock_type,
+                          table_list->table->file->has_transactions());
+    tst->add_trx_state(thd, s);
+  }
+
   DBUG_RETURN(0);
 }
 
diff --git a/sql/sql_cache.cc b/sql/sql_cache.cc
index 42b80d9..b0dacca 100644
--- a/sql/sql_cache.cc
+++ b/sql/sql_cache.cc
@@ -1381,6 +1381,19 @@ void Query_cache::store_query(THD *thd, TABLE_LIST *tables_used)
     DBUG_VOID_RETURN;
   }
 
+  /*
+    Do not store queries while tracking transaction state.
+    The tracker already flags queries that actually have
+    transaction tracker items, but this will make behavior
+    more straight forward.
+  */
+  if (thd->variables.session_track_transaction_info != TX_TRACK_NONE)
+  {
+    DBUG_PRINT("qcache", ("Do not work with transaction tracking"));
+    DBUG_VOID_RETURN;
+  }
+
+
   /* The following assert fails if we haven't called send_result_to_client */
   DBUG_ASSERT(thd->base_query.is_alloced() ||
               thd->base_query.ptr() == thd->query());
@@ -1719,6 +1732,18 @@ Query_cache::send_result_to_client(THD *thd, char *org_sql, uint query_length)
     goto err;
   }
 
+  /*
+    Don't allow serving from Query_cache while tracking transaction
+    state. This is a safeguard in case an otherwise matching query
+    was added to the cache before tracking was turned on.
+  */
+  if (thd->variables.session_track_transaction_info != TX_TRACK_NONE)
+  {
+    DBUG_PRINT("qcache", ("Do not work with transaction tracking"));
+    goto err;
+  }
+
+
   thd->query_cache_is_applicable= 1;
   sql= org_sql; sql_end= sql + query_length;
 
diff --git a/sql/sql_class.h b/sql/sql_class.h
index bd289fd..c7e819f 100644
--- a/sql/sql_class.h
+++ b/sql/sql_class.h
@@ -682,10 +682,11 @@ typedef struct system_variables
 
   my_bool pseudo_slave_mode;
 
+  char *session_track_system_variables;
+  ulong session_track_transaction_info;
   my_bool session_track_schema;
   my_bool session_track_state_change;
 
-  char *session_track_system_variables;
 } SV;
 
 /**
@@ -4121,6 +4122,8 @@ my_eof(THD *thd)
 {
   thd->set_row_count_func(-1);
   thd->get_stmt_da()->set_eof_status(thd);
+
+  TRANSACT_TRACKER(add_trx_state(thd, TX_RESULT_SET));
 }
 
 #define tmp_disable_binlog(A)                                              \
diff --git a/sql/sql_parse.cc b/sql/sql_parse.cc
index 8618fe7..9bf39d9 100644
--- a/sql/sql_parse.cc
+++ b/sql/sql_parse.cc
@@ -5441,8 +5441,7 @@ mysql_execute_command(THD *thd)
     else
     {
       /* Reset the isolation level and access mode if no chaining transaction.*/
-      thd->tx_isolation= (enum_tx_isolation) thd->variables.tx_isolation;
-      thd->tx_read_only= thd->variables.tx_read_only;
+      trans_reset_one_shot_chistics(thd);
     }
     /* Disconnect the current client connection. */
     if (tx_release)
@@ -5489,8 +5488,7 @@ mysql_execute_command(THD *thd)
     else
     {
       /* Reset the isolation level and access mode if no chaining transaction.*/
-      thd->tx_isolation= (enum_tx_isolation) thd->variables.tx_isolation;
-      thd->tx_read_only= thd->variables.tx_read_only;
+      trans_reset_one_shot_chistics(thd);
     }
     /* Disconnect the current client connection. */
     if (tx_release)
@@ -5975,8 +5973,7 @@ mysql_execute_command(THD *thd)
       We've just done a commit, reset transaction
       isolation level and access mode to the session default.
     */
-    thd->tx_isolation= (enum_tx_isolation) thd->variables.tx_isolation;
-    thd->tx_read_only= thd->variables.tx_read_only;
+    trans_reset_one_shot_chistics(thd);
     my_ok(thd);
     break;
   }
@@ -5994,8 +5991,7 @@ mysql_execute_command(THD *thd)
       We've just done a rollback, reset transaction
       isolation level and access mode to the session default.
     */
-    thd->tx_isolation= (enum_tx_isolation) thd->variables.tx_isolation;
-    thd->tx_read_only= thd->variables.tx_read_only;
+    trans_reset_one_shot_chistics(thd);
     my_ok(thd);
     break;
   }
@@ -6213,6 +6209,9 @@ mysql_execute_command(THD *thd)
   {
     thd->mdl_context.release_statement_locks();
   }
+
+  TRANSACT_TRACKER(add_trx_state_from_thd(thd));
+
   WSREP_TO_ISOLATION_END;
 
 #ifdef WITH_WSREP
diff --git a/sql/sys_vars.cc b/sql/sys_vars.cc
index 9e58643..3be2e55 100644
--- a/sql/sys_vars.cc
+++ b/sql/sys_vars.cc
@@ -3361,6 +3361,18 @@ bool Sys_var_tx_read_only::session_update(THD *thd, set_var *var)
   {
     // @see Sys_var_tx_isolation::session_update() above for the rules.
     thd->tx_read_only= var->save_result.ulonglong_value;
+
+    if (thd->variables.session_track_transaction_info > TX_TRACK_NONE)
+    {
+      Transaction_state_tracker *tst= (Transaction_state_tracker *)
+             thd->session_tracker.get_tracker(TRANSACTION_INFO_TRACKER);
+
+      if (var->type == OPT_DEFAULT)
+        tst->set_read_flags(thd,
+                            thd->tx_read_only ? TX_READ_ONLY : TX_READ_WRITE);
+      else
+        tst->set_read_flags(thd, TX_READ_INHERIT);
+    }
   }
   return false;
 }
@@ -5393,6 +5405,32 @@ static Sys_var_mybool Sys_session_track_schema(
        ON_CHECK(0),
        ON_UPDATE(update_session_track_schema));
 
+
+static bool update_session_track_tx_info(sys_var *self, THD *thd,
+                                         enum_var_type type)
+{
+  DBUG_ENTER("update_session_track_tx_info");
+  DBUG_RETURN(thd->session_tracker.get_tracker(TRANSACTION_INFO_TRACKER)->update(thd));
+}
+
+static const char *session_track_transaction_info_names[]=
+  { "OFF", "STATE", "CHARACTERISTICS", NullS };
+
+static Sys_var_enum Sys_session_track_transaction_info(
+       "session_track_transaction_info",
+       "Track changes to the transaction attributes. OFF to disable; "
+       "STATE to track just transaction state (Is there an active transaction? "
+       "Does it have any data? etc.); CHARACTERISTICS to track transaction "
+       "state "
+       "and report all statements needed to start a transaction with the same "
+       "characteristics (isolation level, read only/read write, snapshot - "
+       "but not any work done / data modified within the transaction).",
+       SESSION_VAR(session_track_transaction_info),
+       CMD_LINE(REQUIRED_ARG), session_track_transaction_info_names,
+       DEFAULT(0), NO_MUTEX_GUARD, NOT_IN_BINLOG, ON_CHECK(0),
+       ON_UPDATE(update_session_track_tx_info));
+
+
 static bool update_session_track_state_change(sys_var *self, THD *thd,
                                               enum_var_type type)
 {
diff --git a/sql/sys_vars.ic b/sql/sys_vars.ic
index 45ab6b2..cf01835 100644
--- a/sql/sys_vars.ic
+++ b/sql/sys_vars.ic
@@ -2078,7 +2078,43 @@ class Sys_var_tx_isolation: public Sys_var_enum
     if (var->type == OPT_SESSION && Sys_var_enum::session_update(thd, var))
       return TRUE;
     if (var->type == OPT_DEFAULT || !thd->in_active_multi_stmt_transaction())
+    {
+      Transaction_state_tracker *tst= NULL;
+
+      if (thd->variables.session_track_transaction_info > TX_TRACK_NONE)
+        tst= (Transaction_state_tracker *)
+          thd->session_tracker.get_tracker(TRANSACTION_INFO_TRACKER);
+
       thd->tx_isolation= (enum_tx_isolation) var->save_result.ulonglong_value;
+
+      if (var->type == OPT_DEFAULT)
+      {
+        enum enum_tx_isol_level l;
+        switch (thd->tx_isolation) {
+        case ISO_READ_UNCOMMITTED:
+          l=  TX_ISOL_UNCOMMITTED;
+          break;
+        case ISO_READ_COMMITTED:
+          l=  TX_ISOL_COMMITTED;
+          break;
+        case ISO_REPEATABLE_READ:
+          l= TX_ISOL_REPEATABLE;
+          break;
+        case ISO_SERIALIZABLE:
+          l= TX_ISOL_SERIALIZABLE;
+          break;
+        default:
+          DBUG_ASSERT(0);
+          return TRUE;
+        }
+        if (tst)
+          tst->set_isol_level(thd, l);
+      }
+      else if (tst)
+      {
+        tst->set_isol_level(thd, TX_ISOL_INHERIT);
+      }
+    }
     return FALSE;
   }
 };
diff --git a/sql/transaction.cc b/sql/transaction.cc
index 8b18870..67df4b5 100644
--- a/sql/transaction.cc
+++ b/sql/transaction.cc
@@ -25,6 +25,40 @@
 #include "debug_sync.h"         // DEBUG_SYNC
 #include "sql_acl.h"
 
+
+/**
+  Helper: Tell tracker (if any) that transaction ended.
+*/
+static void trans_track_end_trx(THD *thd)
+{
+  if (thd->variables.session_track_transaction_info > TX_TRACK_NONE)
+  {
+    ((Transaction_state_tracker *)
+     thd->session_tracker.get_tracker(TRANSACTION_INFO_TRACKER))->end_trx(thd);
+  }
+}
+
+
+/**
+  Helper: transaction ended, SET TRANSACTION one-shot variables
+  revert to session values. Let the transaction state tracker know.
+*/
+void trans_reset_one_shot_chistics(THD *thd)
+{
+  if (thd->variables.session_track_transaction_info > TX_TRACK_NONE)
+  {
+    Transaction_state_tracker *tst= (Transaction_state_tracker *)
+      thd->session_tracker.get_tracker(TRANSACTION_INFO_TRACKER);
+
+    tst->set_read_flags(thd, TX_READ_INHERIT);
+    tst->set_isol_level(thd, TX_ISOL_INHERIT);
+  }
+
+  thd->tx_isolation= (enum_tx_isolation) thd->variables.tx_isolation;
+  thd->tx_read_only= thd->variables.tx_read_only;
+}
+
+
 /* Conditions under which the transaction state must not change. */
 static bool trans_check(THD *thd)
 {
@@ -125,11 +159,16 @@ static bool xa_trans_force_rollback(THD *thd)
 bool trans_begin(THD *thd, uint flags)
 {
   int res= FALSE;
+  Transaction_state_tracker *tst= NULL;
   DBUG_ENTER("trans_begin");
 
   if (trans_check(thd))
     DBUG_RETURN(TRUE);
 
+  if (thd->variables.session_track_transaction_info > TX_TRACK_NONE)
+    tst= (Transaction_state_tracker *)
+      thd->session_tracker.get_tracker(TRANSACTION_INFO_TRACKER);
+
   thd->locked_tables_list.unlock_locked_tables(thd);
 
   DBUG_ASSERT(!thd->locked_tables_mode);
@@ -172,7 +211,11 @@ bool trans_begin(THD *thd, uint flags)
   DBUG_ASSERT(!((flags & MYSQL_START_TRANS_OPT_READ_ONLY) &&
                 (flags & MYSQL_START_TRANS_OPT_READ_WRITE)));
   if (flags & MYSQL_START_TRANS_OPT_READ_ONLY)
+  {
     thd->tx_read_only= true;
+    if (tst)
+      tst->set_read_flags(thd, TX_READ_ONLY);
+  }
   else if (flags & MYSQL_START_TRANS_OPT_READ_WRITE)
   {
     /*
@@ -189,6 +232,12 @@ bool trans_begin(THD *thd, uint flags)
       DBUG_RETURN(true);
     }
     thd->tx_read_only= false;
+    /*
+      This flags that tx_read_only was set explicitly, rather than
+      just from the session's default.
+    */
+    if (tst)
+      tst->set_read_flags(thd, TX_READ_WRITE);
   }
 
 #ifdef WITH_WSREP
@@ -203,9 +252,16 @@ bool trans_begin(THD *thd, uint flags)
     thd->server_status|= SERVER_STATUS_IN_TRANS_READONLY;
   DBUG_PRINT("info", ("setting SERVER_STATUS_IN_TRANS"));
 
+  if (tst)
+    tst->add_trx_state(thd, TX_EXPLICIT);
+
   /* ha_start_consistent_snapshot() relies on OPTION_BEGIN flag set. */
   if (flags & MYSQL_START_TRANS_OPT_WITH_CONS_SNAPSHOT)
+  {
+    if (tst)
+      tst->add_trx_state(thd, TX_WITH_SNAPSHOT);
     res= ha_start_consistent_snapshot(thd);
+  }
 
   DBUG_RETURN(MY_TEST(res));
 }
@@ -255,6 +311,8 @@ bool trans_commit(THD *thd)
   thd->transaction.all.m_unsafe_rollback_flags&= ~THD_TRANS::DID_WAIT;
   thd->lex->start_transaction_opt= 0;
 
+  trans_track_end_trx(thd);
+
   DBUG_RETURN(MY_TEST(res));
 }
 
@@ -308,8 +366,9 @@ bool trans_commit_implicit(THD *thd)
     @@session.completion_type since it's documented
     to not have any effect on implicit commit.
   */
-  thd->tx_isolation= (enum_tx_isolation) thd->variables.tx_isolation;
-  thd->tx_read_only= thd->variables.tx_read_only;
+  trans_reset_one_shot_chistics(thd);
+
+  trans_track_end_trx(thd);
 
   DBUG_RETURN(res);
 }
@@ -349,6 +408,8 @@ bool trans_rollback(THD *thd)
   thd->transaction.all.m_unsafe_rollback_flags&= ~THD_TRANS::DID_WAIT;
   thd->lex->start_transaction_opt= 0;
 
+  trans_track_end_trx(thd);
+
   DBUG_RETURN(MY_TEST(res));
 }
 
@@ -396,6 +457,8 @@ bool trans_rollback_implicit(THD *thd)
   /* Rollback should clear transaction_rollback_request flag. */
   DBUG_ASSERT(! thd->transaction_rollback_request);
 
+  trans_track_end_trx(thd);
+
   DBUG_RETURN(MY_TEST(res));
 }
 
@@ -434,8 +497,7 @@ bool trans_commit_stmt(THD *thd)
     res= ha_commit_trans(thd, FALSE);
     if (! thd->in_active_multi_stmt_transaction())
     {
-      thd->tx_isolation= (enum_tx_isolation) thd->variables.tx_isolation;
-      thd->tx_read_only= thd->variables.tx_read_only;
+      trans_reset_one_shot_chistics(thd);
       if (WSREP_ON)
         wsrep_post_commit(thd, FALSE);
     }
@@ -487,10 +549,7 @@ bool trans_rollback_stmt(THD *thd)
       wsrep_register_hton(thd, FALSE);
     ha_rollback_trans(thd, FALSE);
     if (! thd->in_active_multi_stmt_transaction())
-    {
-      thd->tx_isolation= (enum_tx_isolation) thd->variables.tx_isolation;
-      thd->tx_read_only= thd->variables.tx_read_only;
-    }
+      trans_reset_one_shot_chistics(thd);
   }
 
   (void) RUN_HOOK(transaction, after_rollback, (thd, FALSE));
@@ -912,6 +971,8 @@ bool trans_xa_commit(THD *thd)
   xid_cache_delete(thd, &thd->transaction.xid_state);
   thd->transaction.xid_state.xa_state= XA_NOTR;
 
+  trans_track_end_trx(thd);
+
   DBUG_RETURN(res);
 }
 
@@ -968,5 +1029,7 @@ bool trans_xa_rollback(THD *thd)
   xid_cache_delete(thd, &thd->transaction.xid_state);
   thd->transaction.xid_state.xa_state= XA_NOTR;
 
+  trans_track_end_trx(thd);
+
   DBUG_RETURN(res);
 }
diff --git a/sql/transaction.h b/sql/transaction.h
index 54b25f1..90de11a 100644
--- a/sql/transaction.h
+++ b/sql/transaction.h
@@ -44,4 +44,6 @@ bool trans_xa_prepare(THD *thd);
 bool trans_xa_commit(THD *thd);
 bool trans_xa_rollback(THD *thd);
 
+void trans_reset_one_shot_chistics(THD *thd);
+
 #endif /* TRANSACTION_H */



More information about the commits mailing list