**偶尔**将表达式转换为数据类型int的算术溢出错误

时间:2015-11-09 14:56:17

标签: sql sql-server sql-server-2012

我正在运行更新脚本来混淆数据,偶尔会遇到算术溢出错误消息,如标题中所示。正在更新的表有260k条记录,但更新脚本需要多次运行才能产生错误。虽然它非常罕见,但我不能依赖代码直到它被修复,因为它很难调试。

查看其他类似问题,通常可以通过在表格或计算中更改数据类型(例如从INTBIGINT来解决)。但是,我无法确定这可能需要的地方。我已将脚本缩减到下面,因为我已设法将其指向更新一列。

更新调用了一个函数,我在下面将其包含在内。我怀疑,由于错误的随机性,使用NEW_ID函数可能会导致它,但我还没有能够在多次运行该部分函数时重新创建错误。 NEW_ID功能无法在功能中使用,因此可以从视图中调用它,也包含在下面。

更新脚本:

UPDATE dbo.Addresses
SET HouseNumber = CASE WHEN LEN(HouseNumber) > 0 
                       THEN dbo.fn_GenerateRandomString (LEN(HouseNumber), 1, 1, 1) 
                       ELSE HouseNumber 
                  END

NEW_ID视图和随机字符串函数

CREATE VIEW dbo.vw_GetNewID 
AS 
SELECT NEWID() AS New_ID

CREATE FUNCTION dbo.fn_GenerateRandomString (
@stringLength int, 
@upperCaseBit bit, 
@lowerCaseBit bit,
@numberBit bit
)
RETURNS nvarchar(100)
AS
BEGIN
-- Sanitise string length values.
IF ISNULL(@stringLength, -1) < 0
SET @stringLength = 0

-- Generate a random string from the specified character sets.
DECLARE @string nvarchar(100) = ''
SELECT
@string += c2
FROM
(
    SELECT TOP (@stringLength) c2 FROM (
        SELECT c1 FROM
        (
            VALUES ('A'),('B'),('C')
        ) AS T1(c1)
        WHERE @upperCaseBit = 1
        UNION ALL
        SELECT c1 FROM
        (
            VALUES ('a'),('b'),('c')
        ) AS T1(c1)
        WHERE @lowerCaseBit = 1
        SELECT c1 FROM
        (
            VALUES ('0'),('1'),('2'),('3'),('4'),('5'),('6'),('7'),('8'),('9')
        ) AS T1(c1)
        WHERE @numberBit = 1
        ) 
    AS T2(c2)
    ORDER BY (SELECT ABS(CHECKSUM(New_ID)) from vw_GetNewID)
) AS T2

RETURN @string
END

地址表(用于测试):

CREATE TABLE dbo.Addresses(HouseNumber nchar(32) NULL)

INSERT Addresses(HouseNumber)
VALUES ('DSjkmf jkghjsh35hjk h2jkhj3h jhf'),
    ('SDjfksj3548 ksjk'),
    (NULL),
    (''),
    ('2a'),
    ('1234567890'),
    ('An2b')

注意:地址表中只有7k的行输入了值LEN(HouseNumber) > 0

1 个答案:

答案 0 :(得分:3)

基于字符串的代码中的算术溢出令人困惑。但是有一件事可能导致算术溢出。那是你的ORDER BY条款:

ORDER BY (SELECT ABS(CHECKSUM(New_ID)) from vw_GetNewID)

CHECKSUM()返回一个整数,其范围为-2,147,483,648到2,147,483,647。请注意,最小数字的绝对值是2,147,483,648,这就在范围之外。您可以验证SELECT ABS(CAST('-2147483648' as int))是否生成算术溢出错误。

您不需要checksum()。唉,你确实需要视图,因为这个逻辑在一个函数中,而NEWID()是副作用的。但是,您可以使用:

ORDER BY (SELECT New_ID from vw_GetNewID)

我怀疑你每隔百万左右而不是每40亿行看到这个行的原因是因为ORDER BY值作为排序过程的一部分被多次评估。最终,它将达到下限。

编辑:

如果你关心效率,使用字符串操作而不是表来执行此操作可能会更快。我可能会推荐这个版本的函数:

CREATE VIEW vw_rand AS SELECT rand() as rand;
GO
CREATE FUNCTION dbo.fn_GenerateRandomString (
    @stringLength int, 
    @upperCaseBit bit, 
    @lowerCaseBit bit,
    @numberBit bit
)
RETURNS nvarchar(100)
AS
BEGIN
    DECLARE @string NVARCHAR(255) = '';
-- Sanitise string length values.
    IF ISNULL(@stringLength, -1) < 0
        SET @stringLength = 0;
    DECLARE @lets VARCHAR(255) = '';
    IF (@upperCaseBit = 1) SET @lets = @lets + 'ABC';
    IF (@lowerCaseBit = 1) SET @lets = @lets + 'abc';
    IF (@numberBit = 1) SET @lets = @lets + '0123456789';

    DECLARE @len int = len(@lets);

    WHILE @stringLength > 0 BEGIN
        SELECT @string += SUBSTRING(@lets, 1 + CAST(rand * @len as INT), 1)
        FROM vw_rand;
        SET @stringLength = @stringLength - 1;
    END;
    RETURN @string
END;

作为备注:rand()被记录为不包括其范围的结尾,因此您不必担心它会正好返回1。

此外,此版本与您的版本略有不同,因为它可以多次拉出相同的字母(因此也可以处理更长的字符串)。我认为这实际上是一种好处。