精华内容
下载资源
问答
  • DDL和DML!

    千次阅读 2019-03-12 09:11:07
    DDL是数据定义语言,就是对数据库、表层面的操作,如CREATE、ALTER、DROP DML是数据操纵语言,也就是对表中数据的增删改查,如SELECT、UPDATE、INSERT、DELETE 需要大量DML咋办?可以通过select sleep(3);,比如说2000...

    DDL是数据定义语言,就是对数据库、表层面的操作,如CREATE、ALTER、DROP
    DML是数据操纵语言,也就是对表中数据的增删改查,如SELECT、UPDATE、INSERT、DELETE
    需要大量DML咋办?可以通过select sleep(3);,比如说2000条插入,sleep 3s,然后再继续插入2000条,可以缓解主库压力

    展开全文
  • 在上一篇文章温正湖:MySQL 8.0 Online DDL和pt-osc、gh-ost深度对比分析​zhuanlan.zhihu.com中分析并验证了MySQL进行在线创建索引时,不会因为执行时间过长或业务压力较大,在回放增量DML时加锁时间过久而对业务...

    在上一篇文章温正湖:MySQL 8.0 Online DDL和pt-osc、gh-ost深度对比分析​zhuanlan.zhihu.comzhihu-card-default.svg

    中分析并验证了MySQL进行在线创建索引时,不会因为执行时间过长或业务压力较大,在回放增量DML时加锁时间过久而对业务造成严重影响,本文从MySQL 8.0.19源码出发,分析MySQL是如何实现的。同时也确认是否在回放DML时会报duplicate key。

    核心处理流程和对象

    /** @file row/row0log.cc

    Modification log for online index creation and online table rebuild

    Created 2011-05-26 Marko Makela

    通过阅读本文件代码可以发现,创建二级索引的增量DML记录和回放流程跟其他类型DDL是分开的。因为二级索引创建不需要重建表。

    创建二级索引的处理流程

    缓存增量DML操作

    row_log_online_op用于处理创建二级索引的增量DML:

    /** Logs an operation to a secondary index that is (or was) being created. */

    void row_log_online_op(

    dict_index_t *index, /*!< in/out: index, S or X latched */

    const dtuple_t *tuple, /*!< in: index tuple */

    trx_id_t trx_id) /*!< in: transaction ID for insert,

    or 0 for delete */

    {

    该函数直接调用os_file_write_int_fd将日志写入到临时文件中:

    err = os_file_write_int_fd(request, "(modification log)", log->fd,

    log->tail.block, byte_offset, srv_sort_buf_size);

    该场景下,写入临时文件的内容为正在创建的二级索引记录,无需写入聚集/主键索引记录。这样可以大大减少临时文件的数据写入量,二级索引记录构造函数如下:

    rec_convert_dtuple_to_temp(b + extra_size, index, tuple->fields,

    tuple->n_fields, NULL);

    回放增量DML操作

    创建二级索引的增量DML回放入口为row_log_apply:

    /** Apply the row log to the index upon completing index creation.

    @param[in] trx transaction (for checking if the operation was

    interrupted)

    @param[in,out] index secondary index

    @param[in,out] table MySQL table (for reporting duplicates)

    @param[in,out] stage performance schema accounting object, used by

    ALTER TABLE. stage->begin_phase_log_index() will be called initially and then

    stage->inc() will be called for each block of log that is applied.

    @return DB_SUCCESS, or error code on failure */

    dberr_t row_log_apply(const trx_t *trx, dict_index_t *index,

    struct TABLE *table, ut_stage_alter_t *stage) {

    ...

    rw_lock_x_lock(dict_index_get_lock(index));

    if (!index->table->is_corrupted()) {

    error = row_log_apply_ops(trx, index, &dup, stage);

    } else {

    error = DB_SUCCESS;

    }

    ...

    } else {

    ut_ad(dup.n_dup == 0);

    dict_index_set_online_status(index, ONLINE_INDEX_COMPLETE);

    }

    log = index->online_log;

    index->online_log = NULL;

    rw_lock_x_unlock(dict_index_get_lock(index));

    从这段代码可以发现调用row_log_apply_ops实际执行增量DLM回放前加了对应二级索引的互斥锁。回放完成,将索引状态设置为ONLINE_INDEX_COMPLETE,最后进行解锁。

    我们在测试过程已经发现,增量回放过程是不会加长时间锁的,这跟代码实现似乎有冲突。我们在后面分析row_log_apply_ops小结揭晓。

    表重建场景的处理流程

    该场景不是本文分析重点,在此简单说明下。

    对于需重建表的DDL场景,DML操作处理函数分别为row_log_table_apply_insert、row_log_table_apply_update和row_log_table_apply_delete,在函数的注释上进一步注明了用于回放对应DML操作的函数:

    /** Logs an insert to a table that is being rebuilt.

    This will be merged in row_log_table_apply_insert(). */

    void row_log_table_insert(

    const rec_t *rec, /*!< in: clustered index leaf page record,

    page X-latched */

    const dtuple_t *ventry, /*!< in: dtuple holding virtual column info */

    dict_index_t *index, /*!< in/out: clustered index, S-latched

    or X-latched */

    const ulint *offsets) /*!< in: rec_get_offsets(rec,index) */

    {

    row_log_table_low(rec, ventry, NULL, index, offsets, true, NULL);

    }

    /** Logs an update to a table that is being rebuilt.

    This will be merged in row_log_table_apply_update(). */

    void row_log_table_update(

    const rec_t *rec, /*!< in: clustered index leaf page record,

    page X-latched */

    dict_index_t *index, /*!< in/out: clustered index, S-latched

    or X-latched */

    const ulint *offsets, /*!< in: rec_get_offsets(rec,index) */

    const dtuple_t *old_pk, /*!< in: row_log_table_get_pk()

    before the update */

    const dtuple_t *new_v_row, /*!< in: dtuple contains the new virtual

    columns */

    const dtuple_t *old_v_row) /*!< in: dtuple contains the old virtual

    columns */

    {

    row_log_table_low(rec, new_v_row, old_v_row, index, offsets, false, old_pk);

    }

    /** Logs a delete operation to a table that is being rebuilt.

    This will be merged in row_log_table_apply_delete(). */

    void row_log_table_delete(

    trx_t *trx, /*!< in: current transaction */

    const rec_t *rec, /*!< in: clustered index leaf page record,

    page X-latched */

    const dtuple_t *ventry, /*!< in: dtuple holding virtual column info */

    dict_index_t *index, /*!< in/out: clustered index, S-latched

    or X-latched */

    const ulint *offsets, /*!< in: rec_get_offsets(rec,index) */

    const byte *sys) /*!< in: DB_TRX_ID,DB_ROLL_PTR that should

    be logged, or NULL to use those in rec */

    上述3个函数均是调用row_log_table_close执行增量DML日志格式化和写入操作:

    /** Stops logging an operation to a table that is being rebuilt. */

    static void row_log_table_close_func(

    row_log_t *log, /*!< in/out: online rebuild log */

    #ifdef UNIV_DEBUG

    const byte *b, /*!< in: end of log record */

    #endif /* UNIV_DEBUG */

    ulint size, /*!< in: size of log record */

    ulint avail) /*!< in: available size for log record */

    {

    该函数再最后调用os_file_write_int_fd

    err = os_file_write_int_fd(request, "(modification log)", log->fd,

    log->tail.block, byte_offset, srv_sort_buf_size);

    总的来说,创建二级索引和重建表都需要处理增量DML,但处理方式不一样,相对来说,创建二级索引场景更加简单,因为只需要处理新增的二级索引记录即可。

    增量DML记录和回放核心对象

    row_log_t对象

    不管是创建二级索引还是进行表重建,处理增量DML的核心对象都是row_log_t,该对象具体内容如下所示:

    /** @brief Buffer for logging modifications during online index creation

    All modifications to an index that is being created will be logged by

    row_log_online_op() to this buffer.

    All modifications to a table that is being rebuilt will be logged by

    row_log_table_delete(), row_log_table_update(), row_log_table_insert()

    to this buffer.

    When head.blocks == tail.blocks, the reader will access tail.block

    directly. When also head.bytes == tail.bytes, both counts will be

    reset to 0 and the file will be truncated. */

    struct row_log_t {

    int fd; /*!< file descriptor */

    ib_mutex_t mutex; /*!< mutex protecting error,

    max_trx and tail */

    page_no_map *blobs; /*!< map of page numbers of off-page columns

    that have been freed during table-rebuilding

    ALTER TABLE (row_log_table_*); protected by

    index->lock X-latch only */

    dict_table_t *table; /*!< table that is being rebuilt,

    or NULL when this is a secondary

    index that is being created online */

    bool same_pk; /*!< whether the definition of the PRIMARY KEY

    has remained the same */

    const dtuple_t *add_cols;

    /*!< default values of added columns, or NULL */

    const ulint *col_map; /*!< mapping of old column numbers to

    new ones, or NULL if !table */

    dberr_t error; /*!< error that occurred during online

    table rebuild */

    trx_id_t max_trx; /*!< biggest observed trx_id in

    row_log_online_op();

    protected by mutex and index->lock S-latch,

    or by index->lock X-latch only */

    row_log_buf_t tail; /*!< writer context;

    protected by mutex and index->lock S-latch,

    or by index->lock X-latch only */

    row_log_buf_t head; /*!< reader context; protected by MDL only;

    modifiable by row_log_apply_ops() */

    ulint n_old_col;

    /*!< number of non-virtual column in

    old table */

    ulint n_old_vcol;

    /*!< number of virtual column in old table */

    const char *path; /*!< where to create temporary file during

    log operation */

    };

    这里我们仅分析创建二级索引场景,关注的字段包括fd、tail、head和path。

    fd和path分别表示缓存增量DML的文件路径和文件句柄。path所在目录为所设置的innodb_tmpdir指定,若该值为空,则设置为tmpdir对应目录。

    tail和head为row_log_buf_t对象,分别用于进行增量DML缓存和回放。我们单独放在一个小结说明。

    缓存增量DML的临时文件

    临时文件由row_log_tmpfile创建并打开:

    /** Create the file or online log if it does not exist.

    @param[in,out] log online rebuild log

    @return true if success, false if not */

    static MY_ATTRIBUTE((warn_unused_result)) int row_log_tmpfile(row_log_t *log) {

    DBUG_TRACE;

    if (log->fd < 0) {

    log->fd = row_merge_file_create_low(log->path);

    DBUG_EXECUTE_IF("row_log_tmpfile_fail",

    if (log->fd > 0) row_merge_file_destroy_low(log->fd);

    log->fd = -1;);

    if (log->fd >= 0) {

    MONITOR_ATOMIC_INC(MONITOR_ALTER_TABLE_LOG_FILES);

    }

    }

    return log->fd;

    }

    /** Create temporary merge files in the given paramater path, and if

    UNIV_PFS_IO defined, register the file descriptor with Performance Schema.

    @param[in] path location for creating temporary merge files.

    @return File descriptor */

    int row_merge_file_create_low(const char *path) {

    int fd;

    if (path == NULL) {

    path = innobase_mysql_tmpdir();

    }

    #ifdef UNIV_PFS_IO

    /* This temp file open does not go through normal

    file APIs, add instrumentation to register with

    performance schema */

    Datafile df;

    df.make_filepath(path, "Innodb Merge Temp File", NO_EXT);

    struct PSI_file_locker *locker = NULL;

    PSI_file_locker_state state;

    locker = PSI_FILE_CALL(get_thread_file_name_locker)(

    &state, innodb_temp_file_key.m_value, PSI_FILE_OPEN, df.filepath(),

    &locker);

    if (locker != NULL) {

    PSI_FILE_CALL(start_file_open_wait)(locker, __FILE__, __LINE__);

    }

    #endif /* UNIV_PFS_IO */

    fd = innobase_mysql_tmpfile(path);

    #ifdef UNIV_PFS_IO

    if (locker != NULL) {

    PSI_FILE_CALL(end_file_open_wait_and_bind_to_descriptor)(locker, fd);

    }

    #endif /* UNIV_PFS_IO */

    if (fd < 0) {

    ib::error(ER_IB_MSG_967) << "Cannot create temporary merge file";

    return (-1);

    }

    return (fd);

    }

    从中可以看出,所创建的文件名为“Innodb Merge Temp File”,可通过performance_schema.file_instances等系统表查看临时文件位置和相关统计信息。如下所示:

    node1-performance_schema>select * from file_instances where FILE_NAME like "%Innodb Merge Temp File%"\G *************************** 1. row ***************************

    FILE_NAME: /tmp/Innodb Merge Temp File

    EVENT_NAME: wait/io/file/innodb/innodb_temp_file

    OPEN_COUNT: 2

    1 row in set (0.00 sec)

    node1-performance_schema>select * from file_summary_by_instance where FILE_NAME like "%Innodb Merge Temp File%"\G

    *************************** 1. row ***************************

    FILE_NAME: /tmp/Innodb Merge Temp File

    EVENT_NAME: wait/io/file/innodb/innodb_temp_file

    OBJECT_INSTANCE_BEGIN: 140548089243840

    COUNT_STAR: 18484

    SUM_TIMER_WAIT: 7393902183975

    MIN_TIMER_WAIT: 76528245

    AVG_TIMER_WAIT: 400015995

    MAX_TIMER_WAIT: 27160453440

    COUNT_READ: 9240

    SUM_TIMER_READ: 2499001980465

    MIN_TIMER_READ: 183015375

    AVG_TIMER_READ: 270454725

    MAX_TIMER_READ: 27160453440

    SUM_NUMBER_OF_BYTES_READ: 9688842240

    COUNT_WRITE: 9240

    SUM_TIMER_WRITE: 4894539195270

    MIN_TIMER_WRITE: 385078965

    AVG_TIMER_WRITE: 529711680

    MAX_TIMER_WRITE: 1293598650

    SUM_NUMBER_OF_BYTES_WRITE: 9688842240

    COUNT_MISC: 4

    SUM_TIMER_MISC: 361008240

    MIN_TIMER_MISC: 76528245

    AVG_TIMER_MISC: 90252060

    MAX_TIMER_MISC: 106280070

    1 row in set (0.00 sec)

    ...

    临时文件大小由参数innodb_online_alter_log_max_size确定。

    node1-sbtest>show variables like "%innodb_online_alter_log_max_size%";

    +----------------------------------+-----------+

    | Variable_name | Value |

    +----------------------------------+-----------+

    | innodb_online_alter_log_max_size | 134217728 |

    +----------------------------------+-----------+

    1 row in set (0.01 sec)

    该参数默认值为128M,可在线调整,若在执行过程中将该参数调小或设置值不够大,会导致DDL操作失败,如下例子所示:

    node1-performance_schema>show variables like "%innodb_online_alter_log_max_size%";

    +----------------------------------+-------+

    | Variable_name | Value |

    +----------------------------------+-------+

    | innodb_online_alter_log_max_size | 65536 |

    +----------------------------------+-------+

    1 row in set (0.00 sec)

    node1-sbtest>alter table sbtest1 add index idx_d(wzh);

    ERROR 1799 (HY000): Creating index 'idx_d' required more than 'innodb_online_alter_log_max_size' bytes of modification log. Please try again.

    row_log_buf_t

    row_log_buf_t是另一个重要对象,定义如下:

    /** Log block for modifications during online ALTER TABLE */

    struct row_log_buf_t {

    byte *block; /*!< file block buffer */

    ut_new_pfx_t block_pfx; /*!< opaque descriptor of "block". Set

    by ut_allocator::allocate_large() and fed to

    ut_allocator::deallocate_large(). */

    mrec_buf_t buf; /*!< buffer for accessing a record

    that spans two blocks */

    ulint blocks; /*!< current position in blocks */

    ulint bytes; /*!< current position within block */

    ulonglong total; /*!< logical position, in bytes from

    the start of the row_log_table log;

    0 for row_log_online_op() and

    row_log_apply(). */

    };

    根据定义,进一步结合处理流程可以知道,增量DML日志的缓存(写入临时文件)和回放(读取临时文件)时以记录块为单位进行的。一个记录块可保存一条或多条增量DML日志。一条增量DML日志可能跨2个记录块。

    在row_log_buf_t对象中,block字段表示当前正在操作的最后一个未满的记录块,bytes是该记录块已使用的字节数,blocks表示已经往临时文件中写入多少个记录块。buf用于处理一条DML日志横跨2个记录块的场景。

    记录块的大小由参数innodb_sort_buffer_size指定:

    node1-performance_schema>show variables like "%innodb_sort_buffer_size%";

    +-------------------------+---------+

    | Variable_name | Value |

    +-------------------------+---------+

    | innodb_sort_buffer_size | 1048576 |

    +-------------------------+---------+

    1 row in set (0.01 sec)

    参数默认为1MB,该参数为只读参数,无法动态调整。

    增量DML写入实现分析

    我们首先看看row_log_online_op函数的调用场景,经查询发现大致有2处调用,分别为row_log_online_op_try和row_upd_sec_index_entry_low,如下所示:

    /** Try to log an operation to a secondary index that is

    (or was) being created.

    @retval true if the operation was logged or can be ignored

    @retval false if online index creation is not taking place */

    UNIV_INLINE

    bool row_log_online_op_try(

    dict_index_t *index, /*!< in/out: index, S or X latched */

    const dtuple_t *tuple, /*!< in: index tuple */

    trx_id_t trx_id) /*!< in: transaction ID for insert,

    or 0 for delete */

    {

    ut_ad(rw_lock_own_flagged(dict_index_get_lock(index),

    RW_LOCK_FLAG_S | RW_LOCK_FLAG_X | RW_LOCK_FLAG_SX));

    switch (dict_index_get_online_status(index)) {

    case ONLINE_INDEX_COMPLETE:

    /* This is a normal index. Do not log anything.

    The caller must perform the operation on the

    index tree directly. */

    return (false);

    case ONLINE_INDEX_CREATION:

    /* The index is being created online. Log the

    operation. */

    row_log_online_op(index, tuple, trx_id);

    break;

    case ONLINE_INDEX_ABORTED:

    case ONLINE_INDEX_ABORTED_DROPPED:

    /* The index was created online, but the operation was

    aborted. Do not log the operation and tell the caller

    to skip the operation. */

    break;

    }

    return (true);

    }

    /** Updates a secondary index entry of a row.

    @param[in] node row update node

    @param[in] old_entry the old entry to search, or nullptr then it

    has to be created in this function

    @param[in] thr query thread

    @return DB_SUCCESS if operation successfully completed, else error

    code or DB_LOCK_WAIT */

    static MY_ATTRIBUTE((warn_unused_result)) dberr_t

    row_upd_sec_index_entry_low(upd_node_t *node, dtuple_t *old_entry,

    que_thr_t *thr) {

    ...

    mtr_s_lock(dict_index_get_lock(index), &mtr);

    switch (dict_index_get_online_status(index)) {

    case ONLINE_INDEX_COMPLETE:

    /* This is a normal index. Do not log anything.

    Perform the update on the index tree directly. */

    break;

    case ONLINE_INDEX_CREATION:

    /* Log a DELETE and optionally INSERT. */

    row_log_online_op(index, entry, 0);

    if (!node->is_delete) {

    mem_heap_empty(heap);

    entry =

    row_build_index_entry(node->upd_row, node->upd_ext, index, heap);

    ut_a(entry);

    row_log_online_op(index, entry, trx->id);

    }

    /* fall through */

    ...

    row_upd_sec_index_entry_low为对二级索引的更新场景。进一步溯源可以发现,row_log_online_op_try由二级索引的插入和删除等场景的处理函数调用。这是可以理解的,不深入分析。这样展示的是,不管那个路径进来,都是持有二级索引的锁的。这也可以理解,但似乎跟回放DML日志的流程有锁冲突。问题先抛出来,后面再分析。

    从上面还可以看出,对于一个DML操作,会先写一条DELETE日志(row_log_online_op第三参数为0),如果该DML不是删除操作,那么再写一条INSERT操作。也就是说,处理DML时,删除操作仍保持为删除,插入和更新均改写为先删除再插入的形式。

    (2020-3-23:这样的处理方式,不应该会导致duplicate key才对,欢迎讨论)

    (2020-3-23 12:32:55,又分析了下代码,发现唯一索引还是会有问题的,如下所示:

    /* Ensure that we acquire index->lock when inserting into an

    index with index->online_status == ONLINE_INDEX_COMPLETE, but

    could still be subject to rollback_inplace_alter_table().

    This prevents a concurrent change of index->online_status.

    The memory object cannot be freed as long as we have an open

    reference to the table, or index->table->n_ref_count > 0. */

    bool check = !index->is_committed();

    DBUG_EXECUTE_IF("idx_mimic_not_committed", {

    check = true;

    mode = BTR_MODIFY_TREE;

    });

    if (check) {

    DEBUG_SYNC_C("row_ins_sec_index_enter");

    if (mode == BTR_MODIFY_LEAF) {

    search_mode |= BTR_ALREADY_S_LATCHED;

    mtr_s_lock(dict_index_get_lock(index), &mtr);

    } else {

    mtr_sx_lock(dict_index_get_lock(index), &mtr);

    }

    if (row_log_online_op_try(index, entry, thr_get_trx(thr)->id)) {

    goto func_exit;

    }

    }

    ...

    err = row_ins_scan_sec_index_for_duplicate(flags, index, entry, thr, check,

    &mtr, offsets_heap);

    mtr_commit(&mtr);

    switch (err) {

    case DB_SUCCESS:

    break;

    case DB_DUPLICATE_KEY:

    if (!index->is_committed()) {

    ut_ad(!thr_get_trx(thr)->dict_operation_lock_mode);

    dict_set_corrupted(index);

    /* Do not return any error to the

    caller. The duplicate will be reported

    by ALTER TABLE or CREATE UNIQUE INDEX.

    Unfortunately we cannot report the

    duplicate key value to the DDL thread,

    because the altered_table object is

    private to its call stack. */

    err = DB_SUCCESS;

    }

    /* fall through */

    先插入增量DML日志再进行唯一性约束检查,虽然err被置为DB_SUCCESS,但index被标记为corrupted,所以会导致索引的操作出错)

    在row_log_online_op中,由如下代码段判断增量DML是否超过了设置的innodb_online_alter_log_max_size(srv_online_max_size):

    const os_offset_t byte_offset =

    (os_offset_t)log->tail.blocks * srv_sort_buf_size;

    if (byte_offset + srv_sort_buf_size >= srv_online_max_size) {

    goto write_failed;

    }

    这里引申出一个问题,在上一篇进行验证时,在sysbench oltp tps超过3k负载下,创建二级索引操作执行了约30分钟,默认最大为128M的增量日志文件竟然没有超出,这说明记录的日志量是比较有限的。

    我们接着看row_log_online_op函数实现:

    avail_size = srv_sort_buf_size - log->tail.bytes;

    if (mrec_size > avail_size) {

    b = log->tail.buf;

    } else {

    b = log->tail.block + log->tail.bytes;

    }

    ...

    if (mrec_size >= avail_size) {

    dberr_t err;

    IORequest request(IORequest::WRITE);

    const os_offset_t byte_offset =

    (os_offset_t)log->tail.blocks * srv_sort_buf_size;

    if (byte_offset + srv_sort_buf_size >= srv_online_max_size) {

    goto write_failed;

    }

    if (mrec_size == avail_size) {

    ut_ad(b == &log->tail.block[srv_sort_buf_size]);

    } else {

    ut_ad(b == log->tail.buf + mrec_size);

    memcpy(log->tail.block + log->tail.bytes, log->tail.buf, avail_size);

    }

    UNIV_MEM_ASSERT_RW(log->tail.block, srv_sort_buf_size);

    if (row_log_tmpfile(log) < 0) {

    log->error = DB_OUT_OF_MEMORY;

    goto err_exit;

    }

    err = os_file_write_int_fd(request, "(modification log)", log->fd,

    log->tail.block, byte_offset, srv_sort_buf_size);

    log->tail.blocks++;

    if (err != DB_SUCCESS) {

    write_failed:

    /* We set the flag directly instead of

    invoking dict_set_corrupted() here,

    because the index is not "public" yet. */

    index->type |= DICT_CORRUPT;

    }

    UNIV_MEM_INVALID(log->tail.block, srv_sort_buf_size);

    memcpy(log->tail.block, log->tail.buf + avail_size, mrec_size - avail_size);

    log->tail.bytes = mrec_size - avail_size;

    } else {

    log->tail.bytes += mrec_size;

    ut_ad(b == log->tail.block + log->tail.bytes);

    }

    当等待缓存的增量DML日志量mrec_size大于等于当前记录块的可用空间avail_size时,会触发将记录块写入临时文件的操作。如果mrec_size等于avail_size,那么直接写入当前记录块。

    如果mrec_size大于avail_size,那么会将当前的DML日志先写入tail.buf字段,并拷贝DML日志前面部分到当前记录块,将其填满。再调用os_file_write_int_fd将记录块写入临时文件。

    完成当前记录块写入临时文件后,把DML日志的剩余部分拷贝到已经空闲的tail.block上。

    从这里我们可以确认,DML日志不会全部缓存在内存中,而是会写入到临时文件中,内存中仅保留最后一个记录块。因此不存在执行时间过长引起内存空间占用过多的问题。相对来说,临时文件磁盘空间消耗,问题会小很多,而且上面也提到,对于创建二级索引的DDL场景,产生的增量日志量还是远远少于拷贝表中全量数据这种实现方式。

    增量DML回放实现分析

    前面提到row_log_apply函数为日志回放的入口,而且是加了二级索引的锁的。似乎会导致回放期间DML操作阻塞,接下来就看看源码是如何处理的。

    分析由row_log_apply_ops负责的具体回放操作。在该函数中,跟网上大佬分析MySQL 5.6在线加索引的实现一样的,虽然进入该函数时加了index锁,但在处理非最后一个block时,会释放锁,然后读取文件上的对应日志块并进行回放:

    ut_ad(has_index_lock);

    has_index_lock = false;

    rw_lock_x_unlock(dict_index_get_lock(index));

    log_free_check();

    if (!row_log_block_allocate(index->online_log->head)) {

    error = DB_OUT_OF_MEMORY;

    goto func_exit;

    }

    IORequest request;

    dberr_t err = os_file_read_no_error_handling_int_fd(

    request, index->online_log->path, index->online_log->fd,

    index->online_log->head.block, ofs, srv_sort_buf_size, NULL);

    ...

    while (!trx_is_interrupted(trx)) {

    mrec = next_mrec;

    ut_ad(mrec < mrec_end);

    if (!has_index_lock) {

    /* We are applying operations from a different

    block than the one that is being written to.

    We do not hold index->lock in order to

    allow other threads to concurrently buffer

    modifications. */

    ut_ad(mrec >= index->online_log->head.block);

    ut_ad(mrec_end == index->online_log->head.block + srv_sort_buf_size);

    ut_ad(index->online_log->head.bytes < srv_sort_buf_size);

    /* Take the opportunity to do a redo log

    checkpoint if needed. */

    log_free_check();

    } else {

    /* We are applying operations from the last block.

    Do not allow other threads to buffer anything,

    so that we can finally catch up and synchronize. */

    ut_ad(index->online_log->head.blocks == 0);

    ut_ad(index->online_log->tail.blocks == 0);

    ut_ad(mrec_end ==

    index->online_log->tail.block + index->online_log->tail.bytes);

    ut_ad(mrec >= index->online_log->tail.block);

    }

    next_mrec = row_log_apply_op(index, dup, &error, offsets_heap, heap,

    has_index_lock, mrec, mrec_end, offsets);

    在回放场景使用的是row_log_t对象的head子对象,block字段缓存从临时文件中读去的日志块,调用row_log_apply_op回放DML日志,row_log_apply_op会返回下一个DML日志的位置,因此通过while循环记录完成整个block回放。

    完成该block上每条DML日志回放后,会重新加上二级索引的互斥锁,然后修改进度参数:head的blocks字段增一并将偏移量置位。通过next_block标志跳转来继续处理下一个block,如下所示:

    process_next_block:

    rw_lock_x_lock(dict_index_get_lock(index));

    has_index_lock = true;

    index->online_log->head.bytes = 0;

    index->online_log->head.blocks++;

    goto next_block;

    调整后先验证状态是否合法。接着会判断接下来处理的记录块是否为最后一个block,如果是(判断标准就是head和tail的blocks字段相同),那么已经持有的二级索引互斥锁会继续保持。

    处理最后一个block时不需要从日志文件中读取block,因为最后一个block还缓存在内存中。因此,在开始处理前会先将用于缓存增量DML日志的临时文件truncate掉,避免无意义的存储资源消耗。完成所有DML日志处理后,会将返回值设置为DB_SUCCESS,并跳转到func_exit标识的代码段,进行退出row_log_apply_op前的最后处理。

    next_block:

    ut_ad(has_index_lock);

    ut_ad(rw_lock_own(dict_index_get_lock(index), RW_LOCK_X));

    ut_ad(index->online_log->head.bytes == 0);

    stage->inc(row_log_progress_inc_per_block());

    if (trx_is_interrupted(trx)) {

    goto interrupted;

    }

    ...

    if (index->online_log->head.blocks == index->online_log->tail.blocks) {

    if (index->online_log->head.blocks) {

    #ifdef HAVE_FTRUNCATE

    /* Truncate the file in order to save space. */

    if (index->online_log->fd > 0 &&

    ftruncate(index->online_log->fd, 0) == -1) {

    perror("ftruncate");

    }

    #endif /* HAVE_FTRUNCATE */

    index->online_log->head.blocks = index->online_log->tail.blocks = 0;

    }

    next_mrec = index->online_log->tail.block;

    next_mrec_end = next_mrec + index->online_log->tail.bytes;

    if (next_mrec_end == next_mrec) {

    /* End of log reached. */

    all_done:

    ut_ad(has_index_lock);

    ut_ad(index->online_log->head.blocks == 0);

    ut_ad(index->online_log->tail.blocks == 0);

    error = DB_SUCCESS;

    goto func_exit;

    }

    } else {

    从前面几段代码可以发现,创建二级索引时会通过trx_is_interrupted判断创建操作是否被中断,也就是说可以通过kill等方式终止创建操作。

    总结

    本文分析了MySQL 8.0上Online DDL功能中创建二级索引场景的增量DML处理流程,从源码层面确认了加锁时间并不是跟增量DML的数量正相关,应该说MySQL该环节的处理是比较。Online DDL是个很大的功能集,后续将通过其他文章分析索引创建的全量索引记录构造阶段。

    展开全文
  • 一个unitypackage的压缩包, 下载了解然后直接导入就可以了。 Description: 2DDL PRO is a tool that craft procedurally a Light system for 2D Environment. 2DDL works like a real Light in 2 dimensions.The ...
  • 通过战略管理和竞争战略的相关理论,详细分析了DDL Construction Company面临的内部和外部开发环境以及资源能力,并评估了DDL Construction Company的发展机遇,威胁以及优势和劣势。 在此基础上,运用SWOT矩阵分析...
  • 产生原因 当主库的TPS并发较高时,产生的DDL数量超过slave一个sql线程所能承受的范围,那么延时就产生了,当然还有就是可能与slave的大型query语句产生了锁等待。 首要原因:数据库在业务上读写压力太大,CPU计算...

    备注:测试数据库版本为MySQL 8.0

    这个blog我们来聊聊MySQL 的主从(异步复制)

    概述

    1.MySQL主从拓扑图

    f703ed32b901

    image.png

    2.MySQL配置从库的用处

    2.1. 读写分离

    通过MySQL复制可以实现读写分离,将读操作分布到多个不同的服务器上,减轻服务器的压力。

    2.2. 备份

    从库可以作为数据的异地实时备份。

    2.3. 故障切换

    当主库遇到故障,系统可以切换到从库,实现故障切换。

    2.4. 升级测试

    从库可作为测试服务器的数据库。

    3.复制数据的步骤

    3.1 在主库上把数据更改记录到二进制日志(Binary Log)中。

    3.2 备库将主库上的日志复制到自己的中继日志(Relay Log)中。

    3.3 备库读取中继日志中的事件,将其在备库上重新执行一遍。

    一.主从搭建步骤

    1.数据库安装

    服务器类别

    IP

    主库

    10.31.1.112

    从库

    10.31.1.113

    2.主库开启binlog

    配置文件[mysqld]下添加如下:

    log-bin

    server-id=1

    重启mysql服务

    mysqladmin -uroot -p -S /u01/my3306/mysql.sock shutdown

    /u01/my3306/bin/mysqld_safe --defaults-file=/u01/my3306/my.cnf --user=mysql &

    3.授权

    在主库创建同步账号

    CREATE USER 'repl'@'10.31.1.113' IDENTIFIED BY 'test';

    grant replication slave, replication client on *.* to 'repl'@'10.31.1.113';

    flush privileges;

    mysql> CREATE USER 'repl'@'10.31.1.113' IDENTIFIED BY 'test';

    Query OK, 0 rows affected (0.01 sec)

    mysql> grant replication slave, replication client on *.* to 'repl'@'10.31.1.113';

    Query OK, 0 rows affected (0.01 sec)

    mysql> flush privileges;

    Query OK, 0 rows affected (0.01 sec)

    4.登陆主库查看此时日志状态

    show master status;

    mysql> show master status;

    +---------------+----------+--------------+------------------+-------------------+

    | File | Position | Binlog_Do_DB | Binlog_Ignore_DB | Executed_Gtid_Set |

    +---------------+----------+--------------+------------------+-------------------+

    | binlog.000009 | 441 | | | |

    +---------------+----------+--------------+------------------+-------------------+

    1 row in set (0.00 sec)

    5.导出主库当前数据

    备份此时主库的内容

    -- 备份之前锁表

    mysql> flush table with read lock;

    mysqldump -uroot -pabc123 -A -B --events --master-data=2 > /backup/mysql/db-$(date +%F)-all.sql

    -- 备份完成后取消锁

    mysql> unlock tables;

    6.从库指定serverid

    配置文件[mysqld]下添加如下:

    server-id=2

    server-id不能重复

    重启mysql服务

    mysqladmin -uroot -p -S /u01/my3306/mysql.sock shutdown

    /u01/my3306/bin/mysqld_safe --defaults-file=/u01/my3306/my.cnf --user=mysql &

    7.从库导入主库数据

    通过scp将主库的备份文件同步过来

    mysql -uroot -p

    8.指定开始同步位置

    具体同步位置参考步骤4

    change master to

    master_host='10.31.1.112',

    master_user='repl',

    master_password='test',

    MASTER_LOG_FILE='binlog.000009',

    MASTER_LOG_POS=441 ;

    mysql> change master to

    -> master_host='10.31.1.112',

    -> master_user='repl',

    -> master_password='test',

    -> MASTER_LOG_FILE='binlog.000009',

    -> MASTER_LOG_POS=441 ;

    Query OK, 0 rows affected, 2 warnings (0.01 sec)

    9.开启复制

    -- 启动复制

    start slave;

    -- 查看复制状态

    show slave status\G

    mysql> show slave status\G

    *************************** 1. row ***************************

    Slave_IO_State: Waiting for master to send event

    Master_Host: 10.31.1.112

    Master_User: repl

    Master_Port: 3306

    Connect_Retry: 60

    Master_Log_File: binlog.000009

    Read_Master_Log_Pos: 441

    Relay_Log_File: ipctest-relay-bin.000002

    Relay_Log_Pos: 321

    Relay_Master_Log_File: binlog.000009

    Slave_IO_Running: Yes

    Slave_SQL_Running: Yes

    Replicate_Do_DB:

    Replicate_Ignore_DB:

    Replicate_Do_Table:

    Replicate_Ignore_Table:

    Replicate_Wild_Do_Table:

    Replicate_Wild_Ignore_Table:

    Last_Errno: 0

    Last_Error:

    Skip_Counter: 0

    Exec_Master_Log_Pos: 441

    Relay_Log_Space: 532

    Until_Condition: None

    Until_Log_File:

    Until_Log_Pos: 0

    Master_SSL_Allowed: No

    Master_SSL_CA_File:

    Master_SSL_CA_Path:

    Master_SSL_Cert:

    Master_SSL_Cipher:

    Master_SSL_Key:

    Seconds_Behind_Master: 0

    Master_SSL_Verify_Server_Cert: No

    Last_IO_Errno: 0

    Last_IO_Error:

    Last_SQL_Errno: 0

    Last_SQL_Error:

    Replicate_Ignore_Server_Ids:

    Master_Server_Id: 1

    Master_UUID: 08260f93-cbe5-11ea-bd0d-000c293fa60d

    Master_Info_File: mysql.slave_master_info

    SQL_Delay: 0

    SQL_Remaining_Delay: NULL

    Slave_SQL_Running_State: Slave has read all relay log; waiting for more updates

    Master_Retry_Count: 86400

    Master_Bind:

    Last_IO_Error_Timestamp:

    Last_SQL_Error_Timestamp:

    Master_SSL_Crl:

    Master_SSL_Crlpath:

    Retrieved_Gtid_Set:

    Executed_Gtid_Set:

    Auto_Position: 0

    Replicate_Rewrite_DB:

    Channel_Name:

    Master_TLS_Version:

    Master_public_key_path:

    Get_master_public_key: 0

    Network_Namespace:

    1 row in set (0.00 sec)

    二.复制相关参数

    参数

    参数用途

    Slave_IO_state

    显示当前IO线程的状态,一般情况下就是显示等待主服务器发送二进制日志。

    Master_log_file

    显示当前同步的主服务器的二进制日志。

    Read_master_log_pos

    显示当前同步到主服务器上二进制日志的偏移量位置。

    Relay_master_log_file

    当前中继日志同步的二进制日志。

    Relay_log_file

    显示当前写入的中继日志。

    Relay_log_pos

    显示当前执行到中继日志的偏移量位置。

    Slave_IO_running

    从服务器中IO线程的运行状态,yes代表正常

    Slave_SQL_running

    从服务器中sql线程的运行状态,YES代表正常

    Exec_Master_log_pos

    表示同步到主服务器的二进制日志的偏移量位置。

    三.slave启停常用命令

    从库执行

    命令

    用途

    STOP SLAVE IO_THREAD;

    停止IO进程

    STOP SLAVE SQL_THREAD;

    停止SQL进程

    STOP SLAVE;

    停止IO和SQL进程

    START SLAVE IO_THREAD;

    启动IO进程

    START SLAVE SQL_THREAD;

    启动SQL进程

    START SLAVE;

    启动IO和SQL进程

    RESET SLAVE;

    用于让从属服务器忘记其在主服务器的二进制日志中的复制位置, 它会删除master.info和relay-log.info文件,以及所有的中继日志,并启动一个新的中继日志,当你不需要主从的时候可以在从上执行这个操作。

    SHOW SLAVE STATUS;

    查看MySQL同步状态

    STOP SLAVE;

    SET GLOBAL SQL_SLAVE_SKIP_COUNTER=1;

    START SLAVE;

    经常会朋友mysql主从同步遇到错误的时候,比如一个主键冲突等,那么我就需要在确保那一行数据一致的情况下临时的跳过这个错误,那就需要使用SQL_SLAVE_SKIP_COUNTER = n命令了,n是表示跳过后面的n个事件

    CHANGE MASTER TO

    MASTER_HOST='10.1.1.75',

    MASTER_USER='replication',

    MASTER_PASSWORD='123456',

    MASTER_LOG_FILE='mysql-bin.000006',

    MASTER_LOG_POS=106;

    START SLAVE;

    从指定位置重新同步

    四.保证主从一致

    参数

    参数值

    innodb_flush_log_at_trx_commit=1

    0:log buffer 将每秒一次地写入 log file 中,并且 log file 的 flush (刷到磁盘) 操作同时进行。该模式下在事务提交的时候,不会主动触发写入磁盘的操作。

    1:每次事务提交时 mysql 都会把 log buffer 的数据写入 log file,并且 flush (刷到磁盘) 中去,该模式为系统默认。

    2:每次事务提交时 mysql 都会把 log buffer 的数据写入 log file,但是 flush (刷到磁盘) 操作并不会同时进行。该模式下,MySQL 会每秒执行一次 flush (刷到磁盘) 操作

    sync_binlog=1

    sync_binlog=0,当事务提交之后,MySQL 不做 fsync 之类的磁盘同步指令刷新 binlog_cache 中的信息到磁盘,而让 Filesystem 自行决定什么时候来做同步,或者 cache 满了之后才同步到磁盘。

    sync_binlog=n,当每进行 n 次事务提交之后,MySQL 将进行一次 fsync 之类的磁盘同步指令来将 binlog_cache 中的数据强制写入磁盘。

    sync_master_info=1

    每间隔多少事务刷新master.info,如果是table(innodb)设置无效,每个事务都会更新

    sync_relay_log_info=1

    每间隔多少事务刷新relay-log.info,如果是table(innodb)设置无效,每个事务都会更新

    sync_relay_log=10000

    默认为10000,即每10000次sync_relay_log事件会刷新到磁盘。为0则表示不刷新,交由OS的cache控制

    master_info_repository=TABLE

    记录主库binlog的信息,可以设置FILE(master.info)或者TABLE(mysql.slave_master_info)

    relay_log_info_repository=TABLE

    记录备库relaylog的信息,可以设置FILE(relay-log.info)或者TABLE(mysql.slave_relay_log_info)

    五.常见问题

    5.1 IO线程出问题

    Slave_IO_Running: No

    Slave_SQL_Running: Yes

    Last_IO_Error: Got fatal error 1236 from master when reading data from binary log: 'Could not find first log file name in binary log index file'

    问题排查:在主库上

    [root@master mysql3306]# cat mysql-bin.index

    /home/mysql3306/mysql3306/mysql-bin.000001

    发现binlog文件名不正确,需要重新设置。

    处理办法

    stop slave;

    reset slave all;

    change master to master_host='192.168.3.10',master_user='rep',master_password='rep', master_log_file='mysql-bin.000001',master_log_pos=154,MASTER_PORT=3306;

    start slave;

    5.2 SQL线程问题

    5.2.1 ERROR 1062

    主从数据库不一致的时,slave已经有该条记录,但是我们又在master上插入了同一条记录,此时就会报错.

    在从库上执行跳过该insert语句

    mysql> stop slave;

    mysql> set global sql_slave_skip_counter=1;

    mysql> start slave;

    问题解决!

    5.2.2 ERROR 1032

    从库删除一条数据,主库更新该条删除的数据,发生报错

    手工把缺失的数据补上

    mysql> stop slave;

    mysql> set global sql_slave_skip_counter=1;

    mysql> start slave;

    问题解决!

    5.2.3 ERROR 1452

    无法在外键的表中插入或者更新参考主键没有的数据

    5.2.4 将从库设置为readonly

    建议大家线上打开这两个参数,防止主从不一致的产生。

    set global read_only=1

    只读参数,需要大家注意的对于拥有super权限的用户,该参数不起作用

    set global super_read_only=1(5.7新参数)

    如果想对拥有super权限用户只读,使用super_read_only,开启该参数后,read_only会自动开启。

    5.3 主从延迟问题

    5.3.1.从库同步延迟的现象

    show slave status显示参数Seconds_Behind_Master不为0,这个数值可能会很大

    show slave status显示参数Relay_Master_Log_File和Master_Log_File显示bin-log的编号相差很大,说明bin-log在从库上没有及时同步,所以近期执行的bin-log和当前IO线程所读的bin-log相差很大

    MySQL的从库数据目录下存在大量mysql-relay-log日志,该日志同步完成之后就会被系统自动删除,存在大量日志,说明主从同步延迟很厉害

    5.3.2.产生原因

    当主库的TPS并发较高时,产生的DDL数量超过slave一个sql线程所能承受的范围,那么延时就产生了,当然还有就是可能与slave的大型query语句产生了锁等待。

    首要原因:数据库在业务上读写压力太大,CPU计算负荷大,网卡负荷大,硬盘随机IO太高

    次要原因:读写binlog带来的性能影响,网络传输延迟。

    5.3.3.解决方案

    更换从库更快的硬盘

    网络,网卡,更换带宽更大的网卡

    mysql考虑 从库作为备份数据库来说

    增加从库的innodb_buffer_pool_size,可以缓存更多数据防止由于转换造成的IO压力

    增加innodb_log_file_size和innodb_log_files_in_group,减少buffer落盘

    修改参数innodb_flush_method,提高写入性能(SSD强烈推荐使用)

    从库binlog关闭(如果可以) log_slave_updates关闭

    修改innodb_flush_log_at_trx_commit为0或者2

    修改master_info_repository和relay_log_info_repository为TABLE,防止直接落盘压力

    参考文献:

    展开全文
  • 大多数OLTP型的系统都会采用代码绑定变量的方式来减轻对...在实际中,往往一些不经意的操作,会造成执行计划的重新解析,比如发布DDL语句,发布授权信息等. 下面用跟踪Peeked Binds的方式,验证一下这个过程. SQL> se

    大多数OLTP型的系统都会采用代码绑定变量的方式来减轻对share pool的压力。因为,频繁的硬解析势必会占用过多的CPU,加重shared pool的压力,造成大量的碎片。而稳定的执行计划也是我们不可忽视的方面.在实际中,往往一些不经意的操作,会造成执行计划的重新解析,比如发布DDL语句,发布授权信息等.

    下面用跟踪Peeked Binds的方式,验证一下这个过程.

    SQL> select * from emp;
    EMPNO ENAME JOB MGR HIREDATE SAL COMM DEPTNO
    ----- ---------- --------- ----- ----------- --------- --------- ------
    7369 SMITH CLERK 7902 1980-12-17 800.00 20
    7499 ALLEN SALESMAN 7698 1981-2-20 1600.00 300.00 30
    7782 CLARK MANAGER 7839 1981-6-9 2450.00 10
    7788 SCOTT ANALYST 7566 1987-4-19 3000.00 20
    4 rows selected

    SQL> var empno number
    SQL> var id number
    SQL> exec :id:=7788;
    PL/SQL 过程已成功完成。
    SQL> select * from emp where empno=:id;
    EMPNO ENAME JOB MGR HIREDATE  SAL COMM DEPTNO
    ---------- ---------- ----------
    7788 SCOTT ANALYST 7566 19-4月 -87 3000 20

    SQL> select * from table(dbms_xplan.display_cursor(null, null, 'peeked_binds'));
    PLAN_TABLE_OUTPUT
    --------------------------------------------------------------------------------
    SQL_ID ftzypu5fkftks, child number 0
    -------------------------------------
    select * from emp where empno=:id

    Plan hash value: 3956160932
    --------------------------------------------------------------------------
    | Id | Operation | Name | Rows | Bytes | Cost (%CPU)| Time |
    --------------------------------------------------------------------------
    | 0 | SELECT STATEMENT | | | | 3 (100)| |
    |* 1 | TABLE ACCESS FULL| EMP | 1 | 87 | 3 (0)| 00:00:01 |

    PLAN_TABLE_OUTPUT
    --------------------------------------------------------------------------------
    Peeked Binds (identified by position):
    --------------------------------------
    1 - :ID (NUMBER): 7788

    Predicate Information (identified by operation id):
    ---------------------------------------------------
    1 - filter("EMPNO"=:ID)
    PLAN_TABLE_OUTPUT
    --------------------------------------------------------------------------------
    Note
    -----
    - dynamic sampling used for this statement

    可以看到,由于sql是第一次执行,以empno=7788来生成执行计划.然后我们将变量改为10000.

    SQL> exec :id:=10000;
    PL/SQL 过程已成功完成。
    SQL> select * from emp where empno=:id;
    未选定行
    SQL> select * from table(dbms_xplan.display_cursor(null, null, 'peeked_binds'));
    PLAN_TABLE_OUTPUT
    --------------------------------------------------------------------------------
    SQL_ID ftzypu5fkftks, child number 0
    -------------------------------------
    select * from emp where empno=:id

    Plan hash value: 3956160932
    --------------------------------------------------------------------------
    | Id | Operation | Name | Rows | Bytes | Cost (%CPU)| Time |
    --------------------------------------------------------------------------
    | 0 | SELECT STATEMENT | | | | 3 (100)| |
    |* 1 | TABLE ACCESS FULL| EMP | 1 | 87 | 3 (0)| 00:00:01 |

    PLAN_TABLE_OUTPUT
    --------------------------------------------------------------------------------
    Peeked Binds (identified by position):
    --------------------------------------
    1 - :ID (NUMBER): 7788

    Predicate Information (identified by operation id):
    ---------------------------------------------------
    1 - filter("EMPNO"=:ID)

    PLAN_TABLE_OUTPUT
    --------------------------------------------------------------------------------
    Note
    -----
    - dynamic sampling used for this statement

    可以看到Peeked Binds依然为ID (NUMBER): 7788,也就是说sql第二次执行的时候,得以共享,避免了硬解析.

    发布一个ddl语句,我们在emp上增加一个字段:

    SQL> alter table emp add ts number;

    继续发布语句查询,依然沿用原来的变量数值。

    SQL> select * from emp where empno=:id;
    未选定行

    SQL> select * from table(dbms_xplan.display_cursor(null, null, 'peeked_binds'));
    PLAN_TABLE_OUTPUT
    --------------------------------------------------------------------------------
    SQL_ID ftzypu5fkftks, child number 0
    -------------------------------------
    select * from emp where empno=:id

    Plan hash value: 3956160932
    --------------------------------------------------------------------------
    | Id | Operation | Name | Rows | Bytes | Cost (%CPU)| Time |
    --------------------------------------------------------------------------
    | 0 | SELECT STATEMENT | | | | 3 (100)| |
    |* 1 | TABLE ACCESS FULL| EMP | 1 | 100 | 3 (0)| 00:00:01 |

    PLAN_TABLE_OUTPUT
    --------------------------------------------------------------------------------
    Peeked Binds (identified by position):
    --------------------------------------
    1 - :ID (NUMBER): 10000

    Predicate Information (identified by operation id):
    ---------------------------------------------------
    1 - filter("EMPNO"=:ID)

    PLAN_TABLE_OUTPUT
    --------------------------------------------------------------------------------
    Note
    -----
    - dynamic sampling used for this statement

    此时,可以看到Peeked Binds变为 ID (NUMBER): 10000,也就说明sql已经被重新解析,执行计划被重新生成.

    同样,一些DCL(grant/revoke)的发布,同样会造成相应object的属性更改,从而能使得依赖该object的sql不能共享,需要重新生成执行计划.

    转自:http://www.easyora.net/blog/change_cost_peeked_bind.html

     

    展开全文
  • 我们知道,Oracle提供了一个函数dbms_metadata.get_ddl()来提取表或者视图的DDL语句可惜有一个视图写的太大了(里面有到了N个union语句),结果提示字符缓冲区不足想想可能是该函数的实现机制有问题吧—没有用大的View来...
  • 这些困扰你有吗? 研发 建个表还要等DBA,DBA可是稀缺资源啊,这个发布流程效率有点低呢===》提升研发效率 这个表测试环境我都变更好了,怎么就不符合规范要重新改呢?...这业务性能怎么不上...
  • 全文共2500字,预计学习时长7分钟图源:unsplash 就算你是高级项目经理,就算你拥有20年的项目经验,就算你是天才也无法确切地知道一个项目的确切用时。这个问题在软件工程中尤为普遍,但在其他的工程学科中也时有...
  • pglog高压下瓶颈分析 审计日志打开后会影响性能,类似问题已经多次出现,本篇做下根...log_statement:有效值是 none (off)、ddl、mod和 all(所有语句),打开all后会记录大量日志 14115 [local] postgres bench 2...
  • 如果,初次配置完成了 MySQL 数据库的读写分离操作 那么,后面遇到稍大流量访问时; 首先遭遇到的便是 【“主从同步...主库对所有 DDL 和 DML 产生的日志写进 binlog,由于binlog 是顺序写,所以效率很高。 Slave 的S
  • 含能材料计算器

    2014-03-23 19:53:03
    估算能材的爆速,爆,爆热等数据 使用了简单的kj方程及准确的DDL修正公式
  • 大事务(DDL,我觉得DDL也相当于一个大事务) 大表 DDL 主库DML语句并发大,从库qps高 从库服务器配置差或者一台服务器上几台从库(资源竞争激烈,特别是io) 主库和从库的参数配置不一样 从库上在进行备份操作 表上无主...
  • 利用CRIMS-DDL600电子万能试验机,进行了不同围下砂岩、矸石与煤样的三轴压缩试验,采用莫尔准则诠释了不同岩性的试样破坏角大小不等,通过图形对比和现象分析,得到了围对3种不同岩性的试样应力、应变曲线及变性...
  • 利用自制破碎煤岩实装置,结合DDL600电子万能试验机和计算机采集系统等,对不同级配下破碎煤岩进行实特性试验研究,分析了不同级配与轴向应力对破碎煤岩实后粒度分布的影响规律,建立了破碎煤岩分形维数D与轴向...
  • TokuDB存储引擎

    2019-08-24 07:24:48
    TokuDB是一个高性能、支持事务 处理的MySQL和MariaDB的存储引擎,具有高扩展性、高压缩率、高效的写入性能,支持大多数的DDL操作。 一、TokuDB特点 使用Fractal树索引保证高效和插入性能 优秀的压缩我,比...
  • (2)ToKuDB是一个高性能,支持事务处理的MySQL和MariaDB的存储引擎,具有高扩展性,高压缩率,高效的写入性能,支持大多数在线DDL操作。 (3)ToKuDB比InnoDB具有更高效的插入性能,高10倍的压缩特性,在线的DDL...
  • Y同学最近状态十分低落,因为期末来临,DDL压身,以致神思不属、茶饭不思,沉迷手机、无心学习。然而在水手机的时候,Y同学却发现,身边用功的同学好多,自己的时间线上满满的都是学霸们的动态。 “WA了好久终于AC啦...
  • week4作业题

    2020-03-14 18:24:46
    用大根堆存储输入数据,从最后一天往前遍历,把当天要做的事务到大根堆中,若大根堆不为空就取出根(一定要判断是否为空,我第一次就没判断出错了),最后大根堆中剩余的就是在截止日期之前不能完成的工作。...
  • 利用研发的渗透试验装置,在DDL600电子万能试验机上进行了破碎泥岩实过程中的渗透特性测定,并分析了有效应力与渗流速度关系、不同粒径泥岩在不同孔隙度下的渗透率和非Darcy流β因子.研究表明,流体在破碎泥岩中的...
  • Phoenix支持DDL与DML 增删改查。 Impala给外部提供的接口难以提升并发量。 减轻Impala的查询压力。     官网: http://phoenix.apache.org   Phoneix安装:   SIT环境Cloudera5.14.4...
  • mysql 主从复制模式主要为master负责接收用户的请求,DDL,DML,DCL等操作,slave主要负责同步master的二进制日志,以便备份数据。在一此数据库访问量比较大的场景,master-slave模式还可以结合mysql-proxy做读写分离,...
  • mysql 主从复制模式主要为master负责接收用户的请求,DDL,DML,DCL等操作,slave主要负责同步master的二进制日志,以便备份数据。在一此数据库访问量比较大的场景,master-slave模式还可以结合mysql-proxy做读写分离,...
  • 摘要:TiFlash 是配合 TiDB 体系的列存引擎,它和 TiDB 无缝结合,在线 DDL、无缝扩容、自动容错等等方便运维的特点也在 TiFlash 中得到继承,此外,TiFlash 可以实时与行存保持同步。 在上篇关于 TiFlash 的文章...
  • mysql 主从复制模式主要为master负责接收用户的请求,DDL,DML,DCL等操作,slave主要负责同步master的二进制日志,以便备份数据。在一此数据库访问量比较大的场景,master-slave模式还可以结合mysql-proxy做读写分离,...
  • 读写分离特别适用于读...DDL 数据定义语言,比如create/drop/alter等语句 DCL 数据控制语言,比如grant/rollback/commit等语句 Sharding-JDBC是一个开源的分布式数据库中间件解决方案。它在Java的JDBC层以对业务应.
  • 了解到,分布式DDL功能对Zookeeper的依赖情况还是比较轻量级的,接下来介绍的ReplicatedMergeTree表引擎对Zookeeper的依赖可以说是所有表操作全方面的依赖,真实集群中大量的ReplicatedMergeTree表会对Zookeeper造成...
  • mysql-mariadb 双主热备

    2020-04-29 18:56:06
    mysql从3.23版本开始提供复制功能,复制是将主库的DDL和DML操作通过二进制日志传递到复制服务器(从库)上,然后从库对这些日志重新执行(重做),从而使得主库和从库保持数据一致。 mysql复制的优点: 如果主库...
  • 3、不能做DDL。 4、触发器排错困难,而且数据容易造成不一致,后期维护不方便。 优点: 1、预编译,已优化,效率较高。避免了SQL语句在网络传输然后再解释的低效率。 2、存储过程可以重复...
  • 使用场景: 高可用HA(优点) 读写分离,降低主库的访问压力负载均衡(读操作)在从库进行备份,以免备份期间影响主库的服务二、原理流程介绍:主节点:开启二进制日志,主库上的数据更改(DDL DML DCL)记录到二进制日志...

空空如也

空空如也

1 2 3 4 5
收藏数 90
精华内容 36
热门标签
关键字:

压ddl