前往小程序,Get更优阅读体验!
立即前往
首页
学习
活动
专区
工具
TVP
发布
社区首页 >专栏 >Mysql插入超过长度字符串会发生什么

Mysql插入超过长度字符串会发生什么

作者头像
心平气和
发布2021-07-15 13:09:24
3.5K0
发布2021-07-15 13:09:24
举报

一、问题说明

一朋友线上用的mysql5.6.17,sql_mode配的STRICT_TRANS_TABLES,这个配置的具体含义就不在这里说明了,这个是比较严格的模式;

有一天发生一个奇怪的问题,为了简化说明,用以下表结构进行模拟:

代码语言:javascript
复制
CREATE TABLE `user1` (
  `id` int(11) NOT NULL AUTO_INCREMENT,
  `name` varchar(10) DEFAULT NULL,
  PRIMARY KEY (`id`)
) ENGINE=InnoDB DEFAULT CHARSET=utf8;

上面有个表user1有个name字段,定义长度只有10。

具体执行的就是下面2 SQL,其中第一个是失败的,但第二个是成功的

代码语言:javascript
复制
INSERT INTO `user1`(name) VALUES('123451234512')

INSERT INTO `user1`(name) VALUES('1234512345   ')

其中第一条sql语句长度超过10了,并且没有多余的空格;

第2条则特殊一些,总长度超过10,并且尾部是空格,即去掉空格后总长度不超过10。

先手动执行下看下结果:

可以看到sql1失败报太长了,sql2执行成功,但只有一个警告。

二、源码分析

在mysql_insert函数上打断点:

代码语言:javascript
复制
 while ((values= its++))
  {
    if (fields.elements || !value_count)
    {
      restore_record(table,s->default_values);  // Get empty record

      /*
        Check whether default values of the fields not specified in column list
        are correct or not.
      */
      if (validate_default_values_of_unset_fields(thd, table))
      {
        error= 1;
        break;
      }

      if (fill_record_n_invoke_before_triggers(thd, fields, *values, 0,
                                               table->triggers,
                                               TRG_EVENT_INSERT))
      {
  if (values_list.elements != 1 && ! thd->is_error())
  {
    info.stats.records++;
    continue;
  }
  /*
    TODO: set thd->abort_on_warning if values_list.elements == 1
    and check that all items return warning in case of problem with
    storing field.
        */
  error=1;
  break;
      }
    }

比较关键的是函数fill_record_n_invoke_before_triggers,跟进去一直到Field_varstring类的store函数;

mysql对于每种数据类型抽象一个类,varchar对应的是Field_varstring:

代码语言:javascript
复制
type_conversion_status Field_varstring::store(const char *from,uint length,
                                              const CHARSET_INFO *cs)
{
  ASSERT_COLUMN_MARKED_FOR_WRITE;
  uint copy_length;
  const char *well_formed_error_pos;
  const char *cannot_convert_error_pos;
  const char *from_end_pos;

  copy_length= well_formed_copy_nchars(field_charset,
                                       (char*) ptr + length_bytes,
                                       field_length,
                                       cs, from, length,
                                       field_length / field_charset->mbmaxlen,
                                       &well_formed_error_pos,
                                       &cannot_convert_error_pos,
                                       &from_end_pos);
  if (length_bytes == 1)
    *ptr= (uchar) copy_length;
  else
    int2store(ptr, copy_length);

  return check_string_copy_error(well_formed_error_pos,
                                 cannot_convert_error_pos, from_end_pos,
                                 from + length, true, cs);
}

这里可以看from就是我们要插入的内容:

因为类型是varchar(10),所以只拷贝10个字符,重点看函数check_string_copy_error:

代码语言:javascript
复制
type_conversion_status
Field_longstr::check_string_copy_error(const char *well_formed_error_pos,
                                       const char *cannot_convert_error_pos,
                                       const char *from_end_pos,
                                       const char *end,
                                       bool count_spaces,
                                       const CHARSET_INFO *cs) const
{
  const char *pos;
  char tmp[32];
  THD *thd= table->in_use;

  if (!(pos= well_formed_error_pos) &&
      !(pos= cannot_convert_error_pos))
    return report_if_important_data(from_end_pos, end, count_spaces);

  convert_to_printable(tmp, sizeof(tmp), pos, (end - pos), cs, 6);

  push_warning_printf(thd,
                      Sql_condition::WARN_LEVEL_WARN,
                      ER_TRUNCATED_WRONG_VALUE_FOR_FIELD,
                      ER(ER_TRUNCATED_WRONG_VALUE_FOR_FIELD),
                      "string", tmp, field_name,
                      thd->get_stmt_da()->current_row_for_warning());
  return TYPE_WARN_TRUNCATED;
}

再跟进report_if_important_data函数:

代码语言:javascript
复制
type_conversion_status
Field_longstr::report_if_important_data(const char *pstr, const char *end,
                                        bool count_spaces) const
{
  if ((pstr < end) && table->in_use->count_cuted_fields)
  {
    if (test_if_important_data(field_charset, pstr, end))
    {
      if (table->in_use->abort_on_warning)
        set_warning(Sql_condition::WARN_LEVEL_WARN, ER_DATA_TOO_LONG, 1);
      else
        set_warning(Sql_condition::WARN_LEVEL_WARN, WARN_DATA_TRUNCATED, 1);
      return TYPE_WARN_TRUNCATED;
    }
    else if (count_spaces)
    { /* If we lost only spaces then produce a NOTE, not a WARNING */
      set_warning(Sql_condition::WARN_LEVEL_NOTE, WARN_DATA_TRUNCATED, 1);
      return TYPE_NOTE_TRUNCATED;
    }
  }
  return TYPE_OK;
}

这里pstr是<end,因为前面讲了只拷贝10个字符,再看test_if_important_data函数:

代码语言:javascript
复制
static bool
test_if_important_data(const CHARSET_INFO *cs, const char *str,
                       const char *strend)
{
  if (cs != &my_charset_bin)
    str+= cs->cset->scan(cs, str, strend, MY_SEQ_SPACES);
  return (str < strend);
}

这里scan最终对应的是my_scan_8bit函数:

代码语言:javascript
复制
size_t my_scan_8bit(const CHARSET_INFO *cs, const char *str, const char *end,
                    int sq)
{
  const char *str0= str;
  switch (sq)
  {
  case MY_SEQ_INTTAIL:
    if (*str == '.')
    {
      for(str++ ; str != end && *str == '0' ; str++);
      return (size_t) (str - str0);
    }
    return 0;

//进入这个逻辑
  case MY_SEQ_SPACES:
    for ( ; str < end ; str++)
    {
      if (!my_isspace(cs,*str))
        break;
    }
    return (size_t) (str - str0);
  default:
    return 0;
  }
}

因为传递的是MY_SEQ_SPACES,所以这里会判断my_isspace是否空格,如果是由跳过,因此尾部是空格由会跳过,即认为不会超过长度。

因此test_if_important_data会返回失败,设置相应告警,因sql_mode不同从而导致两者的错误码不一样:

代码语言:javascript
复制
 if (table->in_use->abort_on_warning)
        set_warning(Sql_condition::WARN_LEVEL_WARN, ER_DATA_TOO_LONG, 1);
      else
        set_warning(Sql_condition::WARN_LEVEL_WARN, WARN_DATA_TRUNCATED, 1);
      return TYPE_WARN_TRUNCATED;

为什么这么设计,应该也是从数据正确性来看的,删除空格不影响最终数据的,但删除非空格的数据真的是丢数据了。

三、总结

1、varchar字段mysql内部用Field_varstring表示,插入时mysql会调用字段的store方法进行数据复制;

2、Field_varstring继承Field_longstr

并调用report_if_important_data来检查数据长度;

3、report_if_important_data调用test_if_important_data来检查是否超过长度,后者会根据每种字符集来做处理,本例是会略过相应空格。

本文参与 腾讯云自媒体同步曝光计划,分享自微信公众号。
原始发表:2021-07-04,如有侵权请联系 cloudcommunity@tencent.com 删除

本文分享自 程序员升级之路 微信公众号,前往查看

如有侵权,请联系 cloudcommunity@tencent.com 删除。

本文参与 腾讯云自媒体同步曝光计划  ,欢迎热爱写作的你一起参与!

评论
登录后参与评论
0 条评论
热度
最新
推荐阅读
相关产品与服务
云数据库 SQL Server
腾讯云数据库 SQL Server (TencentDB for SQL Server)是业界最常用的商用数据库之一,对基于 Windows 架构的应用程序具有完美的支持。TencentDB for SQL Server 拥有微软正版授权,可持续为用户提供最新的功能,避免未授权使用软件的风险。具有即开即用、稳定可靠、安全运行、弹性扩缩等特点。
领券
问题归档专栏文章快讯文章归档关键词归档开发者手册归档开发者手册 Section 归档