sql的那些事(一)
一.概述
书写sql是我们程序猿在开发中必不可少的技能,优秀的sql语句,执行起来吊炸天,性能杠杠的。差劲的sql,不仅使查询效率降低,维护起来也十分不便。一切都是为了性能,一切都是为了业务,你觉得你的sql技能如何?所有的伟大来自于点滴的积累,不积跬步无以至千里,让sql性能飞起来吧!

二.sql初探
1.常见sql写法注意点
(1)字符类型建议采用varchar/nvarchar数据类型
- char
char是定长的,也就是当你输入的字符小于你指定的数目时,char(8),你输入的字符小于8时,它会再后面补空值。当你输入的字符大于指定的数时,它会截取超出的字符。
nvarchar(n)
包含 n 个字符的可变长度 Unicode 字符数据。n 的值必须介于 1 与 4,000 之间。字节的存储大小是所输入字符个数的两倍。所输入的数据字符长度可以为零。
varchar[(n)]
长度为 n 个字节的可变长度且非 Unicode 的字符数据。n 必须是一个介于 1 和 8,000 之间的数值。存储大小为输入数据的字节的实际长度,而不是 n 个字节。所输入的数据字符长度可以为零。
[1]—CHAR。CHAR存储定长数据很方便,CHAR字段上的索引效率级高,比如定义char(10),那么不论你存储的数据是否达到了10个字节,都要占去10个字节的空间。
[2]—VARCHAR。存储变长数据,但存储效率没有CHAR高。如果一个字段可能的值是不固定长度的,我们只知道它不可能超过10个字符,把它定义为 VARCHAR(10)是最合算的。VARCHAR类型的实际长度是它的值的实际长度+1。为什么“+1”呢?这一个字节用于保存实际使用了多大的长度。 从空间上考虑,用varchar合适;从效率上考虑,用char合适,关键是根据实际情况找到权衡点。
[3]—TEXT。text存储可变长度的非Unicode数据,最大长度为2^31-1(2,147,483,647)个字符。
[4]—NCHAR、NVARCHAR、NTEXT。这三种从名字上看比前面三种多了个“N”。它表示存储的是Unicode数据类型的字符。我们知道字符中,英文字符只需要一个字节存储就足够了,但汉字众多,需要两个字节存储,英文与汉字同时存在时容易造成混乱,Unicode字符集就是为了解决字符集这种不兼容的问题而产生的,它所有的字符都用两个字节表示,即英文字符也是用两个字节表示。nchar、nvarchar的长度是在1到4000之间。和char、varchar比较起来,nchar、nvarchar则最多存储4000个字符,不论是英文还是汉字;而char、varchar最多能存储8000个英文,4000个汉字。可以看出使用nchar、nvarchar数据类型时不用担心输入的字符是英文还是汉字,较为方便,但在存储英文时数量上有些损失。
所以一般来说,如果含有中文字符,用nchar/nvarchar,如果纯英文和数字,用char/varchar。
举例说明:
两字段分别有字段值:我和coffee
那么varchar字段占2×2+6=10个字节的存储空间,而nvarchar字段占8×2=16个字节的存储空间。
如字段值只是英文可选择varchar,而字段值存在较多的双字节(中文、韩文等)字符时用nvarchar
(2)金额货币建议采用money数据类型 (一般常用,最大四位小数)
(3)科学计数建议采用numeric数据类型-- (建议巨额资金交易用numeric)
(4)自增长标识建议采用bigint数据类型 (数据量一大,用int类型就装不下,那以后改造就麻烦了)
(5)时间类型建议采用为datetime数据类型
(6)禁止使用text、ntext、image老的数据类型(已过时)
(7)禁止使用xml数据类型、varchar(max)、nvarchar(max)
(8)禁止在数据库做复杂运算 (业务处理逻辑最好在代码层实现,不要让所有的代码逻辑存在于sql中,不便于后期的问题定位)
(9)禁止使用SELECT * (按需所取,查找自己所需要的列)
(10)禁止在索引列上使用函数或计算
例如:
我们查询注册时间在2015-11-11的店铺账号,找出它们进行活动奖励,我们如果不加注意,很可能写成这样:
select * from T_Account
where Convert(varchar(10,Regtime,121)='2015-11-11'
这样写的话,我们就无法命中索引字段Regtime,如果T_Account的数据量超大的时候,数据库查询分析器走表扫描,查询效率就降低了;要实现上面的查询结果,其实我们可以换一种写法:
select * from T_Account
where Regtime>='2015-11-11 00:00:00'
and Regtime<'2015-11-12 00:00:00'
(11)禁止使用游标
由于游标在处理大数据量的时候,占有的内存较大,效率低。可能造成其他的数据库查询堵塞的现象,除非是当你使用while循环,子查询,临时表,表变量,自建函数或其他方式都无法处理某种操作的时候,再考虑使用游标。
举例说明一下在实际运用中的一个游标处理:
--定义店铺ID
declare @accId int
set @accId=218424
--1.创建临时表并插入数据
select gsid,gid into #gidlist from T_Goods_Sku where accid=@accId and gid in (select gid from T_GoodsInfo where accid=@accId and isService=0 and IsExtend=1)
select gsId,gaVName into #gsidlist from T_Goods_Relation where gsid in (select gsid from T_Goods_Sku where accid=@accId and gid in (select gid from T_GoodsInfo where accid=@accId and isService=0 and IsExtend=1))
order by gsId
select a.gid gid,a.gsId gsId,b.gaVName gaVName into #tempgid from #gidlist a left join #gsidlist b
on a.gsId=b.gsId
drop table #gidlist
drop table #gsidlist
--2.开始事务
BEGIN TRANSACTION
--3.定义变量,累积事务执行过程中的错误
DECLARE @error INT
SET @error = 0
--4.声明游标
DECLARE goodsCursor CURSOR SCROLL
FOR
SELECT gid
,gsId
,gaVName
FROM #tempgid
--5.打开游标
OPEN goodsCursor
--6.声明游标提取数据所要存放的变量
DECLARE @gid INT
,@gsId INT
,@gaVName NVARCHAR(400)
,@gUnionKey NVARCHAR(400)
--7.定位游标到哪一行
FETCH First
FROM goodsCursor
INTO @gid
,@gsId
,@gaVName
--8.提取成功,对数据操作,进行下一条数据的提取操作
WHILE @@fetch_status = 0
BEGIN
SET @gUnionKey = ''
SELECT @gUnionKey = gUnionKey from T_GoodsInfo where accid=@accId and isService=0 and IsExtend=1 and gid=@gid
SELECT @gUnionKey=@gUnionKey+'|'+@gaVName
PRINT '-----start-------'
PRINT @gid
PRINT @gsId
PRINT @gaVName
PRINT @gUnionKey
--更新gUnionKey
update T_GoodsInfo
set gUnionKey=@gUnionKey
where accid=@accId and isService=0 and IsExtend=1 and gid=@gid
PRINT '-----end--------'
--移动游标
FETCH NEXT
FROM goodsCursor
INTO @gid
,@gsId
,@gaVName
END
--9.判读事务错误数,提交或回滚事务
IF @error <> 0 --有误
BEGIN
PRINT '回滚事务'
ROLLBACK TRANSACTION
END
ELSE
BEGIN
PRINT '提交事务'
COMMIT TRANSACTION
END
--10.关闭并删除游标,删除临时表
CLOSE goodsCursor
DEALLOCATE goodsCursor
drop table #tempgid
(12)禁止使用触发器
触发器在开发角度来讲,不知道具体什么时候执行,对于业务来讲不跟代码逻辑一样是显示的呈现,所以导致后期的维护比较困难,所以要处理触发器完成的服务,最好通过服务或者中间件去完成。
例如:
在微信收单的过程中,我们销售结账完成以后,需要通过短信向用户手机推送消费消息,这时候用触发器可能就是在结账以后,触发sql触发器,写入一条消息记录到短信表记录,走消息队列,将短信发送出去。
反之,我们采用中间件,就可以将结账以后的记录,发送给消息中间件EasyNetQ,中间件将记录异步写入记录,这样有问题的话,只用确认中间件消息接受和发送的问题。
(13)禁止在查询里指定索引
在sql里面指定索引索引是这样定义的:
SELECT 字段名表
FROM 表名表
WITH (INDEX(索引名))
WHERE 查询条件
如果在搜索的时候,指定了索引搜索,就会导致新建的索引无法生效,假如删除了指定的索引,会导致程序崩溃,所以建议不采用指定索引进行搜索。
(14)变量/参数/关联字段类型必须与字段类型一致
所谓的变量、参数、关联字段类型一致指的是,数据库中是什么类型,那么我们在成程序中传入参数的过程中,建议保持一直,避免在查询的时候,进行类型转换,在大批量数据处理过程中,可能影响性能。
图1类型:(程序中类型)
图二类型:(数据中类型)

图1、图2中字段类型保持一致。
(15)参数化查询
所谓的“参数化SQL”就是在应用程序设置SqlCommand.CommandText的时候使用参数(如:param1),然后通过SqlCommand.Parameters.Add来设置这些参数的值。这种做法会把你准备好的命令通过sp_executesql系统存储过程来执行,使用参数化,最直接的好处就是防止SQL注入。也就是说使用这种方法,主要是为了保证数据库的安全。禁止拼接sql语句。
另外参数化查询有利于数据库查询计划的复用,比如我们查询注册日期大于2015-12-12和注册日期大于2016-12-12不同的店铺记录,我们可能这样写:
select * from T_Account where Regtime>'2015-12-12'
select * from T_Account where Regtime>'2016-12-12'
上面两条语句,可以完成我们上面的查询结果集,但是sql查询计划会进行两次分析,导致查询计划不能够复用,如果用参数化查询,则可以复用查询计划:
declare @Regtime datetime;
set @Regtime='2015-12-12';
select * from T_Account where Regtime>@Regtime
set @Regtime='2016-12-12';
select * from T_Account where Regtime>@Regtime
只需要改变参数的值就可以了。
(16)限制JOIN个数
join表的次数不要过多,写代码的人,看到过多的join表记录都会懵逼,何况数据库了?会导致数据库执行错误的执行计划,影响性能。
(17)关闭影响的行计数信息返回
在sql语句中,可以设置Set NoAccount on,关闭查询受影响的行数,从而减少流量。
(18)除非必要SELECT语句都必须加上NOLOCK
这个是我们经常在开发中忽略的,加上nolock以后,在查询的时候,不锁表。不要只要自己爽,别人也要查询数据的,占这茅坑不拉shi是不好哦。这也是我们内部工程师的必修课提高的。
(19)使用UNION ALL替换UNION
使用union 的时候,必须满足两个表具体相同数目的列。
union all 包含全部的记录,union 包含去除重复后的结果集
Employees_China:
| E_ID| E_Name|
| :-------- | --------sql的那些事(一)的更多相关文章
- PL/SQL那点事-->SqlSession operation; SQL []; ORA-01722: 无效数字
PL/SQL那点事-->SqlSession operation;SQL []; ORA-01722: 无效数字 出现这种情况,在网上查了很多方法:大致主要有两种方法帮助我们解决这个问题: 1. ...
- PL/SQL那点事-->修改Oracle数据库里面的字段长度
在开发过程中,遇到有个问题:在Oracle数据库中,利用PL/SQL数据库开发工具来开发,某一字段的长度不能满足需求时候,采用下面的语法就行修改 alter table 表名 modify 字段名 长 ...
- 逻辑很重要:一句sql语句的事,自己却想了半天,绕了个大弯子
问题:系统升级后审核认证信息分别写入两个表,现在需要链接用户表和相应的新旧审核表获取字段值? 钻进胡同里:一直纠结于升级之后的会员信息从新表查,升级之前的数据从旧表查,纠结于根据时间戳分条件判断, 其 ...
- DRDS SQL 审计与分析——全面洞察 SQL 之利器
背景 数据库存储着系统的核心数据,其安全方面的问题在传统环境中已经成为泄漏和被篡改的重要根源.而在云端,数据库所面临的威胁被进一步的放大.因此,对云数据库的操作行为尤其是全量 SQL 执行记录的审计日 ...
- Mybatis系列全解(八):Mybatis的9大动态SQL标签你知道几个?提前致女神!
封面:洛小汐 作者:潘潘 2021年,仰望天空,脚踏实地. 这算是春节后首篇 Mybatis 文了~ 跨了个年感觉写了有半个世纪 ... 借着女神节 ヾ(◍°∇°◍)ノ゙ 提前祝男神女神们越靓越富越嗨 ...
- PreparedStatement接口
从实际来讲,Statement现在已经不使用了,他已经称为了历史. Statement执行关键性问题在于他需要一个完整 的字符串定义要使用的SQL语句,而PreparedStatement可以动态的设 ...
- MySQL死锁[转]
案例描述 在定时脚本运行过程中,发现当备份表格的sql语句与删除该表部分数据的sql语句同时运行时,mysql会检测出死锁,并打印出日志. 两个sql语句如下: (1)inse ...
- [经验分享] MySQL Innodb表导致死锁日志情况分析与归纳【转,纯学习】
在定时脚本运行过程中,发现当备份表格的sql语句与删除该表部分数据的sql语句同时运行时,mysql会检测出死锁,并打印出日志. 两个sql语句如下: (1)insert into backup_ta ...
- iOS 结构化数据访问
一.介绍 在存储大量数据时,除了最基本的打开文件,读取文件,存盘等这些没有明确管理机制的方式来存储数据外,iOS还提供了另外几种重要的数据存储方式.虽然这些方式最后还是将数据存储在文件中,但是iOS以 ...
随机推荐
- 论:开发者信仰之“天下IT是一家“(Java .NET篇)
比尔盖茨公认的IT界领军人物,打造了辉煌一时的PC时代. 2008年,史蒂夫鲍尔默接替了盖茨的工作,成为微软公司的总裁. 2013年他与微软做了最后的道别. 2013年以后,我才真正看到了微软的变化. ...
- 04.SQLServer性能优化之---读写分离&数据同步
汇总篇:http://www.cnblogs.com/dunitian/p/4822808.html#tsql 过段时间再继续写文章吧,本来准备把SQLServer一个系列写完的,最近状态很差很不好, ...
- 百度 flash html5自切换 多文件异步上传控件webuploader基本用法
双核浏览器下在chrome内核中使用uploadify总有302问题,也不知道如何修复,之所以喜欢360浏览器是因为帮客户控制渲染内核: 若页面需默认用极速核,增加标签:<meta name=& ...
- [原] KVM 虚拟化原理探究 —— 目录
KVM 虚拟化原理探究 -- 目录 标签(空格分隔): KVM KVM 虚拟化原理探究(1)- overview KVM 虚拟化原理探究(2)- QEMU启动过程 KVM 虚拟化原理探究(3)- CP ...
- Python学习
Python基础教程 网易云课堂-零基础入门学习Python
- 【干货分享】流程DEMO-加班与调休
流程名: 加班.调休 业务描述: 加班: 工作日加班可以申请调休,也可以申请支付加班费.原则上都应申请调休:周末加班原则上申请调休:法定节假日加班支付加班费. 加班申请以半小时为单位. 当月加班不能 ...
- BPM配置故事之案例7-公式计算
行政主管发来邮件.要求物资明细表增加"单价""总价"."单价"由其审批时填写,"总价"根据"单价"与 ...
- angular中使用ngResource模块构建RESTful架构
ngResource模块是angular专门为RESTful架构而设计的一个模块,它提供了'$resource'模块,$resource模块是基于$http的一个封装.下面来看看它的详细用法 1.引入 ...
- Xamarin.Android活动的生命周期
一.前言 用过Android手机的人一定会发现一种现象,当你把一个应用置于后台后,一段时间之后在打开就会发现应用重新打开了,但是之前的相关的数据却没有丢失.可以看出app的“生命”是掌握在系统手上的, ...
- 《深入理解Java虚拟机》内存分配策略
上节学习回顾 1.判断对象存活算法:引用计数法和可行性分析算法 2.垃圾收集算法:标记-清除算法.复制算法.标记-整理算法 3.垃圾收集器: Serial:新生代收集器,采用复制算法,单线程. Par ...

