分类: Mysql

mysql
  • 解决Mysql:1055 – Expression #2 of SELECT list is not in GROUP BY clause and contains nonaggregated

    解决:

    方法一:修改配置。去掉ONLY_FULL_GROUP_BY

    如果是Linux,就在配置文件(my.cnf)中修改 sql_mode 的配置(在/usr/local/etc/my.cnf路径下)并重启mysql服务。

    $ vim /usr/local/etc/my.cnf
    sql_mode=STRICT_TRANS_TABLES,NO_ZERO_IN_DATE,NO_ZERO_DATE,ERROR_FOR_DIVISION_BY_ZERO,NO_AUTO_CREATE_USER,NO_ENGINE_SUBSTITUTION
    set @@sql_mode 
    ='STRICT_TRANS_TABLES,NO_ZERO_IN_DATE,NO_ZERO_DATE,ERROR_FOR_DIVISION_BY_ZERO,NO_AUTO_CREATE_USER,NO_ENGINE_SUBSTITUTION';

    如果要永久生效需要在MySQL的my.ini配置文件中去修改:

    在[mysqld]下添加sql_mode=’STRICT_TRANS_TABLES,NO_ZERO_IN_DATE,NO_ZERO_DATE,ERROR_FOR_DIVISION_BY_ZERO,NO_ENGINE_SUBSTITUTION’,然后保存并重启服务。

  • CentOS7安装MySQL8(亲测无坑百分百安装成功)

    环境介绍

    操作系统:Centos7.6 MySQL版本: 8.0.30

    本文使用的是当前最新8.0.30版本,如果需要安装的版本比8.0.30高或者低,只要是8.0.*版本,那就可以按照本文说明安装,基本不会有任何问题。

    一、安装前准备

    1.卸载MariaDB

    安装MySQL的话会和MariaDB的文件冲突,所以需要先卸载掉MariaDB。

    1.1 查看是否安装mariadb

    rpm -qa|grep mariadb

    1.2 卸载

    rpm -e --nodeps 文件名

    1.3 检查是否卸载干净

    rpm -qa|grep mariadb

    2.检查依赖

    2.1 查看是否安装libaio

    rpm -qa|grep libaio

    如果没有安装则执行

    yum -y install libaio //安装libaio 

    2.2 查看是否安装numactl

    rpm -qa|grep numactl

    如果没有安装则执行

    yum -y install numactl //安装numactl 

    二、安装MySQL

    1.下载资源包

    可以在官网下载安装包或者在服务器直接使用wget下载。

    1.1 官网下载

    MySQL官网下载地址:https://dev.mysql.com/downloads/mysql/ 在这里插入图片描述

    1.2 wget下载

    wget https://downloads.mysql.com/archives/get/p/23/file/mysql-8.0.30-el7-x86_64.tar

    2.解压

    注:本文安装包上传到了 /usr/local/ 目录下

    进入安装包目录

    cd /usr/local/

    拆分tar包

    tar -xvf mysql-8.0.30-el7-x86_64.tar

    解压安装包

    tar -zxvf mysql-8.0.30-el7-x86_64.tar.gz

    3.重命名

    将解压后的文件夹重命名为mysql

    mv mysql-8.0.30-el7-x86_64/ mysql

    4.创建存储数据文件

    在重命名后的mysql文件夹中创建data文件夹

    mkdir mysql/data

    5.设置用户组并赋权

    创建用户组

    groupadd mysql

    创建用户 -r:创建系统用户 -g:指定用户组

    useradd -r -g mysql mysql

    更改属主和数组

    chown -R mysql:mysql /usr/local/mysql/

    更改权限

    chmod -R 755 /usr/local/mysql/

    6.初始化MySQL

    进入MySQL的bin目录

    cd /usr/local/mysql/bin/

    初始化

    ./mysqld --initialize --user=mysql --datadir=/usr/local/mysql/data --basedir=/usr/local/mysql

    初始化完成后会打印一个随机密码,后面会用到。 在这里插入图片描述

    7.配置参数文件

    vi /etc/my.cnf

    配置文件修改为以下内容,也可以根据自己需要设置参数。

    [client]
    port = 3306
    socket = /usr/local/mysql/data/mysql.sock
    default-character-set = utf8mb4
    
    [mysql]  
    default-character-set = utf8mb4
    
    [mysqld]  
    character-set-client-handshake = FALSE
    character-set-server = utf8mb4
    collation-server = utf8mb4_general_ci
    init_connect = 'SET NAMES utf8mb4'
    
    port = 3306
    socket = /usr/local/mysql/data/mysql.sock
    skip-external-locking
    key_buffer_size = 16M
    max_allowed_packet = 1M
    table_open_cache = 64
    sort_buffer_size = 512K
    net_buffer_length = 8K
    read_buffer_size = 256K
    read_rnd_buffer_size = 512K
    myisam_sort_buffer_size = 8M
    datadir = /usr/local/mysql/data
    #lower_case_table_names=1
    #如果要设置lower_case_table_names可以在初始化里面设置 ./mysqld --initialize --user=mysql --datadir=/usr/local/mysql/data --basedir=/usr/local/mysql --lower_case_table_names=1
    
    [mysqldump]
    quick
    max_allowed_packet = 16M
    
    [mysql]
    no-auto-rehash
    
    [myisamchk]
    key_buffer_size = 20M
    sort_buffer_size = 20M
    read_buffer = 2M
    write_buffer = 2M
    
    [mysqlhotcopy]
    interactive-timeout

    配置后修改 /etc/my.cnf 的权限为777

    chmod 777 /etc/my.cnf 1

    8.启动MySQL

    /usr/local/mysql/support-files/mysql.server start

    9.设置软连接,并重启MySQL

    ln -s /usr/local/mysql/support-files/mysql.server /etc/init.d/mysql
    ln -s /usr/local/mysql/bin/mysql /usr/bin/mysql
    ln -s /usr/local/mysql/mysql.sock /var/mysql.sock
    service mysql restart

    10.登录并更改密码

    [root@localhost bin]# mysql -uroot -p
    Enter password: 输入初始化随机密码

    两种改密方式二选一

    alter user 'root'@'localhost' identified by '123456';
    set password for root@localhost = '123456';

    11.开放远程连接

    mysql>use mysql;
    msyql>update user set user.Host='%' where user.User='root';
    mysql>flush privileges;    //刷新权限

    12.连接工具测试连接MySQL 在这里插入图片描述 如果服务器本地可以连接,但是连接工具远程连接不进去,则需要检查一下防火墙是否放行3306端口,也可以暂时先关闭防火墙后重试。

    关闭防火墙

    systemctl stop firewalld

    13.MySQL启动和停止 CentOS6和CentOS7命令都可以使用。

    启动

    service mysql start
    systemctl start mysql

    停止

    service mysql stop
    systemctl stop mysql

    重启

    service mysql restart
    systemctl restart mysql

    查看状态

    service mysql status
    systemctl status mysql

    三、设置开机自启动(可选)

    将服务文件拷贝到 /etc/init.d下,并重命名为mysqld

    cp /usr/local/mysql/support-files/mysql.server /etc/init.d/mysqld

    赋予可执行权限

    chmod +x /etc/init.d/mysqld

    添加服务

    chkconfig --add mysqld

    显示服务列表

    chkconfig --list

    注:如果看到mysqld的服务,并且3,4,5都是on的话则成功,如果是off,则执行

    chkconfig --level 345 mysqld on

    重启系统

    reboot

    重启后查看mysql是否开机自启动

    ps -ef|grep mysql
  • mysql开发使用规范

    本规范旨在帮助开发人员逐步建立合理使用数据库的意识,对数据库相关的资源申请、业务规范使用等提供规范性的指导,从而为公司业务系统稳定、健康地运行提供保障。

    以下所有规范会按照【强制】、【建议】两个级别进行标注,对于【强制】级别的设计需强制修改调整。


    [TOC]

    开发规范

    对象命名

    命名规范的对象,是指数据库SCHEMA、表TABLE、字段COLUMN、索引INDEX、约束CONSTRAINTS等

    • 【强制】凡是需要命名的对象,其标识符不能超过30个字符
    • 【强制】名称必须以英文字母开头,不得以 _(下划线) 作为起始和终止字母
    • 【强制】所有名称的字符范围为:a-z, 0-9 和_(下划线),禁用大写、特殊符号、保留字、汉字和空格
    • 【强制】所有名称统一使用小写,并采用下划线 _ 分割
    • 【强制】名称应该清晰明了,能够准确表达事物的含义,最好可读,遵循“见名知意”的原则
    • 【建议】数据库账户,一定要做到权限划分明确,读写帐号分离,并且有辨识度,能区分具体业务
      dba内部账户以dba_开头;
      应用账户以user_开头:如user_upc、user_upc_r 分别代表读写、只读账号;
      读写分离不提供额外账户,统一使用应用账户;
      所有账户必须都在主库创建,只读查询只能从非候选上操作;
      
    • 【建议】若按日期时间分表,必须符合 _YYYY[MM][DD] 格式
    • 【建议】若按HASH进行分表,库表名后缀使用十进制数,下标从0开始、下划线分隔、需要补0、每个库的表名相同,比如:
      db_00{table_00 - table_31}
      db_01{table_00 - table_31}
      db_02{table_00 - table_31}
      db_03{table_00 - table_31}
      
    • 【建议】备份用的库、表名须以bak为前缀,以日期yyyymmdd为后缀,比如 bak_order_20160425,便于查找和知道有效期
    • 【建议】临时用的库、表名须以tmp为前缀,以日期yyyymmdd为后缀,比如 tmp_order_20160425,正常业务用到的临时表、中间表,前后缀尽量不要包含 tmp 以免造成歧义

    库表设计

    • 【强制】生产/UAT环境建库建表,请参考《mysql建库建表规范》
    • 【强制】单实例的库数量不得超过20个,再多考虑拆分实例
    • 【强制】单库的表数量不得超过100个,再多考虑拆分库
    • 【强制】单表的字段数量不得超过30个,再多考虑垂直分表
    • 【强制】单表的数据量控制在2000万或数据容量超过10G以内,否则考虑归档或分库分表
    • 【强制】单表的分表数量不得超过256个
    • 【建议】如无特殊需求,必须使用Innodb存储引擎
      解读:支持事务、行级锁、并发性能更好、CPU及内存缓存页优化使得资源利用率更高;
    • 【建议】如无特殊需求,必须使用utf8mb4字符集,排序规则使用utf8mb4_unicode_ci
      解读:万国码,无需转码,无乱码风险,节省空间,utf8mb4更可保存emoj表情(utf8不行);
    • 【强制】新建的库表必须添加注释
      解读:N年后鬼知道这个r1,r2,r3字段是干嘛的
    • 【建议】表都必须要显式指定主键,推荐自增id主键
      解读:
      a)主键递增,数据行写入可以提高插入性能,可避免page分裂,减少表碎片提升空间和内存的使用
      b)主键要选择较短的数据类型,Innodb引擎普通索引都会保存主键的值,较短的数据类型可以有效的减少索引的磁盘空间,提高索引的缓存效率
      c)无主键的表删除,在row模式的主从架构,会导致备库夯住
      
    • 【强制】禁止使用外键,外键功能请在应用层实现
      解读:外键使得表之间相互耦合,影响update/delete等SQL性能,有可能造成死锁,高并发情况下容易成为数据库瓶颈
    • 【建议】日志类型的表必须提前规划轮转机制或者选择定期清理/归档 或者选择合适的db,比如hbase/mongodb
    • 【建议】建议将大字段,访问频度低的字段拆分到单独的表中存储,分离冷热数据
    • 【强制】数据库中不允许存储明文密码
    • 【建议】如果对时间精度有要求,建表的时候请注意一下时间精度,比如 timestamp 与 timestamp(3);
    • 【强制】无特殊需求,严禁使用分区表

    字段设计

    • 【强制】各表之间相同意义的字段必须同名
    • 【建议】字段类型在满足需求条件下越小越好,使用unsigned存储非负整数 ,实际使用时候存储负数场景不多
    • 【建议】数值类型优于字符类型,所以能使用数值类型的尽量不要使用字符类型
    • 【强制】小数类型应选择精确度高的decimal等类型,禁止使用float和double
      解读:浮点数(float和double)在存储的时候,超过指定精度后会四舍五入,这是浮点数特有的问题。因此在精度要求比较高的应用中(比如货币)要使用定点数(decimal)而不是浮点数(float和double)来保存数据。
    • 【建议】合理选择char、varchar、text等字符串类型
      对于长度基本固定的小字符类型,如果该列恰好更新又特别频繁,适合char
      不固定长度的大字符类型,应选择varchar类型,varchar(N),N代表的是字符数,N尽可能的小
      varchar虽然存储变长字符串,UTF8最多能存21844个汉字,或65532个英文
      
    • 【强制】禁止使用BLOB和TEXT字段。如要使用,尽可能把text/blob拆到独立的表中,用PK与主表关联;
    • 【强制】禁止在数据库中存储大文件,例如图片、文件等;
    • 【建议】合理选择bit、int、tinyint、decimal等数字类型
      使用tinyint来代替 enum和boolean
      使用Decimal 代替float/double存储精确浮点数
      建议使用 UNSIGNED 存储非负数值
      int使用固定4个字节存储,int(11)与int(4)只是显示宽度的区别
      列禁止使用bit类型,请用tinyint类型替代。bit类型加了索引可能会导致sql结果不准。
      
    • 【建议】合理选择timestamp与datetime等时间类型
      timestamp可以在insert/update行时,自动更新时间字段;
      列为timestamp类型,必须指定默认值,要么current_timestamp,要么'1970-01-02 01:01:01',不要设置为''或0;
      解读:DATETIME和TIMESTAMP都可用来表示YYYY-MM-DD HH:MM:SS类型的日期。两种都保存日期和时间信息,毫秒部分最高精确度都是6位数。建议使用TIMESTAMP(3)。
      A. TIMESTAMP占用4字节,DATETIME占用8字节,当保存毫秒部分时两者都使用额外的空间 (1-3 字节)。
      B. TIMESTAMP的取值范围比DATETIME小得多,不适合存放比较久远的日期。TIMESTAMP只能存储从 '1970-01-01 00:00:01.000000' 到 '2038-01-19 03:14:07.999999' 之间的时间。而DATETIME允许存储从 '1000-01-01 00:00:00.000000' 到 '9999-12-31 23:59:59.999999' 之间的时间。
      C. TIMESTAMP的插入和查询受时区的影响。如果记录的日期需要让不同时区的人使用,最好使用 TIMESTAMP。
      
    • 【建议】如无特殊需要,字段建议使用NOT NULL属性,可用默认值代替NULL。如果是索引字段,一定要定义为not null。因为null值会影响cordinate统计和优化器对索引的选择
    • 【强制】禁止在列上配置字符集
    • 【建议】使用unsigned int存储IPv4,不要使用char(15)
      解读:ip转int使用 INET_ATON 函数,int转ip使用 INET_NTOA 函数,比如:select inet_aton(‘192.168.1.1’),inet_ntoa(3232235777);
    • 【建议】使用varchar(20)存储手机号,不要使用整数
      解读:
      1)涉及到国家代号,可能出现+/-/()等字符,例如+86
      2)手机号不会用来做数学运算
      3)varchar可以模糊查询,例如 like '138%'
      

    索引设计

    • 【强制】单表索引的数量不得超过5个,否则增加维护负担、降低写入性能、占用更多空间
    • 【建议】建立的索引能覆盖80%主要的查询,不求全,解决问题的主要矛盾
    • 【建议】主键应选择不重复、长度小的列,没有特殊要求,使用自增id作为主键
    • 【建议】自增列的名字固定为id,指定unsigned,类型2选1(int/bigint),自增列的值必须从1开始
    • 【建议】主键禁止使用字符类型,禁止使用联合主键,推荐使用唯一索引来替代
    • 【建议】业务上具有唯一特性的字段,必须创建唯一索引
    • 【建议】不建议在频繁更新的字段上建立索引
    • 【建议】索引尽量建在选择性高的列上,不在低基数列上建立索引,例如性别、类型
      选择性的计算方式为: select count(distinct(col_name))/count(*) from tb_name
      如果结果小于0.2,则不建议在此列上创建索引,否则大概率会拖慢SQL执行
      
    • 【建议】合理利用覆盖索引,联合索引,避免过多的单列索引,合理使用索引来避免排序和临时表的使用
    • 【建议】对超过30个字符长度的列创建索引时,考虑使用前缀索引,比如 idx_cs_guid2 (f_cs_guid(26)) 表示截取前26个字符做索引,既可以提高查找效率,也可以节省空间
      解读:前缀索引的缺点是,如果在该列上 ORDER BY 或 GROUP BY 时无法使用索引,也不能把它们用作覆盖索引(Covering Index)
    • 【建议】blob列不能作为key
    • 【建议】bit类型字段禁止单独加索引,或者做联合索引的第一列
    • 【建议】尽量使用Btree索引,不要使用其它类型索引
    • 【建议】DML和order by和group by字段要建立合适的索引

    关于组合索引的几点建议

    • 【强制】组合索引的字段数量不得超过5个
      解读:如果5个字段还不能极大缩小row范围,八成是设计有问题
    • 【建议】组合索引 – 选择性高的永远在左边
    • 【建议】组合索引 – 避免冗余索引,比如:(a,b,c)、(a,b)、(a),后二者为冗余索引
    • 【建议】组合索引的最左匹配原则:数据库引擎使用组合索引时,从左向右(并非where条件顺序)匹配,遇到范围查询(>、<、between、like)则会停止索引匹配,无法用到后续的索引列。where条件里面字段的顺序与索引顺序无关,优化器会自动调整顺序。
      比如索引idx_a_b_c(a,b,c),相当于创建了(a)、(a,b)、(a,b,c)三个索引,后二者为冗余索引:
      ①where a=?                  用到(a)        ⑦where c=?           用不到
      ②where b=? and a=?          用到(a,b)      ⑧where b=? and c=?   用不到
      ③where a=? and c=?          用到(a)
      ④where a>? and b=?          用到(a)
      ⑤where a=? and b=? and c>?  用到(a,b,c)
      ⑥where a=? and b>? and c=?  用到(a,b)
      

    SQL设计

    • 【强制】禁止使用游标、存储过程、视图、触发器、自定义函数、event
      解读:高并发大数据的互联网业务,架构设计思路是“解放数据库CPU,将计算转移到服务层”,并发量大的情况下,这些功能很可能将数据库拖死,业务逻辑放到服务层具备更好的扩展性,能够轻易实现“增机器就加性能”。数据库擅长存储与索引,涉及CPU计算的还是放到应用服务器上吧
    • 【建议】建议将复杂的计算和逻辑操作放到程序端处理,而不是使用SQL语句处理,因为程序端方便扩容,数据库端扩容能力有限
    • 【建议】数据结构设计时,可以适当设计冗余字段,减少查询的复杂度,提高查询的性能
    • 【强制】禁止在数据库中存储大文件,例如图片、文件等,可以将大文件存储在对象存储系统,数据库中存储路径
    • 【强制】禁止使用全文检索(Full Text Search),后续有这种需求使用搜索引擎处理
    • 【强制】禁止在没有匹配索引的表上进行for update这类的操作,会锁定整个表
    • 【强制】未经过DBA同意,禁止在程序端大批量更新或者删除数据,因为这样操作很可能造成复制的大量阻塞和延时,批量归档/删除,可以向dba发邮件提需求,由dba来处理
    • 【强制】禁止在生产/线上环境进行代码逻辑或SQL语句性能的测试,这类操作应在开发或者测试环境进行
    • 【建议】减少锁等待和竞争,避免大事务,使用短小事务
    • 【建议】尽量避免使用子查询,使用join来代替
    • 【强制】禁止使用order by rand()
    • 【建议】多行数据需要做处理时,建议批量处理,而不是一条条来处理
    • 【强制】单表的数据量控制在2000万或数据容量超过10G以内,否则考虑归档或分库分表
    • 【强制】避免大表join,禁止3个大表的join,join字段类型需保持绝对一致,关联字段必须有索引
    • 【建议】线上业务修改或删除数据,务必根据主键来实现
    • 【建议】禁止使用 SELECT * ,必须明确指定列
    • 【建议】insert必须指定字段,禁止使用 insert into xxx values()
      解读:指定字段插入,在表结构变更时,能保证对应用程序无影响
    • 【建议】能确定返回结果只有一条时,使用 limit 1
    • 【建议】避免隐式类型转换
    • 【建议】禁止在where条件列上使用函数
    • 【建议】限制使用like模糊匹配,禁止使用左模糊或者全模糊,%不要放首位
    • 【建议】涉及到复杂sql时,务必先参考已有索引设计,先explain
    • 【建议】考虑使用union all,少使用union,注意考虑去重
    • 【建议】IN的内容尽量不超过200个
    • 【建议】建议在每条查询语句后面加上Limit关键字,控制返回的数据量,防止不可控的返回大量的数据
    • 【建议】禁止大批量的查询数据结果,如果需要返回大量数据,请使用分页的方式处理,遇到分页有大的offset查询,可以使用延迟关联来解决
    • 【建议】分页优化
      SELECT * FROM tel_record t1 INNER JOIN (SELECT id FROM tel_record WHERE qiye_id = xxx ORDER BY id DESC LIMIT 999900,100) t2 ON t1.f_id = t2.f_id;
      程序端保留当前页的最小id、最大id(id是主键),降序情况下,每次提取下一页的数据时,id < min_id order by id desc limit 100; 上一页 id > max_id order by id desc limit 100
      
    • 【建议】大表count是消耗资源的操作,甚至会拖慢整个库,查询性能问题无法解决的,应从产品设计上进行重构。例如当频繁需要count的查询,考虑使用汇总表
    • 【建议】数据库的隔离级别默认为READ-COMMITTED,如不能满足业务需求,可在session层面做相应调整(必须清楚相应的隔离级别带来的锁影响)
    • 【建议】数据库默认的sql_mode为严格模式(STRICT_TRANS_TABLES,NO_ENGINE_SUBSTITUTION),在此模式下任何不支持的语法或者数据校验不合格的,都将直接返回错误
    • 【建议】日志类数据不建议存储在MySQL上,优先考虑mongodb,如需要存储请找DBA评估使用压缩表存储
    • 【建议】禁止使用ENUM,可使用TINYINT代替
      解读:增加新的ENUM值要做DDL操作;ENUM的内部实际存储就是整数而非字符串;
    • 【建议】禁止使用OR条件,必须改为IN查询,并注意in的个数小于200
      解读:旧版本mysql的OR查询是不能命中索引的,即使能命中索引,为何要让数据库耗费更多的CPU帮助实施查询优化呢? 补充:通常情况下,如果条件中有or,即使其中有条件带索引也不会使用,所以除非每个列都建立了索引,否则不建议使用OR。在多列OR中,建议用UNION ALL替换。
    • 【建议】所有连接的SQL必须使用 join … on … 方式进行连接,而不允许直接通过普通的where条件关联方式。外连接的SQL语句,可以使用left join on的join方式,且所有外连接一律写成left join,而不要使用right join
    • 【建议】避免使用否定条件。
      例如,where 条件里面有<>、not in 、not exists的时候,即便是在这些判断字段上加有索引,也不会起作用。
    • 【建议】有NULL值的字段查询
      解读:
      A.不要使用count(列名)或者count(常量)来替代 count(*),count(*)是SQL92定义的标准统计行数的语法,跟数据库无关,跟null和非null无关。count(*)会统计值为null的行,而count(列名)不会统计此列为null的行。
      B.count(distinct col)计算该列除null之外不重复的行数。count(distinct col1, col2),如果其中一列全为null,那么即使另一列有不同的值,也返回0
      C.当某一列的值全为null,count(col)的返回结果为0,但sum(col)的返回结果为null,因此使用sum()时需要注意空指针异常的问题。可以使用ISNULL()来判断是否为NULL值: SELECT IF(ISNULL(SUM(g)), 0, SUM(g)) FROM table;
      
    • 【建议】SQL合并,主要是指的DML时候多个value合并,减少和数据库交互

    使用规范

    开发行为规范

    • 【强制】推广活动或上线新功能必须提前通知DBA进行流量评估
    • 【强制】禁止在线上环境申请个人账号,只能申请业务使用的账号
    • 【强制】超过10w条的大批量更新,如修复数据、导入导出,避开高峰期,并通知DBA。可直接执行的sql由DBA操作
    • 【建议】及时处理已下线业务的SQL
    • 【建议】复杂sql要主动上报DBA评估,比如多表join/count/group by等
    • 【建议】重要项目的数据库方案选型和设计必须提前通知DBA参与
    • 【建议】对单表的多次alter操作必须合并为一次操作
    • 【强制】分库分表情况下,确保所有库表字段顺序一致
    • 【强制】所有数据库账号必须在主库创建
    • 【强制】禁止在数据库中存放业务逻辑SQL
    • 【建议】对特别重要的库表,提前与DBA沟通确定维护和备份优先级
    • 【建议】维护脚本须部署在关联实例机器上,切勿乱放,比如归档,拉数据等
    • 【建议】线上大批量delete和update,为了降低锁影响和减少从库延迟,必须批量执行,思路参考pt-archiver
    • 【强制】线上业务不允许申请truncate、drop权限,如果需要删除表,请提交工单并由dba操作
    • 【建议】线上不允许建立业务相关数据库JOB,业务逻辑在代码层实现
    • 【建议】线上不允许使用长事务,慎用set autocommit=0或者begin,推荐使用set autocommit=1,超时5秒的事务,DBA JOB会自动kill

    线上变更规范

    • 【强制】生产系统变更数据,请参考《线上变更规范》
    • 【建议】数据变更流程,可参考下图:

    mysql-upd-eflow

  • mysql究竟是否适合运行在docker容器中

    目前,容器和 Docker 依旧是技术领域最热门的词语,无状态的服务容器化已经是大势所趋,同时也带来了一个热点问题被大家所争论不以:数据库 MySQL 是否需要容器化?

    [TOC]

    认真分析大家的各种观点,发现赞同者仅仅是从容器优势的角度来阐述 MySQL 需要容器化,几乎没有什么业务场景进行验证自己的观点

    反过来再看反对者,他们从性能、数据安全等多个因素进行阐述 MySQL不需要容器化,也举证了一些不适合的业务场景。

    下面,我们就聊一下 Docker 不适合跑 MySQL 的 N 个原因!

    数据安全问题

    不要将数据储存在容器中,这也是 Docker 官方容器使用技巧中的一条。容器随时可以停止、或者删除。当容器被rm掉,容器里的数据将会丢失。

    为了避免数据丢失,用户可以使用数据卷挂载来存储数据。但是容器的 Volumes 设计是围绕 Union FS 镜像层提供持久存储,数据安全缺乏保证。如果容器突然崩溃,数据库未正常关闭,可能会损坏数据。另外,容器里共享数据卷组,对物理机硬件损伤也比较大。

    性能问题

    大家都知道,MySQL 属于关系型数据库,对IO要求较高。当一台物理机跑多个时,IO就会累加,导致IO瓶颈,大大降低 MySQL 的读写性能。

    在一次Docker应用的十大难点专场上,某国有银行的一位架构师也曾提出过:“数据库的性能瓶颈一般出现在IO上面,如果按 Docker 的思路,那么多个docker最终IO请求又会出现在存储上面。现在互联网的数据库多是share nothing的架构,可能这也是不考虑迁移到 Docker 的一个因素吧”。

    其实也有相对应的一些策略来解决这个问题,比如:

    1)数据库程序与数据分离

    如果使用Docker 跑 MySQL,数据库程序与数据需要进行分离,将数据存放到共享存储,程序放到容器里。如果容器有异常或 MySQL 服务异常,自动启动一个全新的容器。

    2)跑轻量级或分布式数据库

    Docker 里部署轻量级或分布式数据库,Docker 本身就推荐服务挂掉,自动启动新容器,而不是继续重启容器服务。

    3)合理布局应用

    对于IO要求比较高的应用或者服务,将数据库部署在物理机或者KVM中比较合适。目前腾讯云的TDSQL和阿里的Oceanbase都是直接部署在物理机器,而非Docker 。

    状态问题

    在 Docker 中水平伸缩只能用于无状态计算服务,而不是数据库。

    Docker 快速扩展的一个重要特征就是无状态,具有数据状态的都不适合直接放在 Docker 里面,如果 Docker 中安装数据库,存储服务需要单独提供。

    目前,腾讯云的TDSQL(金融分布式数据库)和阿里云的Oceanbase(分布式数据库系统)都直接运行中在物理机器上,并非使用便于管理的 Docker 上。

    资源隔离方面

    资源隔离方面,Docker 确实不如虚拟机KVM,Docker是利用Cgroup实现资源限制的,只能限制资源消耗的最大值,而不能隔绝其他程序占用自己的资源。如果其他应用过渡占用物理机资源,将会影响容器里 MySQL 的读写效率。

    需要的隔离级别越多,获得的资源开销就越多。相比专用环境而言,容易水平伸缩是Docker的一大优势。然而在 Docker 中水平伸缩只能用于无状态计算服务,数据库并不适用。

    难道 MySQL 不能跑在容器里吗?

    MySQL 也不是全然不能容器化。以下几种场景还是适合的。

    • 对数据丢失不敏感的业务(例如用户搜索商品)就可以数据化,利用数据库分片来来增加实例数,从而增加吞吐量。
    • docker适合跑轻量级或分布式数据库,当docker服务挂掉,会自动启动新容器,而不是继续重启容器服务。
    • 数据库利用中间件和容器化系统能够自动伸缩、容灾、切换、自带多个节点,也是可以进行容器化的。

    典型案例:同程旅游、京东、阿里的数据库容器化都是不错的案例,大家可以自行去查看。

  • PostgreSQL数据库的安装配置教程(CentOS系统为例)

    PostgreSQL 被业界誉为“世界上最先进的开源关系型数据库”,虽然 PostgreSQL 是关系型数据库,但其也支持 NoSQL 数据类型(JSON/XML/hstore),并且性能甚至超过了 MongoDB。本文演示如何在 CentOS 7 系统下安装配置 PostgreSQL 数据库。

    1,安装 PostgreSQL

    (1)首先访问官方的下载页面(点击访问),选择合适的版本:
    2021031115365563670
    (2)根据页面上的说明安装 rpm 文件:
    1
    sudo yum install -y https: //download.postgresql.org/pub/repos/yum/reporpms/EL-7-x86_64/pgdg-redhat-repo-latest.noarch.rpm
  • MySQL数字的取整、四舍五入、保留n位小数

    数学函数是MySQL中常用的一类函数。其主要用于处理数字,包括整型和浮点数等等。

    MySQL常用的四舍五入函数:

    函数 说明
    FLOOR(X) 返回不大于X的最大整数。
    CEIL(X)、CEILING(X) 返回不小于X的最小整数。
    TRUNCATE(X,D) 返回数值X保留到小数点后D位的值,截断时不进行四舍五入。
    ROUND(X) 返回离X最近的整数,截断时要进行四舍五入。
    ROUND(X,D) 保留X小数点后D位的值,截断时要进行四舍五入。
    FORMAT(X,D) 将数字X格式化,将X保留到小数点后D位,截断时要进行四舍五入。

    1、FLOOR(X)函数

    返回不大于X的最大整数。

    SELECTFLOOR(1.3); -- 输出结果:1SELECTFLOOR(1.8); -- 输出结果:1

    2、CEIL(X)、CEILING(X)函数

    返回不小于X的最小整数。

    SELECTCEIL(1.3);    -- 输出结果:2SELECTCEILING(1.8); -- 输出结果:2

    3、TRUNCATE(X,D)函数

    返回数值X保留到小数点后D位的值,截断时不进行四舍五入。

    SELECT TRUNCATE(1.2328,3); -- 输出结果:1.232 

    4、ROUND(X)函数

    返回离X最近的整数,截断时要进行四舍五入。

    SELECT ROUND(1.3);  -- 输出结果:1SELECT ROUND(1.8);  -- 输出结果:2

    5、ROUND(X,D)函数

    保留X小数点后D位的值,截断时要进行四舍五入。

    SELECT ROUND(1.2323,3);  -- 输出结果:1.232SELECT ROUND(1.2328,3);  -- 输出结果:1.233

    6、FORMAT(X,D)函数

    将数字X格式化,将X保留到小数点后D位,截断时要进行四舍五入。

    SELECT FORMAT(1.2323,3);  -- 输出结果:1.232SELECT FORMAT(1.2328,3);  -- 输出结果:1.233
  • MySQL删除表数据、清空表命令(truncate、drop、delete 区别)

    一、MySQL清空表数据三种方法

    1.1 清空表数据:truncate

    • sql命令

    代码语言:sql

    复制

    #清空多张表、库中所有表的数据
    truncate table table_name1,table_name2,...;
    
    #清空单张表的数据
    truncate table table_name;
    • 注意:
    • truncate会删除表中的所有数据、释放空间,但是保留表结构
    • 只能操作表,不能与where一起使用
    • truncate删除操作立即生效,原数据不放到rollback segment中,不能rollback,操作不触发trigger
    • truncate删除数据后会释放表空间、重置Identity(标识列、自增字段),相当于自增列会被置为初始值,又重新从1开始记录、而非接着原来的id数
    • truncate删除数据后不写服务器log,整体删除速度快

    1.2 删除表:drop

    • sql命令

    代码语言:sql

    复制

    drop table table_name;
    drop table if exists table_name;
    • 注意:
      • drop会删除整个表,包括表结构和数据,释放空间
      • 立即执行,执行速度最快
      • 不可回滚

    1.3 删除/清空表数据:delete

    • sql命令

    代码语言:sql

    复制

    #删除部分数据
    delete from tb_name where clause;
    
    #清空表,仅删除数据、保留表结构,同时也不释放表空间
    delete from tb_name;
    • 注意:
      • 删除表中数据而不删除表结构,也不释放空间
      • delete可以删除一行、多行、乃至整张表
      • 每次删除一行,都在事务日志中为所删除的每行记录一项,可回滚
      • 如果不加where条件,表示删除表中所有数据,仅删除数据、保留表结构,同时也不释放表空间

    MySQL、Mariadb、PostgreSQL删除表数据、清空表命令 都可用以上三种命令。

    二、使用原则

    使用原则总结如下:

    • 当你不需要该表时(删除数据和结构),用drop;
    • 当你仍要保留该表、仅删除所有数据表内容时,用truncate;
    • 当你要删除部分记录、且希望能回滚的话,用delete;

    在没有备份的情况下,谨慎使用drop、truncate。

    在实际应用中,要根据具体需求和场景选择合适的操作。

    三、truncate、drop、delete区别

    truncate、delete和drop都是用于删除数据或表的操作,但它们之间有一些关键的区别:

    1. 操作类型:
      • truncate:删除表中的所有数据,保留表结构,释放空间。它是一种DDL(数据定义语言)操作,执行速度较快。
      • delete:删除表中的特定行,可以逐行删除,保留表结构,也不释放空间。它是一种DML(数据操作语言)操作,执行速度较慢。
      • drop:删除整个表,包括表结构和数据,释放空间。它是一种DDL操作,但不同于truncate,它会释放表所占用的空间。
    2. 数据删除方式:
      • truncate:删除表中的所有数据,但不会删除表结构。适用于需要保留表结构的情况。
      • delete:删除表中的特定数据,可以根据条件删除,表的结构和约束保持不变。适用于需要根据特定条件删除数据的情况。
      • drop:删除整个表,包括表结构和数据。适用于不再需要表结构的情况。
    3. 执行速度:drop > truncate > delete
      • truncate:执行速度较快,因为它一次性删除所有数据,过释放表的存储空间来删除数据,并将表重置为初始状态。
      • delete:执行速度较慢,因为它需要逐行删除数据,并且会生成大量的事务日志,同时也不释放空间。
      • drop:执行速度较快,因为它一次性删除整个表。
    4. 回滚能力:
      • truncate:不可回滚,一旦执行,数据将被永久删除、无法恢复。
      • delete:可以回滚,使用ROLLBACK语句可以撤销删除操作。
      • drop:不可回滚,一旦执行,表结构和数据都将被永久删除。
    5. 触发器:
      • truncate:不会触发触发器。
      • delete:会触发触发器。
      • drop:不会触发触发器,因为它是删除整个表。

    总结:

    • 如果您需要快速删除整个表中的数据,但保留表结构,可以选择truncate。
    • 如果您需要删除特定行的数据,可以根据条件删除,可以选择delete。
    • 如果您需要删除整个表,包括表结构和数据,可以选择drop。 在实际应用中,要根据具体需求和场景选择合适的操作。
  • MySQL中EXISTS的用法

    比如在Northwind数据库中有一个查询为

    SELECT c.CustomerId,CompanyName FROM Customers c
    WHERE EXISTS(
    SELECT OrderID FROM Orders o WHERE o.CustomerID=c.CustomerID) 

    这里面的EXISTS是如何运作呢?子查询返回的是OrderId字段,可是外面的查询要找的是CustomerID和CompanyName字段,这两个字段肯定不在OrderID里面啊,这是如何匹配的呢? 

    EXISTS用于检查子查询是否至少会返回一行数据,该子查询实际上并不返回任何数据,而是返回值True或False
    EXISTS 指定一个子查询,检测 行 的存在。

    语法: EXISTS subquery
    参数: subquery 是一个受限的 SELECT 语句 (不允许有 COMPUTE 子句和 INTO 关键字)。
    结果类型: Boolean 如果子查询包含行,则返回 TRUE ,否则返回 FLASE 。

    例表A:TableIn 例表B:TableEx
    tableA tableB


    (一). 在子查询中使用 NULL 仍然返回结果集
    select * from TableIn where exists(select null)
    等同于: select * from TableIn
    tableA 
    (二). 比较使用 EXISTS 和 IN 的查询。注意两个查询返回相同的结果。
    select * from TableIn where exists(select BID from TableEx where BNAME=TableIn.ANAME)
    select * from TableIn where ANAME in(select BNAME from TableEx)
    008
    (三). 比较使用 EXISTS 和 = ANY 的查询。注意两个查询返回相同的结果。
    select * from TableIn where exists(select BID from TableEx where BNAME=TableIn.ANAME)
    select * from TableIn where ANAME=ANY(select BNAME from TableEx)
    008
    NOT EXISTS 的作用与 EXISTS 正好相反。如果子查询没有返回行,则满足了 NOT EXISTS 中的 WHERE 子句。

    结论:
      EXISTS(包括 NOT EXISTS )子句的返回值是一个BOOL值。 EXISTS内部有一个子查询语句(SELECT … FROM…), 我将其称为EXIST的内查询语句。其内查询语句返回一个结果集。 EXISTS子句根据其内查询语句的结果集空或者非空,返回一个布尔值。

    一种通俗的可以理解为:将外查询表的每一行,代入内查询作为检验,如果内查询返回的结果取非空值,则EXISTS子句返回TRUE,这一行行可作为外查询的结果行,否则不能作为结果。

    例如:

    1.  查询id为5的数据:  (数据存在)

    copycode
        SELECT *
        FROM class AS c1
        WHERE EXISTS(SELECT
                   class_id
                 FROM class AS c2
                 WHERE c1.class_id = 
    5);
    copycode

      如果exists里面返回的结果行数大于1,则返回true,则外面的查询数据可以返回。

    1196212-20180318224033032-740838109

     2.  查询id为10的数据:  (数据不存在)

    copycode
        SELECT *
        FROM class AS c1
        WHERE EXISTS(SELECT
                   class_id
                 FROM class AS c2
                 WHERE c1.class_id = 
    10);
    copycode

       因为exsits始终返回的是false,所以外层查询始终无效,也就不会产生数据。

     

      

      分析器会先看语句的第一个词,当它发现第一个词是SELECT关键字的时候,它会跳到FROM关键字,然后通过FROM关键字找到表名并把表装入内存。接着是找WHERE关键字,如果找不到则返回到SELECT找字段解析,如果找到WHERE,则分析其中的条件,完成后再回到SELECT分析字段。最后形成一张我们要的虚表。
      WHERE关键字后面的是条件表达式。条件表达式计算完成后,会有一个返回值,即非0或0,非0即为真(true),0即为假(false)。同理WHERE后面的条件也有一个返回值,真或假,来确定接下来执不执行SELECT。
      分析器先找到关键字SELECT,然后跳到FROM关键字将STUDENT表导入内存,并通过指针找到第一条记录,接着找到WHERE关键字计算它的条件表达式,如果为真那么把这条记录装到一个虚表当中,指针再指向下一条记录。如果为假那么指针直接指向下一条记录,而不进行其它操作。一直检索完整个表,并把检索出来的虚拟表返回给用户。EXISTS是条件表达式的一部分,它也有一个返回值(true或false)。

    在插入记录前,需要检查这条记录是否已经存在,只有当记录不存在时才执行插入操作,可以通过使用 EXISTS 条件句防止插入重复记录。
    INSERT INTO TableIn (ANAME,ASEX) 
    SELECT top 1 ‘张三’, ‘男’ FROM TableIn
    WHERE not exists (select * from TableIn where TableIn.AID = 7)

    EXISTS与IN的使用效率的问题,通常情况下采用exists要比in效率高,但要看实际情况具体使用:
    IN适合于外表大而内表小的情况;EXISTS适合于外表小而内表大的情况。

    in、not in、exists和not exists的区别:

    先谈谈in和exists的区别:
    exists:存在,后面一般都是子查询,当子查询返回行数时,exists返回true。

    select * from class where exists (select'x"form stu where stu.cid=class.cid)


    当in和exists在查询效率上比较时,in查询的效率快于exists的查询效率
    exists(xxxxx)后面的子查询被称做相关子查询, 他是不返回列表的值的.
    只是返回一个ture或false的结果(这也是为什么子查询里是select ‘x’的原因 当然也可以
    select任何东西) 也就是它只在乎括号里的数据能不能查找出来,是否存在这样的记录。

    其运行方式是先运行主查询一次 再去子查询里查询与其对应的结果 如果存在,返回ture则输 出,反之返回false则不输出,再根据主查询中的每一行去子查询里去查询.

    执行顺序如下:
    1.首先执行一次外部查询
    2.对于外部查询中的每一行分别执行一次子查询,而且每次执行子查询时都会引用外部查询中当
    前行的值。

    3.使用子查询的结果来确定外部查询的结果集。
    如果外部查询返回100行,SQL   就将执行101次查询,一次执行外部查询,然后为外部查询返回
    的每一行执行一次子查询。

    in:包含

    查询和所有女生年龄相同的男生

    select * from stu where sex=’男’ and age in(select age from stu where sex=’女’)


    in()后面的子查询 是返回结果集的,换句话说执行次序和exists()不一样.子查询先产生结果集,然后主查询再去结果集里去找符合要求的字段列表去.符合要求的输出,反之则不输出.


    not in和not exists的区别:
    not in 只有当子查询中,select 关键字后的字段有not null约束或者有这种暗示时用not in,另外如果主查询中表大,子查询中的表小但是记录多,则应当使用not in,
    例如:查询那些班级中没有学生的,
    select * from class where cid not in(select distinct cid from stu)
    当表中cid存在null值,not in 不对空值进行处理
    解决:select * from class

    where cid not in

    (select distinct cid from stu where cid is not null)


    not in的执行顺序是:是在表中一条记录一条记录的查询(查询每条记录)符合要求的就返回结果集,不符合的就继续查询下一条记录,直到把表中的记录查询完。也就是说为了证明找不到,所以只能查询全部记录才能证明。并没有用到索引。
    not exists:如果主查询表中记录少,子查询表中记录多,并有索引。
    例如:查询那些班级中没有学生的,
    select * from class2

    where not exists

    (select * from stu1 where stu1.cid =class2.cid)


    not exists的执行顺序是:在表中查询,是根据索引查询的,如果存在就返回true,如果不存在就返回false,不会每条记录都去查询。
    之所以要多用not exists,而不用not in,也就是not exists查询的效率远远高与not in查询的效率。

     实例:

    exists,not exists的使用方法示例,需要的朋友可以参考下。

    学生表:create table student
    (
     id number(8) primary key,
     name varchar2(10),deptment number(8)
    )
    选课表:create table select_course
    (
      ID         NUMBER(8) primary key,
      STUDENT_ID NUMBER(8) foreign key (COURSE_ID) references course(ID),
      COURSE_ID  NUMBER(8) foreign key (STUDENT_ID) references student(ID)
    )
    课程表:create table COURSE
    (
      ID     NUMBER(8) not null,
      C_NAME VARCHAR2(20),
      C_NO   VARCHAR2(10)
    )
    student表的数据:
            ID NAME            DEPTMENT_ID
    ---------- --------------- -----------
             1 echo                   1000
             2 spring                 2000
             3 smith                  1000
             4 liter                  2000
    course表的数据:
            ID C_NAME               C_NO
    ---------- -------------------- --------
             1 数据库               data1
             2 数学                 month1
             3 英语                 english1
    select_course表的数据:
            ID STUDENT_ID  COURSE_ID
    ---------- ---------- ----------
             1    1         1
             2    1         2
             3    1         3
             4    2         1
             5    2         2
             6    3         2
    1.查询选修了所有课程的学生id、name:(即这一个学生没有一门课程他没有选的。)
    分析:如果有一门课没有选,则此时(1)select * from select_course sc where sc.student_id=ts.id 
    and sc.course_id=c.id存在null,
    这说明(2)select * from course c 的查询结果中确实有记录不存在(1查询中),查询结果返回没有选的课程,
    此时select * from t_student ts 后的not exists 判断结果为false,不执行查询。
    SQL> select * from t_student ts where not exists
     (select * from course c where not exists
       (select * from select_course sc where sc.student_id=ts.id and sc.course_id=c.id));       
            ID NAME            DEPTMENT_ID
    ---------- --------------- -----------
             1 echo                   1000
    2.查询没有选择所有课程的学生,即没有全选的学生。(存在这样的一个学生,他至少有一门课没有选),
    分析:只要有一个门没有选,即select * from select_course sc where student_id=t_student.id and course_id
    =course.id 有一条为空,即not exists null 为true,此时select * from course有查询结果(id为子查询中的course.id ),
    因此select id,name from t_student 将执行查询(id为子查询中t_student.id )。
    SQL> select id,name from t_student where exists
     (select * from course where not exists
         (select * from select_course sc where student_id=t_student.id and course_id=course.id));
            ID NAME
    ---------- ---------------
             2 spring
             3 smith
             4 liter
    3.查询一门课也没有选的学生。(不存这样的一个学生,他至少选修一门课程),
    分析:如果他选修了一门select * from course结果集不为空,not exists 判断结果为false;
    select id,name from t_student 不执行查询。
    SQL> select id,name from t_student where not exists
     (select * from course where exists
         (select * from select_course sc where student_id=t_student.id and course_id=course.id));
            ID NAME
    ---------- ---------------
             4 liter
    4.查询至少选修了一门课程的学生。
    SQL> select id,name from t_student where exists
     (select * from course where  exists
         (select * from select_course sc where student_id=t_student.id and course_id=course.id));
            ID NAME
    ---------- ---------------
             1 echo
             2 spring
             3 smith


    补充:in 走不走索引还是要视情况而定

    in在某些情况下会走、某些情况下不会走索引。  有些情况下Mysql会判断不走索引更快,会进行全表扫描,比如数据量只有几条的时候;在数据量大的时候in会走索引
    测试如下:
    (1) 查看索引
    show index from user;
    1196212-20210226102338424-173474452

     (2)   数据量只有两条的时候测试: 这时候没有走索引,走的全表扫描

    1196212-20210226102355054-669707660

     (3) 数据量大的时候进行的测试(数据量到达千条以上)

    1196212-20210226102420204-1575624160

      测试二: 子查询测试

    1196212-20210226102435261-1596447844
  • mysql查询按照指定字段的指定顺序进行排序

    之前我们已经了解的mysql按照中文进行排序的实现方法了:https://www.wj0511.com/site/detail.html?id=420

    那么如何按照指定字段的指定顺序进行排序呢?

    例如有一个用户表user,有id,username,status字段,status的字段值有1,2,3,4四种情况,如何将用户表中的数据按照status字段的2,4,1,3顺序进行排序呢?

    这时候需要使用到FIELD或者FIND_IN_SET方法,如:

    SELECT * FROM user ORDER BY FIELD(status,2,4,1,3)

    SELECT * FROM user ORDER BY FIND_IN_SET(status,'2,4,1,3') 

    如上两种方法就可以实现按照指定字段的指定顺序进行排序了

    在Yii框架中实现如下

    User::find()  
        ->orderBy([  
            new \yii\db\Expression('FIELD (status,2,4,1,3'),  
        ])  
        ->all()
  • MySQL 入门:Case 语句很好用

    MySQL CASE 函数

    ❮ MySQL 函数

    实例

    遍历条件并在满足第一个条件时返回一个值:

    SELECT OrderID, Quantity,
    CASE
        WHEN Quantity > 30 THEN “The quantity is greater than 30”
        WHEN Quantity = 30 THEN “The quantity is 30”
        ELSE “The quantity is under 30”
    END
    FROM OrderDetails;

    亲自试一试 »


    定义和用法

    CASE 语句遍历条件并在满足第一个条件时返回一个值(如 IF-THEN-ELSE 语句)。 因此,一旦条件为真,它将停止读取并返回结果。

    如果没有条件为真,它将返回 ELSE 子句中的值。

    如果没有ELSE部分且没有条件为真,则返回NULL。

    语法

    CASE
        WHEN condition1 THEN result1
        WHEN condition2 THEN result2
        WHEN conditionN THEN resultN
        ELSE result
    END;

    参数值

    参数 描述
    condition1, condition2, …conditionN 必需。条件。 它们的评估顺序与列出的顺序相同
    result1, result2, …resultN 必需。条件为真时返回的值

    技术细节

    适用于: From MySQL 4.0

    更多实例

    以下 SQL 将按城市对客户进行排序。 但是,如果 City 为 NULL,则按 Country 排序:

    实例

    SELECT CustomerName, City, Country
    FROM Customers
    ORDER BY
    (CASE
        WHEN City IS NULL THEN Country
        ELSE City
    END);

    亲自试一试 »