Làm cách nào để bạn tạo một chuỗi ngẫu nhiên phù hợp với ID phiên trong PostgreSQL?


101

Tôi muốn tạo một chuỗi ngẫu nhiên để sử dụng trong xác minh phiên bằng PostgreSQL. Tôi biết tôi có thể nhận được một số ngẫu nhiên SELECT random(), vì vậy tôi đã thử SELECT md5(random()), nhưng điều đó không hiệu quả. Tôi có thể làm cái này như thế nào?


Một giải pháp khác có thể được tìm thấy tại đây stackoverflow.com/a/13675441/398670
Craig Ringer

7
Tôi đã chỉnh sửa tiêu đề để các câu trả lời hiện có vẫn có ý nghĩa hoàn toàn phù hợp và câu trả lời của Evan cũng mang những thứ hiện đại hơn một chút. Tôi không muốn khóa câu hỏi lâu đời này vì tranh chấp nội dung - vì vậy, hãy thực hiện bất kỳ chỉnh sửa bổ sung nào phù hợp với tất cả các câu trả lời.
Tim Post

1
Thật tuyệt, hãy xem liệu @gersh có thể làm rõ câu hỏi này không vì có sự bất đồng chính đáng với ý định ban đầu của anh ấy. Nếu ý định ban đầu của anh ấy là như tôi cho là vậy, thì nhiều câu trả lời trong số này cần được điều chỉnh, phản đối hoặc rút lại. Và, có lẽ một câu hỏi mới về việc tạo chuỗi cho mục đích thử nghiệm (hoặc tương tự) sẽ được đặt ra (khi random()không cần thiết). Nếu nó không phải là những gì tôi giả định, thì câu trả lời của tôi cần phải được cung cấp cho câu hỏi đã được tinh chỉnh.
Evan Carroll

5
@EvanCarroll - gersh được nhìn thấy lần cuối vào ngày 21 tháng 11 năm 2015.
BSMP

5
Đối với bất kỳ ai trả lời câu hỏi này trong năm> 2017, hãy xem câu trả lời của Evan stackoverflow.com/a/41608000/190234 vì nó sử dụng các phương pháp không có sẵn khi câu hỏi ban đầu được hỏi và trả lời.
Marcin Raczkowski

Câu trả lời:


83

Tôi muốn đề xuất giải pháp đơn giản này:

Đây là một hàm khá đơn giản trả về một chuỗi ngẫu nhiên có độ dài đã cho:

Create or replace function random_string(length integer) returns text as
$$
declare
  chars text[] := '{0,1,2,3,4,5,6,7,8,9,A,B,C,D,E,F,G,H,I,J,K,L,M,N,O,P,Q,R,S,T,U,V,W,X,Y,Z,a,b,c,d,e,f,g,h,i,j,k,l,m,n,o,p,q,r,s,t,u,v,w,x,y,z}';
  result text := '';
  i integer := 0;
begin
  if length < 0 then
    raise exception 'Given length cannot be less than 0';
  end if;
  for i in 1..length loop
    result := result || chars[1+random()*(array_length(chars, 1)-1)];
  end loop;
  return result;
end;
$$ language plpgsql;

Và cách sử dụng:

select random_string(15);

Ví dụ đầu ra:

select random_string(15) from generate_series(1,15);

  random_string
-----------------
 5emZKMYUB9C2vT6
 3i4JfnKraWduR0J
 R5xEfIZEllNynJR
 tMAxfql0iMWMIxM
 aPSYd7pDLcyibl2
 3fPDd54P5llb84Z
 VeywDb53oQfn9GZ
 BJGaXtfaIkN4NV8
 w1mvxzX33NTiBby
 knI1Opt4QDonHCJ
 P9KC5IBcLE0owBQ
 vvEEwc4qfV4VJLg
 ckpwwuG8YbMYQJi
 rFf6TchXTO3XsLs
 axdQvaLBitm6SDP
(15 rows)

6
Giải pháp này sử dụng các giá trị ở cuối mảng ký tự - 0 và z - thường xuyên hơn một nửa so với phần còn lại. Đối với một phân phối nhiều hơn, ngay cả các nhân vật, tôi đã thay thế chars[1+random()*(array_length(chars, 1)-1)]bằngchars[ceil(61 * random())]
PreciousBodilyFluids

random()được gọi lengthlần (giống như trong nhiều giải pháp khác). Có cách nào hiệu quả hơn để chọn từ 62 ký tự mỗi lần không? Làm thế nào để điều này thực hiện so với md5()?
ma11hew28

Tôi đã tìm thấy một giải pháp khác sử dụng ORDER BY random(). Cái nào nhanh hơn?
ma11hew28

1
Điều đáng chú ý là ngẫu nhiên có thể sử dụng erand48 không phải là CSPRNG, bạn có lẽ tốt hơn nên chỉ sử dụng pgcrypto.
Yaur

2
Câu trả lời tốt ngoại trừ rằng nó không sử dụng trình tạo số ngẫu nhiên an toàn và do đó không tốt cho ID phiên. Xem: stackoverflow.com/questions/9816114/…
sudo

239

Bạn có thể khắc phục nỗ lực ban đầu của mình như sau:

SELECT md5(random()::text);

Đơn giản hơn nhiều so với một số gợi ý khác. :-)


16
Lưu ý rằng điều này chỉ trả về các chuỗi trong "bảng chữ số hex" {0..9, a..f}. Có thể không đủ - tùy thuộc vào những gì bạn muốn làm với chúng.
Laryx Decidua

độ dài của chuỗi trả về là bao nhiêu? Có cách nào để làm cho nó trả về một chuỗi dài hơn không?
andrewrk

8
Khi được biểu diễn bằng hệ thập lục phân, độ dài của chuỗi MD5 luôn là 32 ký tự. Nếu bạn muốn có một chuỗi có độ dài 64, bạn có thể nối 2 MD5 chuỗi: SELECT concat(md5(random()::text), md5(random()::text)); Và nếu bạn muốn ở đâu đó ở giữa (50 ký tự chẳng hạn), bạn có thể tham gia một chuỗi con của rằng: SELECT substr(concat(md5(random()::text), md5(random()::text)), 0, 50);
Jimmie Tyrrell

2
Không phải là một giải pháp tốt cho id phiên, không có nhiều tính ngẫu nhiên. Câu trả lời cũng là 6 năm tuổi. Kiểm tra điều này để biết một phương pháp hoàn toàn khác bằng cách sử dụnggen_random_uuid() : nhanh hơn, ngẫu nhiên hơn, được lưu trữ hiệu quả hơn trong cơ sở dữ liệu.
Evan Carroll

@Evan nếu bạn muốn có thêm 'sự ngẫu nhiên' mà không có tiện ích mở rộng, bạn có thể SELECT md5(random()::text||random()::text);hoặcSELECT md5(random()::text||random()::text||random()::text);

31

Dựa trên giải pháp của Marcin, bạn có thể làm điều này để sử dụng một bảng chữ cái tùy ý (trong trường hợp này là tất cả 62 ký tự chữ và số ASCII):

SELECT array_to_string(array 
       ( 
              select substr('abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ0123456789', trunc(random() * 62)::integer + 1, 1)
              FROM   generate_series(1, 12)), '');

Chậm, không ngẫu nhiên hoặc hiệu quả để lưu trữ. Không phải là một giải pháp tốt cho id phiên, không có nhiều tính ngẫu nhiên. Câu trả lời cũng là 6 năm tuổi. Check out this for a totally different method using gen_random_uuid(): nhanh hơn, ngẫu nhiên hơn, được lưu trữ hiệu quả hơn trong cơ sở dữ liệu.
Evan Carroll

23

Bạn có thể nhận 128 bit ngẫu nhiên từ UUID. Đây là phương pháp để hoàn thành công việc trong PostgreSQL hiện đại.

CREATE EXTENSION pgcrypto;
SELECT gen_random_uuid();

           gen_random_uuid            
--------------------------------------
 202ed325-b8b1-477f-8494-02475973a28f

Cũng có thể đáng đọc các tài liệu về UUID

Loại dữ liệu uuid lưu trữ Số nhận dạng duy nhất phổ biến (UUID) theo định nghĩa của RFC 4122, ISO / IEC 9834-8: 2005 và các tiêu chuẩn liên quan. (Một số hệ thống gọi kiểu dữ liệu này là số nhận dạng duy nhất trên toàn cầu, hoặc GUID, thay vào đó.) Số nhận dạng này là một số lượng 128 bit được tạo bởi một thuật toán được chọn để làm cho rất ít khả năng rằng cùng một số nhận dạng sẽ được tạo bởi bất kỳ ai khác trong vũ trụ đã biết bằng cách sử dụng cùng một thuật toán. Do đó, đối với các hệ thống phân tán, các mã định danh này cung cấp sự đảm bảo về tính duy nhất tốt hơn so với các trình tạo chuỗi, chỉ là duy nhất trong một cơ sở dữ liệu duy nhất.

Mức độ hiếm khi xảy ra va chạm với UUID hay có thể đoán được? Giả sử chúng là ngẫu nhiên,

Khoảng 100 nghìn tỷ UUID phiên bản 4 sẽ cần được tạo để có 1 trong một tỷ cơ hội xảy ra một bản sao duy nhất ("va chạm"). Cơ hội xảy ra một vụ va chạm chỉ tăng lên 50% sau khi 261 UUID (2,3 x 10 ^ 18 hoặc 2,3 tạ) được tạo ra. Liên hệ những con số này với cơ sở dữ liệu và xem xét vấn đề liệu xác suất xảy ra xung đột UUID Phiên bản 4 là không đáng kể hay không, hãy xem xét một tệp chứa 2,3 nghìn tỷ UUID Phiên bản 4, với 50% khả năng chứa một xung đột UUID. Nó sẽ có kích thước 36 exabyte, giả sử không có dữ liệu hoặc chi phí nào khác, lớn hơn hàng nghìn lần so với cơ sở dữ liệu lớn nhất hiện đang tồn tại, có thứ tự là petabyte. Với tốc độ 1 tỷ UUID được tạo mỗi giây, sẽ mất 73 năm để tạo UUID cho tệp. Nó cũng sẽ yêu cầu khoảng 3. 6 triệu ổ cứng 10 terabyte hoặc hộp băng để lưu trữ nó, giả sử không có bản sao lưu hoặc dự phòng. Việc đọc tệp ở tốc độ truyền "disk-to-buffer" điển hình là 1 gigabit mỗi giây sẽ cần hơn 3000 năm đối với một bộ xử lý. Vì tỷ lệ lỗi đọc không thể khôi phục của ổ đĩa tốt nhất là 1 bit trên 1018 bit đọc, trong khi tệp sẽ chứa khoảng 1020 bit, chỉ cần đọc tệp một lần từ đầu đến cuối sẽ dẫn đến kết quả sai nhiều hơn khoảng 100 lần đọc UUID hơn các bản sao. Các lỗi lưu trữ, mạng, nguồn và các lỗi phần cứng và phần mềm khác chắc chắn sẽ thường xuyên hơn hàng nghìn lần so với các sự cố trùng lặp UUID. tốc độ truyền 1 gigabit mỗi giây sẽ cần hơn 3000 năm đối với một bộ xử lý. Vì tỷ lệ lỗi đọc không thể khôi phục của ổ đĩa tốt nhất là 1 bit trên 1018 bit đọc, trong khi tệp sẽ chứa khoảng 1020 bit, chỉ cần đọc tệp một lần từ đầu đến cuối sẽ dẫn đến kết quả sai nhiều hơn khoảng 100 lần đọc UUID hơn các bản sao. Các lỗi lưu trữ, mạng, nguồn và các lỗi phần cứng và phần mềm khác chắc chắn sẽ thường xuyên hơn hàng nghìn lần so với các sự cố trùng lặp UUID. tốc độ truyền 1 gigabit mỗi giây sẽ cần hơn 3000 năm đối với một bộ xử lý. Vì tỷ lệ lỗi đọc không thể khôi phục của ổ đĩa tốt nhất là 1 bit trên 1018 bit đọc, trong khi tệp sẽ chứa khoảng 1020 bit, chỉ cần đọc tệp một lần từ đầu đến cuối sẽ dẫn đến kết quả sai nhiều hơn khoảng 100 lần đọc UUID hơn các bản sao. Các lỗi lưu trữ, mạng, nguồn và các lỗi phần cứng và phần mềm khác chắc chắn sẽ thường xuyên hơn hàng nghìn lần so với các sự cố trùng lặp UUID.

nguồn: wikipedia

Tóm tắt,

  • UUID được chuẩn hóa.
  • gen_random_uuid()là 128 bit ngẫu nhiên được lưu trữ trong 128 bit (2 ** 128 kết hợp). 0-lãng phí.
  • random() chỉ tạo 52 bit ngẫu nhiên trong PostgreSQL (2 ** 52 kết hợp).
  • md5()được lưu trữ dưới dạng UUID là 128 bit, nhưng nó chỉ có thể ngẫu nhiên như đầu vào của nó ( 52 bit nếu sử dụngrandom() )
  • md5()được lưu trữ dưới dạng văn bản là 288 bit, nhưng nó chỉ có thể ngẫu nhiên như đầu vào của nó ( 52 bit nếu sử dụngrandom() ) - gấp đôi kích thước của UUID và một phần của độ ngẫu nhiên)
  • md5() như một hàm băm, có thể được tối ưu hóa đến mức nó không hoạt động hiệu quả.
  • UUID có hiệu quả cao để lưu trữ: PostgreSQL cung cấp một loại chính xác là 128 bit. Không giống như textvarchar, vv lưu trữ dưới dạng một varlenacó chi phí cho độ dài của chuỗi.
  • PostgreSQL UUID tiện lợi đi kèm với một số toán tử, cấu trúc và tính năng mặc định.

3
Một phần không chính xác: Một tạo đúng UUID ngẫu nhiên chỉ có 122 bit ngẫu nhiên từ 4 bit được sử dụng cho các phiên bản và 2 bit cho các biến thể: en.wikipedia.org/wiki/...
Olivier Grégoire

2
Nếu nguồn không thực hiện những gì được viết ở đó, thì nó không phải là UUID và không nên được PostgreSQL gọi như vậy.
Olivier Grégoire

16

Tôi đã chơi với PostgreSQL gần đây và tôi nghĩ rằng tôi đã tìm thấy một giải pháp tốt hơn một chút, chỉ sử dụng các phương thức PostgreSQL được tích hợp sẵn - không pl / pgsql. Hạn chế duy nhất là nó hiện chỉ tạo chuỗi UPCASE, hoặc số hoặc chuỗi chữ thường.

template1=> SELECT array_to_string(ARRAY(SELECT chr((65 + round(random() * 25)) :: integer) FROM generate_series(1,12)), '');
 array_to_string
-----------------
 TFBEGODDVTDM

template1=> SELECT array_to_string(ARRAY(SELECT chr((48 + round(random() * 9)) :: integer) FROM generate_series(1,12)), '');
 array_to_string
-----------------
 868778103681

Đối số thứ hai của generate_seriesphương thức chỉ ra độ dài của chuỗi.


8
Tôi thích điều này, nhưng khi tôi sử dụng nó, một câu lệnh CẬP NHẬT, tất cả các hàng được đặt thành cùng một mật khẩu ngẫu nhiên thay vì mật khẩu duy nhất. Tôi đã giải quyết vấn đề này bằng cách thêm ID khóa chính vào công thức. Tôi thêm nó vào giá trị ngẫu nhiên và trừ nó một lần nữa. Tính ngẫu nhiên không thay đổi, nhưng PostgreSQL bị lừa khi tính toán lại các giá trị cho mỗi hàng. Đây là một ví dụ, sử dụng tên khóa chính là "my_id": array_to_string(ARRAY(SELECT chr((65 + round((random()+my_id-my) * 25)) :: integer) FROM generate_series(1,8)), '')
Mark Stosberg

Giải pháp mà @MarkStosberg trình bày, hoạt động như anh ấy nói, nhưng không như tôi mong đợi; dữ liệu được tạo ra không khớp với mẫu giả (chỉ là chữ cái hoặc chỉ là chữ số). Tôi đã sửa bằng cách mô-đun số học kết quả ngẫu nhiên: array_to_string(ARRAY(SELECT chr((65 + round((random() * 25 + id) :: integer % 25 )) :: integer) FROM generate_series(1, 60)), '');
Nuno Rafael Figueedlyo

4
Không. Bạn đang trả lời câu hỏi 'Làm cách nào để tạo id phiên ngẫu nhiên ' chứ không phải 'Làm cách nào để tạo chuỗi ngẫu nhiên '. Bạn đã thay đổi ý nghĩa của từ quesiton (và tiêu đề), dựa trên hai từ trong mô tả. Bạn đang trả lời một câu hỏi khác. và tiếp tục lạm dụng quyền kiểm duyệt của bạn để thay đổi câu hỏi.
Marcin Raczkowski

13

Hãy sử dụng string_agg!

SELECT string_agg (substr('abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ0123456789', ceil (random() * 62)::integer, 1), '')
FROM   generate_series(1, 45);

Tôi cũng đang sử dụng cái này với MD5 để tạo UUID. Tôi chỉ muốn một giá trị ngẫu nhiên có nhiều bit hơn một random ()số nguyên.


Tôi cho rằng tôi chỉ có thể nối random()cho đến khi tôi nhận được số bit tôi muốn. Ồ tốt.
Andrew Wolfe

11

Mặc dù không hoạt động theo mặc định, bạn có thể kích hoạt một trong các tiện ích mở rộng cốt lõi:

CREATE EXTENSION IF NOT EXISTS pgcrypto;

Sau đó, câu lệnh của bạn trở thành một lệnh gọi đơn giản tới gen_salt () tạo ra một chuỗi ngẫu nhiên:

select gen_salt('md5') from generate_series(1,4);

 gen_salt
-----------
$1$M.QRlF4U
$1$cv7bNJDM
$1$av34779p
$1$ZQkrCXHD

Số đứng đầu là số nhận dạng băm. Một số thuật toán có sẵn, mỗi thuật toán có số nhận dạng riêng:

  • md5: $ 1 $
  • bf: $ 2a $ 06 $
  • des: không có định danh
  • xdes: _J9 ..

Thông tin thêm về tiện ích mở rộng:


BIÊN TẬP

Như được chỉ ra bởi Evan Carrol, kể từ v9.4, bạn có thể sử dụng gen_random_uuid()

http://www.postgresql.org/docs/9.4/static/pgcrypto.html


Các muối được tạo ra có vẻ quá tuần tự để thực sự ngẫu nhiên, phải không?
Le Droid

1
Bạn đang đề cập đến $1$? Đó là định danh kiểu băm (md5 == 1), phần còn lại là giá trị ngẫu nhiên.
Hang động Jefferey

Vâng, đó là cách giải thích sai lầm của tôi, cảm ơn vì sự chính xác.
Le Droid

6

Tôi không nghĩ rằng bạn đang tìm kiếm một chuỗi ngẫu nhiên. Những gì bạn cần để xác minh phiên là một chuỗi được đảm bảo là duy nhất. Bạn có lưu trữ thông tin xác minh phiên để kiểm tra không? Trong trường hợp đó, bạn cần chuỗi là duy nhất giữa các phiên. Tôi biết hai cách tiếp cận khá đơn giản:

  1. Sử dụng một trình tự. Tốt để sử dụng trên một cơ sở dữ liệu duy nhất.
  2. Sử dụng UUID. Rất độc đáo, rất tốt trên môi trường phân tán.

UUID được đảm bảo là duy nhất nhờ thuật toán của chúng để tạo; hiệu quả là rất khó xảy ra trường hợp bạn tạo ra hai số giống hệt nhau trên bất kỳ máy nào, bất kỳ lúc nào, (lưu ý rằng điều này mạnh hơn nhiều so với các chuỗi ngẫu nhiên, có chu kỳ nhỏ hơn nhiều so với UUID).

Bạn cần tải phần mở rộng uuid-ossp để sử dụng UUID. Sau khi cài đặt, hãy gọi bất kỳ hàm uuid_generate_vXXX () khả dụng nào trong các lệnh gọi SELECT, INSERT hoặc UPDATE của bạn. Kiểu uuid là một chữ số 16 byte, nhưng nó cũng có một biểu diễn chuỗi.


Đây có vẻ như là một lời khuyên tiềm ẩn nguy hiểm. Khi nói đến khóa phiên, bạn muốn tính duy nhất ngẫu nhiên đủ ngẫu nhiên về mặt mật mã để loại trừ bất kỳ cơ hội hợp lý nào để đoán nó. Các thuật toán được sử dụng bởi UUID đảm bảo tính duy nhất bằng cơ chế không ngẫu nhiên (chủ yếu), điều này gây ra mối đe dọa bảo mật.
jmar777

6
@ jmar777 Toàn bộ mục đích của UUID là chúng rất khó đoán và rất ngẫu nhiên. Ngoại trừ phiên bản v1, chúng có tính chu kỳ rất cao; v4 là ngẫu nhiên hoàn toàn 128 bit. Chúng đang được sử dụng trong mọi giao dịch ngân hàng trực tuyến mà bạn thực hiện. Nếu họ đủ tốt cho điều đó, họ đủ tốt cho hầu hết mọi thứ khác.
Patrick

1
Vâng, những gì bạn biết không. Tôi không nhận ra điều đó đã được giải quyết trong Phiên bản 4 . Cảm ơn vì đã sửa tôi!
jmar777

@Patrick Small nit, V4 UUID là 122 bit ngẫu nhiên, không phải 128.;)
Jesse

5

Tham số INTEGER xác định độ dài của chuỗi. Đảm bảo bao gồm tất cả 62 ký tự alphanum với xác suất như nhau (không giống như một số giải pháp khác trôi nổi trên Internet).

CREATE OR REPLACE FUNCTION random_string(INTEGER)
RETURNS TEXT AS
$BODY$
SELECT array_to_string(
    ARRAY (
        SELECT substring(
            '0123456789ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz'
            FROM (ceil(random()*62))::int FOR 1
        )
        FROM generate_series(1, $1)
    ), 
    ''
)
$BODY$
LANGUAGE sql VOLATILE;

Chậm, không ngẫu nhiên hoặc hiệu quả để lưu trữ. Không phải là một giải pháp tốt cho id phiên, không có nhiều tính ngẫu nhiên. Câu trả lời cũng là 6 năm tuổi. Check out this for a totally different method using gen_random_uuid(): nhanh hơn, ngẫu nhiên hơn, được lưu trữ hiệu quả hơn trong cơ sở dữ liệu.
Evan Carroll

3
@EvanCarroll: công bằng mà nói, đã gen_random_uuid()xuất hiện trong Phiên bản 9.4 theo như tôi có thể nói, được phát hành 2014-12-18, hơn một năm sau khi câu trả lời mà bạn đã phản đối. Nitpick bổ sung: câu trả lời chỉ là 3 tuổi rưỡi :-) Nhưng bạn nói đúng, bây giờ chúng tôi có gen_random_uuid(), đây là những gì nên được sử dụng. Do đó tôi sẽ ủng hộ câu trả lời của bạn.
Laryx Decidua

5

@Kavius ​​khuyến nghị sử dụng pgcrypto, nhưng thay vì gen_salt, thì gen_random_bytessao? Và làm thế nào về sha512thay vì md5?

create extension if not exists pgcrypto;
select digest(gen_random_bytes(1024), 'sha512');

Tài liệu:

F.25.5. Hàm dữ liệu ngẫu nhiên

gen_random_bytes (đếm số nguyên) trả về bytea

Trả về số byte ngẫu nhiên mạnh về mặt mật mã. Có thể trích xuất tối đa 1024 byte tại một thời điểm. Điều này là để tránh làm cạn kiệt nhóm tạo ngẫu nhiên.



2
select encode(decode(md5(random()::text), 'hex')||decode(md5(random()::text), 'hex'), 'base64')

Tôi sửa đổi nó để loại bỏ dấu gạch chéo và dấu cộng đôi khi xuất hiện trong kết quả và cũng để tạo kết quả chữ hoa, chọn upper (thay thế (thay thế (chuỗi con (mã hóa (giải mã) (md5 (random () :: text), 'hex ') || decode (md5 (random () :: text),' hex '),' base64 '), 0, 10),' / ',' A '),' + ',' Z '));
Seun Matt
Khi sử dụng trang web của chúng tôi, bạn xác nhận rằng bạn đã đọc và hiểu Chính sách cookieChính sách bảo mật của chúng tôi.
Licensed under cc by-sa 3.0 with attribution required.