SQL Server之深入理解STUFF - Jeffcky - 博客园

mikel阅读(641)

来源: SQL Server之深入理解STUFF – Jeffcky – 博客园

前言

最近项目无论查询报表还是其他数据都在和SQL Server数据库打交道,对于STUFF也有了解,但是发现当下一次再写SQL语句时我还得查看相关具体用法,说到底还是没有完全理解其原理,所以本节我们来谈谈STUFF,Jeff是在项目中哪里不熟悉,哪里不会或者哪里耗时比较多就会去深入理解和巩固即使是很基础的知识,直到完全不用浪费时间去查阅相关资料,这是我的出发点。

深入理解STUFF

STUFF字符串函数是将字符串插入到另一个字符串中。它会删除开始位置第一个字符串中的指定长度的字符,然后将第二个字符串插入到开始位置的第一个字符串中,语法如下。

STUFF(<character_expression>,<开始>,<长度>,<character_expression>)
<character_expression>参数是给定的字符串数据,可以是字符或二进制数据的常量,变量或列。<start>参数是一个整数值,指定开始删除和插入的位置,可以是BIGINT类型。如果<开始>或<长度>参数为负数,则返回NULL字符串。如果<start>参数比第一个<character_expression>长,则返回一个NULL字符串。 <length>参数可以是BIGINT类型,它是一个整数,指定要删除的字符数。如果<length>比第一个<character_expression>长,则删除发生到最后一个<character_expression>中的最后一个字符。

复制代码
DECLARE @FullName       VARCHAR(100)
DECLARE @Alias          VARCHAR(20)

SET @FullName = 'Jeffcky Wang'
SET @Alias = ' "Superman" '

SELECT STUFF(@FullName, CHARINDEX(' ', @FullName), 1, @Alias) AS [FullName]
复制代码

 

如上STUFF函数中的第一个参数我们给定的是@FullName,第二个是开始的位置,我们通过CHARINDEX函数找出@FullName以空格隔开的的位置返回,最后由@Alias来代替,结果如图所示。

DECLARE @Time VARCHAR(10)
SET @Time = '1030'

SELECT STUFF(@Time, 3, 0, ':') AS [HH:MM]

我们给定的字符串为@Time即1030,我们从第3个位置开始,删除长度为0,此时则在3前面插入冒号,结果如上图输出10:30。

DECLARE @CreditCardNumber  VARCHAR(20)
SET @CreditCardNumber = '370200199408103544'

SELECT STUFF(@CreditCardNumber, LEN(@CreditCardNumber) -3, 4,
       'XXXX') AS [Output]

如上我们将身份证通过STUFF将最后四位用XXXX代替。以上是STUFF最基础的用法。STUFF最常见的用途莫过于结合FOR XML PATH对返回JSON字符串的拼接。首先利用FOR XML PATH则返回XML格式的字符串,我们将FOR XML PATH添加到查询的末尾,此时允许我们将查询的结果作为XML元素输出,元素名称包含在PATH参数中。。

SELECT TOP 5 ',' + Name 
              FROM  Production.Product
              FOR XML PATH ('')

,Adjustable Race,All-Purpose Bike Stand,AWC Logo Cap,BB Ball Bearing,Bearing Ball

此时我们利用STUFF将上述利用FOR XML PATH生成的字符串中的前置逗号去掉,如下:

SELECT Name = STUFF((
            SELECT TOP 5 ',' + NAME
            FROM Production.Product
            FOR XML PATH('')
            ), 1, 1, '')

比如我们要查询各种产品中的产品列表名称,最后我们改造成如下:

复制代码
SELECT TOP 5 p2.ProductID, Name = STUFF((
            SELECT ',' + NAME
            FROM Production.Product AS p1
            WHERE p1.ProductID = p2.ProductID
            FOR XML PATH('')
            ), 1, 1, '') FROM Production.Product AS p2
GROUP BY p2.ProductID
复制代码

接下来我们利用STUFF结合FOR XML PATH来拼接JSON字符串,如下:

复制代码
DECLARE @content VARCHAR(MAX)

SET @content = (SELECT '['+ STUFF((SELECT TOP 5 ',{"ProductName": "' + ProductName + '","Price": "' + CONVERT(VARCHAR, Price) + '","Quantity": "' + CONVERT(VARCHAR, quantity) + '","Inserton": "' + CONVERT(VARCHAR, Inserton, 105) + '"}' FROM ProductList 
 FOR XML PATH('')), 1, 1,''
 ) 
 + ']'[ProductDetail])

PRINT @content
复制代码

结果如上正确输出JSON字符串,接下来我们将如上拼接换行再试试。

复制代码
DECLARE @content VARCHAR(MAX)

SET @content = ( SELECT '['
                        + STUFF(( SELECT TOP 5
                                            ',{"ProductName": "' + ProductName
                                            + '","Price": "'
                                            + CONVERT(VARCHAR, Price)
                                            + '","Quantity": "'
                                            + CONVERT(VARCHAR, quantity)
                                            + '","Inserton": "'
                                            + CONVERT(VARCHAR, Inserton, 105)
                                            + '"}'
                                  FROM      ProductList
                                FOR
                                  XML PATH('')
                                ), 1, 1, '') + ']' [ProductDetail]
               )

PRINT @content
复制代码

如上是利用SQL Prompt直接格式化换行,结果依然正确输出JSON字符串,我们再来手动换行试试。

复制代码
DECLARE @content VARCHAR(MAX)

SET @content = (SELECT 
'['+ STUFF((SELECT TOP 5 ',
{"ProductName": "' + ProductName 
+ '","Price": "' + CONVERT(VARCHAR, Price) 
+ '","Quantity": "' + CONVERT(VARCHAR, quantity) 
+ '","Inserton": "' + CONVERT(VARCHAR, Inserton, 105) 
+ '"}' FROM ProductList 
 FOR XML PATH('')), 1, 1,''
 ) 
 + ']'[ProductDetail])

PRINT @content
复制代码

结果输出如上我们不期望的字符串,主要是由FOR XML PATH造成的,比如我们利用FOR XML PATH进行如下查询:

SELECT  '    '
FOR     XML PATH('')

当我们利用FOR XML  PATH查询数据时,如果字符串中包含空格时会造成出现以如上错误的字符串来填充,所以此时我们为了消除这种错误格式,我们将上述继续添加参数。

SELECT  '    '
FOR     XML PATH(''),TYPE

此时我们将上述输出JSON字符串不错误的格式修改成如下即可:

复制代码
DECLARE @content VARCHAR(MAX)

SET @content = (SELECT 
'['+ STUFF((SELECT TOP 5 ',
{"ProductName": "' + ProductName 
+ '","Price": "' + CONVERT(VARCHAR, Price) 
+ '","Quantity": "' + CONVERT(VARCHAR, quantity) 
+ '","Inserton": "' + CONVERT(VARCHAR, Inserton, 105) + '"}' FROM ProductList 
 FOR XML PATH('') ,TYPE).value('.', 'NVARCHAR(MAX)'), 1, 1,''
 ) 
 + ']'[ProductDetail])

PRINT @content
复制代码

或者我们对上述输出的错误字符串进行替换,如下:

复制代码
select t.PK, 
    ltrim(rtrim(replace(
    (select ' ' + isnull(ti.Column1, '') + ' ' + isnull(ti.Column2, '')
     from yourTable ti 
     where ti.PK = t.PK
     for xml path (''))
     , '&#x20;', ''))) fruits
from yourTable t
group by t.PK;
复制代码

这里我们解决了利用STUFF有可能输出JSON字符串带有错误的字符串的问题,在利用STUFF输出JSON字符串时只要有一列数据包含NULL,那么返回的数据则为空,那么我们在对列数据通过ISNULL来进行判断,比如如下将输出NULL。

复制代码
DECLARE @content VARCHAR(MAX)

SET @content = (SELECT 
'['+ STUFF((SELECT TOP 5 ',
{"ProductName": "' + NULL 
+ '","Price": "' + CONVERT(VARCHAR, Price) 
+ '","Quantity": "' + CONVERT(VARCHAR, quantity) 
+ '","Inserton": "' + CONVERT(VARCHAR, Inserton, 105) + '"}' FROM ProductList 
 FOR XML PATH('') ,TYPE).value('.', 'NVARCHAR(MAX)'), 1, 1,''
 ) 
 + ']'[ProductDetail])

PRINT @content
复制代码

所以此时我们必须通过ISNULL来判断列数据是否为NULL,修改成如下形式:

复制代码
DECLARE @content VARCHAR(MAX)

SET @content = (SELECT 
'['+ STUFF((SELECT TOP 5 ',
{"ProductName": "' + ISNULL(ProductName,'') 
+ '","Price": "' + CONVERT(VARCHAR, Price) 
+ '","Quantity": "' + CONVERT(VARCHAR, quantity) 
+ '","Inserton": "' + CONVERT(VARCHAR, Inserton, 105) + '"}' FROM ProductList 
 FOR XML PATH('') ,TYPE).value('.', 'NVARCHAR(MAX)'), 1, 1,''
 ) 
 + ']'[ProductDetail])

PRINT @content
复制代码

SQL SERVER 如何把1列多行数据 合并成一列显示 - 岁月寒风 - 博客园

mikel阅读(545)

来源: SQL SERVER 如何把1列多行数据 合并成一列显示 – 岁月寒风 – 博客园

示例

修改前:1列多行数据

修改后:合并成一列

 

示例语句

1
2
3
4
5
6
7
8
9
10
11
select
类别,
    名称 = (
        stuff(
            (select ',' + 名称 from Table_A where 类别 = A.类别 for xml path('')),
            1,
            1,
            ''
        )
    )
from Table_A as group by 类别

 

把得到的内容以XML的形式显示

for xml path(”)

 

把拼接的内容的第一个“,”去掉

stuff((select ‘,’ + ep_name from ep_detail where ep_classes = a.ep_classes for xml path(”)), 1, 1, ”)

通过SQL语句提取存储过程中的内容_数据库_weixin_30462049的博客-CSDN博客

mikel阅读(563)

来源: 通过SQL语句提取存储过程中的内容_数据库_weixin_30462049的博客-CSDN博客

首先,列出服务器上所有数据库。

 

— 获取数据库列表
SELECT name FROM master.dbo.sysdatabases ORDER BY name

 

 

其次,这是一种让所有的用户从数据库中创建存储过程。

 

— 获取存储过程列表
— Type = ‘P’ –> 存储过程
— Category = 0 –> 用户创建的
SELECT * FROM sysobjects WHERE type = ‘ P ‘ AND category = 0 ORDER BY name

 

 

然后我们就可以检索查询与存储过程内容如下:

 

— 获取存储过程内容
— Name = Stored Procedure Name.
SELECT text
FROM syscomments
WHERE id = ( SELECT id FROM sysobjects WHERE name = ‘ 存储过程名称 ‘)

防止form表单多次提交_JavaScript_旧城以南的博客-CSDN博客

mikel阅读(952)

来源: 防止form表单多次提交_JavaScript_旧城以南的博客-CSDN博客

点击提交按钮两次。
点击刷新按钮。
使用浏览器后退按钮重复之前的操作,导致重复提交表单。
使用浏览器历史记录重复提交表单。
浏览器重复的HTTP请求。

用户提交表单时可能因为网速的原因,或者网页被恶意刷新,致使同一条记录重复插入到数据库中,这是一个比较棘手的问题。我们可以从客户端和服务器端一起着手,设法避免同一表单的重复提交。

1、js禁掉提交按钮。

表单提交后使用JavaScript使提交按钮disable。这种方法防止心急的用户多次点击按钮。但有个问题,如果客户端把JavaScript给禁止掉,这种方法就无效了

 

2、使用Post/Redirect/Get模式。

在提交后执行页面重定向,这就是所谓的Post-Redirect-Get (PRG)模式。简言之,当用户提交了表单后,你去执行一个客户端的重定向,转到提交成功信息页面。

这能避免用户按F5导致的重复提交,而其也不会出现浏览器表单重复提交的警告,也能消除按浏览器前进和后退按导致的同样问题。

 

3、在session中存放一个特殊标志。

在服务器端,生成一个唯一的标识符,将它存入session,同时将它写入表单的隐藏字段中,然后将表单页面发给浏览器,用户录入信息后点击提交,在服务器端,获取表单中隐藏字段的值,与session中的唯一标识符比较,相等说明是首次提交,就处理本次请求,然后将session中的唯一标识符移除;不相等说明是重复提交,就不再处理。

这使你的web应用有了更高级的XSRF保护。

请见如下代码:

view plain copy
<?php
session_start();
//根据当前SESSION生成随机数
$code = mt_rand(0,1000000);
$_SESSION[‘code’] = $code;
?>
在页面表单上将随机数作为隐藏值进行传递,代码如下:
<input type=”hidden” name=”originator” value=”<?=$code?>”>

在接收页面的PHP代码如下:

<?php
session_start();
if(isset($_POST[‘originator’])) {
if($_POST[‘originator’] == $_SESSION[‘code’]){
// 处理该表单的语句,省略
}else{
echo ‘请不要刷新本页面或重复提交表单!’;
}
}
?>

4.使用header函数转向

除了上面的方法之外,还有一个更简单的方法,那就是当用户提交表单,服务器端处理后立即转向其他的页面,代码如下所示。

if (isset($_POST[‘action’]) && $_POST[‘action’] == ‘submitted’) {

//处理数据,如插入数据后,立即转向到其他页面

header(‘location:submits_success.php’);

}

这样,即使用户使用刷新键,也不会导致表单的重复提交,因为已经转向新的页面,而这个页面脚本已经不理会任何提交的数据了。

5.表单过期的处理

在开发过程中,经常会出现表单出错而返回页面的时候填写的信息全部丢失的情况,为了支持页面回跳,可以通过以下两种方法实现。

1.使用header头设置缓存控制头Cache-control。

header(‘Cache-control: private, must-revalidate’); //支持页面回跳

2.使用session_cache_limiter方法。

session_cache_limiter(‘private, must-revalidate’); //要写在session_start方法之前

下面的代码片断可以防止用户填写表单的时候,单击“提交”按钮返回时,刚刚在表单上填写的内容不会被清除:

session_cache_limiter(‘nocache’);

session_cache_limiter(‘private’);

session_cache_limiter(‘public’);

session_start();

//以下是表单内容,这样在用户返回该表单时,已经填写的内容不会被清空

 

6、在数据库里添加约束。

在数据库里添加唯一约束或创建唯一索引,防止出现重复数据。这是最有效的防止重复提交数据的方法。
————————————————
版权声明:本文为CSDN博主「旧城以南」的原创文章,遵循CC 4.0 BY-SA版权协议,转载请附上原文出处链接及本声明。
原文链接:https://blog.csdn.net/qq_40072737/article/details/80088366

easyui datagrid列拖拽 - wuwenhai - 博客园

mikel阅读(965)

来源: easyui datagrid列拖拽 – wuwenhai – 博客园

还是那句话:“客户是上帝,他们怎么说我们这些程序猿只能尽自己所能来达到效果。”我觉着吧,他们太厉害了 看似没什么必要的功能,硬是能想出来逼着我这个前端还比较薄弱的人来实现。这次提出的要求 如题:datagrid列要能够拖拽。由于之前项目是使用easyui做的,可是datagrid它并没有提供列拖拽的功能。然后又去网上搜索了下,丫丫的竟然也没多少这方面实现的例子。我拍了拍头郁闷呐,又得自己做。好吧 自己做就自己做吧。说下思路:首先easyui 它有提供了拖拽的功能Draggable,那我们就可以想 拖拽标题头到另外的标题头上面我们就对datagrid的columns重新绑定一次 并刷新datagrid这个功能不就行了?没错思路可行,let’s do it!by 梨洛 。实现上代码来解释

复制代码
<script type="text/javascript">
    var cols = [{ field: 'testName', title: '<span class="dropitem">测试名</span>', align: 'center',width:120 },
               { field: 'testValue', title: '<span class="dropitem">测试值</span>', align: 'center', width: 120}];
    var url="/Test/Test1Data";
    $(document).ready(function () {
        init();
        drag();//绑定datagrid,绑定拖拽
    });
    function init() {
        $("#test").datagrid({
            url: url,
            type: "post",
            datatype: "json",
            width: 600,
            height: 280,
            loadMsg: "数据加载中,请稍后...",
            nowrap: true,
            rownumbers: false,
            pagination: true,
            singleSelect: true,
            columns: [cols],
            //bind数据成功重新设置拖动对象
            onLoadSuccess: function (data) {          
                drag();
            }
        });
    }
    //拖动drag和drop都是datagrid的头的datagrid-cell
    function drag() {
        $('.datagrid-header-inner .datagrid-cell').draggable({
            revert: true,
            proxy: 'clone'
        }).droppable({
            accept: '.datagrid-header-inner .datagrid-cell',
            onDrop: function (e, source) {
                //取得拖动源的html值
                var src = $(e.currentTarget.innerHTML).html();
                //取得拖动目标的html值
                var sou = $(source.innerHTML).html();
                var tempcolsrc;//拖动后源和目标列交换
                var tempcolsou;
                var tempcols=[];
                for (var i = 0; i < cols.length; i++) {
                    if (cols[i].title == sou) {
                        tempcolsrc = cols[i];//循环读一遍列把源和目标列都记下来
                    }
                    else if (cols[i].title == src) {
                        tempcolsou = cols[i];
                    }
                }
                for (var i = 0; i < cols.length; i++) {
                    //再循环一遍,把源和目标的列对换
                    var col = {
                        field: cols[i].field,
                        title: cols[i].title,
                        align: cols[i].align,
                        width: cols[i].width
                    };
                    if (cols[i].title == sou) {
                        col = tempcolsou;
                    }
                    else if (cols[i].title == src) {
                        col = tempcolsrc;
                    }
                     tempcols.push(col);  
                }
                 cols = tempcols;
                 //1秒后执行重绑定datagrid操作。可能是revert需要时间,这边如果没有做延时就直接重绑 就会出错。
                 //我目前的水平就想到这个笨办法,各位如果有好的想法建议可以提出来讨论下。
                timeid = setTimeout("init()", 1000);
            }
        });
    }
</script>

<div id="test"></div>
复制代码

上效果图:

原datagrid

拖动列

拖动后两列交换

再附上这几篇文章的源码

相关JQuery取元素什么的 请参看前两篇 jquery模拟enter按下
转载请注明出处http://www.cnblogs.com/wujie6166

thinkphp在php5.6以上版本显示"No input file specified" - 韦书文 - 博客园

mikel阅读(970)

来源: thinkphp在php5.6以上版本显示”No input file specified” – 韦书文 – 博客园

今天在练习php的时候,有些新功能只能兼容php7+以上的版本,我只好切换php的版本。后来发现再次打开thinkphp时,网址进入登入页面,但是却显示”No input file specified”,如下图所示:

上网查了一下才知道,PHP5.6以上的是fast_cgi模式,而在某些情况下,不能正确识别path_info所造成的错误,这个时候,你只需要更改.htaccess文件,如下图所示

只需要在index.php后面添加”?”即可,如下图所示

再次打开页面将正常显示

[C#/ASP.NET]List中Sort()、Find()、FindAll()、Exist()的使用方法 - zock - 博客园

mikel阅读(792)

来源: [C#/ASP.NET]List中Sort()、Find()、FindAll()、Exist()的使用方法 – zock – 博客园

[C#/ASP.NET]中List<T>真的非常好用。一个例子简单说明一下List<>中Sort()、Find()、FindAll()、Exist()的使用方法

简单介绍:

List<T>.Sort() → 排序T

List<T>.Find() → 找出一個T

List<T>.FindAll() →找出多個T

List<T>.Exist() →判斷T是否存在

示例代码:

页面文件GenericList.aspx

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
<%@ Page Language="C#" AutoEventWireup="true" CodeFile="GenericList.aspx.cs" Inherits="GenericList" %>
<!DOCTYPE html PUBLIC "-//W3C//DTD XHTML 1.0 Transitional//EN" "http://www.w3.org/TR/xhtml1/DTD/xhtml1-transitional.dtd">
<html xmlns="http://www.w3.org/1999/xhtml">
<head runat="server">
    <title>GenericList</title>
</head>
<body>
    <form id="form1" runat="server">
    <div>
        原始資料:
        <asp:GridView ID="GridView1" runat="server">
        </asp:GridView>
    </div>
    </form>
</body>
</html>

后台代码文件GenericList.aspx.cs

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
using System;
using System.Collections.Generic;
using System.Web;
using System.Web.UI;
using System.Web.UI.WebControls;
public partial class GenericList : System.Web.UI.Page
{
    protected void Page_Load(object sender, EventArgs e)
    {
        List<Person> lstPerson = new List<Person>();
        lstPerson.Add(new Person(1, "puma", 10));
        lstPerson.Add(new Person(2, "F6 Team", 20));
        lstPerson.Add(new Person(3, "ASP.NET", 30));
        lstPerson.Add(new Person(4, "Dotblogs", 40));
        //原始資料顯示在GridView上
        this.GridView1.DataSource = lstPerson;
        this.GridView1.DataBind();
        //List<T>.Find()
        //找出Name='puma'的Person
        Response.Write("找出Name='puma'的Person→ ");
        Response.Write(lstPerson.Find(delegate(Person p) { return p.Name == "puma"; }).ToString() + "<p>");
        //List<T>.FindAll()
        //找出Age>10的數目
        Response.Write("找出Age>10的數目→ ");
        Response.Write(lstPerson.FindAll(delegate(Person p) { return p.Age > 10; }).Count.ToString() + "<p>");
        //List<T>.Exists()
        //檢查Name='F6'是否存在
        Response.Write("檢查Name='F6'是否存在→ ");
        Response.Write(lstPerson.Exists(delegate(Person p) { return p.Name == "F6"; }).ToString() + "<p>");
        //List<T>.Sort()
        //依Name升冪排序
        Response.Write("<p>依Name升冪排序↑<br/>");
        lstPerson.Sort(delegate(Person p1, Person p2) { return Comparer<string>.Default.Compare(p1.Name, p2.Name); });
        foreach (Person p in lstPerson)
        {
            Response.Write(p.ToString() + "<br/>");
        }
        //List<T>.Sort()
        //依Name降冪排序
        Response.Write("<p>依Name降冪排序↓<br/>");
        lstPerson.Sort(delegate(Person p1, Person p2) { return Comparer<string>.Default.Compare(p2.Name, p1.Name); });
        foreach (Person p in lstPerson)
        {
            Response.Write(p.ToString() + "<br/>");
        }
    }
}
public class Person
{
    private int _ID;
    private string _Name;
    private int _Age;
    public Person(int ID, string Name, int Age)
    {
        _ID = ID;
        _Name = Name;
        _Age = Age;
    }
    public int ID
    {
        set { _ID = value; }
        get return _ID; }
    }
    public string Name
    {
        set { _Name = value; }
        get return _Name; }
    }
    public int Age
    {
        set { _Age = value; }
        get return _Age; }
    }
    public override string ToString()
    {
        return string.Format("ID:{0},Name:{1},Age:{2}", _ID, _Name, _Age);
    }
}

本文地址:http://www.cnblogs.com/atree/archive/2011/02/25/Asp-Net_List_Sort_Find_FindAll_Exist.html

(1条消息)解决:Uncaught TypeError: $ is not a function_JavaScript_weixin_41551266的博客-CSDN博客

mikel阅读(544)

来源: (1条消息)解决:Uncaught TypeError: $ is not a function_JavaScript_weixin_41551266的博客-CSDN博客

本来好好的,突然就出现的错误,不过这并不是什么难解决的错误;

我的问题是:在js文件里我定义了一个 var $;变量,只要把这个去掉就没问题了。

顺便总结一下:出现这种错误的解决方法:

1,先看看你的jq文件是否已经先导入了

一般来说,你的js文件需要位于最后

2.就是在js文件里找一下有没有 var $; 【呜呜我傻】

看完一些博客,大概明白,WordPress会先运行自己的脚本,然后释放$,这样就不会与其他库冲突。

所以一旦你定义了$;他就会默认使用你对$的定义,而不会管JQ的定义;

3. 下面是一些防止冲突的方法

JQuery(function ($) {
直接使用JQuery就不会又冲突问题,再在JQuery使用$,利用闭包函数的作用域规避冲突

 

参考链接:https://crunchify.com/how-to-fix-wordpress-uncaught-typeerror-is-not-a-function-jQuery-error/

https://stackoverflow.com/questions/12343714/typeerror-is-not-a-function-when-calling-jQuery-function
————————————————
版权声明:本文为CSDN博主「weixin_41551266」的原创文章,遵循 CC 4.0 BY-SA 版权协议,转载请附上原文出处链接及本声明。
原文链接:https://blog.csdn.net/weixin_41551266/article/details/90577776

Redis缓存设计与性能优化 - 白露非霜 - 博客园

mikel阅读(790)

来源: Redis缓存设计与性能优化 – 白露非霜 – 博客园

Redis我们一般是用作缓存,扛并发;或者用于某些特定的业务场景,比如前面说到redis各种数据类型的使用场景以及redis的哨兵和集群模式。

这里主要整理了下redis用作缓存,存在的一些问题,以及改善方案。

 

简单的流程就像这个样子,一般请先到缓存区获取,如果缓存没有再到后端的数据库去查询。

1.缓存穿透

缓存穿透是指,是指查询一个根本不存在数据,这样缓存层里面没有,就会去访问后面的存储层了。如果有大量的这种恶意请求过来,都打向后面的存储层。显然我们的存储层是扛不住这样的压力。这样缓存就失去了保护后面存储的意义了。

解决方案:

1.缓存空对象

对于缓存穿透,可以采用缓存空对象,第一次进来缓存和DB都没有,就存个空对象到缓存里面。但是如果大批量的恶意请求过来,这样做就会导致缓存的key暴增,显然不是一个很好的方案。

2.布隆过滤器

对于不存在的数据布隆过滤器一般都能够过滤掉,不让请求再往后端发送。当布隆过滤器说某个值存在时,这个值可能不存在;但是它说不存在时,那就肯定不存在。布隆过滤器是一个大型的位数组和几个不一样的无偏 hash 函数。所谓无偏就是能够把元素的hash值算得比较均匀。向布隆过滤器中添加 key 时,会使用多个hash 函数对key进行hash分别算得一个整数索引值然后对位数组长度进行取模运算得到一个位置,每个hash函数都会算得一个不同的位置。再把位数组的这几个位置都置为 1 就 完成了 add 操作。

向布隆过滤器询问 key 是否存在时,跟 add 一样,也会把 hash 的几个位置都算出来,看看位数组中这几个位置是否都为1,只要有一个位为0,那么说明布隆过滤器中这个key肯定不存在。但是都是 1,这并不能说明这个key就一定存在,只是极有可能存在,因为这些位被置为1可能是因为其它的key存在所致。

 

guvua包布隆过滤器的使用,导包

 

        <dependency>
            <groupId>com.google.guava</groupId>
            <artifactId>guava</artifactId>
        </dependency>

 

伪代码:

复制代码
    public void bloomFilterTest() {
        BloomFilter<CharSequence> bloomFilter = BloomFilter.create(
                Funnels.stringFunnel(Charset.forName("UTF-8")),
                1000, //期望存入的数据个数
                0.001);//误差率
        //添加到布隆过滤器
        String[] keys = new String[1000];
        for (String key: keys) {
            bloomFilter.put(key);
        }

        String key = "key";
        boolean exist = bloomFilter.mightContain(key);
        if (!exist) {
            return;
        }
        //todo 存在才去缓存获取
    }
复制代码

可以看到这个类里面有很多的hash算法:com.google.common.hash.Hashing

redisson也有布隆过滤器的实现。

 

 

2.缓存失效

由于大批量的key同时失效,导致,大量的请求同时打向数据库,造成数据库压力过大,甚至直接挂掉。我们在批量写入缓存的时候,设置超时时间,可以是一个固定时间+随机时间方式来生成,这样就可以错开失效时间。

3.缓存雪崩

缓存雪崩是指缓存层挂掉之后,所有请求都打向数据库,数据库扛不住,也可能挂掉,就导致对应的服务也挂掉,也会影响上游的调用服务。这样的级联问题。就像雪崩最开始一小片,然后越来越大,导致整个服务崩溃。

解决方案:

1.保证缓存层的高可用性,比如redis哨兵或者redis集群。

2.各依赖服务之间做限流,熔断,降级等,比如Hystri,阿里的sentinel

4.缓存一致性

引入缓存之后,随之而来的问题就是当DB数据更新时,缓存中的数据就会与db数据不一致。所以数据修改时是先更新缓存还是先更新DB?

如果先更新缓存,然后更新DB失败,那么下一个请求过来读取的缓存数据不是最新的。而我们实际上最终数据肯定都是以DB为准的。

先更新db 在更新缓存,这是在更新DB的时候来的请求读取的数据也是不是最新的

淘汰缓存——更新DB——重新刷进缓存,在更新db是来的请求在缓存没有数据,就会去请求DB,如果并发 可能操作多各请求去写DB,那么就需要加锁了

加锁——淘汰缓存——更新DB——重新刷进缓存,这样相对而言就比较保险了

5.bigkey问题

Bigkey是什么?在redis中,一个字符串最大512MB;hash,list,set,zset可以存储2^31 – 1 个元素。

一般来说字符串超过10kb,其他的几种元素个数不要超过5000个。

可以使用src/redis-cli –bigkeys 来查看bigkey,我这里设置了一个30多K的字符串,看下扫描结果,扫除了一个字符串类型的bigkey,4084字节。

 

 

Bigkey有哪些危害。一是删除时阻塞其他请求,比如一个bigkey,平时都没什么,但是设置了过期时间,到期了删除时,可能就会阻塞其他请求,4.0之后可以开启lazyfree-lazy- expire yes来异步删除;二是造成网络拥堵,比如一个key数据量达到1MB,假设并发量1000,这个时候获取它就会产生1000MB的流量,千兆网卡,峰值的速率也才128MB/S,并不是扛不住并发,而是会占用大量网络带宽。

对于很大list,set这些,我们可以将数据拆分,生成一个系列的的key去存放数据。如果是redis集群这些key自然就可以分到不同的小主从上面去,如果是单机,那么可以自己实现一个路由算法,来如何获取这一系列key中的某一个。

6. 客户端使用

1.避免多个服务使用一个redis实例,如果实在有,可以看下将业务拆分,把这些公共数据服务化。

2.使用连接池,控制有效连接,同时也提高效率。连接池重要参数设置:

1 maxActive 资源池中最大连接数 默认值8

2 maxIdle 资源池允许最大空闲 的连接数 默认值8

3 minIdle 资源池确保最少空闲 的连接数 默认值0

4 blockWhenExhausted 当资源池用尽后,调用者是否要等待。只有当为true时,下面的maxWaitMillis才会生效,默认值true 建议使用默认值

5 maxWaitMillis 当资源池连接用尽后,调用者的最大等待时间(单位为毫秒) -1:表示永不超时 不建议使用默认值

6 testOnBorrow 向资源池借用连接时是否做连接有效性检测(ping),无效连接会被移除 默认值false 业务量很大时候建议 设置为false(多一次 ping的开销)。

7 testOnReturn 向资源池归还连接时是否做连接有效性检测(ping),无效连接会被移除 默认值false 业务量很大时候建议 设置为false(多一次 ping的开销)。

8 jmxEnabled 是否开启jmx监控,可用于监控 默认值true 建议开启,但应用本身也要开启

前面三个参数相对而言更重要,单独拎出来再说下:

最大连接数maxActive:

可以从业务希望的并发量,客户端执行时间,redis资源设置(应用个数(集群部署多少个实例) * maxActive <= maxclients(redis最大连接数,redis配置中设置的)),等因素考虑。

比如一次客户端执行时间2ms,那么一个连接的QPS就是500,业务期望的QPS是3000,那么理论上连接池大小3000/500=60个,实际上考虑其他影响,一般设置比理论值稍微大点。但这个值不是越大越好,一方面连接太多占用客户端和服务端资源,另一方面对    于Redis这种高 QPS的服务器,一个大命令的阻塞即使设置再大资源池仍然会无济于事。

最大空闲连接数maxIdle:

maxIdle实际上才是业务需要的最大连接数,空闲的连接造好放在那儿,进来一个请求就可以直接拿来用了。maxActive是为了给出总量,所以maxIdle不要设置过小,否则会有当空闲连接不够,就会创建新的连接,又会有新的开销,最佳就是maxActive =       maxIdle。这样就避免连接池伸缩带来的性能干扰。但是如果并发量不大或者maxActive设置过高,会导致不必要的连接资源浪费。一般推荐maxIdle可以设置为按上面的业务期望QPS计算出来的理论连接数,maxActive可以再放大一些。

最小空闲连接数minIdle:

至少保持多少空闲连接,在使用连接的过程中,如果连接数超过了minIdle,那么继续建立连接,如果超过了 maxIdle,当超过的连接执行完业务后会慢慢被移出连接池释放掉。

3.缓存预热

比如说上线一个抢购活动,肯定到点开始就会有很多人来请求了,这个时候就可以提前做数据的预热,既可以把连接池初始化好,也可以把数据放好。