1 | /***************************************************************************** |
2 | |
3 | Copyright (c) 1996, 2017, Oracle and/or its affiliates. All Rights Reserved. |
4 | Copyright (c) 2017, 2018, MariaDB Corporation. |
5 | |
6 | This program is free software; you can redistribute it and/or modify it under |
7 | the terms of the GNU General Public License as published by the Free Software |
8 | Foundation; version 2 of the License. |
9 | |
10 | This program is distributed in the hope that it will be useful, but WITHOUT |
11 | ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS |
12 | FOR A PARTICULAR PURPOSE. See the GNU General Public License for more details. |
13 | |
14 | You should have received a copy of the GNU General Public License along with |
15 | this program; if not, write to the Free Software Foundation, Inc., |
16 | 51 Franklin Street, Suite 500, Boston, MA 02110-1335 USA |
17 | |
18 | *****************************************************************************/ |
19 | |
20 | /**************************************************//** |
21 | @file trx/trx0sys.cc |
22 | Transaction system |
23 | |
24 | Created 3/26/1996 Heikki Tuuri |
25 | *******************************************************/ |
26 | |
27 | #include "ha_prototypes.h" |
28 | |
29 | #include "mysqld.h" |
30 | #include "trx0sys.h" |
31 | #include "sql_error.h" |
32 | |
33 | #include "fsp0fsp.h" |
34 | #include "mtr0log.h" |
35 | #include "mtr0log.h" |
36 | #include "trx0trx.h" |
37 | #include "trx0rseg.h" |
38 | #include "trx0undo.h" |
39 | #include "srv0srv.h" |
40 | #include "srv0start.h" |
41 | #include "trx0purge.h" |
42 | #include "log0log.h" |
43 | #include "log0recv.h" |
44 | #include "os0file.h" |
45 | #include "fsp0sysspace.h" |
46 | |
47 | #include <mysql/service_wsrep.h> |
48 | |
49 | /** The transaction system */ |
50 | trx_sys_t trx_sys; |
51 | |
52 | /** Check whether transaction id is valid. |
53 | @param[in] id transaction id to check |
54 | @param[in] name table name */ |
55 | void |
56 | ReadView::check_trx_id_sanity( |
57 | trx_id_t id, |
58 | const table_name_t& name) |
59 | { |
60 | if (id >= trx_sys.get_max_trx_id()) { |
61 | |
62 | ib::warn() << "A transaction id" |
63 | << " in a record of table " |
64 | << name |
65 | << " is newer than the" |
66 | << " system-wide maximum." ; |
67 | ut_ad(0); |
68 | THD *thd = current_thd; |
69 | if (thd != NULL) { |
70 | char table_name[MAX_FULL_NAME_LEN + 1]; |
71 | |
72 | innobase_format_name( |
73 | table_name, sizeof(table_name), |
74 | name.m_name); |
75 | |
76 | push_warning_printf(thd, Sql_condition::WARN_LEVEL_WARN, |
77 | ER_SIGNAL_WARN, |
78 | "InnoDB: Transaction id" |
79 | " in a record of table" |
80 | " %s is newer than system-wide" |
81 | " maximum." , table_name); |
82 | } |
83 | } |
84 | } |
85 | |
86 | #ifdef UNIV_DEBUG |
87 | /* Flag to control TRX_RSEG_N_SLOTS behavior debugging. */ |
88 | uint trx_rseg_n_slots_debug = 0; |
89 | #endif |
90 | |
91 | /** Display the MySQL binlog offset info if it is present in the trx |
92 | system header. */ |
93 | void |
94 | trx_sys_print_mysql_binlog_offset() |
95 | { |
96 | if (!*trx_sys.recovered_binlog_filename) { |
97 | return; |
98 | } |
99 | |
100 | ib::info() << "Last binlog file '" |
101 | << trx_sys.recovered_binlog_filename |
102 | << "', position " |
103 | << trx_sys.recovered_binlog_offset; |
104 | } |
105 | |
106 | /** Find an available rollback segment. |
107 | @param[in] sys_header |
108 | @return an unallocated rollback segment slot in the TRX_SYS header |
109 | @retval ULINT_UNDEFINED if not found */ |
110 | ulint |
111 | trx_sys_rseg_find_free(const buf_block_t* ) |
112 | { |
113 | for (ulint rseg_id = 0; rseg_id < TRX_SYS_N_RSEGS; rseg_id++) { |
114 | if (trx_sysf_rseg_get_page_no(sys_header, rseg_id) |
115 | == FIL_NULL) { |
116 | return rseg_id; |
117 | } |
118 | } |
119 | |
120 | return(ULINT_UNDEFINED); |
121 | } |
122 | |
123 | /** Count the number of initialized persistent rollback segment slots. */ |
124 | static |
125 | void |
126 | trx_sysf_get_n_rseg_slots() |
127 | { |
128 | mtr_t mtr; |
129 | mtr.start(); |
130 | |
131 | srv_available_undo_logs = 0; |
132 | if (const buf_block_t* = trx_sysf_get(&mtr, false)) { |
133 | for (ulint rseg_id = 0; rseg_id < TRX_SYS_N_RSEGS; rseg_id++) { |
134 | srv_available_undo_logs |
135 | += trx_sysf_rseg_get_page_no(sys_header, |
136 | rseg_id) |
137 | != FIL_NULL; |
138 | } |
139 | } |
140 | |
141 | mtr.commit(); |
142 | } |
143 | |
144 | /*****************************************************************//** |
145 | Creates the file page for the transaction system. This function is called only |
146 | at the database creation, before trx_sys_init. */ |
147 | static |
148 | void |
149 | trx_sysf_create( |
150 | /*============*/ |
151 | mtr_t* mtr) /*!< in: mtr */ |
152 | { |
153 | ulint slot_no; |
154 | buf_block_t* block; |
155 | page_t* page; |
156 | ulint page_no; |
157 | byte* ptr; |
158 | |
159 | ut_ad(mtr); |
160 | |
161 | /* Note that below we first reserve the file space x-latch, and |
162 | then enter the kernel: we must do it in this order to conform |
163 | to the latching order rules. */ |
164 | |
165 | mtr_x_lock(&fil_system.sys_space->latch, mtr); |
166 | compile_time_assert(TRX_SYS_SPACE == 0); |
167 | |
168 | /* Create the trx sys file block in a new allocated file segment */ |
169 | block = fseg_create(fil_system.sys_space, 0, |
170 | TRX_SYS + TRX_SYS_FSEG_HEADER, |
171 | mtr); |
172 | buf_block_dbg_add_level(block, SYNC_TRX_SYS_HEADER); |
173 | |
174 | ut_a(block->page.id.page_no() == TRX_SYS_PAGE_NO); |
175 | |
176 | page = buf_block_get_frame(block); |
177 | |
178 | mlog_write_ulint(page + FIL_PAGE_TYPE, FIL_PAGE_TYPE_TRX_SYS, |
179 | MLOG_2BYTES, mtr); |
180 | |
181 | /* Reset the doublewrite buffer magic number to zero so that we |
182 | know that the doublewrite buffer has not yet been created (this |
183 | suppresses a Valgrind warning) */ |
184 | |
185 | mlog_write_ulint(page + TRX_SYS_DOUBLEWRITE |
186 | + TRX_SYS_DOUBLEWRITE_MAGIC, 0, MLOG_4BYTES, mtr); |
187 | |
188 | /* Reset the rollback segment slots. Old versions of InnoDB |
189 | (before MySQL 5.5) define TRX_SYS_N_RSEGS as 256 and expect |
190 | that the whole array is initialized. */ |
191 | ptr = TRX_SYS + TRX_SYS_RSEGS + page; |
192 | compile_time_assert(256 >= TRX_SYS_N_RSEGS); |
193 | memset(ptr, 0xff, 256 * TRX_SYS_RSEG_SLOT_SIZE); |
194 | ptr += 256 * TRX_SYS_RSEG_SLOT_SIZE; |
195 | ut_a(ptr <= page + (srv_page_size - FIL_PAGE_DATA_END)); |
196 | |
197 | /* Initialize all of the page. This part used to be uninitialized. */ |
198 | memset(ptr, 0, srv_page_size - FIL_PAGE_DATA_END + size_t(page - ptr)); |
199 | |
200 | mlog_log_string(TRX_SYS + page, srv_page_size - FIL_PAGE_DATA_END |
201 | - TRX_SYS, mtr); |
202 | |
203 | /* Create the first rollback segment in the SYSTEM tablespace */ |
204 | slot_no = trx_sys_rseg_find_free(block); |
205 | page_no = trx_rseg_header_create(fil_system.sys_space, slot_no, block, |
206 | mtr); |
207 | |
208 | ut_a(slot_no == TRX_SYS_SYSTEM_RSEG_ID); |
209 | ut_a(page_no == FSP_FIRST_RSEG_PAGE_NO); |
210 | } |
211 | |
212 | /** Create the instance */ |
213 | void |
214 | trx_sys_t::create() |
215 | { |
216 | ut_ad(this == &trx_sys); |
217 | ut_ad(!is_initialised()); |
218 | m_initialised = true; |
219 | mutex_create(LATCH_ID_TRX_SYS, &mutex); |
220 | UT_LIST_INIT(trx_list, &trx_t::trx_list); |
221 | my_atomic_store32(&rseg_history_len, 0); |
222 | |
223 | rw_trx_hash.init(); |
224 | } |
225 | |
226 | /*****************************************************************//** |
227 | Creates and initializes the transaction system at the database creation. */ |
228 | void |
229 | trx_sys_create_sys_pages(void) |
230 | /*==========================*/ |
231 | { |
232 | mtr_t mtr; |
233 | |
234 | mtr_start(&mtr); |
235 | |
236 | trx_sysf_create(&mtr); |
237 | |
238 | mtr_commit(&mtr); |
239 | } |
240 | |
241 | /** Create the rollback segments. |
242 | @return whether the creation succeeded */ |
243 | bool |
244 | trx_sys_create_rsegs() |
245 | { |
246 | /* srv_available_undo_logs reflects the number of persistent |
247 | rollback segments that have been initialized in the |
248 | transaction system header page. |
249 | |
250 | srv_undo_logs determines how many of the |
251 | srv_available_undo_logs rollback segments may be used for |
252 | logging new transactions. */ |
253 | ut_ad(srv_undo_tablespaces <= TRX_SYS_MAX_UNDO_SPACES); |
254 | ut_ad(srv_undo_logs <= TRX_SYS_N_RSEGS); |
255 | |
256 | if (srv_read_only_mode) { |
257 | srv_undo_logs = srv_available_undo_logs = ULONG_UNDEFINED; |
258 | return(true); |
259 | } |
260 | |
261 | /* This is executed in single-threaded mode therefore it is not |
262 | necessary to use the same mtr in trx_rseg_create(). n_used cannot |
263 | change while the function is executing. */ |
264 | trx_sysf_get_n_rseg_slots(); |
265 | |
266 | ut_ad(srv_available_undo_logs <= TRX_SYS_N_RSEGS); |
267 | |
268 | /* The first persistent rollback segment is always initialized |
269 | in the system tablespace. */ |
270 | ut_a(srv_available_undo_logs > 0); |
271 | |
272 | if (srv_force_recovery) { |
273 | /* Do not create additional rollback segments if |
274 | innodb_force_recovery has been set. */ |
275 | if (srv_undo_logs > srv_available_undo_logs) { |
276 | srv_undo_logs = srv_available_undo_logs; |
277 | } |
278 | } else { |
279 | for (ulint i = 0; srv_available_undo_logs < srv_undo_logs; |
280 | i++, srv_available_undo_logs++) { |
281 | /* Tablespace 0 is the system tablespace. |
282 | Dedicated undo log tablespaces start from 1. */ |
283 | ulint space = srv_undo_tablespaces > 0 |
284 | ? (i % srv_undo_tablespaces) |
285 | + srv_undo_space_id_start |
286 | : TRX_SYS_SPACE; |
287 | |
288 | if (!trx_rseg_create(space)) { |
289 | ib::error() << "Unable to allocate the" |
290 | " requested innodb_undo_logs" ; |
291 | return(false); |
292 | } |
293 | |
294 | /* Increase the number of active undo |
295 | tablespace in case new rollback segment |
296 | assigned to new undo tablespace. */ |
297 | if (space > srv_undo_tablespaces_active) { |
298 | srv_undo_tablespaces_active++; |
299 | |
300 | ut_ad(srv_undo_tablespaces_active == space); |
301 | } |
302 | } |
303 | } |
304 | |
305 | ut_ad(srv_undo_logs <= srv_available_undo_logs); |
306 | |
307 | ib::info info; |
308 | info << srv_undo_logs << " out of " << srv_available_undo_logs; |
309 | if (srv_undo_tablespaces_active) { |
310 | info << " rollback segments in " << srv_undo_tablespaces_active |
311 | << " undo tablespaces are active." ; |
312 | } else { |
313 | info << " rollback segments are active." ; |
314 | } |
315 | |
316 | return(true); |
317 | } |
318 | |
319 | /** Close the transaction system on shutdown */ |
320 | void |
321 | trx_sys_t::close() |
322 | { |
323 | ut_ad(srv_shutdown_state == SRV_SHUTDOWN_EXIT_THREADS); |
324 | if (!is_initialised()) { |
325 | return; |
326 | } |
327 | |
328 | if (size_t size = view_count()) { |
329 | ib::error() << "All read views were not closed before" |
330 | " shutdown: " << size << " read views open" ; |
331 | } |
332 | |
333 | rw_trx_hash.destroy(); |
334 | |
335 | /* There can't be any active transactions. */ |
336 | |
337 | for (ulint i = 0; i < TRX_SYS_N_RSEGS; ++i) { |
338 | if (trx_rseg_t* rseg = rseg_array[i]) { |
339 | trx_rseg_mem_free(rseg); |
340 | } |
341 | |
342 | if (trx_rseg_t* rseg = temp_rsegs[i]) { |
343 | trx_rseg_mem_free(rseg); |
344 | } |
345 | } |
346 | |
347 | ut_a(UT_LIST_GET_LEN(trx_list) == 0); |
348 | mutex_free(&mutex); |
349 | m_initialised = false; |
350 | } |
351 | |
352 | /** @return total number of active (non-prepared) transactions */ |
353 | ulint trx_sys_t::any_active_transactions() |
354 | { |
355 | uint32_t total_trx= 0; |
356 | |
357 | mutex_enter(&mutex); |
358 | for (trx_t* trx= UT_LIST_GET_FIRST(trx_sys.trx_list); |
359 | trx != NULL; |
360 | trx= UT_LIST_GET_NEXT(trx_list, trx)) |
361 | { |
362 | if (trx->state == TRX_STATE_COMMITTED_IN_MEMORY || |
363 | (trx->state == TRX_STATE_ACTIVE && trx->id)) |
364 | total_trx++; |
365 | } |
366 | mutex_exit(&mutex); |
367 | return total_trx; |
368 | } |
369 | |